Commit 05cefbe2 authored by Tomi Valkeinen's avatar Tomi Valkeinen

OMAPDSS: remove all old panel drivers

The board files now use the new panel drivers, making the old panel
drivers obsolete.

Remove the old panel drivers, Kconfig and Makefile entries, and the
panels' platform data structs.
Signed-off-by: default avatarTomi Valkeinen <tomi.valkeinen@ti.com>
Reviewed-by: default avatarArchit Taneja <archit@ti.com>
parent 4123de21
......@@ -5,7 +5,6 @@ if ARCH_OMAP2PLUS
source "drivers/video/omap2/dss/Kconfig"
source "drivers/video/omap2/omapfb/Kconfig"
source "drivers/video/omap2/displays/Kconfig"
source "drivers/video/omap2/displays-new/Kconfig"
endif
obj-$(CONFIG_OMAP2_VRFB) += vrfb.o
obj-$(CONFIG_OMAP2_DSS) += dss/
obj-y += displays/
obj-y += displays-new/
obj-$(CONFIG_FB_OMAP2) += omapfb/
menu "OMAP2/3 Display Device Drivers (old device model)"
depends on OMAP2_DSS
config PANEL_GENERIC_DPI
tristate "Generic DPI Panel"
depends on OMAP2_DSS_DPI
help
Generic DPI panel driver.
Supports DVI output for Beagle and OMAP3 SDP.
Supports LCD Panel used in TI SDP3430 and EVM boards,
OMAP3517 EVM boards and CM-T35.
config PANEL_TFP410
tristate "TFP410 DPI-to-DVI chip"
depends on OMAP2_DSS_DPI && I2C
help
Driver for TFP410 DPI-to-DVI chip. The driver uses i2c to read EDID
information from the monitor.
config PANEL_LGPHILIPS_LB035Q02
tristate "LG.Philips LB035Q02 LCD Panel"
depends on OMAP2_DSS_DPI && SPI
help
LCD Panel used on the Gumstix Overo Palo35
config PANEL_SHARP_LS037V7DW01
tristate "Sharp LS037V7DW01 LCD Panel"
depends on OMAP2_DSS_DPI
depends on BACKLIGHT_CLASS_DEVICE
help
LCD Panel used in TI's SDP3430 and EVM boards
config PANEL_NEC_NL8048HL11_01B
tristate "NEC NL8048HL11-01B Panel"
depends on OMAP2_DSS_DPI
depends on SPI
depends on BACKLIGHT_CLASS_DEVICE
help
This NEC NL8048HL11-01B panel is TFT LCD
used in the Zoom2/3/3630 sdp boards.
config PANEL_PICODLP
tristate "TI PICO DLP mini-projector"
depends on OMAP2_DSS_DPI && I2C
help
A mini-projector used in TI's SDP4430 and EVM boards
For more info please visit http://www.dlp.com/projector/
config PANEL_TAAL
tristate "Taal DSI Panel"
depends on OMAP2_DSS_DSI
depends on BACKLIGHT_CLASS_DEVICE
help
Taal DSI command mode panel from TPO.
config PANEL_TPO_TD043MTEA1
tristate "TPO TD043MTEA1 LCD Panel"
depends on OMAP2_DSS_DPI && SPI
help
LCD Panel used in OMAP3 Pandora
config PANEL_ACX565AKM
tristate "ACX565AKM Panel"
depends on OMAP2_DSS_SDI && SPI
depends on BACKLIGHT_CLASS_DEVICE
help
This is the LCD panel used on Nokia N900
config PANEL_N8X0
tristate "N8X0 Panel"
depends on OMAP2_DSS_RFBI && SPI
depends on BACKLIGHT_CLASS_DEVICE
help
This is the LCD panel used on Nokia N8x0
endmenu
obj-$(CONFIG_PANEL_GENERIC_DPI) += panel-generic-dpi.o
obj-$(CONFIG_PANEL_TFP410) += panel-tfp410.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
obj-$(CONFIG_PANEL_TAAL) += panel-taal.o
obj-$(CONFIG_PANEL_PICODLP) += panel-picodlp.o
obj-$(CONFIG_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o
obj-$(CONFIG_PANEL_ACX565AKM) += panel-acx565akm.o
obj-$(CONFIG_PANEL_N8X0) += panel-n8x0.o
/*
* Support for ACX565AKM LCD Panel used on Nokia N900
*
* Copyright (C) 2010 Nokia Corporation
*
* Original Driver Author: Imre Deak <imre.deak@nokia.com>
* Based on panel-generic.c by Tomi Valkeinen <tomi.valkeinen@nokia.com>
* Adapted to new DSS2 framework: Roger Quadros <roger.quadros@nokia.com>
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/backlight.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
#define MIPID_CMD_READ_DISP_ID 0x04
#define MIPID_CMD_READ_RED 0x06
#define MIPID_CMD_READ_GREEN 0x07
#define MIPID_CMD_READ_BLUE 0x08
#define MIPID_CMD_READ_DISP_STATUS 0x09
#define MIPID_CMD_RDDSDR 0x0F
#define MIPID_CMD_SLEEP_IN 0x10
#define MIPID_CMD_SLEEP_OUT 0x11
#define MIPID_CMD_DISP_OFF 0x28
#define MIPID_CMD_DISP_ON 0x29
#define MIPID_CMD_WRITE_DISP_BRIGHTNESS 0x51
#define MIPID_CMD_READ_DISP_BRIGHTNESS 0x52
#define MIPID_CMD_WRITE_CTRL_DISP 0x53
#define CTRL_DISP_BRIGHTNESS_CTRL_ON (1 << 5)
#define CTRL_DISP_AMBIENT_LIGHT_CTRL_ON (1 << 4)
#define CTRL_DISP_BACKLIGHT_ON (1 << 2)
#define CTRL_DISP_AUTO_BRIGHTNESS_ON (1 << 1)
#define MIPID_CMD_READ_CTRL_DISP 0x54
#define MIPID_CMD_WRITE_CABC 0x55
#define MIPID_CMD_READ_CABC 0x56
#define MIPID_VER_LPH8923 3
#define MIPID_VER_LS041Y3 4
#define MIPID_VER_L4F00311 8
#define MIPID_VER_ACX565AKM 9
struct acx565akm_device {
char *name;
int enabled;
int model;
int revision;
u8 display_id[3];
unsigned has_bc:1;
unsigned has_cabc:1;
unsigned cabc_mode;
unsigned long hw_guard_end; /* next value of jiffies
when we can issue the
next sleep in/out command */
unsigned long hw_guard_wait; /* max guard time in jiffies */
struct spi_device *spi;
struct mutex mutex;
struct omap_dss_device *dssdev;
struct backlight_device *bl_dev;
};
static struct acx565akm_device acx_dev;
static int acx565akm_bl_update_status(struct backlight_device *dev);
/*--------------------MIPID interface-----------------------------*/
static void acx565akm_transfer(struct acx565akm_device *md, int cmd,
const u8 *wbuf, int wlen, u8 *rbuf, int rlen)
{
struct spi_message m;
struct spi_transfer *x, xfer[5];
int r;
BUG_ON(md->spi == NULL);
spi_message_init(&m);
memset(xfer, 0, sizeof(xfer));
x = &xfer[0];
cmd &= 0xff;
x->tx_buf = &cmd;
x->bits_per_word = 9;
x->len = 2;
if (rlen > 1 && wlen == 0) {
/*
* Between the command and the response data there is a
* dummy clock cycle. Add an extra bit after the command
* word to account for this.
*/
x->bits_per_word = 10;
cmd <<= 1;
}
spi_message_add_tail(x, &m);
if (wlen) {
x++;
x->tx_buf = wbuf;
x->len = wlen;
x->bits_per_word = 9;
spi_message_add_tail(x, &m);
}
if (rlen) {
x++;
x->rx_buf = rbuf;
x->len = rlen;
spi_message_add_tail(x, &m);
}
r = spi_sync(md->spi, &m);
if (r < 0)
dev_dbg(&md->spi->dev, "spi_sync %d\n", r);
}
static inline void acx565akm_cmd(struct acx565akm_device *md, int cmd)
{
acx565akm_transfer(md, cmd, NULL, 0, NULL, 0);
}
static inline void acx565akm_write(struct acx565akm_device *md,
int reg, const u8 *buf, int len)
{
acx565akm_transfer(md, reg, buf, len, NULL, 0);
}
static inline void acx565akm_read(struct acx565akm_device *md,
int reg, u8 *buf, int len)
{
acx565akm_transfer(md, reg, NULL, 0, buf, len);
}
static void hw_guard_start(struct acx565akm_device *md, int guard_msec)
{
md->hw_guard_wait = msecs_to_jiffies(guard_msec);
md->hw_guard_end = jiffies + md->hw_guard_wait;
}
static void hw_guard_wait(struct acx565akm_device *md)
{
unsigned long wait = md->hw_guard_end - jiffies;
if ((long)wait > 0 && wait <= md->hw_guard_wait) {
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(wait);
}
}
/*----------------------MIPID wrappers----------------------------*/
static void set_sleep_mode(struct acx565akm_device *md, int on)
{
int cmd;
if (on)
cmd = MIPID_CMD_SLEEP_IN;
else
cmd = MIPID_CMD_SLEEP_OUT;
/*
* We have to keep 120msec between sleep in/out commands.
* (8.2.15, 8.2.16).
*/
hw_guard_wait(md);
acx565akm_cmd(md, cmd);
hw_guard_start(md, 120);
}
static void set_display_state(struct acx565akm_device *md, int enabled)
{
int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF;
acx565akm_cmd(md, cmd);
}
static int panel_enabled(struct acx565akm_device *md)
{
u32 disp_status;
int enabled;
acx565akm_read(md, MIPID_CMD_READ_DISP_STATUS, (u8 *)&disp_status, 4);
disp_status = __be32_to_cpu(disp_status);
enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10));
dev_dbg(&md->spi->dev,
"LCD panel %senabled by bootloader (status 0x%04x)\n",
enabled ? "" : "not ", disp_status);
return enabled;
}
static int panel_detect(struct acx565akm_device *md)
{
acx565akm_read(md, MIPID_CMD_READ_DISP_ID, md->display_id, 3);
dev_dbg(&md->spi->dev, "MIPI display ID: %02x%02x%02x\n",
md->display_id[0], md->display_id[1], md->display_id[2]);
switch (md->display_id[0]) {
case 0x10:
md->model = MIPID_VER_ACX565AKM;
md->name = "acx565akm";
md->has_bc = 1;
md->has_cabc = 1;
break;
case 0x29:
md->model = MIPID_VER_L4F00311;
md->name = "l4f00311";
break;
case 0x45:
md->model = MIPID_VER_LPH8923;
md->name = "lph8923";
break;
case 0x83:
md->model = MIPID_VER_LS041Y3;
md->name = "ls041y3";
break;
default:
md->name = "unknown";
dev_err(&md->spi->dev, "invalid display ID\n");
return -ENODEV;
}
md->revision = md->display_id[1];
dev_info(&md->spi->dev, "omapfb: %s rev %02x LCD detected\n",
md->name, md->revision);
return 0;
}
/*----------------------Backlight Control-------------------------*/
static void enable_backlight_ctrl(struct acx565akm_device *md, int enable)
{
u16 ctrl;
acx565akm_read(md, MIPID_CMD_READ_CTRL_DISP, (u8 *)&ctrl, 1);
if (enable) {
ctrl |= CTRL_DISP_BRIGHTNESS_CTRL_ON |
CTRL_DISP_BACKLIGHT_ON;
} else {
ctrl &= ~(CTRL_DISP_BRIGHTNESS_CTRL_ON |
CTRL_DISP_BACKLIGHT_ON);
}
ctrl |= 1 << 8;
acx565akm_write(md, MIPID_CMD_WRITE_CTRL_DISP, (u8 *)&ctrl, 2);
}
static void set_cabc_mode(struct acx565akm_device *md, unsigned mode)
{
u16 cabc_ctrl;
md->cabc_mode = mode;
if (!md->enabled)
return;
cabc_ctrl = 0;
acx565akm_read(md, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1);
cabc_ctrl &= ~3;
cabc_ctrl |= (1 << 8) | (mode & 3);
acx565akm_write(md, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2);
}
static unsigned get_cabc_mode(struct acx565akm_device *md)
{
return md->cabc_mode;
}
static unsigned get_hw_cabc_mode(struct acx565akm_device *md)
{
u8 cabc_ctrl;
acx565akm_read(md, MIPID_CMD_READ_CABC, &cabc_ctrl, 1);
return cabc_ctrl & 3;
}
static void acx565akm_set_brightness(struct acx565akm_device *md, int level)
{
int bv;
bv = level | (1 << 8);
acx565akm_write(md, MIPID_CMD_WRITE_DISP_BRIGHTNESS, (u8 *)&bv, 2);
if (level)
enable_backlight_ctrl(md, 1);
else
enable_backlight_ctrl(md, 0);
}
static int acx565akm_get_actual_brightness(struct acx565akm_device *md)
{
u8 bv;
acx565akm_read(md, MIPID_CMD_READ_DISP_BRIGHTNESS, &bv, 1);
return bv;
}
static int acx565akm_bl_update_status(struct backlight_device *dev)
{
struct acx565akm_device *md = dev_get_drvdata(&dev->dev);
int r;
int level;
dev_dbg(&md->spi->dev, "%s\n", __func__);
mutex_lock(&md->mutex);
if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
dev->props.power == FB_BLANK_UNBLANK)
level = dev->props.brightness;
else
level = 0;
r = 0;
if (md->has_bc)
acx565akm_set_brightness(md, level);
else
r = -ENODEV;
mutex_unlock(&md->mutex);
return r;
}
static int acx565akm_bl_get_intensity(struct backlight_device *dev)
{
struct acx565akm_device *md = dev_get_drvdata(&dev->dev);
dev_dbg(&dev->dev, "%s\n", __func__);
if (!md->has_bc)
return -ENODEV;
if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
dev->props.power == FB_BLANK_UNBLANK) {
if (md->has_bc)
return acx565akm_get_actual_brightness(md);
else
return dev->props.brightness;
}
return 0;
}
static const struct backlight_ops acx565akm_bl_ops = {
.get_brightness = acx565akm_bl_get_intensity,
.update_status = acx565akm_bl_update_status,
};
/*--------------------Auto Brightness control via Sysfs---------------------*/
static const char *cabc_modes[] = {
"off", /* always used when CABC is not supported */
"ui",
"still-image",
"moving-image",
};
static ssize_t show_cabc_mode(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct acx565akm_device *md = dev_get_drvdata(dev);
const char *mode_str;
int mode;
int len;
if (!md->has_cabc)
mode = 0;
else
mode = get_cabc_mode(md);
mode_str = "unknown";
if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes))
mode_str = cabc_modes[mode];
len = snprintf(buf, PAGE_SIZE, "%s\n", mode_str);
return len < PAGE_SIZE - 1 ? len : PAGE_SIZE - 1;
}
static ssize_t store_cabc_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct acx565akm_device *md = dev_get_drvdata(dev);
int i;
for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) {
const char *mode_str = cabc_modes[i];
int cmp_len = strlen(mode_str);
if (count > 0 && buf[count - 1] == '\n')
count--;
if (count != cmp_len)
continue;
if (strncmp(buf, mode_str, cmp_len) == 0)
break;
}
if (i == ARRAY_SIZE(cabc_modes))
return -EINVAL;
if (!md->has_cabc && i != 0)
return -EINVAL;
mutex_lock(&md->mutex);
set_cabc_mode(md, i);
mutex_unlock(&md->mutex);
return count;
}
static ssize_t show_cabc_available_modes(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct acx565akm_device *md = dev_get_drvdata(dev);
int len;
int i;
if (!md->has_cabc)
return snprintf(buf, PAGE_SIZE, "%s\n", cabc_modes[0]);
for (i = 0, len = 0;
len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++)
len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s",
i ? " " : "", cabc_modes[i],
i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : "");
return len < PAGE_SIZE ? len : PAGE_SIZE - 1;
}
static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR,
show_cabc_mode, store_cabc_mode);
static DEVICE_ATTR(cabc_available_modes, S_IRUGO,
show_cabc_available_modes, NULL);
static struct attribute *bldev_attrs[] = {
&dev_attr_cabc_mode.attr,
&dev_attr_cabc_available_modes.attr,
NULL,
};
static struct attribute_group bldev_attr_group = {
.attrs = bldev_attrs,
};
/*---------------------------ACX Panel----------------------------*/
static int acx_get_recommended_bpp(struct omap_dss_device *dssdev)
{
return 16;
}
static struct omap_video_timings acx_panel_timings = {
.x_res = 800,
.y_res = 480,
.pixel_clock = 24000,
.hfp = 28,
.hsw = 4,
.hbp = 24,
.vfp = 3,
.vsw = 3,
.vbp = 4,
.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_OPPOSITE_EDGES,
};
static struct panel_acx565akm_data *get_panel_data(struct omap_dss_device *dssdev)
{
return (struct panel_acx565akm_data *) dssdev->data;
}
static int acx_panel_probe(struct omap_dss_device *dssdev)
{
int r;
struct acx565akm_device *md = &acx_dev;
struct panel_acx565akm_data *panel_data = get_panel_data(dssdev);
struct backlight_device *bldev;
int max_brightness, brightness;
struct backlight_properties props;
dev_dbg(dssdev->dev, "%s\n", __func__);
if (!panel_data)
return -EINVAL;
/* FIXME AC bias ? */
dssdev->panel.timings = acx_panel_timings;
if (gpio_is_valid(panel_data->reset_gpio)) {
r = devm_gpio_request_one(dssdev->dev, panel_data->reset_gpio,
GPIOF_OUT_INIT_LOW, "lcd reset");
if (r)
return r;
}
if (gpio_is_valid(panel_data->reset_gpio))
gpio_set_value(panel_data->reset_gpio, 1);
/*
* After reset we have to wait 5 msec before the first
* command can be sent.
*/
msleep(5);
md->enabled = panel_enabled(md);
r = panel_detect(md);
if (r) {
dev_err(dssdev->dev, "%s panel detect error\n", __func__);
if (!md->enabled && gpio_is_valid(panel_data->reset_gpio))
gpio_set_value(panel_data->reset_gpio, 0);
return r;
}
mutex_lock(&acx_dev.mutex);
acx_dev.dssdev = dssdev;
mutex_unlock(&acx_dev.mutex);
if (!md->enabled) {
if (gpio_is_valid(panel_data->reset_gpio))
gpio_set_value(panel_data->reset_gpio, 0);
}
/*------- Backlight control --------*/
memset(&props, 0, sizeof(props));
props.fb_blank = FB_BLANK_UNBLANK;
props.power = FB_BLANK_UNBLANK;
props.type = BACKLIGHT_RAW;
bldev = backlight_device_register("acx565akm", &md->spi->dev,
md, &acx565akm_bl_ops, &props);
md->bl_dev = bldev;
if (md->has_cabc) {
r = sysfs_create_group(&bldev->dev.kobj, &bldev_attr_group);
if (r) {
dev_err(&bldev->dev,
"%s failed to create sysfs files\n", __func__);
backlight_device_unregister(bldev);
return r;
}
md->cabc_mode = get_hw_cabc_mode(md);
}
max_brightness = 255;
if (md->has_bc)
brightness = acx565akm_get_actual_brightness(md);
else
brightness = 0;
bldev->props.max_brightness = max_brightness;
bldev->props.brightness = brightness;
acx565akm_bl_update_status(bldev);
return 0;
}
static void acx_panel_remove(struct omap_dss_device *dssdev)
{
struct acx565akm_device *md = &acx_dev;
dev_dbg(dssdev->dev, "%s\n", __func__);
sysfs_remove_group(&md->bl_dev->dev.kobj, &bldev_attr_group);
backlight_device_unregister(md->bl_dev);
mutex_lock(&acx_dev.mutex);
acx_dev.dssdev = NULL;
mutex_unlock(&acx_dev.mutex);
}
static int acx_panel_power_on(struct omap_dss_device *dssdev)
{
struct acx565akm_device *md = &acx_dev;
struct panel_acx565akm_data *panel_data = get_panel_data(dssdev);
int r;
dev_dbg(dssdev->dev, "%s\n", __func__);
if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
return 0;
mutex_lock(&md->mutex);
omapdss_sdi_set_timings(dssdev, &dssdev->panel.timings);
omapdss_sdi_set_datapairs(dssdev, dssdev->phy.sdi.datapairs);
r = omapdss_sdi_display_enable(dssdev);
if (r) {
pr_err("%s sdi enable failed\n", __func__);
goto fail_unlock;
}
/*FIXME tweak me */
msleep(50);
if (gpio_is_valid(panel_data->reset_gpio))
gpio_set_value(panel_data->reset_gpio, 1);
if (md->enabled) {
dev_dbg(&md->spi->dev, "panel already enabled\n");
mutex_unlock(&md->mutex);
return 0;
}
/*
* We have to meet all the following delay requirements:
* 1. tRW: reset pulse width 10usec (7.12.1)
* 2. tRT: reset cancel time 5msec (7.12.1)
* 3. Providing PCLK,HS,VS signals for 2 frames = ~50msec worst
* case (7.6.2)
* 4. 120msec before the sleep out command (7.12.1)
*/
msleep(120);
set_sleep_mode(md, 0);
md->enabled = 1;
/* 5msec between sleep out and the next command. (8.2.16) */
msleep(5);
set_display_state(md, 1);
set_cabc_mode(md, md->cabc_mode);
mutex_unlock(&md->mutex);
return acx565akm_bl_update_status(md->bl_dev);
fail_unlock:
mutex_unlock(&md->mutex);
return r;
}
static void acx_panel_power_off(struct omap_dss_device *dssdev)
{
struct acx565akm_device *md = &acx_dev;
struct panel_acx565akm_data *panel_data = get_panel_data(dssdev);
dev_dbg(dssdev->dev, "%s\n", __func__);
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
mutex_lock(&md->mutex);
if (!md->enabled) {
mutex_unlock(&md->mutex);
return;
}
set_display_state(md, 0);
set_sleep_mode(md, 1);
md->enabled = 0;
/*
* We have to provide PCLK,HS,VS signals for 2 frames (worst case
* ~50msec) after sending the sleep in command and asserting the
* reset signal. We probably could assert the reset w/o the delay
* but we still delay to avoid possible artifacts. (7.6.1)
*/
msleep(50);
if (gpio_is_valid(panel_data->reset_gpio))
gpio_set_value(panel_data->reset_gpio, 0);
/* FIXME need to tweak this delay */
msleep(100);
omapdss_sdi_display_disable(dssdev);
mutex_unlock(&md->mutex);
}
static int acx_panel_enable(struct omap_dss_device *dssdev)
{
int r;
dev_dbg(dssdev->dev, "%s\n", __func__);
r = acx_panel_power_on(dssdev);
if (r)
return r;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void acx_panel_disable(struct omap_dss_device *dssdev)
{
dev_dbg(dssdev->dev, "%s\n", __func__);
acx_panel_power_off(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void acx_panel_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
omapdss_sdi_set_timings(dssdev, timings);
dssdev->panel.timings = *timings;
}
static int acx_panel_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
return 0;
}
static struct omap_dss_driver acx_panel_driver = {
.probe = acx_panel_probe,
.remove = acx_panel_remove,
.enable = acx_panel_enable,
.disable = acx_panel_disable,
.set_timings = acx_panel_set_timings,
.check_timings = acx_panel_check_timings,
.get_recommended_bpp = acx_get_recommended_bpp,
.driver = {
.name = "panel-acx565akm",
.owner = THIS_MODULE,
},
};
/*--------------------SPI probe-------------------------*/
static int acx565akm_spi_probe(struct spi_device *spi)
{
struct acx565akm_device *md = &acx_dev;
dev_dbg(&spi->dev, "%s\n", __func__);
spi->mode = SPI_MODE_3;
md->spi = spi;
mutex_init(&md->mutex);
dev_set_drvdata(&spi->dev, md);
omap_dss_register_driver(&acx_panel_driver);
return 0;
}
static int acx565akm_spi_remove(struct spi_device *spi)
{
struct acx565akm_device *md = dev_get_drvdata(&spi->dev);
dev_dbg(&md->spi->dev, "%s\n", __func__);
omap_dss_unregister_driver(&acx_panel_driver);
return 0;
}
static struct spi_driver acx565akm_spi_driver = {
.driver = {
.name = "acx565akm",
.owner = THIS_MODULE,
},
.probe = acx565akm_spi_probe,
.remove = acx565akm_spi_remove,
};
module_spi_driver(acx565akm_spi_driver);
MODULE_AUTHOR("Nokia Corporation");
MODULE_DESCRIPTION("acx565akm LCD Driver");
MODULE_LICENSE("GPL");
/*
* Generic DPI Panels support
*
* Copyright (C) 2010 Canonical Ltd.
* Author: Bryan Wu <bryan.wu@canonical.com>
*
* LCD panel driver for Sharp LQ043T1DG01
*
* Copyright (C) 2009 Texas Instruments Inc
* Author: Vaibhav Hiremath <hvaibhav@ti.com>
*
* LCD panel driver for Toppoly TDO35S
*
* Copyright (C) 2009 CompuLab, Ltd.
* Author: Mike Rapoport <mike@compulab.co.il>
*
* Copyright (C) 2008 Nokia Corporation
* Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
*
* 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/gpio.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
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 generic_dpi_panels[] = {
/* Sharp LQ043T1DG01 */
{
{
.x_res = 480,
.y_res = 272,
.pixel_clock = 9000,
.hsw = 42,
.hfp = 3,
.hbp = 2,
.vsw = 11,
.vfp = 3,
.vbp = 2,
.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_LOW,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.power_on_delay = 50,
.power_off_delay = 100,
.name = "sharp_lq",
},
/* Sharp LS037V7DW01 */
{
{
.x_res = 480,
.y_res = 640,
.pixel_clock = 19200,
.hsw = 2,
.hfp = 1,
.hbp = 28,
.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_OPPOSITE_EDGES,
},
.power_on_delay = 50,
.power_off_delay = 100,
.name = "sharp_ls",
},
/* Toppoly TDO35S */
{
{
.x_res = 480,
.y_res = 640,
.pixel_clock = 26000,
.hfp = 104,
.hsw = 8,
.hbp = 8,
.vfp = 4,
.vsw = 2,
.vbp = 2,
.vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,
},
.power_on_delay = 0,
.power_off_delay = 0,
.name = "toppoly_tdo35s",
},
/* Samsung LTE430WQ-F0C */
{
{
.x_res = 480,
.y_res = 272,
.pixel_clock = 9200,
.hfp = 8,
.hsw = 41,
.hbp = 45 - 41,
.vfp = 4,
.vsw = 10,
.vbp = 12 - 10,
.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_OPPOSITE_EDGES,
},
.power_on_delay = 0,
.power_off_delay = 0,
.name = "samsung_lte430wq_f0c",
},
/* Seiko 70WVW1TZ3Z3 */
{
{
.x_res = 800,
.y_res = 480,
.pixel_clock = 33000,
.hsw = 128,
.hfp = 10,
.hbp = 10,
.vsw = 2,
.vfp = 4,
.vbp = 11,
.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_OPPOSITE_EDGES,
},
.power_on_delay = 0,
.power_off_delay = 0,
.name = "seiko_70wvw1tz3",
},
/* Powertip PH480272T */
{
{
.x_res = 480,
.y_res = 272,
.pixel_clock = 9000,
.hsw = 40,
.hfp = 2,
.hbp = 2,
.vsw = 10,
.vfp = 2,
.vbp = 2,
.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_LOW,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.power_on_delay = 0,
.power_off_delay = 0,
.name = "powertip_ph480272t",
},
/* Innolux AT070TN83 */
{
{
.x_res = 800,
.y_res = 480,
.pixel_clock = 40000,
.hsw = 48,
.hfp = 1,
.hbp = 1,
.vsw = 3,
.vfp = 12,
.vbp = 25,
.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_OPPOSITE_EDGES,
},
.power_on_delay = 0,
.power_off_delay = 0,
.name = "innolux_at070tn83",
},
/* NEC NL2432DR22-11B */
{
{
.x_res = 240,
.y_res = 320,
.pixel_clock = 5400,
.hsw = 3,
.hfp = 3,
.hbp = 39,
.vsw = 1,
.vfp = 2,
.vbp = 7,
.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_OPPOSITE_EDGES,
},
.name = "nec_nl2432dr22-11b",
},
/* Unknown panel used in OMAP H4 */
{
{
.x_res = 240,
.y_res = 320,
.pixel_clock = 6250,
.hsw = 15,
.hfp = 15,
.hbp = 60,
.vsw = 1,
.vfp = 1,
.vbp = 1,
.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.name = "h4",
},
/* FocalTech ETM070003DH6 */
{
{
.x_res = 800,
.y_res = 480,
.pixel_clock = 28000,
.hsw = 48,
.hfp = 40,
.hbp = 40,
.vsw = 3,
.vfp = 13,
.vbp = 29,
.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_OPPOSITE_EDGES,
},
.name = "focaltech_etm070003dh6",
},
/* Microtips Technologies - UMSH-8173MD */
{
{
.x_res = 800,
.y_res = 480,
.pixel_clock = 34560,
.hsw = 13,
.hfp = 101,
.hbp = 101,
.vsw = 23,
.vfp = 1,
.vbp = 1,
.vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.power_on_delay = 0,
.power_off_delay = 0,
.name = "microtips_umsh_8173md",
},
/* OrtusTech COM43H4M10XTC */
{
{
.x_res = 480,
.y_res = 272,
.pixel_clock = 8000,
.hsw = 41,
.hfp = 8,
.hbp = 4,
.vsw = 10,
.vfp = 4,
.vbp = 2,
.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.name = "ortustech_com43h4m10xtc",
},
/* Innolux AT080TN52 */
{
{
.x_res = 800,
.y_res = 600,
.pixel_clock = 41142,
.hsw = 20,
.hfp = 210,
.hbp = 46,
.vsw = 10,
.vfp = 12,
.vbp = 23,
.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_LOW,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.name = "innolux_at080tn52",
},
/* Mitsubishi AA084SB01 */
{
{
.x_res = 800,
.y_res = 600,
.pixel_clock = 40000,
.hsw = 1,
.hfp = 254,
.hbp = 1,
.vsw = 1,
.vfp = 26,
.vbp = 1,
.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.name = "mitsubishi_aa084sb01",
},
/* EDT ET0500G0DH6 */
{
{
.x_res = 800,
.y_res = 480,
.pixel_clock = 33260,
.hsw = 128,
.hfp = 216,
.hbp = 40,
.vsw = 2,
.vfp = 35,
.vbp = 10,
.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.name = "edt_et0500g0dh6",
},
/* Prime-View PD050VL1 */
{
{
.x_res = 640,
.y_res = 480,
.pixel_clock = 25000,
.hsw = 96,
.hfp = 18,
.hbp = 46,
.vsw = 2,
.vfp = 10,
.vbp = 33,
.vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.name = "primeview_pd050vl1",
},
/* Prime-View PM070WL4 */
{
{
.x_res = 800,
.y_res = 480,
.pixel_clock = 32000,
.hsw = 128,
.hfp = 42,
.hbp = 86,
.vsw = 2,
.vfp = 10,
.vbp = 33,
.vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.name = "primeview_pm070wl4",
},
/* Prime-View PD104SLF */
{
{
.x_res = 800,
.y_res = 600,
.pixel_clock = 40000,
.hsw = 128,
.hfp = 42,
.hbp = 86,
.vsw = 4,
.vfp = 1,
.vbp = 23,
.vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
},
.name = "primeview_pd104slf",
},
};
struct panel_drv_data {
struct omap_dss_device *dssdev;
struct panel_config *panel_config;
struct mutex lock;
};
static inline struct panel_generic_dpi_data
*get_panel_data(const struct omap_dss_device *dssdev)
{
return (struct panel_generic_dpi_data *) dssdev->data;
}
static int generic_dpi_panel_power_on(struct omap_dss_device *dssdev)
{
int r, i;
struct panel_generic_dpi_data *panel_data = get_panel_data(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 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);
for (i = 0; i < panel_data->num_gpios; ++i) {
gpio_set_value_cansleep(panel_data->gpios[i],
panel_data->gpio_invert[i] ? 0 : 1);
}
return 0;
err0:
return r;
}
static void generic_dpi_panel_power_off(struct omap_dss_device *dssdev)
{
struct panel_generic_dpi_data *panel_data = get_panel_data(dssdev);
struct panel_drv_data *drv_data = dev_get_drvdata(dssdev->dev);
struct panel_config *panel_config = drv_data->panel_config;
int i;
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
for (i = panel_data->num_gpios - 1; i >= 0; --i) {
gpio_set_value_cansleep(panel_data->gpios[i],
panel_data->gpio_invert[i] ? 1 : 0);
}
/* 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 generic_dpi_panel_probe(struct omap_dss_device *dssdev)
{
struct panel_generic_dpi_data *panel_data = get_panel_data(dssdev);
struct panel_config *panel_config = NULL;
struct panel_drv_data *drv_data = NULL;
int i, r;
dev_dbg(dssdev->dev, "probe\n");
if (!panel_data || !panel_data->name)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(generic_dpi_panels); i++) {
if (strcmp(panel_data->name, generic_dpi_panels[i].name) == 0) {
panel_config = &generic_dpi_panels[i];
break;
}
}
if (!panel_config)
return -EINVAL;
for (i = 0; i < panel_data->num_gpios; ++i) {
r = devm_gpio_request_one(dssdev->dev, panel_data->gpios[i],
panel_data->gpio_invert[i] ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
"panel gpio");
if (r)
return r;
}
dssdev->panel.timings = panel_config->timings;
drv_data = devm_kzalloc(dssdev->dev, 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 generic_dpi_panel_remove(struct omap_dss_device *dssdev)
{
dev_dbg(dssdev->dev, "remove\n");
dev_set_drvdata(dssdev->dev, NULL);
}
static int generic_dpi_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 = generic_dpi_panel_power_on(dssdev);
if (r)
goto err;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
err:
mutex_unlock(&drv_data->lock);
return r;
}
static void generic_dpi_panel_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *drv_data = dev_get_drvdata(dssdev->dev);
mutex_lock(&drv_data->lock);
generic_dpi_panel_power_off(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
mutex_unlock(&drv_data->lock);
}
static void generic_dpi_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 generic_dpi_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 generic_dpi_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 struct omap_dss_driver dpi_driver = {
.probe = generic_dpi_panel_probe,
.remove = __exit_p(generic_dpi_panel_remove),
.enable = generic_dpi_panel_enable,
.disable = generic_dpi_panel_disable,
.set_timings = generic_dpi_panel_set_timings,
.get_timings = generic_dpi_panel_get_timings,
.check_timings = generic_dpi_panel_check_timings,
.driver = {
.name = "generic_dpi_panel",
.owner = THIS_MODULE,
},
};
static int __init generic_dpi_panel_drv_init(void)
{
return omap_dss_register_driver(&dpi_driver);
}
static void __exit generic_dpi_panel_drv_exit(void)
{
omap_dss_unregister_driver(&dpi_driver);
}
module_init(generic_dpi_panel_drv_init);
module_exit(generic_dpi_panel_drv_exit);
MODULE_LICENSE("GPL");
/*
* LCD panel driver for LG.Philips LB035Q02
*
* Author: Steve Sakoman <steve@sakoman.com>
*
* 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/spi/spi.h>
#include <linux/mutex.h>
#include <linux/gpio.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
struct lb035q02_data {
struct mutex lock;
};
static struct omap_video_timings lb035q02_timings = {
.x_res = 320,
.y_res = 240,
.pixel_clock = 6500,
.hsw = 2,
.hfp = 20,
.hbp = 68,
.vsw = 2,
.vfp = 4,
.vbp = 18,
.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_OPPOSITE_EDGES,
};
static inline struct panel_generic_dpi_data
*get_panel_data(const struct omap_dss_device *dssdev)
{
return (struct panel_generic_dpi_data *) dssdev->data;
}
static int lb035q02_panel_power_on(struct omap_dss_device *dssdev)
{
struct panel_generic_dpi_data *panel_data = get_panel_data(dssdev);
int r, i;
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;
for (i = 0; i < panel_data->num_gpios; ++i) {
gpio_set_value_cansleep(panel_data->gpios[i],
panel_data->gpio_invert[i] ? 0 : 1);
}
return 0;
err0:
return r;
}
static void lb035q02_panel_power_off(struct omap_dss_device *dssdev)
{
struct panel_generic_dpi_data *panel_data = get_panel_data(dssdev);
int i;
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
for (i = panel_data->num_gpios - 1; i >= 0; --i) {
gpio_set_value_cansleep(panel_data->gpios[i],
panel_data->gpio_invert[i] ? 1 : 0);
}
omapdss_dpi_display_disable(dssdev);
}
static int lb035q02_panel_probe(struct omap_dss_device *dssdev)
{
struct panel_generic_dpi_data *panel_data = get_panel_data(dssdev);
struct lb035q02_data *ld;
int r, i;
if (!panel_data)
return -EINVAL;
dssdev->panel.timings = lb035q02_timings;
ld = devm_kzalloc(dssdev->dev, sizeof(*ld), GFP_KERNEL);
if (!ld)
return -ENOMEM;
for (i = 0; i < panel_data->num_gpios; ++i) {
r = devm_gpio_request_one(dssdev->dev, panel_data->gpios[i],
panel_data->gpio_invert[i] ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
"panel gpio");
if (r)
return r;
}
mutex_init(&ld->lock);
dev_set_drvdata(dssdev->dev, ld);
return 0;
}
static void lb035q02_panel_remove(struct omap_dss_device *dssdev)
{
}
static int lb035q02_panel_enable(struct omap_dss_device *dssdev)
{
struct lb035q02_data *ld = dev_get_drvdata(dssdev->dev);
int r;
mutex_lock(&ld->lock);
r = lb035q02_panel_power_on(dssdev);
if (r)
goto err;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
mutex_unlock(&ld->lock);
return 0;
err:
mutex_unlock(&ld->lock);
return r;
}
static void lb035q02_panel_disable(struct omap_dss_device *dssdev)
{
struct lb035q02_data *ld = dev_get_drvdata(dssdev->dev);
mutex_lock(&ld->lock);
lb035q02_panel_power_off(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
mutex_unlock(&ld->lock);
}
static struct omap_dss_driver lb035q02_driver = {
.probe = lb035q02_panel_probe,
.remove = lb035q02_panel_remove,
.enable = lb035q02_panel_enable,
.disable = lb035q02_panel_disable,
.driver = {
.name = "lgphilips_lb035q02_panel",
.owner = THIS_MODULE,
},
};
static int lb035q02_write_reg(struct spi_device *spi, u8 reg, u16 val)
{
struct spi_message msg;
struct spi_transfer index_xfer = {
.len = 3,
.cs_change = 1,
};
struct spi_transfer value_xfer = {
.len = 3,
};
u8 buffer[16];
spi_message_init(&msg);
/* register index */
buffer[0] = 0x70;
buffer[1] = 0x00;
buffer[2] = reg & 0x7f;
index_xfer.tx_buf = buffer;
spi_message_add_tail(&index_xfer, &msg);
/* register value */
buffer[4] = 0x72;
buffer[5] = val >> 8;
buffer[6] = val;
value_xfer.tx_buf = buffer + 4;
spi_message_add_tail(&value_xfer, &msg);
return spi_sync(spi, &msg);
}
static void init_lb035q02_panel(struct spi_device *spi)
{
/* Init sequence from page 28 of the lb035q02 spec */
lb035q02_write_reg(spi, 0x01, 0x6300);
lb035q02_write_reg(spi, 0x02, 0x0200);
lb035q02_write_reg(spi, 0x03, 0x0177);
lb035q02_write_reg(spi, 0x04, 0x04c7);
lb035q02_write_reg(spi, 0x05, 0xffc0);
lb035q02_write_reg(spi, 0x06, 0xe806);
lb035q02_write_reg(spi, 0x0a, 0x4008);
lb035q02_write_reg(spi, 0x0b, 0x0000);
lb035q02_write_reg(spi, 0x0d, 0x0030);
lb035q02_write_reg(spi, 0x0e, 0x2800);
lb035q02_write_reg(spi, 0x0f, 0x0000);
lb035q02_write_reg(spi, 0x16, 0x9f80);
lb035q02_write_reg(spi, 0x17, 0x0a0f);
lb035q02_write_reg(spi, 0x1e, 0x00c1);
lb035q02_write_reg(spi, 0x30, 0x0300);
lb035q02_write_reg(spi, 0x31, 0x0007);
lb035q02_write_reg(spi, 0x32, 0x0000);
lb035q02_write_reg(spi, 0x33, 0x0000);
lb035q02_write_reg(spi, 0x34, 0x0707);
lb035q02_write_reg(spi, 0x35, 0x0004);
lb035q02_write_reg(spi, 0x36, 0x0302);
lb035q02_write_reg(spi, 0x37, 0x0202);
lb035q02_write_reg(spi, 0x3a, 0x0a0d);
lb035q02_write_reg(spi, 0x3b, 0x0806);
}
static int lb035q02_panel_spi_probe(struct spi_device *spi)
{
init_lb035q02_panel(spi);
return omap_dss_register_driver(&lb035q02_driver);
}
static int lb035q02_panel_spi_remove(struct spi_device *spi)
{
omap_dss_unregister_driver(&lb035q02_driver);
return 0;
}
static struct spi_driver lb035q02_spi_driver = {
.driver = {
.name = "lgphilips_lb035q02_panel-spi",
.owner = THIS_MODULE,
},
.probe = lb035q02_panel_spi_probe,
.remove = lb035q02_panel_spi_remove,
};
module_spi_driver(lb035q02_spi_driver);
MODULE_LICENSE("GPL");
/* #define DEBUG */
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <linux/fb.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
#define BLIZZARD_REV_CODE 0x00
#define BLIZZARD_CONFIG 0x02
#define BLIZZARD_PLL_DIV 0x04
#define BLIZZARD_PLL_LOCK_RANGE 0x06
#define BLIZZARD_PLL_CLOCK_SYNTH_0 0x08
#define BLIZZARD_PLL_CLOCK_SYNTH_1 0x0a
#define BLIZZARD_PLL_MODE 0x0c
#define BLIZZARD_CLK_SRC 0x0e
#define BLIZZARD_MEM_BANK0_ACTIVATE 0x10
#define BLIZZARD_MEM_BANK0_STATUS 0x14
#define BLIZZARD_PANEL_CONFIGURATION 0x28
#define BLIZZARD_HDISP 0x2a
#define BLIZZARD_HNDP 0x2c
#define BLIZZARD_VDISP0 0x2e
#define BLIZZARD_VDISP1 0x30
#define BLIZZARD_VNDP 0x32
#define BLIZZARD_HSW 0x34
#define BLIZZARD_VSW 0x38
#define BLIZZARD_DISPLAY_MODE 0x68
#define BLIZZARD_INPUT_WIN_X_START_0 0x6c
#define BLIZZARD_DATA_SOURCE_SELECT 0x8e
#define BLIZZARD_DISP_MEM_DATA_PORT 0x90
#define BLIZZARD_DISP_MEM_READ_ADDR0 0x92
#define BLIZZARD_POWER_SAVE 0xE6
#define BLIZZARD_NDISP_CTRL_STATUS 0xE8
/* Data source select */
/* For S1D13745 */
#define BLIZZARD_SRC_WRITE_LCD_BACKGROUND 0x00
#define BLIZZARD_SRC_WRITE_LCD_DESTRUCTIVE 0x01
#define BLIZZARD_SRC_WRITE_OVERLAY_ENABLE 0x04
#define BLIZZARD_SRC_DISABLE_OVERLAY 0x05
/* For S1D13744 */
#define BLIZZARD_SRC_WRITE_LCD 0x00
#define BLIZZARD_SRC_BLT_LCD 0x06
#define BLIZZARD_COLOR_RGB565 0x01
#define BLIZZARD_COLOR_YUV420 0x09
#define BLIZZARD_VERSION_S1D13745 0x01 /* Hailstorm */
#define BLIZZARD_VERSION_S1D13744 0x02 /* Blizzard */
#define MIPID_CMD_READ_DISP_ID 0x04
#define MIPID_CMD_READ_RED 0x06
#define MIPID_CMD_READ_GREEN 0x07
#define MIPID_CMD_READ_BLUE 0x08
#define MIPID_CMD_READ_DISP_STATUS 0x09
#define MIPID_CMD_RDDSDR 0x0F
#define MIPID_CMD_SLEEP_IN 0x10
#define MIPID_CMD_SLEEP_OUT 0x11
#define MIPID_CMD_DISP_OFF 0x28
#define MIPID_CMD_DISP_ON 0x29
static struct panel_drv_data {
struct mutex lock;
struct omap_dss_device *dssdev;
struct spi_device *spidev;
int blizzard_ver;
} s_drv_data;
static inline
struct panel_n8x0_data *get_board_data(const struct omap_dss_device *dssdev)
{
return dssdev->data;
}
static inline
struct panel_drv_data *get_drv_data(const struct omap_dss_device *dssdev)
{
return &s_drv_data;
}
static inline void blizzard_cmd(u8 cmd)
{
omap_rfbi_write_command(&cmd, 1);
}
static inline void blizzard_write(u8 cmd, const u8 *buf, int len)
{
omap_rfbi_write_command(&cmd, 1);
omap_rfbi_write_data(buf, len);
}
static inline void blizzard_read(u8 cmd, u8 *buf, int len)
{
omap_rfbi_write_command(&cmd, 1);
omap_rfbi_read_data(buf, len);
}
static u8 blizzard_read_reg(u8 cmd)
{
u8 data;
blizzard_read(cmd, &data, 1);
return data;
}
static void blizzard_ctrl_setup_update(struct omap_dss_device *dssdev,
int x, int y, int w, int h)
{
struct panel_drv_data *ddata = get_drv_data(dssdev);
u8 tmp[18];
int x_end, y_end;
x_end = x + w - 1;
y_end = y + h - 1;
tmp[0] = x;
tmp[1] = x >> 8;
tmp[2] = y;
tmp[3] = y >> 8;
tmp[4] = x_end;
tmp[5] = x_end >> 8;
tmp[6] = y_end;
tmp[7] = y_end >> 8;
/* scaling? */
tmp[8] = x;
tmp[9] = x >> 8;
tmp[10] = y;
tmp[11] = y >> 8;
tmp[12] = x_end;
tmp[13] = x_end >> 8;
tmp[14] = y_end;
tmp[15] = y_end >> 8;
tmp[16] = BLIZZARD_COLOR_RGB565;
if (ddata->blizzard_ver == BLIZZARD_VERSION_S1D13745)
tmp[17] = BLIZZARD_SRC_WRITE_LCD_BACKGROUND;
else
tmp[17] = ddata->blizzard_ver == BLIZZARD_VERSION_S1D13744 ?
BLIZZARD_SRC_WRITE_LCD :
BLIZZARD_SRC_WRITE_LCD_DESTRUCTIVE;
omapdss_rfbi_set_pixel_size(dssdev, 16);
omapdss_rfbi_set_data_lines(dssdev, 8);
omap_rfbi_configure(dssdev);
blizzard_write(BLIZZARD_INPUT_WIN_X_START_0, tmp, 18);
omapdss_rfbi_set_pixel_size(dssdev, 16);
omapdss_rfbi_set_data_lines(dssdev, 16);
omap_rfbi_configure(dssdev);
}
static void mipid_transfer(struct spi_device *spi, int cmd, const u8 *wbuf,
int wlen, u8 *rbuf, int rlen)
{
struct spi_message m;
struct spi_transfer *x, xfer[4];
u16 w;
int r;
spi_message_init(&m);
memset(xfer, 0, sizeof(xfer));
x = &xfer[0];
cmd &= 0xff;
x->tx_buf = &cmd;
x->bits_per_word = 9;
x->len = 2;
spi_message_add_tail(x, &m);
if (wlen) {
x++;
x->tx_buf = wbuf;
x->len = wlen;
x->bits_per_word = 9;
spi_message_add_tail(x, &m);
}
if (rlen) {
x++;
x->rx_buf = &w;
x->len = 1;
spi_message_add_tail(x, &m);
if (rlen > 1) {
/* Arrange for the extra clock before the first
* data bit.
*/
x->bits_per_word = 9;
x->len = 2;
x++;
x->rx_buf = &rbuf[1];
x->len = rlen - 1;
spi_message_add_tail(x, &m);
}
}
r = spi_sync(spi, &m);
if (r < 0)
dev_dbg(&spi->dev, "spi_sync %d\n", r);
if (rlen)
rbuf[0] = w & 0xff;
}
static inline void mipid_cmd(struct spi_device *spi, int cmd)
{
mipid_transfer(spi, cmd, NULL, 0, NULL, 0);
}
static inline void mipid_write(struct spi_device *spi,
int reg, const u8 *buf, int len)
{
mipid_transfer(spi, reg, buf, len, NULL, 0);
}
static inline void mipid_read(struct spi_device *spi,
int reg, u8 *buf, int len)
{
mipid_transfer(spi, reg, NULL, 0, buf, len);
}
static void set_data_lines(struct spi_device *spi, int data_lines)
{
u16 par;
switch (data_lines) {
case 16:
par = 0x150;
break;
case 18:
par = 0x160;
break;
case 24:
par = 0x170;
break;
}
mipid_write(spi, 0x3a, (u8 *)&par, 2);
}
static void send_init_string(struct spi_device *spi)
{
u16 initpar[] = { 0x0102, 0x0100, 0x0100 };
mipid_write(spi, 0xc2, (u8 *)initpar, sizeof(initpar));
}
static void send_display_on(struct spi_device *spi)
{
mipid_cmd(spi, MIPID_CMD_DISP_ON);
}
static void send_display_off(struct spi_device *spi)
{
mipid_cmd(spi, MIPID_CMD_DISP_OFF);
}
static void send_sleep_out(struct spi_device *spi)
{
mipid_cmd(spi, MIPID_CMD_SLEEP_OUT);
msleep(120);
}
static void send_sleep_in(struct spi_device *spi)
{
mipid_cmd(spi, MIPID_CMD_SLEEP_IN);
msleep(50);
}
static int n8x0_panel_power_on(struct omap_dss_device *dssdev)
{
int r;
struct panel_n8x0_data *bdata = get_board_data(dssdev);
struct panel_drv_data *ddata = get_drv_data(dssdev);
struct spi_device *spi = ddata->spidev;
u8 rev, conf;
u8 display_id[3];
const char *panel_name;
if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
return 0;
gpio_direction_output(bdata->ctrl_pwrdown, 1);
omapdss_rfbi_set_size(dssdev, dssdev->panel.timings.x_res,
dssdev->panel.timings.y_res);
omapdss_rfbi_set_pixel_size(dssdev, dssdev->ctrl.pixel_size);
omapdss_rfbi_set_data_lines(dssdev, dssdev->phy.rfbi.data_lines);
omapdss_rfbi_set_interface_timings(dssdev, &dssdev->ctrl.rfbi_timings);
r = omapdss_rfbi_display_enable(dssdev);
if (r)
goto err_rfbi_en;
rev = blizzard_read_reg(BLIZZARD_REV_CODE);
conf = blizzard_read_reg(BLIZZARD_CONFIG);
switch (rev & 0xfc) {
case 0x9c:
ddata->blizzard_ver = BLIZZARD_VERSION_S1D13744;
dev_info(dssdev->dev, "s1d13744 LCD controller rev %d "
"initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07);
break;
case 0xa4:
ddata->blizzard_ver = BLIZZARD_VERSION_S1D13745;
dev_info(dssdev->dev, "s1d13745 LCD controller rev %d "
"initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07);
break;
default:
dev_err(dssdev->dev, "invalid s1d1374x revision %02x\n", rev);
r = -ENODEV;
goto err_inv_chip;
}
/* panel */
gpio_direction_output(bdata->panel_reset, 1);
mipid_read(spi, MIPID_CMD_READ_DISP_ID, display_id, 3);
dev_dbg(&spi->dev, "MIPI display ID: %02x%02x%02x\n",
display_id[0], display_id[1], display_id[2]);
switch (display_id[0]) {
case 0x45:
panel_name = "lph8923";
break;
case 0x83:
panel_name = "ls041y3";
break;
default:
dev_err(dssdev->dev, "invalid display ID 0x%x\n",
display_id[0]);
r = -ENODEV;
goto err_inv_panel;
}
dev_info(dssdev->dev, "%s rev %02x LCD detected\n",
panel_name, display_id[1]);
send_sleep_out(spi);
send_init_string(spi);
set_data_lines(spi, 24);
send_display_on(spi);
return 0;
err_inv_panel:
/*
* HACK: we should turn off the panel here, but there is some problem
* with the initialization sequence, and we fail to init the panel if we
* have turned it off
*/
/* gpio_direction_output(bdata->panel_reset, 0); */
err_inv_chip:
omapdss_rfbi_display_disable(dssdev);
err_rfbi_en:
gpio_direction_output(bdata->ctrl_pwrdown, 0);
return r;
}
static void n8x0_panel_power_off(struct omap_dss_device *dssdev)
{
struct panel_n8x0_data *bdata = get_board_data(dssdev);
struct panel_drv_data *ddata = get_drv_data(dssdev);
struct spi_device *spi = ddata->spidev;
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
send_display_off(spi);
send_sleep_in(spi);
/*
* HACK: we should turn off the panel here, but there is some problem
* with the initialization sequence, and we fail to init the panel if we
* have turned it off
*/
/* gpio_direction_output(bdata->panel_reset, 0); */
gpio_direction_output(bdata->ctrl_pwrdown, 0);
omapdss_rfbi_display_disable(dssdev);
}
static const struct rfbi_timings n8x0_panel_timings = {
.cs_on_time = 0,
.we_on_time = 9000,
.we_off_time = 18000,
.we_cycle_time = 36000,
.re_on_time = 9000,
.re_off_time = 27000,
.re_cycle_time = 36000,
.access_time = 27000,
.cs_off_time = 36000,
.cs_pulse_width = 0,
};
static int n8x0_panel_probe(struct omap_dss_device *dssdev)
{
struct panel_n8x0_data *bdata = get_board_data(dssdev);
struct panel_drv_data *ddata;
int r;
dev_dbg(dssdev->dev, "probe\n");
if (!bdata)
return -EINVAL;
s_drv_data.dssdev = dssdev;
ddata = &s_drv_data;
mutex_init(&ddata->lock);
dssdev->panel.timings.x_res = 800;
dssdev->panel.timings.y_res = 480;
dssdev->ctrl.pixel_size = 16;
dssdev->ctrl.rfbi_timings = n8x0_panel_timings;
dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE;
if (gpio_is_valid(bdata->panel_reset)) {
r = devm_gpio_request_one(dssdev->dev, bdata->panel_reset,
GPIOF_OUT_INIT_LOW, "PANEL RESET");
if (r)
return r;
}
if (gpio_is_valid(bdata->ctrl_pwrdown)) {
r = devm_gpio_request_one(dssdev->dev, bdata->ctrl_pwrdown,
GPIOF_OUT_INIT_LOW, "PANEL PWRDOWN");
if (r)
return r;
}
return 0;
}
static void n8x0_panel_remove(struct omap_dss_device *dssdev)
{
dev_dbg(dssdev->dev, "remove\n");
dev_set_drvdata(dssdev->dev, NULL);
}
static int n8x0_panel_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = get_drv_data(dssdev);
int r;
dev_dbg(dssdev->dev, "enable\n");
mutex_lock(&ddata->lock);
rfbi_bus_lock();
r = n8x0_panel_power_on(dssdev);
rfbi_bus_unlock();
if (r) {
mutex_unlock(&ddata->lock);
return r;
}
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
mutex_unlock(&ddata->lock);
return 0;
}
static void n8x0_panel_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = get_drv_data(dssdev);
dev_dbg(dssdev->dev, "disable\n");
mutex_lock(&ddata->lock);
rfbi_bus_lock();
n8x0_panel_power_off(dssdev);
rfbi_bus_unlock();
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
mutex_unlock(&ddata->lock);
}
static void n8x0_panel_get_resolution(struct omap_dss_device *dssdev,
u16 *xres, u16 *yres)
{
*xres = dssdev->panel.timings.x_res;
*yres = dssdev->panel.timings.y_res;
}
static void update_done(void *data)
{
rfbi_bus_unlock();
}
static int n8x0_panel_update(struct omap_dss_device *dssdev,
u16 x, u16 y, u16 w, u16 h)
{
struct panel_drv_data *ddata = get_drv_data(dssdev);
u16 dw, dh;
dev_dbg(dssdev->dev, "update\n");
dw = dssdev->panel.timings.x_res;
dh = dssdev->panel.timings.y_res;
if (x != 0 || y != 0 || w != dw || h != dh) {
dev_err(dssdev->dev, "invalid update region %d, %d, %d, %d\n",
x, y, w, h);
return -EINVAL;
}
mutex_lock(&ddata->lock);
rfbi_bus_lock();
blizzard_ctrl_setup_update(dssdev, x, y, w, h);
omap_rfbi_update(dssdev, update_done, NULL);
mutex_unlock(&ddata->lock);
return 0;
}
static int n8x0_panel_sync(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = get_drv_data(dssdev);
dev_dbg(dssdev->dev, "sync\n");
mutex_lock(&ddata->lock);
rfbi_bus_lock();
rfbi_bus_unlock();
mutex_unlock(&ddata->lock);
return 0;
}
static struct omap_dss_driver n8x0_panel_driver = {
.probe = n8x0_panel_probe,
.remove = n8x0_panel_remove,
.enable = n8x0_panel_enable,
.disable = n8x0_panel_disable,
.update = n8x0_panel_update,
.sync = n8x0_panel_sync,
.get_resolution = n8x0_panel_get_resolution,
.get_recommended_bpp = omapdss_default_get_recommended_bpp,
.driver = {
.name = "n8x0_panel",
.owner = THIS_MODULE,
},
};
/* PANEL */
static int mipid_spi_probe(struct spi_device *spi)
{
int r;
dev_dbg(&spi->dev, "mipid_spi_probe\n");
spi->mode = SPI_MODE_0;
s_drv_data.spidev = spi;
r = omap_dss_register_driver(&n8x0_panel_driver);
if (r)
pr_err("n8x0_panel: dss driver registration failed\n");
return r;
}
static int mipid_spi_remove(struct spi_device *spi)
{
dev_dbg(&spi->dev, "mipid_spi_remove\n");
omap_dss_unregister_driver(&n8x0_panel_driver);
return 0;
}
static struct spi_driver mipid_spi_driver = {
.driver = {
.name = "lcd_mipid",
.owner = THIS_MODULE,
},
.probe = mipid_spi_probe,
.remove = mipid_spi_remove,
};
module_spi_driver(mipid_spi_driver);
MODULE_LICENSE("GPL");
/*
* Support for NEC-nl8048hl11-01b panel driver
*
* Copyright (C) 2010 Texas Instruments Inc.
* Author: Erik Gilling <konkers@android.com>
* 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/spi/spi.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
#define LCD_XRES 800
#define LCD_YRES 480
/*
* NEC PIX Clock Ratings
* MIN:21.8MHz TYP:23.8MHz MAX:25.7MHz
*/
#define LCD_PIXEL_CLOCK 23800
static const struct {
unsigned char addr;
unsigned char dat;
} nec_8048_init_seq[] = {
{ 3, 0x01 }, { 0, 0x00 }, { 1, 0x01 }, { 4, 0x00 }, { 5, 0x14 },
{ 6, 0x24 }, { 16, 0xD7 }, { 17, 0x00 }, { 18, 0x00 }, { 19, 0x55 },
{ 20, 0x01 }, { 21, 0x70 }, { 22, 0x1E }, { 23, 0x25 }, { 24, 0x25 },
{ 25, 0x02 }, { 26, 0x02 }, { 27, 0xA0 }, { 32, 0x2F }, { 33, 0x0F },
{ 34, 0x0F }, { 35, 0x0F }, { 36, 0x0F }, { 37, 0x0F }, { 38, 0x0F },
{ 39, 0x00 }, { 40, 0x02 }, { 41, 0x02 }, { 42, 0x02 }, { 43, 0x0F },
{ 44, 0x0F }, { 45, 0x0F }, { 46, 0x0F }, { 47, 0x0F }, { 48, 0x0F },
{ 49, 0x0F }, { 50, 0x00 }, { 51, 0x02 }, { 52, 0x02 }, { 53, 0x02 },
{ 80, 0x0C }, { 83, 0x42 }, { 84, 0x42 }, { 85, 0x41 }, { 86, 0x14 },
{ 89, 0x88 }, { 90, 0x01 }, { 91, 0x00 }, { 92, 0x02 }, { 93, 0x0C },
{ 94, 0x1C }, { 95, 0x27 }, { 98, 0x49 }, { 99, 0x27 }, { 102, 0x76 },
{ 103, 0x27 }, { 112, 0x01 }, { 113, 0x0E }, { 114, 0x02 },
{ 115, 0x0C }, { 118, 0x0C }, { 121, 0x30 }, { 130, 0x00 },
{ 131, 0x00 }, { 132, 0xFC }, { 134, 0x00 }, { 136, 0x00 },
{ 138, 0x00 }, { 139, 0x00 }, { 140, 0x00 }, { 141, 0xFC },
{ 143, 0x00 }, { 145, 0x00 }, { 147, 0x00 }, { 148, 0x00 },
{ 149, 0x00 }, { 150, 0xFC }, { 152, 0x00 }, { 154, 0x00 },
{ 156, 0x00 }, { 157, 0x00 }, { 2, 0x00 },
};
/*
* NEC NL8048HL11-01B Manual
* defines HFB, HSW, HBP, VFP, VSW, VBP as shown below
*/
static struct omap_video_timings nec_8048_panel_timings = {
/* 800 x 480 @ 60 Hz Reduced blanking VESA CVT 0.31M3-R */
.x_res = LCD_XRES,
.y_res = LCD_YRES,
.pixel_clock = LCD_PIXEL_CLOCK,
.hfp = 6,
.hsw = 1,
.hbp = 4,
.vfp = 3,
.vsw = 1,
.vbp = 4,
.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,
};
static inline struct panel_nec_nl8048_data
*get_panel_data(const struct omap_dss_device *dssdev)
{
return (struct panel_nec_nl8048_data *) dssdev->data;
}
static int nec_8048_panel_probe(struct omap_dss_device *dssdev)
{
struct panel_nec_nl8048_data *pd = get_panel_data(dssdev);
int r;
if (!pd)
return -EINVAL;
dssdev->panel.timings = nec_8048_panel_timings;
if (gpio_is_valid(pd->qvga_gpio)) {
r = devm_gpio_request_one(dssdev->dev, pd->qvga_gpio,
GPIOF_OUT_INIT_HIGH, "lcd QVGA");
if (r)
return r;
}
if (gpio_is_valid(pd->res_gpio)) {
r = devm_gpio_request_one(dssdev->dev, pd->res_gpio,
GPIOF_OUT_INIT_LOW, "lcd RES");
if (r)
return r;
}
return 0;
}
static void nec_8048_panel_remove(struct omap_dss_device *dssdev)
{
}
static int nec_8048_panel_power_on(struct omap_dss_device *dssdev)
{
struct panel_nec_nl8048_data *pd = get_panel_data(dssdev);
int r;
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;
if (gpio_is_valid(pd->res_gpio))
gpio_set_value_cansleep(pd->res_gpio, 1);
return 0;
err0:
return r;
}
static void nec_8048_panel_power_off(struct omap_dss_device *dssdev)
{
struct panel_nec_nl8048_data *pd = get_panel_data(dssdev);
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
if (gpio_is_valid(pd->res_gpio))
gpio_set_value_cansleep(pd->res_gpio, 0);
omapdss_dpi_display_disable(dssdev);
}
static int nec_8048_panel_enable(struct omap_dss_device *dssdev)
{
int r;
r = nec_8048_panel_power_on(dssdev);
if (r)
return r;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void nec_8048_panel_disable(struct omap_dss_device *dssdev)
{
nec_8048_panel_power_off(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static int nec_8048_recommended_bpp(struct omap_dss_device *dssdev)
{
return 16;
}
static struct omap_dss_driver nec_8048_driver = {
.probe = nec_8048_panel_probe,
.remove = nec_8048_panel_remove,
.enable = nec_8048_panel_enable,
.disable = nec_8048_panel_disable,
.get_recommended_bpp = nec_8048_recommended_bpp,
.driver = {
.name = "NEC_8048_panel",
.owner = THIS_MODULE,
},
};
static int nec_8048_spi_send(struct spi_device *spi, unsigned char reg_addr,
unsigned char reg_data)
{
int ret = 0;
unsigned int cmd = 0, data = 0;
cmd = 0x0000 | reg_addr; /* register address write */
data = 0x0100 | reg_data ; /* register data write */
data = (cmd << 16) | data;
ret = spi_write(spi, (unsigned char *)&data, 4);
if (ret)
pr_err("error in spi_write %x\n", data);
return ret;
}
static int init_nec_8048_wvga_lcd(struct spi_device *spi)
{
unsigned int i;
/* Initialization Sequence */
/* nec_8048_spi_send(spi, REG, VAL) */
for (i = 0; i < (ARRAY_SIZE(nec_8048_init_seq) - 1); i++)
nec_8048_spi_send(spi, nec_8048_init_seq[i].addr,
nec_8048_init_seq[i].dat);
udelay(20);
nec_8048_spi_send(spi, nec_8048_init_seq[i].addr,
nec_8048_init_seq[i].dat);
return 0;
}
static int nec_8048_spi_probe(struct spi_device *spi)
{
spi->mode = SPI_MODE_0;
spi->bits_per_word = 32;
spi_setup(spi);
init_nec_8048_wvga_lcd(spi);
return omap_dss_register_driver(&nec_8048_driver);
}
static int nec_8048_spi_remove(struct spi_device *spi)
{
omap_dss_unregister_driver(&nec_8048_driver);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int nec_8048_spi_suspend(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
nec_8048_spi_send(spi, 2, 0x01);
mdelay(40);
return 0;
}
static int nec_8048_spi_resume(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
/* reinitialize the panel */
spi_setup(spi);
nec_8048_spi_send(spi, 2, 0x00);
init_nec_8048_wvga_lcd(spi);
return 0;
}
static SIMPLE_DEV_PM_OPS(nec_8048_spi_pm_ops, nec_8048_spi_suspend,
nec_8048_spi_resume);
#define NEC_8048_SPI_PM_OPS (&nec_8048_spi_pm_ops)
#else
#define NEC_8048_SPI_PM_OPS NULL
#endif
static struct spi_driver nec_8048_spi_driver = {
.probe = nec_8048_spi_probe,
.remove = nec_8048_spi_remove,
.driver = {
.name = "nec_8048_spi",
.owner = THIS_MODULE,
.pm = NEC_8048_SPI_PM_OPS,
},
};
module_spi_driver(nec_8048_spi_driver);
MODULE_AUTHOR("Erik Gilling <konkers@android.com>");
MODULE_DESCRIPTION("NEC-nl8048hl11-01b Driver");
MODULE_LICENSE("GPL");
/*
* picodlp panel driver
* picodlp_i2c_driver: i2c_client driver
*
* Copyright (C) 2009-2011 Texas Instruments
* Author: Mythri P K <mythripk@ti.com>
* Mayuresh Janorkar <mayur@ti.com>
*
* 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/input.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
#include "panel-picodlp.h"
struct picodlp_data {
struct mutex lock;
struct i2c_client *picodlp_i2c_client;
};
static struct i2c_board_info picodlp_i2c_board_info = {
I2C_BOARD_INFO("picodlp_i2c_driver", 0x1b),
};
struct picodlp_i2c_data {
struct mutex xfer_lock;
};
static struct i2c_device_id picodlp_i2c_id[] = {
{ "picodlp_i2c_driver", 0 },
{ }
};
struct picodlp_i2c_command {
u8 reg;
u32 value;
};
static struct omap_video_timings pico_ls_timings = {
.x_res = 864,
.y_res = 480,
.hsw = 7,
.hfp = 11,
.hbp = 7,
.pixel_clock = 19200,
.vsw = 2,
.vfp = 3,
.vbp = 14,
.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_FALLING_EDGE,
};
static inline struct picodlp_panel_data
*get_panel_data(const struct omap_dss_device *dssdev)
{
return (struct picodlp_panel_data *) dssdev->data;
}
static u32 picodlp_i2c_read(struct i2c_client *client, u8 reg)
{
u8 read_cmd[] = {READ_REG_SELECT, reg}, data[4];
struct picodlp_i2c_data *picodlp_i2c_data = i2c_get_clientdata(client);
struct i2c_msg msg[2];
mutex_lock(&picodlp_i2c_data->xfer_lock);
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 2;
msg[0].buf = read_cmd;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 4;
msg[1].buf = data;
i2c_transfer(client->adapter, msg, 2);
mutex_unlock(&picodlp_i2c_data->xfer_lock);
return (data[3] | (data[2] << 8) | (data[1] << 16) | (data[0] << 24));
}
static int picodlp_i2c_write_block(struct i2c_client *client,
u8 *data, int len)
{
struct i2c_msg msg;
int i, r, msg_count = 1;
struct picodlp_i2c_data *picodlp_i2c_data = i2c_get_clientdata(client);
if (len < 1 || len > 32) {
dev_err(&client->dev,
"too long syn_write_block len %d\n", len);
return -EIO;
}
mutex_lock(&picodlp_i2c_data->xfer_lock);
msg.addr = client->addr;
msg.flags = 0;
msg.len = len;
msg.buf = data;
r = i2c_transfer(client->adapter, &msg, msg_count);
mutex_unlock(&picodlp_i2c_data->xfer_lock);
/*
* i2c_transfer returns:
* number of messages sent in case of success
* a negative error number in case of failure
*/
if (r != msg_count)
goto err;
/* In case of success */
for (i = 0; i < len; i++)
dev_dbg(&client->dev,
"addr %x bw 0x%02x[%d]: 0x%02x\n",
client->addr, data[0] + i, i, data[i]);
return 0;
err:
dev_err(&client->dev, "picodlp_i2c_write error\n");
return r;
}
static int picodlp_i2c_write(struct i2c_client *client, u8 reg, u32 value)
{
u8 data[5];
int i;
data[0] = reg;
for (i = 1; i < 5; i++)
data[i] = (value >> (32 - (i) * 8)) & 0xFF;
return picodlp_i2c_write_block(client, data, 5);
}
static int picodlp_i2c_write_array(struct i2c_client *client,
const struct picodlp_i2c_command commands[],
int count)
{
int i, r = 0;
for (i = 0; i < count; i++) {
r = picodlp_i2c_write(client, commands[i].reg,
commands[i].value);
if (r)
return r;
}
return r;
}
static int picodlp_wait_for_dma_done(struct i2c_client *client)
{
u8 trial = 100;
do {
msleep(1);
if (!trial--)
return -ETIMEDOUT;
} while (picodlp_i2c_read(client, MAIN_STATUS) & DMA_STATUS);
return 0;
}
/**
* picodlp_i2c_init: i2c_initialization routine
* client: i2c_client for communication
*
* return
* 0 : Success, no error
* error code : Failure
*/
static int picodlp_i2c_init(struct i2c_client *client)
{
int r;
static const struct picodlp_i2c_command init_cmd_set1[] = {
{SOFT_RESET, 1},
{DMD_PARK_TRIGGER, 1},
{MISC_REG, 5},
{SEQ_CONTROL, 0},
{SEQ_VECTOR, 0x100},
{DMD_BLOCK_COUNT, 7},
{DMD_VCC_CONTROL, 0x109},
{DMD_PARK_PULSE_COUNT, 0xA},
{DMD_PARK_PULSE_WIDTH, 0xB},
{DMD_PARK_DELAY, 0x2ED},
{DMD_SHADOW_ENABLE, 0},
{FLASH_OPCODE, 0xB},
{FLASH_DUMMY_BYTES, 1},
{FLASH_ADDR_BYTES, 3},
{PBC_CONTROL, 0},
{FLASH_START_ADDR, CMT_LUT_0_START_ADDR},
{FLASH_READ_BYTES, CMT_LUT_0_SIZE},
{CMT_SPLASH_LUT_START_ADDR, 0},
{CMT_SPLASH_LUT_DEST_SELECT, CMT_LUT_ALL},
{PBC_CONTROL, 1},
};
static const struct picodlp_i2c_command init_cmd_set2[] = {
{PBC_CONTROL, 0},
{CMT_SPLASH_LUT_DEST_SELECT, 0},
{PBC_CONTROL, 0},
{FLASH_START_ADDR, SEQUENCE_0_START_ADDR},
{FLASH_READ_BYTES, SEQUENCE_0_SIZE},
{SEQ_RESET_LUT_START_ADDR, 0},
{SEQ_RESET_LUT_DEST_SELECT, SEQ_SEQ_LUT},
{PBC_CONTROL, 1},
};
static const struct picodlp_i2c_command init_cmd_set3[] = {
{PBC_CONTROL, 0},
{SEQ_RESET_LUT_DEST_SELECT, 0},
{PBC_CONTROL, 0},
{FLASH_START_ADDR, DRC_TABLE_0_START_ADDR},
{FLASH_READ_BYTES, DRC_TABLE_0_SIZE},
{SEQ_RESET_LUT_START_ADDR, 0},
{SEQ_RESET_LUT_DEST_SELECT, SEQ_DRC_LUT_ALL},
{PBC_CONTROL, 1},
};
static const struct picodlp_i2c_command init_cmd_set4[] = {
{PBC_CONTROL, 0},
{SEQ_RESET_LUT_DEST_SELECT, 0},
{SDC_ENABLE, 1},
{AGC_CTRL, 7},
{CCA_C1A, 0x100},
{CCA_C1B, 0x0},
{CCA_C1C, 0x0},
{CCA_C2A, 0x0},
{CCA_C2B, 0x100},
{CCA_C2C, 0x0},
{CCA_C3A, 0x0},
{CCA_C3B, 0x0},
{CCA_C3C, 0x100},
{CCA_C7A, 0x100},
{CCA_C7B, 0x100},
{CCA_C7C, 0x100},
{CCA_ENABLE, 1},
{CPU_IF_MODE, 1},
{SHORT_FLIP, 1},
{CURTAIN_CONTROL, 0},
{DMD_PARK_TRIGGER, 0},
{R_DRIVE_CURRENT, 0x298},
{G_DRIVE_CURRENT, 0x298},
{B_DRIVE_CURRENT, 0x298},
{RGB_DRIVER_ENABLE, 7},
{SEQ_CONTROL, 0},
{ACTGEN_CONTROL, 0x10},
{SEQUENCE_MODE, SEQ_LOCK},
{DATA_FORMAT, RGB888},
{INPUT_RESOLUTION, WVGA_864_LANDSCAPE},
{INPUT_SOURCE, PARALLEL_RGB},
{CPU_IF_SYNC_METHOD, 1},
{SEQ_CONTROL, 1}
};
r = picodlp_i2c_write_array(client, init_cmd_set1,
ARRAY_SIZE(init_cmd_set1));
if (r)
return r;
r = picodlp_wait_for_dma_done(client);
if (r)
return r;
r = picodlp_i2c_write_array(client, init_cmd_set2,
ARRAY_SIZE(init_cmd_set2));
if (r)
return r;
r = picodlp_wait_for_dma_done(client);
if (r)
return r;
r = picodlp_i2c_write_array(client, init_cmd_set3,
ARRAY_SIZE(init_cmd_set3));
if (r)
return r;
r = picodlp_wait_for_dma_done(client);
if (r)
return r;
r = picodlp_i2c_write_array(client, init_cmd_set4,
ARRAY_SIZE(init_cmd_set4));
if (r)
return r;
return 0;
}
static int picodlp_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct picodlp_i2c_data *picodlp_i2c_data;
picodlp_i2c_data = kzalloc(sizeof(struct picodlp_i2c_data), GFP_KERNEL);
if (!picodlp_i2c_data)
return -ENOMEM;
mutex_init(&picodlp_i2c_data->xfer_lock);
i2c_set_clientdata(client, picodlp_i2c_data);
return 0;
}
static int picodlp_i2c_remove(struct i2c_client *client)
{
struct picodlp_i2c_data *picodlp_i2c_data =
i2c_get_clientdata(client);
kfree(picodlp_i2c_data);
return 0;
}
static struct i2c_driver picodlp_i2c_driver = {
.driver = {
.name = "picodlp_i2c_driver",
},
.probe = picodlp_i2c_probe,
.remove = picodlp_i2c_remove,
.id_table = picodlp_i2c_id,
};
static int picodlp_panel_power_on(struct omap_dss_device *dssdev)
{
int r, trial = 100;
struct picodlp_data *picod = dev_get_drvdata(dssdev->dev);
struct picodlp_panel_data *picodlp_pdata = get_panel_data(dssdev);
gpio_set_value(picodlp_pdata->pwrgood_gpio, 0);
msleep(1);
gpio_set_value(picodlp_pdata->pwrgood_gpio, 1);
while (!gpio_get_value(picodlp_pdata->emu_done_gpio)) {
if (!trial--) {
dev_err(dssdev->dev, "emu_done signal not"
" going high\n");
return -ETIMEDOUT;
}
msleep(5);
}
/*
* As per dpp2600 programming guide,
* it is required to sleep for 1000ms after emu_done signal goes high
* then only i2c commands can be successfully sent to dpp2600
*/
msleep(1000);
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) {
dev_err(dssdev->dev, "failed to enable DPI\n");
goto err1;
}
r = picodlp_i2c_init(picod->picodlp_i2c_client);
if (r)
goto err;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return r;
err:
omapdss_dpi_display_disable(dssdev);
err1:
return r;
}
static void picodlp_panel_power_off(struct omap_dss_device *dssdev)
{
struct picodlp_panel_data *picodlp_pdata = get_panel_data(dssdev);
omapdss_dpi_display_disable(dssdev);
gpio_set_value(picodlp_pdata->emu_done_gpio, 0);
gpio_set_value(picodlp_pdata->pwrgood_gpio, 0);
}
static int picodlp_panel_probe(struct omap_dss_device *dssdev)
{
struct picodlp_data *picod;
struct picodlp_panel_data *picodlp_pdata = get_panel_data(dssdev);
struct i2c_adapter *adapter;
struct i2c_client *picodlp_i2c_client;
int r, picodlp_adapter_id;
dssdev->panel.timings = pico_ls_timings;
if (!picodlp_pdata)
return -EINVAL;
picod = devm_kzalloc(dssdev->dev, sizeof(*picod), GFP_KERNEL);
if (!picod)
return -ENOMEM;
mutex_init(&picod->lock);
picodlp_adapter_id = picodlp_pdata->picodlp_adapter_id;
adapter = i2c_get_adapter(picodlp_adapter_id);
if (!adapter) {
dev_err(dssdev->dev, "can't get i2c adapter\n");
return -ENODEV;
}
picodlp_i2c_client = i2c_new_device(adapter, &picodlp_i2c_board_info);
if (!picodlp_i2c_client) {
dev_err(dssdev->dev, "can't add i2c device::"
" picodlp_i2c_client is NULL\n");
return -ENODEV;
}
picod->picodlp_i2c_client = picodlp_i2c_client;
dev_set_drvdata(dssdev->dev, picod);
if (gpio_is_valid(picodlp_pdata->emu_done_gpio)) {
r = devm_gpio_request_one(dssdev->dev,
picodlp_pdata->emu_done_gpio,
GPIOF_IN, "DLP EMU DONE");
if (r)
return r;
}
if (gpio_is_valid(picodlp_pdata->pwrgood_gpio)) {
r = devm_gpio_request_one(dssdev->dev,
picodlp_pdata->pwrgood_gpio,
GPIOF_OUT_INIT_LOW, "DLP PWRGOOD");
if (r)
return r;
}
return 0;
}
static void picodlp_panel_remove(struct omap_dss_device *dssdev)
{
struct picodlp_data *picod = dev_get_drvdata(dssdev->dev);
i2c_unregister_device(picod->picodlp_i2c_client);
dev_set_drvdata(dssdev->dev, NULL);
dev_dbg(dssdev->dev, "removing picodlp panel\n");
}
static int picodlp_panel_enable(struct omap_dss_device *dssdev)
{
struct picodlp_data *picod = dev_get_drvdata(dssdev->dev);
int r;
dev_dbg(dssdev->dev, "enabling picodlp panel\n");
mutex_lock(&picod->lock);
if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) {
mutex_unlock(&picod->lock);
return -EINVAL;
}
r = picodlp_panel_power_on(dssdev);
mutex_unlock(&picod->lock);
return r;
}
static void picodlp_panel_disable(struct omap_dss_device *dssdev)
{
struct picodlp_data *picod = dev_get_drvdata(dssdev->dev);
mutex_lock(&picod->lock);
/* Turn off DLP Power */
if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
picodlp_panel_power_off(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
mutex_unlock(&picod->lock);
dev_dbg(dssdev->dev, "disabling picodlp panel\n");
}
static void picodlp_get_resolution(struct omap_dss_device *dssdev,
u16 *xres, u16 *yres)
{
*xres = dssdev->panel.timings.x_res;
*yres = dssdev->panel.timings.y_res;
}
static struct omap_dss_driver picodlp_driver = {
.probe = picodlp_panel_probe,
.remove = picodlp_panel_remove,
.enable = picodlp_panel_enable,
.disable = picodlp_panel_disable,
.get_resolution = picodlp_get_resolution,
.driver = {
.name = "picodlp_panel",
.owner = THIS_MODULE,
},
};
static int __init picodlp_init(void)
{
int r = 0;
r = i2c_add_driver(&picodlp_i2c_driver);
if (r) {
printk(KERN_WARNING "picodlp_i2c_driver" \
" registration failed\n");
return r;
}
r = omap_dss_register_driver(&picodlp_driver);
if (r)
i2c_del_driver(&picodlp_i2c_driver);
return r;
}
static void __exit picodlp_exit(void)
{
i2c_del_driver(&picodlp_i2c_driver);
omap_dss_unregister_driver(&picodlp_driver);
}
module_init(picodlp_init);
module_exit(picodlp_exit);
MODULE_AUTHOR("Mythri P K <mythripk@ti.com>");
MODULE_DESCRIPTION("picodlp driver");
MODULE_LICENSE("GPL");
/*
* Header file required by picodlp panel driver
*
* Copyright (C) 2009-2011 Texas Instruments
* Author: Mythri P K <mythripk@ti.com>
*
* 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/>.
*/
#ifndef __OMAP2_DISPLAY_PANEL_PICODLP_H
#define __OMAP2_DISPLAY_PANEL_PICODLP_H
/* Commands used for configuring picodlp panel */
#define MAIN_STATUS 0x03
#define PBC_CONTROL 0x08
#define INPUT_SOURCE 0x0B
#define INPUT_RESOLUTION 0x0C
#define DATA_FORMAT 0x0D
#define IMG_ROTATION 0x0E
#define LONG_FLIP 0x0F
#define SHORT_FLIP 0x10
#define TEST_PAT_SELECT 0x11
#define R_DRIVE_CURRENT 0x12
#define G_DRIVE_CURRENT 0x13
#define B_DRIVE_CURRENT 0x14
#define READ_REG_SELECT 0x15
#define RGB_DRIVER_ENABLE 0x16
#define CPU_IF_MODE 0x18
#define FRAME_RATE 0x19
#define CPU_IF_SYNC_METHOD 0x1A
#define CPU_IF_SOF 0x1B
#define CPU_IF_EOF 0x1C
#define CPU_IF_SLEEP 0x1D
#define SEQUENCE_MODE 0x1E
#define SOFT_RESET 0x1F
#define FRONT_END_RESET 0x21
#define AUTO_PWR_ENABLE 0x22
#define VSYNC_LINE_DELAY 0x23
#define CPU_PI_HORIZ_START 0x24
#define CPU_PI_VERT_START 0x25
#define CPU_PI_HORIZ_WIDTH 0x26
#define CPU_PI_VERT_HEIGHT 0x27
#define PIXEL_MASK_CROP 0x28
#define CROP_FIRST_LINE 0x29
#define CROP_LAST_LINE 0x2A
#define CROP_FIRST_PIXEL 0x2B
#define CROP_LAST_PIXEL 0x2C
#define DMD_PARK_TRIGGER 0x2D
#define MISC_REG 0x30
/* AGC registers */
#define AGC_CTRL 0x50
#define AGC_CLIPPED_PIXS 0x55
#define AGC_BRIGHT_PIXS 0x56
#define AGC_BG_PIXS 0x57
#define AGC_SAFETY_MARGIN 0x17
/* Color Coordinate Adjustment registers */
#define CCA_ENABLE 0x5E
#define CCA_C1A 0x5F
#define CCA_C1B 0x60
#define CCA_C1C 0x61
#define CCA_C2A 0x62
#define CCA_C2B 0x63
#define CCA_C2C 0x64
#define CCA_C3A 0x65
#define CCA_C3B 0x66
#define CCA_C3C 0x67
#define CCA_C7A 0x71
#define CCA_C7B 0x72
#define CCA_C7C 0x73
/**
* DLP Pico Processor 2600 comes with flash
* We can do DMA operations from flash for accessing Look Up Tables
*/
#define DMA_STATUS 0x100
#define FLASH_ADDR_BYTES 0x74
#define FLASH_DUMMY_BYTES 0x75
#define FLASH_WRITE_BYTES 0x76
#define FLASH_READ_BYTES 0x77
#define FLASH_OPCODE 0x78
#define FLASH_START_ADDR 0x79
#define FLASH_DUMMY2 0x7A
#define FLASH_WRITE_DATA 0x7B
#define TEMPORAL_DITH_DISABLE 0x7E
#define SEQ_CONTROL 0x82
#define SEQ_VECTOR 0x83
/* DMD is Digital Micromirror Device */
#define DMD_BLOCK_COUNT 0x84
#define DMD_VCC_CONTROL 0x86
#define DMD_PARK_PULSE_COUNT 0x87
#define DMD_PARK_PULSE_WIDTH 0x88
#define DMD_PARK_DELAY 0x89
#define DMD_SHADOW_ENABLE 0x8E
#define SEQ_STATUS 0x8F
#define FLASH_CLOCK_CONTROL 0x98
#define DMD_PARK 0x2D
#define SDRAM_BIST_ENABLE 0x46
#define DDR_DRIVER_STRENGTH 0x9A
#define SDC_ENABLE 0x9D
#define SDC_BUFF_SWAP_DISABLE 0xA3
#define CURTAIN_CONTROL 0xA6
#define DDR_BUS_SWAP_ENABLE 0xA7
#define DMD_TRC_ENABLE 0xA8
#define DMD_BUS_SWAP_ENABLE 0xA9
#define ACTGEN_ENABLE 0xAE
#define ACTGEN_CONTROL 0xAF
#define ACTGEN_HORIZ_BP 0xB0
#define ACTGEN_VERT_BP 0xB1
/* Look Up Table access */
#define CMT_SPLASH_LUT_START_ADDR 0xFA
#define CMT_SPLASH_LUT_DEST_SELECT 0xFB
#define CMT_SPLASH_LUT_DATA 0xFC
#define SEQ_RESET_LUT_START_ADDR 0xFD
#define SEQ_RESET_LUT_DEST_SELECT 0xFE
#define SEQ_RESET_LUT_DATA 0xFF
/* Input source definitions */
#define PARALLEL_RGB 0
#define INT_TEST_PATTERN 1
#define SPLASH_SCREEN 2
#define CPU_INTF 3
#define BT656 4
/* Standard input resolution definitions */
#define QWVGA_LANDSCAPE 3 /* (427h*240v) */
#define WVGA_864_LANDSCAPE 21 /* (864h*480v) */
#define WVGA_DMD_OPTICAL_TEST 35 /* (608h*684v) */
/* Standard data format definitions */
#define RGB565 0
#define RGB666 1
#define RGB888 2
/* Test Pattern definitions */
#define TPG_CHECKERBOARD 0
#define TPG_BLACK 1
#define TPG_WHITE 2
#define TPG_RED 3
#define TPG_BLUE 4
#define TPG_GREEN 5
#define TPG_VLINES_BLACK 6
#define TPG_HLINES_BLACK 7
#define TPG_VLINES_ALT 8
#define TPG_HLINES_ALT 9
#define TPG_DIAG_LINES 10
#define TPG_GREYRAMP_VERT 11
#define TPG_GREYRAMP_HORIZ 12
#define TPG_ANSI_CHECKERBOARD 13
/* sequence mode definitions */
#define SEQ_FREE_RUN 0
#define SEQ_LOCK 1
/* curtain color definitions */
#define CURTAIN_BLACK 0
#define CURTAIN_RED 1
#define CURTAIN_GREEN 2
#define CURTAIN_BLUE 3
#define CURTAIN_YELLOW 4
#define CURTAIN_MAGENTA 5
#define CURTAIN_CYAN 6
#define CURTAIN_WHITE 7
/* LUT definitions */
#define CMT_LUT_NONE 0
#define CMT_LUT_GREEN 1
#define CMT_LUT_RED 2
#define CMT_LUT_BLUE 3
#define CMT_LUT_ALL 4
#define SPLASH_LUT 5
#define SEQ_LUT_NONE 0
#define SEQ_DRC_LUT_0 1
#define SEQ_DRC_LUT_1 2
#define SEQ_DRC_LUT_2 3
#define SEQ_DRC_LUT_3 4
#define SEQ_SEQ_LUT 5
#define SEQ_DRC_LUT_ALL 6
#define WPC_PROGRAM_LUT 7
#define BITSTREAM_START_ADDR 0x00000000
#define BITSTREAM_SIZE 0x00040000
#define WPC_FW_0_START_ADDR 0x00040000
#define WPC_FW_0_SIZE 0x00000ce8
#define SEQUENCE_0_START_ADDR 0x00044000
#define SEQUENCE_0_SIZE 0x00001000
#define SEQUENCE_1_START_ADDR 0x00045000
#define SEQUENCE_1_SIZE 0x00000d10
#define SEQUENCE_2_START_ADDR 0x00046000
#define SEQUENCE_2_SIZE 0x00000d10
#define SEQUENCE_3_START_ADDR 0x00047000
#define SEQUENCE_3_SIZE 0x00000d10
#define SEQUENCE_4_START_ADDR 0x00048000
#define SEQUENCE_4_SIZE 0x00000d10
#define SEQUENCE_5_START_ADDR 0x00049000
#define SEQUENCE_5_SIZE 0x00000d10
#define SEQUENCE_6_START_ADDR 0x0004a000
#define SEQUENCE_6_SIZE 0x00000d10
#define CMT_LUT_0_START_ADDR 0x0004b200
#define CMT_LUT_0_SIZE 0x00000600
#define CMT_LUT_1_START_ADDR 0x0004b800
#define CMT_LUT_1_SIZE 0x00000600
#define CMT_LUT_2_START_ADDR 0x0004be00
#define CMT_LUT_2_SIZE 0x00000600
#define CMT_LUT_3_START_ADDR 0x0004c400
#define CMT_LUT_3_SIZE 0x00000600
#define CMT_LUT_4_START_ADDR 0x0004ca00
#define CMT_LUT_4_SIZE 0x00000600
#define CMT_LUT_5_START_ADDR 0x0004d000
#define CMT_LUT_5_SIZE 0x00000600
#define CMT_LUT_6_START_ADDR 0x0004d600
#define CMT_LUT_6_SIZE 0x00000600
#define DRC_TABLE_0_START_ADDR 0x0004dc00
#define DRC_TABLE_0_SIZE 0x00000100
#define SPLASH_0_START_ADDR 0x0004dd00
#define SPLASH_0_SIZE 0x00032280
#define SEQUENCE_7_START_ADDR 0x00080000
#define SEQUENCE_7_SIZE 0x00000d10
#define SEQUENCE_8_START_ADDR 0x00081800
#define SEQUENCE_8_SIZE 0x00000d10
#define SEQUENCE_9_START_ADDR 0x00083000
#define SEQUENCE_9_SIZE 0x00000d10
#define CMT_LUT_7_START_ADDR 0x0008e000
#define CMT_LUT_7_SIZE 0x00000600
#define CMT_LUT_8_START_ADDR 0x0008e800
#define CMT_LUT_8_SIZE 0x00000600
#define CMT_LUT_9_START_ADDR 0x0008f000
#define CMT_LUT_9_SIZE 0x00000600
#define SPLASH_1_START_ADDR 0x0009a000
#define SPLASH_1_SIZE 0x00032280
#define SPLASH_2_START_ADDR 0x000cd000
#define SPLASH_2_SIZE 0x00032280
#define SPLASH_3_START_ADDR 0x00100000
#define SPLASH_3_SIZE 0x00032280
#define OPT_SPLASH_0_START_ADDR 0x00134000
#define OPT_SPLASH_0_SIZE 0x000cb100
#endif
/*
* LCD panel driver for Sharp LS037V7DW01
*
* Copyright (C) 2008 Nokia Corporation
* Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
*
* 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/device.h>
#include <linux/fb.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
static struct omap_video_timings sharp_ls_timings = {
.x_res = 480,
.y_res = 640,
.pixel_clock = 19200,
.hsw = 2,
.hfp = 1,
.hbp = 28,
.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_OPPOSITE_EDGES,
};
static inline struct panel_sharp_ls037v7dw01_data
*get_panel_data(const struct omap_dss_device *dssdev)
{
return (struct panel_sharp_ls037v7dw01_data *) dssdev->data;
}
static int sharp_ls_panel_probe(struct omap_dss_device *dssdev)
{
struct panel_sharp_ls037v7dw01_data *pd = get_panel_data(dssdev);
int r;
if (!pd)
return -EINVAL;
dssdev->panel.timings = sharp_ls_timings;
if (gpio_is_valid(pd->mo_gpio)) {
r = devm_gpio_request_one(dssdev->dev, pd->mo_gpio,
GPIOF_OUT_INIT_LOW, "lcd MO");
if (r)
return r;
}
if (gpio_is_valid(pd->lr_gpio)) {
r = devm_gpio_request_one(dssdev->dev, pd->lr_gpio,
GPIOF_OUT_INIT_HIGH, "lcd LR");
if (r)
return r;
}
if (gpio_is_valid(pd->ud_gpio)) {
r = devm_gpio_request_one(dssdev->dev, pd->ud_gpio,
GPIOF_OUT_INIT_HIGH, "lcd UD");
if (r)
return r;
}
if (gpio_is_valid(pd->resb_gpio)) {
r = devm_gpio_request_one(dssdev->dev, pd->resb_gpio,
GPIOF_OUT_INIT_LOW, "lcd RESB");
if (r)
return r;
}
if (gpio_is_valid(pd->ini_gpio)) {
r = devm_gpio_request_one(dssdev->dev, pd->ini_gpio,
GPIOF_OUT_INIT_LOW, "lcd INI");
if (r)
return r;
}
return 0;
}
static void __exit sharp_ls_panel_remove(struct omap_dss_device *dssdev)
{
}
static int sharp_ls_power_on(struct omap_dss_device *dssdev)
{
struct panel_sharp_ls037v7dw01_data *pd = get_panel_data(dssdev);
int r = 0;
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 */
msleep(50);
if (gpio_is_valid(pd->resb_gpio))
gpio_set_value_cansleep(pd->resb_gpio, 1);
if (gpio_is_valid(pd->ini_gpio))
gpio_set_value_cansleep(pd->ini_gpio, 1);
return 0;
err0:
return r;
}
static void sharp_ls_power_off(struct omap_dss_device *dssdev)
{
struct panel_sharp_ls037v7dw01_data *pd = get_panel_data(dssdev);
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
if (gpio_is_valid(pd->ini_gpio))
gpio_set_value_cansleep(pd->ini_gpio, 0);
if (gpio_is_valid(pd->resb_gpio))
gpio_set_value_cansleep(pd->resb_gpio, 0);
/* wait at least 5 vsyncs after disabling the LCD */
msleep(100);
omapdss_dpi_display_disable(dssdev);
}
static int sharp_ls_panel_enable(struct omap_dss_device *dssdev)
{
int r;
r = sharp_ls_power_on(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return r;
}
static void sharp_ls_panel_disable(struct omap_dss_device *dssdev)
{
sharp_ls_power_off(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static struct omap_dss_driver sharp_ls_driver = {
.probe = sharp_ls_panel_probe,
.remove = __exit_p(sharp_ls_panel_remove),
.enable = sharp_ls_panel_enable,
.disable = sharp_ls_panel_disable,
.driver = {
.name = "sharp_ls_panel",
.owner = THIS_MODULE,
},
};
static int __init sharp_ls_panel_drv_init(void)
{
return omap_dss_register_driver(&sharp_ls_driver);
}
static void __exit sharp_ls_panel_drv_exit(void)
{
omap_dss_unregister_driver(&sharp_ls_driver);
}
module_init(sharp_ls_panel_drv_init);
module_exit(sharp_ls_panel_drv_exit);
MODULE_LICENSE("GPL");
/*
* Taal DSI command mode panel
*
* Copyright (C) 2009 Nokia Corporation
* Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
*
* 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/>.
*/
/*#define DEBUG*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/backlight.h>
#include <linux/fb.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
#include <video/mipi_display.h>
/* DSI Virtual channel. Hardcoded for now. */
#define TCH 0
#define DCS_READ_NUM_ERRORS 0x05
#define DCS_BRIGHTNESS 0x51
#define DCS_CTRL_DISPLAY 0x53
#define DCS_WRITE_CABC 0x55
#define DCS_READ_CABC 0x56
#define DCS_GET_ID1 0xda
#define DCS_GET_ID2 0xdb
#define DCS_GET_ID3 0xdc
static irqreturn_t taal_te_isr(int irq, void *data);
static void taal_te_timeout_work_callback(struct work_struct *work);
static int _taal_enable_te(struct omap_dss_device *dssdev, bool enable);
static int taal_panel_reset(struct omap_dss_device *dssdev);
struct taal_data {
struct mutex lock;
struct backlight_device *bldev;
unsigned long hw_guard_end; /* next value of jiffies when we can
* issue the next sleep in/out command
*/
unsigned long hw_guard_wait; /* max guard time in jiffies */
struct omap_dss_device *dssdev;
/* panel HW configuration from DT or platform data */
int reset_gpio;
int ext_te_gpio;
bool use_dsi_backlight;
struct omap_dsi_pin_config pin_config;
/* runtime variables */
bool enabled;
bool te_enabled;
atomic_t do_update;
int channel;
struct delayed_work te_timeout_work;
bool cabc_broken;
unsigned cabc_mode;
bool intro_printed;
struct workqueue_struct *workqueue;
struct delayed_work esd_work;
unsigned esd_interval;
bool ulps_enabled;
unsigned ulps_timeout;
struct delayed_work ulps_work;
};
static void taal_esd_work(struct work_struct *work);
static void taal_ulps_work(struct work_struct *work);
static void hw_guard_start(struct taal_data *td, int guard_msec)
{
td->hw_guard_wait = msecs_to_jiffies(guard_msec);
td->hw_guard_end = jiffies + td->hw_guard_wait;
}
static void hw_guard_wait(struct taal_data *td)
{
unsigned long wait = td->hw_guard_end - jiffies;
if ((long)wait > 0 && wait <= td->hw_guard_wait) {
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(wait);
}
}
static int taal_dcs_read_1(struct taal_data *td, u8 dcs_cmd, u8 *data)
{
int r;
u8 buf[1];
r = dsi_vc_dcs_read(td->dssdev, td->channel, dcs_cmd, buf, 1);
if (r < 0)
return r;
*data = buf[0];
return 0;
}
static int taal_dcs_write_0(struct taal_data *td, u8 dcs_cmd)
{
return dsi_vc_dcs_write(td->dssdev, td->channel, &dcs_cmd, 1);
}
static int taal_dcs_write_1(struct taal_data *td, u8 dcs_cmd, u8 param)
{
u8 buf[2];
buf[0] = dcs_cmd;
buf[1] = param;
return dsi_vc_dcs_write(td->dssdev, td->channel, buf, 2);
}
static int taal_sleep_in(struct taal_data *td)
{
u8 cmd;
int r;
hw_guard_wait(td);
cmd = MIPI_DCS_ENTER_SLEEP_MODE;
r = dsi_vc_dcs_write_nosync(td->dssdev, td->channel, &cmd, 1);
if (r)
return r;
hw_guard_start(td, 120);
msleep(5);
return 0;
}
static int taal_sleep_out(struct taal_data *td)
{
int r;
hw_guard_wait(td);
r = taal_dcs_write_0(td, MIPI_DCS_EXIT_SLEEP_MODE);
if (r)
return r;
hw_guard_start(td, 120);
msleep(5);
return 0;
}
static int taal_get_id(struct taal_data *td, u8 *id1, u8 *id2, u8 *id3)
{
int r;
r = taal_dcs_read_1(td, DCS_GET_ID1, id1);
if (r)
return r;
r = taal_dcs_read_1(td, DCS_GET_ID2, id2);
if (r)
return r;
r = taal_dcs_read_1(td, DCS_GET_ID3, id3);
if (r)
return r;
return 0;
}
static int taal_set_update_window(struct taal_data *td,
u16 x, u16 y, u16 w, u16 h)
{
int r;
u16 x1 = x;
u16 x2 = x + w - 1;
u16 y1 = y;
u16 y2 = y + h - 1;
u8 buf[5];
buf[0] = MIPI_DCS_SET_COLUMN_ADDRESS;
buf[1] = (x1 >> 8) & 0xff;
buf[2] = (x1 >> 0) & 0xff;
buf[3] = (x2 >> 8) & 0xff;
buf[4] = (x2 >> 0) & 0xff;
r = dsi_vc_dcs_write_nosync(td->dssdev, td->channel, buf, sizeof(buf));
if (r)
return r;
buf[0] = MIPI_DCS_SET_PAGE_ADDRESS;
buf[1] = (y1 >> 8) & 0xff;
buf[2] = (y1 >> 0) & 0xff;
buf[3] = (y2 >> 8) & 0xff;
buf[4] = (y2 >> 0) & 0xff;
r = dsi_vc_dcs_write_nosync(td->dssdev, td->channel, buf, sizeof(buf));
if (r)
return r;
dsi_vc_send_bta_sync(td->dssdev, td->channel);
return r;
}
static void taal_queue_esd_work(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
if (td->esd_interval > 0)
queue_delayed_work(td->workqueue, &td->esd_work,
msecs_to_jiffies(td->esd_interval));
}
static void taal_cancel_esd_work(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
cancel_delayed_work(&td->esd_work);
}
static void taal_queue_ulps_work(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
if (td->ulps_timeout > 0)
queue_delayed_work(td->workqueue, &td->ulps_work,
msecs_to_jiffies(td->ulps_timeout));
}
static void taal_cancel_ulps_work(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
cancel_delayed_work(&td->ulps_work);
}
static int taal_enter_ulps(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
if (td->ulps_enabled)
return 0;
taal_cancel_ulps_work(dssdev);
r = _taal_enable_te(dssdev, false);
if (r)
goto err;
if (gpio_is_valid(td->ext_te_gpio))
disable_irq(gpio_to_irq(td->ext_te_gpio));
omapdss_dsi_display_disable(dssdev, false, true);
td->ulps_enabled = true;
return 0;
err:
dev_err(dssdev->dev, "enter ULPS failed");
taal_panel_reset(dssdev);
td->ulps_enabled = false;
taal_queue_ulps_work(dssdev);
return r;
}
static int taal_exit_ulps(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
if (!td->ulps_enabled)
return 0;
r = omapdss_dsi_display_enable(dssdev);
if (r) {
dev_err(dssdev->dev, "failed to enable DSI\n");
goto err1;
}
omapdss_dsi_vc_enable_hs(dssdev, td->channel, true);
r = _taal_enable_te(dssdev, true);
if (r) {
dev_err(dssdev->dev, "failed to re-enable TE");
goto err2;
}
if (gpio_is_valid(td->ext_te_gpio))
enable_irq(gpio_to_irq(td->ext_te_gpio));
taal_queue_ulps_work(dssdev);
td->ulps_enabled = false;
return 0;
err2:
dev_err(dssdev->dev, "failed to exit ULPS");
r = taal_panel_reset(dssdev);
if (!r) {
if (gpio_is_valid(td->ext_te_gpio))
enable_irq(gpio_to_irq(td->ext_te_gpio));
td->ulps_enabled = false;
}
err1:
taal_queue_ulps_work(dssdev);
return r;
}
static int taal_wake_up(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
if (td->ulps_enabled)
return taal_exit_ulps(dssdev);
taal_cancel_ulps_work(dssdev);
taal_queue_ulps_work(dssdev);
return 0;
}
static int taal_bl_update_status(struct backlight_device *dev)
{
struct omap_dss_device *dssdev = dev_get_drvdata(&dev->dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
int level;
if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
dev->props.power == FB_BLANK_UNBLANK)
level = dev->props.brightness;
else
level = 0;
dev_dbg(dssdev->dev, "update brightness to %d\n", level);
mutex_lock(&td->lock);
if (td->enabled) {
dsi_bus_lock(dssdev);
r = taal_wake_up(dssdev);
if (!r)
r = taal_dcs_write_1(td, DCS_BRIGHTNESS, level);
dsi_bus_unlock(dssdev);
} else {
r = 0;
}
mutex_unlock(&td->lock);
return r;
}
static int taal_bl_get_intensity(struct backlight_device *dev)
{
if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
dev->props.power == FB_BLANK_UNBLANK)
return dev->props.brightness;
return 0;
}
static const struct backlight_ops taal_bl_ops = {
.get_brightness = taal_bl_get_intensity,
.update_status = taal_bl_update_status,
};
static void taal_get_resolution(struct omap_dss_device *dssdev,
u16 *xres, u16 *yres)
{
*xres = dssdev->panel.timings.x_res;
*yres = dssdev->panel.timings.y_res;
}
static ssize_t taal_num_errors_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
u8 errors = 0;
int r;
mutex_lock(&td->lock);
if (td->enabled) {
dsi_bus_lock(dssdev);
r = taal_wake_up(dssdev);
if (!r)
r = taal_dcs_read_1(td, DCS_READ_NUM_ERRORS, &errors);
dsi_bus_unlock(dssdev);
} else {
r = -ENODEV;
}
mutex_unlock(&td->lock);
if (r)
return r;
return snprintf(buf, PAGE_SIZE, "%d\n", errors);
}
static ssize_t taal_hw_revision_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
u8 id1, id2, id3;
int r;
mutex_lock(&td->lock);
if (td->enabled) {
dsi_bus_lock(dssdev);
r = taal_wake_up(dssdev);
if (!r)
r = taal_get_id(td, &id1, &id2, &id3);
dsi_bus_unlock(dssdev);
} else {
r = -ENODEV;
}
mutex_unlock(&td->lock);
if (r)
return r;
return snprintf(buf, PAGE_SIZE, "%02x.%02x.%02x\n", id1, id2, id3);
}
static const char *cabc_modes[] = {
"off", /* used also always when CABC is not supported */
"ui",
"still-image",
"moving-image",
};
static ssize_t show_cabc_mode(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
const char *mode_str;
int mode;
int len;
mode = td->cabc_mode;
mode_str = "unknown";
if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes))
mode_str = cabc_modes[mode];
len = snprintf(buf, PAGE_SIZE, "%s\n", mode_str);
return len < PAGE_SIZE - 1 ? len : PAGE_SIZE - 1;
}
static ssize_t store_cabc_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int i;
int r;
for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) {
if (sysfs_streq(cabc_modes[i], buf))
break;
}
if (i == ARRAY_SIZE(cabc_modes))
return -EINVAL;
mutex_lock(&td->lock);
if (td->enabled) {
dsi_bus_lock(dssdev);
if (!td->cabc_broken) {
r = taal_wake_up(dssdev);
if (r)
goto err;
r = taal_dcs_write_1(td, DCS_WRITE_CABC, i);
if (r)
goto err;
}
dsi_bus_unlock(dssdev);
}
td->cabc_mode = i;
mutex_unlock(&td->lock);
return count;
err:
dsi_bus_unlock(dssdev);
mutex_unlock(&td->lock);
return r;
}
static ssize_t show_cabc_available_modes(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int len;
int i;
for (i = 0, len = 0;
len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++)
len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s",
i ? " " : "", cabc_modes[i],
i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : "");
return len < PAGE_SIZE ? len : PAGE_SIZE - 1;
}
static ssize_t taal_store_esd_interval(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
unsigned long t;
int r;
r = kstrtoul(buf, 10, &t);
if (r)
return r;
mutex_lock(&td->lock);
taal_cancel_esd_work(dssdev);
td->esd_interval = t;
if (td->enabled)
taal_queue_esd_work(dssdev);
mutex_unlock(&td->lock);
return count;
}
static ssize_t taal_show_esd_interval(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
unsigned t;
mutex_lock(&td->lock);
t = td->esd_interval;
mutex_unlock(&td->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static ssize_t taal_store_ulps(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
unsigned long t;
int r;
r = kstrtoul(buf, 10, &t);
if (r)
return r;
mutex_lock(&td->lock);
if (td->enabled) {
dsi_bus_lock(dssdev);
if (t)
r = taal_enter_ulps(dssdev);
else
r = taal_wake_up(dssdev);
dsi_bus_unlock(dssdev);
}
mutex_unlock(&td->lock);
if (r)
return r;
return count;
}
static ssize_t taal_show_ulps(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
unsigned t;
mutex_lock(&td->lock);
t = td->ulps_enabled;
mutex_unlock(&td->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static ssize_t taal_store_ulps_timeout(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
unsigned long t;
int r;
r = kstrtoul(buf, 10, &t);
if (r)
return r;
mutex_lock(&td->lock);
td->ulps_timeout = t;
if (td->enabled) {
/* taal_wake_up will restart the timer */
dsi_bus_lock(dssdev);
r = taal_wake_up(dssdev);
dsi_bus_unlock(dssdev);
}
mutex_unlock(&td->lock);
if (r)
return r;
return count;
}
static ssize_t taal_show_ulps_timeout(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct omap_dss_device *dssdev = to_dss_device(dev);
struct taal_data *td = dev_get_drvdata(dssdev->dev);
unsigned t;
mutex_lock(&td->lock);
t = td->ulps_timeout;
mutex_unlock(&td->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static DEVICE_ATTR(num_dsi_errors, S_IRUGO, taal_num_errors_show, NULL);
static DEVICE_ATTR(hw_revision, S_IRUGO, taal_hw_revision_show, NULL);
static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR,
show_cabc_mode, store_cabc_mode);
static DEVICE_ATTR(cabc_available_modes, S_IRUGO,
show_cabc_available_modes, NULL);
static DEVICE_ATTR(esd_interval, S_IRUGO | S_IWUSR,
taal_show_esd_interval, taal_store_esd_interval);
static DEVICE_ATTR(ulps, S_IRUGO | S_IWUSR,
taal_show_ulps, taal_store_ulps);
static DEVICE_ATTR(ulps_timeout, S_IRUGO | S_IWUSR,
taal_show_ulps_timeout, taal_store_ulps_timeout);
static struct attribute *taal_attrs[] = {
&dev_attr_num_dsi_errors.attr,
&dev_attr_hw_revision.attr,
&dev_attr_cabc_mode.attr,
&dev_attr_cabc_available_modes.attr,
&dev_attr_esd_interval.attr,
&dev_attr_ulps.attr,
&dev_attr_ulps_timeout.attr,
NULL,
};
static struct attribute_group taal_attr_group = {
.attrs = taal_attrs,
};
static void taal_hw_reset(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
if (!gpio_is_valid(td->reset_gpio))
return;
gpio_set_value(td->reset_gpio, 1);
udelay(10);
/* reset the panel */
gpio_set_value(td->reset_gpio, 0);
/* assert reset */
udelay(10);
gpio_set_value(td->reset_gpio, 1);
/* wait after releasing reset */
msleep(5);
}
static void taal_probe_pdata(struct taal_data *td,
const struct nokia_dsi_panel_data *pdata)
{
td->reset_gpio = pdata->reset_gpio;
if (pdata->use_ext_te)
td->ext_te_gpio = pdata->ext_te_gpio;
else
td->ext_te_gpio = -1;
td->esd_interval = pdata->esd_interval;
td->ulps_timeout = pdata->ulps_timeout;
td->use_dsi_backlight = pdata->use_dsi_backlight;
td->pin_config = pdata->pin_config;
}
static int taal_probe(struct omap_dss_device *dssdev)
{
struct backlight_properties props;
struct taal_data *td;
struct backlight_device *bldev = NULL;
int r;
dev_dbg(dssdev->dev, "probe\n");
td = devm_kzalloc(dssdev->dev, sizeof(*td), GFP_KERNEL);
if (!td)
return -ENOMEM;
dev_set_drvdata(dssdev->dev, td);
td->dssdev = dssdev;
if (dssdev->data) {
const struct nokia_dsi_panel_data *pdata = dssdev->data;
taal_probe_pdata(td, pdata);
} else {
return -ENODEV;
}
dssdev->panel.timings.x_res = 864;
dssdev->panel.timings.y_res = 480;
dssdev->panel.timings.pixel_clock = DIV_ROUND_UP(864 * 480 * 60, 1000);
dssdev->panel.dsi_pix_fmt = OMAP_DSS_DSI_FMT_RGB888;
dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE |
OMAP_DSS_DISPLAY_CAP_TEAR_ELIM;
mutex_init(&td->lock);
atomic_set(&td->do_update, 0);
if (gpio_is_valid(td->reset_gpio)) {
r = devm_gpio_request_one(dssdev->dev, td->reset_gpio,
GPIOF_OUT_INIT_LOW, "taal rst");
if (r) {
dev_err(dssdev->dev, "failed to request reset gpio\n");
return r;
}
}
if (gpio_is_valid(td->ext_te_gpio)) {
r = devm_gpio_request_one(dssdev->dev, td->ext_te_gpio,
GPIOF_IN, "taal irq");
if (r) {
dev_err(dssdev->dev, "GPIO request failed\n");
return r;
}
r = devm_request_irq(dssdev->dev, gpio_to_irq(td->ext_te_gpio),
taal_te_isr,
IRQF_TRIGGER_RISING,
"taal vsync", dssdev);
if (r) {
dev_err(dssdev->dev, "IRQ request failed\n");
return r;
}
INIT_DEFERRABLE_WORK(&td->te_timeout_work,
taal_te_timeout_work_callback);
dev_dbg(dssdev->dev, "Using GPIO TE\n");
}
td->workqueue = create_singlethread_workqueue("taal_esd");
if (td->workqueue == NULL) {
dev_err(dssdev->dev, "can't create ESD workqueue\n");
return -ENOMEM;
}
INIT_DEFERRABLE_WORK(&td->esd_work, taal_esd_work);
INIT_DELAYED_WORK(&td->ulps_work, taal_ulps_work);
taal_hw_reset(dssdev);
if (td->use_dsi_backlight) {
memset(&props, 0, sizeof(struct backlight_properties));
props.max_brightness = 255;
props.type = BACKLIGHT_RAW;
bldev = backlight_device_register(dev_name(dssdev->dev),
dssdev->dev, dssdev, &taal_bl_ops, &props);
if (IS_ERR(bldev)) {
r = PTR_ERR(bldev);
goto err_bl;
}
td->bldev = bldev;
bldev->props.fb_blank = FB_BLANK_UNBLANK;
bldev->props.power = FB_BLANK_UNBLANK;
bldev->props.brightness = 255;
taal_bl_update_status(bldev);
}
r = omap_dsi_request_vc(dssdev, &td->channel);
if (r) {
dev_err(dssdev->dev, "failed to get virtual channel\n");
goto err_req_vc;
}
r = omap_dsi_set_vc_id(dssdev, td->channel, TCH);
if (r) {
dev_err(dssdev->dev, "failed to set VC_ID\n");
goto err_vc_id;
}
r = sysfs_create_group(&dssdev->dev->kobj, &taal_attr_group);
if (r) {
dev_err(dssdev->dev, "failed to create sysfs files\n");
goto err_vc_id;
}
return 0;
err_vc_id:
omap_dsi_release_vc(dssdev, td->channel);
err_req_vc:
if (bldev != NULL)
backlight_device_unregister(bldev);
err_bl:
destroy_workqueue(td->workqueue);
return r;
}
static void __exit taal_remove(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
struct backlight_device *bldev;
dev_dbg(dssdev->dev, "remove\n");
sysfs_remove_group(&dssdev->dev->kobj, &taal_attr_group);
omap_dsi_release_vc(dssdev, td->channel);
bldev = td->bldev;
if (bldev != NULL) {
bldev->props.power = FB_BLANK_POWERDOWN;
taal_bl_update_status(bldev);
backlight_device_unregister(bldev);
}
taal_cancel_ulps_work(dssdev);
taal_cancel_esd_work(dssdev);
destroy_workqueue(td->workqueue);
/* reset, to be sure that the panel is in a valid state */
taal_hw_reset(dssdev);
}
static int taal_power_on(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
u8 id1, id2, id3;
int r;
struct omap_dss_dsi_config dsi_config = {
.mode = OMAP_DSS_DSI_CMD_MODE,
.pixel_format = OMAP_DSS_DSI_FMT_RGB888,
.timings = &dssdev->panel.timings,
.hs_clk_min = 150000000,
.hs_clk_max = 300000000,
.lp_clk_min = 7000000,
.lp_clk_max = 10000000,
};
r = omapdss_dsi_configure_pins(dssdev, &td->pin_config);
if (r) {
dev_err(dssdev->dev, "failed to configure DSI pins\n");
goto err0;
};
r = omapdss_dsi_set_config(dssdev, &dsi_config);
if (r) {
dev_err(dssdev->dev, "failed to configure DSI\n");
goto err0;
}
r = omapdss_dsi_display_enable(dssdev);
if (r) {
dev_err(dssdev->dev, "failed to enable DSI\n");
goto err0;
}
taal_hw_reset(dssdev);
omapdss_dsi_vc_enable_hs(dssdev, td->channel, false);
r = taal_sleep_out(td);
if (r)
goto err;
r = taal_get_id(td, &id1, &id2, &id3);
if (r)
goto err;
/* on early Taal revisions CABC is broken */
if (id2 == 0x00 || id2 == 0xff || id2 == 0x81)
td->cabc_broken = true;
r = taal_dcs_write_1(td, DCS_BRIGHTNESS, 0xff);
if (r)
goto err;
r = taal_dcs_write_1(td, DCS_CTRL_DISPLAY,
(1<<2) | (1<<5)); /* BL | BCTRL */
if (r)
goto err;
r = taal_dcs_write_1(td, MIPI_DCS_SET_PIXEL_FORMAT,
MIPI_DCS_PIXEL_FMT_24BIT);
if (r)
goto err;
if (!td->cabc_broken) {
r = taal_dcs_write_1(td, DCS_WRITE_CABC, td->cabc_mode);
if (r)
goto err;
}
r = taal_dcs_write_0(td, MIPI_DCS_SET_DISPLAY_ON);
if (r)
goto err;
r = _taal_enable_te(dssdev, td->te_enabled);
if (r)
goto err;
r = dsi_enable_video_output(dssdev, td->channel);
if (r)
goto err;
td->enabled = 1;
if (!td->intro_printed) {
dev_info(dssdev->dev, "panel revision %02x.%02x.%02x\n",
id1, id2, id3);
if (td->cabc_broken)
dev_info(dssdev->dev,
"old Taal version, CABC disabled\n");
td->intro_printed = true;
}
omapdss_dsi_vc_enable_hs(dssdev, td->channel, true);
return 0;
err:
dev_err(dssdev->dev, "error while enabling panel, issuing HW reset\n");
taal_hw_reset(dssdev);
omapdss_dsi_display_disable(dssdev, true, false);
err0:
return r;
}
static void taal_power_off(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
dsi_disable_video_output(dssdev, td->channel);
r = taal_dcs_write_0(td, MIPI_DCS_SET_DISPLAY_OFF);
if (!r)
r = taal_sleep_in(td);
if (r) {
dev_err(dssdev->dev,
"error disabling panel, issuing HW reset\n");
taal_hw_reset(dssdev);
}
omapdss_dsi_display_disable(dssdev, true, false);
td->enabled = 0;
}
static int taal_panel_reset(struct omap_dss_device *dssdev)
{
dev_err(dssdev->dev, "performing LCD reset\n");
taal_power_off(dssdev);
taal_hw_reset(dssdev);
return taal_power_on(dssdev);
}
static int taal_enable(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
dev_dbg(dssdev->dev, "enable\n");
mutex_lock(&td->lock);
if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) {
r = -EINVAL;
goto err;
}
dsi_bus_lock(dssdev);
r = taal_power_on(dssdev);
dsi_bus_unlock(dssdev);
if (r)
goto err;
taal_queue_esd_work(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
mutex_unlock(&td->lock);
return 0;
err:
dev_dbg(dssdev->dev, "enable failed\n");
mutex_unlock(&td->lock);
return r;
}
static void taal_disable(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
dev_dbg(dssdev->dev, "disable\n");
mutex_lock(&td->lock);
taal_cancel_ulps_work(dssdev);
taal_cancel_esd_work(dssdev);
dsi_bus_lock(dssdev);
if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) {
int r;
r = taal_wake_up(dssdev);
if (!r)
taal_power_off(dssdev);
}
dsi_bus_unlock(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
mutex_unlock(&td->lock);
}
static void taal_framedone_cb(int err, void *data)
{
struct omap_dss_device *dssdev = data;
dev_dbg(dssdev->dev, "framedone, err %d\n", err);
dsi_bus_unlock(dssdev);
}
static irqreturn_t taal_te_isr(int irq, void *data)
{
struct omap_dss_device *dssdev = data;
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int old;
int r;
old = atomic_cmpxchg(&td->do_update, 1, 0);
if (old) {
cancel_delayed_work(&td->te_timeout_work);
r = omap_dsi_update(dssdev, td->channel, taal_framedone_cb,
dssdev);
if (r)
goto err;
}
return IRQ_HANDLED;
err:
dev_err(dssdev->dev, "start update failed\n");
dsi_bus_unlock(dssdev);
return IRQ_HANDLED;
}
static void taal_te_timeout_work_callback(struct work_struct *work)
{
struct taal_data *td = container_of(work, struct taal_data,
te_timeout_work.work);
struct omap_dss_device *dssdev = td->dssdev;
dev_err(dssdev->dev, "TE not received for 250ms!\n");
atomic_set(&td->do_update, 0);
dsi_bus_unlock(dssdev);
}
static int taal_update(struct omap_dss_device *dssdev,
u16 x, u16 y, u16 w, u16 h)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
dev_dbg(dssdev->dev, "update %d, %d, %d x %d\n", x, y, w, h);
mutex_lock(&td->lock);
dsi_bus_lock(dssdev);
r = taal_wake_up(dssdev);
if (r)
goto err;
if (!td->enabled) {
r = 0;
goto err;
}
/* XXX no need to send this every frame, but dsi break if not done */
r = taal_set_update_window(td, 0, 0,
dssdev->panel.timings.x_res,
dssdev->panel.timings.y_res);
if (r)
goto err;
if (td->te_enabled && gpio_is_valid(td->ext_te_gpio)) {
schedule_delayed_work(&td->te_timeout_work,
msecs_to_jiffies(250));
atomic_set(&td->do_update, 1);
} else {
r = omap_dsi_update(dssdev, td->channel, taal_framedone_cb,
dssdev);
if (r)
goto err;
}
/* note: no bus_unlock here. unlock is in framedone_cb */
mutex_unlock(&td->lock);
return 0;
err:
dsi_bus_unlock(dssdev);
mutex_unlock(&td->lock);
return r;
}
static int taal_sync(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
dev_dbg(dssdev->dev, "sync\n");
mutex_lock(&td->lock);
dsi_bus_lock(dssdev);
dsi_bus_unlock(dssdev);
mutex_unlock(&td->lock);
dev_dbg(dssdev->dev, "sync done\n");
return 0;
}
static int _taal_enable_te(struct omap_dss_device *dssdev, bool enable)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
if (enable)
r = taal_dcs_write_1(td, MIPI_DCS_SET_TEAR_ON, 0);
else
r = taal_dcs_write_0(td, MIPI_DCS_SET_TEAR_OFF);
if (!gpio_is_valid(td->ext_te_gpio))
omapdss_dsi_enable_te(dssdev, enable);
/* possible panel bug */
msleep(100);
return r;
}
static int taal_enable_te(struct omap_dss_device *dssdev, bool enable)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
mutex_lock(&td->lock);
if (td->te_enabled == enable)
goto end;
dsi_bus_lock(dssdev);
if (td->enabled) {
r = taal_wake_up(dssdev);
if (r)
goto err;
r = _taal_enable_te(dssdev, enable);
if (r)
goto err;
}
td->te_enabled = enable;
dsi_bus_unlock(dssdev);
end:
mutex_unlock(&td->lock);
return 0;
err:
dsi_bus_unlock(dssdev);
mutex_unlock(&td->lock);
return r;
}
static int taal_get_te(struct omap_dss_device *dssdev)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
int r;
mutex_lock(&td->lock);
r = td->te_enabled;
mutex_unlock(&td->lock);
return r;
}
static int taal_run_test(struct omap_dss_device *dssdev, int test_num)
{
struct taal_data *td = dev_get_drvdata(dssdev->dev);
u8 id1, id2, id3;
int r;
mutex_lock(&td->lock);
if (!td->enabled) {
r = -ENODEV;
goto err1;
}
dsi_bus_lock(dssdev);
r = taal_wake_up(dssdev);
if (r)
goto err2;
r = taal_dcs_read_1(td, DCS_GET_ID1, &id1);
if (r)
goto err2;
r = taal_dcs_read_1(td, DCS_GET_ID2, &id2);
if (r)
goto err2;
r = taal_dcs_read_1(td, DCS_GET_ID3, &id3);
if (r)
goto err2;
dsi_bus_unlock(dssdev);
mutex_unlock(&td->lock);
return 0;
err2:
dsi_bus_unlock(dssdev);
err1:
mutex_unlock(&td->lock);
return r;
}
static int taal_memory_read(struct omap_dss_device *dssdev,
void *buf, size_t size,
u16 x, u16 y, u16 w, u16 h)
{
int r;
int first = 1;
int plen;
unsigned buf_used = 0;
struct taal_data *td = dev_get_drvdata(dssdev->dev);
if (size < w * h * 3)
return -ENOMEM;
mutex_lock(&td->lock);
if (!td->enabled) {
r = -ENODEV;
goto err1;
}
size = min(w * h * 3,
dssdev->panel.timings.x_res *
dssdev->panel.timings.y_res * 3);
dsi_bus_lock(dssdev);
r = taal_wake_up(dssdev);
if (r)
goto err2;
/* plen 1 or 2 goes into short packet. until checksum error is fixed,
* use short packets. plen 32 works, but bigger packets seem to cause
* an error. */
if (size % 2)
plen = 1;
else
plen = 2;
taal_set_update_window(td, x, y, w, h);
r = dsi_vc_set_max_rx_packet_size(dssdev, td->channel, plen);
if (r)
goto err2;
while (buf_used < size) {
u8 dcs_cmd = first ? 0x2e : 0x3e;
first = 0;
r = dsi_vc_dcs_read(dssdev, td->channel, dcs_cmd,
buf + buf_used, size - buf_used);
if (r < 0) {
dev_err(dssdev->dev, "read error\n");
goto err3;
}
buf_used += r;
if (r < plen) {
dev_err(dssdev->dev, "short read\n");
break;
}
if (signal_pending(current)) {
dev_err(dssdev->dev, "signal pending, "
"aborting memory read\n");
r = -ERESTARTSYS;
goto err3;
}
}
r = buf_used;
err3:
dsi_vc_set_max_rx_packet_size(dssdev, td->channel, 1);
err2:
dsi_bus_unlock(dssdev);
err1:
mutex_unlock(&td->lock);
return r;
}
static void taal_ulps_work(struct work_struct *work)
{
struct taal_data *td = container_of(work, struct taal_data,
ulps_work.work);
struct omap_dss_device *dssdev = td->dssdev;
mutex_lock(&td->lock);
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || !td->enabled) {
mutex_unlock(&td->lock);
return;
}
dsi_bus_lock(dssdev);
taal_enter_ulps(dssdev);
dsi_bus_unlock(dssdev);
mutex_unlock(&td->lock);
}
static void taal_esd_work(struct work_struct *work)
{
struct taal_data *td = container_of(work, struct taal_data,
esd_work.work);
struct omap_dss_device *dssdev = td->dssdev;
u8 state1, state2;
int r;
mutex_lock(&td->lock);
if (!td->enabled) {
mutex_unlock(&td->lock);
return;
}
dsi_bus_lock(dssdev);
r = taal_wake_up(dssdev);
if (r) {
dev_err(dssdev->dev, "failed to exit ULPS\n");
goto err;
}
r = taal_dcs_read_1(td, MIPI_DCS_GET_DIAGNOSTIC_RESULT, &state1);
if (r) {
dev_err(dssdev->dev, "failed to read Taal status\n");
goto err;
}
/* Run self diagnostics */
r = taal_sleep_out(td);
if (r) {
dev_err(dssdev->dev, "failed to run Taal self-diagnostics\n");
goto err;
}
r = taal_dcs_read_1(td, MIPI_DCS_GET_DIAGNOSTIC_RESULT, &state2);
if (r) {
dev_err(dssdev->dev, "failed to read Taal status\n");
goto err;
}
/* Each sleep out command will trigger a self diagnostic and flip
* Bit6 if the test passes.
*/
if (!((state1 ^ state2) & (1 << 6))) {
dev_err(dssdev->dev, "LCD self diagnostics failed\n");
goto err;
}
/* Self-diagnostics result is also shown on TE GPIO line. We need
* to re-enable TE after self diagnostics */
if (td->te_enabled && gpio_is_valid(td->ext_te_gpio)) {
r = taal_dcs_write_1(td, MIPI_DCS_SET_TEAR_ON, 0);
if (r)
goto err;
}
dsi_bus_unlock(dssdev);
taal_queue_esd_work(dssdev);
mutex_unlock(&td->lock);
return;
err:
dev_err(dssdev->dev, "performing LCD reset\n");
taal_panel_reset(dssdev);
dsi_bus_unlock(dssdev);
taal_queue_esd_work(dssdev);
mutex_unlock(&td->lock);
}
static struct omap_dss_driver taal_driver = {
.probe = taal_probe,
.remove = __exit_p(taal_remove),
.enable = taal_enable,
.disable = taal_disable,
.update = taal_update,
.sync = taal_sync,
.get_resolution = taal_get_resolution,
.get_recommended_bpp = omapdss_default_get_recommended_bpp,
.enable_te = taal_enable_te,
.get_te = taal_get_te,
.run_test = taal_run_test,
.memory_read = taal_memory_read,
.driver = {
.name = "taal",
.owner = THIS_MODULE,
},
};
static int __init taal_init(void)
{
omap_dss_register_driver(&taal_driver);
return 0;
}
static void __exit taal_exit(void)
{
omap_dss_unregister_driver(&taal_driver);
}
module_init(taal_init);
module_exit(taal_exit);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@nokia.com>");
MODULE_DESCRIPTION("Taal Driver");
MODULE_LICENSE("GPL");
/*
* TFP410 DPI-to-DVI chip
*
* Copyright (C) 2011 Texas Instruments Inc
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*
* 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/slab.h>
#include <video/omapdss.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <drm/drm_edid.h>
#include <video/omap-panel-data.h>
static const struct omap_video_timings tfp410_default_timings = {
.x_res = 640,
.y_res = 480,
.pixel_clock = 23500,
.hfp = 48,
.hsw = 32,
.hbp = 80,
.vfp = 3,
.vsw = 4,
.vbp = 7,
.vsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.hsync_level = OMAPDSS_SIG_ACTIVE_HIGH,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
};
struct panel_drv_data {
struct omap_dss_device *dssdev;
struct mutex lock;
int pd_gpio;
struct i2c_adapter *i2c_adapter;
};
static int tfp410_power_on(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
int r;
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;
if (gpio_is_valid(ddata->pd_gpio))
gpio_set_value_cansleep(ddata->pd_gpio, 1);
return 0;
err0:
return r;
}
static void tfp410_power_off(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
if (gpio_is_valid(ddata->pd_gpio))
gpio_set_value_cansleep(ddata->pd_gpio, 0);
omapdss_dpi_display_disable(dssdev);
}
static int tfp410_probe(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata;
int r;
int i2c_bus_num;
ddata = devm_kzalloc(dssdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
dssdev->panel.timings = tfp410_default_timings;
ddata->dssdev = dssdev;
mutex_init(&ddata->lock);
if (dssdev->data) {
struct tfp410_platform_data *pdata = dssdev->data;
ddata->pd_gpio = pdata->power_down_gpio;
i2c_bus_num = pdata->i2c_bus_num;
} else {
ddata->pd_gpio = -1;
i2c_bus_num = -1;
}
if (gpio_is_valid(ddata->pd_gpio)) {
r = devm_gpio_request_one(dssdev->dev, ddata->pd_gpio,
GPIOF_OUT_INIT_LOW, "tfp410 pd");
if (r) {
dev_err(dssdev->dev, "Failed to request PD GPIO %d\n",
ddata->pd_gpio);
return r;
}
}
if (i2c_bus_num != -1) {
struct i2c_adapter *adapter;
adapter = i2c_get_adapter(i2c_bus_num);
if (!adapter) {
dev_err(dssdev->dev, "Failed to get I2C adapter, bus %d\n",
i2c_bus_num);
return -EPROBE_DEFER;
}
ddata->i2c_adapter = adapter;
}
dev_set_drvdata(dssdev->dev, ddata);
return 0;
}
static void __exit tfp410_remove(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
mutex_lock(&ddata->lock);
if (ddata->i2c_adapter)
i2c_put_adapter(ddata->i2c_adapter);
dev_set_drvdata(dssdev->dev, NULL);
mutex_unlock(&ddata->lock);
}
static int tfp410_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
int r;
mutex_lock(&ddata->lock);
r = tfp410_power_on(dssdev);
if (r == 0)
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
mutex_unlock(&ddata->lock);
return r;
}
static void tfp410_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
mutex_lock(&ddata->lock);
tfp410_power_off(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
mutex_unlock(&ddata->lock);
}
static void tfp410_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
mutex_lock(&ddata->lock);
omapdss_dpi_set_timings(dssdev, timings);
dssdev->panel.timings = *timings;
mutex_unlock(&ddata->lock);
}
static void tfp410_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
mutex_lock(&ddata->lock);
*timings = dssdev->panel.timings;
mutex_unlock(&ddata->lock);
}
static int tfp410_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
int r;
mutex_lock(&ddata->lock);
r = dpi_check_timings(dssdev, timings);
mutex_unlock(&ddata->lock);
return r;
}
static int tfp410_ddc_read(struct i2c_adapter *adapter,
unsigned char *buf, u16 count, u8 offset)
{
int r, retries;
for (retries = 3; retries > 0; retries--) {
struct i2c_msg msgs[] = {
{
.addr = DDC_ADDR,
.flags = 0,
.len = 1,
.buf = &offset,
}, {
.addr = DDC_ADDR,
.flags = I2C_M_RD,
.len = count,
.buf = buf,
}
};
r = i2c_transfer(adapter, msgs, 2);
if (r == 2)
return 0;
if (r != -EAGAIN)
break;
}
return r < 0 ? r : -EIO;
}
static int tfp410_read_edid(struct omap_dss_device *dssdev,
u8 *edid, int len)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
int r, l, bytes_read;
mutex_lock(&ddata->lock);
if (!ddata->i2c_adapter) {
r = -ENODEV;
goto err;
}
l = min(EDID_LENGTH, len);
r = tfp410_ddc_read(ddata->i2c_adapter, edid, l, 0);
if (r)
goto err;
bytes_read = l;
/* if there are extensions, read second block */
if (len > EDID_LENGTH && edid[0x7e] > 0) {
l = min(EDID_LENGTH, len - EDID_LENGTH);
r = tfp410_ddc_read(ddata->i2c_adapter, edid + EDID_LENGTH,
l, EDID_LENGTH);
if (r)
goto err;
bytes_read += l;
}
mutex_unlock(&ddata->lock);
return bytes_read;
err:
mutex_unlock(&ddata->lock);
return r;
}
static bool tfp410_detect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
unsigned char out;
int r;
mutex_lock(&ddata->lock);
if (!ddata->i2c_adapter)
goto out;
r = tfp410_ddc_read(ddata->i2c_adapter, &out, 1, 0);
mutex_unlock(&ddata->lock);
return r == 0;
out:
mutex_unlock(&ddata->lock);
return true;
}
static struct omap_dss_driver tfp410_driver = {
.probe = tfp410_probe,
.remove = __exit_p(tfp410_remove),
.enable = tfp410_enable,
.disable = tfp410_disable,
.set_timings = tfp410_set_timings,
.get_timings = tfp410_get_timings,
.check_timings = tfp410_check_timings,
.read_edid = tfp410_read_edid,
.detect = tfp410_detect,
.driver = {
.name = "tfp410",
.owner = THIS_MODULE,
},
};
static int __init tfp410_init(void)
{
return omap_dss_register_driver(&tfp410_driver);
}
static void __exit tfp410_exit(void)
{
omap_dss_unregister_driver(&tfp410_driver);
}
module_init(tfp410_init);
module_exit(tfp410_exit);
MODULE_LICENSE("GPL");
/*
* LCD panel driver for TPO TD043MTEA1
*
* Author: Gražvydas Ignotas <notasas@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/regulator/consumer.h>
#include <linux/gpio.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
#define TPO_R02_MODE(x) ((x) & 7)
#define TPO_R02_MODE_800x480 7
#define TPO_R02_NCLK_RISING BIT(3)
#define TPO_R02_HSYNC_HIGH BIT(4)
#define TPO_R02_VSYNC_HIGH BIT(5)
#define TPO_R03_NSTANDBY BIT(0)
#define TPO_R03_EN_CP_CLK BIT(1)
#define TPO_R03_EN_VGL_PUMP BIT(2)
#define TPO_R03_EN_PWM BIT(3)
#define TPO_R03_DRIVING_CAP_100 BIT(4)
#define TPO_R03_EN_PRE_CHARGE BIT(6)
#define TPO_R03_SOFTWARE_CTL BIT(7)
#define TPO_R04_NFLIP_H BIT(0)
#define TPO_R04_NFLIP_V BIT(1)
#define TPO_R04_CP_CLK_FREQ_1H BIT(2)
#define TPO_R04_VGL_FREQ_1H BIT(4)
#define TPO_R03_VAL_NORMAL (TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | \
TPO_R03_EN_VGL_PUMP | TPO_R03_EN_PWM | \
TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \
TPO_R03_SOFTWARE_CTL)
#define TPO_R03_VAL_STANDBY (TPO_R03_DRIVING_CAP_100 | \
TPO_R03_EN_PRE_CHARGE | TPO_R03_SOFTWARE_CTL)
static const u16 tpo_td043_def_gamma[12] = {
105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023
};
struct tpo_td043_device {
struct spi_device *spi;
struct regulator *vcc_reg;
int nreset_gpio;
u16 gamma[12];
u32 mode;
u32 hmirror:1;
u32 vmirror:1;
u32 powered_on:1;
u32 spi_suspended:1;
u32 power_on_resume:1;
};
/* used to pass spi_device from SPI to DSS portion of the driver */
static struct tpo_td043_device *g_tpo_td043;
static int tpo_td043_write(struct spi_device *spi, u8 addr, u8 data)
{
struct spi_message m;
struct spi_transfer xfer;
u16 w;
int r;
spi_message_init(&m);
memset(&xfer, 0, sizeof(xfer));
w = ((u16)addr << 10) | (1 << 8) | data;
xfer.tx_buf = &w;
xfer.bits_per_word = 16;
xfer.len = 2;
spi_message_add_tail(&xfer, &m);
r = spi_sync(spi, &m);
if (r < 0)
dev_warn(&spi->dev, "failed to write to LCD reg (%d)\n", r);
return r;
}
static void tpo_td043_write_gamma(struct spi_device *spi, u16 gamma[12])
{
u8 i, val;
/* gamma bits [9:8] */
for (val = i = 0; i < 4; i++)
val |= (gamma[i] & 0x300) >> ((i + 1) * 2);
tpo_td043_write(spi, 0x11, val);
for (val = i = 0; i < 4; i++)
val |= (gamma[i+4] & 0x300) >> ((i + 1) * 2);
tpo_td043_write(spi, 0x12, val);
for (val = i = 0; i < 4; i++)
val |= (gamma[i+8] & 0x300) >> ((i + 1) * 2);
tpo_td043_write(spi, 0x13, val);
/* gamma bits [7:0] */
for (val = i = 0; i < 12; i++)
tpo_td043_write(spi, 0x14 + i, gamma[i] & 0xff);
}
static int tpo_td043_write_mirror(struct spi_device *spi, bool h, bool v)
{
u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V | \
TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H;
if (h)
reg4 &= ~TPO_R04_NFLIP_H;
if (v)
reg4 &= ~TPO_R04_NFLIP_V;
return tpo_td043_write(spi, 4, reg4);
}
static int tpo_td043_set_hmirror(struct omap_dss_device *dssdev, bool enable)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dssdev->dev);
tpo_td043->hmirror = enable;
return tpo_td043_write_mirror(tpo_td043->spi, tpo_td043->hmirror,
tpo_td043->vmirror);
}
static bool tpo_td043_get_hmirror(struct omap_dss_device *dssdev)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dssdev->dev);
return tpo_td043->hmirror;
}
static ssize_t tpo_td043_vmirror_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", tpo_td043->vmirror);
}
static ssize_t tpo_td043_vmirror_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev);
int val;
int ret;
ret = kstrtoint(buf, 0, &val);
if (ret < 0)
return ret;
val = !!val;
ret = tpo_td043_write_mirror(tpo_td043->spi, tpo_td043->hmirror, val);
if (ret < 0)
return ret;
tpo_td043->vmirror = val;
return count;
}
static ssize_t tpo_td043_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", tpo_td043->mode);
}
static ssize_t tpo_td043_mode_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev);
long val;
int ret;
ret = kstrtol(buf, 0, &val);
if (ret != 0 || val & ~7)
return -EINVAL;
tpo_td043->mode = val;
val |= TPO_R02_NCLK_RISING;
tpo_td043_write(tpo_td043->spi, 2, val);
return count;
}
static ssize_t tpo_td043_gamma_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev);
ssize_t len = 0;
int ret;
int i;
for (i = 0; i < ARRAY_SIZE(tpo_td043->gamma); i++) {
ret = snprintf(buf + len, PAGE_SIZE - len, "%u ",
tpo_td043->gamma[i]);
if (ret < 0)
return ret;
len += ret;
}
buf[len - 1] = '\n';
return len;
}
static ssize_t tpo_td043_gamma_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev);
unsigned int g[12];
int ret;
int i;
ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u",
&g[0], &g[1], &g[2], &g[3], &g[4], &g[5],
&g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
if (ret != 12)
return -EINVAL;
for (i = 0; i < 12; i++)
tpo_td043->gamma[i] = g[i];
tpo_td043_write_gamma(tpo_td043->spi, tpo_td043->gamma);
return count;
}
static DEVICE_ATTR(vmirror, S_IRUGO | S_IWUSR,
tpo_td043_vmirror_show, tpo_td043_vmirror_store);
static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR,
tpo_td043_mode_show, tpo_td043_mode_store);
static DEVICE_ATTR(gamma, S_IRUGO | S_IWUSR,
tpo_td043_gamma_show, tpo_td043_gamma_store);
static struct attribute *tpo_td043_attrs[] = {
&dev_attr_vmirror.attr,
&dev_attr_mode.attr,
&dev_attr_gamma.attr,
NULL,
};
static struct attribute_group tpo_td043_attr_group = {
.attrs = tpo_td043_attrs,
};
static const struct omap_video_timings tpo_td043_timings = {
.x_res = 800,
.y_res = 480,
.pixel_clock = 36000,
.hsw = 1,
.hfp = 68,
.hbp = 214,
.vsw = 1,
.vfp = 39,
.vbp = 34,
.vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.data_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,
.de_level = OMAPDSS_SIG_ACTIVE_HIGH,
.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES,
};
static inline struct panel_tpo_td043_data
*get_panel_data(const struct omap_dss_device *dssdev)
{
return (struct panel_tpo_td043_data *) dssdev->data;
}
static int tpo_td043_power_on(struct tpo_td043_device *tpo_td043)
{
int r;
if (tpo_td043->powered_on)
return 0;
r = regulator_enable(tpo_td043->vcc_reg);
if (r != 0)
return r;
/* wait for panel to stabilize */
msleep(160);
if (gpio_is_valid(tpo_td043->nreset_gpio))
gpio_set_value(tpo_td043->nreset_gpio, 1);
tpo_td043_write(tpo_td043->spi, 2,
TPO_R02_MODE(tpo_td043->mode) | TPO_R02_NCLK_RISING);
tpo_td043_write(tpo_td043->spi, 3, TPO_R03_VAL_NORMAL);
tpo_td043_write(tpo_td043->spi, 0x20, 0xf0);
tpo_td043_write(tpo_td043->spi, 0x21, 0xf0);
tpo_td043_write_mirror(tpo_td043->spi, tpo_td043->hmirror,
tpo_td043->vmirror);
tpo_td043_write_gamma(tpo_td043->spi, tpo_td043->gamma);
tpo_td043->powered_on = 1;
return 0;
}
static void tpo_td043_power_off(struct tpo_td043_device *tpo_td043)
{
if (!tpo_td043->powered_on)
return;
tpo_td043_write(tpo_td043->spi, 3,
TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM);
if (gpio_is_valid(tpo_td043->nreset_gpio))
gpio_set_value(tpo_td043->nreset_gpio, 0);
/* wait for at least 2 vsyncs before cutting off power */
msleep(50);
tpo_td043_write(tpo_td043->spi, 3, TPO_R03_VAL_STANDBY);
regulator_disable(tpo_td043->vcc_reg);
tpo_td043->powered_on = 0;
}
static int tpo_td043_enable_dss(struct omap_dss_device *dssdev)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dssdev->dev);
int r;
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;
/*
* If we are resuming from system suspend, SPI clocks might not be
* enabled yet, so we'll program the LCD from SPI PM resume callback.
*/
if (!tpo_td043->spi_suspended) {
r = tpo_td043_power_on(tpo_td043);
if (r)
goto err1;
}
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
err1:
omapdss_dpi_display_disable(dssdev);
err0:
return r;
}
static void tpo_td043_disable_dss(struct omap_dss_device *dssdev)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dssdev->dev);
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
omapdss_dpi_display_disable(dssdev);
if (!tpo_td043->spi_suspended)
tpo_td043_power_off(tpo_td043);
}
static int tpo_td043_enable(struct omap_dss_device *dssdev)
{
dev_dbg(dssdev->dev, "enable\n");
return tpo_td043_enable_dss(dssdev);
}
static void tpo_td043_disable(struct omap_dss_device *dssdev)
{
dev_dbg(dssdev->dev, "disable\n");
tpo_td043_disable_dss(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static int tpo_td043_probe(struct omap_dss_device *dssdev)
{
struct tpo_td043_device *tpo_td043 = g_tpo_td043;
struct panel_tpo_td043_data *pdata = get_panel_data(dssdev);
int ret = 0;
dev_dbg(dssdev->dev, "probe\n");
if (tpo_td043 == NULL) {
dev_err(dssdev->dev, "missing tpo_td043_device\n");
return -ENODEV;
}
if (!pdata)
return -EINVAL;
tpo_td043->nreset_gpio = pdata->nreset_gpio;
dssdev->panel.timings = tpo_td043_timings;
dssdev->ctrl.pixel_size = 24;
tpo_td043->mode = TPO_R02_MODE_800x480;
memcpy(tpo_td043->gamma, tpo_td043_def_gamma, sizeof(tpo_td043->gamma));
tpo_td043->vcc_reg = regulator_get(dssdev->dev, "vcc");
if (IS_ERR(tpo_td043->vcc_reg)) {
dev_err(dssdev->dev, "failed to get LCD VCC regulator\n");
ret = PTR_ERR(tpo_td043->vcc_reg);
goto fail_regulator;
}
if (gpio_is_valid(tpo_td043->nreset_gpio)) {
ret = devm_gpio_request_one(dssdev->dev,
tpo_td043->nreset_gpio, GPIOF_OUT_INIT_LOW,
"lcd reset");
if (ret < 0) {
dev_err(dssdev->dev, "couldn't request reset GPIO\n");
goto fail_gpio_req;
}
}
ret = sysfs_create_group(&dssdev->dev->kobj, &tpo_td043_attr_group);
if (ret)
dev_warn(dssdev->dev, "failed to create sysfs files\n");
dev_set_drvdata(dssdev->dev, tpo_td043);
return 0;
fail_gpio_req:
regulator_put(tpo_td043->vcc_reg);
fail_regulator:
kfree(tpo_td043);
return ret;
}
static void tpo_td043_remove(struct omap_dss_device *dssdev)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dssdev->dev);
dev_dbg(dssdev->dev, "remove\n");
sysfs_remove_group(&dssdev->dev->kobj, &tpo_td043_attr_group);
regulator_put(tpo_td043->vcc_reg);
}
static void tpo_td043_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
omapdss_dpi_set_timings(dssdev, timings);
dssdev->panel.timings = *timings;
}
static int tpo_td043_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
return dpi_check_timings(dssdev, timings);
}
static struct omap_dss_driver tpo_td043_driver = {
.probe = tpo_td043_probe,
.remove = tpo_td043_remove,
.enable = tpo_td043_enable,
.disable = tpo_td043_disable,
.set_mirror = tpo_td043_set_hmirror,
.get_mirror = tpo_td043_get_hmirror,
.set_timings = tpo_td043_set_timings,
.check_timings = tpo_td043_check_timings,
.driver = {
.name = "tpo_td043mtea1_panel",
.owner = THIS_MODULE,
},
};
static int tpo_td043_spi_probe(struct spi_device *spi)
{
struct omap_dss_device *dssdev = spi->dev.platform_data;
struct tpo_td043_device *tpo_td043;
int ret;
if (dssdev == NULL) {
dev_err(&spi->dev, "missing dssdev\n");
return -ENODEV;
}
if (g_tpo_td043 != NULL)
return -EBUSY;
spi->bits_per_word = 16;
spi->mode = SPI_MODE_0;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi_setup failed: %d\n", ret);
return ret;
}
tpo_td043 = kzalloc(sizeof(*tpo_td043), GFP_KERNEL);
if (tpo_td043 == NULL)
return -ENOMEM;
tpo_td043->spi = spi;
dev_set_drvdata(&spi->dev, tpo_td043);
g_tpo_td043 = tpo_td043;
omap_dss_register_driver(&tpo_td043_driver);
return 0;
}
static int tpo_td043_spi_remove(struct spi_device *spi)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(&spi->dev);
omap_dss_unregister_driver(&tpo_td043_driver);
kfree(tpo_td043);
g_tpo_td043 = NULL;
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int tpo_td043_spi_suspend(struct device *dev)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev);
dev_dbg(dev, "tpo_td043_spi_suspend, tpo %p\n", tpo_td043);
tpo_td043->power_on_resume = tpo_td043->powered_on;
tpo_td043_power_off(tpo_td043);
tpo_td043->spi_suspended = 1;
return 0;
}
static int tpo_td043_spi_resume(struct device *dev)
{
struct tpo_td043_device *tpo_td043 = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "tpo_td043_spi_resume\n");
if (tpo_td043->power_on_resume) {
ret = tpo_td043_power_on(tpo_td043);
if (ret)
return ret;
}
tpo_td043->spi_suspended = 0;
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(tpo_td043_spi_pm,
tpo_td043_spi_suspend, tpo_td043_spi_resume);
static struct spi_driver tpo_td043_spi_driver = {
.driver = {
.name = "tpo_td043mtea1_panel_spi",
.owner = THIS_MODULE,
.pm = &tpo_td043_spi_pm,
},
.probe = tpo_td043_spi_probe,
.remove = tpo_td043_spi_remove,
};
module_spi_driver(tpo_td043_spi_driver);
MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>");
MODULE_DESCRIPTION("TPO TD043MTEA1 LCD Driver");
MODULE_LICENSE("GPL");
......@@ -32,124 +32,6 @@
struct omap_dss_device;
/**
* struct panel_generic_dpi_data - panel driver configuration data
* @name: panel name
* @platform_enable: platform specific panel enable function
* @platform_disable: platform specific panel disable function
* @num_gpios: number of gpios connected to panel
* @gpios: gpio numbers on the platform
* @gpio_invert: configure gpio as active high or low
*/
struct panel_generic_dpi_data {
const char *name;
int (*platform_enable)(struct omap_dss_device *dssdev);
void (*platform_disable)(struct omap_dss_device *dssdev);
int num_gpios;
int gpios[10];
bool gpio_invert[10];
};
/**
* struct panel_n8x0_data - N800 panel driver configuration data
*/
struct panel_n8x0_data {
int (*platform_enable)(struct omap_dss_device *dssdev);
void (*platform_disable)(struct omap_dss_device *dssdev);
int panel_reset;
int ctrl_pwrdown;
};
/**
* struct nokia_dsi_panel_data - Nokia DSI panel driver configuration data
* @name: panel name
* @use_ext_te: use external TE
* @ext_te_gpio: external TE GPIO
* @esd_interval: interval of ESD checks, 0 = disabled (ms)
* @ulps_timeout: time to wait before entering ULPS, 0 = disabled (ms)
* @use_dsi_backlight: true if panel uses DSI command to control backlight
* @pin_config: DSI pin configuration
*/
struct nokia_dsi_panel_data {
const char *name;
int reset_gpio;
bool use_ext_te;
int ext_te_gpio;
unsigned esd_interval;
unsigned ulps_timeout;
bool use_dsi_backlight;
struct omap_dsi_pin_config pin_config;
};
/**
* struct picodlp_panel_data - picodlp panel driver configuration data
* @picodlp_adapter_id: i2c_adapter number for picodlp
*/
struct picodlp_panel_data {
int picodlp_adapter_id;
int emu_done_gpio;
int pwrgood_gpio;
};
/**
* struct tfp410_platform_data - tfp410 panel driver configuration data
* @i2c_bus_num: i2c bus id for the panel
* @power_down_gpio: gpio number for PD pin (or -1 if not available)
*/
struct tfp410_platform_data {
int i2c_bus_num;
int power_down_gpio;
};
/**
* sharp ls panel driver configuration data
* @resb_gpio: reset signal
* @ini_gpio: power on control
* @mo_gpio: selection for resolution(VGA/QVGA)
* @lr_gpio: selection for horizontal scanning direction
* @ud_gpio: selection for vertical scanning direction
*/
struct panel_sharp_ls037v7dw01_data {
int resb_gpio;
int ini_gpio;
int mo_gpio;
int lr_gpio;
int ud_gpio;
};
/**
* acx565akm panel driver configuration data
* @reset_gpio: reset signal
*/
struct panel_acx565akm_data {
int reset_gpio;
};
/**
* nec nl8048 panel driver configuration data
* @res_gpio: reset signal
* @qvga_gpio: selection for resolution(QVGA/WVGA)
*/
struct panel_nec_nl8048_data {
int res_gpio;
int qvga_gpio;
};
/**
* tpo td043 panel driver configuration data
* @nreset_gpio: reset signal
*/
struct panel_tpo_td043_data {
int nreset_gpio;
};
/**
* encoder_tfp410 platform data
* @name: name for this display entity
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment