drm/panel: Add driver for the Sony ACX565AKM panel
This panel is used on the Nokia N900. The code is based on the omapdrm-specific panel-sony-acx565akm driver. The hardware-related logic has been changed as little as possible to avoid regressions as hardware availability is lacking to test the changes. Follow-up patches should address the items listed in the TODO list. v2: - fix checkpatch warning (lcd == NULL) => (!lcd) (sam) Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Sam Ravnborg <sam@ravnborg.org> Signed-off-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20190813201101.30980-8-laurent.pinchart@ideasonboard.com
This commit is contained in:
		
							parent
							
								
									c9cf4c2a3b
								
							
						
					
					
						commit
						1c8fc3f0c5
					
				| @ -316,6 +316,14 @@ config DRM_PANEL_SITRONIX_ST7789V | ||||
| 	  Say Y here if you want to enable support for the Sitronix | ||||
| 	  ST7789V controller for 240x320 LCD panels | ||||
| 
 | ||||
| config DRM_PANEL_SONY_ACX565AKM | ||||
| 	tristate "Sony ACX565AKM panel" | ||||
| 	depends on GPIOLIB && OF && SPI | ||||
| 	depends on BACKLIGHT_CLASS_DEVICE | ||||
| 	help | ||||
| 	  Say Y here if you want to enable support for the Sony ACX565AKM | ||||
| 	  800x600 3.5" panel (found on the Nokia N900). | ||||
| 
 | ||||
| config DRM_PANEL_TPO_TPG110 | ||||
| 	tristate "TPO TPG 800x400 panel" | ||||
| 	depends on OF && SPI && GPIOLIB | ||||
|  | ||||
| @ -33,5 +33,6 @@ obj-$(CONFIG_DRM_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o | ||||
| obj-$(CONFIG_DRM_PANEL_SHARP_LS043T1LE01) += panel-sharp-ls043t1le01.o | ||||
| obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7701) += panel-sitronix-st7701.o | ||||
| obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o | ||||
| obj-$(CONFIG_DRM_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o | ||||
| obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o | ||||
| obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o | ||||
|  | ||||
| @ -140,7 +140,7 @@ static int ls037v7dw01_probe(struct platform_device *pdev) | ||||
| 	struct ls037v7dw01_panel *lcd; | ||||
| 
 | ||||
| 	lcd = devm_kzalloc(&pdev->dev, sizeof(*lcd), GFP_KERNEL); | ||||
| 	if (lcd == NULL) | ||||
| 	if (!lcd) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	platform_set_drvdata(pdev, lcd); | ||||
|  | ||||
							
								
								
									
										701
									
								
								drivers/gpu/drm/panel/panel-sony-acx565akm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										701
									
								
								drivers/gpu/drm/panel/panel-sony-acx565akm.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,701 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| /*
 | ||||
|  * Sony ACX565AKM LCD Panel driver | ||||
|  * | ||||
|  * Copyright (C) 2019 Texas Instruments Incorporated | ||||
|  * | ||||
|  * Based on the omapdrm-specific panel-sony-acx565akm driver | ||||
|  * | ||||
|  * Copyright (C) 2010 Nokia Corporation | ||||
|  * Author: Imre Deak <imre.deak@nokia.com> | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * TODO (to be addressed with hardware access to test the changes): | ||||
|  * | ||||
|  * - Update backlight support to use backlight_update_status() etc. | ||||
|  * - Use prepare/unprepare for the basic power on/off of the backligt | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/backlight.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/gpio/consumer.h> | ||||
| #include <linux/jiffies.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/mutex.h> | ||||
| #include <linux/sched.h> | ||||
| #include <linux/spi/spi.h> | ||||
| #include <video/mipi_display.h> | ||||
| 
 | ||||
| #include <drm/drm_connector.h> | ||||
| #include <drm/drm_modes.h> | ||||
| #include <drm/drm_panel.h> | ||||
| 
 | ||||
| #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_WRITE_CABC		0x55 | ||||
| #define MIPID_CMD_READ_CABC		0x56 | ||||
| 
 | ||||
| #define MIPID_VER_LPH8923		3 | ||||
| #define MIPID_VER_LS041Y3		4 | ||||
| #define MIPID_VER_L4F00311		8 | ||||
| #define MIPID_VER_ACX565AKM		9 | ||||
| 
 | ||||
| struct acx565akm_panel { | ||||
| 	struct drm_panel panel; | ||||
| 
 | ||||
| 	struct spi_device *spi; | ||||
| 	struct gpio_desc *reset_gpio; | ||||
| 	struct backlight_device *backlight; | ||||
| 
 | ||||
| 	struct mutex mutex; | ||||
| 
 | ||||
| 	const char *name; | ||||
| 	u8 display_id[3]; | ||||
| 	int model; | ||||
| 	int revision; | ||||
| 	bool has_bc; | ||||
| 	bool has_cabc; | ||||
| 
 | ||||
| 	bool enabled; | ||||
| 	unsigned int cabc_mode; | ||||
| 	/*
 | ||||
| 	 * Next value of jiffies when we can issue the next sleep in/out | ||||
| 	 * command. | ||||
| 	 */ | ||||
| 	unsigned long hw_guard_end; | ||||
| 	unsigned long hw_guard_wait;		/* max guard time in jiffies */ | ||||
| }; | ||||
| 
 | ||||
