1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2f76ee892STomi Valkeinen /* 3f76ee892STomi Valkeinen * LG.Philips LB035Q02 LCD Panel driver 4f76ee892STomi Valkeinen * 5f76ee892STomi Valkeinen * Copyright (C) 2013 Texas Instruments 6f76ee892STomi Valkeinen * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> 7f76ee892STomi Valkeinen * Based on a driver by: Steve Sakoman <steve@sakoman.com> 8f76ee892STomi Valkeinen */ 9f76ee892STomi Valkeinen 10f76ee892STomi Valkeinen #include <linux/module.h> 11f76ee892STomi Valkeinen #include <linux/delay.h> 12f76ee892STomi Valkeinen #include <linux/spi/spi.h> 13f76ee892STomi Valkeinen #include <linux/mutex.h> 14f76ee892STomi Valkeinen #include <linux/gpio.h> 15f76ee892STomi Valkeinen 1662d9e44eSPeter Ujfalusi #include <video/omapfb_dss.h> 17f76ee892STomi Valkeinen 18aa55457dSJulia Lawall static const struct omap_video_timings lb035q02_timings = { 19f76ee892STomi Valkeinen .x_res = 320, 20f76ee892STomi Valkeinen .y_res = 240, 21f76ee892STomi Valkeinen 22f76ee892STomi Valkeinen .pixelclock = 6500000, 23f76ee892STomi Valkeinen 24f76ee892STomi Valkeinen .hsw = 2, 25f76ee892STomi Valkeinen .hfp = 20, 26f76ee892STomi Valkeinen .hbp = 68, 27f76ee892STomi Valkeinen 28f76ee892STomi Valkeinen .vsw = 2, 29f76ee892STomi Valkeinen .vfp = 4, 30f76ee892STomi Valkeinen .vbp = 18, 31f76ee892STomi Valkeinen 32f76ee892STomi Valkeinen .vsync_level = OMAPDSS_SIG_ACTIVE_LOW, 33f76ee892STomi Valkeinen .hsync_level = OMAPDSS_SIG_ACTIVE_LOW, 34f76ee892STomi Valkeinen .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE, 35f76ee892STomi Valkeinen .de_level = OMAPDSS_SIG_ACTIVE_HIGH, 36f76ee892STomi Valkeinen .sync_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE, 37f76ee892STomi Valkeinen }; 38f76ee892STomi Valkeinen 39f76ee892STomi Valkeinen struct panel_drv_data { 40f76ee892STomi Valkeinen struct omap_dss_device dssdev; 41f76ee892STomi Valkeinen struct omap_dss_device *in; 42f76ee892STomi Valkeinen 43f76ee892STomi Valkeinen struct spi_device *spi; 44f76ee892STomi Valkeinen 45f76ee892STomi Valkeinen int data_lines; 46f76ee892STomi Valkeinen 47f76ee892STomi Valkeinen struct omap_video_timings videomode; 48f76ee892STomi Valkeinen 49f76ee892STomi Valkeinen /* used for non-DT boot, to be removed */ 50f76ee892STomi Valkeinen int backlight_gpio; 51f76ee892STomi Valkeinen 52f76ee892STomi Valkeinen struct gpio_desc *enable_gpio; 53f76ee892STomi Valkeinen }; 54f76ee892STomi Valkeinen 55f76ee892STomi Valkeinen #define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev) 56f76ee892STomi Valkeinen 57f76ee892STomi Valkeinen static int lb035q02_write_reg(struct spi_device *spi, u8 reg, u16 val) 58f76ee892STomi Valkeinen { 59f76ee892STomi Valkeinen struct spi_message msg; 60f76ee892STomi Valkeinen struct spi_transfer index_xfer = { 61f76ee892STomi Valkeinen .len = 3, 62f76ee892STomi Valkeinen .cs_change = 1, 63f76ee892STomi Valkeinen }; 64f76ee892STomi Valkeinen struct spi_transfer value_xfer = { 65f76ee892STomi Valkeinen .len = 3, 66f76ee892STomi Valkeinen }; 67f76ee892STomi Valkeinen u8 buffer[16]; 68f76ee892STomi Valkeinen 69f76ee892STomi Valkeinen spi_message_init(&msg); 70f76ee892STomi Valkeinen 71f76ee892STomi Valkeinen /* register index */ 72f76ee892STomi Valkeinen buffer[0] = 0x70; 73f76ee892STomi Valkeinen buffer[1] = 0x00; 74f76ee892STomi Valkeinen buffer[2] = reg & 0x7f; 75f76ee892STomi Valkeinen index_xfer.tx_buf = buffer; 76f76ee892STomi Valkeinen spi_message_add_tail(&index_xfer, &msg); 77f76ee892STomi Valkeinen 78f76ee892STomi Valkeinen /* register value */ 79f76ee892STomi Valkeinen buffer[4] = 0x72; 80f76ee892STomi Valkeinen buffer[5] = val >> 8; 81f76ee892STomi Valkeinen buffer[6] = val; 82f76ee892STomi Valkeinen value_xfer.tx_buf = buffer + 4; 83f76ee892STomi Valkeinen spi_message_add_tail(&value_xfer, &msg); 84f76ee892STomi Valkeinen 85f76ee892STomi Valkeinen return spi_sync(spi, &msg); 86f76ee892STomi Valkeinen } 87f76ee892STomi Valkeinen 88f76ee892STomi Valkeinen static void init_lb035q02_panel(struct spi_device *spi) 89f76ee892STomi Valkeinen { 90f76ee892STomi Valkeinen /* Init sequence from page 28 of the lb035q02 spec */ 91f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x01, 0x6300); 92f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x02, 0x0200); 93f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x03, 0x0177); 94f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x04, 0x04c7); 95f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x05, 0xffc0); 96f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x06, 0xe806); 97f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x0a, 0x4008); 98f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x0b, 0x0000); 99f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x0d, 0x0030); 100f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x0e, 0x2800); 101f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x0f, 0x0000); 102f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x16, 0x9f80); 103f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x17, 0x0a0f); 104f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x1e, 0x00c1); 105f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x30, 0x0300); 106f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x31, 0x0007); 107f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x32, 0x0000); 108f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x33, 0x0000); 109f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x34, 0x0707); 110f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x35, 0x0004); 111f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x36, 0x0302); 112f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x37, 0x0202); 113f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x3a, 0x0a0d); 114f76ee892STomi Valkeinen lb035q02_write_reg(spi, 0x3b, 0x0806); 115f76ee892STomi Valkeinen } 116f76ee892STomi Valkeinen 117f76ee892STomi Valkeinen static int lb035q02_connect(struct omap_dss_device *dssdev) 118f76ee892STomi Valkeinen { 119f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev); 120f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in; 121f76ee892STomi Valkeinen int r; 122f76ee892STomi Valkeinen 123f76ee892STomi Valkeinen if (omapdss_device_is_connected(dssdev)) 124f76ee892STomi Valkeinen return 0; 125f76ee892STomi Valkeinen 126f76ee892STomi Valkeinen r = in->ops.dpi->connect(in, dssdev); 127f76ee892STomi Valkeinen if (r) 128f76ee892STomi Valkeinen return r; 129f76ee892STomi Valkeinen 130f76ee892STomi Valkeinen init_lb035q02_panel(ddata->spi); 131f76ee892STomi Valkeinen 132f76ee892STomi Valkeinen return 0; 133f76ee892STomi Valkeinen } 134f76ee892STomi Valkeinen 135f76ee892STomi Valkeinen static void lb035q02_disconnect(struct omap_dss_device *dssdev) 136f76ee892STomi Valkeinen { 137f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev); 138f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in; 139f76ee892STomi Valkeinen 140f76ee892STomi Valkeinen if (!omapdss_device_is_connected(dssdev)) 141f76ee892STomi Valkeinen return; 142f76ee892STomi Valkeinen 143f76ee892STomi Valkeinen in->ops.dpi->disconnect(in, dssdev); 144f76ee892STomi Valkeinen } 145f76ee892STomi Valkeinen 146f76ee892STomi Valkeinen static int lb035q02_enable(struct omap_dss_device *dssdev) 147f76ee892STomi Valkeinen { 148f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev); 149f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in; 150f76ee892STomi Valkeinen int r; 151f76ee892STomi Valkeinen 152f76ee892STomi Valkeinen if (!omapdss_device_is_connected(dssdev)) 153f76ee892STomi Valkeinen return -ENODEV; 154f76ee892STomi Valkeinen 155f76ee892STomi Valkeinen if (omapdss_device_is_enabled(dssdev)) 156f76ee892STomi Valkeinen return 0; 157f76ee892STomi Valkeinen 158f76ee892STomi Valkeinen if (ddata->data_lines) 159f76ee892STomi Valkeinen in->ops.dpi->set_data_lines(in, ddata->data_lines); 160f76ee892STomi Valkeinen in->ops.dpi->set_timings(in, &ddata->videomode); 161f76ee892STomi Valkeinen 162f76ee892STomi Valkeinen r = in->ops.dpi->enable(in); 163f76ee892STomi Valkeinen if (r) 164f76ee892STomi Valkeinen return r; 165f76ee892STomi Valkeinen 166f76ee892STomi Valkeinen if (ddata->enable_gpio) 167f76ee892STomi Valkeinen gpiod_set_value_cansleep(ddata->enable_gpio, 1); 168f76ee892STomi Valkeinen 169f76ee892STomi Valkeinen if (gpio_is_valid(ddata->backlight_gpio)) 170f76ee892STomi Valkeinen gpio_set_value_cansleep(ddata->backlight_gpio, 1); 171f76ee892STomi Valkeinen 172f76ee892STomi Valkeinen dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; 173f76ee892STomi Valkeinen 174f76ee892STomi Valkeinen return 0; 175f76ee892STomi Valkeinen } 176f76ee892STomi Valkeinen 177f76ee892STomi Valkeinen static void lb035q02_disable(struct omap_dss_device *dssdev) 178f76ee892STomi Valkeinen { 179f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev); 180f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in; 181f76ee892STomi Valkeinen 182f76ee892STomi Valkeinen if (!omapdss_device_is_enabled(dssdev)) 183f76ee892STomi Valkeinen return; 184f76ee892STomi Valkeinen 185f76ee892STomi Valkeinen if (ddata->enable_gpio) 186f76ee892STomi Valkeinen gpiod_set_value_cansleep(ddata->enable_gpio, 0); 187f76ee892STomi Valkeinen 188f76ee892STomi Valkeinen if (gpio_is_valid(ddata->backlight_gpio)) 189f76ee892STomi Valkeinen gpio_set_value_cansleep(ddata->backlight_gpio, 0); 190f76ee892STomi Valkeinen 191f76ee892STomi Valkeinen in->ops.dpi->disable(in); 192f76ee892STomi Valkeinen 193f76ee892STomi Valkeinen dssdev->state = OMAP_DSS_DISPLAY_DISABLED; 194f76ee892STomi Valkeinen } 195f76ee892STomi Valkeinen 196f76ee892STomi Valkeinen static void lb035q02_set_timings(struct omap_dss_device *dssdev, 197f76ee892STomi Valkeinen struct omap_video_timings *timings) 198f76ee892STomi Valkeinen { 199f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev); 200f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in; 201f76ee892STomi Valkeinen 202f76ee892STomi Valkeinen ddata->videomode = *timings; 203f76ee892STomi Valkeinen dssdev->panel.timings = *timings; 204f76ee892STomi Valkeinen 205f76ee892STomi Valkeinen in->ops.dpi->set_timings(in, timings); 206f76ee892STomi Valkeinen } 207f76ee892STomi Valkeinen 208f76ee892STomi Valkeinen static void lb035q02_get_timings(struct omap_dss_device *dssdev, 209f76ee892STomi Valkeinen struct omap_video_timings *timings) 210f76ee892STomi Valkeinen { 211f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev); 212f76ee892STomi Valkeinen 213f76ee892STomi Valkeinen *timings = ddata->videomode; 214f76ee892STomi Valkeinen } 215f76ee892STomi Valkeinen 216f76ee892STomi Valkeinen static int lb035q02_check_timings(struct omap_dss_device *dssdev, 217f76ee892STomi Valkeinen struct omap_video_timings *timings) 218f76ee892STomi Valkeinen { 219f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev); 220f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in; 221f76ee892STomi Valkeinen 222f76ee892STomi Valkeinen return in->ops.dpi->check_timings(in, timings); 223f76ee892STomi Valkeinen } 224f76ee892STomi Valkeinen 225f76ee892STomi Valkeinen static struct omap_dss_driver lb035q02_ops = { 226f76ee892STomi Valkeinen .connect = lb035q02_connect, 227f76ee892STomi Valkeinen .disconnect = lb035q02_disconnect, 228f76ee892STomi Valkeinen 229f76ee892STomi Valkeinen .enable = lb035q02_enable, 230f76ee892STomi Valkeinen .disable = lb035q02_disable, 231f76ee892STomi Valkeinen 232f76ee892STomi Valkeinen .set_timings = lb035q02_set_timings, 233f76ee892STomi Valkeinen .get_timings = lb035q02_get_timings, 234f76ee892STomi Valkeinen .check_timings = lb035q02_check_timings, 235f76ee892STomi Valkeinen 236f76ee892STomi Valkeinen .get_resolution = omapdss_default_get_resolution, 237f76ee892STomi Valkeinen }; 238f76ee892STomi Valkeinen 239f76ee892STomi Valkeinen static int lb035q02_probe_of(struct spi_device *spi) 240f76ee892STomi Valkeinen { 241f76ee892STomi Valkeinen struct device_node *node = spi->dev.of_node; 242f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); 243f76ee892STomi Valkeinen struct omap_dss_device *in; 244f76ee892STomi Valkeinen struct gpio_desc *gpio; 245f76ee892STomi Valkeinen 246f76ee892STomi Valkeinen gpio = devm_gpiod_get(&spi->dev, "enable", GPIOD_OUT_LOW); 247f76ee892STomi Valkeinen if (IS_ERR(gpio)) { 248f76ee892STomi Valkeinen dev_err(&spi->dev, "failed to parse enable gpio\n"); 249f76ee892STomi Valkeinen return PTR_ERR(gpio); 250f76ee892STomi Valkeinen } 251f76ee892STomi Valkeinen 252f76ee892STomi Valkeinen ddata->enable_gpio = gpio; 253f76ee892STomi Valkeinen 254f76ee892STomi Valkeinen ddata->backlight_gpio = -ENOENT; 255f76ee892STomi Valkeinen 256f76ee892STomi Valkeinen in = omapdss_of_find_source_for_first_ep(node); 257f76ee892STomi Valkeinen if (IS_ERR(in)) { 258f76ee892STomi Valkeinen dev_err(&spi->dev, "failed to find video source\n"); 259f76ee892STomi Valkeinen return PTR_ERR(in); 260f76ee892STomi Valkeinen } 261f76ee892STomi Valkeinen 262f76ee892STomi Valkeinen ddata->in = in; 263f76ee892STomi Valkeinen 264f76ee892STomi Valkeinen return 0; 265f76ee892STomi Valkeinen } 266f76ee892STomi Valkeinen 267f76ee892STomi Valkeinen static int lb035q02_panel_spi_probe(struct spi_device *spi) 268f76ee892STomi Valkeinen { 269f76ee892STomi Valkeinen struct panel_drv_data *ddata; 270f76ee892STomi Valkeinen struct omap_dss_device *dssdev; 271f76ee892STomi Valkeinen int r; 272f76ee892STomi Valkeinen 273f048e8c1SPeter Ujfalusi if (!spi->dev.of_node) 274f048e8c1SPeter Ujfalusi return -ENODEV; 275f048e8c1SPeter Ujfalusi 276f76ee892STomi Valkeinen ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL); 277f76ee892STomi Valkeinen if (ddata == NULL) 278f76ee892STomi Valkeinen return -ENOMEM; 279f76ee892STomi Valkeinen 280f76ee892STomi Valkeinen dev_set_drvdata(&spi->dev, ddata); 281f76ee892STomi Valkeinen 282f76ee892STomi Valkeinen ddata->spi = spi; 283f76ee892STomi Valkeinen 284f76ee892STomi Valkeinen r = lb035q02_probe_of(spi); 285f76ee892STomi Valkeinen if (r) 286f76ee892STomi Valkeinen return r; 287f76ee892STomi Valkeinen 288f76ee892STomi Valkeinen if (gpio_is_valid(ddata->backlight_gpio)) { 289f76ee892STomi Valkeinen r = devm_gpio_request_one(&spi->dev, ddata->backlight_gpio, 290f76ee892STomi Valkeinen GPIOF_OUT_INIT_LOW, "panel backlight"); 291f76ee892STomi Valkeinen if (r) 292f76ee892STomi Valkeinen goto err_gpio; 293f76ee892STomi Valkeinen } 294f76ee892STomi Valkeinen 295f76ee892STomi Valkeinen ddata->videomode = lb035q02_timings; 296f76ee892STomi Valkeinen 297f76ee892STomi Valkeinen dssdev = &ddata->dssdev; 298f76ee892STomi Valkeinen dssdev->dev = &spi->dev; 299f76ee892STomi Valkeinen dssdev->driver = &lb035q02_ops; 300f76ee892STomi Valkeinen dssdev->type = OMAP_DISPLAY_TYPE_DPI; 301f76ee892STomi Valkeinen dssdev->owner = THIS_MODULE; 302f76ee892STomi Valkeinen dssdev->panel.timings = ddata->videomode; 303f76ee892STomi Valkeinen dssdev->phy.dpi.data_lines = ddata->data_lines; 304f76ee892STomi Valkeinen 305f76ee892STomi Valkeinen r = omapdss_register_display(dssdev); 306f76ee892STomi Valkeinen if (r) { 307f76ee892STomi Valkeinen dev_err(&spi->dev, "Failed to register panel\n"); 308f76ee892STomi Valkeinen goto err_reg; 309f76ee892STomi Valkeinen } 310f76ee892STomi Valkeinen 311f76ee892STomi Valkeinen return 0; 312f76ee892STomi Valkeinen 313f76ee892STomi Valkeinen err_reg: 314f76ee892STomi Valkeinen err_gpio: 315f76ee892STomi Valkeinen omap_dss_put_device(ddata->in); 316f76ee892STomi Valkeinen return r; 317f76ee892STomi Valkeinen } 318f76ee892STomi Valkeinen 319f76ee892STomi Valkeinen static int lb035q02_panel_spi_remove(struct spi_device *spi) 320f76ee892STomi Valkeinen { 321f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev); 322f76ee892STomi Valkeinen struct omap_dss_device *dssdev = &ddata->dssdev; 323f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in; 324f76ee892STomi Valkeinen 325f76ee892STomi Valkeinen omapdss_unregister_display(dssdev); 326f76ee892STomi Valkeinen 327f76ee892STomi Valkeinen lb035q02_disable(dssdev); 328f76ee892STomi Valkeinen lb035q02_disconnect(dssdev); 329f76ee892STomi Valkeinen 330f76ee892STomi Valkeinen omap_dss_put_device(in); 331f76ee892STomi Valkeinen 332f76ee892STomi Valkeinen return 0; 333f76ee892STomi Valkeinen } 334f76ee892STomi Valkeinen 335f76ee892STomi Valkeinen static const struct of_device_id lb035q02_of_match[] = { 336f76ee892STomi Valkeinen { .compatible = "omapdss,lgphilips,lb035q02", }, 337f76ee892STomi Valkeinen {}, 338f76ee892STomi Valkeinen }; 339f76ee892STomi Valkeinen 340f76ee892STomi Valkeinen MODULE_DEVICE_TABLE(of, lb035q02_of_match); 341f76ee892STomi Valkeinen 342f76ee892STomi Valkeinen static struct spi_driver lb035q02_spi_driver = { 343f76ee892STomi Valkeinen .probe = lb035q02_panel_spi_probe, 344f76ee892STomi Valkeinen .remove = lb035q02_panel_spi_remove, 345f76ee892STomi Valkeinen .driver = { 346f76ee892STomi Valkeinen .name = "panel_lgphilips_lb035q02", 347f76ee892STomi Valkeinen .of_match_table = lb035q02_of_match, 348f76ee892STomi Valkeinen .suppress_bind_attrs = true, 349f76ee892STomi Valkeinen }, 350f76ee892STomi Valkeinen }; 351f76ee892STomi Valkeinen 352f76ee892STomi Valkeinen module_spi_driver(lb035q02_spi_driver); 353f76ee892STomi Valkeinen 354f76ee892STomi Valkeinen MODULE_ALIAS("spi:lgphilips,lb035q02"); 355f76ee892STomi Valkeinen MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>"); 356f76ee892STomi Valkeinen MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver"); 357f76ee892STomi Valkeinen MODULE_LICENSE("GPL"); 358