summaryrefslogtreecommitdiff
path: root/drivers/video/omap2/displays
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/omap2/displays')
-rw-r--r--drivers/video/omap2/displays/Kconfig8
-rw-r--r--drivers/video/omap2/displays/Makefile1
-rw-r--r--drivers/video/omap2/displays/panel-ili9342.c598
3 files changed, 606 insertions, 1 deletions
diff --git a/drivers/video/omap2/displays/Kconfig b/drivers/video/omap2/displays/Kconfig
index af8f97ebbe0..92f8d32b63e 100644
--- a/drivers/video/omap2/displays/Kconfig
+++ b/drivers/video/omap2/displays/Kconfig
@@ -16,7 +16,13 @@ config PANEL_TFP410
help
Driver for TFP410 DPI-to-DVI chip. The driver uses i2c to read EDID
information from the monitor.
-
+
+config PANEL_ILI_9342
+ tristate "ili9342 display controller"
+ depends on OMAP2_DSS_DPI && SPI
+ help
+ LCD Display controller used on the Olio H1
+
config PANEL_LGPHILIPS_LB035Q02
tristate "LG.Philips LB035Q02 LCD Panel"
depends on OMAP2_DSS_DPI && SPI
diff --git a/drivers/video/omap2/displays/Makefile b/drivers/video/omap2/displays/Makefile
index abc0d9dae56..3fb5d3ac750 100644
--- a/drivers/video/omap2/displays/Makefile
+++ b/drivers/video/omap2/displays/Makefile
@@ -1,5 +1,6 @@
obj-$(CONFIG_PANEL_GENERIC_DPI) += panel-generic-dpi.o
obj-$(CONFIG_PANEL_TFP410) += panel-tfp410.o
+obj-$(CONFIG_PANEL_ILI_9342) += panel-ili9342.o
obj-$(CONFIG_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o
obj-$(CONFIG_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o
obj-$(CONFIG_PANEL_NEC_NL8048HL11_01B) += panel-nec-nl8048hl11-01b.o
diff --git a/drivers/video/omap2/displays/panel-ili9342.c b/drivers/video/omap2/displays/panel-ili9342.c
new file mode 100644
index 00000000000..40a9713aa0d
--- /dev/null
+++ b/drivers/video/omap2/displays/panel-ili9342.c
@@ -0,0 +1,598 @@
+#define DEBUG
+/*
+ * Driver for ili9342 display driver
+ *
+ * Copyright (C) 2014,2015 Olio Devices Inc.
+ * Author: Evan Wilson <evan@oliodevices.com>
+ * Author: Mattis Fjallstrom <mattis@oliodevices.com>
+ *
+ * Adapted from panel-generic-dpi.c, panel-nec-nl8048hl11-01b.c
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/gpio.h>
+#include <linux/device.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/omapdss.h>
+
+#define OLIODEBUG
+#ifdef OLIODEBUG
+#define oliodebug(...) printk ( __VA_ARGS__ )
+#else
+#define oliodebug(...)
+#endif
+
+struct panel_config {
+ struct omap_video_timings timings;
+
+ int power_on_delay;
+ int power_off_delay;
+
+ /*
+ * Used to match device to panel configuration
+ * when use generic panel driver
+ */
+ const char *name;
+};
+
+/* Panel configurations */
+static struct panel_config ili9342_panels[] = {
+ /* Olio H1 panel */
+ {
+ {
+ .x_res = 320,
+ .y_res = 240,
+
+ .pixel_clock = 5333,
+
+ .hsw = 10,
+ .hfp = 10,
+ .hbp = 10,
+
+ .vsw = 1,
+ .vfp = 1,
+ .vbp = 1,
+
+ .vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
+ .hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
+ .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,
+ .de_level = OMAPDSS_SIG_ACTIVE_HIGH,
+ .sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,
+ },
+ .name = "olio_h1_panel",
+ },
+};
+
+struct panel_drv_data {
+
+ struct omap_dss_device *dssdev;
+
+ struct panel_config *panel_config;
+
+ struct mutex lock;
+};
+
+static int ili9342_panel_power_on(struct omap_dss_device *dssdev)
+{
+ int r;
+ struct panel_drv_data *drv_data = dev_get_drvdata(&dssdev->dev);
+ struct panel_config *panel_config = drv_data->panel_config;
+
+ if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
+ return 0;
+
+ omapdss_dpi_set_timings(dssdev, &dssdev->panel.timings);
+ omapdss_dpi_set_data_lines(dssdev, dssdev->phy.dpi.data_lines);
+
+ r = omapdss_dpi_display_enable(dssdev);
+ if (r)
+ goto err0;
+
+ /* wait couple of vsyncs until enabling the LCD */
+ if (panel_config->power_on_delay)
+ msleep(panel_config->power_on_delay);
+
+ if (dssdev->platform_enable) {
+ r = dssdev->platform_enable(dssdev);
+ if (r)
+ goto err1;
+ }
+
+ return 0;
+err1:
+ omapdss_dpi_display_disable(dssdev);
+err0:
+ return r;
+}
+
+static void ili9342_panel_power_off(struct omap_dss_device *dssdev)
+{
+ struct panel_drv_data *drv_data = dev_get_drvdata(&dssdev->dev);
+ struct panel_config *panel_config = drv_data->panel_config;
+
+ if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
+ return;
+
+ if (dssdev->platform_disable)
+ dssdev->platform_disable(dssdev);
+
+ /* wait couple of vsyncs after disabling the LCD */
+ if (panel_config->power_off_delay)
+ msleep(panel_config->power_off_delay);
+
+ omapdss_dpi_display_disable(dssdev);
+}
+
+static int ili9342_panel_probe(struct omap_dss_device *dssdev)
+{
+ struct panel_config *panel_config = NULL;
+ struct panel_drv_data *drv_data = NULL;
+ int i;
+
+ dev_dbg(&dssdev->dev, "probe\n");
+
+ if (!dssdev || !dssdev->name)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(ili9342_panels); i++) {
+ if (strcmp(dssdev->name, ili9342_panels[i].name) == 0) {
+ panel_config = &ili9342_panels[i];
+ break;
+ }
+ }
+
+ if (!panel_config) {
+ dev_err(&dssdev->dev, "Could not find %s in ili9342 panel configs\n", dssdev->name);
+ return -EINVAL;
+ }
+
+ dssdev->panel.timings = panel_config->timings;
+
+ drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
+ if (!drv_data)
+ return -ENOMEM;
+
+ drv_data->dssdev = dssdev;
+ drv_data->panel_config = panel_config;
+
+ mutex_init(&drv_data->lock);
+
+ dev_set_drvdata(&dssdev->dev, drv_data);
+
+ return 0;
+}
+
+static void __exit ili9342_panel_remove(struct omap_dss_device *dssdev)
+{
+ dev_dbg(&dssdev->dev, "remove\n");
+
+ dev_set_drvdata(&dssdev->dev, NULL);
+}
+
+static int ili9342_panel_enable(struct omap_dss_device *dssdev)
+{
+ struct panel_drv_data *drv_data = dev_get_drvdata(&dssdev->dev);
+ int r;
+
+ mutex_lock(&drv_data->lock);
+
+ r = ili9342_panel_power_on(dssdev);
+ if (r)
+ goto err;
+
+ dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
+err:
+ mutex_unlock(&drv_data->lock);
+
+ return r;
+}
+
+static void ili9342_panel_disable(struct omap_dss_device *dssdev)
+{
+ struct panel_drv_data *drv_data = dev_get_drvdata(&dssdev->dev);
+
+ mutex_lock(&drv_data->lock);
+
+ ili9342_panel_power_off(dssdev);
+
+ dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
+
+ mutex_unlock(&drv_data->lock);
+}
+
+static void ili9342_panel_set_timings(struct omap_dss_device *dssdev,
+ struct omap_video_timings *timings)
+{
+ struct panel_drv_data *drv_data = dev_get_drvdata(&dssdev->dev);
+
+ mutex_lock(&drv_data->lock);
+
+ omapdss_dpi_set_timings(dssdev, timings);
+
+ dssdev->panel.timings = *timings;
+
+ mutex_unlock(&drv_data->lock);
+}
+
+static void ili9342_panel_get_timings(struct omap_dss_device *dssdev,
+ struct omap_video_timings *timings)
+{
+ struct panel_drv_data *drv_data = dev_get_drvdata(&dssdev->dev);
+
+ mutex_lock(&drv_data->lock);
+
+ *timings = dssdev->panel.timings;
+
+ mutex_unlock(&drv_data->lock);
+}
+
+static int ili9342_panel_check_timings(struct omap_dss_device *dssdev,
+ struct omap_video_timings *timings)
+{
+ struct panel_drv_data *drv_data = dev_get_drvdata(&dssdev->dev);
+ int r;
+
+ mutex_lock(&drv_data->lock);
+
+ r = dpi_check_timings(dssdev, timings);
+
+ mutex_unlock(&drv_data->lock);
+
+ return r;
+}
+
+static int ili9342_get_recommended_bpp(struct omap_dss_device *dssdev) {
+ return 24;
+}
+
+/***************************************************************************
+ * suspend & resume
+ *
+ * For now, this is all handled by the SPI driver (see below).
+ * Leaving this functions here should we change our minds.
+ */
+
+static int ili9342_disp_suspend (struct device * dev) {
+ ili9342_panel_disable((struct omap_dss_device *) dev);
+ return 0;
+}
+
+static int ili9342_disp_resume (struct device * dev) {
+ ili9342_panel_enable((struct omap_dss_device *) dev);
+ return 0;
+}
+
+static struct dev_pm_ops ili9342_disp_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ili9342_disp_suspend, ili9342_disp_resume)
+};
+
+
+static struct omap_dss_driver ili9342_driver = {
+ .probe = ili9342_panel_probe,
+ .remove = __exit_p(ili9342_panel_remove),
+
+ .enable = ili9342_panel_enable,
+ .disable = ili9342_panel_disable,
+
+ .set_timings = ili9342_panel_set_timings,
+ .get_timings = ili9342_panel_get_timings,
+ .check_timings = ili9342_panel_check_timings,
+
+ .get_recommended_bpp = ili9342_get_recommended_bpp,
+
+ .driver = {
+ .name = "ili9342_panel",
+ .owner = THIS_MODULE,
+ .pm = &ili9342_disp_pm_ops,
+ },
+};
+
+/* ====================================================================== */
+
+/* Here follows the SPI driver - it's required by the panel, but the
+ * coupling is rather weak. It registers another driver.
+ */
+
+/* ====================================================================== */
+
+static struct regulator *spi_regulator;
+
+static int ili9342_spi_write(struct spi_device *spi, bool cmd, unsigned char val) {
+ unsigned short buf;
+ struct spi_message m;
+ struct spi_transfer t = {
+ .tx_buf = &buf,
+ .len = 2,
+ .bits_per_word = 9,
+ };
+ int r;
+
+ if(cmd) {
+ buf = 0;
+ } else {
+ buf = 1 << 8;
+ }
+ buf |= val;
+
+ dev_dbg(&spi->dev, "SPI sync: %x", buf);
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ r = spi_sync(spi, &m);
+ if(r < 0) {
+ dev_err(&spi->dev, "SPI sync failed.");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ili9342_write_cmd(struct spi_device *spi, unsigned char val) {
+ return ili9342_spi_write(spi, 1, val);
+}
+
+static int ili9342_write_data(struct spi_device *spi, unsigned char val) {
+ return ili9342_spi_write(spi, 0, val);
+}
+
+static inline void ili9342_init_seq(struct spi_device *spi) {
+ ili9342_write_cmd(spi, 0xC8);
+ ili9342_write_data(spi, 0xFF);
+ ili9342_write_data(spi, 0x93);
+ ili9342_write_data(spi, 0x42);
+
+ ili9342_write_cmd(spi, 0xB0);
+ ili9342_write_data(spi, 0xE0);
+
+// Timing settings
+
+// ili9342_write_cmd(spi, 0xB5);
+// ili9342_write_data(spi, 0x04);
+// ili9342_write_data(spi, 0x01);
+// ili9342_write_data(spi, 0x0a);
+// ili9342_write_data(spi, 0x14);
+
+ //ili9342_write_cmd(spi, 0xB1);
+ //ili9342_write_data(spi, 0x00);
+ //ili9342_write_data(spi, 0x10);
+
+ ili9342_write_cmd(spi, 0x0B);
+ ili9342_write_data(spi, 0x20);
+
+ ili9342_write_cmd(spi, 0xF6);
+ ili9342_write_data(spi, 0x01);
+ ili9342_write_data(spi, 0x00);
+ ili9342_write_data(spi, 0x06);
+
+
+ ili9342_write_cmd(spi, 0xB4);
+ ili9342_write_data(spi, 0x00);
+
+ ili9342_write_cmd(spi, 0xC0);
+ ili9342_write_data(spi, 0x16);
+ ili9342_write_data(spi, 0x11);
+
+ ili9342_write_cmd(spi, 0xC1);
+ ili9342_write_data(spi, 0x01);
+
+ ili9342_write_cmd(spi, 0xC5);
+ ili9342_write_data(spi, 0xDB);
+
+// RGB color mode
+
+ //ili9342_write_cmd(spi, 0x36);
+ //ili9342_write_data(spi, 0x08);
+
+ ili9342_write_cmd(spi, 0xB6);
+ ili9342_write_data(spi, 0x0A);
+ ili9342_write_data(spi, 0x00);
+ ili9342_write_data(spi, 0x1D);
+ ili9342_write_data(spi, 0x04);
+// GAMMA settings
+ //ili9342_write_cmd(spi, 0x26);
+ //ili9342_write_data(spi, 0x04);
+
+ ili9342_write_cmd(spi, 0xE0);
+ ili9342_write_data(spi, 0x00); //63
+ ili9342_write_data(spi, 0x1b); //62
+ ili9342_write_data(spi, 0x22); //61
+ ili9342_write_data(spi, 0x05); //59
+ ili9342_write_data(spi, 0x13); //57
+ ili9342_write_data(spi, 0x07); //50
+ ili9342_write_data(spi, 0x4c); //43
+ ili9342_write_data(spi, 0xa7); //27
+ ili9342_write_data(spi, 0x5f); //20
+ ili9342_write_data(spi, 0x05); //13
+ ili9342_write_data(spi, 0x0b); //06
+ ili9342_write_data(spi, 0x09); //04
+ ili9342_write_data(spi, 0x32); //02
+ ili9342_write_data(spi, 0x36); //01
+ ili9342_write_data(spi, 0x0F); //00
+
+ ili9342_write_cmd(spi, 0xE1);
+ ili9342_write_data(spi, 0x00); //00
+ ili9342_write_data(spi, 0x0c); //01
+ ili9342_write_data(spi, 0x0d); //02
+ ili9342_write_data(spi, 0x05); //04
+ ili9342_write_data(spi, 0x11); //06
+ ili9342_write_data(spi, 0x06); //13
+ ili9342_write_data(spi, 0x30); //20
+ ili9342_write_data(spi, 0x58); //27
+ ili9342_write_data(spi, 0x44); //43
+ ili9342_write_data(spi, 0x08); //50
+ ili9342_write_data(spi, 0x14); //57
+ ili9342_write_data(spi, 0x0c); //59
+ ili9342_write_data(spi, 0x1e); //61
+ ili9342_write_data(spi, 0x24); //62
+ ili9342_write_data(spi, 0x0F); //63
+
+ ili9342_write_cmd(spi, 0x35);
+ ili9342_write_data(spi, 0x00);
+
+ ili9342_write_cmd(spi, 0x3A);
+ ili9342_write_data(spi, 0x66);
+
+ ili9342_write_cmd(spi, 0x11);
+ msleep(120);
+ ili9342_write_cmd(spi, 0x29);
+}
+
+
+static inline void init_ili9342_hw (struct spi_device *spi) {
+ struct omap_dss_device *panel = spi->dev.platform_data;
+
+ gpio_set_value(panel->reset_gpio, 1);
+ mdelay(1);
+ gpio_set_value(panel->reset_gpio, 0);
+ mdelay(50);
+ gpio_set_value(panel->reset_gpio, 1);
+ mdelay(120);
+
+ ili9342_init_seq(spi);
+}
+
+static inline int init_ili9342_spi(struct spi_device *spi) {
+ struct omap_dss_device *panel = spi->dev.platform_data;
+ int reset_gpio;
+
+ if(!panel->reset_gpio) {
+ dev_err(&spi->dev, "platform data requires reset\n");
+ return -EINVAL;
+ }
+
+ reset_gpio = panel->reset_gpio;
+
+ if (!panel) {
+ dev_err(&spi->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ if(gpio_request_one(reset_gpio, GPIOF_OUT_INIT_LOW, "ili9342-reset")) {
+ dev_err(&spi->dev, "Could not request reset gpio %d", panel->reset_gpio);
+ return -EINVAL;
+ }
+
+ if(gpio_export(reset_gpio, 0)) {
+ dev_err(&spi->dev, "Could not export reset gpio %d", panel->reset_gpio);
+ return -EINVAL;
+ }
+
+ init_ili9342_hw (spi);
+
+ return 0;
+}
+
+static int ili9342_spi_probe(struct spi_device *spi)
+{
+ int err;
+ int ret;
+
+ spi_regulator = devm_regulator_get(&spi->dev, "vdd");
+
+ if (IS_ERR(spi_regulator)) {
+ dev_err(&spi->dev, "regulator get failed\n");
+ err = PTR_ERR(spi_regulator);
+ spi_regulator = NULL;
+ return err;
+ }
+
+ ret = regulator_enable(spi_regulator);
+
+ if (ret) {
+ dev_err(&spi->dev, "Failed to enable vdd: %d\n", ret);
+ return ret;
+ }
+
+ init_ili9342_spi(spi);
+
+ return omap_dss_register_driver(&ili9342_driver);
+}
+
+static int ili9342_spi_remove(struct spi_device *spi)
+{
+ oliodebug ("OLIO %s:%s Removing SPI\n", __FILE__, __FUNCTION__);
+
+ omap_dss_unregister_driver(&ili9342_driver);
+ return 0;
+}
+
+static int ili9342_suspend(struct device *dev) {
+ int ret;
+
+ oliodebug ("OLIO %s:%s Suspending SPI for %s\n", __FILE__, __FUNCTION__, dev_name(dev));
+
+ ret = regulator_disable(spi_regulator);
+ if (ret) {
+ dev_err(dev, "Failed to disable vdd:%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static int ili9342_resume(struct device *dev) {
+ int ret;
+
+ /* HACK WARNING: We need an spi_device here. If, as can be assumed,
+ * the device pointer passed in points to a device in an spi_device,
+ * it's the first device in the spi_device struct. In other words,
+ * it's address is the same as the spi_device and a cast should be OK.
+ */
+
+ /* IF, otoh, that's an incorrect assumption ... then this will lead
+ * to horrible crashes. But oh well, you can't win 'em all.
+ */
+
+ struct spi_device * spi = (struct spi_device *) dev;
+
+ oliodebug ("OLIO %s:%s Resuming SPI for %s\n", __FILE__, __FUNCTION__, dev_name(dev));
+
+ ret = regulator_enable(spi_regulator);
+
+ if (ret) {
+ dev_err(&spi->dev, "Failed to enable vdd: %d\n", ret);
+ return ret;
+ }
+
+ ili9342_init_seq(spi); /* init_ili9342_hw (spi); */
+
+ return 0;
+}
+
+
+static struct dev_pm_ops ili9342_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ili9342_suspend, ili9342_resume)
+};
+
+
+static struct spi_driver ili9342_spi_driver = {
+ .probe = ili9342_spi_probe,
+ .remove = ili9342_spi_remove,
+ .driver = {
+ .name = "ili9342-spi",
+ .owner = THIS_MODULE,
+ .pm = &ili9342_pm_ops,
+ },
+};
+
+module_spi_driver(ili9342_spi_driver);
+
+MODULE_AUTHOR("Evan Wilson <evan@oliodevice.com");
+MODULE_DESCRIPTION("ili9342 Display Controller Driver");
+MODULE_LICENSE("GPL");