| #define to_acx565akm_device(p) container_of(p, struct acx565akm_panel, panel) | ||||
| 
 | ||||
| static void acx565akm_transfer(struct acx565akm_panel *lcd, int cmd, | ||||
| 			      const u8 *wbuf, int wlen, u8 *rbuf, int rlen) | ||||
| { | ||||
| 	struct spi_message	m; | ||||
| 	struct spi_transfer	*x, xfer[5]; | ||||
| 	int			ret; | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = spi_sync(lcd->spi, &m); | ||||
| 	if (ret < 0) | ||||
| 		dev_dbg(&lcd->spi->dev, "spi_sync %d\n", ret); | ||||
| } | ||||
| 
 | ||||
| static inline void acx565akm_cmd(struct acx565akm_panel *lcd, int cmd) | ||||
| { | ||||
| 	acx565akm_transfer(lcd, cmd, NULL, 0, NULL, 0); | ||||
| } | ||||
| 
 | ||||
| static inline void acx565akm_write(struct acx565akm_panel *lcd, | ||||
| 			       int reg, const u8 *buf, int len) | ||||
| { | ||||
| 	acx565akm_transfer(lcd, reg, buf, len, NULL, 0); | ||||
| } | ||||
| 
 | ||||
| static inline void acx565akm_read(struct acx565akm_panel *lcd, | ||||
| 			      int reg, u8 *buf, int len) | ||||
| { | ||||
| 	acx565akm_transfer(lcd, reg, NULL, 0, buf, len); | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * Auto Brightness Control Via sysfs | ||||
|  */ | ||||
| 
 | ||||
| static unsigned int acx565akm_get_cabc_mode(struct acx565akm_panel *lcd) | ||||
| { | ||||
| 	return lcd->cabc_mode; | ||||
| } | ||||
| 
 | ||||
| static void acx565akm_set_cabc_mode(struct acx565akm_panel *lcd, | ||||
| 				    unsigned int mode) | ||||
| { | ||||
| 	u16 cabc_ctrl; | ||||
| 
 | ||||
| 	lcd->cabc_mode = mode; | ||||
| 	if (!lcd->enabled) | ||||
| 		return; | ||||
| 	cabc_ctrl = 0; | ||||
| 	acx565akm_read(lcd, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1); | ||||
| 	cabc_ctrl &= ~3; | ||||
| 	cabc_ctrl |= (1 << 8) | (mode & 3); | ||||
| 	acx565akm_write(lcd, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2); | ||||
| } | ||||
| 
 | ||||
| static unsigned int acx565akm_get_hw_cabc_mode(struct acx565akm_panel *lcd) | ||||
| { | ||||
| 	u8 cabc_ctrl; | ||||
| 
 | ||||
| 	acx565akm_read(lcd, MIPID_CMD_READ_CABC, &cabc_ctrl, 1); | ||||
| 	return cabc_ctrl & 3; | ||||
| } | ||||
| 
 | ||||
| static const char * const acx565akm_cabc_modes[] = { | ||||
| 	"off",		/* always used when CABC is not supported */ | ||||
| 	"ui", | ||||
| 	"still-image", | ||||
| 	"moving-image", | ||||
| }; | ||||
| 
 | ||||
| static ssize_t cabc_mode_show(struct device *dev, | ||||
| 			      struct device_attribute *attr, | ||||
| 			      char *buf) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = dev_get_drvdata(dev); | ||||
| 	const char *mode_str; | ||||
| 	int mode; | ||||
| 
 | ||||
| 	if (!lcd->has_cabc) | ||||
| 		mode = 0; | ||||
| 	else | ||||
| 		mode = acx565akm_get_cabc_mode(lcd); | ||||
| 
 | ||||
| 	mode_str = "unknown"; | ||||
| 	if (mode >= 0 && mode < ARRAY_SIZE(acx565akm_cabc_modes)) | ||||
| 		mode_str = acx565akm_cabc_modes[mode]; | ||||
| 
 | ||||
| 	return sprintf(buf, "%s\n", mode_str); | ||||
| } | ||||
| 
 | ||||
