diff options
Diffstat (limited to 'drivers/video/omap2/displays/panel-ili9342.c')
| -rw-r--r-- | drivers/video/omap2/displays/panel-ili9342.c | 598 |
1 files changed, 598 insertions, 0 deletions
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"); |