Olivier Deprez | 157378f | 2022-04-04 15:47:50 +0200 | [diff] [blame^] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright (C) 2020 Marek Vasut <marex@denx.de> |
| 4 | * |
| 5 | * Based on tc358764.c by |
| 6 | * Andrzej Hajda <a.hajda@samsung.com> |
| 7 | * Maciej Purski <m.purski@samsung.com> |
| 8 | * |
| 9 | * Based on rpi_touchscreen.c by |
| 10 | * Eric Anholt <eric@anholt.net> |
| 11 | */ |
| 12 | |
| 13 | #include <linux/delay.h> |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/of_graph.h> |
| 16 | #include <linux/regulator/consumer.h> |
| 17 | |
| 18 | #include <video/mipi_display.h> |
| 19 | |
| 20 | #include <drm/drm_atomic_helper.h> |
| 21 | #include <drm/drm_crtc.h> |
| 22 | #include <drm/drm_fb_helper.h> |
| 23 | #include <drm/drm_mipi_dsi.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 | |
| 29 | /* PPI layer registers */ |
| 30 | #define PPI_STARTPPI 0x0104 /* START control bit */ |
| 31 | #define PPI_LPTXTIMECNT 0x0114 /* LPTX timing signal */ |
| 32 | #define PPI_D0S_ATMR 0x0144 |
| 33 | #define PPI_D1S_ATMR 0x0148 |
| 34 | #define PPI_D0S_CLRSIPOCOUNT 0x0164 /* Assertion timer for Lane 0 */ |
| 35 | #define PPI_D1S_CLRSIPOCOUNT 0x0168 /* Assertion timer for Lane 1 */ |
| 36 | #define PPI_START_FUNCTION 1 |
| 37 | |
| 38 | /* DSI layer registers */ |
| 39 | #define DSI_STARTDSI 0x0204 /* START control bit of DSI-TX */ |
| 40 | #define DSI_LANEENABLE 0x0210 /* Enables each lane */ |
| 41 | #define DSI_RX_START 1 |
| 42 | |
| 43 | /* LCDC/DPI Host Registers */ |
| 44 | #define LCDCTRL 0x0420 |
| 45 | |
| 46 | /* SPI Master Registers */ |
| 47 | #define SPICMR 0x0450 |
| 48 | #define SPITCR 0x0454 |
| 49 | |
| 50 | /* System Controller Registers */ |
| 51 | #define SYSCTRL 0x0464 |
| 52 | |
| 53 | /* System registers */ |
| 54 | #define LPX_PERIOD 3 |
| 55 | |
| 56 | /* Lane enable PPI and DSI register bits */ |
| 57 | #define LANEENABLE_CLEN BIT(0) |
| 58 | #define LANEENABLE_L0EN BIT(1) |
| 59 | #define LANEENABLE_L1EN BIT(2) |
| 60 | |
| 61 | struct tc358762 { |
| 62 | struct device *dev; |
| 63 | struct drm_bridge bridge; |
| 64 | struct drm_connector connector; |
| 65 | struct regulator *regulator; |
| 66 | struct drm_bridge *panel_bridge; |
| 67 | bool pre_enabled; |
| 68 | int error; |
| 69 | }; |
| 70 | |
| 71 | static int tc358762_clear_error(struct tc358762 *ctx) |
| 72 | { |
| 73 | int ret = ctx->error; |
| 74 | |
| 75 | ctx->error = 0; |
| 76 | return ret; |
| 77 | } |
| 78 | |
| 79 | static void tc358762_write(struct tc358762 *ctx, u16 addr, u32 val) |
| 80 | { |
| 81 | struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); |
| 82 | ssize_t ret; |
| 83 | u8 data[6]; |
| 84 | |
| 85 | if (ctx->error) |
| 86 | return; |
| 87 | |
| 88 | data[0] = addr; |
| 89 | data[1] = addr >> 8; |
| 90 | data[2] = val; |
| 91 | data[3] = val >> 8; |
| 92 | data[4] = val >> 16; |
| 93 | data[5] = val >> 24; |
| 94 | |
| 95 | ret = mipi_dsi_generic_write(dsi, data, sizeof(data)); |
| 96 | if (ret < 0) |
| 97 | ctx->error = ret; |
| 98 | } |
| 99 | |
| 100 | static inline struct tc358762 *bridge_to_tc358762(struct drm_bridge *bridge) |
| 101 | { |
| 102 | return container_of(bridge, struct tc358762, bridge); |
| 103 | } |
| 104 | |
| 105 | static int tc358762_init(struct tc358762 *ctx) |
| 106 | { |
| 107 | tc358762_write(ctx, DSI_LANEENABLE, |
| 108 | LANEENABLE_L0EN | LANEENABLE_CLEN); |
| 109 | tc358762_write(ctx, PPI_D0S_CLRSIPOCOUNT, 5); |
| 110 | tc358762_write(ctx, PPI_D1S_CLRSIPOCOUNT, 5); |
| 111 | tc358762_write(ctx, PPI_D0S_ATMR, 0); |
| 112 | tc358762_write(ctx, PPI_D1S_ATMR, 0); |
| 113 | tc358762_write(ctx, PPI_LPTXTIMECNT, LPX_PERIOD); |
| 114 | |
| 115 | tc358762_write(ctx, SPICMR, 0x00); |
| 116 | tc358762_write(ctx, LCDCTRL, 0x00100150); |
| 117 | tc358762_write(ctx, SYSCTRL, 0x040f); |
| 118 | msleep(100); |
| 119 | |
| 120 | tc358762_write(ctx, PPI_STARTPPI, PPI_START_FUNCTION); |
| 121 | tc358762_write(ctx, DSI_STARTDSI, DSI_RX_START); |
| 122 | |
| 123 | msleep(100); |
| 124 | |
| 125 | return tc358762_clear_error(ctx); |
| 126 | } |
| 127 | |
| 128 | static void tc358762_post_disable(struct drm_bridge *bridge) |
| 129 | { |
| 130 | struct tc358762 *ctx = bridge_to_tc358762(bridge); |
| 131 | int ret; |
| 132 | |
| 133 | /* |
| 134 | * The post_disable hook might be called multiple times. |
| 135 | * We want to avoid regulator imbalance below. |
| 136 | */ |
| 137 | if (!ctx->pre_enabled) |
| 138 | return; |
| 139 | |
| 140 | ctx->pre_enabled = false; |
| 141 | |
| 142 | ret = regulator_disable(ctx->regulator); |
| 143 | if (ret < 0) |
| 144 | dev_err(ctx->dev, "error disabling regulators (%d)\n", ret); |
| 145 | } |
| 146 | |
| 147 | static void tc358762_pre_enable(struct drm_bridge *bridge) |
| 148 | { |
| 149 | struct tc358762 *ctx = bridge_to_tc358762(bridge); |
| 150 | int ret; |
| 151 | |
| 152 | ret = regulator_enable(ctx->regulator); |
| 153 | if (ret < 0) |
| 154 | dev_err(ctx->dev, "error enabling regulators (%d)\n", ret); |
| 155 | |
| 156 | ret = tc358762_init(ctx); |
| 157 | if (ret < 0) |
| 158 | dev_err(ctx->dev, "error initializing bridge (%d)\n", ret); |
| 159 | |
| 160 | ctx->pre_enabled = true; |
| 161 | } |
| 162 | |
| 163 | static int tc358762_attach(struct drm_bridge *bridge, |
| 164 | enum drm_bridge_attach_flags flags) |
| 165 | { |
| 166 | struct tc358762 *ctx = bridge_to_tc358762(bridge); |
| 167 | |
| 168 | return drm_bridge_attach(bridge->encoder, ctx->panel_bridge, |
| 169 | bridge, flags); |
| 170 | } |
| 171 | |
| 172 | static const struct drm_bridge_funcs tc358762_bridge_funcs = { |
| 173 | .post_disable = tc358762_post_disable, |
| 174 | .pre_enable = tc358762_pre_enable, |
| 175 | .attach = tc358762_attach, |
| 176 | }; |
| 177 | |
| 178 | static int tc358762_parse_dt(struct tc358762 *ctx) |
| 179 | { |
| 180 | struct drm_bridge *panel_bridge; |
| 181 | struct device *dev = ctx->dev; |
| 182 | struct drm_panel *panel; |
| 183 | int ret; |
| 184 | |
| 185 | ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL); |
| 186 | if (ret) |
| 187 | return ret; |
| 188 | |
| 189 | panel_bridge = devm_drm_panel_bridge_add(dev, panel); |
| 190 | |
| 191 | if (IS_ERR(panel_bridge)) |
| 192 | return PTR_ERR(panel_bridge); |
| 193 | |
| 194 | ctx->panel_bridge = panel_bridge; |
| 195 | |
| 196 | return 0; |
| 197 | } |
| 198 | |
| 199 | static int tc358762_configure_regulators(struct tc358762 *ctx) |
| 200 | { |
| 201 | ctx->regulator = devm_regulator_get(ctx->dev, "vddc"); |
| 202 | if (IS_ERR(ctx->regulator)) |
| 203 | return PTR_ERR(ctx->regulator); |
| 204 | |
| 205 | return 0; |
| 206 | } |
| 207 | |
| 208 | static int tc358762_probe(struct mipi_dsi_device *dsi) |
| 209 | { |
| 210 | struct device *dev = &dsi->dev; |
| 211 | struct tc358762 *ctx; |
| 212 | int ret; |
| 213 | |
| 214 | ctx = devm_kzalloc(dev, sizeof(struct tc358762), GFP_KERNEL); |
| 215 | if (!ctx) |
| 216 | return -ENOMEM; |
| 217 | |
| 218 | mipi_dsi_set_drvdata(dsi, ctx); |
| 219 | |
| 220 | ctx->dev = dev; |
| 221 | ctx->pre_enabled = false; |
| 222 | |
| 223 | /* TODO: Find out how to get dual-lane mode working */ |
| 224 | dsi->lanes = 1; |
| 225 | dsi->format = MIPI_DSI_FMT_RGB888; |
| 226 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | |
| 227 | MIPI_DSI_MODE_LPM; |
| 228 | |
| 229 | ret = tc358762_parse_dt(ctx); |
| 230 | if (ret < 0) |
| 231 | return ret; |
| 232 | |
| 233 | ret = tc358762_configure_regulators(ctx); |
| 234 | if (ret < 0) |
| 235 | return ret; |
| 236 | |
| 237 | ctx->bridge.funcs = &tc358762_bridge_funcs; |
| 238 | ctx->bridge.type = DRM_MODE_CONNECTOR_DPI; |
| 239 | ctx->bridge.of_node = dev->of_node; |
| 240 | |
| 241 | drm_bridge_add(&ctx->bridge); |
| 242 | |
| 243 | ret = mipi_dsi_attach(dsi); |
| 244 | if (ret < 0) { |
| 245 | drm_bridge_remove(&ctx->bridge); |
| 246 | dev_err(dev, "failed to attach dsi\n"); |
| 247 | } |
| 248 | |
| 249 | return ret; |
| 250 | } |
| 251 | |
| 252 | static int tc358762_remove(struct mipi_dsi_device *dsi) |
| 253 | { |
| 254 | struct tc358762 *ctx = mipi_dsi_get_drvdata(dsi); |
| 255 | |
| 256 | mipi_dsi_detach(dsi); |
| 257 | drm_bridge_remove(&ctx->bridge); |
| 258 | |
| 259 | return 0; |
| 260 | } |
| 261 | |
| 262 | static const struct of_device_id tc358762_of_match[] = { |
| 263 | { .compatible = "toshiba,tc358762" }, |
| 264 | { } |
| 265 | }; |
| 266 | MODULE_DEVICE_TABLE(of, tc358762_of_match); |
| 267 | |
| 268 | static struct mipi_dsi_driver tc358762_driver = { |
| 269 | .probe = tc358762_probe, |
| 270 | .remove = tc358762_remove, |
| 271 | .driver = { |
| 272 | .name = "tc358762", |
| 273 | .of_match_table = tc358762_of_match, |
| 274 | }, |
| 275 | }; |
| 276 | module_mipi_dsi_driver(tc358762_driver); |
| 277 | |
| 278 | MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); |
| 279 | MODULE_DESCRIPTION("MIPI-DSI based Driver for TC358762 DSI/DPI Bridge"); |
| 280 | MODULE_LICENSE("GPL v2"); |