| static ssize_t cabc_mode_store(struct device *dev, | ||||
| 			       struct device_attribute *attr, | ||||
| 			       const char *buf, size_t count) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = dev_get_drvdata(dev); | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	for (i = 0; i < ARRAY_SIZE(acx565akm_cabc_modes); i++) { | ||||
| 		const char *mode_str = acx565akm_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(acx565akm_cabc_modes)) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	if (!lcd->has_cabc && i != 0) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	mutex_lock(&lcd->mutex); | ||||
| 	acx565akm_set_cabc_mode(lcd, i); | ||||
| 	mutex_unlock(&lcd->mutex); | ||||
| 
 | ||||
| 	return count; | ||||
| } | ||||
| 
 | ||||
| static ssize_t cabc_available_modes_show(struct device *dev, | ||||
| 					 struct device_attribute *attr, | ||||
| 					 char *buf) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = dev_get_drvdata(dev); | ||||
| 	unsigned int i; | ||||
| 	size_t len = 0; | ||||
| 
 | ||||
| 	if (!lcd->has_cabc) | ||||
| 		return sprintf(buf, "%s\n", acx565akm_cabc_modes[0]); | ||||
| 
 | ||||
| 	for (i = 0; i < ARRAY_SIZE(acx565akm_cabc_modes); i++) | ||||
| 		len += sprintf(&buf[len], "%s%s", i ? " " : "", | ||||
| 			       acx565akm_cabc_modes[i]); | ||||
| 
 | ||||
| 	buf[len++] = '\n'; | ||||
| 
 | ||||
| 	return len; | ||||
| } | ||||
| 
 | ||||
| static DEVICE_ATTR_RW(cabc_mode); | ||||
| static DEVICE_ATTR_RO(cabc_available_modes); | ||||
| 
 | ||||
| static struct attribute *acx565akm_cabc_attrs[] = { | ||||
| 	&dev_attr_cabc_mode.attr, | ||||
| 	&dev_attr_cabc_available_modes.attr, | ||||
| 	NULL, | ||||
| }; | ||||
| 
 | ||||
| static const struct attribute_group acx565akm_cabc_attr_group = { | ||||
| 	.attrs = acx565akm_cabc_attrs, | ||||
| }; | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * Backlight Device | ||||
|  */ | ||||
| 
 | ||||
| static int acx565akm_get_actual_brightness(struct acx565akm_panel *lcd) | ||||
| { | ||||
| 	u8 bv; | ||||
| 
 | ||||
| 	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_BRIGHTNESS, &bv, 1); | ||||
| 
 | ||||
| 	return bv; | ||||
| } | ||||
| 
 | ||||
