Hi Neil, On 05/02/24 4:41 pm, neil.armstrong@xxxxxxxxxx wrote: > EXTERNAL EMAIL: Do not click links or open attachments unless you know > the content is safe > > Hi, > > On 05/02/2024 12:06, Dharma Balasubiramani wrote: >> Add a new LVDS controller driver for sam9x7 which does the following: >> - Prepares and enables the LVDS Peripheral clock >> - Defines its connector type as DRM_MODE_CONNECTOR_LVDS and adds itself >> to the global bridge list. >> - Identifies its output endpoint as panel and adds it to the encoder >> display pipeline >> - Enables the LVDS serializer >> >> Signed-off-by: Manikandan Muralidharan <manikandan.m@xxxxxxxxxxxxx> >> Signed-off-by: Dharma Balasubiramani <dharma.b@xxxxxxxxxxxxx> >> --- >> Changelog >> v1 -> v2 >> - Drop 'res' variable and combine two lines into one. >> - Handle deferred probe properly, use dev_err_probe(). >> - Don't print anything on deferred probe. Dropped print. >> - Remove the MODULE_ALIAS and add MODULE_DEVICE_TABLE(). >> - symbol 'mchp_lvds_driver' was not declared. It should be static. >> --- >> drivers/gpu/drm/bridge/Kconfig | 7 + >> drivers/gpu/drm/bridge/Makefile | 1 + >> drivers/gpu/drm/bridge/microchip-lvds.c | 246 ++++++++++++++++++++++++ >> 3 files changed, 254 insertions(+) >> create mode 100644 drivers/gpu/drm/bridge/microchip-lvds.c >> >> diff --git a/drivers/gpu/drm/bridge/Kconfig >> b/drivers/gpu/drm/bridge/Kconfig >> index 3e6a4e2044c0..200afb36e421 100644 >> --- a/drivers/gpu/drm/bridge/Kconfig >> +++ b/drivers/gpu/drm/bridge/Kconfig >> @@ -173,6 +173,13 @@ config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW >> to DP++. This is used with the i.MX6 imx-ldb >> driver. You are likely to say N here. >> >> +config DRM_MICROCHIP_LVDS_SERIALIZER >> + tristate "Microchip LVDS serailzer support" > ---------------------------------/\ > > Should be serializer Noted, Thanks. > >> + depends on OF >> + depends on DRM_ATMEL_HLCDC >> + help >> + Support for Microchip's LVDS serializer. >> + >> config DRM_NWL_MIPI_DSI >> tristate "Northwest Logic MIPI DSI Host controller" >> depends on DRM >> diff --git a/drivers/gpu/drm/bridge/Makefile >> b/drivers/gpu/drm/bridge/Makefile >> index 2b892b7ed59e..e3804e93d324 100644 >> --- a/drivers/gpu/drm/bridge/Makefile >> +++ b/drivers/gpu/drm/bridge/Makefile >> @@ -11,6 +11,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o >> obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o >> obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o >> obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += >> megachips-stdpxxxx-ge-b850v3-fw.o >> +obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.o >> obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o >> obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o >> obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o >> diff --git a/drivers/gpu/drm/bridge/microchip-lvds.c >> b/drivers/gpu/drm/bridge/microchip-lvds.c >> new file mode 100644 >> index 000000000000..508321ad0f66 >> --- /dev/null >> +++ b/drivers/gpu/drm/bridge/microchip-lvds.c >> @@ -0,0 +1,246 @@ >> +// SPDX-License-Identifier: GPL-2.0-only >> +/* >> + * Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries >> + * >> + * Author: Manikandan Muralidharan <manikandan.m@xxxxxxxxxxxxx> >> + * Author: Dharma Balasubiramani <dharma.b@xxxxxxxxxxxxx> >> + * >> + */ >> + >> +#include <linux/clk.h> >> +#include <linux/component.h> >> +#include <linux/delay.h> >> +#include <linux/jiffies.h> >> +#include <linux/mfd/syscon.h> >> +#include <linux/of_graph.h> >> +#include <linux/pinctrl/devinfo.h> >> +#include <linux/phy/phy.h> >> +#include <linux/platform_device.h> >> +#include <linux/pm_runtime.h> >> +#include <linux/regmap.h> >> +#include <linux/reset.h> >> + >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_bridge.h> >> +#include <drm/drm_of.h> >> +#include <drm/drm_panel.h> >> +#include <drm/drm_print.h> >> +#include <drm/drm_probe_helper.h> >> +#include <drm/drm_simple_kms_helper.h> >> + >> +#define LVDS_POLL_TIMEOUT_MS 1000 >> + >> +/* LVDSC register offsets */ >> +#define LVDSC_CR 0x00 >> +#define LVDSC_CFGR 0x04 >> +#define LVDSC_SR 0x0C >> +#define LVDSC_WPMR 0xE4 >> + >> +/* Bitfields in LVDSC_CR (Control Register) */ >> +#define LVDSC_CR_SER_EN BIT(0) >> + >> +/* Bitfields in LVDSC_CFGR (Configuration Register) */ >> +#define LVDSC_CFGR_PIXSIZE_24BITS 0 >> +#define LVDSC_CFGR_DEN_POL_HIGH 0 >> +#define LVDSC_CFGR_DC_UNBALANCED 0 >> +#define LVDSC_CFGR_MAPPING_JEIDA BIT(6) >> + >> +/*Bitfields in LVDSC_SR */ >> +#define LVDSC_SR_CS BIT(0) >> + >> +/* Bitfields in LVDSC_WPMR (Write Protection Mode Register) */ >> +#define LVDSC_WPMR_WPKEY_MASK GENMASK(31, 8) >> +#define LVDSC_WPMR_WPKEY_PSSWD 0x4C5644 >> + >> +struct mchp_lvds { >> + struct device *dev; >> + void __iomem *regs; >> + struct clk *pclk; >> + int format; /* vesa or jeida format */ >> + struct drm_panel *panel; >> + struct drm_bridge bridge; >> + struct drm_bridge *panel_bridge; >> +}; >> + >> +static inline struct mchp_lvds *bridge_to_lvds(struct drm_bridge >> *bridge) >> +{ >> + return container_of(bridge, struct mchp_lvds, bridge); >> +} >> + >> +static inline u32 lvds_readl(struct mchp_lvds *lvds, u32 offset) >> +{ >> + return readl_relaxed(lvds->regs + offset); >> +} >> + >> +static inline void lvds_writel(struct mchp_lvds *lvds, u32 offset, >> u32 val) >> +{ >> + writel_relaxed(val, lvds->regs + offset); >> +} >> + >> +static void lvds_serialiser_on(struct mchp_lvds *lvds) >> +{ >> + unsigned long timeout = jiffies + >> msecs_to_jiffies(LVDS_POLL_TIMEOUT_MS); >> + >> + /* The LVDSC registers can only be written if WPEN is cleared */ >> + lvds_writel(lvds, LVDSC_WPMR, (LVDSC_WPMR_WPKEY_PSSWD & >> + LVDSC_WPMR_WPKEY_MASK)); >> + >> + /* Wait for the status of configuration registers to be changed */ >> + while (lvds_readl(lvds, LVDSC_SR) & LVDSC_SR_CS) { >> + if (time_after(jiffies, timeout)) { >> + dev_err(lvds->dev, "%s: timeout error\n", >> __func__); >> + return; >> + } >> + usleep_range(1000, 2000); >> + } >> + >> + /* Configure the LVDSC */ >> + lvds_writel(lvds, LVDSC_CFGR, (LVDSC_CFGR_MAPPING_JEIDA | >> + LVDSC_CFGR_DC_UNBALANCED | >> + LVDSC_CFGR_DEN_POL_HIGH | >> + LVDSC_CFGR_PIXSIZE_24BITS)); >> + >> + /* Enable the LVDS serializer */ >> + lvds_writel(lvds, LVDSC_CR, LVDSC_CR_SER_EN); >> +} >> + >> +static int mchp_lvds_attach(struct drm_bridge *bridge, >> + enum drm_bridge_attach_flags flags) >> +{ >> + struct mchp_lvds *lvds = bridge_to_lvds(bridge); >> + >> + bridge->encoder->encoder_type = DRM_MODE_ENCODER_LVDS; >> + >> + return drm_bridge_attach(bridge->encoder, lvds->panel_bridge, >> + bridge, flags); >> +} >> + >> +static void mchp_lvds_enable(struct drm_bridge *bridge) >> +{ >> + struct mchp_lvds *lvds = bridge_to_lvds(bridge); >> + int ret; >> + >> + ret = clk_enable(lvds->pclk); >> + if (ret < 0) { >> + DRM_DEV_ERROR(lvds->dev, "failed to enable lvds pclk >> %d\n", ret); >> + return; >> + } >> + >> + ret = pm_runtime_get_sync(lvds->dev); >> + if (ret < 0) { >> + DRM_DEV_ERROR(lvds->dev, "failed to get pm runtime: >> %d\n", ret); >> + clk_disable(lvds->pclk); >> + return; >> + } >> + >> + lvds_serialiser_on(lvds); >> +} >> + >> +static void mchp_lvds_disable(struct drm_bridge *bridge) >> +{ >> + struct mchp_lvds *lvds = bridge_to_lvds(bridge); >> + >> + pm_runtime_put(lvds->dev); >> + clk_disable(lvds->pclk); >> +} >> + >> +static const struct drm_bridge_funcs mchp_lvds_bridge_funcs = { >> + .attach = mchp_lvds_attach, >> + .enable = mchp_lvds_enable, >> + .disable = mchp_lvds_disable, >> +}; >> + >> +static int mchp_lvds_probe(struct platform_device *pdev) >> +{ >> + struct device *dev = &pdev->dev; >> + struct mchp_lvds *lvds; >> + struct device_node *port; >> + int ret; >> + >> + if (!dev->of_node) >> + return -ENODEV; >> + >> + lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL); >> + if (!lvds) >> + return -ENOMEM; >> + >> + lvds->dev = dev; >> + >> + lvds->regs = devm_ioremap_resource(lvds->dev, >> + platform_get_resource(pdev, IORESOURCE_MEM, 0)); >> + if (IS_ERR(lvds->regs)) >> + return PTR_ERR(lvds->regs); >> + >> + lvds->pclk = devm_clk_get(lvds->dev, "pclk"); >> + if (IS_ERR(lvds->pclk)) >> + return dev_err_probe(lvds->dev, PTR_ERR(lvds->pclk), >> + "could not get pclk_lvds\n"); >> + >> + ret = clk_prepare(lvds->pclk); >> + if (ret < 0) { >> + DRM_DEV_ERROR(lvds->dev, "failed to prepare pclk_lvds\n"); >> + return ret; >> + } > > I think you can switch to devm_clk_get_prepared() here Sure, I'll consolidate get() and prepare() as below + lvds->pclk = devm_clk_get_prepared(lvds->dev, "pclk"); + if (IS_ERR(lvds->pclk)) + return dev_err_probe(lvds->dev, PTR_ERR(lvds->pclk), + "could not get pclk_lvds prepared\n"); Additionally I'll remove the "ret" variable as it is not used anywhere else in probe(). > >> + >> + port = of_graph_get_remote_node(dev->of_node, 1, 0); >> + if (!port) { >> + DRM_DEV_ERROR(dev, >> + "can't find port point, please init lvds >> panel port!\n"); >> + return -EINVAL; >> + } >> + >> + lvds->panel = of_drm_find_panel(port); >> + of_node_put(port); >> + >> + if (IS_ERR(lvds->panel)) >> + return -EPROBE_DEFER; >> + >> + lvds->panel_bridge = devm_drm_panel_bridge_add(dev, lvds->panel); >> + >> + if (IS_ERR(lvds->panel_bridge)) >> + return PTR_ERR(lvds->panel_bridge); >> + >> + lvds->bridge.of_node = dev->of_node; >> + lvds->bridge.type = DRM_MODE_CONNECTOR_LVDS; >> + lvds->bridge.funcs = &mchp_lvds_bridge_funcs; >> + >> + dev_set_drvdata(dev, lvds); >> + pm_runtime_enable(dev); > > If you also use devm_pm_runtime_enable() you can drop mchp_lvds_remove() Sure, I will use devm_pm_runtime_enable() and drop the mchp_lvds_remove() from ops. > >> + >> + drm_bridge_add(&lvds->bridge); >> + >> + return 0; >> +} >> + >> +static int mchp_lvds_remove(struct platform_device *pdev) >> +{ >> + struct mchp_lvds *lvds = platform_get_drvdata(pdev); >> + >> + pm_runtime_disable(&pdev->dev); >> + clk_unprepare(lvds->pclk); >> + >> + return 0; >> +} >> + >> +static const struct of_device_id mchp_lvds_dt_ids[] = { >> + { >> + .compatible = "microchip,sam9x75-lvds", >> + }, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(of, mchp_lvds_dt_ids); >> + >> +static struct platform_driver mchp_lvds_driver = { >> + .probe = mchp_lvds_probe, >> + .remove = mchp_lvds_remove, >> + .driver = { >> + .name = "microchip-lvds", >> + .of_match_table = mchp_lvds_dt_ids, >> + }, >> +}; >> +module_platform_driver(mchp_lvds_driver); >> + >> +MODULE_AUTHOR("Manikandan Muralidharan <manikandan.m@xxxxxxxxxxxxx>"); >> +MODULE_AUTHOR("Dharma Balasubiramani <dharma.b@xxxxxxxxxxxxx>"); >> +MODULE_DESCRIPTION("Low Voltage Differential Signaling Controller >> Driver"); >> +MODULE_LICENSE("GPL"); > > Thanks, > Neil -- With Best Regards, Dharma B.