1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries 4 * 5 * Author: Manikandan Muralidharan <manikandan.m@microchip.com> 6 * Author: Dharma Balasubiramani <dharma.b@microchip.com> 7 * 8 */ 9 10 #include <linux/clk.h> 11 #include <linux/component.h> 12 #include <linux/delay.h> 13 #include <linux/jiffies.h> 14 #include <linux/mfd/syscon.h> 15 #include <linux/of_graph.h> 16 #include <linux/pinctrl/devinfo.h> 17 #include <linux/phy/phy.h> 18 #include <linux/platform_device.h> 19 #include <linux/pm_runtime.h> 20 #include <linux/regmap.h> 21 #include <linux/reset.h> 22 23 #include <drm/drm_atomic_helper.h> 24 #include <drm/drm_bridge.h> 25 #include <drm/drm_of.h> 26 #include <drm/drm_panel.h> 27 #include <drm/drm_print.h> 28 #include <drm/drm_probe_helper.h> 29 #include <drm/drm_simple_kms_helper.h> 30 31 #define LVDS_POLL_TIMEOUT_MS 1000 32 33 /* LVDSC register offsets */ 34 #define LVDSC_CR 0x00 35 #define LVDSC_CFGR 0x04 36 #define LVDSC_SR 0x0C 37 #define LVDSC_WPMR 0xE4 38 39 /* Bitfields in LVDSC_CR (Control Register) */ 40 #define LVDSC_CR_SER_EN BIT(0) 41 42 /* Bitfields in LVDSC_CFGR (Configuration Register) */ 43 #define LVDSC_CFGR_PIXSIZE_24BITS 0 44 #define LVDSC_CFGR_DEN_POL_HIGH 0 45 #define LVDSC_CFGR_DC_UNBALANCED 0 46 #define LVDSC_CFGR_MAPPING_JEIDA BIT(6) 47 48 /*Bitfields in LVDSC_SR */ 49 #define LVDSC_SR_CS BIT(0) 50 51 /* Bitfields in LVDSC_WPMR (Write Protection Mode Register) */ 52 #define LVDSC_WPMR_WPKEY_MASK GENMASK(31, 8) 53 #define LVDSC_WPMR_WPKEY_PSSWD 0x4C5644 54 55 struct mchp_lvds { 56 struct device *dev; 57 void __iomem *regs; 58 struct clk *pclk; 59 struct drm_panel *panel; 60 struct drm_bridge bridge; 61 struct drm_bridge *panel_bridge; 62 }; 63 64 static inline struct mchp_lvds *bridge_to_lvds(struct drm_bridge *bridge) 65 { 66 return container_of(bridge, struct mchp_lvds, bridge); 67 } 68 69 static inline u32 lvds_readl(struct mchp_lvds *lvds, u32 offset) 70 { 71 return readl_relaxed(lvds->regs + offset); 72 } 73 74 static inline void lvds_writel(struct mchp_lvds *lvds, u32 offset, u32 val) 75 { 76 writel_relaxed(val, lvds->regs + offset); 77 } 78 79 static void lvds_serialiser_on(struct mchp_lvds *lvds) 80 { 81 unsigned long timeout = jiffies + msecs_to_jiffies(LVDS_POLL_TIMEOUT_MS); 82 83 /* The LVDSC registers can only be written if WPEN is cleared */ 84 lvds_writel(lvds, LVDSC_WPMR, (LVDSC_WPMR_WPKEY_PSSWD & 85 LVDSC_WPMR_WPKEY_MASK)); 86 87 /* Wait for the status of configuration registers to be changed */ 88 while (lvds_readl(lvds, LVDSC_SR) & LVDSC_SR_CS) { 89 if (time_after(jiffies, timeout)) { 90 dev_err(lvds->dev, "%s: timeout error\n", __func__); 91 return; 92 } 93 usleep_range(1000, 2000); 94 } 95 96 /* Configure the LVDSC */ 97 lvds_writel(lvds, LVDSC_CFGR, (LVDSC_CFGR_MAPPING_JEIDA | 98 LVDSC_CFGR_DC_UNBALANCED | 99 LVDSC_CFGR_DEN_POL_HIGH | 100 LVDSC_CFGR_PIXSIZE_24BITS)); 101 102 /* Enable the LVDS serializer */ 103 lvds_writel(lvds, LVDSC_CR, LVDSC_CR_SER_EN); 104 } 105 106 static int mchp_lvds_attach(struct drm_bridge *bridge, 107 enum drm_bridge_attach_flags flags) 108 { 109 struct mchp_lvds *lvds = bridge_to_lvds(bridge); 110 111 return drm_bridge_attach(bridge->encoder, lvds->panel_bridge, 112 bridge, flags); 113 } 114 115 static void mchp_lvds_enable(struct drm_bridge *bridge) 116 { 117 struct mchp_lvds *lvds = bridge_to_lvds(bridge); 118 int ret; 119 120 ret = clk_prepare_enable(lvds->pclk); 121 if (ret < 0) { 122 dev_err(lvds->dev, "failed to enable lvds pclk %d\n", ret); 123 return; 124 } 125 126 ret = pm_runtime_get_sync(lvds->dev); 127 if (ret < 0) { 128 dev_err(lvds->dev, "failed to get pm runtime: %d\n", ret); 129 return; 130 } 131 132 lvds_serialiser_on(lvds); 133 } 134 135 static void mchp_lvds_disable(struct drm_bridge *bridge) 136 { 137 struct mchp_lvds *lvds = bridge_to_lvds(bridge); 138 139 pm_runtime_put(lvds->dev); 140 clk_disable_unprepare(lvds->pclk); 141 } 142 143 static 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 }; 148 149 static int mchp_lvds_probe(struct platform_device *pdev) 150 { 151 struct device *dev = &pdev->dev; 152 struct mchp_lvds *lvds; 153 struct device_node *port; 154 int ret; 155 156 if (!dev->of_node) 157 return -ENODEV; 158 159 lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL); 160 if (!lvds) 161 return -ENOMEM; 162 163 lvds->dev = dev; 164 165 lvds->regs = devm_ioremap_resource(lvds->dev, 166 platform_get_resource(pdev, IORESOURCE_MEM, 0)); 167 if (IS_ERR(lvds->regs)) 168 return PTR_ERR(lvds->regs); 169 170 lvds->pclk = devm_clk_get(lvds->dev, "pclk"); 171 if (IS_ERR(lvds->pclk)) 172 return dev_err_probe(lvds->dev, PTR_ERR(lvds->pclk), 173 "could not get pclk_lvds\n"); 174 175 port = of_graph_get_remote_node(dev->of_node, 1, 0); 176 if (!port) { 177 dev_err(dev, 178 "can't find port point, please init lvds panel port!\n"); 179 return -ENODEV; 180 } 181 182 lvds->panel = of_drm_find_panel(port); 183 of_node_put(port); 184 185 if (IS_ERR(lvds->panel)) 186 return -EPROBE_DEFER; 187 188 lvds->panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0); 189 190 if (IS_ERR(lvds->panel_bridge)) 191 return PTR_ERR(lvds->panel_bridge); 192 193 lvds->bridge.of_node = dev->of_node; 194 lvds->bridge.type = DRM_MODE_CONNECTOR_LVDS; 195 lvds->bridge.funcs = &mchp_lvds_bridge_funcs; 196 197 dev_set_drvdata(dev, lvds); 198 ret = devm_pm_runtime_enable(dev); 199 if (ret < 0) { 200 dev_err(lvds->dev, "failed to enable pm runtime: %d\n", ret); 201 return ret; 202 } 203 204 drm_bridge_add(&lvds->bridge); 205 206 return 0; 207 } 208 209 static const struct of_device_id mchp_lvds_dt_ids[] = { 210 { 211 .compatible = "microchip,sam9x75-lvds", 212 }, 213 {}, 214 }; 215 MODULE_DEVICE_TABLE(of, mchp_lvds_dt_ids); 216 217 static struct platform_driver mchp_lvds_driver = { 218 .probe = mchp_lvds_probe, 219 .driver = { 220 .name = "microchip-lvds", 221 .of_match_table = mchp_lvds_dt_ids, 222 }, 223 }; 224 module_platform_driver(mchp_lvds_driver); 225 226 MODULE_AUTHOR("Manikandan Muralidharan <manikandan.m@microchip.com>"); 227 MODULE_AUTHOR("Dharma Balasubiramani <dharma.b@microchip.com>"); 228 MODULE_DESCRIPTION("Low Voltage Differential Signaling Controller Driver"); 229 MODULE_LICENSE("GPL"); 230