| static void acx565akm_set_brightness(struct acx565akm_panel *lcd, int level) | ||||
| { | ||||
| 	u16 ctrl; | ||||
| 	int bv; | ||||
| 
 | ||||
| 	bv = level | (1 << 8); | ||||
| 	acx565akm_write(lcd, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, (u8 *)&bv, 2); | ||||
| 
 | ||||
| 	acx565akm_read(lcd, MIPI_DCS_GET_CONTROL_DISPLAY, (u8 *)&ctrl, 1); | ||||
| 	if (level) | ||||
| 		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(lcd, MIPI_DCS_WRITE_CONTROL_DISPLAY, (u8 *)&ctrl, 2); | ||||
| } | ||||
| 
 | ||||
| static int acx565akm_bl_update_status_locked(struct backlight_device *dev) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev); | ||||
| 	int level; | ||||
| 
 | ||||
| 	if (dev->props.fb_blank == FB_BLANK_UNBLANK && | ||||
| 	    dev->props.power == FB_BLANK_UNBLANK) | ||||
| 		level = dev->props.brightness; | ||||
| 	else | ||||
| 		level = 0; | ||||
| 
 | ||||
| 	acx565akm_set_brightness(lcd, level); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int acx565akm_bl_update_status(struct backlight_device *dev) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	mutex_lock(&lcd->mutex); | ||||
| 	ret = acx565akm_bl_update_status_locked(dev); | ||||
| 	mutex_unlock(&lcd->mutex); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int acx565akm_bl_get_intensity(struct backlight_device *dev) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev); | ||||
| 	unsigned int intensity; | ||||
| 
 | ||||
| 	mutex_lock(&lcd->mutex); | ||||
| 
 | ||||
| 	if (dev->props.fb_blank == FB_BLANK_UNBLANK && | ||||
| 	    dev->props.power == FB_BLANK_UNBLANK) | ||||
| 		intensity = acx565akm_get_actual_brightness(lcd); | ||||
| 	else | ||||
| 		intensity = 0; | ||||
| 
 | ||||
| 	mutex_unlock(&lcd->mutex); | ||||
| 
 | ||||
| 	return intensity; | ||||
| } | ||||
| 
 | ||||
| static const struct backlight_ops acx565akm_bl_ops = { | ||||
| 	.get_brightness = acx565akm_bl_get_intensity, | ||||
| 	.update_status  = acx565akm_bl_update_status, | ||||
| }; | ||||
| 
 | ||||
