OMAPDSS: Add Sony ACX565AKM panel driver
Add Sony ACX565AKM panel driver which uses the new DSS device model and DSS ops. Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Tested-by: Aaro Koskinen <aaro.koskinen@iki.fi>
This commit is contained in:
		
							parent
							
								
									dbc23840b4
								
							
						
					
					
						commit
						84192742d9
					
				| @ -38,4 +38,10 @@ config DISPLAY_PANEL_DSI_CM | ||||
| 	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 | ||||
| 
 | ||||
| endmenu | ||||
|  | ||||
| @ -5,3 +5,4 @@ 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 | ||||
|  | ||||
							
								
								
									
										865
									
								
								drivers/video/omap2/displays-new/panel-sony-acx565akm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										865
									
								
								drivers/video/omap2/displays-new/panel-sony-acx565akm.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,865 @@ | ||||
| /*
 | ||||
|  * 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"); | ||||
| @ -264,4 +264,20 @@ struct panel_dsicm_platform_data { | ||||
| 	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; | ||||
| }; | ||||
| 
 | ||||
| #endif /* __OMAP_PANEL_DATA_H */ | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user