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