| static int acx565akm_backlight_init(struct acx565akm_panel *lcd) | ||||
| { | ||||
| 	struct backlight_properties props = { | ||||
| 		.fb_blank = FB_BLANK_UNBLANK, | ||||
| 		.power = FB_BLANK_UNBLANK, | ||||
| 		.type = BACKLIGHT_RAW, | ||||
| 	}; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	lcd->backlight = backlight_device_register(lcd->name, &lcd->spi->dev, | ||||
| 						   lcd, &acx565akm_bl_ops, | ||||
| 						   &props); | ||||
| 	if (IS_ERR(lcd->backlight)) { | ||||
| 		ret = PTR_ERR(lcd->backlight); | ||||
| 		lcd->backlight = NULL; | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	if (lcd->has_cabc) { | ||||
| 		ret = sysfs_create_group(&lcd->backlight->dev.kobj, | ||||
| 					 &acx565akm_cabc_attr_group); | ||||
| 		if (ret < 0) { | ||||
| 			dev_err(&lcd->spi->dev, | ||||
| 				"%s failed to create sysfs files\n", __func__); | ||||
| 			backlight_device_unregister(lcd->backlight); | ||||
| 			return ret; | ||||
| 		} | ||||
| 
 | ||||
| 		lcd->cabc_mode = acx565akm_get_hw_cabc_mode(lcd); | ||||
| 	} | ||||
| 
 | ||||
| 	lcd->backlight->props.max_brightness = 255; | ||||
| 	lcd->backlight->props.brightness = acx565akm_get_actual_brightness(lcd); | ||||
| 
 | ||||
| 	acx565akm_bl_update_status_locked(lcd->backlight); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void acx565akm_backlight_cleanup(struct acx565akm_panel *lcd) | ||||
| { | ||||
| 	if (lcd->has_cabc) | ||||
| 		sysfs_remove_group(&lcd->backlight->dev.kobj, | ||||
| 				   &acx565akm_cabc_attr_group); | ||||
| 
 | ||||
| 	backlight_device_unregister(lcd->backlight); | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DRM Bridge Operations | ||||
|  */ | ||||
| 
 | ||||
| static void acx565akm_set_sleep_mode(struct acx565akm_panel *lcd, int on) | ||||
| { | ||||
| 	int cmd = on ? MIPI_DCS_ENTER_SLEEP_MODE : MIPI_DCS_EXIT_SLEEP_MODE; | ||||
| 	unsigned long wait; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * We have to keep 120msec between sleep in/out commands. | ||||
| 	 * (8.2.15, 8.2.16). | ||||
| 	 */ | ||||
| 	wait = lcd->hw_guard_end - jiffies; | ||||
| 	if ((long)wait > 0 && wait <= lcd->hw_guard_wait) { | ||||
| 		set_current_state(TASK_UNINTERRUPTIBLE); | ||||
| 		schedule_timeout(wait); | ||||
| 	} | ||||
| 
 | ||||
| 	acx565akm_cmd(lcd, cmd); | ||||
| 
 | ||||
| 	lcd->hw_guard_wait = msecs_to_jiffies(120); | ||||
| 	lcd->hw_guard_end = jiffies + lcd->hw_guard_wait; | ||||
| } | ||||
| 
 | ||||
| static void acx565akm_set_display_state(struct acx565akm_panel *lcd, | ||||
| 					int enabled) | ||||
| { | ||||
| 	int cmd = enabled ? MIPI_DCS_SET_DISPLAY_ON : MIPI_DCS_SET_DISPLAY_OFF; | ||||
| 
 | ||||
| 	acx565akm_cmd(lcd, cmd); | ||||
| } | ||||
| 
 | ||||
| static int acx565akm_power_on(struct acx565akm_panel *lcd) | ||||
| { | ||||
| 	/*FIXME tweak me */ | ||||
| 	msleep(50); | ||||
| 
 | ||||
| 	gpiod_set_value(lcd->reset_gpio, 1); | ||||
| 
 | ||||
| 	if (lcd->enabled) { | ||||
| 		dev_dbg(&lcd->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); | ||||
| 
 | ||||
| 	acx565akm_set_sleep_mode(lcd, 0); | ||||
| 	lcd->enabled = true; | ||||
| 
 | ||||
| 	/* 5msec between sleep out and the next command. (8.2.16) */ | ||||
| 	usleep_range(5000, 10000); | ||||
| 	acx565akm_set_display_state(lcd, 1); | ||||
| 	acx565akm_set_cabc_mode(lcd, lcd->cabc_mode); | ||||
| 
 | ||||
| 	return acx565akm_bl_update_status_locked(lcd->backlight); | ||||
| } | ||||
| 
 | ||||
| static void acx565akm_power_off(struct acx565akm_panel *lcd) | ||||
| { | ||||
| 	if (!lcd->enabled) | ||||
| 		return; | ||||
| 
 | ||||
| 	acx565akm_set_display_state(lcd, 0); | ||||
| 	acx565akm_set_sleep_mode(lcd, 1); | ||||
| 	lcd->enabled = false; | ||||
| 	/*
 | ||||
| 	 * 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); | ||||
| 
 | ||||
| 	gpiod_set_value(lcd->reset_gpio, 0); | ||||
| 
 | ||||
| 	/* FIXME need to tweak this delay */ | ||||
| 	msleep(100); | ||||
| } | ||||
| 
 | ||||
| static int acx565akm_disable(struct drm_panel *panel) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = to_acx565akm_device(panel); | ||||
| 
 | ||||
| 	mutex_lock(&lcd->mutex); | ||||
| 	acx565akm_power_off(lcd); | ||||
| 	mutex_unlock(&lcd->mutex); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int acx565akm_enable(struct drm_panel *panel) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = to_acx565akm_device(panel); | ||||
| 
 | ||||
| 	mutex_lock(&lcd->mutex); | ||||
| 	acx565akm_power_on(lcd); | ||||
| 	mutex_unlock(&lcd->mutex); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct drm_display_mode acx565akm_mode = { | ||||
| 	.clock = 24000, | ||||
| 	.hdisplay = 800, | ||||
| 	.hsync_start = 800 + 28, | ||||
| 	.hsync_end = 800 + 28 + 4, | ||||
| 	.htotal = 800 + 28 + 4 + 24, | ||||
| 	.vdisplay = 480, | ||||
| 	.vsync_start = 480 + 3, | ||||
| 	.vsync_end = 480 + 3 + 3, | ||||
| 	.vtotal = 480 + 3 + 3 + 4, | ||||
| 	.vrefresh = 57, | ||||
| 	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, | ||||
| 	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, | ||||
| 	.width_mm = 77, | ||||
| 	.height_mm = 46, | ||||
| }; | ||||
| 
 | ||||
| static int acx565akm_get_modes(struct drm_panel *panel) | ||||
| { | ||||
| 	struct drm_connector *connector = panel->connector; | ||||
| 	struct drm_display_mode *mode; | ||||
| 
 | ||||
| 	mode = drm_mode_duplicate(panel->drm, &acx565akm_mode); | ||||
| 	if (!mode) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	drm_mode_set_name(mode); | ||||
| 	drm_mode_probed_add(connector, mode); | ||||
| 
 | ||||
| 	connector->display_info.width_mm = acx565akm_mode.width_mm; | ||||
| 	connector->display_info.height_mm = acx565akm_mode.height_mm; | ||||
| 	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH | ||||
| 					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE | ||||
| 					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| static const struct drm_panel_funcs acx565akm_funcs = { | ||||
| 	.disable = acx565akm_disable, | ||||
| 	.enable = acx565akm_enable, | ||||
| 	.get_modes = acx565akm_get_modes, | ||||
| }; | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * Probe, Detect and Remove | ||||
|  */ | ||||
| 
 | ||||
| static int acx565akm_detect(struct acx565akm_panel *lcd) | ||||
| { | ||||
| 	__be32 value; | ||||
| 	u32 status; | ||||
| 	int ret = 0; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * After being taken out of reset the panel needs 5ms before the first | ||||
| 	 * command can be sent. | ||||
| 	 */ | ||||
| 	gpiod_set_value(lcd->reset_gpio, 1); | ||||
| 	usleep_range(5000, 10000); | ||||
| 
 | ||||
| 	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_STATUS, (u8 *)&value, 4); | ||||
| 	status = __be32_to_cpu(value); | ||||
| 	lcd->enabled = (status & (1 << 17)) && (status & (1 << 10)); | ||||
| 
 | ||||
| 	dev_dbg(&lcd->spi->dev, | ||||
| 		"LCD panel %s by bootloader (status 0x%04x)\n", | ||||
| 		lcd->enabled ? "enabled" : "disabled ", status); | ||||
| 
 | ||||
| 	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_ID, lcd->display_id, 3); | ||||
| 	dev_dbg(&lcd->spi->dev, "MIPI display ID: %02x%02x%02x\n", | ||||
| 		lcd->display_id[0], lcd->display_id[1], lcd->display_id[2]); | ||||
| 
 | ||||
| 	switch (lcd->display_id[0]) { | ||||
| 	case 0x10: | ||||
| 		lcd->model = MIPID_VER_ACX565AKM; | ||||
| 		lcd->name = "acx565akm"; | ||||
| 		lcd->has_bc = 1; | ||||
| 		lcd->has_cabc = 1; | ||||
| 		break; | ||||
| 	case 0x29: | ||||
| 		lcd->model = MIPID_VER_L4F00311; | ||||
| 		lcd->name = "l4f00311"; | ||||
| 		break; | ||||
| 	case 0x45: | ||||
| 		lcd->model = MIPID_VER_LPH8923; | ||||
| 		lcd->name = "lph8923"; | ||||
| 		break; | ||||
| 	case 0x83: | ||||
| 		lcd->model = MIPID_VER_LS041Y3; | ||||
| 		lcd->name = "ls041y3"; | ||||
| 		break; | ||||
| 	default: | ||||
| 		lcd->name = "unknown"; | ||||
| 		dev_err(&lcd->spi->dev, "unknown display ID\n"); | ||||
| 		ret = -ENODEV; | ||||
| 		goto done; | ||||
| 	} | ||||
| 
 | ||||
| 	lcd->revision = lcd->display_id[1]; | ||||
| 
 | ||||
| 	dev_info(&lcd->spi->dev, "%s rev %02x panel detected\n", | ||||
| 		 lcd->name, lcd->revision); | ||||
| 
 | ||||
| done: | ||||
| 	if (!lcd->enabled) | ||||
| 		gpiod_set_value(lcd->reset_gpio, 0); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int acx565akm_probe(struct spi_device *spi) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL); | ||||
| 	if (lcd == NULL) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	spi_set_drvdata(spi, lcd); | ||||
| 	spi->mode = SPI_MODE_3; | ||||
| 
 | ||||
| 	lcd->spi = spi; | ||||
| 	mutex_init(&lcd->mutex); | ||||
| 
 | ||||
| 	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW); | ||||
| 	if (IS_ERR(lcd->reset_gpio)) { | ||||
| 		dev_err(&spi->dev, "failed to get reset GPIO\n"); | ||||
| 		return PTR_ERR(lcd->reset_gpio); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = acx565akm_detect(lcd); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(&spi->dev, "panel detection failed\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	if (lcd->has_bc) { | ||||
| 		ret = acx565akm_backlight_init(lcd); | ||||
| 		if (ret < 0) | ||||
| 			return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	drm_panel_init(&lcd->panel); | ||||
| 	lcd->panel.dev = &lcd->spi->dev; | ||||
| 	lcd->panel.funcs = &acx565akm_funcs; | ||||
| 
 | ||||
| 	ret = drm_panel_add(&lcd->panel); | ||||
| 	if (ret < 0) { | ||||
| 		if (lcd->has_bc) | ||||
| 			acx565akm_backlight_cleanup(lcd); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int acx565akm_remove(struct spi_device *spi) | ||||
| { | ||||
| 	struct acx565akm_panel *lcd = spi_get_drvdata(spi); | ||||
| 
 | ||||
| 	drm_panel_remove(&lcd->panel); | ||||
| 
 | ||||
| 	if (lcd->has_bc) | ||||
| 		acx565akm_backlight_cleanup(lcd); | ||||
| 
 | ||||
| 	drm_panel_disable(&lcd->panel); | ||||
| 	drm_panel_unprepare(&lcd->panel); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct of_device_id acx565akm_of_match[] = { | ||||
| 	{ .compatible = "sony,acx565akm", }, | ||||
| 	{ /* sentinel */ }, | ||||
| }; | ||||
| 
 | ||||
| MODULE_DEVICE_TABLE(of, acx565akm_of_match); | ||||
| 
 | ||||
| static struct spi_driver acx565akm_driver = { | ||||
| 	.probe		= acx565akm_probe, | ||||
| 	.remove		= acx565akm_remove, | ||||
| 	.driver		= { | ||||
| 		.name	= "panel-sony-acx565akm", | ||||
| 		.of_match_table = acx565akm_of_match, | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| module_spi_driver(acx565akm_driver); | ||||
| 
 | ||||
| MODULE_ALIAS("spi:sony,acx565akm"); | ||||
| MODULE_AUTHOR("Nokia Corporation"); | ||||
| MODULE_DESCRIPTION("Sony ACX565AKM LCD Panel Driver"); | ||||
| MODULE_LICENSE("GPL"); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user