Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 | // SPDX-License-Identifier: GPL-2.0 /* * Panel driver for the ARM Versatile family reference designs from * ARM Limited. * * Author: * Linus Walleij <linus.wallei@linaro.org> * * On the Versatile AB, these panels come mounted on daughterboards * named "IB1" or "IB2" (Interface Board 1 & 2 respectively.) They * are documented in ARM DUI 0225D Appendix C and D. These daughter * boards support TFT display panels. * * - The IB1 is a passive board where the display connector defines a * few wires for encoding the display type for autodetection, * suitable display settings can then be looked up from this setting. * The magic bits can be read out from the system controller. * * - The IB2 is a more complex board intended for GSM phone development * with some logic and a control register, which needs to be accessed * and the board display needs to be turned on explicitly. * * On the Versatile PB, a special CLCD adaptor board is available * supporting the same displays as the Versatile AB, plus one more * Epson QCIF display. * */ #include <linux/bitops.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/mfd/syscon.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <video/of_videomode.h> #include <video/videomode.h> #include <drm/drm_modes.h> #include <drm/drm_panel.h> /* * This configuration register in the Versatile and RealView * family is uniformly present but appears more and more * unutilized starting with the RealView series. */ #define SYS_CLCD 0x50 /* The Versatile can detect the connected panel type */ #define SYS_CLCD_CLCDID_MASK (BIT(8)|BIT(9)|BIT(10)|BIT(11)|BIT(12)) #define SYS_CLCD_ID_SANYO_3_8 (0x00 << 8) #define SYS_CLCD_ID_SHARP_8_4 (0x01 << 8) #define SYS_CLCD_ID_EPSON_2_2 (0x02 << 8) #define SYS_CLCD_ID_SANYO_2_5 (0x07 << 8) #define SYS_CLCD_ID_VGA (0x1f << 8) /* IB2 control register for the Versatile daughterboard */ #define IB2_CTRL 0x00 #define IB2_CTRL_LCD_SD BIT(1) /* 1 = shut down LCD */ #define IB2_CTRL_LCD_BL_ON BIT(0) #define IB2_CTRL_LCD_MASK (BIT(0)|BIT(1)) /** * struct versatile_panel_type - lookup struct for the supported panels */ struct versatile_panel_type { /** * @name: the name of this panel */ const char *name; /** * @magic: the magic value from the detection register */ u32 magic; /** * @mode: the DRM display mode for this panel */ struct drm_display_mode mode; /** * @bus_flags: the DRM bus flags for this panel e.g. inverted clock */ u32 bus_flags; /** * @width_mm: the panel width in mm */ u32 width_mm; /** * @height_mm: the panel height in mm */ u32 height_mm; /** * @ib2: the panel may be connected on an IB2 daughterboard */ bool ib2; }; /** * struct versatile_panel - state container for the Versatile panels */ struct versatile_panel { /** * @dev: the container device */ struct device *dev; /** * @panel: the DRM panel instance for this device */ struct drm_panel panel; /** * @panel_type: the Versatile panel type as detected */ const struct versatile_panel_type *panel_type; /** * @map: map to the parent syscon where the main register reside */ struct regmap *map; /** * @ib2_map: map to the IB2 syscon, if applicable */ struct regmap *ib2_map; }; static const struct versatile_panel_type versatile_panels[] = { /* * Sanyo TM38QV67A02A - 3.8 inch QVGA (320x240) Color TFT * found on the Versatile AB IB1 connector or the Versatile * PB adaptor board connector. */ { .name = "Sanyo TM38QV67A02A", .magic = SYS_CLCD_ID_SANYO_3_8, .width_mm = 79, .height_mm = 54, .mode = { .clock = 10000, .hdisplay = 320, .hsync_start = 320 + 6, .hsync_end = 320 + 6 + 6, .htotal = 320 + 6 + 6 + 6, .vdisplay = 240, .vsync_start = 240 + 5, .vsync_end = 240 + 5 + 6, .vtotal = 240 + 5 + 6 + 5, .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }, }, /* * Sharp LQ084V1DG21 640x480 VGA Color TFT module * found on the Versatile AB IB1 connector or the Versatile * PB adaptor board connector. */ { .name = "Sharp LQ084V1DG21", .magic = SYS_CLCD_ID_SHARP_8_4, .width_mm = 171, .height_mm = 130, .mode = { .clock = 25000, .hdisplay = 640, .hsync_start = 640 + 24, .hsync_end = 640 + 24 + 96, .htotal = 640 + 24 + 96 + 24, .vdisplay = 480, .vsync_start = 480 + 11, .vsync_end = 480 + 11 + 2, .vtotal = 480 + 11 + 2 + 32, .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }, }, /* * Epson L2F50113T00 - 2.2 inch QCIF 176x220 Color TFT * found on the Versatile PB adaptor board connector. */ { .name = "Epson L2F50113T00", .magic = SYS_CLCD_ID_EPSON_2_2, .width_mm = 34, .height_mm = 45, .mode = { .clock = 62500, .hdisplay = 176, .hsync_start = 176 + 2, .hsync_end = 176 + 2 + 3, .htotal = 176 + 2 + 3 + 3, .vdisplay = 220, .vsync_start = 220 + 0, .vsync_end = 220 + 0 + 2, .vtotal = 220 + 0 + 2 + 1, .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC, }, .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, }, /* * Sanyo ALR252RGT 240x320 portrait display found on the * Versatile AB IB2 daughterboard for GSM prototyping. */ { .name = "Sanyo ALR252RGT", .magic = SYS_CLCD_ID_SANYO_2_5, .width_mm = 37, .height_mm = 50, .mode = { .clock = 5400, .hdisplay = 240, .hsync_start = 240 + 10, .hsync_end = 240 + 10 + 10, .htotal = 240 + 10 + 10 + 20, .vdisplay = 320, .vsync_start = 320 + 2, .vsync_end = 320 + 2 + 2, .vtotal = 320 + 2 + 2 + 2, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }, .bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE, .ib2 = true, }, }; static inline struct versatile_panel * to_versatile_panel(struct drm_panel *panel) { return container_of(panel, struct versatile_panel, panel); } static int versatile_panel_disable(struct drm_panel *panel) { struct versatile_panel *vpanel = to_versatile_panel(panel); /* If we're on an IB2 daughterboard, turn off display */ if (vpanel->ib2_map) { dev_dbg(vpanel->dev, "disable IB2 display\n"); regmap_update_bits(vpanel->ib2_map, IB2_CTRL, IB2_CTRL_LCD_MASK, IB2_CTRL_LCD_SD); } return 0; } static int versatile_panel_enable(struct drm_panel *panel) { struct versatile_panel *vpanel = to_versatile_panel(panel); /* If we're on an IB2 daughterboard, turn on display */ if (vpanel->ib2_map) { dev_dbg(vpanel->dev, "enable IB2 display\n"); regmap_update_bits(vpanel->ib2_map, IB2_CTRL, IB2_CTRL_LCD_MASK, IB2_CTRL_LCD_BL_ON); } return 0; } static int versatile_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector) { struct versatile_panel *vpanel = to_versatile_panel(panel); struct drm_display_mode *mode; connector->display_info.width_mm = vpanel->panel_type->width_mm; connector->display_info.height_mm = vpanel->panel_type->height_mm; connector->display_info.bus_flags = vpanel->panel_type->bus_flags; mode = drm_mode_duplicate(connector->dev, &vpanel->panel_type->mode); drm_mode_set_name(mode); mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; mode->width_mm = vpanel->panel_type->width_mm; mode->height_mm = vpanel->panel_type->height_mm; drm_mode_probed_add(connector, mode); return 1; } static const struct drm_panel_funcs versatile_panel_drm_funcs = { .disable = versatile_panel_disable, .enable = versatile_panel_enable, .get_modes = versatile_panel_get_modes, }; static int versatile_panel_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct versatile_panel *vpanel; struct device *parent; struct regmap *map; int ret; u32 val; int i; parent = dev->parent; if (!parent) { dev_err(dev, "no parent for versatile panel\n"); return -ENODEV; } map = syscon_node_to_regmap(parent->of_node); if (IS_ERR(map)) { dev_err(dev, "no regmap for versatile panel parent\n"); return PTR_ERR(map); } vpanel = devm_kzalloc(dev, sizeof(*vpanel), GFP_KERNEL); if (!vpanel) return -ENOMEM; ret = regmap_read(map, SYS_CLCD, &val); if (ret) { dev_err(dev, "cannot access syscon regs\n"); return ret; } val &= SYS_CLCD_CLCDID_MASK; for (i = 0; i < ARRAY_SIZE(versatile_panels); i++) { const struct versatile_panel_type *pt; pt = &versatile_panels[i]; if (pt->magic == val) { vpanel->panel_type = pt; break; } } /* No panel detected or VGA, let's leave this show */ if (i == ARRAY_SIZE(versatile_panels)) { dev_info(dev, "no panel detected\n"); return -ENODEV; } dev_info(dev, "detected: %s\n", vpanel->panel_type->name); vpanel->dev = dev; vpanel->map = map; /* Check if the panel is mounted on an IB2 daughterboard */ if (vpanel->panel_type->ib2) { vpanel->ib2_map = syscon_regmap_lookup_by_compatible( "arm,versatile-ib2-syscon"); if (IS_ERR(vpanel->ib2_map)) vpanel->ib2_map = NULL; else dev_info(dev, "panel mounted on IB2 daughterboard\n"); } drm_panel_init(&vpanel->panel, dev, &versatile_panel_drm_funcs, DRM_MODE_CONNECTOR_DPI); drm_panel_add(&vpanel->panel); return 0; } static const struct of_device_id versatile_panel_match[] = { { .compatible = "arm,versatile-tft-panel", }, {}, }; MODULE_DEVICE_TABLE(of, versatile_panel_match); static struct platform_driver versatile_panel_driver = { .probe = versatile_panel_probe, .driver = { .name = "versatile-tft-panel", .of_match_table = versatile_panel_match, }, }; module_platform_driver(versatile_panel_driver); MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); MODULE_DESCRIPTION("ARM Versatile panel driver"); MODULE_LICENSE("GPL v2"); |