Commit b75bf98b authored by Jean-Christophe PLAGNIOL-VILLARD's avatar Jean-Christophe PLAGNIOL-VILLARD

Merge tag 'omapdss-for-3.11-2' of git://gitorious.org/linux-omap-dss2/linux into fbdev/for-next

OMAP display subsystem changes for 3.11 (part 2/2)

This is the second part of OMAP DSS changes for 3.11. This part contains the
new DSS device model support.

The current OMAP panel drivers use a custom DSS bus, and there's a hard limit
of one external display block per video pipeline. In the new DSS device model
the devices/drivers are made according to the control bus of the display block,
usually platform, i2c or spi. The display blocks can also be chained so that we
can have separate drivers for setups with both external encoder and panel.

To allow the current board files, which use the old style panels, to function,
the old display drivers are left in their current state, and new ones are added
to drivers/video/omap2/displays-new/. When the board files have been converted
to use the new style panels, we can remove the old code. This is planned to
happen in v3.12.

Having to support two very different DSS device models makes the driver
somewhat confusing in some parts, and prevents us from properly cleaning up
some other parts. These cleanups will be done when the old code is removed.

The new device model is designed with CDF (Common Display Framework) in mind.
While CDF is still under work, the new DSS device model should be much more
similar to CDF's model than the old device model, which should make the
eventual conversion to CDF much easier.
parents 8e980455 c545b595
......@@ -65,10 +65,8 @@ static int get_connector_type(struct omap_dss_device *dssdev)
switch (dssdev->type) {
case OMAP_DISPLAY_TYPE_HDMI:
return DRM_MODE_CONNECTOR_HDMIA;
case OMAP_DISPLAY_TYPE_DPI:
if (!strcmp(dssdev->name, "dvi"))
case OMAP_DISPLAY_TYPE_DVI:
return DRM_MODE_CONNECTOR_DVID;
/* fallthrough */
default:
return DRM_MODE_CONNECTOR_Unknown;
}
......
......@@ -6,5 +6,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
......@@ -2,4 +2,5 @@ obj-$(CONFIG_OMAP2_VRFB) += vrfb.o
obj-$(CONFIG_OMAP2_DSS) += dss/
obj-y += displays/
obj-y += displays-new/
obj-$(CONFIG_FB_OMAP2) += omapfb/
menu "OMAP Display Device Drivers (new device model)"
depends on OMAP2_DSS
config DISPLAY_ENCODER_TFP410
tristate "TFP410 DPI to DVI Encoder"
help
Driver for TFP410 DPI to DVI encoder.
config DISPLAY_ENCODER_TPD12S015
tristate "TPD12S015 HDMI ESD protection and level shifter"
help
Driver for TPD12S015, which offers HDMI ESD protection and level
shifting.
config DISPLAY_CONNECTOR_DVI
tristate "DVI Connector"
depends on I2C
help
Driver for a generic DVI connector.
config DISPLAY_CONNECTOR_HDMI
tristate "HDMI Connector"
help
Driver for a generic HDMI connector.
config DISPLAY_CONNECTOR_ANALOG_TV
tristate "Analog TV Connector"
help
Driver for a generic analog TV connector.
config DISPLAY_PANEL_DPI
tristate "Generic DPI panel"
help
Driver for generic DPI panels.
config DISPLAY_PANEL_DSI_CM
tristate "Generic DSI Command Mode Panel"
help
Driver for generic DSI command mode panels.
config DISPLAY_PANEL_SONY_ACX565AKM
tristate "ACX565AKM Panel"
depends on SPI && BACKLIGHT_CLASS_DEVICE
help
This is the LCD panel used on Nokia N900
config DISPLAY_PANEL_LGPHILIPS_LB035Q02
tristate "LG.Philips LB035Q02 LCD Panel"
depends on SPI
help
LCD Panel used on the Gumstix Overo Palo35
config DISPLAY_PANEL_SHARP_LS037V7DW01
tristate "Sharp LS037V7DW01 LCD Panel"
depends on BACKLIGHT_CLASS_DEVICE
help
LCD Panel used in TI's SDP3430 and EVM boards
config DISPLAY_PANEL_TPO_TD043MTEA1
tristate "TPO TD043MTEA1 LCD Panel"
depends on SPI
help
LCD Panel used in OMAP3 Pandora
config DISPLAY_PANEL_NEC_NL8048HL11
tristate "NEC NL8048HL11 Panel"
depends on SPI
depends on BACKLIGHT_CLASS_DEVICE
help
This NEC NL8048HL11 panel is TFT LCD used in the
Zoom2/3/3630 sdp boards.
endmenu
obj-$(CONFIG_DISPLAY_ENCODER_TFP410) += encoder-tfp410.o
obj-$(CONFIG_DISPLAY_ENCODER_TPD12S015) += encoder-tpd12s015.o
obj-$(CONFIG_DISPLAY_CONNECTOR_DVI) += connector-dvi.o
obj-$(CONFIG_DISPLAY_CONNECTOR_HDMI) += connector-hdmi.o
obj-$(CONFIG_DISPLAY_CONNECTOR_ANALOG_TV) += connector-analog-tv.o
obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o
obj-$(CONFIG_DISPLAY_PANEL_DSI_CM) += panel-dsi-cm.o
obj-$(CONFIG_DISPLAY_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o
obj-$(CONFIG_DISPLAY_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o
obj-$(CONFIG_DISPLAY_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o
obj-$(CONFIG_DISPLAY_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o
obj-$(CONFIG_DISPLAY_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
/*
* Analog TV Connector driver
*
* Copyright (C) 2013 Texas Instruments
* 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.
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
struct device *dev;
struct omap_video_timings timings;
enum omap_dss_venc_type connector_type;
bool invert_polarity;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static int tvc_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
dev_dbg(ddata->dev, "connect\n");
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.atv->connect(in, dssdev);
if (r)
return r;
return 0;
}
static void tvc_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
dev_dbg(ddata->dev, "disconnect\n");
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.atv->disconnect(in, dssdev);
}
static int tvc_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
dev_dbg(ddata->dev, "enable\n");
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.atv->set_timings(in, &ddata->timings);
in->ops.atv->set_type(in, ddata->connector_type);
in->ops.atv->invert_vid_out_polarity(in, ddata->invert_polarity);
r = in->ops.atv->enable(in);
if (r)
return r;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return r;
}
static void tvc_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
dev_dbg(ddata->dev, "disable\n");
if (!omapdss_device_is_enabled(dssdev))
return;
in->ops.atv->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void tvc_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->timings = *timings;
dssdev->panel.timings = *timings;
in->ops.atv->set_timings(in, timings);
}
static void tvc_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->timings;
}
static int tvc_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.atv->check_timings(in, timings);
}
static u32 tvc_get_wss(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.atv->get_wss(in);
}
static int tvc_set_wss(struct omap_dss_device *dssdev, u32 wss)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.atv->set_wss(in, wss);
}
static struct omap_dss_driver tvc_driver = {
.connect = tvc_connect,
.disconnect = tvc_disconnect,
.enable = tvc_enable,
.disable = tvc_disable,
.set_timings = tvc_set_timings,
.get_timings = tvc_get_timings,
.check_timings = tvc_check_timings,
.get_resolution = omapdss_default_get_resolution,
.get_wss = tvc_get_wss,
.set_wss = tvc_set_wss,
};
static int tvc_probe_pdata(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct connector_atv_platform_data *pdata;
struct omap_dss_device *in, *dssdev;
pdata = dev_get_platdata(&pdev->dev);
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&pdev->dev, "Failed to find video source\n");
return -ENODEV;
}
ddata->in = in;
ddata->connector_type = pdata->connector_type;
ddata->invert_polarity = ddata->invert_polarity;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int tvc_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
ddata->dev = &pdev->dev;
if (dev_get_platdata(&pdev->dev)) {
r = tvc_probe_pdata(pdev);
if (r)
return r;
} else {
return -ENODEV;
}
ddata->timings = omap_dss_pal_timings;
dssdev = &ddata->dssdev;
dssdev->driver = &tvc_driver;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_VENC;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = omap_dss_pal_timings;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&pdev->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
omap_dss_put_device(ddata->in);
return r;
}
static int __exit tvc_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
omapdss_unregister_display(&ddata->dssdev);
tvc_disable(dssdev);
tvc_disconnect(dssdev);
omap_dss_put_device(in);
return 0;
}
static struct platform_driver tvc_connector_driver = {
.probe = tvc_probe,
.remove = __exit_p(tvc_remove),
.driver = {
.name = "connector-analog-tv",
.owner = THIS_MODULE,
},
};
module_platform_driver(tvc_connector_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("Analog TV Connector driver");
MODULE_LICENSE("GPL");
/*
* Generic DVI Connector driver
*
* Copyright (C) 2013 Texas Instruments
* 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.
*/
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <drm/drm_edid.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
static const struct omap_video_timings dvic_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 omap_dss_device *in;
struct omap_video_timings timings;
struct i2c_adapter *i2c_adapter;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static int dvic_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.dvi->connect(in, dssdev);
if (r)
return r;
return 0;
}
static void dvic_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.dvi->disconnect(in, dssdev);
}
static int dvic_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.dvi->set_timings(in, &ddata->timings);
r = in->ops.dvi->enable(in);
if (r)
return r;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void dvic_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_enabled(dssdev))
return;
in->ops.dvi->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void dvic_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->timings = *timings;
dssdev->panel.timings = *timings;
in->ops.dvi->set_timings(in, timings);
}
static void dvic_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->timings;
}
static int dvic_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.dvi->check_timings(in, timings);
}
static int dvic_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 dvic_read_edid(struct omap_dss_device *dssdev,
u8 *edid, int len)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
int r, l, bytes_read;
if (!ddata->i2c_adapter)
return -ENODEV;
l = min(EDID_LENGTH, len);
r = dvic_ddc_read(ddata->i2c_adapter, edid, l, 0);
if (r)
return r;
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 = dvic_ddc_read(ddata->i2c_adapter, edid + EDID_LENGTH,
l, EDID_LENGTH);
if (r)
return r;
bytes_read += l;
}
return bytes_read;
}
static bool dvic_detect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
unsigned char out;
int r;
if (!ddata->i2c_adapter)
return true;
r = dvic_ddc_read(ddata->i2c_adapter, &out, 1, 0);
return r == 0;
}
static struct omap_dss_driver dvic_driver = {
.connect = dvic_connect,
.disconnect = dvic_disconnect,
.enable = dvic_enable,
.disable = dvic_disable,
.set_timings = dvic_set_timings,
.get_timings = dvic_get_timings,
.check_timings = dvic_check_timings,
.get_resolution = omapdss_default_get_resolution,
.read_edid = dvic_read_edid,
.detect = dvic_detect,
};
static int dvic_probe_pdata(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct connector_dvi_platform_data *pdata;
struct omap_dss_device *in, *dssdev;
int i2c_bus_num;
pdata = dev_get_platdata(&pdev->dev);
i2c_bus_num = pdata->i2c_bus_num;
if (i2c_bus_num != -1) {
struct i2c_adapter *adapter;
adapter = i2c_get_adapter(i2c_bus_num);
if (!adapter) {
dev_err(&pdev->dev,
"Failed to get I2C adapter, bus %d\n",
i2c_bus_num);
return -EPROBE_DEFER;
}
ddata->i2c_adapter = adapter;
}
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&pdev->dev, "Failed to find video source\n");
return -ENODEV;
}
ddata->in = in;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int dvic_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
if (dev_get_platdata(&pdev->dev)) {
r = dvic_probe_pdata(pdev);
if (r)
return r;
} else {
return -ENODEV;
}
ddata->timings = dvic_default_timings;
dssdev = &ddata->dssdev;
dssdev->driver = &dvic_driver;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_DVI;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = dvic_default_timings;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&pdev->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
omap_dss_put_device(ddata->in);
return r;
}
static int __exit dvic_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
omapdss_unregister_display(&ddata->dssdev);
dvic_disable(dssdev);
dvic_disconnect(dssdev);
omap_dss_put_device(in);
if (ddata->i2c_adapter)
i2c_put_adapter(ddata->i2c_adapter);
return 0;
}
static struct platform_driver dvi_connector_driver = {
.probe = dvic_probe,
.remove = __exit_p(dvic_remove),
.driver = {
.name = "connector-dvi",
.owner = THIS_MODULE,
},
};
module_platform_driver(dvi_connector_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("Generic DVI Connector driver");
MODULE_LICENSE("GPL");
/*
* HDMI Connector driver
*
* Copyright (C) 2013 Texas Instruments
* 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.
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <drm/drm_edid.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
static const struct omap_video_timings hdmic_default_timings = {
.x_res = 640,
.y_res = 480,
.pixel_clock = 25175,
.hsw = 96,
.hfp = 16,
.hbp = 48,
.vsw = 2,
.vfp = 11,
.vbp = 31,
.vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
.interlace = false,
};
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
struct device *dev;
struct omap_video_timings timings;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static int hdmic_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
dev_dbg(ddata->dev, "connect\n");
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.hdmi->connect(in, dssdev);
if (r)
return r;
return 0;
}
static void hdmic_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
dev_dbg(ddata->dev, "disconnect\n");
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.hdmi->disconnect(in, dssdev);
}
static int hdmic_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
dev_dbg(ddata->dev, "enable\n");
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.hdmi->set_timings(in, &ddata->timings);
r = in->ops.hdmi->enable(in);
if (r)
return r;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return r;
}
static void hdmic_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
dev_dbg(ddata->dev, "disable\n");
if (!omapdss_device_is_enabled(dssdev))
return;
in->ops.hdmi->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void hdmic_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->timings = *timings;
dssdev->panel.timings = *timings;
in->ops.hdmi->set_timings(in, timings);
}
static void hdmic_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->timings;
}
static int hdmic_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.hdmi->check_timings(in, timings);
}
static int hdmic_read_edid(struct omap_dss_device *dssdev,
u8 *edid, int len)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.hdmi->read_edid(in, edid, len);
}
static bool hdmic_detect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.hdmi->detect(in);
}
static int hdmic_audio_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
/* enable audio only if the display is active */
if (!omapdss_device_is_enabled(dssdev))
return -EPERM;
r = in->ops.hdmi->audio_enable(in);
if (r)
return r;
dssdev->audio_state = OMAP_DSS_AUDIO_ENABLED;
return 0;
}
static void hdmic_audio_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
in->ops.hdmi->audio_disable(in);
dssdev->audio_state = OMAP_DSS_AUDIO_DISABLED;
}
static int hdmic_audio_start(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
/*
* No need to check the panel state. It was checked when trasitioning
* to AUDIO_ENABLED.
*/
if (dssdev->audio_state != OMAP_DSS_AUDIO_ENABLED)
return -EPERM;
r = in->ops.hdmi->audio_start(in);
if (r)
return r;
dssdev->audio_state = OMAP_DSS_AUDIO_PLAYING;
return 0;
}
static void hdmic_audio_stop(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
in->ops.hdmi->audio_stop(in);
dssdev->audio_state = OMAP_DSS_AUDIO_ENABLED;
}
static bool hdmic_audio_supported(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_enabled(dssdev))
return false;
return in->ops.hdmi->audio_supported(in);
}
static int hdmic_audio_config(struct omap_dss_device *dssdev,
struct omap_dss_audio *audio)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
/* config audio only if the display is active */
if (!omapdss_device_is_enabled(dssdev))
return -EPERM;
r = in->ops.hdmi->audio_config(in, audio);
if (r)
return r;
dssdev->audio_state = OMAP_DSS_AUDIO_CONFIGURED;
return 0;
}
static struct omap_dss_driver hdmic_driver = {
.connect = hdmic_connect,
.disconnect = hdmic_disconnect,
.enable = hdmic_enable,
.disable = hdmic_disable,
.set_timings = hdmic_set_timings,
.get_timings = hdmic_get_timings,
.check_timings = hdmic_check_timings,
.get_resolution = omapdss_default_get_resolution,
.read_edid = hdmic_read_edid,
.detect = hdmic_detect,
.audio_enable = hdmic_audio_enable,
.audio_disable = hdmic_audio_disable,
.audio_start = hdmic_audio_start,
.audio_stop = hdmic_audio_stop,
.audio_supported = hdmic_audio_supported,
.audio_config = hdmic_audio_config,
};
static int hdmic_probe_pdata(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct connector_hdmi_platform_data *pdata;
struct omap_dss_device *in, *dssdev;
pdata = dev_get_platdata(&pdev->dev);
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&pdev->dev, "Failed to find video source\n");
return -ENODEV;
}
ddata->in = in;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int hdmic_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
ddata->dev = &pdev->dev;
if (dev_get_platdata(&pdev->dev)) {
r = hdmic_probe_pdata(pdev);
if (r)
return r;
} else {
return -ENODEV;
}
ddata->timings = hdmic_default_timings;
dssdev = &ddata->dssdev;
dssdev->driver = &hdmic_driver;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = hdmic_default_timings;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&pdev->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
omap_dss_put_device(ddata->in);
return r;
}
static int __exit hdmic_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
omapdss_unregister_display(&ddata->dssdev);
hdmic_disable(dssdev);
hdmic_disconnect(dssdev);
omap_dss_put_device(in);
return 0;
}
static struct platform_driver hdmi_connector_driver = {
.probe = hdmic_probe,
.remove = __exit_p(hdmic_remove),
.driver = {
.name = "connector-hdmi",
.owner = THIS_MODULE,
},
};
module_platform_driver(hdmi_connector_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("HDMI Connector driver");
MODULE_LICENSE("GPL");
/*
* TFP410 DPI-to-DVI encoder driver
*
* Copyright (C) 2013 Texas Instruments
* 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.
*/
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
int pd_gpio;
int data_lines;
struct omap_video_timings timings;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static int tfp410_connect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (omapdss_device_is_connected(dssdev))
return -EBUSY;
r = in->ops.dpi->connect(in, dssdev);
if (r)
return r;
dst->output = dssdev;
dssdev->device = dst;
return 0;
}
static void tfp410_disconnect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
WARN_ON(!omapdss_device_is_connected(dssdev));
if (!omapdss_device_is_connected(dssdev))
return;
WARN_ON(dst != dssdev->device);
if (dst != dssdev->device)
return;
dst->output = NULL;
dssdev->device = NULL;
in->ops.dpi->disconnect(in, &ddata->dssdev);
}
static int tfp410_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.dpi->set_timings(in, &ddata->timings);
in->ops.dpi->set_data_lines(in, ddata->data_lines);
r = in->ops.dpi->enable(in);
if (r)
return r;
if (gpio_is_valid(ddata->pd_gpio))
gpio_set_value_cansleep(ddata->pd_gpio, 1);
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void tfp410_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_enabled(dssdev))
return;
if (gpio_is_valid(ddata->pd_gpio))
gpio_set_value_cansleep(ddata->pd_gpio, 0);
in->ops.dpi->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void tfp410_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->timings = *timings;
dssdev->panel.timings = *timings;
in->ops.dpi->set_timings(in, timings);
}
static void tfp410_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->timings;
}
static int tfp410_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.dpi->check_timings(in, timings);
}
static const struct omapdss_dvi_ops tfp410_dvi_ops = {
.connect = tfp410_connect,
.disconnect = tfp410_disconnect,
.enable = tfp410_enable,
.disable = tfp410_disable,
.check_timings = tfp410_check_timings,
.set_timings = tfp410_set_timings,
.get_timings = tfp410_get_timings,
};
static int tfp410_probe_pdata(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct encoder_tfp410_platform_data *pdata;
struct omap_dss_device *dssdev, *in;
pdata = dev_get_platdata(&pdev->dev);
ddata->pd_gpio = pdata->power_down_gpio;
ddata->data_lines = pdata->data_lines;
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&pdev->dev, "Failed to find video source\n");
return -ENODEV;
}
ddata->in = in;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int tfp410_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
if (dev_get_platdata(&pdev->dev)) {
r = tfp410_probe_pdata(pdev);
if (r)
return r;
} else {
return -ENODEV;
}
if (gpio_is_valid(ddata->pd_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->pd_gpio,
GPIOF_OUT_INIT_LOW, "tfp410 PD");
if (r) {
dev_err(&pdev->dev, "Failed to request PD GPIO %d\n",
ddata->pd_gpio);
goto err_gpio;
}
}
dssdev = &ddata->dssdev;
dssdev->ops.dvi = &tfp410_dvi_ops;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_DPI;
dssdev->output_type = OMAP_DISPLAY_TYPE_DVI;
dssdev->owner = THIS_MODULE;
dssdev->phy.dpi.data_lines = ddata->data_lines;
r = omapdss_register_output(dssdev);
if (r) {
dev_err(&pdev->dev, "Failed to register output\n");
goto err_reg;
}
return 0;
err_reg:
err_gpio:
omap_dss_put_device(ddata->in);
return r;
}
static int __exit tfp410_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
omapdss_unregister_output(&ddata->dssdev);
WARN_ON(omapdss_device_is_enabled(dssdev));
if (omapdss_device_is_enabled(dssdev))
tfp410_disable(dssdev);
WARN_ON(omapdss_device_is_connected(dssdev));
if (omapdss_device_is_connected(dssdev))
tfp410_disconnect(dssdev, dssdev->device);
omap_dss_put_device(in);
return 0;
}
static struct platform_driver tfp410_driver = {
.probe = tfp410_probe,
.remove = __exit_p(tfp410_remove),
.driver = {
.name = "tfp410",
.owner = THIS_MODULE,
},
};
module_platform_driver(tfp410_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("TFP410 DPI to DVI encoder driver");
MODULE_LICENSE("GPL");
/*
* TPD12S015 HDMI ESD protection & level shifter chip driver
*
* Copyright (C) 2013 Texas Instruments
* 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.
*/
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
int ct_cp_hpd_gpio;
int ls_oe_gpio;
int hpd_gpio;
struct omap_video_timings timings;
struct completion hpd_completion;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static irqreturn_t tpd_hpd_irq_handler(int irq, void *data)
{
struct panel_drv_data *ddata = data;
bool hpd;
hpd = gpio_get_value_cansleep(ddata->hpd_gpio);
dev_dbg(ddata->dssdev.dev, "hpd %d\n", hpd);
if (gpio_is_valid(ddata->ls_oe_gpio)) {
if (hpd)
gpio_set_value_cansleep(ddata->ls_oe_gpio, 1);
else
gpio_set_value_cansleep(ddata->ls_oe_gpio, 0);
}
complete_all(&ddata->hpd_completion);
return IRQ_HANDLED;
}
static int tpd_connect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
r = in->ops.hdmi->connect(in, dssdev);
if (r)
return r;
dst->output = dssdev;
dssdev->device = dst;
INIT_COMPLETION(ddata->hpd_completion);
gpio_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1);
/* DC-DC converter needs at max 300us to get to 90% of 5V */
udelay(300);
/*
* If there's a cable connected, wait for the hpd irq to trigger,
* which turns on the level shifters.
*/
if (gpio_get_value_cansleep(ddata->hpd_gpio)) {
unsigned long to;
to = wait_for_completion_timeout(&ddata->hpd_completion,
msecs_to_jiffies(250));
WARN_ON_ONCE(to == 0);
}
return 0;
}
static void tpd_disconnect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
WARN_ON(dst != dssdev->device);
if (dst != dssdev->device)
return;
gpio_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0);
dst->output = NULL;
dssdev->device = NULL;
in->ops.hdmi->disconnect(in, &ddata->dssdev);
}
static int tpd_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE)
return 0;
in->ops.hdmi->set_timings(in, &ddata->timings);
r = in->ops.hdmi->enable(in);
if (r)
return r;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return r;
}
static void tpd_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
return;
in->ops.hdmi->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void tpd_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->timings = *timings;
dssdev->panel.timings = *timings;
in->ops.hdmi->set_timings(in, timings);
}
static void tpd_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->timings;
}
static int tpd_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
r = in->ops.hdmi->check_timings(in, timings);
return r;
}
static int tpd_read_edid(struct omap_dss_device *dssdev,
u8 *edid, int len)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!gpio_get_value_cansleep(ddata->hpd_gpio))
return -ENODEV;
return in->ops.hdmi->read_edid(in, edid, len);
}
static bool tpd_detect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
return gpio_get_value_cansleep(ddata->hpd_gpio);
}
static int tpd_audio_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.hdmi->audio_enable(in);
}
static void tpd_audio_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
in->ops.hdmi->audio_disable(in);
}
static int tpd_audio_start(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.hdmi->audio_start(in);
}
static void tpd_audio_stop(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
in->ops.hdmi->audio_stop(in);
}
static bool tpd_audio_supported(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.hdmi->audio_supported(in);
}
static int tpd_audio_config(struct omap_dss_device *dssdev,
struct omap_dss_audio *audio)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.hdmi->audio_config(in, audio);
}
static const struct omapdss_hdmi_ops tpd_hdmi_ops = {
.connect = tpd_connect,
.disconnect = tpd_disconnect,
.enable = tpd_enable,
.disable = tpd_disable,
.check_timings = tpd_check_timings,
.set_timings = tpd_set_timings,
.get_timings = tpd_get_timings,
.read_edid = tpd_read_edid,
.detect = tpd_detect,
.audio_enable = tpd_audio_enable,
.audio_disable = tpd_audio_disable,
.audio_start = tpd_audio_start,
.audio_stop = tpd_audio_stop,
.audio_supported = tpd_audio_supported,
.audio_config = tpd_audio_config,
};
static int tpd_probe_pdata(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct encoder_tpd12s015_platform_data *pdata;
struct omap_dss_device *dssdev, *in;
pdata = dev_get_platdata(&pdev->dev);
ddata->ct_cp_hpd_gpio = pdata->ct_cp_hpd_gpio;
ddata->ls_oe_gpio = pdata->ls_oe_gpio;
ddata->hpd_gpio = pdata->hpd_gpio;
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&pdev->dev, "Failed to find video source\n");
return -ENODEV;
}
ddata->in = in;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int tpd_probe(struct platform_device *pdev)
{
struct omap_dss_device *in, *dssdev;
struct panel_drv_data *ddata;
int r;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
init_completion(&ddata->hpd_completion);
if (dev_get_platdata(&pdev->dev)) {
r = tpd_probe_pdata(pdev);
if (r)
return r;
} else {
return -ENODEV;
}
r = devm_gpio_request_one(&pdev->dev, ddata->ct_cp_hpd_gpio,
GPIOF_OUT_INIT_LOW, "hdmi_ct_cp_hpd");
if (r)
goto err_gpio;
if (gpio_is_valid(ddata->ls_oe_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->ls_oe_gpio,
GPIOF_OUT_INIT_LOW, "hdmi_ls_oe");
if (r)
goto err_gpio;
}
r = devm_gpio_request_one(&pdev->dev, ddata->hpd_gpio,
GPIOF_DIR_IN, "hdmi_hpd");
if (r)
goto err_gpio;
r = devm_request_threaded_irq(&pdev->dev, gpio_to_irq(ddata->hpd_gpio),
NULL, tpd_hpd_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
IRQF_ONESHOT, "hpd", ddata);
if (r)
goto err_irq;
dssdev = &ddata->dssdev;
dssdev->ops.hdmi = &tpd_hdmi_ops;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI;
dssdev->owner = THIS_MODULE;
in = ddata->in;
r = omapdss_register_output(dssdev);
if (r) {
dev_err(&pdev->dev, "Failed to register output\n");
goto err_reg;
}
return 0;
err_reg:
err_irq:
err_gpio:
omap_dss_put_device(ddata->in);
return r;
}
static int __exit tpd_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
omapdss_unregister_output(&ddata->dssdev);
WARN_ON(omapdss_device_is_enabled(dssdev));
if (omapdss_device_is_enabled(dssdev))
tpd_disable(dssdev);
WARN_ON(omapdss_device_is_connected(dssdev));
if (omapdss_device_is_connected(dssdev))
tpd_disconnect(dssdev, dssdev->device);
omap_dss_put_device(in);
return 0;
}
static struct platform_driver tpd_driver = {
.probe = tpd_probe,
.remove = __exit_p(tpd_remove),
.driver = {
.name = "tpd12s015",
.owner = THIS_MODULE,
},
};
module_platform_driver(tpd_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("TPD12S015 driver");
MODULE_LICENSE("GPL");
/*
* Generic MIPI DPI Panel Driver
*
* Copyright (C) 2013 Texas Instruments
* 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.
*/
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
int data_lines;
struct omap_video_timings videomode;
int backlight_gpio;
int enable_gpio;
};
#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
static int panel_dpi_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.dpi->connect(in, dssdev);
if (r)
return r;
return 0;
}
static void panel_dpi_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.dpi->disconnect(in, dssdev);
}
static int panel_dpi_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.dpi->set_data_lines(in, ddata->data_lines);
in->ops.dpi->set_timings(in, &ddata->videomode);
r = in->ops.dpi->enable(in);
if (r)
return r;
if (gpio_is_valid(ddata->enable_gpio))
gpio_set_value_cansleep(ddata->enable_gpio, 1);
if (gpio_is_valid(ddata->backlight_gpio))
gpio_set_value_cansleep(ddata->backlight_gpio, 1);
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void panel_dpi_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_enabled(dssdev))
return;
if (gpio_is_valid(ddata->enable_gpio))
gpio_set_value_cansleep(ddata->enable_gpio, 0);
if (gpio_is_valid(ddata->backlight_gpio))
gpio_set_value_cansleep(ddata->backlight_gpio, 0);
in->ops.dpi->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void panel_dpi_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->videomode = *timings;
dssdev->panel.timings = *timings;
in->ops.dpi->set_timings(in, timings);
}
static void panel_dpi_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->videomode;
}
static int panel_dpi_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.dpi->check_timings(in, timings);
}
static struct omap_dss_driver panel_dpi_ops = {
.connect = panel_dpi_connect,
.disconnect = panel_dpi_disconnect,
.enable = panel_dpi_enable,
.disable = panel_dpi_disable,
.set_timings = panel_dpi_set_timings,
.get_timings = panel_dpi_get_timings,
.check_timings = panel_dpi_check_timings,
.get_resolution = omapdss_default_get_resolution,
};
static int panel_dpi_probe_pdata(struct platform_device *pdev)
{
const struct panel_dpi_platform_data *pdata;
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev, *in;
struct videomode vm;
pdata = dev_get_platdata(&pdev->dev);
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&pdev->dev, "failed to find video source '%s'\n",
pdata->source);
return -EPROBE_DEFER;
}
ddata->in = in;
ddata->data_lines = pdata->data_lines;
videomode_from_timing(pdata->display_timing, &vm);
videomode_to_omap_video_timings(&vm, &ddata->videomode);
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
ddata->enable_gpio = pdata->enable_gpio;
ddata->backlight_gpio = pdata->backlight_gpio;
return 0;
}
static int panel_dpi_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (ddata == NULL)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
if (dev_get_platdata(&pdev->dev)) {
r = panel_dpi_probe_pdata(pdev);
if (r)
return r;
} else {
return -ENODEV;
}
if (gpio_is_valid(ddata->enable_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->enable_gpio,
GPIOF_OUT_INIT_LOW, "panel enable");
if (r)
goto err_gpio;
}
if (gpio_is_valid(ddata->backlight_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->backlight_gpio,
GPIOF_OUT_INIT_LOW, "panel backlight");
if (r)
goto err_gpio;
}
dssdev = &ddata->dssdev;
dssdev->dev = &pdev->dev;
dssdev->driver = &panel_dpi_ops;
dssdev->type = OMAP_DISPLAY_TYPE_DPI;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = ddata->videomode;
dssdev->phy.dpi.data_lines = ddata->data_lines;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&pdev->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
err_gpio:
omap_dss_put_device(ddata->in);
return r;
}
static int __exit panel_dpi_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
omapdss_unregister_display(dssdev);
panel_dpi_disable(dssdev);
panel_dpi_disconnect(dssdev);
omap_dss_put_device(in);
return 0;
}
static struct platform_driver panel_dpi_driver = {
.probe = panel_dpi_probe,
.remove = __exit_p(panel_dpi_remove),
.driver = {
.name = "panel-dpi",
.owner = THIS_MODULE,
},
};
module_platform_driver(panel_dpi_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("Generic MIPI DPI Panel Driver");
MODULE_LICENSE("GPL");
/*
* Generic DSI Command Mode panel driver
*
* Copyright (C) 2013 Texas Instruments
* 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.
*/
/* #define DEBUG */
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/workqueue.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_GET_ID1 0xda
#define DCS_GET_ID2 0xdb
#define DCS_GET_ID3 0xdc
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
struct omap_video_timings timings;
struct platform_device *pdev;
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 */
/* 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 intro_printed;
struct workqueue_struct *workqueue;
bool ulps_enabled;
unsigned ulps_timeout;
struct delayed_work ulps_work;
};
#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
static irqreturn_t dsicm_te_isr(int irq, void *data);
static void dsicm_te_timeout_work_callback(struct work_struct *work);
static int _dsicm_enable_te(struct panel_drv_data *ddata, bool enable);
static int dsicm_panel_reset(struct panel_drv_data *ddata);
static void dsicm_ulps_work(struct work_struct *work);
static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec)
{
ddata->hw_guard_wait = msecs_to_jiffies(guard_msec);
ddata->hw_guard_end = jiffies + ddata->hw_guard_wait;
}
static void hw_guard_wait(struct panel_drv_data *ddata)
{
unsigned long wait = ddata->hw_guard_end - jiffies;
if ((long)wait > 0 && wait <= ddata->hw_guard_wait) {
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(wait);
}
}
static int dsicm_dcs_read_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 *data)
{
struct omap_dss_device *in = ddata->in;
int r;
u8 buf[1];
r = in->ops.dsi->dcs_read(in, ddata->channel, dcs_cmd, buf, 1);
if (r < 0)
return r;
*data = buf[0];
return 0;
}
static int dsicm_dcs_write_0(struct panel_drv_data *ddata, u8 dcs_cmd)
{
struct omap_dss_device *in = ddata->in;
return in->ops.dsi->dcs_write(in, ddata->channel, &dcs_cmd, 1);
}
static int dsicm_dcs_write_1(struct panel_drv_data *ddata, u8 dcs_cmd, u8 param)
{
struct omap_dss_device *in = ddata->in;
u8 buf[2] = { dcs_cmd, param };
return in->ops.dsi->dcs_write(in, ddata->channel, buf, 2);
}
static int dsicm_sleep_in(struct panel_drv_data *ddata)
{
struct omap_dss_device *in = ddata->in;
u8 cmd;
int r;
hw_guard_wait(ddata);
cmd = MIPI_DCS_ENTER_SLEEP_MODE;
r = in->ops.dsi->dcs_write_nosync(in, ddata->channel, &cmd, 1);
if (r)
return r;
hw_guard_start(ddata, 120);
usleep_range(5000, 10000);
return 0;
}
static int dsicm_sleep_out(struct panel_drv_data *ddata)
{
int r;
hw_guard_wait(ddata);
r = dsicm_dcs_write_0(ddata, MIPI_DCS_EXIT_SLEEP_MODE);
if (r)
return r;
hw_guard_start(ddata, 120);
usleep_range(5000, 10000);
return 0;
}
static int dsicm_get_id(struct panel_drv_data *ddata, u8 *id1, u8 *id2, u8 *id3)
{
int r;
r = dsicm_dcs_read_1(ddata, DCS_GET_ID1, id1);
if (r)
return r;
r = dsicm_dcs_read_1(ddata, DCS_GET_ID2, id2);
if (r)
return r;
r = dsicm_dcs_read_1(ddata, DCS_GET_ID3, id3);
if (r)
return r;
return 0;
}
static int dsicm_set_update_window(struct panel_drv_data *ddata,
u16 x, u16 y, u16 w, u16 h)
{
struct omap_dss_device *in = ddata->in;
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 = in->ops.dsi->dcs_write_nosync(in, ddata->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 = in->ops.dsi->dcs_write_nosync(in, ddata->channel, buf, sizeof(buf));
if (r)
return r;
in->ops.dsi->bta_sync(in, ddata->channel);
return r;
}
static void dsicm_queue_ulps_work(struct panel_drv_data *ddata)
{
if (ddata->ulps_timeout > 0)
queue_delayed_work(ddata->workqueue, &ddata->ulps_work,
msecs_to_jiffies(ddata->ulps_timeout));
}
static void dsicm_cancel_ulps_work(struct panel_drv_data *ddata)
{
cancel_delayed_work(&ddata->ulps_work);
}
static int dsicm_enter_ulps(struct panel_drv_data *ddata)
{
struct omap_dss_device *in = ddata->in;
int r;
if (ddata->ulps_enabled)
return 0;
dsicm_cancel_ulps_work(ddata);
r = _dsicm_enable_te(ddata, false);
if (r)
goto err;
if (gpio_is_valid(ddata->ext_te_gpio))
disable_irq(gpio_to_irq(ddata->ext_te_gpio));
in->ops.dsi->disable(in, false, true);
ddata->ulps_enabled = true;
return 0;
err:
dev_err(&ddata->pdev->dev, "enter ULPS failed");
dsicm_panel_reset(ddata);
ddata->ulps_enabled = false;
dsicm_queue_ulps_work(ddata);
return r;
}
static int dsicm_exit_ulps(struct panel_drv_data *ddata)
{
struct omap_dss_device *in = ddata->in;
int r;
if (!ddata->ulps_enabled)
return 0;
r = in->ops.dsi->enable(in);
if (r) {
dev_err(&ddata->pdev->dev, "failed to enable DSI\n");
goto err1;
}
in->ops.dsi->enable_hs(in, ddata->channel, true);
r = _dsicm_enable_te(ddata, true);
if (r) {
dev_err(&ddata->pdev->dev, "failed to re-enable TE");
goto err2;
}
if (gpio_is_valid(ddata->ext_te_gpio))
enable_irq(gpio_to_irq(ddata->ext_te_gpio));
dsicm_queue_ulps_work(ddata);
ddata->ulps_enabled = false;
return 0;
err2:
dev_err(&ddata->pdev->dev, "failed to exit ULPS");
r = dsicm_panel_reset(ddata);
if (!r) {
if (gpio_is_valid(ddata->ext_te_gpio))
enable_irq(gpio_to_irq(ddata->ext_te_gpio));
ddata->ulps_enabled = false;
}
err1:
dsicm_queue_ulps_work(ddata);
return r;
}
static int dsicm_wake_up(struct panel_drv_data *ddata)
{
if (ddata->ulps_enabled)
return dsicm_exit_ulps(ddata);
dsicm_cancel_ulps_work(ddata);
dsicm_queue_ulps_work(ddata);
return 0;
}
static int dsicm_bl_update_status(struct backlight_device *dev)
{
struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
struct omap_dss_device *in = ddata->in;
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(&ddata->pdev->dev, "update brightness to %d\n", level);
mutex_lock(&ddata->lock);
if (ddata->enabled) {
in->ops.dsi->bus_lock(in);
r = dsicm_wake_up(ddata);
if (!r)
r = dsicm_dcs_write_1(ddata, DCS_BRIGHTNESS, level);
in->ops.dsi->bus_unlock(in);
} else {
r = 0;
}
mutex_unlock(&ddata->lock);
return r;
}
static int dsicm_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 dsicm_bl_ops = {
.get_brightness = dsicm_bl_get_intensity,
.update_status = dsicm_bl_update_status,
};
static void dsicm_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 dsicm_num_errors_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *in = ddata->in;
u8 errors = 0;
int r;
mutex_lock(&ddata->lock);
if (ddata->enabled) {
in->ops.dsi->bus_lock(in);
r = dsicm_wake_up(ddata);
if (!r)
r = dsicm_dcs_read_1(ddata, DCS_READ_NUM_ERRORS,
&errors);
in->ops.dsi->bus_unlock(in);
} else {
r = -ENODEV;
}
mutex_unlock(&ddata->lock);
if (r)
return r;
return snprintf(buf, PAGE_SIZE, "%d\n", errors);
}
static ssize_t dsicm_hw_revision_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *in = ddata->in;
u8 id1, id2, id3;
int r;
mutex_lock(&ddata->lock);
if (ddata->enabled) {
in->ops.dsi->bus_lock(in);
r = dsicm_wake_up(ddata);
if (!r)
r = dsicm_get_id(ddata, &id1, &id2, &id3);
in->ops.dsi->bus_unlock(in);
} else {
r = -ENODEV;
}
mutex_unlock(&ddata->lock);
if (r)
return r;
return snprintf(buf, PAGE_SIZE, "%02x.%02x.%02x\n", id1, id2, id3);
}
static ssize_t dsicm_store_ulps(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *in = ddata->in;
unsigned long t;
int r;
r = kstrtoul(buf, 0, &t);
if (r)
return r;
mutex_lock(&ddata->lock);
if (ddata->enabled) {
in->ops.dsi->bus_lock(in);
if (t)
r = dsicm_enter_ulps(ddata);
else
r = dsicm_wake_up(ddata);
in->ops.dsi->bus_unlock(in);
}
mutex_unlock(&ddata->lock);
if (r)
return r;
return count;
}
static ssize_t dsicm_show_ulps(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
unsigned t;
mutex_lock(&ddata->lock);
t = ddata->ulps_enabled;
mutex_unlock(&ddata->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static ssize_t dsicm_store_ulps_timeout(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *in = ddata->in;
unsigned long t;
int r;
r = kstrtoul(buf, 0, &t);
if (r)
return r;
mutex_lock(&ddata->lock);
ddata->ulps_timeout = t;
if (ddata->enabled) {
/* dsicm_wake_up will restart the timer */
in->ops.dsi->bus_lock(in);
r = dsicm_wake_up(ddata);
in->ops.dsi->bus_unlock(in);
}
mutex_unlock(&ddata->lock);
if (r)
return r;
return count;
}
static ssize_t dsicm_show_ulps_timeout(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
unsigned t;
mutex_lock(&ddata->lock);
t = ddata->ulps_timeout;
mutex_unlock(&ddata->lock);
return snprintf(buf, PAGE_SIZE, "%u\n", t);
}
static DEVICE_ATTR(num_dsi_errors, S_IRUGO, dsicm_num_errors_show, NULL);
static DEVICE_ATTR(hw_revision, S_IRUGO, dsicm_hw_revision_show, NULL);
static DEVICE_ATTR(ulps, S_IRUGO | S_IWUSR,
dsicm_show_ulps, dsicm_store_ulps);
static DEVICE_ATTR(ulps_timeout, S_IRUGO | S_IWUSR,
dsicm_show_ulps_timeout, dsicm_store_ulps_timeout);
static struct attribute *dsicm_attrs[] = {
&dev_attr_num_dsi_errors.attr,
&dev_attr_hw_revision.attr,
&dev_attr_ulps.attr,
&dev_attr_ulps_timeout.attr,
NULL,
};
static struct attribute_group dsicm_attr_group = {
.attrs = dsicm_attrs,
};
static void dsicm_hw_reset(struct panel_drv_data *ddata)
{
if (!gpio_is_valid(ddata->reset_gpio))
return;
gpio_set_value(ddata->reset_gpio, 1);
udelay(10);
/* reset the panel */
gpio_set_value(ddata->reset_gpio, 0);
/* assert reset */
udelay(10);
gpio_set_value(ddata->reset_gpio, 1);
/* wait after releasing reset */
usleep_range(5000, 10000);
}
static int dsicm_power_on(struct panel_drv_data *ddata)
{
struct omap_dss_device *in = ddata->in;
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 = &ddata->timings,
.hs_clk_min = 150000000,
.hs_clk_max = 300000000,
.lp_clk_min = 7000000,
.lp_clk_max = 10000000,
};
r = in->ops.dsi->configure_pins(in, &ddata->pin_config);
if (r) {
dev_err(&ddata->pdev->dev, "failed to configure DSI pins\n");
goto err0;
};
r = in->ops.dsi->set_config(in, &dsi_config);
if (r) {
dev_err(&ddata->pdev->dev, "failed to configure DSI\n");
goto err0;
}
r = in->ops.dsi->enable(in);
if (r) {
dev_err(&ddata->pdev->dev, "failed to enable DSI\n");
goto err0;
}
dsicm_hw_reset(ddata);
in->ops.dsi->enable_hs(in, ddata->channel, false);
r = dsicm_sleep_out(ddata);
if (r)
goto err;
r = dsicm_get_id(ddata, &id1, &id2, &id3);
if (r)
goto err;
r = dsicm_dcs_write_1(ddata, DCS_BRIGHTNESS, 0xff);
if (r)
goto err;
r = dsicm_dcs_write_1(ddata, DCS_CTRL_DISPLAY,
(1<<2) | (1<<5)); /* BL | BCTRL */
if (r)
goto err;
r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_PIXEL_FORMAT,
MIPI_DCS_PIXEL_FMT_24BIT);
if (r)
goto err;
r = dsicm_dcs_write_0(ddata, MIPI_DCS_SET_DISPLAY_ON);
if (r)
goto err;
r = _dsicm_enable_te(ddata, ddata->te_enabled);
if (r)
goto err;
r = in->ops.dsi->enable_video_output(in, ddata->channel);
if (r)
goto err;
ddata->enabled = 1;
if (!ddata->intro_printed) {
dev_info(&ddata->pdev->dev, "panel revision %02x.%02x.%02x\n",
id1, id2, id3);
ddata->intro_printed = true;
}
in->ops.dsi->enable_hs(in, ddata->channel, true);
return 0;
err:
dev_err(&ddata->pdev->dev, "error while enabling panel, issuing HW reset\n");
dsicm_hw_reset(ddata);
in->ops.dsi->disable(in, true, false);
err0:
return r;
}
static void dsicm_power_off(struct panel_drv_data *ddata)
{
struct omap_dss_device *in = ddata->in;
int r;
in->ops.dsi->disable_video_output(in, ddata->channel);
r = dsicm_dcs_write_0(ddata, MIPI_DCS_SET_DISPLAY_OFF);
if (!r)
r = dsicm_sleep_in(ddata);
if (r) {
dev_err(&ddata->pdev->dev,
"error disabling panel, issuing HW reset\n");
dsicm_hw_reset(ddata);
}
in->ops.dsi->disable(in, true, false);
ddata->enabled = 0;
}
static int dsicm_panel_reset(struct panel_drv_data *ddata)
{
dev_err(&ddata->pdev->dev, "performing LCD reset\n");
dsicm_power_off(ddata);
dsicm_hw_reset(ddata);
return dsicm_power_on(ddata);
}
static int dsicm_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
struct device *dev = &ddata->pdev->dev;
int r;
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.dsi->connect(in, dssdev);
if (r) {
dev_err(dev, "Failed to connect to video source\n");
return r;
}
r = in->ops.dsi->request_vc(ddata->in, &ddata->channel);
if (r) {
dev_err(dev, "failed to get virtual channel\n");
goto err_req_vc;
}
r = in->ops.dsi->set_vc_id(ddata->in, ddata->channel, TCH);
if (r) {
dev_err(dev, "failed to set VC_ID\n");
goto err_vc_id;
}
return 0;
err_vc_id:
in->ops.dsi->release_vc(ddata->in, ddata->channel);
err_req_vc:
in->ops.dsi->disconnect(in, dssdev);
return r;
}
static void dsicm_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.dsi->release_vc(in, ddata->channel);
in->ops.dsi->disconnect(in, dssdev);
}
static int dsicm_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
dev_dbg(&ddata->pdev->dev, "enable\n");
mutex_lock(&ddata->lock);
if (!omapdss_device_is_connected(dssdev)) {
r = -ENODEV;
goto err;
}
if (omapdss_device_is_enabled(dssdev)) {
r = 0;
goto err;
}
in->ops.dsi->bus_lock(in);
r = dsicm_power_on(ddata);
in->ops.dsi->bus_unlock(in);
if (r)
goto err;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
mutex_unlock(&ddata->lock);
return 0;
err:
dev_dbg(&ddata->pdev->dev, "enable failed\n");
mutex_unlock(&ddata->lock);
return r;
}
static void dsicm_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
dev_dbg(&ddata->pdev->dev, "disable\n");
mutex_lock(&ddata->lock);
dsicm_cancel_ulps_work(ddata);
in->ops.dsi->bus_lock(in);
if (omapdss_device_is_enabled(dssdev)) {
r = dsicm_wake_up(ddata);
if (!r)
dsicm_power_off(ddata);
}
in->ops.dsi->bus_unlock(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
mutex_unlock(&ddata->lock);
}
static void dsicm_framedone_cb(int err, void *data)
{
struct panel_drv_data *ddata = data;
struct omap_dss_device *in = ddata->in;
dev_dbg(&ddata->pdev->dev, "framedone, err %d\n", err);
in->ops.dsi->bus_unlock(ddata->in);
}
static irqreturn_t dsicm_te_isr(int irq, void *data)
{
struct panel_drv_data *ddata = data;
struct omap_dss_device *in = ddata->in;
int old;
int r;
old = atomic_cmpxchg(&ddata->do_update, 1, 0);
if (old) {
cancel_delayed_work(&ddata->te_timeout_work);
r = in->ops.dsi->update(in, ddata->channel, dsicm_framedone_cb,
ddata);
if (r)
goto err;
}
return IRQ_HANDLED;
err:
dev_err(&ddata->pdev->dev, "start update failed\n");
in->ops.dsi->bus_unlock(in);
return IRQ_HANDLED;
}
static void dsicm_te_timeout_work_callback(struct work_struct *work)
{
struct panel_drv_data *ddata = container_of(work, struct panel_drv_data,
te_timeout_work.work);
struct omap_dss_device *in = ddata->in;
dev_err(&ddata->pdev->dev, "TE not received for 250ms!\n");
atomic_set(&ddata->do_update, 0);
in->ops.dsi->bus_unlock(in);
}
static int dsicm_update(struct omap_dss_device *dssdev,
u16 x, u16 y, u16 w, u16 h)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
dev_dbg(&ddata->pdev->dev, "update %d, %d, %d x %d\n", x, y, w, h);
mutex_lock(&ddata->lock);
in->ops.dsi->bus_lock(in);
r = dsicm_wake_up(ddata);
if (r)
goto err;
if (!ddata->enabled) {
r = 0;
goto err;
}
/* XXX no need to send this every frame, but dsi break if not done */
r = dsicm_set_update_window(ddata, 0, 0,
dssdev->panel.timings.x_res,
dssdev->panel.timings.y_res);
if (r)
goto err;
if (ddata->te_enabled && gpio_is_valid(ddata->ext_te_gpio)) {
schedule_delayed_work(&ddata->te_timeout_work,
msecs_to_jiffies(250));
atomic_set(&ddata->do_update, 1);
} else {
r = in->ops.dsi->update(in, ddata->channel, dsicm_framedone_cb,
ddata);
if (r)
goto err;
}
/* note: no bus_unlock here. unlock is in framedone_cb */
mutex_unlock(&ddata->lock);
return 0;
err:
in->ops.dsi->bus_unlock(in);
mutex_unlock(&ddata->lock);
return r;
}
static int dsicm_sync(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
dev_dbg(&ddata->pdev->dev, "sync\n");
mutex_lock(&ddata->lock);
in->ops.dsi->bus_lock(in);
in->ops.dsi->bus_unlock(in);
mutex_unlock(&ddata->lock);
dev_dbg(&ddata->pdev->dev, "sync done\n");
return 0;
}
static int _dsicm_enable_te(struct panel_drv_data *ddata, bool enable)
{
struct omap_dss_device *in = ddata->in;
int r;
if (enable)
r = dsicm_dcs_write_1(ddata, MIPI_DCS_SET_TEAR_ON, 0);
else
r = dsicm_dcs_write_0(ddata, MIPI_DCS_SET_TEAR_OFF);
if (!gpio_is_valid(ddata->ext_te_gpio))
in->ops.dsi->enable_te(in, enable);
/* possible panel bug */
msleep(100);
return r;
}
static int dsicm_enable_te(struct omap_dss_device *dssdev, bool enable)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
mutex_lock(&ddata->lock);
if (ddata->te_enabled == enable)
goto end;
in->ops.dsi->bus_lock(in);
if (ddata->enabled) {
r = dsicm_wake_up(ddata);
if (r)
goto err;
r = _dsicm_enable_te(ddata, enable);
if (r)
goto err;
}
ddata->te_enabled = enable;
in->ops.dsi->bus_unlock(in);
end:
mutex_unlock(&ddata->lock);
return 0;
err:
in->ops.dsi->bus_unlock(in);
mutex_unlock(&ddata->lock);
return r;
}
static int dsicm_get_te(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
int r;
mutex_lock(&ddata->lock);
r = ddata->te_enabled;
mutex_unlock(&ddata->lock);
return r;
}
static int dsicm_memory_read(struct omap_dss_device *dssdev,
void *buf, size_t size,
u16 x, u16 y, u16 w, u16 h)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
int first = 1;
int plen;
unsigned buf_used = 0;
if (size < w * h * 3)
return -ENOMEM;
mutex_lock(&ddata->lock);
if (!ddata->enabled) {
r = -ENODEV;
goto err1;
}
size = min(w * h * 3,
dssdev->panel.timings.x_res *
dssdev->panel.timings.y_res * 3);
in->ops.dsi->bus_lock(in);
r = dsicm_wake_up(ddata);
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;
dsicm_set_update_window(ddata, x, y, w, h);
r = in->ops.dsi->set_max_rx_packet_size(in, ddata->channel, plen);
if (r)
goto err2;
while (buf_used < size) {
u8 dcs_cmd = first ? 0x2e : 0x3e;
first = 0;
r = in->ops.dsi->dcs_read(in, ddata->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(&ddata->pdev->dev, "short read\n");
break;
}
if (signal_pending(current)) {
dev_err(&ddata->pdev->dev, "signal pending, "
"aborting memory read\n");
r = -ERESTARTSYS;
goto err3;
}
}
r = buf_used;
err3:
in->ops.dsi->set_max_rx_packet_size(in, ddata->channel, 1);
err2:
in->ops.dsi->bus_unlock(in);
err1:
mutex_unlock(&ddata->lock);
return r;
}
static void dsicm_ulps_work(struct work_struct *work)
{
struct panel_drv_data *ddata = container_of(work, struct panel_drv_data,
ulps_work.work);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
mutex_lock(&ddata->lock);
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || !ddata->enabled) {
mutex_unlock(&ddata->lock);
return;
}
in->ops.dsi->bus_lock(in);
dsicm_enter_ulps(ddata);
in->ops.dsi->bus_unlock(in);
mutex_unlock(&ddata->lock);
}
static struct omap_dss_driver dsicm_ops = {
.connect = dsicm_connect,
.disconnect = dsicm_disconnect,
.enable = dsicm_enable,
.disable = dsicm_disable,
.update = dsicm_update,
.sync = dsicm_sync,
.get_resolution = dsicm_get_resolution,
.get_recommended_bpp = omapdss_default_get_recommended_bpp,
.enable_te = dsicm_enable_te,
.get_te = dsicm_get_te,
.memory_read = dsicm_memory_read,
};
static int dsicm_probe_pdata(struct platform_device *pdev)
{
const struct panel_dsicm_platform_data *pdata;
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev, *in;
pdata = dev_get_platdata(&pdev->dev);
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&pdev->dev, "failed to find video source\n");
return -EPROBE_DEFER;
}
ddata->in = in;
ddata->reset_gpio = pdata->reset_gpio;
if (pdata->use_ext_te)
ddata->ext_te_gpio = pdata->ext_te_gpio;
else
ddata->ext_te_gpio = -1;
ddata->ulps_timeout = pdata->ulps_timeout;
ddata->use_dsi_backlight = pdata->use_dsi_backlight;
ddata->pin_config = pdata->pin_config;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int dsicm_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct panel_drv_data *ddata;
struct backlight_device *bldev = NULL;
struct device *dev = &pdev->dev;
struct omap_dss_device *dssdev;
int r;
dev_dbg(dev, "probe\n");
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
ddata->pdev = pdev;
if (dev_get_platdata(dev)) {
r = dsicm_probe_pdata(pdev);
if (r)
return r;
} else {
return -ENODEV;
}
ddata->timings.x_res = 864;
ddata->timings.y_res = 480;
ddata->timings.pixel_clock = DIV_ROUND_UP(864 * 480 * 60, 1000);
dssdev = &ddata->dssdev;
dssdev->dev = dev;
dssdev->driver = &dsicm_ops;
dssdev->panel.timings = ddata->timings;
dssdev->type = OMAP_DISPLAY_TYPE_DSI;
dssdev->owner = THIS_MODULE;
dssdev->panel.dsi_pix_fmt = OMAP_DSS_DSI_FMT_RGB888;
dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE |
OMAP_DSS_DISPLAY_CAP_TEAR_ELIM;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(dev, "Failed to register panel\n");
goto err_reg;
}
mutex_init(&ddata->lock);
atomic_set(&ddata->do_update, 0);
if (gpio_is_valid(ddata->reset_gpio)) {
r = devm_gpio_request_one(dev, ddata->reset_gpio,
GPIOF_OUT_INIT_LOW, "taal rst");
if (r) {
dev_err(dev, "failed to request reset gpio\n");
return r;
}
}
if (gpio_is_valid(ddata->ext_te_gpio)) {
r = devm_gpio_request_one(dev, ddata->ext_te_gpio,
GPIOF_IN, "taal irq");
if (r) {
dev_err(dev, "GPIO request failed\n");
return r;
}
r = devm_request_irq(dev, gpio_to_irq(ddata->ext_te_gpio),
dsicm_te_isr,
IRQF_TRIGGER_RISING,
"taal vsync", ddata);
if (r) {
dev_err(dev, "IRQ request failed\n");
return r;
}
INIT_DEFERRABLE_WORK(&ddata->te_timeout_work,
dsicm_te_timeout_work_callback);
dev_dbg(dev, "Using GPIO TE\n");
}
ddata->workqueue = create_singlethread_workqueue("dsicm_wq");
if (ddata->workqueue == NULL) {
dev_err(dev, "can't create workqueue\n");
return -ENOMEM;
}
INIT_DELAYED_WORK(&ddata->ulps_work, dsicm_ulps_work);
dsicm_hw_reset(ddata);
if (ddata->use_dsi_backlight) {
memset(&props, 0, sizeof(struct backlight_properties));
props.max_brightness = 255;
props.type = BACKLIGHT_RAW;
bldev = backlight_device_register(dev_name(dev),
dev, ddata, &dsicm_bl_ops, &props);
if (IS_ERR(bldev)) {
r = PTR_ERR(bldev);
goto err_bl;
}
ddata->bldev = bldev;
bldev->props.fb_blank = FB_BLANK_UNBLANK;
bldev->props.power = FB_BLANK_UNBLANK;
bldev->props.brightness = 255;
dsicm_bl_update_status(bldev);
}
r = sysfs_create_group(&dev->kobj, &dsicm_attr_group);
if (r) {
dev_err(dev, "failed to create sysfs files\n");
goto err_sysfs_create;
}
return 0;
err_sysfs_create:
if (bldev != NULL)
backlight_device_unregister(bldev);
err_bl:
destroy_workqueue(ddata->workqueue);
err_reg:
return r;
}
static int __exit dsicm_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct backlight_device *bldev;
dev_dbg(&pdev->dev, "remove\n");
omapdss_unregister_display(dssdev);
dsicm_disable(dssdev);
dsicm_disconnect(dssdev);
sysfs_remove_group(&pdev->dev.kobj, &dsicm_attr_group);
bldev = ddata->bldev;
if (bldev != NULL) {
bldev->props.power = FB_BLANK_POWERDOWN;
dsicm_bl_update_status(bldev);
backlight_device_unregister(bldev);
}
omap_dss_put_device(ddata->in);
dsicm_cancel_ulps_work(ddata);
destroy_workqueue(ddata->workqueue);
/* reset, to be sure that the panel is in a valid state */
dsicm_hw_reset(ddata);
return 0;
}
static struct platform_driver dsicm_driver = {
.probe = dsicm_probe,
.remove = __exit_p(dsicm_remove),
.driver = {
.name = "panel-dsi-cm",
.owner = THIS_MODULE,
},
};
module_platform_driver(dsicm_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("Generic DSI Command Mode Panel Driver");
MODULE_LICENSE("GPL");
/*
* LG.Philips LB035Q02 LCD Panel driver
*
* Copyright (C) 2013 Texas Instruments
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
* Based on a driver by: 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.
*/
#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>
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,
};
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
struct spi_device *spi;
int data_lines;
struct omap_video_timings videomode;
int reset_gpio;
int backlight_gpio;
int enable_gpio;
};
#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
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_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.dpi->connect(in, dssdev);
if (r)
return r;
init_lb035q02_panel(ddata->spi);
return 0;
}
static void lb035q02_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.dpi->disconnect(in, dssdev);
}
static int lb035q02_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.dpi->set_data_lines(in, ddata->data_lines);
in->ops.dpi->set_timings(in, &ddata->videomode);
r = in->ops.dpi->enable(in);
if (r)
return r;
if (gpio_is_valid(ddata->enable_gpio))
gpio_set_value_cansleep(ddata->enable_gpio, 1);
if (gpio_is_valid(ddata->backlight_gpio))
gpio_set_value_cansleep(ddata->backlight_gpio, 1);
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void lb035q02_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_enabled(dssdev))
return;
if (gpio_is_valid(ddata->enable_gpio))
gpio_set_value_cansleep(ddata->enable_gpio, 0);
if (gpio_is_valid(ddata->backlight_gpio))
gpio_set_value_cansleep(ddata->backlight_gpio, 0);
in->ops.dpi->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void lb035q02_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->videomode = *timings;
dssdev->panel.timings = *timings;
in->ops.dpi->set_timings(in, timings);
}
static void lb035q02_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->videomode;
}
static int lb035q02_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.dpi->check_timings(in, timings);
}
static struct omap_dss_driver lb035q02_ops = {
.connect = lb035q02_connect,
.disconnect = lb035q02_disconnect,
.enable = lb035q02_enable,
.disable = lb035q02_disable,
.set_timings = lb035q02_set_timings,
.get_timings = lb035q02_get_timings,
.check_timings = lb035q02_check_timings,
.get_resolution = omapdss_default_get_resolution,
};
static int lb035q02_probe_pdata(struct spi_device *spi)
{
const struct panel_lb035q02_platform_data *pdata;
struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
struct omap_dss_device *dssdev, *in;
pdata = dev_get_platdata(&spi->dev);
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&spi->dev, "failed to find video source '%s'\n",
pdata->source);
return -EPROBE_DEFER;
}
ddata->in = in;
ddata->data_lines = pdata->data_lines;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
ddata->enable_gpio = pdata->enable_gpio;
ddata->backlight_gpio = pdata->backlight_gpio;
return 0;
}
static int lb035q02_panel_spi_probe(struct spi_device *spi)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
if (ddata == NULL)
return -ENOMEM;
dev_set_drvdata(&spi->dev, ddata);
ddata->spi = spi;
if (dev_get_platdata(&spi->dev)) {
r = lb035q02_probe_pdata(spi);
if (r)
return r;
} else {
return -ENODEV;
}
if (gpio_is_valid(ddata->enable_gpio)) {
r = devm_gpio_request_one(&spi->dev, ddata->enable_gpio,
GPIOF_OUT_INIT_LOW, "panel enable");
if (r)
goto err_gpio;
}
if (gpio_is_valid(ddata->backlight_gpio)) {
r = devm_gpio_request_one(&spi->dev, ddata->backlight_gpio,
GPIOF_OUT_INIT_LOW, "panel backlight");
if (r)
goto err_gpio;
}
ddata->videomode = lb035q02_timings;
dssdev = &ddata->dssdev;
dssdev->dev = &spi->dev;
dssdev->driver = &lb035q02_ops;
dssdev->type = OMAP_DISPLAY_TYPE_DPI;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = ddata->videomode;
dssdev->phy.dpi.data_lines = ddata->data_lines;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&spi->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
err_gpio:
omap_dss_put_device(ddata->in);
return r;
}
static int lb035q02_panel_spi_remove(struct spi_device *spi)
{
struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
omapdss_unregister_display(dssdev);
lb035q02_disable(dssdev);
lb035q02_disconnect(dssdev);
omap_dss_put_device(in);
return 0;
}
static struct spi_driver lb035q02_spi_driver = {
.probe = lb035q02_panel_spi_probe,
.remove = lb035q02_panel_spi_remove,
.driver = {
.name = "panel_lgphilips_lb035q02",
.owner = THIS_MODULE,
},
};
module_spi_driver(lb035q02_spi_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("LG.Philips LB035Q02 LCD Panel driver");
MODULE_LICENSE("GPL");
/*
* NEC NL8048HL11 Panel driver
*
* Copyright (C) 2010 Texas Instruments Inc.
* Author: Erik Gilling <konkers@android.com>
* Converted to new DSS device model: 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 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/fb.h>
#include <linux/gpio.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
struct omap_video_timings videomode;
int data_lines;
int res_gpio;
int qvga_gpio;
struct spi_device *spi;
};
#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 },
};
static const struct omap_video_timings nec_8048_panel_timings = {
.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,
};
#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
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_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.dpi->connect(in, dssdev);
if (r)
return r;
return 0;
}
static void nec_8048_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.dpi->disconnect(in, dssdev);
}
static int nec_8048_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.dpi->set_data_lines(in, ddata->data_lines);
in->ops.dpi->set_timings(in, &ddata->videomode);
r = in->ops.dpi->enable(in);
if (r)
return r;
if (gpio_is_valid(ddata->res_gpio))
gpio_set_value_cansleep(ddata->res_gpio, 1);
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void nec_8048_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_enabled(dssdev))
return;
if (gpio_is_valid(ddata->res_gpio))
gpio_set_value_cansleep(ddata->res_gpio, 0);
in->ops.dpi->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void nec_8048_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->videomode = *timings;
dssdev->panel.timings = *timings;
in->ops.dpi->set_timings(in, timings);
}
static void nec_8048_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->videomode;
}
static int nec_8048_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.dpi->check_timings(in, timings);
}
static struct omap_dss_driver nec_8048_ops = {
.connect = nec_8048_connect,
.disconnect = nec_8048_disconnect,
.enable = nec_8048_enable,
.disable = nec_8048_disable,
.set_timings = nec_8048_set_timings,
.get_timings = nec_8048_get_timings,
.check_timings = nec_8048_check_timings,
.get_resolution = omapdss_default_get_resolution,
};
static int nec_8048_probe_pdata(struct spi_device *spi)
{
const struct panel_nec_nl8048hl11_platform_data *pdata;
struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
struct omap_dss_device *dssdev, *in;
pdata = dev_get_platdata(&spi->dev);
ddata->qvga_gpio = pdata->qvga_gpio;
ddata->res_gpio = pdata->res_gpio;
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&spi->dev, "failed to find video source '%s'\n",
pdata->source);
return -EPROBE_DEFER;
}
ddata->in = in;
ddata->data_lines = pdata->data_lines;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int nec_8048_probe(struct spi_device *spi)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
dev_dbg(&spi->dev, "%s\n", __func__);
spi->mode = SPI_MODE_0;
spi->bits_per_word = 32;
r = spi_setup(spi);
if (r < 0) {
dev_err(&spi->dev, "spi_setup failed: %d\n", r);
return r;
}
init_nec_8048_wvga_lcd(spi);
ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
if (ddata == NULL)
return -ENOMEM;
dev_set_drvdata(&spi->dev, ddata);
ddata->spi = spi;
if (dev_get_platdata(&spi->dev)) {
r = nec_8048_probe_pdata(spi);
if (r)
return r;
} else {
return -ENODEV;
}
if (gpio_is_valid(ddata->qvga_gpio)) {
r = devm_gpio_request_one(&spi->dev, ddata->qvga_gpio,
GPIOF_OUT_INIT_HIGH, "lcd QVGA");
if (r)
goto err_gpio;
}
if (gpio_is_valid(ddata->res_gpio)) {
r = devm_gpio_request_one(&spi->dev, ddata->res_gpio,
GPIOF_OUT_INIT_LOW, "lcd RES");
if (r)
goto err_gpio;
}
ddata->videomode = nec_8048_panel_timings;
dssdev = &ddata->dssdev;
dssdev->dev = &spi->dev;
dssdev->driver = &nec_8048_ops;
dssdev->type = OMAP_DISPLAY_TYPE_DPI;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = ddata->videomode;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&spi->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
err_gpio:
omap_dss_put_device(ddata->in);
return r;
}
static int nec_8048_remove(struct spi_device *spi)
{
struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
dev_dbg(&ddata->spi->dev, "%s\n", __func__);
omapdss_unregister_display(dssdev);
nec_8048_disable(dssdev);
nec_8048_disconnect(dssdev);
omap_dss_put_device(in);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int nec_8048_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_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_pm_ops, nec_8048_suspend,
nec_8048_resume);
#define NEC_8048_PM_OPS (&nec_8048_pm_ops)
#else
#define NEC_8048_PM_OPS NULL
#endif
static struct spi_driver nec_8048_driver = {
.driver = {
.name = "panel-nec-nl8048hl11",
.owner = THIS_MODULE,
.pm = NEC_8048_PM_OPS,
},
.probe = nec_8048_probe,
.remove = nec_8048_remove,
};
module_spi_driver(nec_8048_driver);
MODULE_AUTHOR("Erik Gilling <konkers@android.com>");
MODULE_DESCRIPTION("NEC-NL8048HL11 Driver");
MODULE_LICENSE("GPL");
/*
* LCD panel driver for Sharp LS037V7DW01
*
* Copyright (C) 2013 Texas Instruments
* 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.
*/
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <video/omapdss.h>
#include <video/omap-panel-data.h>
struct panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
int data_lines;
struct omap_video_timings videomode;
int resb_gpio;
int ini_gpio;
int mo_gpio;
int lr_gpio;
int ud_gpio;
};
static const 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,
};
#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
static int sharp_ls_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.dpi->connect(in, dssdev);
if (r)
return r;
return 0;
}
static void sharp_ls_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.dpi->disconnect(in, dssdev);
}
static int sharp_ls_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.dpi->set_data_lines(in, ddata->data_lines);
in->ops.dpi->set_timings(in, &ddata->videomode);
r = in->ops.dpi->enable(in);
if (r)
return r;
/* wait couple of vsyncs until enabling the LCD */
msleep(50);
if (gpio_is_valid(ddata->resb_gpio))
gpio_set_value_cansleep(ddata->resb_gpio, 1);
if (gpio_is_valid(ddata->ini_gpio))
gpio_set_value_cansleep(ddata->ini_gpio, 1);
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void sharp_ls_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_enabled(dssdev))
return;
if (gpio_is_valid(ddata->ini_gpio))
gpio_set_value_cansleep(ddata->ini_gpio, 0);
if (gpio_is_valid(ddata->resb_gpio))
gpio_set_value_cansleep(ddata->resb_gpio, 0);
/* wait at least 5 vsyncs after disabling the LCD */
msleep(100);
in->ops.dpi->disable(in);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void sharp_ls_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->videomode = *timings;
dssdev->panel.timings = *timings;
in->ops.dpi->set_timings(in, timings);
}
static void sharp_ls_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->videomode;
}
static int sharp_ls_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.dpi->check_timings(in, timings);
}
static struct omap_dss_driver sharp_ls_ops = {
.connect = sharp_ls_connect,
.disconnect = sharp_ls_disconnect,
.enable = sharp_ls_enable,
.disable = sharp_ls_disable,
.set_timings = sharp_ls_set_timings,
.get_timings = sharp_ls_get_timings,
.check_timings = sharp_ls_check_timings,
.get_resolution = omapdss_default_get_resolution,
};
static int sharp_ls_probe_pdata(struct platform_device *pdev)
{
const struct panel_sharp_ls037v7dw01_platform_data *pdata;
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev, *in;
pdata = dev_get_platdata(&pdev->dev);
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&pdev->dev, "failed to find video source '%s'\n",
pdata->source);
return -EPROBE_DEFER;
}
ddata->in = in;
ddata->data_lines = pdata->data_lines;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
ddata->resb_gpio = pdata->resb_gpio;
ddata->ini_gpio = pdata->ini_gpio;
ddata->mo_gpio = pdata->mo_gpio;
ddata->lr_gpio = pdata->lr_gpio;
ddata->ud_gpio = pdata->ud_gpio;
return 0;
}
static int sharp_ls_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (ddata == NULL)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
if (dev_get_platdata(&pdev->dev)) {
r = sharp_ls_probe_pdata(pdev);
if (r)
return r;
} else {
return -ENODEV;
}
if (gpio_is_valid(ddata->mo_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->mo_gpio,
GPIOF_OUT_INIT_LOW, "lcd MO");
if (r)
goto err_gpio;
}
if (gpio_is_valid(ddata->lr_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->lr_gpio,
GPIOF_OUT_INIT_HIGH, "lcd LR");
if (r)
goto err_gpio;
}
if (gpio_is_valid(ddata->ud_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->ud_gpio,
GPIOF_OUT_INIT_HIGH, "lcd UD");
if (r)
goto err_gpio;
}
if (gpio_is_valid(ddata->resb_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->resb_gpio,
GPIOF_OUT_INIT_LOW, "lcd RESB");
if (r)
goto err_gpio;
}
if (gpio_is_valid(ddata->ini_gpio)) {
r = devm_gpio_request_one(&pdev->dev, ddata->ini_gpio,
GPIOF_OUT_INIT_LOW, "lcd INI");
if (r)
goto err_gpio;
}
ddata->videomode = sharp_ls_timings;
dssdev = &ddata->dssdev;
dssdev->dev = &pdev->dev;
dssdev->driver = &sharp_ls_ops;
dssdev->type = OMAP_DISPLAY_TYPE_DPI;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = ddata->videomode;
dssdev->phy.dpi.data_lines = ddata->data_lines;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&pdev->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
err_gpio:
omap_dss_put_device(ddata->in);
return r;
}
static int __exit sharp_ls_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
omapdss_unregister_display(dssdev);
sharp_ls_disable(dssdev);
sharp_ls_disconnect(dssdev);
omap_dss_put_device(in);
return 0;
}
static struct platform_driver sharp_ls_driver = {
.probe = sharp_ls_probe,
.remove = __exit_p(sharp_ls_remove),
.driver = {
.name = "panel-sharp-ls037v7dw01",
.owner = THIS_MODULE,
},
};
module_platform_driver(sharp_ls_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("Sharp LS037V7DW01 Panel Driver");
MODULE_LICENSE("GPL");
/*
* Sony ACX565AKM LCD Panel driver
*
* 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 panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
int reset_gpio;
int datapairs;
struct omap_video_timings videomode;
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 backlight_device *bl_dev;
};
static const struct omap_video_timings acx565akm_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,
};
#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
static void acx565akm_transfer(struct panel_drv_data *ddata, 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(ddata->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(ddata->spi, &m);
if (r < 0)
dev_dbg(&ddata->spi->dev, "spi_sync %d\n", r);
}
static inline void acx565akm_cmd(struct panel_drv_data *ddata, int cmd)
{
acx565akm_transfer(ddata, cmd, NULL, 0, NULL, 0);
}
static inline void acx565akm_write(struct panel_drv_data *ddata,
int reg, const u8 *buf, int len)
{
acx565akm_transfer(ddata, reg, buf, len, NULL, 0);
}
static inline void acx565akm_read(struct panel_drv_data *ddata,
int reg, u8 *buf, int len)
{
acx565akm_transfer(ddata, reg, NULL, 0, buf, len);
}
static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec)
{
ddata->hw_guard_wait = msecs_to_jiffies(guard_msec);
ddata->hw_guard_end = jiffies + ddata->hw_guard_wait;
}
static void hw_guard_wait(struct panel_drv_data *ddata)
{
unsigned long wait = ddata->hw_guard_end - jiffies;
if ((long)wait > 0 && wait <= ddata->hw_guard_wait) {
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(wait);
}
}
static void set_sleep_mode(struct panel_drv_data *ddata, 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(ddata);
acx565akm_cmd(ddata, cmd);
hw_guard_start(ddata, 120);
}
static void set_display_state(struct panel_drv_data *ddata, int enabled)
{
int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF;
acx565akm_cmd(ddata, cmd);
}
static int panel_enabled(struct panel_drv_data *ddata)
{
u32 disp_status;
int enabled;
acx565akm_read(ddata, 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(&ddata->spi->dev,
"LCD panel %senabled by bootloader (status 0x%04x)\n",
enabled ? "" : "not ", disp_status);
return enabled;
}
static int panel_detect(struct panel_drv_data *ddata)
{
acx565akm_read(ddata, MIPID_CMD_READ_DISP_ID, ddata->display_id, 3);
dev_dbg(&ddata->spi->dev, "MIPI display ID: %02x%02x%02x\n",
ddata->display_id[0],
ddata->display_id[1],
ddata->display_id[2]);
switch (ddata->display_id[0]) {
case 0x10:
ddata->model = MIPID_VER_ACX565AKM;
ddata->name = "acx565akm";
ddata->has_bc = 1;
ddata->has_cabc = 1;
break;
case 0x29:
ddata->model = MIPID_VER_L4F00311;
ddata->name = "l4f00311";
break;
case 0x45:
ddata->model = MIPID_VER_LPH8923;
ddata->name = "lph8923";
break;
case 0x83:
ddata->model = MIPID_VER_LS041Y3;
ddata->name = "ls041y3";
break;
default:
ddata->name = "unknown";
dev_err(&ddata->spi->dev, "invalid display ID\n");
return -ENODEV;
}
ddata->revision = ddata->display_id[1];
dev_info(&ddata->spi->dev, "omapfb: %s rev %02x LCD detected\n",
ddata->name, ddata->revision);
return 0;
}
/*----------------------Backlight Control-------------------------*/
static void enable_backlight_ctrl(struct panel_drv_data *ddata, int enable)
{
u16 ctrl;
acx565akm_read(ddata, 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(ddata, MIPID_CMD_WRITE_CTRL_DISP, (u8 *)&ctrl, 2);
}
static void set_cabc_mode(struct panel_drv_data *ddata, unsigned mode)
{
u16 cabc_ctrl;
ddata->cabc_mode = mode;
if (!ddata->enabled)
return;
cabc_ctrl = 0;
acx565akm_read(ddata, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1);
cabc_ctrl &= ~3;
cabc_ctrl |= (1 << 8) | (mode & 3);
acx565akm_write(ddata, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2);
}
static unsigned get_cabc_mode(struct panel_drv_data *ddata)
{
return ddata->cabc_mode;
}
static unsigned get_hw_cabc_mode(struct panel_drv_data *ddata)
{
u8 cabc_ctrl;
acx565akm_read(ddata, MIPID_CMD_READ_CABC, &cabc_ctrl, 1);
return cabc_ctrl & 3;
}
static void acx565akm_set_brightness(struct panel_drv_data *ddata, int level)
{
int bv;
bv = level | (1 << 8);
acx565akm_write(ddata, MIPID_CMD_WRITE_DISP_BRIGHTNESS, (u8 *)&bv, 2);
if (level)
enable_backlight_ctrl(ddata, 1);
else
enable_backlight_ctrl(ddata, 0);
}
static int acx565akm_get_actual_brightness(struct panel_drv_data *ddata)
{
u8 bv;
acx565akm_read(ddata, MIPID_CMD_READ_DISP_BRIGHTNESS, &bv, 1);
return bv;
}
static int acx565akm_bl_update_status(struct backlight_device *dev)
{
struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
int r;
int level;
dev_dbg(&ddata->spi->dev, "%s\n", __func__);
mutex_lock(&ddata->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 (ddata->has_bc)
acx565akm_set_brightness(ddata, level);
else
r = -ENODEV;
mutex_unlock(&ddata->mutex);
return r;
}
static int acx565akm_bl_get_intensity(struct backlight_device *dev)
{
struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
dev_dbg(&dev->dev, "%s\n", __func__);
if (!ddata->has_bc)
return -ENODEV;
if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
dev->props.power == FB_BLANK_UNBLANK) {
if (ddata->has_bc)
return acx565akm_get_actual_brightness(ddata);
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 * const 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 panel_drv_data *ddata = dev_get_drvdata(dev);
const char *mode_str;
int mode;
int len;
if (!ddata->has_cabc)
mode = 0;
else
mode = get_cabc_mode(ddata);
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 panel_drv_data *ddata = 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 (!ddata->has_cabc && i != 0)
return -EINVAL;
mutex_lock(&ddata->mutex);
set_cabc_mode(ddata, i);
mutex_unlock(&ddata->mutex);
return count;
}
static ssize_t show_cabc_available_modes(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct panel_drv_data *ddata = dev_get_drvdata(dev);
int len;
int i;
if (!ddata->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,
};
static int acx565akm_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.sdi->connect(in, dssdev);
if (r)
return r;
return 0;
}
static void acx565akm_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.sdi->disconnect(in, dssdev);
}
static int acx565akm_panel_power_on(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
dev_dbg(&ddata->spi->dev, "%s\n", __func__);
in->ops.sdi->set_timings(in, &ddata->videomode);
in->ops.sdi->set_datapairs(in, ddata->datapairs);
r = in->ops.sdi->enable(in);
if (r) {
pr_err("%s sdi enable failed\n", __func__);
return r;
}
/*FIXME tweak me */
msleep(50);
if (gpio_is_valid(ddata->reset_gpio))
gpio_set_value(ddata->reset_gpio, 1);
if (ddata->enabled) {
dev_dbg(&ddata->spi->dev, "panel already enabled\n");
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(ddata, 0);
ddata->enabled = 1;
/* 5msec between sleep out and the next command. (8.2.16) */
usleep_range(5000, 10000);
set_display_state(ddata, 1);
set_cabc_mode(ddata, ddata->cabc_mode);
mutex_unlock(&ddata->mutex);
return acx565akm_bl_update_status(ddata->bl_dev);
}
static void acx565akm_panel_power_off(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
dev_dbg(dssdev->dev, "%s\n", __func__);
if (!ddata->enabled)
return;
set_display_state(ddata, 0);
set_sleep_mode(ddata, 1);
ddata->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(ddata->reset_gpio))
gpio_set_value(ddata->reset_gpio, 0);
/* FIXME need to tweak this delay */
msleep(100);
in->ops.sdi->disable(in);
}
static int acx565akm_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
int r;
dev_dbg(dssdev->dev, "%s\n", __func__);
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
mutex_lock(&ddata->mutex);
r = acx565akm_panel_power_on(dssdev);
mutex_unlock(&ddata->mutex);
if (r)
return r;
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void acx565akm_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
dev_dbg(dssdev->dev, "%s\n", __func__);
if (!omapdss_device_is_enabled(dssdev))
return;
mutex_lock(&ddata->mutex);
acx565akm_panel_power_off(dssdev);
mutex_unlock(&ddata->mutex);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void acx565akm_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->videomode = *timings;
dssdev->panel.timings = *timings;
in->ops.sdi->set_timings(in, timings);
}
static void acx565akm_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->videomode;
}
static int acx565akm_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.sdi->check_timings(in, timings);
}
static struct omap_dss_driver acx565akm_ops = {
.connect = acx565akm_connect,
.disconnect = acx565akm_disconnect,
.enable = acx565akm_enable,
.disable = acx565akm_disable,
.set_timings = acx565akm_set_timings,
.get_timings = acx565akm_get_timings,
.check_timings = acx565akm_check_timings,
.get_resolution = omapdss_default_get_resolution,
};
static int acx565akm_probe_pdata(struct spi_device *spi)
{
const struct panel_acx565akm_platform_data *pdata;
struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
struct omap_dss_device *dssdev, *in;
pdata = dev_get_platdata(&spi->dev);
ddata->reset_gpio = pdata->reset_gpio;
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&spi->dev, "failed to find video source '%s'\n",
pdata->source);
return -EPROBE_DEFER;
}
ddata->in = in;
ddata->datapairs = pdata->datapairs;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int acx565akm_probe(struct spi_device *spi)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
struct backlight_device *bldev;
int max_brightness, brightness;
struct backlight_properties props;
int r;
dev_dbg(&spi->dev, "%s\n", __func__);
spi->mode = SPI_MODE_3;
ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
if (ddata == NULL)
return -ENOMEM;
dev_set_drvdata(&spi->dev, ddata);
ddata->spi = spi;
mutex_init(&ddata->mutex);
if (dev_get_platdata(&spi->dev)) {
r = acx565akm_probe_pdata(spi);
if (r)
return r;
} else {
return -ENODEV;
}
if (gpio_is_valid(ddata->reset_gpio)) {
r = devm_gpio_request_one(&spi->dev, ddata->reset_gpio,
GPIOF_OUT_INIT_LOW, "lcd reset");
if (r)
goto err_gpio;
}
if (gpio_is_valid(ddata->reset_gpio))
gpio_set_value(ddata->reset_gpio, 1);
/*
* After reset we have to wait 5 msec before the first
* command can be sent.
*/
usleep_range(5000, 10000);
ddata->enabled = panel_enabled(ddata);
r = panel_detect(ddata);
if (!ddata->enabled && gpio_is_valid(ddata->reset_gpio))
gpio_set_value(ddata->reset_gpio, 0);
if (r) {
dev_err(&spi->dev, "%s panel detect error\n", __func__);
goto err_detect;
}
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", &ddata->spi->dev,
ddata, &acx565akm_bl_ops, &props);
ddata->bl_dev = bldev;
if (ddata->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__);
goto err_sysfs;
}
ddata->cabc_mode = get_hw_cabc_mode(ddata);
}
max_brightness = 255;
if (ddata->has_bc)
brightness = acx565akm_get_actual_brightness(ddata);
else
brightness = 0;
bldev->props.max_brightness = max_brightness;
bldev->props.brightness = brightness;
acx565akm_bl_update_status(bldev);
ddata->videomode = acx565akm_panel_timings;
dssdev = &ddata->dssdev;
dssdev->dev = &spi->dev;
dssdev->driver = &acx565akm_ops;
dssdev->type = OMAP_DISPLAY_TYPE_SDI;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = ddata->videomode;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&spi->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
sysfs_remove_group(&bldev->dev.kobj, &bldev_attr_group);
err_sysfs:
backlight_device_unregister(bldev);
err_detect:
err_gpio:
omap_dss_put_device(ddata->in);
return r;
}
static int acx565akm_remove(struct spi_device *spi)
{
struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
dev_dbg(&ddata->spi->dev, "%s\n", __func__);
sysfs_remove_group(&ddata->bl_dev->dev.kobj, &bldev_attr_group);
backlight_device_unregister(ddata->bl_dev);
omapdss_unregister_display(dssdev);
acx565akm_disable(dssdev);
acx565akm_disconnect(dssdev);
omap_dss_put_device(in);
return 0;
}
static struct spi_driver acx565akm_driver = {
.driver = {
.name = "acx565akm",
.owner = THIS_MODULE,
},
.probe = acx565akm_probe,
.remove = acx565akm_remove,
};
module_spi_driver(acx565akm_driver);
MODULE_AUTHOR("Nokia Corporation");
MODULE_DESCRIPTION("acx565akm LCD Driver");
MODULE_LICENSE("GPL");
/*
* TPO TD043MTEA1 Panel driver
*
* Author: Gražvydas Ignotas <notasas@gmail.com>
* Converted to new DSS device model: 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 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 panel_drv_data {
struct omap_dss_device dssdev;
struct omap_dss_device *in;
struct omap_video_timings videomode;
int data_lines;
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;
};
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,
};
#define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
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 panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
ddata->hmirror = enable;
return tpo_td043_write_mirror(ddata->spi, ddata->hmirror,
ddata->vmirror);
}
static bool tpo_td043_get_hmirror(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dssdev->dev);
return ddata->hmirror;
}
static ssize_t tpo_td043_vmirror_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct panel_drv_data *ddata = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", ddata->vmirror);
}
static ssize_t tpo_td043_vmirror_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct panel_drv_data *ddata = 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(ddata->spi, ddata->hmirror, val);
if (ret < 0)
return ret;
ddata->vmirror = val;
return count;
}
static ssize_t tpo_td043_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct panel_drv_data *ddata = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", ddata->mode);
}
static ssize_t tpo_td043_mode_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct panel_drv_data *ddata = dev_get_drvdata(dev);
long val;
int ret;
ret = kstrtol(buf, 0, &val);
if (ret != 0 || val & ~7)
return -EINVAL;
ddata->mode = val;
val |= TPO_R02_NCLK_RISING;
tpo_td043_write(ddata->spi, 2, val);
return count;
}
static ssize_t tpo_td043_gamma_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct panel_drv_data *ddata = dev_get_drvdata(dev);
ssize_t len = 0;
int ret;
int i;
for (i = 0; i < ARRAY_SIZE(ddata->gamma); i++) {
ret = snprintf(buf + len, PAGE_SIZE - len, "%u ",
ddata->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 panel_drv_data *ddata = 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++)
ddata->gamma[i] = g[i];
tpo_td043_write_gamma(ddata->spi, ddata->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 int tpo_td043_power_on(struct panel_drv_data *ddata)
{
int r;
if (ddata->powered_on)
return 0;
r = regulator_enable(ddata->vcc_reg);
if (r != 0)
return r;
/* wait for panel to stabilize */
msleep(160);
if (gpio_is_valid(ddata->nreset_gpio))
gpio_set_value(ddata->nreset_gpio, 1);
tpo_td043_write(ddata->spi, 2,
TPO_R02_MODE(ddata->mode) | TPO_R02_NCLK_RISING);
tpo_td043_write(ddata->spi, 3, TPO_R03_VAL_NORMAL);
tpo_td043_write(ddata->spi, 0x20, 0xf0);
tpo_td043_write(ddata->spi, 0x21, 0xf0);
tpo_td043_write_mirror(ddata->spi, ddata->hmirror,
ddata->vmirror);
tpo_td043_write_gamma(ddata->spi, ddata->gamma);
ddata->powered_on = 1;
return 0;
}
static void tpo_td043_power_off(struct panel_drv_data *ddata)
{
if (!ddata->powered_on)
return;
tpo_td043_write(ddata->spi, 3,
TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM);
if (gpio_is_valid(ddata->nreset_gpio))
gpio_set_value(ddata->nreset_gpio, 0);
/* wait for at least 2 vsyncs before cutting off power */
msleep(50);
tpo_td043_write(ddata->spi, 3, TPO_R03_VAL_STANDBY);
regulator_disable(ddata->vcc_reg);
ddata->powered_on = 0;
}
static int tpo_td043_connect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (omapdss_device_is_connected(dssdev))
return 0;
r = in->ops.dpi->connect(in, dssdev);
if (r)
return r;
return 0;
}
static void tpo_td043_disconnect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_connected(dssdev))
return;
in->ops.dpi->disconnect(in, dssdev);
}
static int tpo_td043_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
int r;
if (!omapdss_device_is_connected(dssdev))
return -ENODEV;
if (omapdss_device_is_enabled(dssdev))
return 0;
in->ops.dpi->set_data_lines(in, ddata->data_lines);
in->ops.dpi->set_timings(in, &ddata->videomode);
r = in->ops.dpi->enable(in);
if (r)
return r;
/*
* 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 (!ddata->spi_suspended) {
r = tpo_td043_power_on(ddata);
if (r) {
in->ops.dpi->disable(in);
return r;
}
}
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
return 0;
}
static void tpo_td043_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
if (!omapdss_device_is_enabled(dssdev))
return;
in->ops.dpi->disable(in);
if (!ddata->spi_suspended)
tpo_td043_power_off(ddata);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
static void tpo_td043_set_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
ddata->videomode = *timings;
dssdev->panel.timings = *timings;
in->ops.dpi->set_timings(in, timings);
}
static void tpo_td043_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
*timings = ddata->videomode;
}
static int tpo_td043_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
struct omap_dss_device *in = ddata->in;
return in->ops.dpi->check_timings(in, timings);
}
static struct omap_dss_driver tpo_td043_ops = {
.connect = tpo_td043_connect,
.disconnect = tpo_td043_disconnect,
.enable = tpo_td043_enable,
.disable = tpo_td043_disable,
.set_timings = tpo_td043_set_timings,
.get_timings = tpo_td043_get_timings,
.check_timings = tpo_td043_check_timings,
.set_mirror = tpo_td043_set_hmirror,
.get_mirror = tpo_td043_get_hmirror,
.get_resolution = omapdss_default_get_resolution,
};
static int tpo_td043_probe_pdata(struct spi_device *spi)
{
const struct panel_tpo_td043mtea1_platform_data *pdata;
struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
struct omap_dss_device *dssdev, *in;
pdata = dev_get_platdata(&spi->dev);
ddata->nreset_gpio = pdata->nreset_gpio;
in = omap_dss_find_output(pdata->source);
if (in == NULL) {
dev_err(&spi->dev, "failed to find video source '%s'\n",
pdata->source);
return -EPROBE_DEFER;
}
ddata->in = in;
ddata->data_lines = pdata->data_lines;
dssdev = &ddata->dssdev;
dssdev->name = pdata->name;
return 0;
}
static int tpo_td043_probe(struct spi_device *spi)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
int r;
dev_dbg(&spi->dev, "%s\n", __func__);
spi->bits_per_word = 16;
spi->mode = SPI_MODE_0;
r = spi_setup(spi);
if (r < 0) {
dev_err(&spi->dev, "spi_setup failed: %d\n", r);
return r;
}
ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
if (ddata == NULL)
return -ENOMEM;
dev_set_drvdata(&spi->dev, ddata);
ddata->spi = spi;
if (dev_get_platdata(&spi->dev)) {
r = tpo_td043_probe_pdata(spi);
if (r)
return r;
} else {
return -ENODEV;
}
ddata->mode = TPO_R02_MODE_800x480;
memcpy(ddata->gamma, tpo_td043_def_gamma, sizeof(ddata->gamma));
ddata->vcc_reg = devm_regulator_get(&spi->dev, "vcc");
if (IS_ERR(ddata->vcc_reg)) {
dev_err(&spi->dev, "failed to get LCD VCC regulator\n");
r = PTR_ERR(ddata->vcc_reg);
goto err_regulator;
}
if (gpio_is_valid(ddata->nreset_gpio)) {
r = devm_gpio_request_one(&spi->dev,
ddata->nreset_gpio, GPIOF_OUT_INIT_LOW,
"lcd reset");
if (r < 0) {
dev_err(&spi->dev, "couldn't request reset GPIO\n");
goto err_gpio_req;
}
}
r = sysfs_create_group(&spi->dev.kobj, &tpo_td043_attr_group);
if (r) {
dev_err(&spi->dev, "failed to create sysfs files\n");
goto err_sysfs;
}
ddata->videomode = tpo_td043_timings;
dssdev = &ddata->dssdev;
dssdev->dev = &spi->dev;
dssdev->driver = &tpo_td043_ops;
dssdev->type = OMAP_DISPLAY_TYPE_DPI;
dssdev->owner = THIS_MODULE;
dssdev->panel.timings = ddata->videomode;
r = omapdss_register_display(dssdev);
if (r) {
dev_err(&spi->dev, "Failed to register panel\n");
goto err_reg;
}
return 0;
err_reg:
sysfs_remove_group(&spi->dev.kobj, &tpo_td043_attr_group);
err_sysfs:
err_gpio_req:
err_regulator:
omap_dss_put_device(ddata->in);
return r;
}
static int tpo_td043_remove(struct spi_device *spi)
{
struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
struct omap_dss_device *dssdev = &ddata->dssdev;
struct omap_dss_device *in = ddata->in;
dev_dbg(&ddata->spi->dev, "%s\n", __func__);
omapdss_unregister_display(dssdev);
tpo_td043_disable(dssdev);
tpo_td043_disconnect(dssdev);
omap_dss_put_device(in);
sysfs_remove_group(&spi->dev.kobj, &tpo_td043_attr_group);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int tpo_td043_spi_suspend(struct device *dev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dev);
dev_dbg(dev, "tpo_td043_spi_suspend, tpo %p\n", ddata);
ddata->power_on_resume = ddata->powered_on;
tpo_td043_power_off(ddata);
ddata->spi_suspended = 1;
return 0;
}
static int tpo_td043_spi_resume(struct device *dev)
{
struct panel_drv_data *ddata = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "tpo_td043_spi_resume\n");
if (ddata->power_on_resume) {
ret = tpo_td043_power_on(ddata);
if (ret)
return ret;
}
ddata->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 = "panel-tpo-td043mtea1",
.owner = THIS_MODULE,
.pm = &tpo_td043_spi_pm,
},
.probe = tpo_td043_probe,
.remove = tpo_td043_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");
menu "OMAP2/3 Display Device Drivers"
menu "OMAP2/3 Display Device Drivers (old device model)"
depends on OMAP2_DSS
config PANEL_GENERIC_DPI
......
......@@ -422,7 +422,19 @@ static void wait_pending_extra_info_updates(void)
static struct omap_dss_device *dss_mgr_get_device(struct omap_overlay_manager *mgr)
{
return mgr->output ? mgr->output->device : NULL;
struct omap_dss_device *dssdev;
dssdev = mgr->output;
if (dssdev == NULL)
return NULL;
while (dssdev->device)
dssdev = dssdev->device;
if (dssdev->driver)
return dssdev;
else
return NULL;
}
static struct omap_dss_device *dss_ovl_get_device(struct omap_overlay *ovl)
......
......@@ -61,6 +61,7 @@ int omapdss_default_get_recommended_bpp(struct omap_dss_device *dssdev)
case OMAP_DISPLAY_TYPE_VENC:
case OMAP_DISPLAY_TYPE_SDI:
case OMAP_DISPLAY_TYPE_HDMI:
case OMAP_DISPLAY_TYPE_DVI:
return 24;
default:
BUG();
......
......@@ -461,6 +461,16 @@ void omapdss_dpi_set_timings(struct omap_dss_device *dssdev,
}
EXPORT_SYMBOL(omapdss_dpi_set_timings);
static void dpi_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
mutex_lock(&dpi.lock);
*timings = dpi.timings;
mutex_unlock(&dpi.lock);
}
int dpi_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
......@@ -678,6 +688,65 @@ static int dpi_probe_pdata(struct platform_device *dpidev)
return 0;
}
static int dpi_connect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct omap_overlay_manager *mgr;
int r;
r = dpi_init_regulator();
if (r)
return r;
dpi_init_pll();
mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel);
if (!mgr)
return -ENODEV;
r = dss_mgr_connect(mgr, dssdev);
if (r)
return r;
r = omapdss_output_set_device(dssdev, dst);
if (r) {
DSSERR("failed to connect output to new device: %s\n",
dst->name);
dss_mgr_disconnect(mgr, dssdev);
return r;
}
return 0;
}
static void dpi_disconnect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
WARN_ON(dst != dssdev->device);
if (dst != dssdev->device)
return;
omapdss_output_unset_device(dssdev);
if (dssdev->manager)
dss_mgr_disconnect(dssdev->manager, dssdev);
}
static const struct omapdss_dpi_ops dpi_ops = {
.connect = dpi_connect,
.disconnect = dpi_disconnect,
.enable = omapdss_dpi_display_enable,
.disable = omapdss_dpi_display_disable,
.check_timings = dpi_check_timings,
.set_timings = omapdss_dpi_set_timings,
.get_timings = dpi_get_timings,
.set_data_lines = omapdss_dpi_set_data_lines,
};
static void dpi_init_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &dpi.output;
......@@ -687,16 +756,17 @@ static void dpi_init_output(struct platform_device *pdev)
out->output_type = OMAP_DISPLAY_TYPE_DPI;
out->name = "dpi.0";
out->dispc_channel = dpi_get_channel();
out->ops.dpi = &dpi_ops;
out->owner = THIS_MODULE;
dss_register_output(out);
omapdss_register_output(out);
}
static void __exit dpi_uninit_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &dpi.output;
dss_unregister_output(out);
omapdss_unregister_output(out);
}
static int omap_dpi_probe(struct platform_device *pdev)
......
......@@ -383,6 +383,15 @@ static inline struct dsi_data *dsi_get_dsidrv_data(struct platform_device *dside
static inline struct platform_device *dsi_get_dsidev_from_dssdev(struct omap_dss_device *dssdev)
{
/* HACK: dssdev can be either the panel device, when using old API, or
* the dsi device itself, when using the new API. So we solve this for
* now by checking the dssdev->id. This will be removed when the old API
* is removed.
*/
if (dssdev->id == OMAP_DSS_OUTPUT_DSI1 ||
dssdev->id == OMAP_DSS_OUTPUT_DSI2)
return to_platform_device(dssdev->dev);
return to_platform_device(dssdev->output->dev);
}
......@@ -5412,6 +5421,89 @@ static int dsi_probe_pdata(struct platform_device *dsidev)
return 0;
}
static int dsi_connect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct omap_overlay_manager *mgr;
int r;
r = dsi_regulator_init(dsidev);
if (r)
return r;
mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel);
if (!mgr)
return -ENODEV;
r = dss_mgr_connect(mgr, dssdev);
if (r)
return r;
r = omapdss_output_set_device(dssdev, dst);
if (r) {
DSSERR("failed to connect output to new device: %s\n",
dssdev->name);
dss_mgr_disconnect(mgr, dssdev);
return r;
}
return 0;
}
static void dsi_disconnect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
WARN_ON(dst != dssdev->device);
if (dst != dssdev->device)
return;
omapdss_output_unset_device(dssdev);
if (dssdev->manager)
dss_mgr_disconnect(dssdev->manager, dssdev);
}
static const struct omapdss_dsi_ops dsi_ops = {
.connect = dsi_connect,
.disconnect = dsi_disconnect,
.bus_lock = dsi_bus_lock,
.bus_unlock = dsi_bus_unlock,
.enable = omapdss_dsi_display_enable,
.disable = omapdss_dsi_display_disable,
.enable_hs = omapdss_dsi_vc_enable_hs,
.configure_pins = omapdss_dsi_configure_pins,
.set_config = omapdss_dsi_set_config,
.enable_video_output = dsi_enable_video_output,
.disable_video_output = dsi_disable_video_output,
.update = omap_dsi_update,
.enable_te = omapdss_dsi_enable_te,
.request_vc = omap_dsi_request_vc,
.set_vc_id = omap_dsi_set_vc_id,
.release_vc = omap_dsi_release_vc,
.dcs_write = dsi_vc_dcs_write,
.dcs_write_nosync = dsi_vc_dcs_write_nosync,
.dcs_read = dsi_vc_dcs_read,
.gen_write = dsi_vc_generic_write,
.gen_write_nosync = dsi_vc_generic_write_nosync,
.gen_read = dsi_vc_generic_read,
.bta_sync = dsi_vc_send_bta_sync,
.set_max_rx_packet_size = dsi_vc_set_max_rx_packet_size,
};
static void dsi_init_output(struct platform_device *dsidev)
{
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
......@@ -5424,9 +5516,10 @@ static void dsi_init_output(struct platform_device *dsidev)
out->output_type = OMAP_DISPLAY_TYPE_DSI;
out->name = dsi->module_id == 0 ? "dsi.0" : "dsi.1";
out->dispc_channel = dsi_get_channel(dsi->module_id);
out->ops.dsi = &dsi_ops;
out->owner = THIS_MODULE;
dss_register_output(out);
omapdss_register_output(out);
}
static void dsi_uninit_output(struct platform_device *dsidev)
......@@ -5434,7 +5527,7 @@ static void dsi_uninit_output(struct platform_device *dsidev)
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
struct omap_dss_device *out = &dsi->output;
dss_unregister_output(out);
omapdss_unregister_output(out);
}
/* DSI1 HW IP initialisation */
......
......@@ -179,10 +179,6 @@ void dss_put_device(struct omap_dss_device *dssdev);
void dss_copy_device_pdata(struct omap_dss_device *dst,
const struct omap_dss_device *src);
/* output */
void dss_register_output(struct omap_dss_device *out);
void dss_unregister_output(struct omap_dss_device *out);
/* display */
int dss_suspend_all_devices(void);
int dss_resume_all_devices(void);
......
......@@ -70,6 +70,8 @@ static struct {
int ls_oe_gpio;
int hpd_gpio;
bool core_enabled;
struct omap_dss_device output;
} hdmi;
......@@ -515,7 +517,9 @@ static int hdmi_power_on_core(struct omap_dss_device *dssdev)
{
int r;
if (gpio_is_valid(hdmi.ct_cp_hpd_gpio))
gpio_set_value(hdmi.ct_cp_hpd_gpio, 1);
if (gpio_is_valid(hdmi.ls_oe_gpio))
gpio_set_value(hdmi.ls_oe_gpio, 1);
/* wait 300us after CT_CP_HPD for the 5V power output to reach 90% */
......@@ -532,21 +536,29 @@ static int hdmi_power_on_core(struct omap_dss_device *dssdev)
/* Make selection of HDMI in DSS */
dss_select_hdmi_venc_clk_source(DSS_HDMI_M_PCLK);
hdmi.core_enabled = true;
return 0;
err_runtime_get:
regulator_disable(hdmi.vdda_hdmi_dac_reg);
err_vdac_enable:
if (gpio_is_valid(hdmi.ct_cp_hpd_gpio))
gpio_set_value(hdmi.ct_cp_hpd_gpio, 0);
if (gpio_is_valid(hdmi.ls_oe_gpio))
gpio_set_value(hdmi.ls_oe_gpio, 0);
return r;
}
static void hdmi_power_off_core(struct omap_dss_device *dssdev)
{
hdmi.core_enabled = false;
hdmi_runtime_put();
regulator_disable(hdmi.vdda_hdmi_dac_reg);
if (gpio_is_valid(hdmi.ct_cp_hpd_gpio))
gpio_set_value(hdmi.ct_cp_hpd_gpio, 0);
if (gpio_is_valid(hdmi.ls_oe_gpio))
gpio_set_value(hdmi.ls_oe_gpio, 0);
}
......@@ -662,6 +674,18 @@ void omapdss_hdmi_display_set_timing(struct omap_dss_device *dssdev,
mutex_unlock(&hdmi.lock);
}
static void omapdss_hdmi_display_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
const struct hdmi_config *cfg;
cfg = hdmi_get_timings();
if (cfg == NULL)
cfg = &vesa_timings[0];
memcpy(timings, &cfg->timings, sizeof(cfg->timings));
}
static void hdmi_dump_regs(struct seq_file *s)
{
mutex_lock(&hdmi.lock);
......@@ -1025,6 +1049,199 @@ static int hdmi_probe_pdata(struct platform_device *pdev)
return 0;
}
static int hdmi_connect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct omap_overlay_manager *mgr;
int r;
dss_init_hdmi_ip_ops(&hdmi.ip_data, omapdss_get_version());
r = hdmi_init_regulator();
if (r)
return r;
mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel);
if (!mgr)
return -ENODEV;
r = dss_mgr_connect(mgr, dssdev);
if (r)
return r;
r = omapdss_output_set_device(dssdev, dst);
if (r) {
DSSERR("failed to connect output to new device: %s\n",
dst->name);
dss_mgr_disconnect(mgr, dssdev);
return r;
}
return 0;
}
static void hdmi_disconnect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
WARN_ON(dst != dssdev->device);
if (dst != dssdev->device)
return;
omapdss_output_unset_device(dssdev);
if (dssdev->manager)
dss_mgr_disconnect(dssdev->manager, dssdev);
}
static int hdmi_read_edid(struct omap_dss_device *dssdev,
u8 *edid, int len)
{
bool need_enable;
int r;
need_enable = hdmi.core_enabled == false;
if (need_enable) {
r = omapdss_hdmi_core_enable(dssdev);
if (r)
return r;
}
r = omapdss_hdmi_read_edid(edid, len);
if (need_enable)
omapdss_hdmi_core_disable(dssdev);
return r;
}
#if defined(CONFIG_OMAP4_DSS_HDMI_AUDIO)
static int omapdss_hdmi_audio_enable(struct omap_dss_device *dssdev)
{
int r;
mutex_lock(&hdmi.lock);
if (!hdmi_mode_has_audio()) {
r = -EPERM;
goto err;
}
r = hdmi_audio_enable();
if (r)
goto err;
mutex_unlock(&hdmi.lock);
return 0;
err:
mutex_unlock(&hdmi.lock);
return r;
}
static void omapdss_hdmi_audio_disable(struct omap_dss_device *dssdev)
{
hdmi_audio_disable();
}
static int omapdss_hdmi_audio_start(struct omap_dss_device *dssdev)
{
return hdmi_audio_start();
}
static void omapdss_hdmi_audio_stop(struct omap_dss_device *dssdev)
{
hdmi_audio_stop();
}
static bool omapdss_hdmi_audio_supported(struct omap_dss_device *dssdev)
{
bool r;
mutex_lock(&hdmi.lock);
r = hdmi_mode_has_audio();
mutex_unlock(&hdmi.lock);
return r;
}
static int omapdss_hdmi_audio_config(struct omap_dss_device *dssdev,
struct omap_dss_audio *audio)
{
int r;
mutex_lock(&hdmi.lock);
if (!hdmi_mode_has_audio()) {
r = -EPERM;
goto err;
}
r = hdmi_audio_config(audio);
if (r)
goto err;
mutex_unlock(&hdmi.lock);
return 0;
err:
mutex_unlock(&hdmi.lock);
return r;
}
#else
static int omapdss_hdmi_audio_enable(struct omap_dss_device *dssdev)
{
return -EPERM;
}
static void omapdss_hdmi_audio_disable(struct omap_dss_device *dssdev)
{
}
static int omapdss_hdmi_audio_start(struct omap_dss_device *dssdev)
{
return -EPERM;
}
static void omapdss_hdmi_audio_stop(struct omap_dss_device *dssdev)
{
}
static bool omapdss_hdmi_audio_supported(struct omap_dss_device *dssdev)
{
return false;
}
static int omapdss_hdmi_audio_config(struct omap_dss_device *dssdev,
struct omap_dss_audio *audio)
{
return -EPERM;
}
#endif
static const struct omapdss_hdmi_ops hdmi_ops = {
.connect = hdmi_connect,
.disconnect = hdmi_disconnect,
.enable = omapdss_hdmi_display_enable,
.disable = omapdss_hdmi_display_disable,
.check_timings = omapdss_hdmi_display_check_timing,
.set_timings = omapdss_hdmi_display_set_timing,
.get_timings = omapdss_hdmi_display_get_timings,
.read_edid = hdmi_read_edid,
.audio_enable = omapdss_hdmi_audio_enable,
.audio_disable = omapdss_hdmi_audio_disable,
.audio_start = omapdss_hdmi_audio_start,
.audio_stop = omapdss_hdmi_audio_stop,
.audio_supported = omapdss_hdmi_audio_supported,
.audio_config = omapdss_hdmi_audio_config,
};
static void hdmi_init_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &hdmi.output;
......@@ -1034,16 +1251,17 @@ static void hdmi_init_output(struct platform_device *pdev)
out->output_type = OMAP_DISPLAY_TYPE_HDMI;
out->name = "hdmi.0";
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
out->ops.hdmi = &hdmi_ops;
out->owner = THIS_MODULE;
dss_register_output(out);
omapdss_register_output(out);
}
static void __exit hdmi_uninit_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &hdmi.output;
dss_unregister_output(out);
omapdss_unregister_output(out);
}
/* HDMI HW IP initialisation */
......@@ -1083,6 +1301,10 @@ static int omapdss_hdmihw_probe(struct platform_device *pdev)
hdmi.ip_data.pll_offset = HDMI_PLLCTRL;
hdmi.ip_data.phy_offset = HDMI_PHY;
hdmi.ct_cp_hpd_gpio = -1;
hdmi.ls_oe_gpio = -1;
hdmi.hpd_gpio = -1;
hdmi_init_output(pdev);
r = hdmi_panel_init();
......
......@@ -92,15 +92,18 @@ int omapdss_output_unset_device(struct omap_dss_device *out)
}
EXPORT_SYMBOL(omapdss_output_unset_device);
void dss_register_output(struct omap_dss_device *out)
int omapdss_register_output(struct omap_dss_device *out)
{
list_add_tail(&out->list, &output_list);
return 0;
}
EXPORT_SYMBOL(omapdss_register_output);
void dss_unregister_output(struct omap_dss_device *out)
void omapdss_unregister_output(struct omap_dss_device *out)
{
list_del(&out->list);
}
EXPORT_SYMBOL(omapdss_unregister_output);
struct omap_dss_device *omap_dss_get_output(enum omap_dss_output_id id)
{
......@@ -143,7 +146,13 @@ EXPORT_SYMBOL(omap_dss_find_output_by_node);
struct omap_dss_device *omapdss_find_output_from_display(struct omap_dss_device *dssdev)
{
return omap_dss_get_device(dssdev->output);
while (dssdev->output)
dssdev = dssdev->output;
if (dssdev->id != 0)
return omap_dss_get_device(dssdev);
return NULL;
}
EXPORT_SYMBOL(omapdss_find_output_from_display);
......
......@@ -1022,14 +1022,14 @@ static void rfbi_init_output(struct platform_device *pdev)
out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
out->owner = THIS_MODULE;
dss_register_output(out);
omapdss_register_output(out);
}
static void __exit rfbi_uninit_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &rfbi.output;
dss_unregister_output(out);
omapdss_unregister_output(out);
}
/* RFBI HW IP initialisation */
......
......@@ -234,6 +234,26 @@ void omapdss_sdi_set_timings(struct omap_dss_device *dssdev,
}
EXPORT_SYMBOL(omapdss_sdi_set_timings);
static void sdi_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
*timings = sdi.timings;
}
static int sdi_check_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
struct omap_overlay_manager *mgr = sdi.output.manager;
if (mgr && !dispc_mgr_timings_ok(mgr->id, timings))
return -EINVAL;
if (timings->pixel_clock == 0)
return -EINVAL;
return 0;
}
void omapdss_sdi_set_datapairs(struct omap_dss_device *dssdev, int datapairs)
{
sdi.datapairs = datapairs;
......@@ -333,6 +353,63 @@ static int sdi_probe_pdata(struct platform_device *sdidev)
return 0;
}
static int sdi_connect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct omap_overlay_manager *mgr;
int r;
r = sdi_init_regulator();
if (r)
return r;
mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel);
if (!mgr)
return -ENODEV;
r = dss_mgr_connect(mgr, dssdev);
if (r)
return r;
r = omapdss_output_set_device(dssdev, dst);
if (r) {
DSSERR("failed to connect output to new device: %s\n",
dst->name);
dss_mgr_disconnect(mgr, dssdev);
return r;
}
return 0;
}
static void sdi_disconnect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
WARN_ON(dst != dssdev->device);
if (dst != dssdev->device)
return;
omapdss_output_unset_device(dssdev);
if (dssdev->manager)
dss_mgr_disconnect(dssdev->manager, dssdev);
}
static const struct omapdss_sdi_ops sdi_ops = {
.connect = sdi_connect,
.disconnect = sdi_disconnect,
.enable = omapdss_sdi_display_enable,
.disable = omapdss_sdi_display_disable,
.check_timings = sdi_check_timings,
.set_timings = omapdss_sdi_set_timings,
.get_timings = sdi_get_timings,
.set_datapairs = omapdss_sdi_set_datapairs,
};
static void sdi_init_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &sdi.output;
......@@ -342,16 +419,17 @@ static void sdi_init_output(struct platform_device *pdev)
out->output_type = OMAP_DISPLAY_TYPE_SDI;
out->name = "sdi.0";
out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
out->ops.sdi = &sdi_ops;
out->owner = THIS_MODULE;
dss_register_output(out);
omapdss_register_output(out);
}
static void __exit sdi_uninit_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &sdi.output;
dss_unregister_output(out);
omapdss_unregister_output(out);
}
static int omap_sdi_probe(struct platform_device *pdev)
......
......@@ -564,6 +564,16 @@ int omapdss_venc_check_timings(struct omap_dss_device *dssdev,
return -EINVAL;
}
static void venc_get_timings(struct omap_dss_device *dssdev,
struct omap_video_timings *timings)
{
mutex_lock(&venc.venc_lock);
*timings = venc.timings;
mutex_unlock(&venc.venc_lock);
}
u32 omapdss_venc_get_wss(struct omap_dss_device *dssdev)
{
/* Invert due to VENC_L21_WC_CTL:INV=1 */
......@@ -779,6 +789,67 @@ static int venc_probe_pdata(struct platform_device *vencdev)
return 0;
}
static int venc_connect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
struct omap_overlay_manager *mgr;
int r;
r = venc_init_regulator();
if (r)
return r;
mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel);
if (!mgr)
return -ENODEV;
r = dss_mgr_connect(mgr, dssdev);
if (r)
return r;
r = omapdss_output_set_device(dssdev, dst);
if (r) {
DSSERR("failed to connect output to new device: %s\n",
dst->name);
dss_mgr_disconnect(mgr, dssdev);
return r;
}
return 0;
}
static void venc_disconnect(struct omap_dss_device *dssdev,
struct omap_dss_device *dst)
{
WARN_ON(dst != dssdev->device);
if (dst != dssdev->device)
return;
omapdss_output_unset_device(dssdev);
if (dssdev->manager)
dss_mgr_disconnect(dssdev->manager, dssdev);
}
static const struct omapdss_atv_ops venc_ops = {
.connect = venc_connect,
.disconnect = venc_disconnect,
.enable = omapdss_venc_display_enable,
.disable = omapdss_venc_display_disable,
.check_timings = omapdss_venc_check_timings,
.set_timings = omapdss_venc_set_timings,
.get_timings = venc_get_timings,
.set_type = omapdss_venc_set_type,
.invert_vid_out_polarity = omapdss_venc_invert_vid_out_polarity,
.set_wss = omapdss_venc_set_wss,
.get_wss = omapdss_venc_get_wss,
};
static void venc_init_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &venc.output;
......@@ -788,16 +859,17 @@ static void venc_init_output(struct platform_device *pdev)
out->output_type = OMAP_DISPLAY_TYPE_VENC;
out->name = "venc.0";
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
out->ops.atv = &venc_ops;
out->owner = THIS_MODULE;
dss_register_output(out);
omapdss_register_output(out);
}
static void __exit venc_uninit_output(struct platform_device *pdev)
{
struct omap_dss_device *out = &venc.output;
dss_unregister_output(out);
omapdss_unregister_output(out);
}
/* VENC HW IP initialisation */
......
......@@ -27,6 +27,9 @@
#ifndef __OMAP_PANEL_DATA_H
#define __OMAP_PANEL_DATA_H
#include <video/omapdss.h>
#include <video/display_timing.h>
struct omap_dss_device;
/**
......@@ -147,4 +150,210 @@ struct panel_tpo_td043_data {
int nreset_gpio;
};
/**
* encoder_tfp410 platform data
* @name: name for this display entity
* @power_down_gpio: gpio number for PD pin (or -1 if not available)
* @data_lines: number of DPI datalines
*/
struct encoder_tfp410_platform_data {
const char *name;
const char *source;
int power_down_gpio;
int data_lines;
};
/**
* encoder_tpd12s015 platform data
* @name: name for this display entity
* @ct_cp_hpd_gpio: CT_CP_HPD gpio number
* @ls_oe_gpio: LS_OE gpio number
* @hpd_gpio: HPD gpio number
*/
struct encoder_tpd12s015_platform_data {
const char *name;
const char *source;
int ct_cp_hpd_gpio;
int ls_oe_gpio;
int hpd_gpio;
};
/**
* connector_dvi platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @i2c_bus_num: i2c bus number to be used for reading EDID
*/
struct connector_dvi_platform_data {
const char *name;
const char *source;
int i2c_bus_num;
};
/**
* connector_hdmi platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
*/
struct connector_hdmi_platform_data {
const char *name;
const char *source;
};
/**
* connector_atv platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @connector_type: composite/svideo
* @invert_polarity: invert signal polarity
*/
struct connector_atv_platform_data {
const char *name;
const char *source;
enum omap_dss_venc_type connector_type;
bool invert_polarity;
};
/**
* panel_dpi platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @data_lines: number of DPI datalines
* @display_timing: timings for this panel
* @backlight_gpio: gpio to enable/disable the backlight (or -1)
* @enable_gpio: gpio to enable/disable the panel (or -1)
*/
struct panel_dpi_platform_data {
const char *name;
const char *source;
int data_lines;
const struct display_timing *display_timing;
int backlight_gpio;
int enable_gpio;
};
/**
* panel_dsicm platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @reset_gpio: gpio to reset the panel (or -1)
* @use_ext_te: use external TE GPIO
* @ext_te_gpio: external TE GPIO
* @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 panel_dsicm_platform_data {
const char *name;
const char *source;
int reset_gpio;
bool use_ext_te;
int ext_te_gpio;
unsigned ulps_timeout;
bool use_dsi_backlight;
struct omap_dsi_pin_config pin_config;
};
/**
* panel_acx565akm platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @reset_gpio: gpio to reset the panel (or -1)
* @datapairs: number of SDI datapairs
*/
struct panel_acx565akm_platform_data {
const char *name;
const char *source;
int reset_gpio;
int datapairs;
};
/**
* panel_lb035q02 platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @data_lines: number of DPI datalines
* @backlight_gpio: gpio to enable/disable the backlight (or -1)
* @enable_gpio: gpio to enable/disable the panel (or -1)
*/
struct panel_lb035q02_platform_data {
const char *name;
const char *source;
int data_lines;
int backlight_gpio;
int enable_gpio;
};
/**
* panel_sharp_ls037v7dw01 platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @data_lines: number of DPI datalines
* @resb_gpio: reset signal GPIO
* @ini_gpio: power on control GPIO
* @mo_gpio: selection for resolution(VGA/QVGA) GPIO
* @lr_gpio: selection for horizontal scanning direction GPIO
* @ud_gpio: selection for vertical scanning direction GPIO
*/
struct panel_sharp_ls037v7dw01_platform_data {
const char *name;
const char *source;
int data_lines;
int resb_gpio;
int ini_gpio;
int mo_gpio;
int lr_gpio;
int ud_gpio;
};
/**
* panel-tpo-td043mtea1 platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @data_lines: number of DPI datalines
* @nreset_gpio: reset signal
*/
struct panel_tpo_td043mtea1_platform_data {
const char *name;
const char *source;
int data_lines;
int nreset_gpio;
};
/**
* panel-nec-nl8048hl11 platform data
* @name: name for this display entity
* @source: name of the display entity used as a video source
* @data_lines: number of DPI datalines
* @res_gpio: reset signal
* @qvga_gpio: selection for resolution(QVGA/WVGA)
*/
struct panel_nec_nl8048hl11_platform_data {
const char *name;
const char *source;
int data_lines;
int res_gpio;
int qvga_gpio;
};
#endif /* __OMAP_PANEL_DATA_H */
......@@ -70,6 +70,7 @@ enum omap_display_type {
OMAP_DISPLAY_TYPE_DSI = 1 << 3,
OMAP_DISPLAY_TYPE_VENC = 1 << 4,
OMAP_DISPLAY_TYPE_HDMI = 1 << 5,
OMAP_DISPLAY_TYPE_DVI = 1 << 6,
};
enum omap_plane {
......@@ -171,6 +172,11 @@ enum omap_dss_audio_state {
OMAP_DSS_AUDIO_PLAYING,
};
struct omap_dss_audio {
struct snd_aes_iec958 *iec;
struct snd_cea_861_aud_if *cea;
};
enum omap_dss_rotation_type {
OMAP_DSS_ROT_DMA = 1 << 0,
OMAP_DSS_ROT_VRFB = 1 << 1,
......@@ -572,6 +578,176 @@ struct omap_dss_writeback_info {
u8 pre_mult_alpha;
};
struct omapdss_dpi_ops {
int (*connect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
void (*disconnect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
int (*enable)(struct omap_dss_device *dssdev);
void (*disable)(struct omap_dss_device *dssdev);
int (*check_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*set_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*get_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*set_data_lines)(struct omap_dss_device *dssdev, int data_lines);
};
struct omapdss_sdi_ops {
int (*connect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
void (*disconnect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
int (*enable)(struct omap_dss_device *dssdev);
void (*disable)(struct omap_dss_device *dssdev);
int (*check_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*set_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*get_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*set_datapairs)(struct omap_dss_device *dssdev, int datapairs);
};
struct omapdss_dvi_ops {
int (*connect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
void (*disconnect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
int (*enable)(struct omap_dss_device *dssdev);
void (*disable)(struct omap_dss_device *dssdev);
int (*check_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*set_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*get_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
};
struct omapdss_atv_ops {
int (*connect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
void (*disconnect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
int (*enable)(struct omap_dss_device *dssdev);
void (*disable)(struct omap_dss_device *dssdev);
int (*check_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*set_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*get_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*set_type)(struct omap_dss_device *dssdev,
enum omap_dss_venc_type type);
void (*invert_vid_out_polarity)(struct omap_dss_device *dssdev,
bool invert_polarity);
int (*set_wss)(struct omap_dss_device *dssdev, u32 wss);
u32 (*get_wss)(struct omap_dss_device *dssdev);
};
struct omapdss_hdmi_ops {
int (*connect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
void (*disconnect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
int (*enable)(struct omap_dss_device *dssdev);
void (*disable)(struct omap_dss_device *dssdev);
int (*check_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*set_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
void (*get_timings)(struct omap_dss_device *dssdev,
struct omap_video_timings *timings);
int (*read_edid)(struct omap_dss_device *dssdev, u8 *buf, int len);
bool (*detect)(struct omap_dss_device *dssdev);
/*
* Note: These functions might sleep. Do not call while
* holding a spinlock/readlock.
*/
int (*audio_enable)(struct omap_dss_device *dssdev);
void (*audio_disable)(struct omap_dss_device *dssdev);
bool (*audio_supported)(struct omap_dss_device *dssdev);
int (*audio_config)(struct omap_dss_device *dssdev,
struct omap_dss_audio *audio);
/* Note: These functions may not sleep */
int (*audio_start)(struct omap_dss_device *dssdev);
void (*audio_stop)(struct omap_dss_device *dssdev);
};
struct omapdss_dsi_ops {
int (*connect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
void (*disconnect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst);
int (*enable)(struct omap_dss_device *dssdev);
void (*disable)(struct omap_dss_device *dssdev, bool disconnect_lanes,
bool enter_ulps);
/* bus configuration */
int (*set_config)(struct omap_dss_device *dssdev,
const struct omap_dss_dsi_config *cfg);
int (*configure_pins)(struct omap_dss_device *dssdev,
const struct omap_dsi_pin_config *pin_cfg);
void (*enable_hs)(struct omap_dss_device *dssdev, int channel,
bool enable);
int (*enable_te)(struct omap_dss_device *dssdev, bool enable);
int (*update)(struct omap_dss_device *dssdev, int channel,
void (*callback)(int, void *), void *data);
void (*bus_lock)(struct omap_dss_device *dssdev);
void (*bus_unlock)(struct omap_dss_device *dssdev);
int (*enable_video_output)(struct omap_dss_device *dssdev, int channel);
void (*disable_video_output)(struct omap_dss_device *dssdev,
int channel);
int (*request_vc)(struct omap_dss_device *dssdev, int *channel);
int (*set_vc_id)(struct omap_dss_device *dssdev, int channel,
int vc_id);
void (*release_vc)(struct omap_dss_device *dssdev, int channel);
/* data transfer */
int (*dcs_write)(struct omap_dss_device *dssdev, int channel,
u8 *data, int len);
int (*dcs_write_nosync)(struct omap_dss_device *dssdev, int channel,
u8 *data, int len);
int (*dcs_read)(struct omap_dss_device *dssdev, int channel, u8 dcs_cmd,
u8 *data, int len);
int (*gen_write)(struct omap_dss_device *dssdev, int channel,
u8 *data, int len);
int (*gen_write_nosync)(struct omap_dss_device *dssdev, int channel,
u8 *data, int len);
int (*gen_read)(struct omap_dss_device *dssdev, int channel,
u8 *reqdata, int reqlen,
u8 *data, int len);
int (*bta_sync)(struct omap_dss_device *dssdev, int channel);
int (*set_max_rx_packet_size)(struct omap_dss_device *dssdev,
int channel, u16 plen);
};
struct omap_dss_device {
/* old device, to be removed */
struct device old_dev;
......@@ -637,6 +813,15 @@ struct omap_dss_device {
struct omap_dss_driver *driver;
union {
const struct omapdss_dpi_ops *dpi;
const struct omapdss_sdi_ops *sdi;
const struct omapdss_dvi_ops *dvi;
const struct omapdss_hdmi_ops *hdmi;
const struct omapdss_atv_ops *atv;
const struct omapdss_dsi_ops *dsi;
} ops;
/* helper variable for driver suspend/resume */
bool activate_after_resume;
......@@ -671,11 +856,6 @@ struct omap_dss_hdmi_data
int hpd_gpio;
};
struct omap_dss_audio {
struct snd_aes_iec958 *iec;
struct snd_cea_861_aud_if *cea;
};
struct omap_dss_driver {
struct device_driver driver;
......@@ -780,6 +960,8 @@ struct omap_overlay_manager *omap_dss_get_overlay_manager(int num);
int omap_dss_get_num_overlays(void);
struct omap_overlay *omap_dss_get_overlay(int num);
int omapdss_register_output(struct omap_dss_device *output);
void omapdss_unregister_output(struct omap_dss_device *output);
struct omap_dss_device *omap_dss_get_output(enum omap_dss_output_id id);
struct omap_dss_device *omap_dss_find_output(const char *name);
struct omap_dss_device *omap_dss_find_output_by_node(struct device_node *node);
......
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