Path: blob/master/drivers/gpu/drm/bridge/microchip-lvds.c
26493 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries3*4* Author: Manikandan Muralidharan <[email protected]>5* Author: Dharma Balasubiramani <[email protected]>6*7*/89#include <linux/clk.h>10#include <linux/component.h>11#include <linux/delay.h>12#include <linux/jiffies.h>13#include <linux/mfd/syscon.h>14#include <linux/of_graph.h>15#include <linux/pinctrl/devinfo.h>16#include <linux/phy/phy.h>17#include <linux/platform_device.h>18#include <linux/pm_runtime.h>19#include <linux/regmap.h>20#include <linux/reset.h>2122#include <drm/drm_atomic_helper.h>23#include <drm/drm_bridge.h>24#include <drm/drm_of.h>25#include <drm/drm_panel.h>26#include <drm/drm_print.h>27#include <drm/drm_probe_helper.h>28#include <drm/drm_simple_kms_helper.h>2930#define LVDS_POLL_TIMEOUT_MS 10003132/* LVDSC register offsets */33#define LVDSC_CR 0x0034#define LVDSC_CFGR 0x0435#define LVDSC_SR 0x0C36#define LVDSC_WPMR 0xE43738/* Bitfields in LVDSC_CR (Control Register) */39#define LVDSC_CR_SER_EN BIT(0)4041/* Bitfields in LVDSC_CFGR (Configuration Register) */42#define LVDSC_CFGR_PIXSIZE_24BITS 043#define LVDSC_CFGR_DEN_POL_HIGH 044#define LVDSC_CFGR_DC_UNBALANCED 045#define LVDSC_CFGR_MAPPING_JEIDA BIT(6)4647/*Bitfields in LVDSC_SR */48#define LVDSC_SR_CS BIT(0)4950/* Bitfields in LVDSC_WPMR (Write Protection Mode Register) */51#define LVDSC_WPMR_WPKEY_MASK GENMASK(31, 8)52#define LVDSC_WPMR_WPKEY_PSSWD 0x4C56445354struct mchp_lvds {55struct device *dev;56void __iomem *regs;57struct clk *pclk;58struct drm_panel *panel;59struct drm_bridge bridge;60struct drm_bridge *panel_bridge;61};6263static inline struct mchp_lvds *bridge_to_lvds(struct drm_bridge *bridge)64{65return container_of(bridge, struct mchp_lvds, bridge);66}6768static inline u32 lvds_readl(struct mchp_lvds *lvds, u32 offset)69{70return readl_relaxed(lvds->regs + offset);71}7273static inline void lvds_writel(struct mchp_lvds *lvds, u32 offset, u32 val)74{75writel_relaxed(val, lvds->regs + offset);76}7778static void lvds_serialiser_on(struct mchp_lvds *lvds)79{80unsigned long timeout = jiffies + msecs_to_jiffies(LVDS_POLL_TIMEOUT_MS);8182/* The LVDSC registers can only be written if WPEN is cleared */83lvds_writel(lvds, LVDSC_WPMR, (LVDSC_WPMR_WPKEY_PSSWD &84LVDSC_WPMR_WPKEY_MASK));8586/* Wait for the status of configuration registers to be changed */87while (lvds_readl(lvds, LVDSC_SR) & LVDSC_SR_CS) {88if (time_after(jiffies, timeout)) {89dev_err(lvds->dev, "%s: timeout error\n", __func__);90return;91}92usleep_range(1000, 2000);93}9495/* Configure the LVDSC */96lvds_writel(lvds, LVDSC_CFGR, (LVDSC_CFGR_MAPPING_JEIDA |97LVDSC_CFGR_DC_UNBALANCED |98LVDSC_CFGR_DEN_POL_HIGH |99LVDSC_CFGR_PIXSIZE_24BITS));100101/* Enable the LVDS serializer */102lvds_writel(lvds, LVDSC_CR, LVDSC_CR_SER_EN);103}104105static int mchp_lvds_attach(struct drm_bridge *bridge,106struct drm_encoder *encoder,107enum drm_bridge_attach_flags flags)108{109struct mchp_lvds *lvds = bridge_to_lvds(bridge);110111return drm_bridge_attach(encoder, lvds->panel_bridge,112bridge, flags);113}114115static void mchp_lvds_enable(struct drm_bridge *bridge)116{117struct mchp_lvds *lvds = bridge_to_lvds(bridge);118int ret;119120ret = clk_prepare_enable(lvds->pclk);121if (ret < 0) {122dev_err(lvds->dev, "failed to enable lvds pclk %d\n", ret);123return;124}125126ret = pm_runtime_get_sync(lvds->dev);127if (ret < 0) {128dev_err(lvds->dev, "failed to get pm runtime: %d\n", ret);129return;130}131132lvds_serialiser_on(lvds);133}134135static void mchp_lvds_disable(struct drm_bridge *bridge)136{137struct mchp_lvds *lvds = bridge_to_lvds(bridge);138139pm_runtime_put(lvds->dev);140clk_disable_unprepare(lvds->pclk);141}142143static const struct drm_bridge_funcs mchp_lvds_bridge_funcs = {144.attach = mchp_lvds_attach,145.enable = mchp_lvds_enable,146.disable = mchp_lvds_disable,147};148149static int mchp_lvds_probe(struct platform_device *pdev)150{151struct device *dev = &pdev->dev;152struct mchp_lvds *lvds;153struct device_node *port;154int ret;155156if (!dev->of_node)157return -ENODEV;158159lvds = devm_drm_bridge_alloc(&pdev->dev, struct mchp_lvds, bridge,160&mchp_lvds_bridge_funcs);161if (IS_ERR(lvds))162return PTR_ERR(lvds);163164lvds->dev = dev;165166lvds->regs = devm_platform_ioremap_resource(pdev, 0);167if (IS_ERR(lvds->regs))168return PTR_ERR(lvds->regs);169170lvds->pclk = devm_clk_get(lvds->dev, "pclk");171if (IS_ERR(lvds->pclk))172return dev_err_probe(lvds->dev, PTR_ERR(lvds->pclk),173"could not get pclk_lvds\n");174175port = of_graph_get_remote_node(dev->of_node, 1, 0);176if (!port) {177dev_err(dev,178"can't find port point, please init lvds panel port!\n");179return -ENODEV;180}181182lvds->panel = of_drm_find_panel(port);183of_node_put(port);184185if (IS_ERR(lvds->panel))186return -EPROBE_DEFER;187188lvds->panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0);189190if (IS_ERR(lvds->panel_bridge))191return PTR_ERR(lvds->panel_bridge);192193lvds->bridge.of_node = dev->of_node;194lvds->bridge.type = DRM_MODE_CONNECTOR_LVDS;195196dev_set_drvdata(dev, lvds);197ret = devm_pm_runtime_enable(dev);198if (ret < 0) {199dev_err(lvds->dev, "failed to enable pm runtime: %d\n", ret);200return ret;201}202203drm_bridge_add(&lvds->bridge);204205return 0;206}207208static const struct of_device_id mchp_lvds_dt_ids[] = {209{210.compatible = "microchip,sam9x75-lvds",211},212{},213};214MODULE_DEVICE_TABLE(of, mchp_lvds_dt_ids);215216static struct platform_driver mchp_lvds_driver = {217.probe = mchp_lvds_probe,218.driver = {219.name = "microchip-lvds",220.of_match_table = mchp_lvds_dt_ids,221},222};223module_platform_driver(mchp_lvds_driver);224225MODULE_AUTHOR("Manikandan Muralidharan <[email protected]>");226MODULE_AUTHOR("Dharma Balasubiramani <[email protected]>");227MODULE_DESCRIPTION("Low Voltage Differential Signaling Controller Driver");228MODULE_LICENSE("GPL");229230231