* [RFC PATCH 0/4] CDFv3: MIPI DSI bus implementation
@ 2013-09-24 14:23 ` Andrzej Hajda
0 siblings, 0 replies; 14+ messages in thread
From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park
Hi Laurent,
This is my MIPI DSI bus implementation. The patchset
contains also two drivers:
- DSI bus master driver for Exynos,
- DSI slave driver for s6e8aa0 panel family.
All code has been tested on real device - Exynos 4210 based 'Trats'.
This is not final version, there are still some things to do.
DSI bus code is based on mipi-dbi-bus, with few major changes.
1. I have replaced three DBI opses:
- write_command,
- write_data,
- read_data
with one op 'transfer', this way it better fits to
MIPI DSI standard, I wonder if this cannot be adapted to DBI also.
The only things which bothers me is number of arguments,
maybe struct should be used instead.
I have added DCS helpers functions which use 'transfer' op:
- mipi_dsi_dcs_read
- mipi_dsi_dcs_write
and two macros which allow to pass variable number of bytes
as arguments, example usage in panel driver code:
- mipi_dsi_dcs_write_seq
- mipi_dsi_dcs_write_static_seq
I have added also following opses:
- set_stream - to enable/disable streaming on DSI master,
- set_power - I have temporarily dropped idea of using runtime_pm
due to compliacted power on sequence of panel/dsi,
which would probably require different op anyway.
struct mipi_dsi_device contains videomode and mipi_dsi_interface_params fields.
Those fields are read by opses, I wonder if it would not be better to
pass them directly to opses as arguments.
TODO:
- helpers for non-DT drivers,
- minor power management issues,
- better error handling
- ...
Regards
Andrzej
Andrzej Hajda (4):
mipi-dsi-bus: add MIPI DSI bus support
mipi-dsi-exynos: add driver
panel-s6e8aa0: add driver
ARM: dts: exynos4210-trats: add panel and dsi nodes
arch/arm/boot/dts/exynos4210-trats.dts | 54 ++
drivers/video/display/Kconfig | 14 +
drivers/video/display/Makefile | 3 +
drivers/video/display/mipi-dsi-bus.c | 332 ++++++++
drivers/video/display/mipi-dsi-exynos.c | 1310 +++++++++++++++++++++++++++++++
drivers/video/display/panel-s6e8aa0.c | 1286 ++++++++++++++++++++++++++++++
include/video/display.h | 3 +
include/video/mipi-dsi-bus.h | 144 ++++
include/video/mipi-dsi-exynos.h | 41 +
include/video/panel-s6e8aa0.h | 42 +
10 files changed, 3229 insertions(+)
create mode 100644 drivers/video/display/mipi-dsi-bus.c
create mode 100644 drivers/video/display/mipi-dsi-exynos.c
create mode 100644 drivers/video/display/panel-s6e8aa0.c
create mode 100644 include/video/mipi-dsi-bus.h
create mode 100644 include/video/mipi-dsi-exynos.h
create mode 100644 include/video/panel-s6e8aa0.h
--
1.8.1.2
^ permalink raw reply [flat|nested] 14+ messages in thread* [RFC PATCH 0/4] CDFv3: MIPI DSI bus implementation @ 2013-09-24 14:23 ` Andrzej Hajda 0 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park Hi Laurent, This is my MIPI DSI bus implementation. The patchset contains also two drivers: - DSI bus master driver for Exynos, - DSI slave driver for s6e8aa0 panel family. All code has been tested on real device - Exynos 4210 based 'Trats'. This is not final version, there are still some things to do. DSI bus code is based on mipi-dbi-bus, with few major changes. 1. I have replaced three DBI opses: - write_command, - write_data, - read_data with one op 'transfer', this way it better fits to MIPI DSI standard, I wonder if this cannot be adapted to DBI also. The only things which bothers me is number of arguments, maybe struct should be used instead. I have added DCS helpers functions which use 'transfer' op: - mipi_dsi_dcs_read - mipi_dsi_dcs_write and two macros which allow to pass variable number of bytes as arguments, example usage in panel driver code: - mipi_dsi_dcs_write_seq - mipi_dsi_dcs_write_static_seq I have added also following opses: - set_stream - to enable/disable streaming on DSI master, - set_power - I have temporarily dropped idea of using runtime_pm due to compliacted power on sequence of panel/dsi, which would probably require different op anyway. struct mipi_dsi_device contains videomode and mipi_dsi_interface_params fields. Those fields are read by opses, I wonder if it would not be better to pass them directly to opses as arguments. TODO: - helpers for non-DT drivers, - minor power management issues, - better error handling - ... Regards Andrzej Andrzej Hajda (4): mipi-dsi-bus: add MIPI DSI bus support mipi-dsi-exynos: add driver panel-s6e8aa0: add driver ARM: dts: exynos4210-trats: add panel and dsi nodes arch/arm/boot/dts/exynos4210-trats.dts | 54 ++ drivers/video/display/Kconfig | 14 + drivers/video/display/Makefile | 3 + drivers/video/display/mipi-dsi-bus.c | 332 ++++++++ drivers/video/display/mipi-dsi-exynos.c | 1310 +++++++++++++++++++++++++++++++ drivers/video/display/panel-s6e8aa0.c | 1286 ++++++++++++++++++++++++++++++ include/video/display.h | 3 + include/video/mipi-dsi-bus.h | 144 ++++ include/video/mipi-dsi-exynos.h | 41 + include/video/panel-s6e8aa0.h | 42 + 10 files changed, 3229 insertions(+) create mode 100644 drivers/video/display/mipi-dsi-bus.c create mode 100644 drivers/video/display/mipi-dsi-exynos.c create mode 100644 drivers/video/display/panel-s6e8aa0.c create mode 100644 include/video/mipi-dsi-bus.h create mode 100644 include/video/mipi-dsi-exynos.h create mode 100644 include/video/panel-s6e8aa0.h -- 1.8.1.2 ^ permalink raw reply [flat|nested] 14+ messages in thread
* [RFC PATCH 1/4] mipi-dsi-bus: add MIPI DSI bus support 2013-09-24 14:23 ` Andrzej Hajda @ 2013-09-24 14:23 ` Andrzej Hajda -1 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park MIPI DSI is a high-speed serial interface to transmit data from/to host to display module. Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> --- drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/mipi-dsi-bus.c | 332 +++++++++++++++++++++++++++++++++++ include/video/display.h | 3 + include/video/mipi-dsi-bus.h | 144 +++++++++++++++ 5 files changed, 484 insertions(+) create mode 100644 drivers/video/display/mipi-dsi-bus.c create mode 100644 include/video/mipi-dsi-bus.h diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 9b482a8..619b05d 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -20,6 +20,10 @@ config DISPLAY_MIPI_DBI tristate default n +config DISPLAY_MIPI_DSI + tristate + default n + config DISPLAY_PANEL_DPI tristate "DPI (Parallel) Display Panels" ---help--- diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index d03c64a..b323fd4 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -3,6 +3,7 @@ display-y := display-core.o \ obj-$(CONFIG_DISPLAY_CORE) += display.o obj-$(CONFIG_DISPLAY_CONNECTOR_VGA) += con-vga.o obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o +obj-$(CONFIG_DISPLAY_MIPI_DSI) += mipi-dsi-bus.o obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o diff --git a/drivers/video/display/mipi-dsi-bus.c b/drivers/video/display/mipi-dsi-bus.c new file mode 100644 index 0000000..a194d92 --- /dev/null +++ b/drivers/video/display/mipi-dsi-bus.c @@ -0,0 +1,332 @@ +/* + * MIPI DSI Bus + * + * Copyright (C) 2012, Samsung Electronics, Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.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/device.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <video/mipi_display.h> +#include <video/mipi-dsi-bus.h> + +/* ----------------------------------------------------------------------------- + * Bus operations + */ + +int mipi_dsi_set_power(struct mipi_dsi_device *dev, bool on) +{ + const struct mipi_dsi_bus_ops *ops = dev->bus->ops; + + if (!ops->set_power) + return 0; + + return ops->set_power(dev->bus, dev, on); +} +EXPORT_SYMBOL_GPL(mipi_dsi_set_power); + +int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on) +{ + const struct mipi_dsi_bus_ops *ops = dev->bus->ops; + + if (!ops->set_stream) + return 0; + + return ops->set_stream(dev->bus, dev, on); +} +EXPORT_SYMBOL_GPL(mipi_dsi_set_stream); + +int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 *data, + size_t len) +{ + const struct mipi_dsi_bus_ops *ops = dev->bus->ops; + u8 type = channel << 6; + + if (!ops->transfer) + return -EINVAL; + + switch (len) { + case 0: + return -EINVAL; + case 1: + type |= MIPI_DSI_DCS_SHORT_WRITE; + break; + case 2: + type |= MIPI_DSI_DCS_SHORT_WRITE_PARAM; + break; + default: + type |= MIPI_DSI_DCS_LONG_WRITE; + } + + return ops->transfer(dev->bus, dev, type, data, len, NULL, 0); +} +EXPORT_SYMBOL_GPL(mipi_dsi_dcs_write); + +int mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd, + u8 *data, size_t len) +{ + const struct mipi_dsi_bus_ops *ops = dev->bus->ops; + u8 type = MIPI_DSI_DCS_READ | (channel << 6); + + if (!ops->transfer) + return -EINVAL; + + return ops->transfer(dev->bus, dev, type, &cmd, 1, data, len); +} +EXPORT_SYMBOL_GPL(mipi_dsi_dcs_read); + +/* ----------------------------------------------------------------------------- + * Bus type + */ + +static const struct mipi_dsi_device_id * +mipi_dsi_match_id(const struct mipi_dsi_device_id *id, + struct mipi_dsi_device *dev) +{ + while (id->name[0]) { + if (strcmp(dev->name, id->name) = 0) { + dev->id_entry = id; + return id; + } + id++; + } + return NULL; +} + +static int mipi_dsi_match(struct device *_dev, struct device_driver *_drv) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_drv); + + if (of_driver_match_device(_dev, _drv)) + return 1; + + if (drv->id_table) + return mipi_dsi_match_id(drv->id_table, dev) != NULL; + + return (strcmp(dev->name, _drv->name) = 0); +} + +static ssize_t modalias_show(struct device *_dev, struct device_attribute *a, + char *buf) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + int len = snprintf(buf, PAGE_SIZE, MIPI_DSI_MODULE_PREFIX "%s\n", + dev->name); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} + +static struct device_attribute mipi_dsi_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int mipi_dsi_uevent(struct device *_dev, struct kobj_uevent_env *env) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + + add_uevent_var(env, "MODALIAS=%s%s", MIPI_DSI_MODULE_PREFIX, + dev->name); + return 0; +} + +static const struct dev_pm_ops mipi_dsi_dev_pm_ops = { + .runtime_suspend = pm_generic_runtime_suspend, + .runtime_resume = pm_generic_runtime_resume, + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, +}; + +static struct bus_type mipi_dsi_bus_type = { + .name = "mipi-dsi", + .dev_attrs = mipi_dsi_dev_attrs, + .match = mipi_dsi_match, + .uevent = mipi_dsi_uevent, + .pm = &mipi_dsi_dev_pm_ops, +}; + +void mipi_dsi_dev_release(struct device *_dev) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + kfree(dev); +} + +static struct device_type mipi_dsi_dev_type = { + .release = mipi_dsi_dev_release, +}; + + +/* ----------------------------------------------------------------------------- + * Device and driver (un)registration + */ + +/** + * mipi_dsi_device_register - register a DSI device + * @dev: DSI device we're registering + */ +int mipi_dsi_device_register(struct mipi_dsi_device *dev, + struct mipi_dsi_bus *bus) +{ + device_initialize(&dev->dev); + + dev->bus = bus; + dev->dev.bus = &mipi_dsi_bus_type; + dev->dev.parent = bus->dev; + dev->dev.type = &mipi_dsi_dev_type; + + if (dev->id != -1) + dev_set_name(&dev->dev, "%s.%d", dev->name, dev->id); + else + dev_set_name(&dev->dev, "%s", dev->name); + + return device_add(&dev->dev); +} +EXPORT_SYMBOL_GPL(mipi_dsi_device_register); + +/** + * mipi_dsi_device_unregister - unregister a DSI device + * @dev: DSI device we're unregistering + */ +void mipi_dsi_device_unregister(struct mipi_dsi_device *dev) +{ + device_del(&dev->dev); + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(mipi_dsi_device_unregister); + +static int mipi_dsi_drv_probe(struct device *_dev) +{ + struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_dev->driver); + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + + return drv->probe(dev); +} + +static int mipi_dsi_drv_remove(struct device *_dev) +{ + struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_dev->driver); + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + + return drv->remove(dev); +} + +/** + * mipi_dsi_driver_register - register a driver for DSI devices + * @drv: DSI driver structure + */ +int mipi_dsi_driver_register(struct mipi_dsi_driver *drv) +{ + drv->driver.bus = &mipi_dsi_bus_type; + if (drv->probe) + drv->driver.probe = mipi_dsi_drv_probe; + if (drv->remove) + drv->driver.remove = mipi_dsi_drv_remove; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(mipi_dsi_driver_register); + +/** + * mipi_dsi_driver_unregister - unregister a driver for DSI devices + * @drv: DSI driver structure + */ +void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(mipi_dsi_driver_unregister); + +struct mipi_dsi_device *of_mipi_dsi_register_device(struct mipi_dsi_bus *bus, + struct device_node *node) +{ + struct mipi_dsi_device *d = NULL; + int ret; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return ERR_PTR(-ENOMEM); + + ret = of_modalias_node(node, d->name, sizeof(d->name)); + if (ret) { + dev_err(bus->dev, "modalias failure on %s\n", node->full_name); + goto err_free; + } + + d->dev.of_node = of_node_get(node); + if (!d->dev.of_node) + goto err_free; + + ret = mipi_dsi_device_register(d, bus); + + if (!ret) + return d; + + of_node_put(node); +err_free: + kfree(d); + + return ERR_PTR(ret); +} + +int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus) +{ + struct device_node *n; + + for_each_child_of_node(bus->dev->of_node, n) + of_mipi_dsi_register_device(bus, n); + + return 0; +} +EXPORT_SYMBOL_GPL(of_mipi_dsi_register_devices); + +static int mipi_dsi_remove_device_fn(struct device *_dev, void *priv) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + + mipi_dsi_device_unregister(dev); + + return 0; +} + +void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus) +{ + device_for_each_child(bus->dev, bus, mipi_dsi_remove_device_fn); +} +EXPORT_SYMBOL_GPL(mipi_dsi_unregister_devices); +/* ----------------------------------------------------------------------------- + * Init/exit + */ + +static int __init mipi_dsi_init(void) +{ + return bus_register(&mipi_dsi_bus_type); +} + +static void __exit mipi_dsi_exit(void) +{ + bus_unregister(&mipi_dsi_bus_type); +} + +module_init(mipi_dsi_init); +module_exit(mipi_dsi_exit) + +MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>"); +MODULE_DESCRIPTION("MIPI DSI Bus"); +MODULE_LICENSE("GPL v2"); diff --git a/include/video/display.h b/include/video/display.h index 3138401..7faca0f 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -18,6 +18,7 @@ #include <linux/module.h> #include <media/media-entity.h> #include <video/mipi-dbi-bus.h> +#include <video/mipi-dsi-bus.h> #define DISPLAY_PIXEL_CODING(option, type, from, to, variant) \ (((option) << 17) | ((type) << 13) | ((variant) << 10) | \ @@ -184,6 +185,7 @@ enum display_entity_stream_state { enum display_entity_interface_type { DISPLAY_ENTITY_INTERFACE_DPI, DISPLAY_ENTITY_INTERFACE_DBI, + DISPLAY_ENTITY_INTERFACE_DSI, DISPLAY_ENTITY_INTERFACE_LVDS, DISPLAY_ENTITY_INTERFACE_VGA, }; @@ -192,6 +194,7 @@ struct display_entity_interface_params { enum display_entity_interface_type type; union { struct mipi_dbi_interface_params dbi; + struct mipi_dsi_interface_params dsi; } p; }; diff --git a/include/video/mipi-dsi-bus.h b/include/video/mipi-dsi-bus.h new file mode 100644 index 0000000..a78792d --- /dev/null +++ b/include/video/mipi-dsi-bus.h @@ -0,0 +1,144 @@ +/* + * MIPI DSI Bus + * + * Copyright (C) 2013, Samsung Electronics, Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.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. + */ + +#ifndef __MIPI_DSI_BUS_H__ +#define __MIPI_DSI_BUS_H__ + +#include <linux/device.h> +#include <video/videomode.h> + +struct mipi_dsi_bus; +struct mipi_dsi_device; + +struct mipi_dsi_bus_ops { + int (*set_power)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev, + bool on); + int (*set_stream)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev, + bool on); + int (*transfer)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev, + u8 type, const u8 *tx_buf, size_t tx_len, u8 *rx_buf, + size_t rx_len); +}; + +#define DSI_MODE_VIDEO (1 << 0) +#define DSI_MODE_VIDEO_BURST (1 << 1) +#define DSI_MODE_VIDEO_SYNC_PULSE (1 << 2) +#define DSI_MODE_VIDEO_AUTO_VERT (1 << 3) +#define DSI_MODE_VIDEO_HSE (1 << 4) +#define DSI_MODE_VIDEO_HFP (1 << 5) +#define DSI_MODE_VIDEO_HBP (1 << 6) +#define DSI_MODE_VIDEO_HSA (1 << 7) +#define DSI_MODE_VSYNC_FLUSH (1 << 8) +#define DSI_MODE_EOT_PACKET (1 << 9) + +enum mipi_dsi_pixel_format { + DSI_FMT_RGB888, + DSI_FMT_RGB666, + DSI_FMT_RGB666_PACKED, + DSI_FMT_RGB565, +}; + +struct mipi_dsi_interface_params { + enum mipi_dsi_pixel_format format; + unsigned long mode; + unsigned long hs_clk_freq; + unsigned long esc_clk_freq; + unsigned char data_lanes; + unsigned char cmd_allow; +}; + +struct mipi_dsi_bus { + struct device *dev; + const struct mipi_dsi_bus_ops *ops; +}; + +#define MIPI_DSI_MODULE_PREFIX "mipi-dsi:" +#define MIPI_DSI_NAME_SIZE 32 + +struct mipi_dsi_device_id { + char name[MIPI_DSI_NAME_SIZE]; + __kernel_ulong_t driver_data /* Data private to the driver */ + __aligned(sizeof(__kernel_ulong_t)); +}; + +struct mipi_dsi_device { + char name[MIPI_DSI_NAME_SIZE]; + int id; + struct device dev; + + const struct mipi_dsi_device_id *id_entry; + struct mipi_dsi_bus *bus; + struct videomode vm; + struct mipi_dsi_interface_params params; +}; + +#define to_mipi_dsi_device(d) container_of(d, struct mipi_dsi_device, dev) + +int mipi_dsi_device_register(struct mipi_dsi_device *dev, + struct mipi_dsi_bus *bus); +void mipi_dsi_device_unregister(struct mipi_dsi_device *dev); + +struct mipi_dsi_driver { + int(*probe)(struct mipi_dsi_device *); + int(*remove)(struct mipi_dsi_device *); + struct device_driver driver; + const struct mipi_dsi_device_id *id_table; +}; + +#define to_mipi_dsi_driver(d) container_of(d, struct mipi_dsi_driver, driver) + +int mipi_dsi_driver_register(struct mipi_dsi_driver *drv); +void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv); + +static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dev, + void *data) +{ + dev_set_drvdata(&dev->dev, data); +} + +int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus); +void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus); + +/* module_mipi_dsi_driver() - Helper macro for drivers that don't do + * anything special in module init/exit. This eliminates a lot of + * boilerplate. Each module may only use this macro once, and + * calling it replaces module_init() and module_exit() + */ +#define module_mipi_dsi_driver(__mipi_dsi_driver) \ + module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ + mipi_dsi_driver_unregister) + +int mipi_dsi_set_power(struct mipi_dsi_device *dev, bool on); +int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on); +int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 *data, + size_t len); +int mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd, + u8 *data, size_t len); + +#define mipi_dsi_dcs_write_seq(dev, channel, seq...) \ +({\ + const u8 d[] = { seq };\ + BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too long for stack");\ + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ +}) + +#define mipi_dsi_dcs_write_static_seq(dev, channel, seq...) \ +({\ + static const u8 d[] = { seq };\ + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ +}) + +#endif /* __MIPI_DSI_BUS__ */ -- 1.8.1.2 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC PATCH 1/4] mipi-dsi-bus: add MIPI DSI bus support @ 2013-09-24 14:23 ` Andrzej Hajda 0 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park MIPI DSI is a high-speed serial interface to transmit data from/to host to display module. Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> --- drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/mipi-dsi-bus.c | 332 +++++++++++++++++++++++++++++++++++ include/video/display.h | 3 + include/video/mipi-dsi-bus.h | 144 +++++++++++++++ 5 files changed, 484 insertions(+) create mode 100644 drivers/video/display/mipi-dsi-bus.c create mode 100644 include/video/mipi-dsi-bus.h diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 9b482a8..619b05d 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -20,6 +20,10 @@ config DISPLAY_MIPI_DBI tristate default n +config DISPLAY_MIPI_DSI + tristate + default n + config DISPLAY_PANEL_DPI tristate "DPI (Parallel) Display Panels" ---help--- diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index d03c64a..b323fd4 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -3,6 +3,7 @@ display-y := display-core.o \ obj-$(CONFIG_DISPLAY_CORE) += display.o obj-$(CONFIG_DISPLAY_CONNECTOR_VGA) += con-vga.o obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o +obj-$(CONFIG_DISPLAY_MIPI_DSI) += mipi-dsi-bus.o obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o diff --git a/drivers/video/display/mipi-dsi-bus.c b/drivers/video/display/mipi-dsi-bus.c new file mode 100644 index 0000000..a194d92 --- /dev/null +++ b/drivers/video/display/mipi-dsi-bus.c @@ -0,0 +1,332 @@ +/* + * MIPI DSI Bus + * + * Copyright (C) 2012, Samsung Electronics, Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.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/device.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <video/mipi_display.h> +#include <video/mipi-dsi-bus.h> + +/* ----------------------------------------------------------------------------- + * Bus operations + */ + +int mipi_dsi_set_power(struct mipi_dsi_device *dev, bool on) +{ + const struct mipi_dsi_bus_ops *ops = dev->bus->ops; + + if (!ops->set_power) + return 0; + + return ops->set_power(dev->bus, dev, on); +} +EXPORT_SYMBOL_GPL(mipi_dsi_set_power); + +int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on) +{ + const struct mipi_dsi_bus_ops *ops = dev->bus->ops; + + if (!ops->set_stream) + return 0; + + return ops->set_stream(dev->bus, dev, on); +} +EXPORT_SYMBOL_GPL(mipi_dsi_set_stream); + +int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 *data, + size_t len) +{ + const struct mipi_dsi_bus_ops *ops = dev->bus->ops; + u8 type = channel << 6; + + if (!ops->transfer) + return -EINVAL; + + switch (len) { + case 0: + return -EINVAL; + case 1: + type |= MIPI_DSI_DCS_SHORT_WRITE; + break; + case 2: + type |= MIPI_DSI_DCS_SHORT_WRITE_PARAM; + break; + default: + type |= MIPI_DSI_DCS_LONG_WRITE; + } + + return ops->transfer(dev->bus, dev, type, data, len, NULL, 0); +} +EXPORT_SYMBOL_GPL(mipi_dsi_dcs_write); + +int mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd, + u8 *data, size_t len) +{ + const struct mipi_dsi_bus_ops *ops = dev->bus->ops; + u8 type = MIPI_DSI_DCS_READ | (channel << 6); + + if (!ops->transfer) + return -EINVAL; + + return ops->transfer(dev->bus, dev, type, &cmd, 1, data, len); +} +EXPORT_SYMBOL_GPL(mipi_dsi_dcs_read); + +/* ----------------------------------------------------------------------------- + * Bus type + */ + +static const struct mipi_dsi_device_id * +mipi_dsi_match_id(const struct mipi_dsi_device_id *id, + struct mipi_dsi_device *dev) +{ + while (id->name[0]) { + if (strcmp(dev->name, id->name) == 0) { + dev->id_entry = id; + return id; + } + id++; + } + return NULL; +} + +static int mipi_dsi_match(struct device *_dev, struct device_driver *_drv) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_drv); + + if (of_driver_match_device(_dev, _drv)) + return 1; + + if (drv->id_table) + return mipi_dsi_match_id(drv->id_table, dev) != NULL; + + return (strcmp(dev->name, _drv->name) == 0); +} + +static ssize_t modalias_show(struct device *_dev, struct device_attribute *a, + char *buf) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + int len = snprintf(buf, PAGE_SIZE, MIPI_DSI_MODULE_PREFIX "%s\n", + dev->name); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} + +static struct device_attribute mipi_dsi_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int mipi_dsi_uevent(struct device *_dev, struct kobj_uevent_env *env) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + + add_uevent_var(env, "MODALIAS=%s%s", MIPI_DSI_MODULE_PREFIX, + dev->name); + return 0; +} + +static const struct dev_pm_ops mipi_dsi_dev_pm_ops = { + .runtime_suspend = pm_generic_runtime_suspend, + .runtime_resume = pm_generic_runtime_resume, + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, +}; + +static struct bus_type mipi_dsi_bus_type = { + .name = "mipi-dsi", + .dev_attrs = mipi_dsi_dev_attrs, + .match = mipi_dsi_match, + .uevent = mipi_dsi_uevent, + .pm = &mipi_dsi_dev_pm_ops, +}; + +void mipi_dsi_dev_release(struct device *_dev) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + kfree(dev); +} + +static struct device_type mipi_dsi_dev_type = { + .release = mipi_dsi_dev_release, +}; + + +/* ----------------------------------------------------------------------------- + * Device and driver (un)registration + */ + +/** + * mipi_dsi_device_register - register a DSI device + * @dev: DSI device we're registering + */ +int mipi_dsi_device_register(struct mipi_dsi_device *dev, + struct mipi_dsi_bus *bus) +{ + device_initialize(&dev->dev); + + dev->bus = bus; + dev->dev.bus = &mipi_dsi_bus_type; + dev->dev.parent = bus->dev; + dev->dev.type = &mipi_dsi_dev_type; + + if (dev->id != -1) + dev_set_name(&dev->dev, "%s.%d", dev->name, dev->id); + else + dev_set_name(&dev->dev, "%s", dev->name); + + return device_add(&dev->dev); +} +EXPORT_SYMBOL_GPL(mipi_dsi_device_register); + +/** + * mipi_dsi_device_unregister - unregister a DSI device + * @dev: DSI device we're unregistering + */ +void mipi_dsi_device_unregister(struct mipi_dsi_device *dev) +{ + device_del(&dev->dev); + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(mipi_dsi_device_unregister); + +static int mipi_dsi_drv_probe(struct device *_dev) +{ + struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_dev->driver); + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + + return drv->probe(dev); +} + +static int mipi_dsi_drv_remove(struct device *_dev) +{ + struct mipi_dsi_driver *drv = to_mipi_dsi_driver(_dev->driver); + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + + return drv->remove(dev); +} + +/** + * mipi_dsi_driver_register - register a driver for DSI devices + * @drv: DSI driver structure + */ +int mipi_dsi_driver_register(struct mipi_dsi_driver *drv) +{ + drv->driver.bus = &mipi_dsi_bus_type; + if (drv->probe) + drv->driver.probe = mipi_dsi_drv_probe; + if (drv->remove) + drv->driver.remove = mipi_dsi_drv_remove; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(mipi_dsi_driver_register); + +/** + * mipi_dsi_driver_unregister - unregister a driver for DSI devices + * @drv: DSI driver structure + */ +void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(mipi_dsi_driver_unregister); + +struct mipi_dsi_device *of_mipi_dsi_register_device(struct mipi_dsi_bus *bus, + struct device_node *node) +{ + struct mipi_dsi_device *d = NULL; + int ret; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return ERR_PTR(-ENOMEM); + + ret = of_modalias_node(node, d->name, sizeof(d->name)); + if (ret) { + dev_err(bus->dev, "modalias failure on %s\n", node->full_name); + goto err_free; + } + + d->dev.of_node = of_node_get(node); + if (!d->dev.of_node) + goto err_free; + + ret = mipi_dsi_device_register(d, bus); + + if (!ret) + return d; + + of_node_put(node); +err_free: + kfree(d); + + return ERR_PTR(ret); +} + +int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus) +{ + struct device_node *n; + + for_each_child_of_node(bus->dev->of_node, n) + of_mipi_dsi_register_device(bus, n); + + return 0; +} +EXPORT_SYMBOL_GPL(of_mipi_dsi_register_devices); + +static int mipi_dsi_remove_device_fn(struct device *_dev, void *priv) +{ + struct mipi_dsi_device *dev = to_mipi_dsi_device(_dev); + + mipi_dsi_device_unregister(dev); + + return 0; +} + +void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus) +{ + device_for_each_child(bus->dev, bus, mipi_dsi_remove_device_fn); +} +EXPORT_SYMBOL_GPL(mipi_dsi_unregister_devices); +/* ----------------------------------------------------------------------------- + * Init/exit + */ + +static int __init mipi_dsi_init(void) +{ + return bus_register(&mipi_dsi_bus_type); +} + +static void __exit mipi_dsi_exit(void) +{ + bus_unregister(&mipi_dsi_bus_type); +} + +module_init(mipi_dsi_init); +module_exit(mipi_dsi_exit) + +MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>"); +MODULE_DESCRIPTION("MIPI DSI Bus"); +MODULE_LICENSE("GPL v2"); diff --git a/include/video/display.h b/include/video/display.h index 3138401..7faca0f 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -18,6 +18,7 @@ #include <linux/module.h> #include <media/media-entity.h> #include <video/mipi-dbi-bus.h> +#include <video/mipi-dsi-bus.h> #define DISPLAY_PIXEL_CODING(option, type, from, to, variant) \ (((option) << 17) | ((type) << 13) | ((variant) << 10) | \ @@ -184,6 +185,7 @@ enum display_entity_stream_state { enum display_entity_interface_type { DISPLAY_ENTITY_INTERFACE_DPI, DISPLAY_ENTITY_INTERFACE_DBI, + DISPLAY_ENTITY_INTERFACE_DSI, DISPLAY_ENTITY_INTERFACE_LVDS, DISPLAY_ENTITY_INTERFACE_VGA, }; @@ -192,6 +194,7 @@ struct display_entity_interface_params { enum display_entity_interface_type type; union { struct mipi_dbi_interface_params dbi; + struct mipi_dsi_interface_params dsi; } p; }; diff --git a/include/video/mipi-dsi-bus.h b/include/video/mipi-dsi-bus.h new file mode 100644 index 0000000..a78792d --- /dev/null +++ b/include/video/mipi-dsi-bus.h @@ -0,0 +1,144 @@ +/* + * MIPI DSI Bus + * + * Copyright (C) 2013, Samsung Electronics, Co., Ltd. + * Andrzej Hajda <a.hajda@samsung.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. + */ + +#ifndef __MIPI_DSI_BUS_H__ +#define __MIPI_DSI_BUS_H__ + +#include <linux/device.h> +#include <video/videomode.h> + +struct mipi_dsi_bus; +struct mipi_dsi_device; + +struct mipi_dsi_bus_ops { + int (*set_power)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev, + bool on); + int (*set_stream)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev, + bool on); + int (*transfer)(struct mipi_dsi_bus *bus, struct mipi_dsi_device *dev, + u8 type, const u8 *tx_buf, size_t tx_len, u8 *rx_buf, + size_t rx_len); +}; + +#define DSI_MODE_VIDEO (1 << 0) +#define DSI_MODE_VIDEO_BURST (1 << 1) +#define DSI_MODE_VIDEO_SYNC_PULSE (1 << 2) +#define DSI_MODE_VIDEO_AUTO_VERT (1 << 3) +#define DSI_MODE_VIDEO_HSE (1 << 4) +#define DSI_MODE_VIDEO_HFP (1 << 5) +#define DSI_MODE_VIDEO_HBP (1 << 6) +#define DSI_MODE_VIDEO_HSA (1 << 7) +#define DSI_MODE_VSYNC_FLUSH (1 << 8) +#define DSI_MODE_EOT_PACKET (1 << 9) + +enum mipi_dsi_pixel_format { + DSI_FMT_RGB888, + DSI_FMT_RGB666, + DSI_FMT_RGB666_PACKED, + DSI_FMT_RGB565, +}; + +struct mipi_dsi_interface_params { + enum mipi_dsi_pixel_format format; + unsigned long mode; + unsigned long hs_clk_freq; + unsigned long esc_clk_freq; + unsigned char data_lanes; + unsigned char cmd_allow; +}; + +struct mipi_dsi_bus { + struct device *dev; + const struct mipi_dsi_bus_ops *ops; +}; + +#define MIPI_DSI_MODULE_PREFIX "mipi-dsi:" +#define MIPI_DSI_NAME_SIZE 32 + +struct mipi_dsi_device_id { + char name[MIPI_DSI_NAME_SIZE]; + __kernel_ulong_t driver_data /* Data private to the driver */ + __aligned(sizeof(__kernel_ulong_t)); +}; + +struct mipi_dsi_device { + char name[MIPI_DSI_NAME_SIZE]; + int id; + struct device dev; + + const struct mipi_dsi_device_id *id_entry; + struct mipi_dsi_bus *bus; + struct videomode vm; + struct mipi_dsi_interface_params params; +}; + +#define to_mipi_dsi_device(d) container_of(d, struct mipi_dsi_device, dev) + +int mipi_dsi_device_register(struct mipi_dsi_device *dev, + struct mipi_dsi_bus *bus); +void mipi_dsi_device_unregister(struct mipi_dsi_device *dev); + +struct mipi_dsi_driver { + int(*probe)(struct mipi_dsi_device *); + int(*remove)(struct mipi_dsi_device *); + struct device_driver driver; + const struct mipi_dsi_device_id *id_table; +}; + +#define to_mipi_dsi_driver(d) container_of(d, struct mipi_dsi_driver, driver) + +int mipi_dsi_driver_register(struct mipi_dsi_driver *drv); +void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv); + +static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dev, + void *data) +{ + dev_set_drvdata(&dev->dev, data); +} + +int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus); +void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus); + +/* module_mipi_dsi_driver() - Helper macro for drivers that don't do + * anything special in module init/exit. This eliminates a lot of + * boilerplate. Each module may only use this macro once, and + * calling it replaces module_init() and module_exit() + */ +#define module_mipi_dsi_driver(__mipi_dsi_driver) \ + module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ + mipi_dsi_driver_unregister) + +int mipi_dsi_set_power(struct mipi_dsi_device *dev, bool on); +int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on); +int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 *data, + size_t len); +int mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd, + u8 *data, size_t len); + +#define mipi_dsi_dcs_write_seq(dev, channel, seq...) \ +({\ + const u8 d[] = { seq };\ + BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too long for stack");\ + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ +}) + +#define mipi_dsi_dcs_write_static_seq(dev, channel, seq...) \ +({\ + static const u8 d[] = { seq };\ + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ +}) + +#endif /* __MIPI_DSI_BUS__ */ -- 1.8.1.2 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* RE: [RFC PATCH 1/4] mipi-dsi-bus: add MIPI DSI bus support 2013-09-24 14:23 ` Andrzej Hajda @ 2013-10-07 10:47 ` Bert Kenward -1 siblings, 0 replies; 14+ messages in thread From: Bert Kenward @ 2013-10-07 10:47 UTC (permalink / raw) To: Andrzej Hajda, Laurent Pinchart Cc: linux-fbdev@vger.kernel.org, Kyungmin Park, dri-devel@lists.freedesktop.org, linux-media@vger.kernel.org T24gVHVlc2RheSBTZXB0ZW1iZXIgMjQgMjAxMyBhdCAxNToyMywgQW5kcnplaiBIYWpkYSB3cm90 ZToNCj4gTUlQSSBEU0kgaXMgYSBoaWdoLXNwZWVkIHNlcmlhbCBpbnRlcmZhY2UgdG8gdHJhbnNt aXQNCj4gZGF0YSBmcm9tL3RvIGhvc3QgdG8gZGlzcGxheSBtb2R1bGUuDQo+IA0KPiBTaWduZWQt b2ZmLWJ5OiBBbmRyemVqIEhhamRhIDxhLmhhamRhQHNhbXN1bmcuY29tPg0KPiBTaWduZWQtb2Zm LWJ5OiBLeXVuZ21pbiBQYXJrIDxreXVuZ21pbi5wYXJrQHNhbXN1bmcuY29tPg0KPiAtLS0NCj4g IGRyaXZlcnMvdmlkZW8vZGlzcGxheS9LY29uZmlnICAgICAgICB8ICAgNCArDQo+ICBkcml2ZXJz L3ZpZGVvL2Rpc3BsYXkvTWFrZWZpbGUgICAgICAgfCAgIDEgKw0KPiAgZHJpdmVycy92aWRlby9k aXNwbGF5L21pcGktZHNpLWJ1cy5jIHwgMzMyDQo+ICsrKysrKysrKysrKysrKysrKysrKysrKysr KysrKysrKysrDQo+ICBpbmNsdWRlL3ZpZGVvL2Rpc3BsYXkuaCAgICAgICAgICAgICAgfCAgIDMg Kw0KPiAgaW5jbHVkZS92aWRlby9taXBpLWRzaS1idXMuaCAgICAgICAgIHwgMTQ0ICsrKysrKysr KysrKysrKw0KPiAgNSBmaWxlcyBjaGFuZ2VkLCA0ODQgaW5zZXJ0aW9ucygrKQ0KDQo8c25pcHBl ZCBhcyBmYXIgYXMgbWlwaS1kc2ktYnVzLmgNCg0KPiBkaWZmIC0tZ2l0IGEvaW5jbHVkZS92aWRl by9taXBpLWRzaS1idXMuaCBiL2luY2x1ZGUvdmlkZW8vbWlwaS1kc2ktYnVzLmgNCj4gbmV3IGZp bGUgbW9kZSAxMDA2NDQNCj4gaW5kZXggMDAwMDAwMC4uYTc4NzkyZA0KPiAtLS0gL2Rldi9udWxs DQo+ICsrKyBiL2luY2x1ZGUvdmlkZW8vbWlwaS1kc2ktYnVzLmgNCj4gQEAgLTAsMCArMSwxNDQg QEANCj4gKy8qDQo+ICsgKiBNSVBJIERTSSBCdXMNCj4gKyAqDQo+ICsgKiBDb3B5cmlnaHQgKEMp IDIwMTMsIFNhbXN1bmcgRWxlY3Ryb25pY3MsIENvLiwgTHRkLg0KPiArICogQW5kcnplaiBIYWpk YSA8YS5oYWpkYUBzYW1zdW5nLmNvbT4NCj4gKyAqDQo+ICsgKiBUaGlzIHByb2dyYW0gaXMgZnJl ZSBzb2Z0d2FyZTsgeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeQ0KPiArICog aXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSB2ZXJz aW9uIDIgYXMNCj4gKyAqIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9u Lg0KPiArICovDQo+ICsNCj4gKyNpZm5kZWYgX19NSVBJX0RTSV9CVVNfSF9fDQo+ICsjZGVmaW5l IF9fTUlQSV9EU0lfQlVTX0hfXw0KPiArDQo+ICsjaW5jbHVkZSA8bGludXgvZGV2aWNlLmg+DQo+ ICsjaW5jbHVkZSA8dmlkZW8vdmlkZW9tb2RlLmg+DQo+ICsNCj4gK3N0cnVjdCBtaXBpX2RzaV9i dXM7DQo+ICtzdHJ1Y3QgbWlwaV9kc2lfZGV2aWNlOw0KPiArDQo+ICtzdHJ1Y3QgbWlwaV9kc2lf YnVzX29wcyB7DQo+ICsJaW50ICgqc2V0X3Bvd2VyKShzdHJ1Y3QgbWlwaV9kc2lfYnVzICpidXMs IHN0cnVjdCBtaXBpX2RzaV9kZXZpY2UNCj4gKmRldiwNCj4gKwkJCSBib29sIG9uKTsNCj4gKwlp bnQgKCpzZXRfc3RyZWFtKShzdHJ1Y3QgbWlwaV9kc2lfYnVzICpidXMsIHN0cnVjdCBtaXBpX2Rz aV9kZXZpY2UNCj4gKmRldiwNCj4gKwkJCSAgYm9vbCBvbik7DQo+ICsJaW50ICgqdHJhbnNmZXIp KHN0cnVjdCBtaXBpX2RzaV9idXMgKmJ1cywgc3RydWN0IG1pcGlfZHNpX2RldmljZQ0KPiAqZGV2 LA0KPiArCQkJdTggdHlwZSwgY29uc3QgdTggKnR4X2J1Ziwgc2l6ZV90IHR4X2xlbiwgdTggKnJ4 X2J1ZiwNCj4gKwkJCXNpemVfdCByeF9sZW4pOw0KPiArfTsNCj4gKw0KPiArI2RlZmluZSBEU0lf TU9ERV9WSURFTwkJCSgxIDw8IDApDQo+ICsjZGVmaW5lIERTSV9NT0RFX1ZJREVPX0JVUlNUCQko MSA8PCAxKQ0KPiArI2RlZmluZSBEU0lfTU9ERV9WSURFT19TWU5DX1BVTFNFCSgxIDw8IDIpDQo+ ICsjZGVmaW5lIERTSV9NT0RFX1ZJREVPX0FVVE9fVkVSVAkoMSA8PCAzKQ0KPiArI2RlZmluZSBE U0lfTU9ERV9WSURFT19IU0UJCSgxIDw8IDQpDQo+ICsjZGVmaW5lIERTSV9NT0RFX1ZJREVPX0hG UAkJKDEgPDwgNSkNCj4gKyNkZWZpbmUgRFNJX01PREVfVklERU9fSEJQCQkoMSA8PCA2KQ0KPiAr I2RlZmluZSBEU0lfTU9ERV9WSURFT19IU0EJCSgxIDw8IDcpDQo+ICsjZGVmaW5lIERTSV9NT0RF X1ZTWU5DX0ZMVVNICQkoMSA8PCA4KQ0KPiArI2RlZmluZSBEU0lfTU9ERV9FT1RfUEFDS0VUCQko MSA8PCA5KQ0KPiArDQo+ICtlbnVtIG1pcGlfZHNpX3BpeGVsX2Zvcm1hdCB7DQo+ICsJRFNJX0ZN VF9SR0I4ODgsDQo+ICsJRFNJX0ZNVF9SR0I2NjYsDQo+ICsJRFNJX0ZNVF9SR0I2NjZfUEFDS0VE LA0KPiArCURTSV9GTVRfUkdCNTY1LA0KPiArfTsNCj4gKw0KPiArc3RydWN0IG1pcGlfZHNpX2lu dGVyZmFjZV9wYXJhbXMgew0KPiArCWVudW0gbWlwaV9kc2lfcGl4ZWxfZm9ybWF0IGZvcm1hdDsN Cj4gKwl1bnNpZ25lZCBsb25nIG1vZGU7DQo+ICsJdW5zaWduZWQgbG9uZyBoc19jbGtfZnJlcTsN Cj4gKwl1bnNpZ25lZCBsb25nIGVzY19jbGtfZnJlcTsNCj4gKwl1bnNpZ25lZCBjaGFyIGRhdGFf bGFuZXM7DQo+ICsJdW5zaWduZWQgY2hhciBjbWRfYWxsb3c7DQo+ICt9Ow0KPiArDQo+ICtzdHJ1 Y3QgbWlwaV9kc2lfYnVzIHsNCj4gKwlzdHJ1Y3QgZGV2aWNlICpkZXY7DQo+ICsJY29uc3Qgc3Ry dWN0IG1pcGlfZHNpX2J1c19vcHMgKm9wczsNCj4gK307DQo+ICsNCj4gKyNkZWZpbmUgTUlQSV9E U0lfTU9EVUxFX1BSRUZJWAkJIm1pcGktZHNpOiINCj4gKyNkZWZpbmUgTUlQSV9EU0lfTkFNRV9T SVpFCQkzMg0KPiArDQo+ICtzdHJ1Y3QgbWlwaV9kc2lfZGV2aWNlX2lkIHsNCj4gKwljaGFyIG5h bWVbTUlQSV9EU0lfTkFNRV9TSVpFXTsNCj4gKwlfX2tlcm5lbF91bG9uZ190IGRyaXZlcl9kYXRh CS8qIERhdGEgcHJpdmF0ZSB0byB0aGUgZHJpdmVyICovDQo+ICsJCQlfX2FsaWduZWQoc2l6ZW9m KF9fa2VybmVsX3Vsb25nX3QpKTsNCj4gK307DQo+ICsNCj4gK3N0cnVjdCBtaXBpX2RzaV9kZXZp Y2Ugew0KPiArCWNoYXIgbmFtZVtNSVBJX0RTSV9OQU1FX1NJWkVdOw0KPiArCWludCBpZDsNCj4g KwlzdHJ1Y3QgZGV2aWNlIGRldjsNCj4gKw0KPiArCWNvbnN0IHN0cnVjdCBtaXBpX2RzaV9kZXZp Y2VfaWQgKmlkX2VudHJ5Ow0KPiArCXN0cnVjdCBtaXBpX2RzaV9idXMgKmJ1czsNCj4gKwlzdHJ1 Y3QgdmlkZW9tb2RlIHZtOw0KPiArCXN0cnVjdCBtaXBpX2RzaV9pbnRlcmZhY2VfcGFyYW1zIHBh cmFtczsNCj4gK307DQo+ICsNCj4gKyNkZWZpbmUgdG9fbWlwaV9kc2lfZGV2aWNlKGQpCWNvbnRh aW5lcl9vZihkLCBzdHJ1Y3QNCj4gbWlwaV9kc2lfZGV2aWNlLCBkZXYpDQo+ICsNCj4gK2ludCBt aXBpX2RzaV9kZXZpY2VfcmVnaXN0ZXIoc3RydWN0IG1pcGlfZHNpX2RldmljZSAqZGV2LA0KPiAr CQkJICAgICBzdHJ1Y3QgbWlwaV9kc2lfYnVzICpidXMpOw0KPiArdm9pZCBtaXBpX2RzaV9kZXZp Y2VfdW5yZWdpc3RlcihzdHJ1Y3QgbWlwaV9kc2lfZGV2aWNlICpkZXYpOw0KPiArDQo+ICtzdHJ1 Y3QgbWlwaV9kc2lfZHJpdmVyIHsNCj4gKwlpbnQoKnByb2JlKShzdHJ1Y3QgbWlwaV9kc2lfZGV2 aWNlICopOw0KPiArCWludCgqcmVtb3ZlKShzdHJ1Y3QgbWlwaV9kc2lfZGV2aWNlICopOw0KPiAr CXN0cnVjdCBkZXZpY2VfZHJpdmVyIGRyaXZlcjsNCj4gKwljb25zdCBzdHJ1Y3QgbWlwaV9kc2lf ZGV2aWNlX2lkICppZF90YWJsZTsNCj4gK307DQo+ICsNCj4gKyNkZWZpbmUgdG9fbWlwaV9kc2lf ZHJpdmVyKGQpCWNvbnRhaW5lcl9vZihkLCBzdHJ1Y3QNCj4gbWlwaV9kc2lfZHJpdmVyLCBkcml2 ZXIpDQo+ICsNCj4gK2ludCBtaXBpX2RzaV9kcml2ZXJfcmVnaXN0ZXIoc3RydWN0IG1pcGlfZHNp X2RyaXZlciAqZHJ2KTsNCj4gK3ZvaWQgbWlwaV9kc2lfZHJpdmVyX3VucmVnaXN0ZXIoc3RydWN0 IG1pcGlfZHNpX2RyaXZlciAqZHJ2KTsNCj4gKw0KPiArc3RhdGljIGlubGluZSB2b2lkICptaXBp X2RzaV9nZXRfZHJ2ZGF0YShjb25zdCBzdHJ1Y3QgbWlwaV9kc2lfZGV2aWNlDQo+ICpkZXYpDQo+ ICt7DQo+ICsJcmV0dXJuIGRldl9nZXRfZHJ2ZGF0YSgmZGV2LT5kZXYpOw0KPiArfQ0KPiArDQo+ ICtzdGF0aWMgaW5saW5lIHZvaWQgbWlwaV9kc2lfc2V0X2RydmRhdGEoc3RydWN0IG1pcGlfZHNp X2RldmljZSAqZGV2LA0KPiArCQkJCQl2b2lkICpkYXRhKQ0KPiArew0KPiArCWRldl9zZXRfZHJ2 ZGF0YSgmZGV2LT5kZXYsIGRhdGEpOw0KPiArfQ0KPiArDQo+ICtpbnQgb2ZfbWlwaV9kc2lfcmVn aXN0ZXJfZGV2aWNlcyhzdHJ1Y3QgbWlwaV9kc2lfYnVzICpidXMpOw0KPiArdm9pZCBtaXBpX2Rz aV91bnJlZ2lzdGVyX2RldmljZXMoc3RydWN0IG1pcGlfZHNpX2J1cyAqYnVzKTsNCj4gKw0KPiAr LyogbW9kdWxlX21pcGlfZHNpX2RyaXZlcigpIC0gSGVscGVyIG1hY3JvIGZvciBkcml2ZXJzIHRo YXQgZG9uJ3QgZG8NCj4gKyAqIGFueXRoaW5nIHNwZWNpYWwgaW4gbW9kdWxlIGluaXQvZXhpdC4g IFRoaXMgZWxpbWluYXRlcyBhIGxvdCBvZg0KPiArICogYm9pbGVycGxhdGUuICBFYWNoIG1vZHVs ZSBtYXkgb25seSB1c2UgdGhpcyBtYWNybyBvbmNlLCBhbmQNCj4gKyAqIGNhbGxpbmcgaXQgcmVw bGFjZXMgbW9kdWxlX2luaXQoKSBhbmQgbW9kdWxlX2V4aXQoKQ0KPiArICovDQo+ICsjZGVmaW5l IG1vZHVsZV9taXBpX2RzaV9kcml2ZXIoX19taXBpX2RzaV9kcml2ZXIpIFwNCj4gKwltb2R1bGVf ZHJpdmVyKF9fbWlwaV9kc2lfZHJpdmVyLCBtaXBpX2RzaV9kcml2ZXJfcmVnaXN0ZXIsIFwNCj4g KwkJCW1pcGlfZHNpX2RyaXZlcl91bnJlZ2lzdGVyKQ0KPiArDQo+ICtpbnQgbWlwaV9kc2lfc2V0 X3Bvd2VyKHN0cnVjdCBtaXBpX2RzaV9kZXZpY2UgKmRldiwgYm9vbCBvbik7DQo+ICtpbnQgbWlw aV9kc2lfc2V0X3N0cmVhbShzdHJ1Y3QgbWlwaV9kc2lfZGV2aWNlICpkZXYsIGJvb2wgb24pOw0K PiAraW50IG1pcGlfZHNpX2Rjc193cml0ZShzdHJ1Y3QgbWlwaV9kc2lfZGV2aWNlICpkZXYsIGlu dCBjaGFubmVsLCBjb25zdCB1OA0KPiAqZGF0YSwNCj4gKwkJICAgICAgIHNpemVfdCBsZW4pOw0K PiAraW50IG1pcGlfZHNpX2Rjc19yZWFkKHN0cnVjdCBtaXBpX2RzaV9kZXZpY2UgKmRldiwgaW50 IGNoYW5uZWwsIHU4IGNtZCwNCj4gKwkJICAgICAgdTggKmRhdGEsIHNpemVfdCBsZW4pOw0KPiAr DQo+ICsjZGVmaW5lIG1pcGlfZHNpX2Rjc193cml0ZV9zZXEoZGV2LCBjaGFubmVsLCBzZXEuLi4p IFwNCj4gKyh7XA0KPiArCWNvbnN0IHU4IGRbXSA9IHsgc2VxIH07XA0KPiArCUJVSUxEX0JVR19P Tl9NU0coQVJSQVlfU0laRShkKSA+IDY0LCAiRENTIHNlcXVlbmNlIHRvbyBsb25nIGZvcg0KPiBz dGFjayIpO1wNCj4gKwltaXBpX2RzaV9kY3Nfd3JpdGUoZGV2LCBjaGFubmVsLCBkLCBBUlJBWV9T SVpFKGQpKTtcDQo+ICt9KQ0KPiArDQo+ICsjZGVmaW5lIG1pcGlfZHNpX2Rjc193cml0ZV9zdGF0 aWNfc2VxKGRldiwgY2hhbm5lbCwgc2VxLi4uKSBcDQo+ICsoe1wNCj4gKwlzdGF0aWMgY29uc3Qg dTggZFtdID0geyBzZXEgfTtcDQo+ICsJbWlwaV9kc2lfZGNzX3dyaXRlKGRldiwgY2hhbm5lbCwg ZCwgQVJSQVlfU0laRShkKSk7XA0KPiArfSkNCj4gKw0KPiArI2VuZGlmIC8qIF9fTUlQSV9EU0lf QlVTX18gKi8NCg0KSSBtYXkgd2VsbCBoYXZlIG1pc3NlZCBzb21ldGhpbmcsIGJ1dCBJIGNhbid0 IHNlZSBleGFjdGx5IGhvdyBhIGNvbW1hbmQgbW9kZQ0KdXBkYXRlIHdvdWxkIGJlIGRvbmUgd2l0 aCB0aGlzIGludGVyZmFjZS4gV291bGQgdGhpcyByZXF1aXJlIHJlcGVhdGVkIGNhbGxzIHRvDQou dHJhbnNmZXI/IFN1Y2ggdHJhbnNmZXJzIHdvdWxkIG5lZWQgdG8gYmUgZmxhZ2dlZCBhcyByZXF1 aXJpbmcNCnN5bmNocm9uaXNhdGlvbiB3aXRoIGEgdGVhcmluZyBlZmZlY3QgY29udHJvbCBzaWdu YWwgLSBlaXRoZXIgdGhlIGluYmFuZA0KbWV0aG9kIG9yIGEgZGVkaWNhdGVkIGxpbmUuIEkgc3Vz cGVjdCBtYW55IGhhcmR3YXJlIGltcGxlbWVudGF0aW9ucyB3aWxsIGhhdmUNCmEgc3BlY2lmaWMg bWV0aG9kIGZvciB0cmFuc2ZlcnJpbmcgcGl4ZWwgZGF0YSBpbiBhIERTSSBjb21tYW5kIG1vZGUg dHJhbnNmZXIuDQoNClRoZSBjb21tYW5kIHNlbmRpbmcgcGVyaW9kIGR1cmluZyB2aWRlbyBtb2Rl IHNob3VsZCBwcm9iYWJseSBiZSBjb25maWd1cmFibGUNCm9uIGEgcGVyLXRyYW5zZmVyIGJhc2lz LiBTb21lIGNvbW1hbmRzIGhhdmUgdG8gYmUgc3luY2hyb25pc2VkIHdpdGggdmVydGljYWwNCmJs YW5raW5nLCBvdGhlcnMgZG8gbm90LiBUaGlzIGNvdWxkIHBlcmhhcHMgYmUgY29tYmluZWQgd2l0 aCBhIHdpZGVyDQpjb25maWd1cmF0aW9uIG9wdGlvbiBmb3IgYSBnaXZlbiBwYW5lbCBvciBpbnRl cmZhY2UuIFNpbWlsYXJseSwgc2VsZWN0aW9uIG9mDQpsb3cgcG93ZXIgKExQKSBhbmQgaGlnaCBz cGVlZCAoSFMpIG1vZGUgb24gYSBwZXItdHJhbnNmZXIgYmFzaXMgY2FuIGJlIG5lZWRlZA0KZm9y IHNvbWUgcGFuZWxzLg0KDQpJcyB0aGVyZSBhIG1lY2hhbmlzbSBmb3IgY29udHJvbGxpbmcgdWx0 cmEtbG93IHBvd2VyIHN0YXRlIChVTFBTKSBlbnRyeT8gQWxzbywNCmlzIHRoZXJlIGEgbWV0aG9k IGZvciBzZW5kaW5nIGFyYml0cmFyeSB0cmlnZ2VyIG1lc3NhZ2VzIChlZyB0aGUgcmVzZXQNCnRy aWdnZXIpPw0KDQpUaGFua3MsDQoNCkJlcnQuDQotLSANCkJlcnQgS2Vud2FyZA0KU29mdHdhcmUg RW5naW5lZXINCkJyb2FkY29tIE1vYmlsZSBQbGF0Zm9ybSBTb2x1dGlvbnMNCkNhbWJyaWRnZSwg VUsNCg= ^ permalink raw reply [flat|nested] 14+ messages in thread
* RE: [RFC PATCH 1/4] mipi-dsi-bus: add MIPI DSI bus support @ 2013-10-07 10:47 ` Bert Kenward 0 siblings, 0 replies; 14+ messages in thread From: Bert Kenward @ 2013-10-07 10:47 UTC (permalink / raw) To: Andrzej Hajda, Laurent Pinchart Cc: linux-fbdev@vger.kernel.org, Kyungmin Park, dri-devel@lists.freedesktop.org, linux-media@vger.kernel.org On Tuesday September 24 2013 at 15:23, Andrzej Hajda wrote: > MIPI DSI is a high-speed serial interface to transmit > data from/to host to display module. > > Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> > Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> > --- > drivers/video/display/Kconfig | 4 + > drivers/video/display/Makefile | 1 + > drivers/video/display/mipi-dsi-bus.c | 332 > +++++++++++++++++++++++++++++++++++ > include/video/display.h | 3 + > include/video/mipi-dsi-bus.h | 144 +++++++++++++++ > 5 files changed, 484 insertions(+) <snipped as far as mipi-dsi-bus.h > diff --git a/include/video/mipi-dsi-bus.h b/include/video/mipi-dsi-bus.h > new file mode 100644 > index 0000000..a78792d > --- /dev/null > +++ b/include/video/mipi-dsi-bus.h > @@ -0,0 +1,144 @@ > +/* > + * MIPI DSI Bus > + * > + * Copyright (C) 2013, Samsung Electronics, Co., Ltd. > + * Andrzej Hajda <a.hajda@samsung.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. > + */ > + > +#ifndef __MIPI_DSI_BUS_H__ > +#define __MIPI_DSI_BUS_H__ > + > +#include <linux/device.h> > +#include <video/videomode.h> > + > +struct mipi_dsi_bus; > +struct mipi_dsi_device; > + > +struct mipi_dsi_bus_ops { > + int (*set_power)(struct mipi_dsi_bus *bus, struct mipi_dsi_device > *dev, > + bool on); > + int (*set_stream)(struct mipi_dsi_bus *bus, struct mipi_dsi_device > *dev, > + bool on); > + int (*transfer)(struct mipi_dsi_bus *bus, struct mipi_dsi_device > *dev, > + u8 type, const u8 *tx_buf, size_t tx_len, u8 *rx_buf, > + size_t rx_len); > +}; > + > +#define DSI_MODE_VIDEO (1 << 0) > +#define DSI_MODE_VIDEO_BURST (1 << 1) > +#define DSI_MODE_VIDEO_SYNC_PULSE (1 << 2) > +#define DSI_MODE_VIDEO_AUTO_VERT (1 << 3) > +#define DSI_MODE_VIDEO_HSE (1 << 4) > +#define DSI_MODE_VIDEO_HFP (1 << 5) > +#define DSI_MODE_VIDEO_HBP (1 << 6) > +#define DSI_MODE_VIDEO_HSA (1 << 7) > +#define DSI_MODE_VSYNC_FLUSH (1 << 8) > +#define DSI_MODE_EOT_PACKET (1 << 9) > + > +enum mipi_dsi_pixel_format { > + DSI_FMT_RGB888, > + DSI_FMT_RGB666, > + DSI_FMT_RGB666_PACKED, > + DSI_FMT_RGB565, > +}; > + > +struct mipi_dsi_interface_params { > + enum mipi_dsi_pixel_format format; > + unsigned long mode; > + unsigned long hs_clk_freq; > + unsigned long esc_clk_freq; > + unsigned char data_lanes; > + unsigned char cmd_allow; > +}; > + > +struct mipi_dsi_bus { > + struct device *dev; > + const struct mipi_dsi_bus_ops *ops; > +}; > + > +#define MIPI_DSI_MODULE_PREFIX "mipi-dsi:" > +#define MIPI_DSI_NAME_SIZE 32 > + > +struct mipi_dsi_device_id { > + char name[MIPI_DSI_NAME_SIZE]; > + __kernel_ulong_t driver_data /* Data private to the driver */ > + __aligned(sizeof(__kernel_ulong_t)); > +}; > + > +struct mipi_dsi_device { > + char name[MIPI_DSI_NAME_SIZE]; > + int id; > + struct device dev; > + > + const struct mipi_dsi_device_id *id_entry; > + struct mipi_dsi_bus *bus; > + struct videomode vm; > + struct mipi_dsi_interface_params params; > +}; > + > +#define to_mipi_dsi_device(d) container_of(d, struct > mipi_dsi_device, dev) > + > +int mipi_dsi_device_register(struct mipi_dsi_device *dev, > + struct mipi_dsi_bus *bus); > +void mipi_dsi_device_unregister(struct mipi_dsi_device *dev); > + > +struct mipi_dsi_driver { > + int(*probe)(struct mipi_dsi_device *); > + int(*remove)(struct mipi_dsi_device *); > + struct device_driver driver; > + const struct mipi_dsi_device_id *id_table; > +}; > + > +#define to_mipi_dsi_driver(d) container_of(d, struct > mipi_dsi_driver, driver) > + > +int mipi_dsi_driver_register(struct mipi_dsi_driver *drv); > +void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv); > + > +static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device > *dev) > +{ > + return dev_get_drvdata(&dev->dev); > +} > + > +static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dev, > + void *data) > +{ > + dev_set_drvdata(&dev->dev, data); > +} > + > +int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus); > +void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus); > + > +/* module_mipi_dsi_driver() - Helper macro for drivers that don't do > + * anything special in module init/exit. This eliminates a lot of > + * boilerplate. Each module may only use this macro once, and > + * calling it replaces module_init() and module_exit() > + */ > +#define module_mipi_dsi_driver(__mipi_dsi_driver) \ > + module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ > + mipi_dsi_driver_unregister) > + > +int mipi_dsi_set_power(struct mipi_dsi_device *dev, bool on); > +int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on); > +int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 > *data, > + size_t len); > +int mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd, > + u8 *data, size_t len); > + > +#define mipi_dsi_dcs_write_seq(dev, channel, seq...) \ > +({\ > + const u8 d[] = { seq };\ > + BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too long for > stack");\ > + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ > +}) > + > +#define mipi_dsi_dcs_write_static_seq(dev, channel, seq...) \ > +({\ > + static const u8 d[] = { seq };\ > + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ > +}) > + > +#endif /* __MIPI_DSI_BUS__ */ I may well have missed something, but I can't see exactly how a command mode update would be done with this interface. Would this require repeated calls to .transfer? Such transfers would need to be flagged as requiring synchronisation with a tearing effect control signal - either the inband method or a dedicated line. I suspect many hardware implementations will have a specific method for transferring pixel data in a DSI command mode transfer. The command sending period during video mode should probably be configurable on a per-transfer basis. Some commands have to be synchronised with vertical blanking, others do not. This could perhaps be combined with a wider configuration option for a given panel or interface. Similarly, selection of low power (LP) and high speed (HS) mode on a per-transfer basis can be needed for some panels. Is there a mechanism for controlling ultra-low power state (ULPS) entry? Also, is there a method for sending arbitrary trigger messages (eg the reset trigger)? Thanks, Bert. -- Bert Kenward Software Engineer Broadcom Mobile Platform Solutions Cambridge, UK ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 1/4] mipi-dsi-bus: add MIPI DSI bus support 2013-10-07 10:47 ` Bert Kenward @ 2013-10-10 9:57 ` Andrzej Hajda -1 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-10-10 9:57 UTC (permalink / raw) To: Bert Kenward, Laurent Pinchart Cc: linux-fbdev@vger.kernel.org, Kyungmin Park, dri-devel@lists.freedesktop.org, linux-media@vger.kernel.org On 10/07/2013 12:47 PM, Bert Kenward wrote: > On Tuesday September 24 2013 at 15:23, Andrzej Hajda wrote: >> MIPI DSI is a high-speed serial interface to transmit >> data from/to host to display module. >> >> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> >> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> >> --- >> drivers/video/display/Kconfig | 4 + >> drivers/video/display/Makefile | 1 + >> drivers/video/display/mipi-dsi-bus.c | 332 >> +++++++++++++++++++++++++++++++++++ >> include/video/display.h | 3 + >> include/video/mipi-dsi-bus.h | 144 +++++++++++++++ >> 5 files changed, 484 insertions(+) > <snipped as far as mipi-dsi-bus.h > >> diff --git a/include/video/mipi-dsi-bus.h b/include/video/mipi-dsi-bus.h >> new file mode 100644 >> index 0000000..a78792d >> --- /dev/null >> +++ b/include/video/mipi-dsi-bus.h >> @@ -0,0 +1,144 @@ >> +/* >> + * MIPI DSI Bus >> + * >> + * Copyright (C) 2013, Samsung Electronics, Co., Ltd. >> + * Andrzej Hajda <a.hajda@samsung.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. >> + */ >> + >> +#ifndef __MIPI_DSI_BUS_H__ >> +#define __MIPI_DSI_BUS_H__ >> + >> +#include <linux/device.h> >> +#include <video/videomode.h> >> + >> +struct mipi_dsi_bus; >> +struct mipi_dsi_device; >> + >> +struct mipi_dsi_bus_ops { >> + int (*set_power)(struct mipi_dsi_bus *bus, struct mipi_dsi_device >> *dev, >> + bool on); >> + int (*set_stream)(struct mipi_dsi_bus *bus, struct mipi_dsi_device >> *dev, >> + bool on); >> + int (*transfer)(struct mipi_dsi_bus *bus, struct mipi_dsi_device >> *dev, >> + u8 type, const u8 *tx_buf, size_t tx_len, u8 *rx_buf, >> + size_t rx_len); >> +}; >> + >> +#define DSI_MODE_VIDEO (1 << 0) >> +#define DSI_MODE_VIDEO_BURST (1 << 1) >> +#define DSI_MODE_VIDEO_SYNC_PULSE (1 << 2) >> +#define DSI_MODE_VIDEO_AUTO_VERT (1 << 3) >> +#define DSI_MODE_VIDEO_HSE (1 << 4) >> +#define DSI_MODE_VIDEO_HFP (1 << 5) >> +#define DSI_MODE_VIDEO_HBP (1 << 6) >> +#define DSI_MODE_VIDEO_HSA (1 << 7) >> +#define DSI_MODE_VSYNC_FLUSH (1 << 8) >> +#define DSI_MODE_EOT_PACKET (1 << 9) >> + >> +enum mipi_dsi_pixel_format { >> + DSI_FMT_RGB888, >> + DSI_FMT_RGB666, >> + DSI_FMT_RGB666_PACKED, >> + DSI_FMT_RGB565, >> +}; >> + >> +struct mipi_dsi_interface_params { >> + enum mipi_dsi_pixel_format format; >> + unsigned long mode; >> + unsigned long hs_clk_freq; >> + unsigned long esc_clk_freq; >> + unsigned char data_lanes; >> + unsigned char cmd_allow; >> +}; >> + >> +struct mipi_dsi_bus { >> + struct device *dev; >> + const struct mipi_dsi_bus_ops *ops; >> +}; >> + >> +#define MIPI_DSI_MODULE_PREFIX "mipi-dsi:" >> +#define MIPI_DSI_NAME_SIZE 32 >> + >> +struct mipi_dsi_device_id { >> + char name[MIPI_DSI_NAME_SIZE]; >> + __kernel_ulong_t driver_data /* Data private to the driver */ >> + __aligned(sizeof(__kernel_ulong_t)); >> +}; >> + >> +struct mipi_dsi_device { >> + char name[MIPI_DSI_NAME_SIZE]; >> + int id; >> + struct device dev; >> + >> + const struct mipi_dsi_device_id *id_entry; >> + struct mipi_dsi_bus *bus; >> + struct videomode vm; >> + struct mipi_dsi_interface_params params; >> +}; >> + >> +#define to_mipi_dsi_device(d) container_of(d, struct >> mipi_dsi_device, dev) >> + >> +int mipi_dsi_device_register(struct mipi_dsi_device *dev, >> + struct mipi_dsi_bus *bus); >> +void mipi_dsi_device_unregister(struct mipi_dsi_device *dev); >> + >> +struct mipi_dsi_driver { >> + int(*probe)(struct mipi_dsi_device *); >> + int(*remove)(struct mipi_dsi_device *); >> + struct device_driver driver; >> + const struct mipi_dsi_device_id *id_table; >> +}; >> + >> +#define to_mipi_dsi_driver(d) container_of(d, struct >> mipi_dsi_driver, driver) >> + >> +int mipi_dsi_driver_register(struct mipi_dsi_driver *drv); >> +void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv); >> + >> +static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device >> *dev) >> +{ >> + return dev_get_drvdata(&dev->dev); >> +} >> + >> +static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dev, >> + void *data) >> +{ >> + dev_set_drvdata(&dev->dev, data); >> +} >> + >> +int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus); >> +void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus); >> + >> +/* module_mipi_dsi_driver() - Helper macro for drivers that don't do >> + * anything special in module init/exit. This eliminates a lot of >> + * boilerplate. Each module may only use this macro once, and >> + * calling it replaces module_init() and module_exit() >> + */ >> +#define module_mipi_dsi_driver(__mipi_dsi_driver) \ >> + module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ >> + mipi_dsi_driver_unregister) >> + >> +int mipi_dsi_set_power(struct mipi_dsi_device *dev, bool on); >> +int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on); >> +int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 >> *data, >> + size_t len); >> +int mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd, >> + u8 *data, size_t len); >> + >> +#define mipi_dsi_dcs_write_seq(dev, channel, seq...) \ >> +({\ >> + const u8 d[] = { seq };\ >> + BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too long for >> stack");\ >> + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ >> +}) >> + >> +#define mipi_dsi_dcs_write_static_seq(dev, channel, seq...) \ >> +({\ >> + static const u8 d[] = { seq };\ >> + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ >> +}) >> + >> +#endif /* __MIPI_DSI_BUS__ */ > I may well have missed something, > but I can't see exactly how a command mode > update would be done with this interface. Would this require repeated calls to > .transfer? Such transfers would need to be flagged as requiring > synchronisation with a tearing effect control signal - either the inband > method or a dedicated line. I suspect many hardware implementations will have > a specific method for transferring pixel data in a DSI command mode transfer. > > The command sending period during video mode should probably be configurable > on a per-transfer basis. Some commands have to be synchronised with vertical > blanking, others do not. This could perhaps be combined with a wider > configuration option for a given panel or interface. Similarly, selection of > low power (LP) and high speed (HS) mode on a per-transfer basis can be needed > for some panels. > > Is there a mechanism for controlling ultra-low power state (ULPS) entry? Also, > is there a method for sending arbitrary trigger messages (eg the reset > trigger)? Thanks for the feedback. The current dsi bus implementation was just made to work with the hw I have. It should be extended to be more generic, but I hope now it is just matter of adding good opses and parameters. Feel free to propose new opses. Andrzej > > Thanks, > > Bert. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC PATCH 1/4] mipi-dsi-bus: add MIPI DSI bus support @ 2013-10-10 9:57 ` Andrzej Hajda 0 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-10-10 9:57 UTC (permalink / raw) To: Bert Kenward, Laurent Pinchart Cc: linux-fbdev@vger.kernel.org, Kyungmin Park, dri-devel@lists.freedesktop.org, linux-media@vger.kernel.org On 10/07/2013 12:47 PM, Bert Kenward wrote: > On Tuesday September 24 2013 at 15:23, Andrzej Hajda wrote: >> MIPI DSI is a high-speed serial interface to transmit >> data from/to host to display module. >> >> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> >> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> >> --- >> drivers/video/display/Kconfig | 4 + >> drivers/video/display/Makefile | 1 + >> drivers/video/display/mipi-dsi-bus.c | 332 >> +++++++++++++++++++++++++++++++++++ >> include/video/display.h | 3 + >> include/video/mipi-dsi-bus.h | 144 +++++++++++++++ >> 5 files changed, 484 insertions(+) > <snipped as far as mipi-dsi-bus.h > >> diff --git a/include/video/mipi-dsi-bus.h b/include/video/mipi-dsi-bus.h >> new file mode 100644 >> index 0000000..a78792d >> --- /dev/null >> +++ b/include/video/mipi-dsi-bus.h >> @@ -0,0 +1,144 @@ >> +/* >> + * MIPI DSI Bus >> + * >> + * Copyright (C) 2013, Samsung Electronics, Co., Ltd. >> + * Andrzej Hajda <a.hajda@samsung.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. >> + */ >> + >> +#ifndef __MIPI_DSI_BUS_H__ >> +#define __MIPI_DSI_BUS_H__ >> + >> +#include <linux/device.h> >> +#include <video/videomode.h> >> + >> +struct mipi_dsi_bus; >> +struct mipi_dsi_device; >> + >> +struct mipi_dsi_bus_ops { >> + int (*set_power)(struct mipi_dsi_bus *bus, struct mipi_dsi_device >> *dev, >> + bool on); >> + int (*set_stream)(struct mipi_dsi_bus *bus, struct mipi_dsi_device >> *dev, >> + bool on); >> + int (*transfer)(struct mipi_dsi_bus *bus, struct mipi_dsi_device >> *dev, >> + u8 type, const u8 *tx_buf, size_t tx_len, u8 *rx_buf, >> + size_t rx_len); >> +}; >> + >> +#define DSI_MODE_VIDEO (1 << 0) >> +#define DSI_MODE_VIDEO_BURST (1 << 1) >> +#define DSI_MODE_VIDEO_SYNC_PULSE (1 << 2) >> +#define DSI_MODE_VIDEO_AUTO_VERT (1 << 3) >> +#define DSI_MODE_VIDEO_HSE (1 << 4) >> +#define DSI_MODE_VIDEO_HFP (1 << 5) >> +#define DSI_MODE_VIDEO_HBP (1 << 6) >> +#define DSI_MODE_VIDEO_HSA (1 << 7) >> +#define DSI_MODE_VSYNC_FLUSH (1 << 8) >> +#define DSI_MODE_EOT_PACKET (1 << 9) >> + >> +enum mipi_dsi_pixel_format { >> + DSI_FMT_RGB888, >> + DSI_FMT_RGB666, >> + DSI_FMT_RGB666_PACKED, >> + DSI_FMT_RGB565, >> +}; >> + >> +struct mipi_dsi_interface_params { >> + enum mipi_dsi_pixel_format format; >> + unsigned long mode; >> + unsigned long hs_clk_freq; >> + unsigned long esc_clk_freq; >> + unsigned char data_lanes; >> + unsigned char cmd_allow; >> +}; >> + >> +struct mipi_dsi_bus { >> + struct device *dev; >> + const struct mipi_dsi_bus_ops *ops; >> +}; >> + >> +#define MIPI_DSI_MODULE_PREFIX "mipi-dsi:" >> +#define MIPI_DSI_NAME_SIZE 32 >> + >> +struct mipi_dsi_device_id { >> + char name[MIPI_DSI_NAME_SIZE]; >> + __kernel_ulong_t driver_data /* Data private to the driver */ >> + __aligned(sizeof(__kernel_ulong_t)); >> +}; >> + >> +struct mipi_dsi_device { >> + char name[MIPI_DSI_NAME_SIZE]; >> + int id; >> + struct device dev; >> + >> + const struct mipi_dsi_device_id *id_entry; >> + struct mipi_dsi_bus *bus; >> + struct videomode vm; >> + struct mipi_dsi_interface_params params; >> +}; >> + >> +#define to_mipi_dsi_device(d) container_of(d, struct >> mipi_dsi_device, dev) >> + >> +int mipi_dsi_device_register(struct mipi_dsi_device *dev, >> + struct mipi_dsi_bus *bus); >> +void mipi_dsi_device_unregister(struct mipi_dsi_device *dev); >> + >> +struct mipi_dsi_driver { >> + int(*probe)(struct mipi_dsi_device *); >> + int(*remove)(struct mipi_dsi_device *); >> + struct device_driver driver; >> + const struct mipi_dsi_device_id *id_table; >> +}; >> + >> +#define to_mipi_dsi_driver(d) container_of(d, struct >> mipi_dsi_driver, driver) >> + >> +int mipi_dsi_driver_register(struct mipi_dsi_driver *drv); >> +void mipi_dsi_driver_unregister(struct mipi_dsi_driver *drv); >> + >> +static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device >> *dev) >> +{ >> + return dev_get_drvdata(&dev->dev); >> +} >> + >> +static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dev, >> + void *data) >> +{ >> + dev_set_drvdata(&dev->dev, data); >> +} >> + >> +int of_mipi_dsi_register_devices(struct mipi_dsi_bus *bus); >> +void mipi_dsi_unregister_devices(struct mipi_dsi_bus *bus); >> + >> +/* module_mipi_dsi_driver() - Helper macro for drivers that don't do >> + * anything special in module init/exit. This eliminates a lot of >> + * boilerplate. Each module may only use this macro once, and >> + * calling it replaces module_init() and module_exit() >> + */ >> +#define module_mipi_dsi_driver(__mipi_dsi_driver) \ >> + module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ >> + mipi_dsi_driver_unregister) >> + >> +int mipi_dsi_set_power(struct mipi_dsi_device *dev, bool on); >> +int mipi_dsi_set_stream(struct mipi_dsi_device *dev, bool on); >> +int mipi_dsi_dcs_write(struct mipi_dsi_device *dev, int channel, const u8 >> *data, >> + size_t len); >> +int mipi_dsi_dcs_read(struct mipi_dsi_device *dev, int channel, u8 cmd, >> + u8 *data, size_t len); >> + >> +#define mipi_dsi_dcs_write_seq(dev, channel, seq...) \ >> +({\ >> + const u8 d[] = { seq };\ >> + BUILD_BUG_ON_MSG(ARRAY_SIZE(d) > 64, "DCS sequence too long for >> stack");\ >> + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ >> +}) >> + >> +#define mipi_dsi_dcs_write_static_seq(dev, channel, seq...) \ >> +({\ >> + static const u8 d[] = { seq };\ >> + mipi_dsi_dcs_write(dev, channel, d, ARRAY_SIZE(d));\ >> +}) >> + >> +#endif /* __MIPI_DSI_BUS__ */ > I may well have missed something, > but I can't see exactly how a command mode > update would be done with this interface. Would this require repeated calls to > .transfer? Such transfers would need to be flagged as requiring > synchronisation with a tearing effect control signal - either the inband > method or a dedicated line. I suspect many hardware implementations will have > a specific method for transferring pixel data in a DSI command mode transfer. > > The command sending period during video mode should probably be configurable > on a per-transfer basis. Some commands have to be synchronised with vertical > blanking, others do not. This could perhaps be combined with a wider > configuration option for a given panel or interface. Similarly, selection of > low power (LP) and high speed (HS) mode on a per-transfer basis can be needed > for some panels. > > Is there a mechanism for controlling ultra-low power state (ULPS) entry? Also, > is there a method for sending arbitrary trigger messages (eg the reset > trigger)? Thanks for the feedback. The current dsi bus implementation was just made to work with the hw I have. It should be extended to be more generic, but I hope now it is just matter of adding good opses and parameters. Feel free to propose new opses. Andrzej > > Thanks, > > Bert. ^ permalink raw reply [flat|nested] 14+ messages in thread
* [RFC PATCH 2/4] mipi-dsi-exynos: add driver 2013-09-24 14:23 ` Andrzej Hajda @ 2013-09-24 14:23 ` Andrzej Hajda -1 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park, Tomasz Figa, Donghwa Lee, Sylwester Nawrocki This patch adds mipi-dsi-bus master driver for Exynos chipset family. Signed-off-by: Tomasz Figa <t.figa@samsung.com> Signed-off-by: Donghwa Lee <dh09.lee@samsung.com> Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> --- drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/mipi-dsi-exynos.c | 1310 +++++++++++++++++++++++++++++++ include/video/mipi-dsi-exynos.h | 41 + 4 files changed, 1356 insertions(+) create mode 100644 drivers/video/display/mipi-dsi-exynos.c create mode 100644 include/video/mipi-dsi-exynos.h diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 619b05d..0a1e90b 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -24,6 +24,10 @@ config DISPLAY_MIPI_DSI tristate default n +config DISPLAY_MIPI_DSI_EXYNOS + select DISPLAY_MIPI_DSI + tristate "Samsung SoC MIPI DSI Master" + config DISPLAY_PANEL_DPI tristate "DPI (Parallel) Display Panels" ---help--- diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index b323fd4..2fd84f5 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_DISPLAY_CORE) += display.o obj-$(CONFIG_DISPLAY_CONNECTOR_VGA) += con-vga.o obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o obj-$(CONFIG_DISPLAY_MIPI_DSI) += mipi-dsi-bus.o +obj-$(CONFIG_DISPLAY_MIPI_DSI_EXYNOS) += mipi-dsi-exynos.o obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o diff --git a/drivers/video/display/mipi-dsi-exynos.c b/drivers/video/display/mipi-dsi-exynos.c new file mode 100644 index 0000000..e094744 --- /dev/null +++ b/drivers/video/display/mipi-dsi-exynos.c @@ -0,0 +1,1310 @@ +/* + * Samsung SoC MIPI DSI Master driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * Contacts: Tomasz Figa <t.figa@samsung.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/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/memory.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/spinlock.h> +#include <linux/wait.h> + +#include <video/mipi_display.h> +#include <video/mipi-dsi-bus.h> +#include <video/mipi-dsi-exynos.h> +#include <video/videomode.h> + +#define DSIM_STATUS_REG 0x0 /* Status register */ +#define DSIM_SWRST_REG 0x4 /* Software reset register */ +#define DSIM_CLKCTRL_REG 0x8 /* Clock control register */ +#define DSIM_TIMEOUT_REG 0xc /* Time out register */ +#define DSIM_CONFIG_REG 0x10 /* Configuration register */ +#define DSIM_ESCMODE_REG 0x14 /* Escape mode register */ + +/* Main display image resolution register */ +#define DSIM_MDRESOL_REG 0x18 +#define DSIM_MVPORCH_REG 0x1c /* Main display Vporch register */ +#define DSIM_MHPORCH_REG 0x20 /* Main display Hporch register */ +#define DSIM_MSYNC_REG 0x24 /* Main display sync area register */ + +/* Sub display image resolution register */ +#define DSIM_SDRESOL_REG 0x28 +#define DSIM_INTSRC_REG 0x2c /* Interrupt source register */ +#define DSIM_INTMSK_REG 0x30 /* Interrupt mask register */ +#define DSIM_PKTHDR_REG 0x34 /* Packet Header FIFO register */ +#define DSIM_PAYLOAD_REG 0x38 /* Payload FIFO register */ +#define DSIM_RXFIFO_REG 0x3c /* Read FIFO register */ +#define DSIM_FIFOTHLD_REG 0x40 /* FIFO threshold level register */ +#define DSIM_FIFOCTRL_REG 0x44 /* FIFO status and control register */ + +/* FIFO memory AC characteristic register */ +#define DSIM_PLLCTRL_REG 0x4c /* PLL control register */ +#define DSIM_PLLTMR_REG 0x50 /* PLL timer register */ +#define DSIM_PHYACCHR_REG 0x54 /* D-PHY AC characteristic register */ +#define DSIM_PHYACCHR1_REG 0x58 /* D-PHY AC characteristic register1 */ + +/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) +#define DSIM_PLL_STABLE (1 << 31) + +/* DSIM_SWRST */ +#define DSIM_FUNCRST (1 << 16) +#define DSIM_SWRST (1 << 0) + +/* DSIM_TIMEOUT */ +#define DSIM_LPDR_TOUT(x) ((x) << 0) +#define DSIM_BTA_TOUT(x) ((x) << 16) + +/* DSIM_CLKCTRL */ +#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) +#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) +#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) +#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) +#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) +#define DSIM_BYTE_CLKEN (1 << 24) +#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) +#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) +#define DSIM_PLL_BYPASS (1 << 27) +#define DSIM_ESC_CLKEN (1 << 28) +#define DSIM_TX_REQUEST_HSCLK (1 << 31) + +/* DSIM_CONFIG */ +#define DSIM_LANE_EN_CLK (1 << 0) +#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) +#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) +#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) +#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) +#define DSIM_SUB_VC (((x) & 0x3) << 16) +#define DSIM_MAIN_VC (((x) & 0x3) << 18) +#define DSIM_HSA_MODE (1 << 20) +#define DSIM_HBP_MODE (1 << 21) +#define DSIM_HFP_MODE (1 << 22) +#define DSIM_HSE_MODE (1 << 23) +#define DSIM_AUTO_MODE (1 << 24) +#define DSIM_VIDEO_MODE (1 << 25) +#define DSIM_BURST_MODE (1 << 26) +#define DSIM_SYNC_INFORM (1 << 27) +#define DSIM_EOT_DISABLE (1 << 28) +#define DSIM_MFLUSH_VS (1 << 29) + +/* DSIM_ESCMODE */ +#define DSIM_TX_TRIGGER_RST (1 << 4) +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_BTA (1 << 16) +#define DSIM_FORCE_STOP_STATE (1 << 20) +#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) +#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21) + +/* DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x) (((x) & 0x7ff) << 16) +#define DSIM_MAIN_HRESOL(x) (((x) & 0X7ff) << 0) + +/* DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW(x) ((x) << 28) +#define DSIM_STABLE_VFP(x) ((x) << 16) +#define DSIM_MAIN_VBP(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0) + +/* DSIM_MHPORCH */ +#define DSIM_MAIN_HFP(x) ((x) << 16) +#define DSIM_MAIN_HBP(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0) + +/* DSIM_MSYNC */ +#define DSIM_MAIN_VSA(x) ((x) << 22) +#define DSIM_MAIN_HSA(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0) + +/* DSIM_SDRESOL */ +#define DSIM_SUB_STANDY(x) ((x) << 31) +#define DSIM_SUB_VRESOL(x) ((x) << 16) +#define DSIM_SUB_HRESOL(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0) + +/* DSIM_INTSRC */ +#define DSIM_INT_PLL_STABLE (1 << 31) +#define DSIM_INT_SW_RST_RELEASE (1 << 30) +#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) +#define DSIM_INT_BTA (1 << 25) +#define DSIM_INT_FRAME_DONE (1 << 24) +#define DSIM_INT_RX_TIMEOUT (1 << 21) +#define DSIM_INT_BTA_TIMEOUT (1 << 20) +#define DSIM_INT_RX_DONE (1 << 18) +#define DSIM_INT_RX_TE (1 << 17) +#define DSIM_INT_RX_ACK (1 << 16) +#define DSIM_INT_RX_ECC_ERR (1 << 15) +#define DSIM_INT_RX_CRC_ERR (1 << 14) + +/* DSIM_FIFOCTRL */ +#define DSIM_FULL_H_SFR (1 << 23) + +/* DSIM_PHYACCHR */ +#define DSIM_AFC_EN (1 << 14) +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5) + +/* DSIM_PLLCTRL */ +#define DSIM_FREQ_BAND(x) ((x) << 24) +#define DSIM_PLL_EN (1 << 23) +#define DSIM_PLL_P(x) ((x) << 13) +#define DSIM_PLL_M(x) ((x) << 4) +#define DSIM_PLL_S(x) ((x) << 1) + +#define DSI_MAX_BUS_WIDTH 4 +#define DSI_NUM_VIRTUAL_CHANNELS 4 +#define DSI_TX_FIFO_SIZE 2048 +#define DSI_RX_FIFO_SIZE 256 +#define DSI_XFER_TIMEOUT_MS 100 +#define DSI_RX_FIFO_EMPTY 0x30800002 + +enum exynos_dsi_transfer_type { + EXYNOS_DSI_TX, + EXYNOS_DSI_RX, +}; + +struct exynos_dsi_transfer { + struct list_head list; + struct completion completed; + int result; + u8 type; + u8 data[2]; + + const u8 *tx_payload; + u16 tx_len; + u16 tx_done; + + u8 *rx_payload; + u16 rx_len; + u16 rx_done; +}; + +struct exynos_dsi { + struct mipi_dsi_bus bus; + struct mipi_dsi_interface_params params; + bool streaming; + bool enabled; + + struct platform_device *pdev; + struct phy *phy; + struct device *dev; + struct resource *res; + struct clk *pll_clk; + struct clk *bus_clk; + unsigned int irq; + void __iomem *reg_base; + struct regulator_bulk_data supplies[2]; + struct exynos_dsi_platform_data *pd; + u32 intsrc; + wait_queue_head_t queue; + + spinlock_t transfer_lock; + struct list_head transfer_list; +}; + +#define bus_to_dsi(_bus) container_of(_bus, struct exynos_dsi, bus) + +/* + * H/W control + */ + +static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) +{ + int ret; + + ret = wait_event_timeout(dsi->queue, + (dsi->intsrc & DSIM_INT_SW_RST_RELEASE), + msecs_to_jiffies(300)); + if (ret <= 0) + dev_err(dsi->dev, "timeout waiting for reset\n"); + + dsi->intsrc = 0; +} + +static void exynos_dsi_reset(struct exynos_dsi *dsi) +{ + writel(DSIM_SWRST, dsi->reg_base + DSIM_SWRST_REG); +} + +#ifndef MHZ +#define MHZ (1000*1000) +#endif + +static const unsigned long freq_bands[] = { + 100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ, + 270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ, + 510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ, + 770 * MHZ, 870 * MHZ, 950 * MHZ, +}; + +static const int afc_settings[] = { + 1, 0, 3, 2, 5, 4, +}; + +static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi, + unsigned long fin, unsigned long fout, u8 *p, u16 *m, u8 *s) +{ + unsigned long best_freq = 0; + u32 min_delta = 0xffffffff; + u8 p_min, p_max; + u8 _p, uninitialized_var(best_p); + u16 _m, uninitialized_var(best_m); + u8 _s, uninitialized_var(best_s); + + p_min = DIV_ROUND_UP(fin, (12 * MHZ)); + p_max = fin / (6 * MHZ); + + for (_p = p_min; _p <= p_max; ++_p) { + for (_s = 0; _s <= 5; ++_s) { + u64 tmp; + u32 delta; + u16 div; + + tmp = (u64)fout * (_p << _s); + do_div(tmp, fin); + _m = tmp; + if (_m < 41 || _m > 125) + continue; + + tmp = (u64)_m * fin; + do_div(tmp, _p); + if (tmp < 500 * MHZ || tmp > 1000 * MHZ) + continue; + + tmp = (u64)_m * fin; + div = (_p << _s); + do_div(tmp, div); + + delta = abs(fout - tmp); + if (delta < min_delta) { + best_p = _p; + best_m = _m; + best_s = _s; + min_delta = delta; + best_freq = tmp; + } + } + } + + if (best_freq) { + *p = best_p; + *m = best_m; + *s = best_s; + } + + return best_freq; +} + +static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi, + unsigned long freq) +{ + unsigned long fin, fout, fin_pll; + int timeout; + u8 p; + u16 m; + u8 s; + int band; + int afc; + u32 reg; + + clk_set_rate(dsi->pll_clk, dsi->pd->pll_clk_rate); + + fin = clk_get_rate(dsi->pll_clk); + if (!fin) { + dev_err(dsi->dev, "failed to get PLL clock frequency\n"); + return 0; + } + + dev_dbg(dsi->dev, "PLL input frequency: %lu\n", fin); + + fout = exynos_dsi_pll_find_pms(dsi, fin, freq, &p, &m, &s); + if (!fout) { + dev_err(dsi->dev, + "failed to find PLL coefficients for requested frequency\n"); + return -EFAULT; + } + + dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s); + + for (band = 0; band < ARRAY_SIZE(freq_bands); ++band) + if (fout < freq_bands[band]) + break; + + fin_pll = DIV_ROUND_CLOSEST(fin, p); + fin_pll /= MHZ; + if (fin_pll > 6) + fin_pll -= 6; + else + fin_pll = 0; + if (fin_pll >= ARRAY_SIZE(afc_settings)) + fin_pll = ARRAY_SIZE(afc_settings) - 1; + + afc = afc_settings[fin_pll]; + + dev_dbg(dsi->dev, "freq band %d, afc_setting %d\n", band, afc); + + writel(dsi->pd->pll_stable_time, dsi->reg_base + DSIM_PLLTMR_REG); + + reg = DSIM_AFC_CTL(afc) | DSIM_AFC_EN; + writel(reg, dsi->reg_base + DSIM_PHYACCHR_REG); + + reg = DSIM_FREQ_BAND(band) | DSIM_PLL_EN + | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s); + writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG); + + timeout = 1000; + do { + if (timeout-- = 0) { + dev_err(dsi->dev, "PLL failed to stabilize\n"); + return -EFAULT; + } + reg = readl(dsi->reg_base + DSIM_STATUS_REG); + } while ((reg & DSIM_PLL_STABLE) = 0); + + return fout; +} + +static int exynos_dsi_enable_clock(struct exynos_dsi *dsi) +{ + struct mipi_dsi_interface_params *params = &dsi->params; + unsigned long hs_clk, byte_clk, esc_clk; + unsigned long esc_div; + u32 reg; + + hs_clk = exynos_dsi_set_pll(dsi, params->hs_clk_freq); + if (!hs_clk) { + dev_err(dsi->dev, "failed to configure DSI PLL\n"); + return -EFAULT; + } + + byte_clk = hs_clk / 8; + esc_div = DIV_ROUND_UP(byte_clk, params->esc_clk_freq); + esc_clk = byte_clk / esc_div; + + if (esc_clk > 20 * MHZ) { + ++esc_div; + esc_clk = byte_clk / esc_div; + } + + dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n", + hs_clk, byte_clk, esc_clk); + + reg = readl(dsi->reg_base + DSIM_CLKCTRL_REG); + reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK + | DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS + | DSIM_BYTE_CLK_SRC_MASK); + reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN + | DSIM_ESC_PRESCALER(esc_div) + | DSIM_LANE_ESC_CLK_EN_CLK + | DSIM_LANE_ESC_CLK_EN_DATA(params->data_lanes) + | DSIM_BYTE_CLK_SRC(0) + | DSIM_TX_REQUEST_HSCLK; + writel(reg, dsi->reg_base + DSIM_CLKCTRL_REG); + + return 0; +} + +static void exynos_dsi_disable_clock(struct exynos_dsi *dsi) +{ + u32 reg; + + reg = readl(dsi->reg_base + DSIM_CLKCTRL_REG); + reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK | + DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN); + writel(reg, dsi->reg_base + DSIM_CLKCTRL_REG); + + reg = readl(dsi->reg_base + DSIM_PLLCTRL_REG); + reg &= ~DSIM_PLL_EN; + writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG); +} + +static int exynos_dsi_init_link(struct exynos_dsi *dsi) +{ + struct mipi_dsi_interface_params *params = &dsi->params; + int timeout; + u32 reg; + + /* Initialize FIFO pointers */ + reg = readl(dsi->reg_base + DSIM_FIFOCTRL_REG); + reg &= ~0x1f; + writel(reg, dsi->reg_base + DSIM_FIFOCTRL_REG); + + usleep_range(10000, 10000); + + reg |= 0x1f; + writel(reg, dsi->reg_base + DSIM_FIFOCTRL_REG); + + usleep_range(10000, 10000); + + /* DSI configuration */ + reg = 0; + + if (params->mode & DSI_MODE_VIDEO) { + reg |= DSIM_VIDEO_MODE; + + if (!(params->mode & DSI_MODE_VSYNC_FLUSH)) + reg |= DSIM_MFLUSH_VS; + if (!(params->mode & DSI_MODE_EOT_PACKET)) + reg |= DSIM_EOT_DISABLE; + if (params->mode & DSI_MODE_VIDEO_SYNC_PULSE) + reg |= DSIM_SYNC_INFORM; + if (params->mode & DSI_MODE_VIDEO_BURST) + reg |= DSIM_BURST_MODE; + if (params->mode & DSI_MODE_VIDEO_AUTO_VERT) + reg |= DSIM_AUTO_MODE; + if (params->mode & DSI_MODE_VIDEO_HSE) + reg |= DSIM_HSE_MODE; + if (!(params->mode & DSI_MODE_VIDEO_HFP)) + reg |= DSIM_HFP_MODE; + if (!(params->mode & DSI_MODE_VIDEO_HBP)) + reg |= DSIM_HBP_MODE; + if (!(params->mode & DSI_MODE_VIDEO_HSA)) + reg |= DSIM_HSA_MODE; + } + + switch (params->format) { + case DSI_FMT_RGB888: + reg |= DSIM_MAIN_PIX_FORMAT_RGB888; + break; + case DSI_FMT_RGB666: + reg |= DSIM_MAIN_PIX_FORMAT_RGB666; + break; + case DSI_FMT_RGB666_PACKED: + reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P; + break; + case DSI_FMT_RGB565: + reg |= DSIM_MAIN_PIX_FORMAT_RGB565; + break; + default: + dev_err(dsi->dev, "invalid pixel format\n"); + return -EINVAL; + } + + switch (params->data_lanes) { + case 0x1: + reg |= DSIM_NUM_OF_DATA_LANE(0); + break; + case 0x3: + reg |= DSIM_NUM_OF_DATA_LANE(1); + break; + case 0x7: + reg |= DSIM_NUM_OF_DATA_LANE(2); + break; + case 0xf: + reg |= DSIM_NUM_OF_DATA_LANE(3); + break; + default: + return -EINVAL; + } + + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + reg |= DSIM_LANE_EN_CLK; + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + reg |= DSIM_LANE_EN(params->data_lanes); + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + /* Check clock and data lane state are stop state */ + timeout = 100; + do { + if (timeout-- = 0) { + dev_err(dsi->dev, "waiting for bus lanes timed out\n"); + return -EFAULT; + } + + reg = readl(dsi->reg_base + DSIM_STATUS_REG); + if ((reg & DSIM_STOP_STATE_DAT(params->data_lanes)) + != DSIM_STOP_STATE_DAT(params->data_lanes)) + continue; + } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK))); + + reg = readl(dsi->reg_base + DSIM_ESCMODE_REG); + reg &= ~DSIM_STOP_STATE_CNT_MASK; + reg |= DSIM_STOP_STATE_CNT(dsi->pd->stop_holding_cnt); + writel(reg, dsi->reg_base + DSIM_ESCMODE_REG); + + reg = DSIM_BTA_TOUT(dsi->pd->bta_timeout) + | DSIM_LPDR_TOUT(dsi->pd->rx_timeout); + writel(reg, dsi->reg_base + DSIM_TIMEOUT_REG); + + return 0; +} + +static int exynos_dsi_set_display_mode(struct exynos_dsi *dsi, + const struct videomode *mode) +{ + struct mipi_dsi_interface_params *params = &dsi->params; + u32 reg; + + if (params->mode & DSI_MODE_VIDEO) { + reg = DSIM_CMD_ALLOW(params->cmd_allow) + | DSIM_STABLE_VFP(mode->vfront_porch) + | DSIM_MAIN_VBP(mode->vback_porch); + writel(reg, dsi->reg_base + DSIM_MVPORCH_REG); + + reg = DSIM_MAIN_HFP(mode->hfront_porch) + | DSIM_MAIN_HBP(mode->hback_porch); + writel(reg, dsi->reg_base + DSIM_MHPORCH_REG); + + reg = DSIM_MAIN_VSA(mode->vsync_len) + | DSIM_MAIN_HSA(mode->hsync_len); + writel(reg, dsi->reg_base + DSIM_MSYNC_REG); + } + + reg = DSIM_MAIN_HRESOL(mode->hactive) | DSIM_MAIN_VRESOL(mode->vactive); + writel(reg, dsi->reg_base + DSIM_MDRESOL_REG); + + dev_dbg(dsi->dev, "LCD width = %d, height = %d\n", + mode->hactive, mode->vactive); + + return 0; +} + +static void exynos_dsi_set_display_enable(struct exynos_dsi *dsi, bool enable) +{ + u32 reg; + + reg = readl(dsi->reg_base + DSIM_MDRESOL_REG); + if (enable) + reg |= DSIM_MAIN_STAND_BY; + else + reg &= ~DSIM_MAIN_STAND_BY; + writel(reg, dsi->reg_base + DSIM_MDRESOL_REG); +} + +/* + * FIFO + */ + +static int exynos_dsi_wait_for_hdr_fifo(struct exynos_dsi *dsi) +{ + int timeout = 20000; + + do { + u32 reg = readl(dsi->reg_base + DSIM_FIFOCTRL_REG); + + if (!(reg & DSIM_FULL_H_SFR)) + return 0; + + if (!cond_resched()) + usleep_range(950, 1050); + } while (--timeout); + + return -ETIMEDOUT; +} + +static void exynos_dsi_send_to_fifo(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + const u8 *payload = xfer->tx_payload + xfer->tx_done; + u16 length = xfer->tx_len - xfer->tx_done; + bool first = !xfer->tx_done; + u32 reg; + + dev_dbg(dsi->dev, + "< xfer %p, tx_len %u, tx_done %u, rx_len %u, rx_done %u\n", + xfer, xfer->tx_len, xfer->tx_done, xfer->rx_len, xfer->rx_done); + + if (length > DSI_TX_FIFO_SIZE) + length = DSI_TX_FIFO_SIZE; + + xfer->tx_done += length; + + /* Send payload */ + while (length >= 4) { + reg = (payload[3] << 24) | (payload[2] << 16) + | (payload[1] << 8) | payload[0]; + writel(reg, dsi->reg_base + DSIM_PAYLOAD_REG); + payload += 4; + length -= 4; + } + + reg = 0; + switch (length) { + case 3: + reg |= payload[2] << 16; + /* Fall through */ + case 2: + reg |= payload[1] << 8; + /* Fall through */ + case 1: + reg |= payload[0]; + writel(reg, dsi->reg_base + DSIM_PAYLOAD_REG); + break; + case 0: + /* Do nothing */ + break; + } + + /* Send packet header */ + if (!first) + return; + + if (xfer->rx_len) { + reg = (xfer->rx_len << 8) + | MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE; + if (exynos_dsi_wait_for_hdr_fifo(dsi)) { + dev_err(dsi->dev, + "waiting for header FIFO timed out\n"); + return; + } + writel(reg, dsi->reg_base + DSIM_PKTHDR_REG); + } + + reg = (xfer->data[1] << 16) | (xfer->data[0] << 8) | xfer->type; + if (exynos_dsi_wait_for_hdr_fifo(dsi)) { + dev_err(dsi->dev, "waiting for header FIFO timed out\n"); + return; + } + writel(reg, dsi->reg_base + DSIM_PKTHDR_REG); +} + +static void exynos_dsi_read_from_fifo(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + u8 *payload = xfer->rx_payload + xfer->rx_done; + bool first = !xfer->rx_done; + u16 length; + u32 reg; + + if (first) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + + switch (reg & 0x3f) { + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + if (xfer->rx_len >= 2) { + payload[1] = reg >> 16; + ++xfer->rx_done; + } + /* Fall through */ + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + payload[0] = reg >> 8; + ++xfer->rx_done; + xfer->rx_len = xfer->rx_done; + xfer->result = 0; + goto clear_fifo; + } + + length = (reg >> 8) & 0xffff; + if (length > xfer->rx_len) { + dev_err(dsi->dev, + "response too long (expected %u, got %u bytes)\n", + xfer->rx_len, length); + xfer->rx_len = 0; + xfer->rx_done = 0; + xfer->result = -EFAULT; + goto clear_fifo; + } + if (length < xfer->rx_len) + xfer->rx_len = length; + } + + length = xfer->rx_len - xfer->rx_done; + xfer->rx_done += length; + + /* Receive payload */ + while (length >= 4) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + payload[0] = (reg >> 0) & 0xff; + payload[1] = (reg >> 8) & 0xff; + payload[2] = (reg >> 16) & 0xff; + payload[3] = (reg >> 24) & 0xff; + payload += 4; + length -= 4; + } + + if (length) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + switch (length) { + case 3: + payload[2] = (reg >> 16) & 0xff; + /* Fall through */ + case 2: + payload[1] = (reg >> 8) & 0xff; + /* Fall through */ + case 1: + payload[0] = reg & 0xff; + } + } + + if (xfer->rx_done = xfer->rx_len) + xfer->result = 0; + +clear_fifo: + length = DSI_RX_FIFO_SIZE / 4; + do { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + if (reg = DSI_RX_FIFO_EMPTY) + break; + } while (--length); +} + +/* + * Transfer + */ + +static void exynos_dsi_transfer_start(struct exynos_dsi *dsi) +{ + unsigned long flags; + struct exynos_dsi_transfer *xfer; + bool start = false; + +again: + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (list_empty(&dsi->transfer_list)) { + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + return; + } + + xfer = list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (xfer->tx_len && xfer->tx_done = xfer->tx_len) + /* waiting for RX */ + return; + + exynos_dsi_send_to_fifo(dsi, xfer); + + if (xfer->tx_len || xfer->rx_len) + return; + + xfer->result = 0; + complete(&xfer->completed); + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (start) + goto again; +} + +static bool exynos_dsi_transfer_finish(struct exynos_dsi *dsi) +{ + struct exynos_dsi_transfer *xfer; + unsigned long flags; + bool start = true; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (list_empty(&dsi->transfer_list)) { + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + return false; + } + + xfer = list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + dev_dbg(dsi->dev, + "> xfer %p, tx_len %u, tx_done %u, rx_len %u, rx_done %u\n", + xfer, xfer->tx_len, xfer->tx_done, xfer->rx_len, xfer->rx_done); + + if (xfer->tx_done != xfer->tx_len) + return true; + + if (xfer->rx_done != xfer->rx_len) + exynos_dsi_read_from_fifo(dsi, xfer); + + if (xfer->rx_done != xfer->rx_len) + return true; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (!xfer->rx_len) + xfer->result = 0; + complete(&xfer->completed); + + return start; +} + +static void exynos_dsi_remove_transfer(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + unsigned long flags; + bool start; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (!list_empty(&dsi->transfer_list) + && xfer = list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list)) { + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + if (start) + exynos_dsi_transfer_start(dsi); + return; + } + + list_del_init(&xfer->list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); +} + +static int exynos_dsi_transfer(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + unsigned long flags; + bool stopped; + + xfer->tx_done = 0; + xfer->rx_done = 0; + xfer->result = -ETIMEDOUT; + init_completion(&xfer->completed); + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + stopped = list_empty(&dsi->transfer_list); + list_add_tail(&xfer->list, &dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (stopped) + exynos_dsi_transfer_start(dsi); + + wait_for_completion_timeout(&xfer->completed, + msecs_to_jiffies(DSI_XFER_TIMEOUT_MS)); + if (xfer->result = -ETIMEDOUT) { + exynos_dsi_remove_transfer(dsi, xfer); + dev_err(dsi->dev, "xfer timed out\n"); + return -ETIMEDOUT; + } + + /* Also covers hardware timeout condition */ + return xfer->result; +} + +/* + * Interrupt handler + */ + +static irqreturn_t exynos_dsi_irq(int irq, void *dev_id) +{ + struct exynos_dsi *dsi = dev_id; + u32 status; + + status = readl(dsi->reg_base + DSIM_INTSRC_REG); + if (!status) { + static unsigned long int j; + if (printk_timed_ratelimit(&j, 500)) + dev_warn(dsi->dev, "spurious interrupt\n"); + return IRQ_HANDLED; + } + writel(status, dsi->reg_base + DSIM_INTSRC_REG); + + dev_dbg(dsi->dev, "%s: status = %08x\n", __func__, status); + + if (status & DSIM_INT_SW_RST_RELEASE) { + u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY); + writel(mask, dsi->reg_base + DSIM_INTMSK_REG); + dsi->intsrc = status; + wake_up(&dsi->queue); + return IRQ_HANDLED; + } + + if (exynos_dsi_transfer_finish(dsi)) + exynos_dsi_transfer_start(dsi); + + return IRQ_HANDLED; +} + +static int exynos_dsi_set_stream(struct mipi_dsi_bus *bus, + struct mipi_dsi_device *dev, bool on) +{ + struct exynos_dsi *dsi = bus_to_dsi(bus); + + if (!dsi->enabled) + return -EINVAL; + + if (on) + exynos_dsi_set_display_mode(dsi, &dev->vm); + exynos_dsi_set_display_enable(dsi, on); + dsi->streaming = on; + + return 0; +} + +/* enable/disable dsi bus */ +static int exynos_dsi_enable(struct exynos_dsi *dsi) +{ + int ret; + + if (dsi->enabled) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret < 0) + return ret; + + clk_prepare_enable(dsi->bus_clk); + clk_prepare_enable(dsi->pll_clk); + + phy_power_on(dsi->phy); + + exynos_dsi_enable_clock(dsi); + + dsi->intsrc = 0; + exynos_dsi_reset(dsi); + enable_irq(dsi->irq); + exynos_dsi_wait_for_reset(dsi); + + exynos_dsi_init_link(dsi); + + dsi->enabled = true; + + return 0; +} + +static int exynos_dsi_disable(struct exynos_dsi *dsi) +{ + int ret; + + if (!dsi->enabled) + return 0; + + if (dsi->streaming) + return -EBUSY; + + dsi->enabled = false; + + exynos_dsi_disable_clock(dsi); + + disable_irq(dsi->irq); + + phy_power_off(dsi->phy); + + clk_disable_unprepare(dsi->pll_clk); + clk_disable_unprepare(dsi->bus_clk); + + ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret < 0) + return ret; + + return 0; +} + +static int exynos_dsi_set_power(struct mipi_dsi_bus *bus, + struct mipi_dsi_device *dev, bool on) +{ + struct exynos_dsi *dsi = bus_to_dsi(bus); + + if (on) { + dsi->params = dev->params; + return exynos_dsi_enable(dsi); + } else { + return exynos_dsi_disable(dsi); + } +} + +static bool exynos_dsi_is_short_dsi_type(u8 type) +{ + return ((type & 0x0f) <= 8); +} + +static int exynos_dsi_bus_transfer(struct mipi_dsi_bus *bus, + struct mipi_dsi_device *dev, u8 type, + const u8 *tx_buf, size_t tx_len, + u8 *rx_buf, size_t rx_len) +{ + struct exynos_dsi *dsi = bus_to_dsi(bus); + struct exynos_dsi_transfer xfer; + + if (!tx_len) + return -EINVAL; + + xfer.type = type; + + if (exynos_dsi_is_short_dsi_type(type)) { + if (tx_len > 2) + return -EINVAL; + xfer.tx_len = 0; + xfer.data[0] = tx_buf[0]; + xfer.data[1] = (tx_len = 2) ? tx_buf[1] : 0; + } else { + xfer.tx_len = tx_len; + xfer.data[0] = tx_len & 0xff; + xfer.data[1] = tx_len >> 8; + xfer.tx_payload = tx_buf; + } + + xfer.rx_len = rx_len; + xfer.rx_payload = rx_buf; + + return exynos_dsi_transfer(dsi, &xfer); +} + +static const struct mipi_dsi_bus_ops exynos_dsi_ops = { + .set_power = exynos_dsi_set_power, + .set_stream = exynos_dsi_set_stream, + .transfer = exynos_dsi_bus_transfer, +}; + +#ifdef CONFIG_OF +/* + * Device Tree + */ + +static struct exynos_dsi_platform_data *exynos_dsi_parse_dt( + struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct exynos_dsi_platform_data *dsi_pd; + struct device *dev = &pdev->dev; + const __be32 *prop_data; + + dsi_pd = kzalloc(sizeof(*dsi_pd), GFP_KERNEL); + if (!dsi_pd) { + dev_err(dev, "failed to allocate dsi platform data\n"); + return NULL; + } + + prop_data = of_get_property(node, "samsung,pll-stable-time", NULL); + if (!prop_data) { + dev_err(dev, "failed to get pll-stable-time property\n"); + goto err_free_pd; + } + dsi_pd->pll_stable_time = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "samsung,stop-holding-count", NULL); + if (!prop_data) { + dev_err(dev, "failed to get stop-holding-count property\n"); + goto err_free_pd; + } + dsi_pd->stop_holding_cnt = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "samsung,bta-timeout", NULL); + if (!prop_data) { + dev_err(dev, "failed to get bta-timeout property\n"); + goto err_free_pd; + } + dsi_pd->bta_timeout = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "samsung,rx-timeout", NULL); + if (!prop_data) { + dev_err(dev, "failed to get rx-timeout property\n"); + goto err_free_pd; + } + dsi_pd->rx_timeout = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "samsung,pll-clk-freq", NULL); + if (!prop_data) { + dev_err(dev, "failed to get pll-clk-freq property\n"); + goto err_free_pd; + } + dsi_pd->pll_clk_rate = be32_to_cpu(*prop_data); + + return dsi_pd; + +err_free_pd: + kfree(dsi_pd); + + return NULL; +} + +static struct of_device_id exynos_dsi_of_match[] = { + { .compatible = "samsung,exynos4210-mipi-dsi" }, + { } +}; + +MODULE_DEVICE_TABLE(of, exynos_dsi_of_match); +#else +static struct exynos_dsi_platform_data *exynos_dsi_parse_dt( + struct platform_device *pdev) +{ + return NULL; +} +#endif + +/* + * Platform driver + */ + +static int exynos_dsi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct exynos_dsi *dsi; + int ret; + + dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) { + dev_err(&pdev->dev, "failed to allocate dsi object.\n"); + return -ENOMEM; + } + + init_waitqueue_head(&dsi->queue); + spin_lock_init(&dsi->transfer_lock); + INIT_LIST_HEAD(&dsi->transfer_list); + + dsi->bus.ops = &exynos_dsi_ops; + dsi->bus.dev = &pdev->dev; + + dsi->pdev = pdev; + dsi->dev = &pdev->dev; + dsi->pd = pdev->dev.platform_data; + + if (dsi->pd = NULL && pdev->dev.of_node) + dsi->pd = exynos_dsi_parse_dt(pdev); + + if (dsi->pd = NULL) { + dev_err(&pdev->dev, "failed to get platform data for dsi.\n"); + return -EINVAL; + } + + dsi->supplies[0].supply = "vdd11"; + dsi->supplies[1].supply = "vdd18"; + ret = devm_regulator_bulk_get(&pdev->dev, + ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret) { + dev_err(&pdev->dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + dsi->pll_clk = devm_clk_get(&pdev->dev, "pll_clk"); + if (IS_ERR(dsi->pll_clk)) { + dev_err(&pdev->dev, "failed to get dsi pll input clock\n"); + return -ENODEV; + } + + dsi->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); + if (IS_ERR(dsi->bus_clk)) { + dev_err(&pdev->dev, "failed to get dsi bus clock\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get io memory region\n"); + return -ENODEV; + } + + dsi->reg_base = devm_request_and_ioremap(&pdev->dev, res); + if (!dsi->reg_base) { + dev_err(&pdev->dev, "failed to remap io region\n"); + return -ENOMEM; + } + + dsi->phy = devm_phy_get(&pdev->dev, "dsim"); + if (IS_ERR(dsi->phy)) + return PTR_ERR(dsi->phy); + + platform_set_drvdata(pdev, dsi); + + dsi->irq = platform_get_irq(pdev, 0); + if (dsi->irq < 0) { + dev_err(&pdev->dev, "failed to request dsi irq resource\n"); + return -EINVAL; + } + + irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, dsi->irq, NULL, + exynos_dsi_irq, IRQF_ONESHOT, dev_name(&pdev->dev), dsi); + if (ret) { + dev_err(&pdev->dev, "failed to request dsi irq\n"); + return ret; + } + + return of_mipi_dsi_register_devices(&dsi->bus); +} + +static int exynos_dsi_remove(struct platform_device *pdev) +{ + struct exynos_dsi *dsi = dev_get_drvdata(&pdev->dev); + + mipi_dsi_unregister_devices(&dsi->bus); + + return 0; +} +/* + * Power management + */ + +#ifdef CONFIG_PM_SLEEP +static int exynos_dsi_suspend(struct device *dev) +{ + struct exynos_dsi *dsi = dev_get_drvdata(dev); + + return exynos_dsi_disable(dsi); +} + +static int exynos_dsi_resume(struct device *dev) +{ + struct exynos_dsi *dsi = dev_get_drvdata(dev); + + return exynos_dsi_enable(dsi); +} +#endif + +static const struct dev_pm_ops exynos_dsi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume) +}; + +/* + * Module + */ + +static struct platform_driver exynos_dsi_driver = { + .probe = exynos_dsi_probe, + .remove = exynos_dsi_remove, + .driver = { + .name = "exynos-dsi", + .owner = THIS_MODULE, + .pm = &exynos_dsi_pm_ops, + .of_match_table = of_match_ptr(exynos_dsi_of_match), + }, +}; + +module_platform_driver(exynos_dsi_driver); + +MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>"); +MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); +MODULE_LICENSE("GPL"); diff --git a/include/video/mipi-dsi-exynos.h b/include/video/mipi-dsi-exynos.h new file mode 100644 index 0000000..548c473 --- /dev/null +++ b/include/video/mipi-dsi-exynos.h @@ -0,0 +1,41 @@ +/* include/video/mipi-dsi-exynos.h + * + * Platform data header for Samsung SoC MIPI-DSIM. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae <inki.dae@samsung.com> + * Donghwa Lee <dh09.lee@samsung.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. +*/ + +#ifndef _MIPI_DSI_EXYNOS_H +#define _MIPI_DSI_EXYNOS_H + +#include <linux/device.h> + +/* + * struct exynos_dsi_platform_data - interface to platform data + * for mipi-dsi driver. + * + * TODO + */ +struct exynos_dsi_platform_data { + unsigned int enabled; + + int (*phy_enable)(struct platform_device *pdev, bool on); + + unsigned int pll_stable_time; + unsigned long pll_clk_rate; + unsigned long esc_clk_rate; + unsigned short stop_holding_cnt; + unsigned char bta_timeout; + unsigned short rx_timeout; +}; + +int s5p_dsim_phy_enable(struct platform_device *pdev, bool on); + +#endif /* _MIPI_DSI_EXYNOS_H */ -- 1.8.1.2 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC PATCH 2/4] mipi-dsi-exynos: add driver @ 2013-09-24 14:23 ` Andrzej Hajda 0 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park, Tomasz Figa, Donghwa Lee, Sylwester Nawrocki This patch adds mipi-dsi-bus master driver for Exynos chipset family. Signed-off-by: Tomasz Figa <t.figa@samsung.com> Signed-off-by: Donghwa Lee <dh09.lee@samsung.com> Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> --- drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 1 + drivers/video/display/mipi-dsi-exynos.c | 1310 +++++++++++++++++++++++++++++++ include/video/mipi-dsi-exynos.h | 41 + 4 files changed, 1356 insertions(+) create mode 100644 drivers/video/display/mipi-dsi-exynos.c create mode 100644 include/video/mipi-dsi-exynos.h diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 619b05d..0a1e90b 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -24,6 +24,10 @@ config DISPLAY_MIPI_DSI tristate default n +config DISPLAY_MIPI_DSI_EXYNOS + select DISPLAY_MIPI_DSI + tristate "Samsung SoC MIPI DSI Master" + config DISPLAY_PANEL_DPI tristate "DPI (Parallel) Display Panels" ---help--- diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index b323fd4..2fd84f5 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_DISPLAY_CORE) += display.o obj-$(CONFIG_DISPLAY_CONNECTOR_VGA) += con-vga.o obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o obj-$(CONFIG_DISPLAY_MIPI_DSI) += mipi-dsi-bus.o +obj-$(CONFIG_DISPLAY_MIPI_DSI_EXYNOS) += mipi-dsi-exynos.o obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o diff --git a/drivers/video/display/mipi-dsi-exynos.c b/drivers/video/display/mipi-dsi-exynos.c new file mode 100644 index 0000000..e094744 --- /dev/null +++ b/drivers/video/display/mipi-dsi-exynos.c @@ -0,0 +1,1310 @@ +/* + * Samsung SoC MIPI DSI Master driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * Contacts: Tomasz Figa <t.figa@samsung.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/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/memory.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/spinlock.h> +#include <linux/wait.h> + +#include <video/mipi_display.h> +#include <video/mipi-dsi-bus.h> +#include <video/mipi-dsi-exynos.h> +#include <video/videomode.h> + +#define DSIM_STATUS_REG 0x0 /* Status register */ +#define DSIM_SWRST_REG 0x4 /* Software reset register */ +#define DSIM_CLKCTRL_REG 0x8 /* Clock control register */ +#define DSIM_TIMEOUT_REG 0xc /* Time out register */ +#define DSIM_CONFIG_REG 0x10 /* Configuration register */ +#define DSIM_ESCMODE_REG 0x14 /* Escape mode register */ + +/* Main display image resolution register */ +#define DSIM_MDRESOL_REG 0x18 +#define DSIM_MVPORCH_REG 0x1c /* Main display Vporch register */ +#define DSIM_MHPORCH_REG 0x20 /* Main display Hporch register */ +#define DSIM_MSYNC_REG 0x24 /* Main display sync area register */ + +/* Sub display image resolution register */ +#define DSIM_SDRESOL_REG 0x28 +#define DSIM_INTSRC_REG 0x2c /* Interrupt source register */ +#define DSIM_INTMSK_REG 0x30 /* Interrupt mask register */ +#define DSIM_PKTHDR_REG 0x34 /* Packet Header FIFO register */ +#define DSIM_PAYLOAD_REG 0x38 /* Payload FIFO register */ +#define DSIM_RXFIFO_REG 0x3c /* Read FIFO register */ +#define DSIM_FIFOTHLD_REG 0x40 /* FIFO threshold level register */ +#define DSIM_FIFOCTRL_REG 0x44 /* FIFO status and control register */ + +/* FIFO memory AC characteristic register */ +#define DSIM_PLLCTRL_REG 0x4c /* PLL control register */ +#define DSIM_PLLTMR_REG 0x50 /* PLL timer register */ +#define DSIM_PHYACCHR_REG 0x54 /* D-PHY AC characteristic register */ +#define DSIM_PHYACCHR1_REG 0x58 /* D-PHY AC characteristic register1 */ + +/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) +#define DSIM_PLL_STABLE (1 << 31) + +/* DSIM_SWRST */ +#define DSIM_FUNCRST (1 << 16) +#define DSIM_SWRST (1 << 0) + +/* DSIM_TIMEOUT */ +#define DSIM_LPDR_TOUT(x) ((x) << 0) +#define DSIM_BTA_TOUT(x) ((x) << 16) + +/* DSIM_CLKCTRL */ +#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) +#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) +#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) +#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) +#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) +#define DSIM_BYTE_CLKEN (1 << 24) +#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) +#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) +#define DSIM_PLL_BYPASS (1 << 27) +#define DSIM_ESC_CLKEN (1 << 28) +#define DSIM_TX_REQUEST_HSCLK (1 << 31) + +/* DSIM_CONFIG */ +#define DSIM_LANE_EN_CLK (1 << 0) +#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) +#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) +#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) +#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) +#define DSIM_SUB_VC (((x) & 0x3) << 16) +#define DSIM_MAIN_VC (((x) & 0x3) << 18) +#define DSIM_HSA_MODE (1 << 20) +#define DSIM_HBP_MODE (1 << 21) +#define DSIM_HFP_MODE (1 << 22) +#define DSIM_HSE_MODE (1 << 23) +#define DSIM_AUTO_MODE (1 << 24) +#define DSIM_VIDEO_MODE (1 << 25) +#define DSIM_BURST_MODE (1 << 26) +#define DSIM_SYNC_INFORM (1 << 27) +#define DSIM_EOT_DISABLE (1 << 28) +#define DSIM_MFLUSH_VS (1 << 29) + +/* DSIM_ESCMODE */ +#define DSIM_TX_TRIGGER_RST (1 << 4) +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_BTA (1 << 16) +#define DSIM_FORCE_STOP_STATE (1 << 20) +#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) +#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21) + +/* DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x) (((x) & 0x7ff) << 16) +#define DSIM_MAIN_HRESOL(x) (((x) & 0X7ff) << 0) + +/* DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW(x) ((x) << 28) +#define DSIM_STABLE_VFP(x) ((x) << 16) +#define DSIM_MAIN_VBP(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0) + +/* DSIM_MHPORCH */ +#define DSIM_MAIN_HFP(x) ((x) << 16) +#define DSIM_MAIN_HBP(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0) + +/* DSIM_MSYNC */ +#define DSIM_MAIN_VSA(x) ((x) << 22) +#define DSIM_MAIN_HSA(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0) + +/* DSIM_SDRESOL */ +#define DSIM_SUB_STANDY(x) ((x) << 31) +#define DSIM_SUB_VRESOL(x) ((x) << 16) +#define DSIM_SUB_HRESOL(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0) + +/* DSIM_INTSRC */ +#define DSIM_INT_PLL_STABLE (1 << 31) +#define DSIM_INT_SW_RST_RELEASE (1 << 30) +#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) +#define DSIM_INT_BTA (1 << 25) +#define DSIM_INT_FRAME_DONE (1 << 24) +#define DSIM_INT_RX_TIMEOUT (1 << 21) +#define DSIM_INT_BTA_TIMEOUT (1 << 20) +#define DSIM_INT_RX_DONE (1 << 18) +#define DSIM_INT_RX_TE (1 << 17) +#define DSIM_INT_RX_ACK (1 << 16) +#define DSIM_INT_RX_ECC_ERR (1 << 15) +#define DSIM_INT_RX_CRC_ERR (1 << 14) + +/* DSIM_FIFOCTRL */ +#define DSIM_FULL_H_SFR (1 << 23) + +/* DSIM_PHYACCHR */ +#define DSIM_AFC_EN (1 << 14) +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5) + +/* DSIM_PLLCTRL */ +#define DSIM_FREQ_BAND(x) ((x) << 24) +#define DSIM_PLL_EN (1 << 23) +#define DSIM_PLL_P(x) ((x) << 13) +#define DSIM_PLL_M(x) ((x) << 4) +#define DSIM_PLL_S(x) ((x) << 1) + +#define DSI_MAX_BUS_WIDTH 4 +#define DSI_NUM_VIRTUAL_CHANNELS 4 +#define DSI_TX_FIFO_SIZE 2048 +#define DSI_RX_FIFO_SIZE 256 +#define DSI_XFER_TIMEOUT_MS 100 +#define DSI_RX_FIFO_EMPTY 0x30800002 + +enum exynos_dsi_transfer_type { + EXYNOS_DSI_TX, + EXYNOS_DSI_RX, +}; + +struct exynos_dsi_transfer { + struct list_head list; + struct completion completed; + int result; + u8 type; + u8 data[2]; + + const u8 *tx_payload; + u16 tx_len; + u16 tx_done; + + u8 *rx_payload; + u16 rx_len; + u16 rx_done; +}; + +struct exynos_dsi { + struct mipi_dsi_bus bus; + struct mipi_dsi_interface_params params; + bool streaming; + bool enabled; + + struct platform_device *pdev; + struct phy *phy; + struct device *dev; + struct resource *res; + struct clk *pll_clk; + struct clk *bus_clk; + unsigned int irq; + void __iomem *reg_base; + struct regulator_bulk_data supplies[2]; + struct exynos_dsi_platform_data *pd; + u32 intsrc; + wait_queue_head_t queue; + + spinlock_t transfer_lock; + struct list_head transfer_list; +}; + +#define bus_to_dsi(_bus) container_of(_bus, struct exynos_dsi, bus) + +/* + * H/W control + */ + +static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) +{ + int ret; + + ret = wait_event_timeout(dsi->queue, + (dsi->intsrc & DSIM_INT_SW_RST_RELEASE), + msecs_to_jiffies(300)); + if (ret <= 0) + dev_err(dsi->dev, "timeout waiting for reset\n"); + + dsi->intsrc = 0; +} + +static void exynos_dsi_reset(struct exynos_dsi *dsi) +{ + writel(DSIM_SWRST, dsi->reg_base + DSIM_SWRST_REG); +} + +#ifndef MHZ +#define MHZ (1000*1000) +#endif + +static const unsigned long freq_bands[] = { + 100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ, + 270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ, + 510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ, + 770 * MHZ, 870 * MHZ, 950 * MHZ, +}; + +static const int afc_settings[] = { + 1, 0, 3, 2, 5, 4, +}; + +static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi, + unsigned long fin, unsigned long fout, u8 *p, u16 *m, u8 *s) +{ + unsigned long best_freq = 0; + u32 min_delta = 0xffffffff; + u8 p_min, p_max; + u8 _p, uninitialized_var(best_p); + u16 _m, uninitialized_var(best_m); + u8 _s, uninitialized_var(best_s); + + p_min = DIV_ROUND_UP(fin, (12 * MHZ)); + p_max = fin / (6 * MHZ); + + for (_p = p_min; _p <= p_max; ++_p) { + for (_s = 0; _s <= 5; ++_s) { + u64 tmp; + u32 delta; + u16 div; + + tmp = (u64)fout * (_p << _s); + do_div(tmp, fin); + _m = tmp; + if (_m < 41 || _m > 125) + continue; + + tmp = (u64)_m * fin; + do_div(tmp, _p); + if (tmp < 500 * MHZ || tmp > 1000 * MHZ) + continue; + + tmp = (u64)_m * fin; + div = (_p << _s); + do_div(tmp, div); + + delta = abs(fout - tmp); + if (delta < min_delta) { + best_p = _p; + best_m = _m; + best_s = _s; + min_delta = delta; + best_freq = tmp; + } + } + } + + if (best_freq) { + *p = best_p; + *m = best_m; + *s = best_s; + } + + return best_freq; +} + +static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi, + unsigned long freq) +{ + unsigned long fin, fout, fin_pll; + int timeout; + u8 p; + u16 m; + u8 s; + int band; + int afc; + u32 reg; + + clk_set_rate(dsi->pll_clk, dsi->pd->pll_clk_rate); + + fin = clk_get_rate(dsi->pll_clk); + if (!fin) { + dev_err(dsi->dev, "failed to get PLL clock frequency\n"); + return 0; + } + + dev_dbg(dsi->dev, "PLL input frequency: %lu\n", fin); + + fout = exynos_dsi_pll_find_pms(dsi, fin, freq, &p, &m, &s); + if (!fout) { + dev_err(dsi->dev, + "failed to find PLL coefficients for requested frequency\n"); + return -EFAULT; + } + + dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s); + + for (band = 0; band < ARRAY_SIZE(freq_bands); ++band) + if (fout < freq_bands[band]) + break; + + fin_pll = DIV_ROUND_CLOSEST(fin, p); + fin_pll /= MHZ; + if (fin_pll > 6) + fin_pll -= 6; + else + fin_pll = 0; + if (fin_pll >= ARRAY_SIZE(afc_settings)) + fin_pll = ARRAY_SIZE(afc_settings) - 1; + + afc = afc_settings[fin_pll]; + + dev_dbg(dsi->dev, "freq band %d, afc_setting %d\n", band, afc); + + writel(dsi->pd->pll_stable_time, dsi->reg_base + DSIM_PLLTMR_REG); + + reg = DSIM_AFC_CTL(afc) | DSIM_AFC_EN; + writel(reg, dsi->reg_base + DSIM_PHYACCHR_REG); + + reg = DSIM_FREQ_BAND(band) | DSIM_PLL_EN + | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s); + writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG); + + timeout = 1000; + do { + if (timeout-- == 0) { + dev_err(dsi->dev, "PLL failed to stabilize\n"); + return -EFAULT; + } + reg = readl(dsi->reg_base + DSIM_STATUS_REG); + } while ((reg & DSIM_PLL_STABLE) == 0); + + return fout; +} + +static int exynos_dsi_enable_clock(struct exynos_dsi *dsi) +{ + struct mipi_dsi_interface_params *params = &dsi->params; + unsigned long hs_clk, byte_clk, esc_clk; + unsigned long esc_div; + u32 reg; + + hs_clk = exynos_dsi_set_pll(dsi, params->hs_clk_freq); + if (!hs_clk) { + dev_err(dsi->dev, "failed to configure DSI PLL\n"); + return -EFAULT; + } + + byte_clk = hs_clk / 8; + esc_div = DIV_ROUND_UP(byte_clk, params->esc_clk_freq); + esc_clk = byte_clk / esc_div; + + if (esc_clk > 20 * MHZ) { + ++esc_div; + esc_clk = byte_clk / esc_div; + } + + dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n", + hs_clk, byte_clk, esc_clk); + + reg = readl(dsi->reg_base + DSIM_CLKCTRL_REG); + reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK + | DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS + | DSIM_BYTE_CLK_SRC_MASK); + reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN + | DSIM_ESC_PRESCALER(esc_div) + | DSIM_LANE_ESC_CLK_EN_CLK + | DSIM_LANE_ESC_CLK_EN_DATA(params->data_lanes) + | DSIM_BYTE_CLK_SRC(0) + | DSIM_TX_REQUEST_HSCLK; + writel(reg, dsi->reg_base + DSIM_CLKCTRL_REG); + + return 0; +} + +static void exynos_dsi_disable_clock(struct exynos_dsi *dsi) +{ + u32 reg; + + reg = readl(dsi->reg_base + DSIM_CLKCTRL_REG); + reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK | + DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN); + writel(reg, dsi->reg_base + DSIM_CLKCTRL_REG); + + reg = readl(dsi->reg_base + DSIM_PLLCTRL_REG); + reg &= ~DSIM_PLL_EN; + writel(reg, dsi->reg_base + DSIM_PLLCTRL_REG); +} + +static int exynos_dsi_init_link(struct exynos_dsi *dsi) +{ + struct mipi_dsi_interface_params *params = &dsi->params; + int timeout; + u32 reg; + + /* Initialize FIFO pointers */ + reg = readl(dsi->reg_base + DSIM_FIFOCTRL_REG); + reg &= ~0x1f; + writel(reg, dsi->reg_base + DSIM_FIFOCTRL_REG); + + usleep_range(10000, 10000); + + reg |= 0x1f; + writel(reg, dsi->reg_base + DSIM_FIFOCTRL_REG); + + usleep_range(10000, 10000); + + /* DSI configuration */ + reg = 0; + + if (params->mode & DSI_MODE_VIDEO) { + reg |= DSIM_VIDEO_MODE; + + if (!(params->mode & DSI_MODE_VSYNC_FLUSH)) + reg |= DSIM_MFLUSH_VS; + if (!(params->mode & DSI_MODE_EOT_PACKET)) + reg |= DSIM_EOT_DISABLE; + if (params->mode & DSI_MODE_VIDEO_SYNC_PULSE) + reg |= DSIM_SYNC_INFORM; + if (params->mode & DSI_MODE_VIDEO_BURST) + reg |= DSIM_BURST_MODE; + if (params->mode & DSI_MODE_VIDEO_AUTO_VERT) + reg |= DSIM_AUTO_MODE; + if (params->mode & DSI_MODE_VIDEO_HSE) + reg |= DSIM_HSE_MODE; + if (!(params->mode & DSI_MODE_VIDEO_HFP)) + reg |= DSIM_HFP_MODE; + if (!(params->mode & DSI_MODE_VIDEO_HBP)) + reg |= DSIM_HBP_MODE; + if (!(params->mode & DSI_MODE_VIDEO_HSA)) + reg |= DSIM_HSA_MODE; + } + + switch (params->format) { + case DSI_FMT_RGB888: + reg |= DSIM_MAIN_PIX_FORMAT_RGB888; + break; + case DSI_FMT_RGB666: + reg |= DSIM_MAIN_PIX_FORMAT_RGB666; + break; + case DSI_FMT_RGB666_PACKED: + reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P; + break; + case DSI_FMT_RGB565: + reg |= DSIM_MAIN_PIX_FORMAT_RGB565; + break; + default: + dev_err(dsi->dev, "invalid pixel format\n"); + return -EINVAL; + } + + switch (params->data_lanes) { + case 0x1: + reg |= DSIM_NUM_OF_DATA_LANE(0); + break; + case 0x3: + reg |= DSIM_NUM_OF_DATA_LANE(1); + break; + case 0x7: + reg |= DSIM_NUM_OF_DATA_LANE(2); + break; + case 0xf: + reg |= DSIM_NUM_OF_DATA_LANE(3); + break; + default: + return -EINVAL; + } + + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + reg |= DSIM_LANE_EN_CLK; + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + reg |= DSIM_LANE_EN(params->data_lanes); + writel(reg, dsi->reg_base + DSIM_CONFIG_REG); + + /* Check clock and data lane state are stop state */ + timeout = 100; + do { + if (timeout-- == 0) { + dev_err(dsi->dev, "waiting for bus lanes timed out\n"); + return -EFAULT; + } + + reg = readl(dsi->reg_base + DSIM_STATUS_REG); + if ((reg & DSIM_STOP_STATE_DAT(params->data_lanes)) + != DSIM_STOP_STATE_DAT(params->data_lanes)) + continue; + } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK))); + + reg = readl(dsi->reg_base + DSIM_ESCMODE_REG); + reg &= ~DSIM_STOP_STATE_CNT_MASK; + reg |= DSIM_STOP_STATE_CNT(dsi->pd->stop_holding_cnt); + writel(reg, dsi->reg_base + DSIM_ESCMODE_REG); + + reg = DSIM_BTA_TOUT(dsi->pd->bta_timeout) + | DSIM_LPDR_TOUT(dsi->pd->rx_timeout); + writel(reg, dsi->reg_base + DSIM_TIMEOUT_REG); + + return 0; +} + +static int exynos_dsi_set_display_mode(struct exynos_dsi *dsi, + const struct videomode *mode) +{ + struct mipi_dsi_interface_params *params = &dsi->params; + u32 reg; + + if (params->mode & DSI_MODE_VIDEO) { + reg = DSIM_CMD_ALLOW(params->cmd_allow) + | DSIM_STABLE_VFP(mode->vfront_porch) + | DSIM_MAIN_VBP(mode->vback_porch); + writel(reg, dsi->reg_base + DSIM_MVPORCH_REG); + + reg = DSIM_MAIN_HFP(mode->hfront_porch) + | DSIM_MAIN_HBP(mode->hback_porch); + writel(reg, dsi->reg_base + DSIM_MHPORCH_REG); + + reg = DSIM_MAIN_VSA(mode->vsync_len) + | DSIM_MAIN_HSA(mode->hsync_len); + writel(reg, dsi->reg_base + DSIM_MSYNC_REG); + } + + reg = DSIM_MAIN_HRESOL(mode->hactive) | DSIM_MAIN_VRESOL(mode->vactive); + writel(reg, dsi->reg_base + DSIM_MDRESOL_REG); + + dev_dbg(dsi->dev, "LCD width = %d, height = %d\n", + mode->hactive, mode->vactive); + + return 0; +} + +static void exynos_dsi_set_display_enable(struct exynos_dsi *dsi, bool enable) +{ + u32 reg; + + reg = readl(dsi->reg_base + DSIM_MDRESOL_REG); + if (enable) + reg |= DSIM_MAIN_STAND_BY; + else + reg &= ~DSIM_MAIN_STAND_BY; + writel(reg, dsi->reg_base + DSIM_MDRESOL_REG); +} + +/* + * FIFO + */ + +static int exynos_dsi_wait_for_hdr_fifo(struct exynos_dsi *dsi) +{ + int timeout = 20000; + + do { + u32 reg = readl(dsi->reg_base + DSIM_FIFOCTRL_REG); + + if (!(reg & DSIM_FULL_H_SFR)) + return 0; + + if (!cond_resched()) + usleep_range(950, 1050); + } while (--timeout); + + return -ETIMEDOUT; +} + +static void exynos_dsi_send_to_fifo(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + const u8 *payload = xfer->tx_payload + xfer->tx_done; + u16 length = xfer->tx_len - xfer->tx_done; + bool first = !xfer->tx_done; + u32 reg; + + dev_dbg(dsi->dev, + "< xfer %p, tx_len %u, tx_done %u, rx_len %u, rx_done %u\n", + xfer, xfer->tx_len, xfer->tx_done, xfer->rx_len, xfer->rx_done); + + if (length > DSI_TX_FIFO_SIZE) + length = DSI_TX_FIFO_SIZE; + + xfer->tx_done += length; + + /* Send payload */ + while (length >= 4) { + reg = (payload[3] << 24) | (payload[2] << 16) + | (payload[1] << 8) | payload[0]; + writel(reg, dsi->reg_base + DSIM_PAYLOAD_REG); + payload += 4; + length -= 4; + } + + reg = 0; + switch (length) { + case 3: + reg |= payload[2] << 16; + /* Fall through */ + case 2: + reg |= payload[1] << 8; + /* Fall through */ + case 1: + reg |= payload[0]; + writel(reg, dsi->reg_base + DSIM_PAYLOAD_REG); + break; + case 0: + /* Do nothing */ + break; + } + + /* Send packet header */ + if (!first) + return; + + if (xfer->rx_len) { + reg = (xfer->rx_len << 8) + | MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE; + if (exynos_dsi_wait_for_hdr_fifo(dsi)) { + dev_err(dsi->dev, + "waiting for header FIFO timed out\n"); + return; + } + writel(reg, dsi->reg_base + DSIM_PKTHDR_REG); + } + + reg = (xfer->data[1] << 16) | (xfer->data[0] << 8) | xfer->type; + if (exynos_dsi_wait_for_hdr_fifo(dsi)) { + dev_err(dsi->dev, "waiting for header FIFO timed out\n"); + return; + } + writel(reg, dsi->reg_base + DSIM_PKTHDR_REG); +} + +static void exynos_dsi_read_from_fifo(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + u8 *payload = xfer->rx_payload + xfer->rx_done; + bool first = !xfer->rx_done; + u16 length; + u32 reg; + + if (first) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + + switch (reg & 0x3f) { + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + if (xfer->rx_len >= 2) { + payload[1] = reg >> 16; + ++xfer->rx_done; + } + /* Fall through */ + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + payload[0] = reg >> 8; + ++xfer->rx_done; + xfer->rx_len = xfer->rx_done; + xfer->result = 0; + goto clear_fifo; + } + + length = (reg >> 8) & 0xffff; + if (length > xfer->rx_len) { + dev_err(dsi->dev, + "response too long (expected %u, got %u bytes)\n", + xfer->rx_len, length); + xfer->rx_len = 0; + xfer->rx_done = 0; + xfer->result = -EFAULT; + goto clear_fifo; + } + if (length < xfer->rx_len) + xfer->rx_len = length; + } + + length = xfer->rx_len - xfer->rx_done; + xfer->rx_done += length; + + /* Receive payload */ + while (length >= 4) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + payload[0] = (reg >> 0) & 0xff; + payload[1] = (reg >> 8) & 0xff; + payload[2] = (reg >> 16) & 0xff; + payload[3] = (reg >> 24) & 0xff; + payload += 4; + length -= 4; + } + + if (length) { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + switch (length) { + case 3: + payload[2] = (reg >> 16) & 0xff; + /* Fall through */ + case 2: + payload[1] = (reg >> 8) & 0xff; + /* Fall through */ + case 1: + payload[0] = reg & 0xff; + } + } + + if (xfer->rx_done == xfer->rx_len) + xfer->result = 0; + +clear_fifo: + length = DSI_RX_FIFO_SIZE / 4; + do { + reg = readl(dsi->reg_base + DSIM_RXFIFO_REG); + if (reg == DSI_RX_FIFO_EMPTY) + break; + } while (--length); +} + +/* + * Transfer + */ + +static void exynos_dsi_transfer_start(struct exynos_dsi *dsi) +{ + unsigned long flags; + struct exynos_dsi_transfer *xfer; + bool start = false; + +again: + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (list_empty(&dsi->transfer_list)) { + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + return; + } + + xfer = list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (xfer->tx_len && xfer->tx_done == xfer->tx_len) + /* waiting for RX */ + return; + + exynos_dsi_send_to_fifo(dsi, xfer); + + if (xfer->tx_len || xfer->rx_len) + return; + + xfer->result = 0; + complete(&xfer->completed); + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (start) + goto again; +} + +static bool exynos_dsi_transfer_finish(struct exynos_dsi *dsi) +{ + struct exynos_dsi_transfer *xfer; + unsigned long flags; + bool start = true; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (list_empty(&dsi->transfer_list)) { + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + return false; + } + + xfer = list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + dev_dbg(dsi->dev, + "> xfer %p, tx_len %u, tx_done %u, rx_len %u, rx_done %u\n", + xfer, xfer->tx_len, xfer->tx_done, xfer->rx_len, xfer->rx_done); + + if (xfer->tx_done != xfer->tx_len) + return true; + + if (xfer->rx_done != xfer->rx_len) + exynos_dsi_read_from_fifo(dsi, xfer); + + if (xfer->rx_done != xfer->rx_len) + return true; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (!xfer->rx_len) + xfer->result = 0; + complete(&xfer->completed); + + return start; +} + +static void exynos_dsi_remove_transfer(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + unsigned long flags; + bool start; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (!list_empty(&dsi->transfer_list) + && xfer == list_first_entry(&dsi->transfer_list, + struct exynos_dsi_transfer, list)) { + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + if (start) + exynos_dsi_transfer_start(dsi); + return; + } + + list_del_init(&xfer->list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); +} + +static int exynos_dsi_transfer(struct exynos_dsi *dsi, + struct exynos_dsi_transfer *xfer) +{ + unsigned long flags; + bool stopped; + + xfer->tx_done = 0; + xfer->rx_done = 0; + xfer->result = -ETIMEDOUT; + init_completion(&xfer->completed); + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + stopped = list_empty(&dsi->transfer_list); + list_add_tail(&xfer->list, &dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (stopped) + exynos_dsi_transfer_start(dsi); + + wait_for_completion_timeout(&xfer->completed, + msecs_to_jiffies(DSI_XFER_TIMEOUT_MS)); + if (xfer->result == -ETIMEDOUT) { + exynos_dsi_remove_transfer(dsi, xfer); + dev_err(dsi->dev, "xfer timed out\n"); + return -ETIMEDOUT; + } + + /* Also covers hardware timeout condition */ + return xfer->result; +} + +/* + * Interrupt handler + */ + +static irqreturn_t exynos_dsi_irq(int irq, void *dev_id) +{ + struct exynos_dsi *dsi = dev_id; + u32 status; + + status = readl(dsi->reg_base + DSIM_INTSRC_REG); + if (!status) { + static unsigned long int j; + if (printk_timed_ratelimit(&j, 500)) + dev_warn(dsi->dev, "spurious interrupt\n"); + return IRQ_HANDLED; + } + writel(status, dsi->reg_base + DSIM_INTSRC_REG); + + dev_dbg(dsi->dev, "%s: status = %08x\n", __func__, status); + + if (status & DSIM_INT_SW_RST_RELEASE) { + u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY); + writel(mask, dsi->reg_base + DSIM_INTMSK_REG); + dsi->intsrc = status; + wake_up(&dsi->queue); + return IRQ_HANDLED; + } + + if (exynos_dsi_transfer_finish(dsi)) + exynos_dsi_transfer_start(dsi); + + return IRQ_HANDLED; +} + +static int exynos_dsi_set_stream(struct mipi_dsi_bus *bus, + struct mipi_dsi_device *dev, bool on) +{ + struct exynos_dsi *dsi = bus_to_dsi(bus); + + if (!dsi->enabled) + return -EINVAL; + + if (on) + exynos_dsi_set_display_mode(dsi, &dev->vm); + exynos_dsi_set_display_enable(dsi, on); + dsi->streaming = on; + + return 0; +} + +/* enable/disable dsi bus */ +static int exynos_dsi_enable(struct exynos_dsi *dsi) +{ + int ret; + + if (dsi->enabled) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret < 0) + return ret; + + clk_prepare_enable(dsi->bus_clk); + clk_prepare_enable(dsi->pll_clk); + + phy_power_on(dsi->phy); + + exynos_dsi_enable_clock(dsi); + + dsi->intsrc = 0; + exynos_dsi_reset(dsi); + enable_irq(dsi->irq); + exynos_dsi_wait_for_reset(dsi); + + exynos_dsi_init_link(dsi); + + dsi->enabled = true; + + return 0; +} + +static int exynos_dsi_disable(struct exynos_dsi *dsi) +{ + int ret; + + if (!dsi->enabled) + return 0; + + if (dsi->streaming) + return -EBUSY; + + dsi->enabled = false; + + exynos_dsi_disable_clock(dsi); + + disable_irq(dsi->irq); + + phy_power_off(dsi->phy); + + clk_disable_unprepare(dsi->pll_clk); + clk_disable_unprepare(dsi->bus_clk); + + ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret < 0) + return ret; + + return 0; +} + +static int exynos_dsi_set_power(struct mipi_dsi_bus *bus, + struct mipi_dsi_device *dev, bool on) +{ + struct exynos_dsi *dsi = bus_to_dsi(bus); + + if (on) { + dsi->params = dev->params; + return exynos_dsi_enable(dsi); + } else { + return exynos_dsi_disable(dsi); + } +} + +static bool exynos_dsi_is_short_dsi_type(u8 type) +{ + return ((type & 0x0f) <= 8); +} + +static int exynos_dsi_bus_transfer(struct mipi_dsi_bus *bus, + struct mipi_dsi_device *dev, u8 type, + const u8 *tx_buf, size_t tx_len, + u8 *rx_buf, size_t rx_len) +{ + struct exynos_dsi *dsi = bus_to_dsi(bus); + struct exynos_dsi_transfer xfer; + + if (!tx_len) + return -EINVAL; + + xfer.type = type; + + if (exynos_dsi_is_short_dsi_type(type)) { + if (tx_len > 2) + return -EINVAL; + xfer.tx_len = 0; + xfer.data[0] = tx_buf[0]; + xfer.data[1] = (tx_len == 2) ? tx_buf[1] : 0; + } else { + xfer.tx_len = tx_len; + xfer.data[0] = tx_len & 0xff; + xfer.data[1] = tx_len >> 8; + xfer.tx_payload = tx_buf; + } + + xfer.rx_len = rx_len; + xfer.rx_payload = rx_buf; + + return exynos_dsi_transfer(dsi, &xfer); +} + +static const struct mipi_dsi_bus_ops exynos_dsi_ops = { + .set_power = exynos_dsi_set_power, + .set_stream = exynos_dsi_set_stream, + .transfer = exynos_dsi_bus_transfer, +}; + +#ifdef CONFIG_OF +/* + * Device Tree + */ + +static struct exynos_dsi_platform_data *exynos_dsi_parse_dt( + struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct exynos_dsi_platform_data *dsi_pd; + struct device *dev = &pdev->dev; + const __be32 *prop_data; + + dsi_pd = kzalloc(sizeof(*dsi_pd), GFP_KERNEL); + if (!dsi_pd) { + dev_err(dev, "failed to allocate dsi platform data\n"); + return NULL; + } + + prop_data = of_get_property(node, "samsung,pll-stable-time", NULL); + if (!prop_data) { + dev_err(dev, "failed to get pll-stable-time property\n"); + goto err_free_pd; + } + dsi_pd->pll_stable_time = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "samsung,stop-holding-count", NULL); + if (!prop_data) { + dev_err(dev, "failed to get stop-holding-count property\n"); + goto err_free_pd; + } + dsi_pd->stop_holding_cnt = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "samsung,bta-timeout", NULL); + if (!prop_data) { + dev_err(dev, "failed to get bta-timeout property\n"); + goto err_free_pd; + } + dsi_pd->bta_timeout = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "samsung,rx-timeout", NULL); + if (!prop_data) { + dev_err(dev, "failed to get rx-timeout property\n"); + goto err_free_pd; + } + dsi_pd->rx_timeout = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "samsung,pll-clk-freq", NULL); + if (!prop_data) { + dev_err(dev, "failed to get pll-clk-freq property\n"); + goto err_free_pd; + } + dsi_pd->pll_clk_rate = be32_to_cpu(*prop_data); + + return dsi_pd; + +err_free_pd: + kfree(dsi_pd); + + return NULL; +} + +static struct of_device_id exynos_dsi_of_match[] = { + { .compatible = "samsung,exynos4210-mipi-dsi" }, + { } +}; + +MODULE_DEVICE_TABLE(of, exynos_dsi_of_match); +#else +static struct exynos_dsi_platform_data *exynos_dsi_parse_dt( + struct platform_device *pdev) +{ + return NULL; +} +#endif + +/* + * Platform driver + */ + +static int exynos_dsi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct exynos_dsi *dsi; + int ret; + + dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) { + dev_err(&pdev->dev, "failed to allocate dsi object.\n"); + return -ENOMEM; + } + + init_waitqueue_head(&dsi->queue); + spin_lock_init(&dsi->transfer_lock); + INIT_LIST_HEAD(&dsi->transfer_list); + + dsi->bus.ops = &exynos_dsi_ops; + dsi->bus.dev = &pdev->dev; + + dsi->pdev = pdev; + dsi->dev = &pdev->dev; + dsi->pd = pdev->dev.platform_data; + + if (dsi->pd == NULL && pdev->dev.of_node) + dsi->pd = exynos_dsi_parse_dt(pdev); + + if (dsi->pd == NULL) { + dev_err(&pdev->dev, "failed to get platform data for dsi.\n"); + return -EINVAL; + } + + dsi->supplies[0].supply = "vdd11"; + dsi->supplies[1].supply = "vdd18"; + ret = devm_regulator_bulk_get(&pdev->dev, + ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret) { + dev_err(&pdev->dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + dsi->pll_clk = devm_clk_get(&pdev->dev, "pll_clk"); + if (IS_ERR(dsi->pll_clk)) { + dev_err(&pdev->dev, "failed to get dsi pll input clock\n"); + return -ENODEV; + } + + dsi->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); + if (IS_ERR(dsi->bus_clk)) { + dev_err(&pdev->dev, "failed to get dsi bus clock\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get io memory region\n"); + return -ENODEV; + } + + dsi->reg_base = devm_request_and_ioremap(&pdev->dev, res); + if (!dsi->reg_base) { + dev_err(&pdev->dev, "failed to remap io region\n"); + return -ENOMEM; + } + + dsi->phy = devm_phy_get(&pdev->dev, "dsim"); + if (IS_ERR(dsi->phy)) + return PTR_ERR(dsi->phy); + + platform_set_drvdata(pdev, dsi); + + dsi->irq = platform_get_irq(pdev, 0); + if (dsi->irq < 0) { + dev_err(&pdev->dev, "failed to request dsi irq resource\n"); + return -EINVAL; + } + + irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, dsi->irq, NULL, + exynos_dsi_irq, IRQF_ONESHOT, dev_name(&pdev->dev), dsi); + if (ret) { + dev_err(&pdev->dev, "failed to request dsi irq\n"); + return ret; + } + + return of_mipi_dsi_register_devices(&dsi->bus); +} + +static int exynos_dsi_remove(struct platform_device *pdev) +{ + struct exynos_dsi *dsi = dev_get_drvdata(&pdev->dev); + + mipi_dsi_unregister_devices(&dsi->bus); + + return 0; +} +/* + * Power management + */ + +#ifdef CONFIG_PM_SLEEP +static int exynos_dsi_suspend(struct device *dev) +{ + struct exynos_dsi *dsi = dev_get_drvdata(dev); + + return exynos_dsi_disable(dsi); +} + +static int exynos_dsi_resume(struct device *dev) +{ + struct exynos_dsi *dsi = dev_get_drvdata(dev); + + return exynos_dsi_enable(dsi); +} +#endif + +static const struct dev_pm_ops exynos_dsi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume) +}; + +/* + * Module + */ + +static struct platform_driver exynos_dsi_driver = { + .probe = exynos_dsi_probe, + .remove = exynos_dsi_remove, + .driver = { + .name = "exynos-dsi", + .owner = THIS_MODULE, + .pm = &exynos_dsi_pm_ops, + .of_match_table = of_match_ptr(exynos_dsi_of_match), + }, +}; + +module_platform_driver(exynos_dsi_driver); + +MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>"); +MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); +MODULE_LICENSE("GPL"); diff --git a/include/video/mipi-dsi-exynos.h b/include/video/mipi-dsi-exynos.h new file mode 100644 index 0000000..548c473 --- /dev/null +++ b/include/video/mipi-dsi-exynos.h @@ -0,0 +1,41 @@ +/* include/video/mipi-dsi-exynos.h + * + * Platform data header for Samsung SoC MIPI-DSIM. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae <inki.dae@samsung.com> + * Donghwa Lee <dh09.lee@samsung.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. +*/ + +#ifndef _MIPI_DSI_EXYNOS_H +#define _MIPI_DSI_EXYNOS_H + +#include <linux/device.h> + +/* + * struct exynos_dsi_platform_data - interface to platform data + * for mipi-dsi driver. + * + * TODO + */ +struct exynos_dsi_platform_data { + unsigned int enabled; + + int (*phy_enable)(struct platform_device *pdev, bool on); + + unsigned int pll_stable_time; + unsigned long pll_clk_rate; + unsigned long esc_clk_rate; + unsigned short stop_holding_cnt; + unsigned char bta_timeout; + unsigned short rx_timeout; +}; + +int s5p_dsim_phy_enable(struct platform_device *pdev, bool on); + +#endif /* _MIPI_DSI_EXYNOS_H */ -- 1.8.1.2 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC PATCH 3/4] panel-s6e8aa0: add driver 2013-09-24 14:23 ` Andrzej Hajda @ 2013-09-24 14:23 ` Andrzej Hajda -1 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park, Donghwa Lee, Inki Dae, Joongmock Shin, Eunchul Kim, Tomasz Figa The patch adds mipi-dsi-bus slave driver for s6e8aa0 familiy panels. Signed-off-by: Donghwa Lee <dh09.lee@samsung.com> Signed-off-by: Inki Dae <inki.dae@samsung.com> Signed-off-by: Joongmock Shin <jmock.shin@samsung.com> Signed-off-by: Eunchul Kim <chulspro.kim@samsung.com> Signed-off-by: Tomasz Figa <t.figa@samsung.com> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> --- drivers/video/display/Kconfig | 6 + drivers/video/display/Makefile | 1 + drivers/video/display/panel-s6e8aa0.c | 1286 +++++++++++++++++++++++++++++++++ include/video/panel-s6e8aa0.h | 42 ++ 4 files changed, 1335 insertions(+) create mode 100644 drivers/video/display/panel-s6e8aa0.c create mode 100644 include/video/panel-s6e8aa0.h diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 0a1e90b..9a9b7e9 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -67,4 +67,10 @@ config DISPLAY_VGA_DAC If you are in doubt, say N. To compile this driver as a module, choose M here: the module will be called vga-dac. +config DISPLAY_PANEL_S6E8AA0 + tristate "S6E8AA0 DSI video mode panel" + select BACKLIGHT_CLASS_DEVICE + select DISPLAY_MIPI_DSI + select VIDEOMODE_HELPERS + endif # DISPLAY_CORE diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index 2fd84f5..45225e1 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o obj-$(CONFIG_DISPLAY_VGA_DAC) += vga-dac.o +obj-$(CONFIG_DISPLAY_PANEL_S6E8AA0) += panel-s6e8aa0.o diff --git a/drivers/video/display/panel-s6e8aa0.c b/drivers/video/display/panel-s6e8aa0.c new file mode 100644 index 0000000..99246da --- /dev/null +++ b/drivers/video/display/panel-s6e8aa0.c @@ -0,0 +1,1286 @@ +/* linux/drivers/video/s6e8aa0.c + * + * MIPI-DSI based s6e8aa0 AMOLED lcd 5.3 inch panel driver. + * + * Inki Dae, <inki.dae@samsung.com> + * Donghwa Lee, <dh09.lee@samsung.com> + * Joongmock Shin <jmock.shin@samsung.com> + * Eunchul Kim <chulspro.kim@samsung.com> + * Tomasz Figa <t.figa@samsung.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/kernel.h> + +#include <linux/backlight.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/lcd.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> +#include <video/mipi-dsi-bus.h> +#include <video/of_videomode.h> +#include <video/panel-s6e8aa0.h> + +#define LDI_MTP_LENGTH 24 +#define GAMMA_LEVEL_NUM 25 +#define GAMMA_TABLE_LEN 26 +#define S6E8AA0_PANEL_COND_LEN 39 + +/* 1 */ +#define PANELCTL_SS_MASK (1 << 5) +#define PANELCTL_SS_1_800 (0 << 5) +#define PANELCTL_SS_800_1 (1 << 5) +#define PANELCTL_GTCON_MASK (7 << 2) +#define PANELCTL_GTCON_110 (6 << 2) +#define PANELCTL_GTCON_111 (7 << 2) +/* LTPS */ +/* 30 */ +#define PANELCTL_CLK1_CON_MASK (7 << 3) +#define PANELCTL_CLK1_000 (0 << 3) +#define PANELCTL_CLK1_001 (1 << 3) +#define PANELCTL_CLK2_CON_MASK (7 << 0) +#define PANELCTL_CLK2_000 (0 << 0) +#define PANELCTL_CLK2_001 (1 << 0) +/* 31 */ +#define PANELCTL_INT1_CON_MASK (7 << 3) +#define PANELCTL_INT1_000 (0 << 3) +#define PANELCTL_INT1_001 (1 << 3) +#define PANELCTL_INT2_CON_MASK (7 << 0) +#define PANELCTL_INT2_000 (0 << 0) +#define PANELCTL_INT2_001 (1 << 0) +/* 32 */ +#define PANELCTL_BICTL_CON_MASK (7 << 3) +#define PANELCTL_BICTL_000 (0 << 3) +#define PANELCTL_BICTL_001 (1 << 3) +#define PANELCTL_BICTLB_CON_MASK (7 << 0) +#define PANELCTL_BICTLB_000 (0 << 0) +#define PANELCTL_BICTLB_001 (1 << 0) +/* 36 */ +#define PANELCTL_EM_CLK1_CON_MASK (7 << 3) +#define PANELCTL_EM_CLK1_110 (6 << 3) +#define PANELCTL_EM_CLK1_111 (7 << 3) +#define PANELCTL_EM_CLK1B_CON_MASK (7 << 0) +#define PANELCTL_EM_CLK1B_110 (6 << 0) +#define PANELCTL_EM_CLK1B_111 (7 << 0) +/* 37 */ +#define PANELCTL_EM_CLK2_CON_MASK (7 << 3) +#define PANELCTL_EM_CLK2_110 (6 << 3) +#define PANELCTL_EM_CLK2_111 (7 << 3) +#define PANELCTL_EM_CLK2B_CON_MASK (7 << 0) +#define PANELCTL_EM_CLK2B_110 (6 << 0) +#define PANELCTL_EM_CLK2B_111 (7 << 0) +/* 38 */ +#define PANELCTL_EM_INT1_CON_MASK (7 << 3) +#define PANELCTL_EM_INT1_000 (0 << 3) +#define PANELCTL_EM_INT1_001 (1 << 3) +#define PANELCTL_EM_INT2_CON_MASK (7 << 0) +#define PANELCTL_EM_INT2_000 (0 << 0) +#define PANELCTL_EM_INT2_001 (1 << 0) + +#define AID_DISABLE (0x4) +#define AID_1 (0x5) +#define AID_2 (0x6) +#define AID_3 (0x7) + +typedef u8 s6e8aa0_gamma_table[GAMMA_TABLE_LEN]; + +struct s6e8aa0 { + struct mipi_dsi_device *dev; + + struct s6e8aa0_platform_data *pdata; + struct backlight_device *bd; + struct lcd_device *ld; + struct regulator_bulk_data supplies[2]; + + unsigned int id; + unsigned int aid; + const struct s6e8aa0_variant *variant; + + unsigned int reset_gpio; + + int power; + bool streaming; + int brightness; + struct mutex mutex; +}; + +struct s6e8aa0_variant { + u8 version; + + int (*panel_cond_set)(struct s6e8aa0 *); + + const u8 *pentile_ctl_table; + size_t pentile_ctl_len; + + const u8 *power_ctl_table; + size_t power_ctl_len; + + int (*elvss_nvm_set)(struct s6e8aa0 *); + + int (*brightness_set)(struct s6e8aa0 *); + const s6e8aa0_gamma_table *gamma_tables; +}; + +static void s6e8aa0_apply_level_1_key(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf0, 0x5a, 0x5a); +} + +static u8 s6e8aa0_apply_aid_panel_cond(unsigned int aid) +{ + u8 ret; + + switch (aid) { + case AID_1: + ret = 0x60; + break; + case AID_2: + ret = 0x80; + break; + case AID_3: + ret = 0xA0; + break; + default: + ret = 0x04; + break; + } + + return ret; +} + +static int s6e8aa0_panel_cond_set(struct s6e8aa0 *lcd) +{ + return mipi_dsi_dcs_write_static_seq(lcd->dev, 0, + 0xf8, 0x19, 0x35, 0x00, 0x00, 0x00, 0x94, 0x00, 0x3c, + 0x78, 0x10, 0x27, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, + 0x23, 0x6e, 0xc0, 0xc1, 0x01, 0x81, 0xc1, 0x00, 0xc3, + 0xf6, 0xf6, 0xc1 + ); +} + +static int s6e8aa0_panel_cond_set_v142(struct s6e8aa0 *lcd) +{ + u8 aid = s6e8aa0_apply_aid_panel_cond(lcd->aid); + u8 cfg = 0x3d; + u8 clk_con = 0xc8; + u8 int_con = 0x08; + u8 bictl_con = 0x48; + u8 em_clk1_con = 0xff; + u8 em_clk2_con = 0xff; + u8 em_int_con = 0xc8; + + if (lcd->pdata->flip_vertical) { + /* GTCON */ + cfg &= ~(PANELCTL_GTCON_MASK); + cfg |= (PANELCTL_GTCON_110); + } + + if (lcd->pdata->flip_horizontal) { + /* SS */ + cfg &= ~(PANELCTL_SS_MASK); + cfg |= (PANELCTL_SS_1_800); + } + + if (lcd->pdata->flip_horizontal || lcd->pdata->flip_vertical) { + /* CLK1,2_CON */ + clk_con &= ~(PANELCTL_CLK1_CON_MASK | + PANELCTL_CLK2_CON_MASK); + clk_con |= (PANELCTL_CLK1_000 | PANELCTL_CLK2_001); + + /* INT1,2_CON */ + int_con &= ~(PANELCTL_INT1_CON_MASK | + PANELCTL_INT2_CON_MASK); + int_con |= (PANELCTL_INT1_000 | PANELCTL_INT2_001); + + /* BICTL,B_CON */ + bictl_con &= ~(PANELCTL_BICTL_CON_MASK | + PANELCTL_BICTLB_CON_MASK); + bictl_con |= (PANELCTL_BICTL_000 | + PANELCTL_BICTLB_001); + + /* EM_CLK1,1B_CON */ + em_clk1_con &= ~(PANELCTL_EM_CLK1_CON_MASK | + PANELCTL_EM_CLK1B_CON_MASK); + em_clk1_con |= (PANELCTL_EM_CLK1_110 | + PANELCTL_EM_CLK1B_110); + + /* EM_CLK2,2B_CON */ + em_clk2_con &= ~(PANELCTL_EM_CLK2_CON_MASK | + PANELCTL_EM_CLK2B_CON_MASK); + em_clk2_con |= (PANELCTL_EM_CLK2_110 | + PANELCTL_EM_CLK2B_110); + + /* EM_INT1,2_CON */ + em_int_con &= ~(PANELCTL_EM_INT1_CON_MASK | + PANELCTL_EM_INT2_CON_MASK); + em_int_con |= (PANELCTL_EM_INT1_000 | + PANELCTL_EM_INT2_001); + } + + return mipi_dsi_dcs_write_seq(lcd->dev, 0, 0xf8, cfg, 0x35, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x3c, 0x78, 0x08, 0x27, 0x7d, 0x3f, 0x00, + 0x00, 0x00, 0x20, aid, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x07, + 0x07, 0x23, 0x23, 0xc0, clk_con, int_con, bictl_con, 0xc1, 0x00, + 0xc1, em_clk1_con, em_clk2_con, em_int_con); +} + +static void s6e8aa0_display_condition_set(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf2, 0x80, 0x03, 0x0d); +} + +static void s6e8aa0_etc_source_control(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf6, 0x00, 0x02, 0x00); +} + +static u8 s6e8aa0_pentile_ctl_table[] = { + 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xff, 0x44, 0x44, 0xc0, 0x00 +}; + +static u8 s6e8aa0_pentile_ctl_table_v32[] = { + 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xc0, 0x44, 0x44, 0xc0, 0x00 +}; + +static int s6e8aa0_etc_pentile_control(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *v = lcd->variant; + + return mipi_dsi_dcs_write(lcd->dev, 0, v->pentile_ctl_table, + v->pentile_ctl_len); +} + +static const u8 s6e8aa0_power_ctl_table[] = { + 0xf4, 0xcf, 0x0a, 0x12, 0x10, 0x1e, 0x33, 0x02 +}; + +static const u8 s6e8aa0_power_ctl_table_v32[] = { + 0xf4, 0xcf, 0x0a, 0x15, 0x10, 0x19, 0x33, 0x02 +}; + +static int s6e8aa0_etc_power_control(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *v = lcd->variant; + + return mipi_dsi_dcs_write(lcd->dev, 0, v->power_ctl_table, + v->power_ctl_len); +} + +static int s6e8aa0_etc_elvss_control(struct s6e8aa0 *lcd) +{ + u8 id = !lcd->id ? 0x95 : 0; + + return mipi_dsi_dcs_write_seq(lcd->dev, 0, 0xb1, 0x04, id); +} + +static int s6e8aa0_elvss_nvm_set(struct s6e8aa0 *lcd) +{ + return mipi_dsi_dcs_write_static_seq(lcd->dev, 0, + 0xd9, 0x14, 0x40, 0x0c, 0xcb, 0xce, 0x6e, 0xc4, 0x07, + 0x40, 0x41, 0xc1, 0x00, 0x60, 0x19); +}; + +static int s6e8aa0_elvss_nvm_set_v142(struct s6e8aa0 *lcd) +{ + u8 br; + + switch (lcd->brightness) { + case 0 ... 6: /* 30cd ~ 100cd */ + br = 0xdf; + break; + case 7 ... 11: /* 120cd ~ 150cd */ + br = 0xdd; + break; + case 12 ... 15: /* 180cd ~ 210cd */ + default: + br = 0xd9; + break; + case 16 ... 24: /* 240cd ~ 300cd */ + br = 0xd0; + break; + } + + return mipi_dsi_dcs_write_seq(lcd->dev, 0, 0xd9, 0x14, 0x40, 0x0c, 0xcb, + 0xce, 0x6e, 0xc4, 0x0f, 0x40, 0x41, br, 0x00, 0x60, 0x19); +} + +static void s6e8aa0_apply_level_2_key(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xfc, 0x5a, 0x5a); +} + +static void s6e8aa0_enable_mtp_register(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf1, 0x5a, 0x5a); +} + +static void s6e8aa0_disable_mtp_register(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf1, 0xa5, 0xa5); +} + +static void s6e8aa0_read_id(struct s6e8aa0 *lcd, u8 *mtp_id) +{ + unsigned int addr = 0xd1; /* MTP ID */ + + mipi_dsi_dcs_read(lcd->dev, 0, addr, mtp_id, 3); +} + +static unsigned int s6e8aa0_read_mtp(struct s6e8aa0 *lcd, u8 *mtp_data) +{ + unsigned int ret; + unsigned int addr = 0xd3; /* MTP addr */ + + s6e8aa0_enable_mtp_register(lcd); + + ret = mipi_dsi_dcs_read(lcd->dev, 0, addr, mtp_data, LDI_MTP_LENGTH); + if (ret) + dev_err(&lcd->dev->dev, "%s: dsi read error\n", __func__); + + s6e8aa0_disable_mtp_register(lcd); + + return ret; +} + +static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v142[GAMMA_LEVEL_NUM] = { + { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x62, 0x55, 0x55, + 0xaf, 0xb1, 0xb1, 0xbd, 0xce, 0xb7, 0x9a, 0xb1, + 0x90, 0xb2, 0xc4, 0xae, 0x00, 0x60, 0x00, 0x40, + 0x00, 0x70, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x74, 0x68, 0x69, + 0xb8, 0xc1, 0xb7, 0xbd, 0xcd, 0xb8, 0x93, 0xab, + 0x88, 0xb4, 0xc4, 0xb1, 0x00, 0x6b, 0x00, 0x4d, + 0x00, 0x7d, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x95, 0x8a, 0x89, + 0xb4, 0xc6, 0xb2, 0xc5, 0xd2, 0xbf, 0x90, 0xa8, + 0x85, 0xb5, 0xc4, 0xb3, 0x00, 0x7b, 0x00, 0x5d, + 0x00, 0x8f, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9f, 0x98, 0x92, + 0xb3, 0xc4, 0xb0, 0xbc, 0xcc, 0xb4, 0x91, 0xa6, + 0x87, 0xb5, 0xc5, 0xb4, 0x00, 0x87, 0x00, 0x6a, + 0x00, 0x9e, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x99, 0x93, 0x8b, + 0xb2, 0xc2, 0xb0, 0xbd, 0xce, 0xb4, 0x90, 0xa6, + 0x87, 0xb3, 0xc3, 0xb2, 0x00, 0x8d, 0x00, 0x70, + 0x00, 0xa4, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xa5, 0x99, + 0xb2, 0xc2, 0xb0, 0xbb, 0xcd, 0xb1, 0x93, 0xa7, + 0x8a, 0xb2, 0xc1, 0xb0, 0x00, 0x92, 0x00, 0x75, + 0x00, 0xaa, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa0, 0xa0, 0x93, + 0xb6, 0xc4, 0xb4, 0xb5, 0xc8, 0xaa, 0x94, 0xa9, + 0x8c, 0xb2, 0xc0, 0xb0, 0x00, 0x97, 0x00, 0x7a, + 0x00, 0xaf, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xa7, 0x96, + 0xb3, 0xc2, 0xb0, 0xba, 0xcb, 0xb0, 0x94, 0xa8, + 0x8c, 0xb0, 0xbf, 0xaf, 0x00, 0x9f, 0x00, 0x83, + 0x00, 0xb9, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9d, 0xa2, 0x90, + 0xb6, 0xc5, 0xb3, 0xb8, 0xc9, 0xae, 0x94, 0xa8, + 0x8d, 0xaf, 0xbd, 0xad, 0x00, 0xa4, 0x00, 0x88, + 0x00, 0xbf, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa6, 0xac, 0x97, + 0xb4, 0xc4, 0xb1, 0xbb, 0xcb, 0xb2, 0x93, 0xa7, + 0x8d, 0xae, 0xbc, 0xad, 0x00, 0xa7, 0x00, 0x8c, + 0x00, 0xc3, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa2, 0xa9, 0x93, + 0xb6, 0xc5, 0xb2, 0xba, 0xc9, 0xb0, 0x93, 0xa7, + 0x8d, 0xae, 0xbb, 0xac, 0x00, 0xab, 0x00, 0x90, + 0x00, 0xc8, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9e, 0xa6, 0x8f, + 0xb7, 0xc6, 0xb3, 0xb8, 0xc8, 0xb0, 0x93, 0xa6, + 0x8c, 0xae, 0xbb, 0xad, 0x00, 0xae, 0x00, 0x93, + 0x00, 0xcc, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xab, 0xb4, 0x9c, + 0xb3, 0xc3, 0xaf, 0xb7, 0xc7, 0xaf, 0x93, 0xa6, + 0x8c, 0xaf, 0xbc, 0xad, 0x00, 0xb1, 0x00, 0x97, + 0x00, 0xcf, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa6, 0xb1, 0x98, + 0xb1, 0xc2, 0xab, 0xba, 0xc9, 0xb2, 0x93, 0xa6, + 0x8d, 0xae, 0xba, 0xab, 0x00, 0xb5, 0x00, 0x9b, + 0x00, 0xd4, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xae, 0x94, + 0xb2, 0xc3, 0xac, 0xbb, 0xca, 0xb4, 0x91, 0xa4, + 0x8a, 0xae, 0xba, 0xac, 0x00, 0xb8, 0x00, 0x9e, + 0x00, 0xd8, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xab, 0xb7, 0x9c, + 0xae, 0xc0, 0xa9, 0xba, 0xc9, 0xb3, 0x92, 0xa5, + 0x8b, 0xad, 0xb9, 0xab, 0x00, 0xbb, 0x00, 0xa1, + 0x00, 0xdc, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb4, 0x97, + 0xb0, 0xc1, 0xaa, 0xb9, 0xc8, 0xb2, 0x92, 0xa5, + 0x8c, 0xae, 0xb9, 0xab, 0x00, 0xbe, 0x00, 0xa4, + 0x00, 0xdf, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xb0, 0x94, + 0xb0, 0xc2, 0xab, 0xbb, 0xc9, 0xb3, 0x91, 0xa4, + 0x8b, 0xad, 0xb8, 0xaa, 0x00, 0xc1, 0x00, 0xa8, + 0x00, 0xe2, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xb0, 0x94, + 0xae, 0xbf, 0xa8, 0xb9, 0xc8, 0xb3, 0x92, 0xa4, + 0x8b, 0xad, 0xb7, 0xa9, 0x00, 0xc4, 0x00, 0xab, + 0x00, 0xe6, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb6, 0x98, + 0xaf, 0xc0, 0xa8, 0xb8, 0xc7, 0xb2, 0x93, 0xa5, + 0x8d, 0xad, 0xb7, 0xa9, 0x00, 0xc7, 0x00, 0xae, + 0x00, 0xe9, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb3, 0x95, + 0xaf, 0xc1, 0xa9, 0xb9, 0xc8, 0xb3, 0x92, 0xa4, + 0x8b, 0xad, 0xb7, 0xaa, 0x00, 0xc9, 0x00, 0xb0, + 0x00, 0xec, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb3, 0x95, + 0xac, 0xbe, 0xa6, 0xbb, 0xc9, 0xb4, 0x90, 0xa3, + 0x8a, 0xad, 0xb7, 0xa9, 0x00, 0xcc, 0x00, 0xb4, + 0x00, 0xf0, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa0, 0xb0, 0x91, + 0xae, 0xc0, 0xa6, 0xba, 0xc8, 0xb4, 0x91, 0xa4, + 0x8b, 0xad, 0xb7, 0xa9, 0x00, 0xcf, 0x00, 0xb7, + 0x00, 0xf3, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb8, 0x98, + 0xab, 0xbd, 0xa4, 0xbb, 0xc9, 0xb5, 0x91, 0xa3, + 0x8b, 0xac, 0xb6, 0xa8, 0x00, 0xd1, 0x00, 0xb9, + 0x00, 0xf6, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb5, 0x95, + 0xa9, 0xbc, 0xa1, 0xbb, 0xc9, 0xb5, 0x91, 0xa3, + 0x8a, 0xad, 0xb6, 0xa8, 0x00, 0xd6, 0x00, 0xbf, + 0x00, 0xfc, + }, +}; + +static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v96[GAMMA_LEVEL_NUM] = { + { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xdf, 0x1f, 0xd7, 0xdc, 0xb7, 0xe1, 0xc0, 0xaf, + 0xc4, 0xd2, 0xd0, 0xcf, 0x00, 0x4d, 0x00, 0x40, + 0x00, 0x5f, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd5, 0x35, 0xcf, 0xdc, 0xc1, 0xe1, 0xbf, 0xb3, + 0xc1, 0xd2, 0xd1, 0xce, 0x00, 0x53, 0x00, 0x46, + 0x00, 0x67, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd2, 0x64, 0xcf, 0xdb, 0xc6, 0xe1, 0xbd, 0xb3, + 0xbd, 0xd2, 0xd2, 0xce, 0x00, 0x59, 0x00, 0x4b, + 0x00, 0x6e, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd0, 0x7c, 0xcf, 0xdb, 0xc9, 0xe0, 0xbc, 0xb4, + 0xbb, 0xcf, 0xd1, 0xcc, 0x00, 0x5f, 0x00, 0x50, + 0x00, 0x75, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd0, 0x8e, 0xd1, 0xdb, 0xcc, 0xdf, 0xbb, 0xb6, + 0xb9, 0xd0, 0xd1, 0xcd, 0x00, 0x63, 0x00, 0x54, + 0x00, 0x7a, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd1, 0x9e, 0xd5, 0xda, 0xcd, 0xdd, 0xbb, 0xb7, + 0xb9, 0xce, 0xce, 0xc9, 0x00, 0x68, 0x00, 0x59, + 0x00, 0x81, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd0, 0xa5, 0xd6, 0xda, 0xcf, 0xdd, 0xbb, 0xb7, + 0xb8, 0xcc, 0xcd, 0xc7, 0x00, 0x6c, 0x00, 0x5c, + 0x00, 0x86, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xfe, + 0xd0, 0xae, 0xd7, 0xd9, 0xd0, 0xdb, 0xb9, 0xb6, + 0xb5, 0xca, 0xcc, 0xc5, 0x00, 0x74, 0x00, 0x63, + 0x00, 0x90, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xf9, + 0xcf, 0xb0, 0xd6, 0xd9, 0xd1, 0xdb, 0xb9, 0xb6, + 0xb4, 0xca, 0xcb, 0xc5, 0x00, 0x77, 0x00, 0x66, + 0x00, 0x94, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xf7, + 0xcf, 0xb3, 0xd7, 0xd8, 0xd1, 0xd9, 0xb7, 0xb6, + 0xb3, 0xc9, 0xca, 0xc3, 0x00, 0x7b, 0x00, 0x69, + 0x00, 0x99, + + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xfd, 0x2f, 0xf7, + 0xdf, 0xb5, 0xd6, 0xd8, 0xd1, 0xd8, 0xb6, 0xb5, + 0xb2, 0xca, 0xcb, 0xc4, 0x00, 0x7e, 0x00, 0x6c, + 0x00, 0x9d, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xfa, 0x2f, 0xf5, + 0xce, 0xb6, 0xd5, 0xd7, 0xd2, 0xd8, 0xb6, 0xb4, + 0xb0, 0xc7, 0xc9, 0xc1, 0x00, 0x84, 0x00, 0x71, + 0x00, 0xa5, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf7, 0x2f, 0xf2, + 0xce, 0xb9, 0xd5, 0xd8, 0xd2, 0xd8, 0xb4, 0xb4, + 0xaf, 0xc7, 0xc9, 0xc1, 0x00, 0x87, 0x00, 0x73, + 0x00, 0xa8, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf5, 0x2f, 0xf0, + 0xdf, 0xba, 0xd5, 0xd7, 0xd2, 0xd7, 0xb4, 0xb4, + 0xaf, 0xc5, 0xc7, 0xbf, 0x00, 0x8a, 0x00, 0x76, + 0x00, 0xac, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf2, 0x2f, 0xed, + 0xcE, 0xbb, 0xd4, 0xd6, 0xd2, 0xd6, 0xb5, 0xb4, + 0xaF, 0xc5, 0xc7, 0xbf, 0x00, 0x8c, 0x00, 0x78, + 0x00, 0xaf, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xef, 0x2f, 0xeb, + 0xcd, 0xbb, 0xd2, 0xd7, 0xd3, 0xd6, 0xb3, 0xb4, + 0xae, 0xc5, 0xc6, 0xbe, 0x00, 0x91, 0x00, 0x7d, + 0x00, 0xb6, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xee, 0x2f, 0xea, + 0xce, 0xbd, 0xd4, 0xd6, 0xd2, 0xd5, 0xb2, 0xb3, + 0xad, 0xc3, 0xc4, 0xbb, 0x00, 0x94, 0x00, 0x7f, + 0x00, 0xba, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xec, 0x2f, 0xe8, + 0xce, 0xbe, 0xd3, 0xd6, 0xd3, 0xd5, 0xb2, 0xb2, + 0xac, 0xc3, 0xc5, 0xbc, 0x00, 0x96, 0x00, 0x81, + 0x00, 0xbd, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xeb, 0x2f, 0xe7, + 0xce, 0xbf, 0xd3, 0xd6, 0xd2, 0xd5, 0xb1, 0xb2, + 0xab, 0xc2, 0xc4, 0xbb, 0x00, 0x99, 0x00, 0x83, + 0x00, 0xc0, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xef, 0x5f, 0xe9, + 0xca, 0xbf, 0xd3, 0xd5, 0xd2, 0xd4, 0xb2, 0xb2, + 0xab, 0xc1, 0xc4, 0xba, 0x00, 0x9b, 0x00, 0x85, + 0x00, 0xc3, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xea, 0x5f, 0xe8, + 0xee, 0xbf, 0xd2, 0xd5, 0xd2, 0xd4, 0xb1, 0xb2, + 0xab, 0xc1, 0xc2, 0xb9, 0x00, 0x9D, 0x00, 0x87, + 0x00, 0xc6, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe9, 0x5f, 0xe7, + 0xcd, 0xbf, 0xd2, 0xd6, 0xd2, 0xd4, 0xb1, 0xb2, + 0xab, 0xbe, 0xc0, 0xb7, 0x00, 0xa1, 0x00, 0x8a, + 0x00, 0xca, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe8, 0x61, 0xe6, + 0xcd, 0xbf, 0xd1, 0xd6, 0xd3, 0xd4, 0xaf, 0xb0, + 0xa9, 0xbe, 0xc1, 0xb7, 0x00, 0xa3, 0x00, 0x8b, + 0x00, 0xce, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe8, 0x62, 0xe5, + 0xcc, 0xc0, 0xd0, 0xd6, 0xd2, 0xd4, 0xaf, 0xb1, + 0xa9, 0xbd, 0xc0, 0xb6, 0x00, 0xa5, 0x00, 0x8d, + 0x00, 0xd0, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe7, 0x7f, 0xe3, + 0xcc, 0xc1, 0xd0, 0xd5, 0xd3, 0xd3, 0xae, 0xaf, + 0xa8, 0xbe, 0xc0, 0xb7, 0x00, 0xa8, 0x00, 0x90, + 0x00, 0xd3, + } +}; + +static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v32[GAMMA_LEVEL_NUM] = { + { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0x72, 0x5e, 0x6b, + 0xa1, 0xa7, 0x9a, 0xb4, 0xcb, 0xb8, 0x92, 0xac, + 0x97, 0xb4, 0xc3, 0xb5, 0x00, 0x4e, 0x00, 0x37, + 0x00, 0x58, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0x85, 0x71, 0x7d, + 0xa6, 0xb6, 0xa1, 0xb5, 0xca, 0xba, 0x93, 0xac, + 0x98, 0xb2, 0xc0, 0xaf, 0x00, 0x59, 0x00, 0x43, + 0x00, 0x64, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa4, 0x94, 0x9e, + 0xa0, 0xbb, 0x9c, 0xc3, 0xd2, 0xc6, 0x93, 0xaa, + 0x95, 0xb7, 0xc2, 0xb4, 0x00, 0x65, 0x00, 0x50, + 0x00, 0x74, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xa1, 0xa6, + 0xa0, 0xb9, 0x9b, 0xc3, 0xd1, 0xc8, 0x90, 0xa6, + 0x90, 0xbb, 0xc3, 0xb7, 0x00, 0x6f, 0x00, 0x5b, + 0x00, 0x80, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa6, 0x9d, 0x9f, + 0x9f, 0xb8, 0x9a, 0xc7, 0xd5, 0xcc, 0x90, 0xa5, + 0x8f, 0xb8, 0xc1, 0xb6, 0x00, 0x74, 0x00, 0x60, + 0x00, 0x85, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb3, 0xae, 0xae, + 0x9e, 0xb7, 0x9a, 0xc8, 0xd6, 0xce, 0x91, 0xa6, + 0x90, 0xb6, 0xc0, 0xb3, 0x00, 0x78, 0x00, 0x65, + 0x00, 0x8a, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xa9, 0xa8, + 0xa3, 0xb9, 0x9e, 0xc4, 0xd3, 0xcb, 0x94, 0xa6, + 0x90, 0xb6, 0xbf, 0xb3, 0x00, 0x7c, 0x00, 0x69, + 0x00, 0x8e, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xaf, 0xaf, 0xa9, + 0xa5, 0xbc, 0xa2, 0xc7, 0xd5, 0xcd, 0x93, 0xa5, + 0x8f, 0xb4, 0xbd, 0xb1, 0x00, 0x83, 0x00, 0x70, + 0x00, 0x96, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xab, 0xa3, + 0xaa, 0xbf, 0xa7, 0xc5, 0xd3, 0xcb, 0x93, 0xa5, + 0x8f, 0xb2, 0xbb, 0xb0, 0x00, 0x86, 0x00, 0x74, + 0x00, 0x9b, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb1, 0xb5, 0xab, + 0xab, 0xc0, 0xa9, 0xc7, 0xd4, 0xcc, 0x94, 0xa4, + 0x8f, 0xb1, 0xbb, 0xaf, 0x00, 0x8a, 0x00, 0x77, + 0x00, 0x9e, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb2, 0xa7, + 0xae, 0xc2, 0xab, 0xc5, 0xd3, 0xca, 0x93, 0xa4, + 0x8f, 0xb1, 0xba, 0xae, 0x00, 0x8d, 0x00, 0x7b, + 0x00, 0xa2, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xaf, 0xa3, + 0xb0, 0xc3, 0xae, 0xc4, 0xd1, 0xc8, 0x93, 0xa4, + 0x8f, 0xb1, 0xba, 0xaf, 0x00, 0x8f, 0x00, 0x7d, + 0x00, 0xa5, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb4, 0xbd, 0xaf, + 0xae, 0xc1, 0xab, 0xc2, 0xd0, 0xc6, 0x94, 0xa4, + 0x8f, 0xb1, 0xba, 0xaf, 0x00, 0x92, 0x00, 0x80, + 0x00, 0xa8, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xb9, 0xac, + 0xad, 0xc1, 0xab, 0xc4, 0xd1, 0xc7, 0x95, 0xa4, + 0x90, 0xb0, 0xb9, 0xad, 0x00, 0x95, 0x00, 0x84, + 0x00, 0xac, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb6, 0xa7, + 0xaf, 0xc2, 0xae, 0xc5, 0xd1, 0xc7, 0x93, 0xa3, + 0x8e, 0xb0, 0xb9, 0xad, 0x00, 0x98, 0x00, 0x86, + 0x00, 0xaf, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb4, 0xbf, 0xaf, + 0xad, 0xc1, 0xab, 0xc3, 0xd0, 0xc6, 0x94, 0xa3, + 0x8f, 0xaf, 0xb8, 0xac, 0x00, 0x9a, 0x00, 0x89, + 0x00, 0xb2, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xbc, 0xac, + 0xaf, 0xc2, 0xad, 0xc2, 0xcf, 0xc4, 0x94, 0xa3, + 0x90, 0xaf, 0xb8, 0xad, 0x00, 0x9c, 0x00, 0x8b, + 0x00, 0xb5, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb9, 0xa7, + 0xb1, 0xc4, 0xaf, 0xc3, 0xcf, 0xc5, 0x94, 0xa3, + 0x8f, 0xae, 0xb7, 0xac, 0x00, 0x9f, 0x00, 0x8e, + 0x00, 0xb8, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb9, 0xa7, + 0xaf, 0xc2, 0xad, 0xc1, 0xce, 0xc3, 0x95, 0xa3, + 0x90, 0xad, 0xb6, 0xab, 0x00, 0xa2, 0x00, 0x91, + 0x00, 0xbb, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb1, 0xbe, 0xac, + 0xb1, 0xc4, 0xaf, 0xc1, 0xcd, 0xc1, 0x95, 0xa4, + 0x91, 0xad, 0xb6, 0xab, 0x00, 0xa4, 0x00, 0x93, + 0x00, 0xbd, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbb, 0xa8, + 0xb3, 0xc5, 0xb2, 0xc1, 0xcd, 0xc2, 0x95, 0xa3, + 0x90, 0xad, 0xb6, 0xab, 0x00, 0xa6, 0x00, 0x95, + 0x00, 0xc0, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbb, 0xa8, + 0xb0, 0xc3, 0xaf, 0xc2, 0xce, 0xc2, 0x94, 0xa2, + 0x90, 0xac, 0xb6, 0xab, 0x00, 0xa8, 0x00, 0x98, + 0x00, 0xc3, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xb8, 0xa5, + 0xb3, 0xc5, 0xb2, 0xc1, 0xcc, 0xc0, 0x95, 0xa2, + 0x90, 0xad, 0xb6, 0xab, 0x00, 0xaa, 0x00, 0x9a, + 0x00, 0xc5, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xc0, 0xac, + 0xb0, 0xc3, 0xaf, 0xc1, 0xcd, 0xc1, 0x95, 0xa2, + 0x90, 0xac, 0xb5, 0xa9, 0x00, 0xac, 0x00, 0x9c, + 0x00, 0xc8, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbd, 0xa8, + 0xaf, 0xc2, 0xaf, 0xc1, 0xcc, 0xc0, 0x95, 0xa2, + 0x90, 0xac, 0xb5, 0xaa, 0x00, 0xb1, 0x00, 0xa1, + 0x00, 0xcc, + }, +}; + +static int s6e8aa0_brightness_set(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *variant = lcd->variant; + int ret; + + ret = mipi_dsi_dcs_write(lcd->dev, 0, + variant->gamma_tables[lcd->brightness], + sizeof(variant->gamma_tables[lcd->brightness])); + if (ret < 0) + return ret; + + /* update gamma table. */ + ret = mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf7, 0x03); + if (ret < 0) + return ret; + + return 0; +} + +static int s6e8aa0_brightness_set_v142(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *variant = lcd->variant; + int ret; + + ret = variant->elvss_nvm_set(lcd); + if (ret < 0) + return ret; + + return s6e8aa0_brightness_set(lcd); +} + +static int s6e8aa0_panel_init(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *variant = lcd->variant; + + s6e8aa0_apply_level_1_key(lcd); + s6e8aa0_apply_level_2_key(lcd); + msleep(20); + + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(40); + + variant->panel_cond_set(lcd); + s6e8aa0_display_condition_set(lcd); + + s6e8aa0_brightness_set(lcd); + + s6e8aa0_etc_source_control(lcd); + s6e8aa0_etc_pentile_control(lcd); + variant->elvss_nvm_set(lcd); + s6e8aa0_etc_power_control(lcd); + s6e8aa0_etc_elvss_control(lcd); + msleep(lcd->pdata->init_delay); + + dev_dbg(&lcd->dev->dev, "panel init sequence done.\n"); + + return 0; +} + +static int s6e8aa0_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static int s6e8aa0_update_status(struct backlight_device *bd) +{ + struct s6e8aa0 *lcd = bl_get_data(bd); + const struct s6e8aa0_variant *variant = lcd->variant; + int ret; + + mutex_lock(&lcd->mutex); + + lcd->brightness = bd->props.brightness; + + if (!lcd->power) + goto unlock; + + ret = variant->brightness_set(lcd); + if (ret) { + dev_err(&lcd->dev->dev, "lcd brightness setting failed.\n"); + goto unlock; + } + +unlock: + mutex_unlock(&lcd->mutex); + + return 0; +} + +static const struct backlight_ops s6e8aa0_backlight_ops = { + .get_brightness = s6e8aa0_get_brightness, + .update_status = s6e8aa0_update_status, +}; + +static int s6e8aa0_set_stream(struct s6e8aa0 *lcd, bool on); + +static int s6e8aa0_set_power(struct lcd_device *ld, int power) +{ + struct s6e8aa0 *lcd = lcd_get_data(ld); + int ret = 0; + + mutex_lock(&lcd->mutex); + + ret = s6e8aa0_set_stream(lcd, power = FB_BLANK_UNBLANK); + + if (ret) + goto unlock; + + lcd->power = power; + +unlock: + mutex_unlock(&lcd->mutex); + + return ret; +} + +static int s6e8aa0_get_power(struct lcd_device *ld) +{ + struct s6e8aa0 *lcd = lcd_get_data(ld); + + return lcd->power; +} + +static struct lcd_ops s6e8aa0_lcd_ops = { + .set_power = s6e8aa0_set_power, + .get_power = s6e8aa0_get_power, +}; + +static const struct s6e8aa0_variant s6e8aa0_variants[] = { + { + .version = 32, + .panel_cond_set = s6e8aa0_panel_cond_set, + .pentile_ctl_table = s6e8aa0_pentile_ctl_table_v32, + .pentile_ctl_len = ARRAY_SIZE(s6e8aa0_pentile_ctl_table_v32), + .power_ctl_table = s6e8aa0_power_ctl_table_v32, + .power_ctl_len = ARRAY_SIZE(s6e8aa0_power_ctl_table_v32), + .elvss_nvm_set = s6e8aa0_elvss_nvm_set, + .brightness_set = s6e8aa0_brightness_set, + .gamma_tables = s6e8aa0_gamma_tables_v32, + }, { + .version = 142, + .panel_cond_set = s6e8aa0_panel_cond_set_v142, + .pentile_ctl_table = s6e8aa0_pentile_ctl_table, + .pentile_ctl_len = ARRAY_SIZE(s6e8aa0_pentile_ctl_table), + .power_ctl_table = s6e8aa0_power_ctl_table, + .power_ctl_len = ARRAY_SIZE(s6e8aa0_power_ctl_table), + .elvss_nvm_set = s6e8aa0_elvss_nvm_set_v142, + .brightness_set = s6e8aa0_brightness_set_v142, + .gamma_tables = s6e8aa0_gamma_tables_v142, + }, { + /* FIXME: support M0 panel v96 + * need to suitable panel specification + */ + .version = 96, + .panel_cond_set = s6e8aa0_panel_cond_set, + .pentile_ctl_table = s6e8aa0_pentile_ctl_table_v32, + .pentile_ctl_len = ARRAY_SIZE(s6e8aa0_pentile_ctl_table_v32), + .power_ctl_table = s6e8aa0_power_ctl_table_v32, + .power_ctl_len = ARRAY_SIZE(s6e8aa0_power_ctl_table_v32), + .elvss_nvm_set = s6e8aa0_elvss_nvm_set, + .brightness_set = s6e8aa0_brightness_set, + .gamma_tables = s6e8aa0_gamma_tables_v96, + + }, { + /* FIXME: support U1HD panel v210 + * need to suitable panel specification + */ + .version = 210, + .panel_cond_set = s6e8aa0_panel_cond_set_v142, + .pentile_ctl_table = s6e8aa0_pentile_ctl_table, + .pentile_ctl_len = ARRAY_SIZE(s6e8aa0_pentile_ctl_table), + .power_ctl_table = s6e8aa0_power_ctl_table, + .power_ctl_len = ARRAY_SIZE(s6e8aa0_power_ctl_table), + .elvss_nvm_set = s6e8aa0_elvss_nvm_set_v142, + .brightness_set = s6e8aa0_brightness_set_v142, + .gamma_tables = s6e8aa0_gamma_tables_v142, + } +}; + +static int s6e8aa0_check_mtp(struct s6e8aa0 *lcd) +{ + int ret; + u8 mtp_data[LDI_MTP_LENGTH] = {0, }; + u8 mtp_id[3] = {0, }; + int i; + + s6e8aa0_read_id(lcd, mtp_id); + if (mtp_id[0] = 0x00) { + dev_err(&lcd->dev->dev, "read id failed\n"); + return -EIO; + } + + dev_info(&lcd->dev->dev, "Read ID : 0x%2x, 0x%2x, 0x%2x\n", + mtp_id[0], mtp_id[1], mtp_id[2]); + + if (mtp_id[2] = 0x33) + dev_dbg(&lcd->dev->dev, + "ID-3 is 0xff does not support dynamic elvss\n"); + else { + dev_dbg(&lcd->dev->dev, + "ID-3 is 0x%x support dynamic elvss\n", mtp_id[2]); + dev_dbg(&lcd->dev->dev, "Dynamic ELVSS Information\n"); + } + + for (i = 0; i < ARRAY_SIZE(s6e8aa0_variants); ++i) { + if (mtp_id[1] = s6e8aa0_variants[i].version) + break; + } + if (i >= ARRAY_SIZE(s6e8aa0_variants)) { + dev_err(&lcd->dev->dev, "unsupported display version %d\n", + mtp_id[1]); + return -EINVAL; + } + + lcd->variant = &s6e8aa0_variants[i]; + lcd->id = mtp_id[2]; + lcd->aid = (mtp_id[2] >> 5); + + ret = s6e8aa0_read_mtp(lcd, mtp_data); + if (ret) { + dev_err(&lcd->dev->dev, "read mtp failed\n"); + return -EIO; + } + + return 0; +} + +static int s6e8aa0_set_sequence(struct s6e8aa0 *lcd) +{ + int ret; + + ret = s6e8aa0_check_mtp(lcd); + if (ret < 0) + return ret; + + s6e8aa0_panel_init(lcd); + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, MIPI_DCS_SET_DISPLAY_ON); + + dev_dbg(&lcd->dev->dev, "%s:done.\n", __func__); + + return 0; +} + +#ifdef CONFIG_OF +static int s6e8aa0_generic_reset(struct device *dev) +{ + struct s6e8aa0 *lcd = dev_get_drvdata(dev); + + gpio_set_value(lcd->reset_gpio, 1); + usleep_range(10000, 11000); + gpio_set_value(lcd->reset_gpio, 0); + usleep_range(10000, 11000); + gpio_set_value(lcd->reset_gpio, 1); + + return 0; +} + +static struct s6e8aa0_platform_data *s6e8aa0_parse_dt(struct s6e8aa0 *lcd) +{ + struct device_node *node = lcd->dev->dev.of_node; + struct s6e8aa0_platform_data *data; + const __be32 *prop_data; + int ret; + + data = devm_kzalloc(&lcd->dev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&lcd->dev->dev, "failed to allocate platform data.\n"); + return NULL; + } + + ret = of_get_videomode(node, &data->mode, 0); + if (ret) { + dev_err(&lcd->dev->dev, "failed to read video mode from DT\n"); + return NULL; + } + + lcd->reset_gpio = of_get_named_gpio(node, "reset-gpio", 0); + if (lcd->reset_gpio < 0) + return NULL; + + prop_data = of_get_property(node, "reset-delay", NULL); + if (!prop_data) + return NULL; + data->reset_delay = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "power-on-delay", NULL); + if (!prop_data) + return NULL; + data->power_on_delay = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "init-delay", NULL); + if (!prop_data) + return NULL; + data->init_delay = be32_to_cpu(*prop_data); + + if (of_find_property(node, "flip-horizontal", NULL)) + data->flip_horizontal = true; + + if (of_find_property(node, "flip-vertical", NULL)) + data->flip_vertical = true; + + data->reset = s6e8aa0_generic_reset; + + return data; +} + +static struct of_device_id s6e8aa0_of_match[] = { + { .compatible = "samsung,s6e8aa0" }, + { } +}; + +MODULE_DEVICE_TABLE(of, s6e8aa0_of_match); +#else +static struct s6e8aa0_platform_data *s6e8aa0_parse_dt(struct s6e8aa0 *lcd) +{ + return NULL; +} +#endif + +static int s6e8aa0_power_on(struct s6e8aa0 *lcd) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(lcd->supplies), lcd->supplies); + if (ret) + return ret; + + msleep(lcd->pdata->power_on_delay); + + /* lcd reset */ + if (lcd->pdata->reset) { + ret = lcd->pdata->reset(&lcd->dev->dev); + if (ret) + goto err; + } + + msleep(lcd->pdata->reset_delay); + ret = mipi_dsi_set_power(lcd->dev, 1); + if (ret) + goto err; + + ret = s6e8aa0_set_sequence(lcd); + if (!ret) + return 0; + + mipi_dsi_set_power(lcd->dev, 0); +err: + regulator_bulk_disable(ARRAY_SIZE(lcd->supplies), lcd->supplies); + + return ret; +} + +static void s6e8aa0_power_off(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, MIPI_DCS_ENTER_SLEEP_MODE); + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, MIPI_DCS_SET_DISPLAY_OFF); + + mipi_dsi_set_power(lcd->dev, 0); + + regulator_bulk_disable(ARRAY_SIZE(lcd->supplies), lcd->supplies); +} + +static int s6e8aa0_set_stream(struct s6e8aa0 *lcd, bool on) +{ + if (on = lcd->streaming) + return 0; + + if (on) { + s6e8aa0_power_on(lcd); + mipi_dsi_set_stream(lcd->dev, true); + } else { + mipi_dsi_set_stream(lcd->dev, false); + s6e8aa0_power_off(lcd); + } + + lcd->streaming = on; + + return 0; +} + +static int s6e8aa0_probe(struct mipi_dsi_device *pdev) +{ + struct s6e8aa0 *lcd; + int ret; + + lcd = kzalloc(sizeof(struct s6e8aa0), GFP_KERNEL); + if (!lcd) { + dev_err(&pdev->dev, "failed to allocate s6e8aa0 structure.\n"); + return -ENOMEM; + } + + mipi_dsi_set_drvdata(pdev, lcd); + + lcd->dev = pdev; + lcd->pdata = (struct s6e8aa0_platform_data *)pdev->dev.platform_data; + + if (!lcd->pdata) { + lcd->pdata = s6e8aa0_parse_dt(lcd); + if (!lcd->pdata) { + dev_err(&pdev->dev, "failed to find platform data\n"); + return -ENODEV; + } + } + + lcd->supplies[0].supply = "vdd3"; + lcd->supplies[1].supply = "vci"; + ret = regulator_bulk_get(&pdev->dev, + ARRAY_SIZE(lcd->supplies), lcd->supplies); + if (ret) { + dev_err(&pdev->dev, "Failed to get regulators: %d\n", ret); + goto err_regulator_bulk_get; + } + + lcd->ld = lcd_device_register("s6e8aa0", &pdev->dev, lcd, + &s6e8aa0_lcd_ops); + if (IS_ERR(lcd->ld)) { + dev_err(&lcd->dev->dev, "failed to register lcd ops.\n"); + ret = PTR_ERR(lcd->ld); + goto err_lcd_register; + } + + lcd->bd = backlight_device_register("s6e8aa0-bl", &pdev->dev, lcd, + &s6e8aa0_backlight_ops, NULL); + if (IS_ERR(lcd->bd)) { + dev_err(&pdev->dev, "failed to register backlight ops.\n"); + ret = PTR_ERR(lcd->bd); + goto err_backlight_register; + } + + mutex_init(&lcd->mutex); + + lcd->bd->props.max_brightness = GAMMA_LEVEL_NUM - 1; + lcd->bd->props.brightness = GAMMA_LEVEL_NUM - 1; + lcd->brightness = GAMMA_LEVEL_NUM - 1; + + dev_dbg(&pdev->dev, "probed s6e8aa0 panel driver.\n"); + pdev->vm = lcd->pdata->mode; + + pdev->params = (struct mipi_dsi_interface_params) { + .format = DSI_FMT_RGB888, + .mode = DSI_MODE_VIDEO | DSI_MODE_VIDEO_BURST + | DSI_MODE_VIDEO_HFP | DSI_MODE_VIDEO_HBP + | DSI_MODE_VIDEO_HSA | DSI_MODE_EOT_PACKET + | DSI_MODE_VSYNC_FLUSH, + .data_lanes = 0xf, + .hs_clk_freq = 500000000, + .esc_clk_freq = 20000000, + }; + + s6e8aa0_set_stream(lcd, true); + + return 0; + +err_backlight_register: + lcd_device_unregister(lcd->ld); +err_lcd_register: + regulator_bulk_free(ARRAY_SIZE(lcd->supplies), lcd->supplies); +err_regulator_bulk_get: + kfree(lcd); + + return ret; +} + +static int s6e8aa0_remove(struct mipi_dsi_device *pdev) +{ + struct s6e8aa0 *lcd = mipi_dsi_get_drvdata(pdev); + + backlight_device_unregister(lcd->bd); + lcd_device_unregister(lcd->ld); + mipi_dsi_set_drvdata(pdev, NULL); + regulator_bulk_free(ARRAY_SIZE(lcd->supplies), lcd->supplies); + kfree(lcd); + + return 0; +} + +static int s6e8aa0_suspend(struct device *dev) +{ + struct s6e8aa0 *lcd = dev_get_drvdata(dev); + + dev_err(dev, "%s: %d\n", __func__, lcd->power); + + if (lcd->power != FB_BLANK_UNBLANK) + return 0; + + mipi_dsi_set_stream(lcd->dev, 0); + s6e8aa0_power_off(lcd); + + return 0; +} + +static int s6e8aa0_resume(struct device *dev) +{ + struct s6e8aa0 *lcd = dev_get_drvdata(dev); + int ret; + + dev_err(dev, "%s: %d\n", __func__, lcd->power); + + if (lcd->power != FB_BLANK_UNBLANK) + return 0; + + s6e8aa0_power_on(lcd); + + ret = mipi_dsi_set_stream(lcd->dev, 1); + dev_err(dev, "%s: dsi_set_stream=%d\n", __func__, ret); + + return 0; +} + +static struct dev_pm_ops s6e8aa0_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(s6e8aa0_suspend, s6e8aa0_resume) +}; + +static const struct mipi_dsi_device_id s6e8aa0_id[] = { + { "s6e8aa0", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(mipi_dsi, s6e8aa0_id); + +static struct mipi_dsi_driver s6e8aa0_driver = { + .probe = s6e8aa0_probe, + .remove = s6e8aa0_remove, + .driver = { + .name = "panel_s6e8aa0", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(s6e8aa0_of_match), + .pm = &s6e8aa0_pm_ops, + }, + .id_table = s6e8aa0_id +}; +module_mipi_dsi_driver(s6e8aa0_driver); + +MODULE_ALIAS("mipi-dsi:s6e8aa0"); +MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>"); +MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); +MODULE_AUTHOR("Joongmock Shin <jmock.shin@samsung.com>"); +MODULE_AUTHOR("Eunchul Kim <chulspro.kim@samsung.com>"); +MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>"); +MODULE_DESCRIPTION("MIPI-DSI based s6e8aa0 AMOLED LCD Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-s6e8aa0.h b/include/video/panel-s6e8aa0.h new file mode 100644 index 0000000..8a32de8 --- /dev/null +++ b/include/video/panel-s6e8aa0.h @@ -0,0 +1,42 @@ +/* + * Renesas R61505-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.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. + */ + +#ifndef __PANEL_S6E8AX0_H__ +#define __PANEL_S6E8AX0_H__ + +#include <video/videomode.h> + +struct s6e8aa0_platform_data { + unsigned long width; /* Panel width in mm */ + unsigned long height; /* Panel height in mm */ + struct videomode mode; + + /* reset lcd panel device. */ + int (*reset)(struct device *dev); + + /* it indicates whether lcd panel was enabled + from bootloader or not. */ + int lcd_enabled; + /* it means delay for stable time when it becomes low to high + or high to low that is dependent on whether reset gpio is + low active or high active. */ + unsigned int reset_delay; + /* stable time needing to become lcd power on. */ + unsigned int power_on_delay; + /* stable time needing to become lcd power off. */ + unsigned int init_delay; + /* panel is reversed */ + bool flip_vertical; + bool flip_horizontal; +}; + +#endif /* __PANEL_S6E8AX0_H__ */ -- 1.8.1.2 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC PATCH 3/4] panel-s6e8aa0: add driver @ 2013-09-24 14:23 ` Andrzej Hajda 0 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park, Donghwa Lee, Inki Dae, Joongmock Shin, Eunchul Kim, Tomasz Figa The patch adds mipi-dsi-bus slave driver for s6e8aa0 familiy panels. Signed-off-by: Donghwa Lee <dh09.lee@samsung.com> Signed-off-by: Inki Dae <inki.dae@samsung.com> Signed-off-by: Joongmock Shin <jmock.shin@samsung.com> Signed-off-by: Eunchul Kim <chulspro.kim@samsung.com> Signed-off-by: Tomasz Figa <t.figa@samsung.com> Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> --- drivers/video/display/Kconfig | 6 + drivers/video/display/Makefile | 1 + drivers/video/display/panel-s6e8aa0.c | 1286 +++++++++++++++++++++++++++++++++ include/video/panel-s6e8aa0.h | 42 ++ 4 files changed, 1335 insertions(+) create mode 100644 drivers/video/display/panel-s6e8aa0.c create mode 100644 include/video/panel-s6e8aa0.h diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 0a1e90b..9a9b7e9 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -67,4 +67,10 @@ config DISPLAY_VGA_DAC If you are in doubt, say N. To compile this driver as a module, choose M here: the module will be called vga-dac. +config DISPLAY_PANEL_S6E8AA0 + tristate "S6E8AA0 DSI video mode panel" + select BACKLIGHT_CLASS_DEVICE + select DISPLAY_MIPI_DSI + select VIDEOMODE_HELPERS + endif # DISPLAY_CORE diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index 2fd84f5..45225e1 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o obj-$(CONFIG_DISPLAY_VGA_DAC) += vga-dac.o +obj-$(CONFIG_DISPLAY_PANEL_S6E8AA0) += panel-s6e8aa0.o diff --git a/drivers/video/display/panel-s6e8aa0.c b/drivers/video/display/panel-s6e8aa0.c new file mode 100644 index 0000000..99246da --- /dev/null +++ b/drivers/video/display/panel-s6e8aa0.c @@ -0,0 +1,1286 @@ +/* linux/drivers/video/s6e8aa0.c + * + * MIPI-DSI based s6e8aa0 AMOLED lcd 5.3 inch panel driver. + * + * Inki Dae, <inki.dae@samsung.com> + * Donghwa Lee, <dh09.lee@samsung.com> + * Joongmock Shin <jmock.shin@samsung.com> + * Eunchul Kim <chulspro.kim@samsung.com> + * Tomasz Figa <t.figa@samsung.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/kernel.h> + +#include <linux/backlight.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/lcd.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> +#include <video/mipi-dsi-bus.h> +#include <video/of_videomode.h> +#include <video/panel-s6e8aa0.h> + +#define LDI_MTP_LENGTH 24 +#define GAMMA_LEVEL_NUM 25 +#define GAMMA_TABLE_LEN 26 +#define S6E8AA0_PANEL_COND_LEN 39 + +/* 1 */ +#define PANELCTL_SS_MASK (1 << 5) +#define PANELCTL_SS_1_800 (0 << 5) +#define PANELCTL_SS_800_1 (1 << 5) +#define PANELCTL_GTCON_MASK (7 << 2) +#define PANELCTL_GTCON_110 (6 << 2) +#define PANELCTL_GTCON_111 (7 << 2) +/* LTPS */ +/* 30 */ +#define PANELCTL_CLK1_CON_MASK (7 << 3) +#define PANELCTL_CLK1_000 (0 << 3) +#define PANELCTL_CLK1_001 (1 << 3) +#define PANELCTL_CLK2_CON_MASK (7 << 0) +#define PANELCTL_CLK2_000 (0 << 0) +#define PANELCTL_CLK2_001 (1 << 0) +/* 31 */ +#define PANELCTL_INT1_CON_MASK (7 << 3) +#define PANELCTL_INT1_000 (0 << 3) +#define PANELCTL_INT1_001 (1 << 3) +#define PANELCTL_INT2_CON_MASK (7 << 0) +#define PANELCTL_INT2_000 (0 << 0) +#define PANELCTL_INT2_001 (1 << 0) +/* 32 */ +#define PANELCTL_BICTL_CON_MASK (7 << 3) +#define PANELCTL_BICTL_000 (0 << 3) +#define PANELCTL_BICTL_001 (1 << 3) +#define PANELCTL_BICTLB_CON_MASK (7 << 0) +#define PANELCTL_BICTLB_000 (0 << 0) +#define PANELCTL_BICTLB_001 (1 << 0) +/* 36 */ +#define PANELCTL_EM_CLK1_CON_MASK (7 << 3) +#define PANELCTL_EM_CLK1_110 (6 << 3) +#define PANELCTL_EM_CLK1_111 (7 << 3) +#define PANELCTL_EM_CLK1B_CON_MASK (7 << 0) +#define PANELCTL_EM_CLK1B_110 (6 << 0) +#define PANELCTL_EM_CLK1B_111 (7 << 0) +/* 37 */ +#define PANELCTL_EM_CLK2_CON_MASK (7 << 3) +#define PANELCTL_EM_CLK2_110 (6 << 3) +#define PANELCTL_EM_CLK2_111 (7 << 3) +#define PANELCTL_EM_CLK2B_CON_MASK (7 << 0) +#define PANELCTL_EM_CLK2B_110 (6 << 0) +#define PANELCTL_EM_CLK2B_111 (7 << 0) +/* 38 */ +#define PANELCTL_EM_INT1_CON_MASK (7 << 3) +#define PANELCTL_EM_INT1_000 (0 << 3) +#define PANELCTL_EM_INT1_001 (1 << 3) +#define PANELCTL_EM_INT2_CON_MASK (7 << 0) +#define PANELCTL_EM_INT2_000 (0 << 0) +#define PANELCTL_EM_INT2_001 (1 << 0) + +#define AID_DISABLE (0x4) +#define AID_1 (0x5) +#define AID_2 (0x6) +#define AID_3 (0x7) + +typedef u8 s6e8aa0_gamma_table[GAMMA_TABLE_LEN]; + +struct s6e8aa0 { + struct mipi_dsi_device *dev; + + struct s6e8aa0_platform_data *pdata; + struct backlight_device *bd; + struct lcd_device *ld; + struct regulator_bulk_data supplies[2]; + + unsigned int id; + unsigned int aid; + const struct s6e8aa0_variant *variant; + + unsigned int reset_gpio; + + int power; + bool streaming; + int brightness; + struct mutex mutex; +}; + +struct s6e8aa0_variant { + u8 version; + + int (*panel_cond_set)(struct s6e8aa0 *); + + const u8 *pentile_ctl_table; + size_t pentile_ctl_len; + + const u8 *power_ctl_table; + size_t power_ctl_len; + + int (*elvss_nvm_set)(struct s6e8aa0 *); + + int (*brightness_set)(struct s6e8aa0 *); + const s6e8aa0_gamma_table *gamma_tables; +}; + +static void s6e8aa0_apply_level_1_key(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf0, 0x5a, 0x5a); +} + +static u8 s6e8aa0_apply_aid_panel_cond(unsigned int aid) +{ + u8 ret; + + switch (aid) { + case AID_1: + ret = 0x60; + break; + case AID_2: + ret = 0x80; + break; + case AID_3: + ret = 0xA0; + break; + default: + ret = 0x04; + break; + } + + return ret; +} + +static int s6e8aa0_panel_cond_set(struct s6e8aa0 *lcd) +{ + return mipi_dsi_dcs_write_static_seq(lcd->dev, 0, + 0xf8, 0x19, 0x35, 0x00, 0x00, 0x00, 0x94, 0x00, 0x3c, + 0x78, 0x10, 0x27, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, + 0x23, 0x6e, 0xc0, 0xc1, 0x01, 0x81, 0xc1, 0x00, 0xc3, + 0xf6, 0xf6, 0xc1 + ); +} + +static int s6e8aa0_panel_cond_set_v142(struct s6e8aa0 *lcd) +{ + u8 aid = s6e8aa0_apply_aid_panel_cond(lcd->aid); + u8 cfg = 0x3d; + u8 clk_con = 0xc8; + u8 int_con = 0x08; + u8 bictl_con = 0x48; + u8 em_clk1_con = 0xff; + u8 em_clk2_con = 0xff; + u8 em_int_con = 0xc8; + + if (lcd->pdata->flip_vertical) { + /* GTCON */ + cfg &= ~(PANELCTL_GTCON_MASK); + cfg |= (PANELCTL_GTCON_110); + } + + if (lcd->pdata->flip_horizontal) { + /* SS */ + cfg &= ~(PANELCTL_SS_MASK); + cfg |= (PANELCTL_SS_1_800); + } + + if (lcd->pdata->flip_horizontal || lcd->pdata->flip_vertical) { + /* CLK1,2_CON */ + clk_con &= ~(PANELCTL_CLK1_CON_MASK | + PANELCTL_CLK2_CON_MASK); + clk_con |= (PANELCTL_CLK1_000 | PANELCTL_CLK2_001); + + /* INT1,2_CON */ + int_con &= ~(PANELCTL_INT1_CON_MASK | + PANELCTL_INT2_CON_MASK); + int_con |= (PANELCTL_INT1_000 | PANELCTL_INT2_001); + + /* BICTL,B_CON */ + bictl_con &= ~(PANELCTL_BICTL_CON_MASK | + PANELCTL_BICTLB_CON_MASK); + bictl_con |= (PANELCTL_BICTL_000 | + PANELCTL_BICTLB_001); + + /* EM_CLK1,1B_CON */ + em_clk1_con &= ~(PANELCTL_EM_CLK1_CON_MASK | + PANELCTL_EM_CLK1B_CON_MASK); + em_clk1_con |= (PANELCTL_EM_CLK1_110 | + PANELCTL_EM_CLK1B_110); + + /* EM_CLK2,2B_CON */ + em_clk2_con &= ~(PANELCTL_EM_CLK2_CON_MASK | + PANELCTL_EM_CLK2B_CON_MASK); + em_clk2_con |= (PANELCTL_EM_CLK2_110 | + PANELCTL_EM_CLK2B_110); + + /* EM_INT1,2_CON */ + em_int_con &= ~(PANELCTL_EM_INT1_CON_MASK | + PANELCTL_EM_INT2_CON_MASK); + em_int_con |= (PANELCTL_EM_INT1_000 | + PANELCTL_EM_INT2_001); + } + + return mipi_dsi_dcs_write_seq(lcd->dev, 0, 0xf8, cfg, 0x35, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x3c, 0x78, 0x08, 0x27, 0x7d, 0x3f, 0x00, + 0x00, 0x00, 0x20, aid, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x07, + 0x07, 0x23, 0x23, 0xc0, clk_con, int_con, bictl_con, 0xc1, 0x00, + 0xc1, em_clk1_con, em_clk2_con, em_int_con); +} + +static void s6e8aa0_display_condition_set(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf2, 0x80, 0x03, 0x0d); +} + +static void s6e8aa0_etc_source_control(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf6, 0x00, 0x02, 0x00); +} + +static u8 s6e8aa0_pentile_ctl_table[] = { + 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xff, 0x44, 0x44, 0xc0, 0x00 +}; + +static u8 s6e8aa0_pentile_ctl_table_v32[] = { + 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xc0, 0x44, 0x44, 0xc0, 0x00 +}; + +static int s6e8aa0_etc_pentile_control(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *v = lcd->variant; + + return mipi_dsi_dcs_write(lcd->dev, 0, v->pentile_ctl_table, + v->pentile_ctl_len); +} + +static const u8 s6e8aa0_power_ctl_table[] = { + 0xf4, 0xcf, 0x0a, 0x12, 0x10, 0x1e, 0x33, 0x02 +}; + +static const u8 s6e8aa0_power_ctl_table_v32[] = { + 0xf4, 0xcf, 0x0a, 0x15, 0x10, 0x19, 0x33, 0x02 +}; + +static int s6e8aa0_etc_power_control(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *v = lcd->variant; + + return mipi_dsi_dcs_write(lcd->dev, 0, v->power_ctl_table, + v->power_ctl_len); +} + +static int s6e8aa0_etc_elvss_control(struct s6e8aa0 *lcd) +{ + u8 id = !lcd->id ? 0x95 : 0; + + return mipi_dsi_dcs_write_seq(lcd->dev, 0, 0xb1, 0x04, id); +} + +static int s6e8aa0_elvss_nvm_set(struct s6e8aa0 *lcd) +{ + return mipi_dsi_dcs_write_static_seq(lcd->dev, 0, + 0xd9, 0x14, 0x40, 0x0c, 0xcb, 0xce, 0x6e, 0xc4, 0x07, + 0x40, 0x41, 0xc1, 0x00, 0x60, 0x19); +}; + +static int s6e8aa0_elvss_nvm_set_v142(struct s6e8aa0 *lcd) +{ + u8 br; + + switch (lcd->brightness) { + case 0 ... 6: /* 30cd ~ 100cd */ + br = 0xdf; + break; + case 7 ... 11: /* 120cd ~ 150cd */ + br = 0xdd; + break; + case 12 ... 15: /* 180cd ~ 210cd */ + default: + br = 0xd9; + break; + case 16 ... 24: /* 240cd ~ 300cd */ + br = 0xd0; + break; + } + + return mipi_dsi_dcs_write_seq(lcd->dev, 0, 0xd9, 0x14, 0x40, 0x0c, 0xcb, + 0xce, 0x6e, 0xc4, 0x0f, 0x40, 0x41, br, 0x00, 0x60, 0x19); +} + +static void s6e8aa0_apply_level_2_key(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xfc, 0x5a, 0x5a); +} + +static void s6e8aa0_enable_mtp_register(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf1, 0x5a, 0x5a); +} + +static void s6e8aa0_disable_mtp_register(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf1, 0xa5, 0xa5); +} + +static void s6e8aa0_read_id(struct s6e8aa0 *lcd, u8 *mtp_id) +{ + unsigned int addr = 0xd1; /* MTP ID */ + + mipi_dsi_dcs_read(lcd->dev, 0, addr, mtp_id, 3); +} + +static unsigned int s6e8aa0_read_mtp(struct s6e8aa0 *lcd, u8 *mtp_data) +{ + unsigned int ret; + unsigned int addr = 0xd3; /* MTP addr */ + + s6e8aa0_enable_mtp_register(lcd); + + ret = mipi_dsi_dcs_read(lcd->dev, 0, addr, mtp_data, LDI_MTP_LENGTH); + if (ret) + dev_err(&lcd->dev->dev, "%s: dsi read error\n", __func__); + + s6e8aa0_disable_mtp_register(lcd); + + return ret; +} + +static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v142[GAMMA_LEVEL_NUM] = { + { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x62, 0x55, 0x55, + 0xaf, 0xb1, 0xb1, 0xbd, 0xce, 0xb7, 0x9a, 0xb1, + 0x90, 0xb2, 0xc4, 0xae, 0x00, 0x60, 0x00, 0x40, + 0x00, 0x70, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x74, 0x68, 0x69, + 0xb8, 0xc1, 0xb7, 0xbd, 0xcd, 0xb8, 0x93, 0xab, + 0x88, 0xb4, 0xc4, 0xb1, 0x00, 0x6b, 0x00, 0x4d, + 0x00, 0x7d, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x95, 0x8a, 0x89, + 0xb4, 0xc6, 0xb2, 0xc5, 0xd2, 0xbf, 0x90, 0xa8, + 0x85, 0xb5, 0xc4, 0xb3, 0x00, 0x7b, 0x00, 0x5d, + 0x00, 0x8f, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9f, 0x98, 0x92, + 0xb3, 0xc4, 0xb0, 0xbc, 0xcc, 0xb4, 0x91, 0xa6, + 0x87, 0xb5, 0xc5, 0xb4, 0x00, 0x87, 0x00, 0x6a, + 0x00, 0x9e, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x99, 0x93, 0x8b, + 0xb2, 0xc2, 0xb0, 0xbd, 0xce, 0xb4, 0x90, 0xa6, + 0x87, 0xb3, 0xc3, 0xb2, 0x00, 0x8d, 0x00, 0x70, + 0x00, 0xa4, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xa5, 0x99, + 0xb2, 0xc2, 0xb0, 0xbb, 0xcd, 0xb1, 0x93, 0xa7, + 0x8a, 0xb2, 0xc1, 0xb0, 0x00, 0x92, 0x00, 0x75, + 0x00, 0xaa, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa0, 0xa0, 0x93, + 0xb6, 0xc4, 0xb4, 0xb5, 0xc8, 0xaa, 0x94, 0xa9, + 0x8c, 0xb2, 0xc0, 0xb0, 0x00, 0x97, 0x00, 0x7a, + 0x00, 0xaf, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xa7, 0x96, + 0xb3, 0xc2, 0xb0, 0xba, 0xcb, 0xb0, 0x94, 0xa8, + 0x8c, 0xb0, 0xbf, 0xaf, 0x00, 0x9f, 0x00, 0x83, + 0x00, 0xb9, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9d, 0xa2, 0x90, + 0xb6, 0xc5, 0xb3, 0xb8, 0xc9, 0xae, 0x94, 0xa8, + 0x8d, 0xaf, 0xbd, 0xad, 0x00, 0xa4, 0x00, 0x88, + 0x00, 0xbf, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa6, 0xac, 0x97, + 0xb4, 0xc4, 0xb1, 0xbb, 0xcb, 0xb2, 0x93, 0xa7, + 0x8d, 0xae, 0xbc, 0xad, 0x00, 0xa7, 0x00, 0x8c, + 0x00, 0xc3, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa2, 0xa9, 0x93, + 0xb6, 0xc5, 0xb2, 0xba, 0xc9, 0xb0, 0x93, 0xa7, + 0x8d, 0xae, 0xbb, 0xac, 0x00, 0xab, 0x00, 0x90, + 0x00, 0xc8, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0x9e, 0xa6, 0x8f, + 0xb7, 0xc6, 0xb3, 0xb8, 0xc8, 0xb0, 0x93, 0xa6, + 0x8c, 0xae, 0xbb, 0xad, 0x00, 0xae, 0x00, 0x93, + 0x00, 0xcc, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xab, 0xb4, 0x9c, + 0xb3, 0xc3, 0xaf, 0xb7, 0xc7, 0xaf, 0x93, 0xa6, + 0x8c, 0xaf, 0xbc, 0xad, 0x00, 0xb1, 0x00, 0x97, + 0x00, 0xcf, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa6, 0xb1, 0x98, + 0xb1, 0xc2, 0xab, 0xba, 0xc9, 0xb2, 0x93, 0xa6, + 0x8d, 0xae, 0xba, 0xab, 0x00, 0xb5, 0x00, 0x9b, + 0x00, 0xd4, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xae, 0x94, + 0xb2, 0xc3, 0xac, 0xbb, 0xca, 0xb4, 0x91, 0xa4, + 0x8a, 0xae, 0xba, 0xac, 0x00, 0xb8, 0x00, 0x9e, + 0x00, 0xd8, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xab, 0xb7, 0x9c, + 0xae, 0xc0, 0xa9, 0xba, 0xc9, 0xb3, 0x92, 0xa5, + 0x8b, 0xad, 0xb9, 0xab, 0x00, 0xbb, 0x00, 0xa1, + 0x00, 0xdc, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb4, 0x97, + 0xb0, 0xc1, 0xaa, 0xb9, 0xc8, 0xb2, 0x92, 0xa5, + 0x8c, 0xae, 0xb9, 0xab, 0x00, 0xbe, 0x00, 0xa4, + 0x00, 0xdf, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xb0, 0x94, + 0xb0, 0xc2, 0xab, 0xbb, 0xc9, 0xb3, 0x91, 0xa4, + 0x8b, 0xad, 0xb8, 0xaa, 0x00, 0xc1, 0x00, 0xa8, + 0x00, 0xe2, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa3, 0xb0, 0x94, + 0xae, 0xbf, 0xa8, 0xb9, 0xc8, 0xb3, 0x92, 0xa4, + 0x8b, 0xad, 0xb7, 0xa9, 0x00, 0xc4, 0x00, 0xab, + 0x00, 0xe6, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb6, 0x98, + 0xaf, 0xc0, 0xa8, 0xb8, 0xc7, 0xb2, 0x93, 0xa5, + 0x8d, 0xad, 0xb7, 0xa9, 0x00, 0xc7, 0x00, 0xae, + 0x00, 0xe9, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb3, 0x95, + 0xaf, 0xc1, 0xa9, 0xb9, 0xc8, 0xb3, 0x92, 0xa4, + 0x8b, 0xad, 0xb7, 0xaa, 0x00, 0xc9, 0x00, 0xb0, + 0x00, 0xec, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb3, 0x95, + 0xac, 0xbe, 0xa6, 0xbb, 0xc9, 0xb4, 0x90, 0xa3, + 0x8a, 0xad, 0xb7, 0xa9, 0x00, 0xcc, 0x00, 0xb4, + 0x00, 0xf0, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa0, 0xb0, 0x91, + 0xae, 0xc0, 0xa6, 0xba, 0xc8, 0xb4, 0x91, 0xa4, + 0x8b, 0xad, 0xb7, 0xa9, 0x00, 0xcf, 0x00, 0xb7, + 0x00, 0xf3, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa7, 0xb8, 0x98, + 0xab, 0xbd, 0xa4, 0xbb, 0xc9, 0xb5, 0x91, 0xa3, + 0x8b, 0xac, 0xb6, 0xa8, 0x00, 0xd1, 0x00, 0xb9, + 0x00, 0xf6, + }, { + 0xfa, 0x01, 0x71, 0x31, 0x7b, 0xa4, 0xb5, 0x95, + 0xa9, 0xbc, 0xa1, 0xbb, 0xc9, 0xb5, 0x91, 0xa3, + 0x8a, 0xad, 0xb6, 0xa8, 0x00, 0xd6, 0x00, 0xbf, + 0x00, 0xfc, + }, +}; + +static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v96[GAMMA_LEVEL_NUM] = { + { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xdf, 0x1f, 0xd7, 0xdc, 0xb7, 0xe1, 0xc0, 0xaf, + 0xc4, 0xd2, 0xd0, 0xcf, 0x00, 0x4d, 0x00, 0x40, + 0x00, 0x5f, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd5, 0x35, 0xcf, 0xdc, 0xc1, 0xe1, 0xbf, 0xb3, + 0xc1, 0xd2, 0xd1, 0xce, 0x00, 0x53, 0x00, 0x46, + 0x00, 0x67, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd2, 0x64, 0xcf, 0xdb, 0xc6, 0xe1, 0xbd, 0xb3, + 0xbd, 0xd2, 0xd2, 0xce, 0x00, 0x59, 0x00, 0x4b, + 0x00, 0x6e, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd0, 0x7c, 0xcf, 0xdb, 0xc9, 0xe0, 0xbc, 0xb4, + 0xbb, 0xcf, 0xd1, 0xcc, 0x00, 0x5f, 0x00, 0x50, + 0x00, 0x75, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd0, 0x8e, 0xd1, 0xdb, 0xcc, 0xdf, 0xbb, 0xb6, + 0xb9, 0xd0, 0xd1, 0xcd, 0x00, 0x63, 0x00, 0x54, + 0x00, 0x7a, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd1, 0x9e, 0xd5, 0xda, 0xcd, 0xdd, 0xbb, 0xb7, + 0xb9, 0xce, 0xce, 0xc9, 0x00, 0x68, 0x00, 0x59, + 0x00, 0x81, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0xff, + 0xd0, 0xa5, 0xd6, 0xda, 0xcf, 0xdd, 0xbb, 0xb7, + 0xb8, 0xcc, 0xcd, 0xc7, 0x00, 0x6c, 0x00, 0x5c, + 0x00, 0x86, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xfe, + 0xd0, 0xae, 0xd7, 0xd9, 0xd0, 0xdb, 0xb9, 0xb6, + 0xb5, 0xca, 0xcc, 0xc5, 0x00, 0x74, 0x00, 0x63, + 0x00, 0x90, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xf9, + 0xcf, 0xb0, 0xd6, 0xd9, 0xd1, 0xdb, 0xb9, 0xb6, + 0xb4, 0xca, 0xcb, 0xc5, 0x00, 0x77, 0x00, 0x66, + 0x00, 0x94, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xff, 0x1f, 0xf7, + 0xcf, 0xb3, 0xd7, 0xd8, 0xd1, 0xd9, 0xb7, 0xb6, + 0xb3, 0xc9, 0xca, 0xc3, 0x00, 0x7b, 0x00, 0x69, + 0x00, 0x99, + + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xfd, 0x2f, 0xf7, + 0xdf, 0xb5, 0xd6, 0xd8, 0xd1, 0xd8, 0xb6, 0xb5, + 0xb2, 0xca, 0xcb, 0xc4, 0x00, 0x7e, 0x00, 0x6c, + 0x00, 0x9d, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xfa, 0x2f, 0xf5, + 0xce, 0xb6, 0xd5, 0xd7, 0xd2, 0xd8, 0xb6, 0xb4, + 0xb0, 0xc7, 0xc9, 0xc1, 0x00, 0x84, 0x00, 0x71, + 0x00, 0xa5, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf7, 0x2f, 0xf2, + 0xce, 0xb9, 0xd5, 0xd8, 0xd2, 0xd8, 0xb4, 0xb4, + 0xaf, 0xc7, 0xc9, 0xc1, 0x00, 0x87, 0x00, 0x73, + 0x00, 0xa8, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf5, 0x2f, 0xf0, + 0xdf, 0xba, 0xd5, 0xd7, 0xd2, 0xd7, 0xb4, 0xb4, + 0xaf, 0xc5, 0xc7, 0xbf, 0x00, 0x8a, 0x00, 0x76, + 0x00, 0xac, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xf2, 0x2f, 0xed, + 0xcE, 0xbb, 0xd4, 0xd6, 0xd2, 0xd6, 0xb5, 0xb4, + 0xaF, 0xc5, 0xc7, 0xbf, 0x00, 0x8c, 0x00, 0x78, + 0x00, 0xaf, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xef, 0x2f, 0xeb, + 0xcd, 0xbb, 0xd2, 0xd7, 0xd3, 0xd6, 0xb3, 0xb4, + 0xae, 0xc5, 0xc6, 0xbe, 0x00, 0x91, 0x00, 0x7d, + 0x00, 0xb6, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xee, 0x2f, 0xea, + 0xce, 0xbd, 0xd4, 0xd6, 0xd2, 0xd5, 0xb2, 0xb3, + 0xad, 0xc3, 0xc4, 0xbb, 0x00, 0x94, 0x00, 0x7f, + 0x00, 0xba, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xec, 0x2f, 0xe8, + 0xce, 0xbe, 0xd3, 0xd6, 0xd3, 0xd5, 0xb2, 0xb2, + 0xac, 0xc3, 0xc5, 0xbc, 0x00, 0x96, 0x00, 0x81, + 0x00, 0xbd, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xeb, 0x2f, 0xe7, + 0xce, 0xbf, 0xd3, 0xd6, 0xd2, 0xd5, 0xb1, 0xb2, + 0xab, 0xc2, 0xc4, 0xbb, 0x00, 0x99, 0x00, 0x83, + 0x00, 0xc0, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xef, 0x5f, 0xe9, + 0xca, 0xbf, 0xd3, 0xd5, 0xd2, 0xd4, 0xb2, 0xb2, + 0xab, 0xc1, 0xc4, 0xba, 0x00, 0x9b, 0x00, 0x85, + 0x00, 0xc3, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xea, 0x5f, 0xe8, + 0xee, 0xbf, 0xd2, 0xd5, 0xd2, 0xd4, 0xb1, 0xb2, + 0xab, 0xc1, 0xc2, 0xb9, 0x00, 0x9D, 0x00, 0x87, + 0x00, 0xc6, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe9, 0x5f, 0xe7, + 0xcd, 0xbf, 0xd2, 0xd6, 0xd2, 0xd4, 0xb1, 0xb2, + 0xab, 0xbe, 0xc0, 0xb7, 0x00, 0xa1, 0x00, 0x8a, + 0x00, 0xca, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe8, 0x61, 0xe6, + 0xcd, 0xbf, 0xd1, 0xd6, 0xd3, 0xd4, 0xaf, 0xb0, + 0xa9, 0xbe, 0xc1, 0xb7, 0x00, 0xa3, 0x00, 0x8b, + 0x00, 0xce, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe8, 0x62, 0xe5, + 0xcc, 0xc0, 0xd0, 0xd6, 0xd2, 0xd4, 0xaf, 0xb1, + 0xa9, 0xbd, 0xc0, 0xb6, 0x00, 0xa5, 0x00, 0x8d, + 0x00, 0xd0, + }, { + 0xfa, 0x01, 0x1f, 0x1f, 0x1f, 0xe7, 0x7f, 0xe3, + 0xcc, 0xc1, 0xd0, 0xd5, 0xd3, 0xd3, 0xae, 0xaf, + 0xa8, 0xbe, 0xc0, 0xb7, 0x00, 0xa8, 0x00, 0x90, + 0x00, 0xd3, + } +}; + +static const s6e8aa0_gamma_table s6e8aa0_gamma_tables_v32[GAMMA_LEVEL_NUM] = { + { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0x72, 0x5e, 0x6b, + 0xa1, 0xa7, 0x9a, 0xb4, 0xcb, 0xb8, 0x92, 0xac, + 0x97, 0xb4, 0xc3, 0xb5, 0x00, 0x4e, 0x00, 0x37, + 0x00, 0x58, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0x85, 0x71, 0x7d, + 0xa6, 0xb6, 0xa1, 0xb5, 0xca, 0xba, 0x93, 0xac, + 0x98, 0xb2, 0xc0, 0xaf, 0x00, 0x59, 0x00, 0x43, + 0x00, 0x64, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa4, 0x94, 0x9e, + 0xa0, 0xbb, 0x9c, 0xc3, 0xd2, 0xc6, 0x93, 0xaa, + 0x95, 0xb7, 0xc2, 0xb4, 0x00, 0x65, 0x00, 0x50, + 0x00, 0x74, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xa1, 0xa6, + 0xa0, 0xb9, 0x9b, 0xc3, 0xd1, 0xc8, 0x90, 0xa6, + 0x90, 0xbb, 0xc3, 0xb7, 0x00, 0x6f, 0x00, 0x5b, + 0x00, 0x80, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa6, 0x9d, 0x9f, + 0x9f, 0xb8, 0x9a, 0xc7, 0xd5, 0xcc, 0x90, 0xa5, + 0x8f, 0xb8, 0xc1, 0xb6, 0x00, 0x74, 0x00, 0x60, + 0x00, 0x85, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb3, 0xae, 0xae, + 0x9e, 0xb7, 0x9a, 0xc8, 0xd6, 0xce, 0x91, 0xa6, + 0x90, 0xb6, 0xc0, 0xb3, 0x00, 0x78, 0x00, 0x65, + 0x00, 0x8a, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xa9, 0xa8, + 0xa3, 0xb9, 0x9e, 0xc4, 0xd3, 0xcb, 0x94, 0xa6, + 0x90, 0xb6, 0xbf, 0xb3, 0x00, 0x7c, 0x00, 0x69, + 0x00, 0x8e, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xaf, 0xaf, 0xa9, + 0xa5, 0xbc, 0xa2, 0xc7, 0xd5, 0xcd, 0x93, 0xa5, + 0x8f, 0xb4, 0xbd, 0xb1, 0x00, 0x83, 0x00, 0x70, + 0x00, 0x96, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xab, 0xa3, + 0xaa, 0xbf, 0xa7, 0xc5, 0xd3, 0xcb, 0x93, 0xa5, + 0x8f, 0xb2, 0xbb, 0xb0, 0x00, 0x86, 0x00, 0x74, + 0x00, 0x9b, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb1, 0xb5, 0xab, + 0xab, 0xc0, 0xa9, 0xc7, 0xd4, 0xcc, 0x94, 0xa4, + 0x8f, 0xb1, 0xbb, 0xaf, 0x00, 0x8a, 0x00, 0x77, + 0x00, 0x9e, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb2, 0xa7, + 0xae, 0xc2, 0xab, 0xc5, 0xd3, 0xca, 0x93, 0xa4, + 0x8f, 0xb1, 0xba, 0xae, 0x00, 0x8d, 0x00, 0x7b, + 0x00, 0xa2, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xaf, 0xa3, + 0xb0, 0xc3, 0xae, 0xc4, 0xd1, 0xc8, 0x93, 0xa4, + 0x8f, 0xb1, 0xba, 0xaf, 0x00, 0x8f, 0x00, 0x7d, + 0x00, 0xa5, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb4, 0xbd, 0xaf, + 0xae, 0xc1, 0xab, 0xc2, 0xd0, 0xc6, 0x94, 0xa4, + 0x8f, 0xb1, 0xba, 0xaf, 0x00, 0x92, 0x00, 0x80, + 0x00, 0xa8, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xb9, 0xac, + 0xad, 0xc1, 0xab, 0xc4, 0xd1, 0xc7, 0x95, 0xa4, + 0x90, 0xb0, 0xb9, 0xad, 0x00, 0x95, 0x00, 0x84, + 0x00, 0xac, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb6, 0xa7, + 0xaf, 0xc2, 0xae, 0xc5, 0xd1, 0xc7, 0x93, 0xa3, + 0x8e, 0xb0, 0xb9, 0xad, 0x00, 0x98, 0x00, 0x86, + 0x00, 0xaf, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb4, 0xbf, 0xaf, + 0xad, 0xc1, 0xab, 0xc3, 0xd0, 0xc6, 0x94, 0xa3, + 0x8f, 0xaf, 0xb8, 0xac, 0x00, 0x9a, 0x00, 0x89, + 0x00, 0xb2, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xbc, 0xac, + 0xaf, 0xc2, 0xad, 0xc2, 0xcf, 0xc4, 0x94, 0xa3, + 0x90, 0xaf, 0xb8, 0xad, 0x00, 0x9c, 0x00, 0x8b, + 0x00, 0xb5, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb9, 0xa7, + 0xb1, 0xc4, 0xaf, 0xc3, 0xcf, 0xc5, 0x94, 0xa3, + 0x8f, 0xae, 0xb7, 0xac, 0x00, 0x9f, 0x00, 0x8e, + 0x00, 0xb8, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xb9, 0xa7, + 0xaf, 0xc2, 0xad, 0xc1, 0xce, 0xc3, 0x95, 0xa3, + 0x90, 0xad, 0xb6, 0xab, 0x00, 0xa2, 0x00, 0x91, + 0x00, 0xbb, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb1, 0xbe, 0xac, + 0xb1, 0xc4, 0xaf, 0xc1, 0xcd, 0xc1, 0x95, 0xa4, + 0x91, 0xad, 0xb6, 0xab, 0x00, 0xa4, 0x00, 0x93, + 0x00, 0xbd, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbb, 0xa8, + 0xb3, 0xc5, 0xb2, 0xc1, 0xcd, 0xc2, 0x95, 0xa3, + 0x90, 0xad, 0xb6, 0xab, 0x00, 0xa6, 0x00, 0x95, + 0x00, 0xc0, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbb, 0xa8, + 0xb0, 0xc3, 0xaf, 0xc2, 0xce, 0xc2, 0x94, 0xa2, + 0x90, 0xac, 0xb6, 0xab, 0x00, 0xa8, 0x00, 0x98, + 0x00, 0xc3, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xa9, 0xb8, 0xa5, + 0xb3, 0xc5, 0xb2, 0xc1, 0xcc, 0xc0, 0x95, 0xa2, + 0x90, 0xad, 0xb6, 0xab, 0x00, 0xaa, 0x00, 0x9a, + 0x00, 0xc5, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xb0, 0xc0, 0xac, + 0xb0, 0xc3, 0xaf, 0xc1, 0xcd, 0xc1, 0x95, 0xa2, + 0x90, 0xac, 0xb5, 0xa9, 0x00, 0xac, 0x00, 0x9c, + 0x00, 0xc8, + }, { + 0xfa, 0x01, 0x43, 0x14, 0x45, 0xad, 0xbd, 0xa8, + 0xaf, 0xc2, 0xaf, 0xc1, 0xcc, 0xc0, 0x95, 0xa2, + 0x90, 0xac, 0xb5, 0xaa, 0x00, 0xb1, 0x00, 0xa1, + 0x00, 0xcc, + }, +}; + +static int s6e8aa0_brightness_set(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *variant = lcd->variant; + int ret; + + ret = mipi_dsi_dcs_write(lcd->dev, 0, + variant->gamma_tables[lcd->brightness], + sizeof(variant->gamma_tables[lcd->brightness])); + if (ret < 0) + return ret; + + /* update gamma table. */ + ret = mipi_dsi_dcs_write_static_seq(lcd->dev, 0, 0xf7, 0x03); + if (ret < 0) + return ret; + + return 0; +} + +static int s6e8aa0_brightness_set_v142(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *variant = lcd->variant; + int ret; + + ret = variant->elvss_nvm_set(lcd); + if (ret < 0) + return ret; + + return s6e8aa0_brightness_set(lcd); +} + +static int s6e8aa0_panel_init(struct s6e8aa0 *lcd) +{ + const struct s6e8aa0_variant *variant = lcd->variant; + + s6e8aa0_apply_level_1_key(lcd); + s6e8aa0_apply_level_2_key(lcd); + msleep(20); + + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, MIPI_DCS_EXIT_SLEEP_MODE); + msleep(40); + + variant->panel_cond_set(lcd); + s6e8aa0_display_condition_set(lcd); + + s6e8aa0_brightness_set(lcd); + + s6e8aa0_etc_source_control(lcd); + s6e8aa0_etc_pentile_control(lcd); + variant->elvss_nvm_set(lcd); + s6e8aa0_etc_power_control(lcd); + s6e8aa0_etc_elvss_control(lcd); + msleep(lcd->pdata->init_delay); + + dev_dbg(&lcd->dev->dev, "panel init sequence done.\n"); + + return 0; +} + +static int s6e8aa0_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static int s6e8aa0_update_status(struct backlight_device *bd) +{ + struct s6e8aa0 *lcd = bl_get_data(bd); + const struct s6e8aa0_variant *variant = lcd->variant; + int ret; + + mutex_lock(&lcd->mutex); + + lcd->brightness = bd->props.brightness; + + if (!lcd->power) + goto unlock; + + ret = variant->brightness_set(lcd); + if (ret) { + dev_err(&lcd->dev->dev, "lcd brightness setting failed.\n"); + goto unlock; + } + +unlock: + mutex_unlock(&lcd->mutex); + + return 0; +} + +static const struct backlight_ops s6e8aa0_backlight_ops = { + .get_brightness = s6e8aa0_get_brightness, + .update_status = s6e8aa0_update_status, +}; + +static int s6e8aa0_set_stream(struct s6e8aa0 *lcd, bool on); + +static int s6e8aa0_set_power(struct lcd_device *ld, int power) +{ + struct s6e8aa0 *lcd = lcd_get_data(ld); + int ret = 0; + + mutex_lock(&lcd->mutex); + + ret = s6e8aa0_set_stream(lcd, power == FB_BLANK_UNBLANK); + + if (ret) + goto unlock; + + lcd->power = power; + +unlock: + mutex_unlock(&lcd->mutex); + + return ret; +} + +static int s6e8aa0_get_power(struct lcd_device *ld) +{ + struct s6e8aa0 *lcd = lcd_get_data(ld); + + return lcd->power; +} + +static struct lcd_ops s6e8aa0_lcd_ops = { + .set_power = s6e8aa0_set_power, + .get_power = s6e8aa0_get_power, +}; + +static const struct s6e8aa0_variant s6e8aa0_variants[] = { + { + .version = 32, + .panel_cond_set = s6e8aa0_panel_cond_set, + .pentile_ctl_table = s6e8aa0_pentile_ctl_table_v32, + .pentile_ctl_len = ARRAY_SIZE(s6e8aa0_pentile_ctl_table_v32), + .power_ctl_table = s6e8aa0_power_ctl_table_v32, + .power_ctl_len = ARRAY_SIZE(s6e8aa0_power_ctl_table_v32), + .elvss_nvm_set = s6e8aa0_elvss_nvm_set, + .brightness_set = s6e8aa0_brightness_set, + .gamma_tables = s6e8aa0_gamma_tables_v32, + }, { + .version = 142, + .panel_cond_set = s6e8aa0_panel_cond_set_v142, + .pentile_ctl_table = s6e8aa0_pentile_ctl_table, + .pentile_ctl_len = ARRAY_SIZE(s6e8aa0_pentile_ctl_table), + .power_ctl_table = s6e8aa0_power_ctl_table, + .power_ctl_len = ARRAY_SIZE(s6e8aa0_power_ctl_table), + .elvss_nvm_set = s6e8aa0_elvss_nvm_set_v142, + .brightness_set = s6e8aa0_brightness_set_v142, + .gamma_tables = s6e8aa0_gamma_tables_v142, + }, { + /* FIXME: support M0 panel v96 + * need to suitable panel specification + */ + .version = 96, + .panel_cond_set = s6e8aa0_panel_cond_set, + .pentile_ctl_table = s6e8aa0_pentile_ctl_table_v32, + .pentile_ctl_len = ARRAY_SIZE(s6e8aa0_pentile_ctl_table_v32), + .power_ctl_table = s6e8aa0_power_ctl_table_v32, + .power_ctl_len = ARRAY_SIZE(s6e8aa0_power_ctl_table_v32), + .elvss_nvm_set = s6e8aa0_elvss_nvm_set, + .brightness_set = s6e8aa0_brightness_set, + .gamma_tables = s6e8aa0_gamma_tables_v96, + + }, { + /* FIXME: support U1HD panel v210 + * need to suitable panel specification + */ + .version = 210, + .panel_cond_set = s6e8aa0_panel_cond_set_v142, + .pentile_ctl_table = s6e8aa0_pentile_ctl_table, + .pentile_ctl_len = ARRAY_SIZE(s6e8aa0_pentile_ctl_table), + .power_ctl_table = s6e8aa0_power_ctl_table, + .power_ctl_len = ARRAY_SIZE(s6e8aa0_power_ctl_table), + .elvss_nvm_set = s6e8aa0_elvss_nvm_set_v142, + .brightness_set = s6e8aa0_brightness_set_v142, + .gamma_tables = s6e8aa0_gamma_tables_v142, + } +}; + +static int s6e8aa0_check_mtp(struct s6e8aa0 *lcd) +{ + int ret; + u8 mtp_data[LDI_MTP_LENGTH] = {0, }; + u8 mtp_id[3] = {0, }; + int i; + + s6e8aa0_read_id(lcd, mtp_id); + if (mtp_id[0] == 0x00) { + dev_err(&lcd->dev->dev, "read id failed\n"); + return -EIO; + } + + dev_info(&lcd->dev->dev, "Read ID : 0x%2x, 0x%2x, 0x%2x\n", + mtp_id[0], mtp_id[1], mtp_id[2]); + + if (mtp_id[2] == 0x33) + dev_dbg(&lcd->dev->dev, + "ID-3 is 0xff does not support dynamic elvss\n"); + else { + dev_dbg(&lcd->dev->dev, + "ID-3 is 0x%x support dynamic elvss\n", mtp_id[2]); + dev_dbg(&lcd->dev->dev, "Dynamic ELVSS Information\n"); + } + + for (i = 0; i < ARRAY_SIZE(s6e8aa0_variants); ++i) { + if (mtp_id[1] == s6e8aa0_variants[i].version) + break; + } + if (i >= ARRAY_SIZE(s6e8aa0_variants)) { + dev_err(&lcd->dev->dev, "unsupported display version %d\n", + mtp_id[1]); + return -EINVAL; + } + + lcd->variant = &s6e8aa0_variants[i]; + lcd->id = mtp_id[2]; + lcd->aid = (mtp_id[2] >> 5); + + ret = s6e8aa0_read_mtp(lcd, mtp_data); + if (ret) { + dev_err(&lcd->dev->dev, "read mtp failed\n"); + return -EIO; + } + + return 0; +} + +static int s6e8aa0_set_sequence(struct s6e8aa0 *lcd) +{ + int ret; + + ret = s6e8aa0_check_mtp(lcd); + if (ret < 0) + return ret; + + s6e8aa0_panel_init(lcd); + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, MIPI_DCS_SET_DISPLAY_ON); + + dev_dbg(&lcd->dev->dev, "%s:done.\n", __func__); + + return 0; +} + +#ifdef CONFIG_OF +static int s6e8aa0_generic_reset(struct device *dev) +{ + struct s6e8aa0 *lcd = dev_get_drvdata(dev); + + gpio_set_value(lcd->reset_gpio, 1); + usleep_range(10000, 11000); + gpio_set_value(lcd->reset_gpio, 0); + usleep_range(10000, 11000); + gpio_set_value(lcd->reset_gpio, 1); + + return 0; +} + +static struct s6e8aa0_platform_data *s6e8aa0_parse_dt(struct s6e8aa0 *lcd) +{ + struct device_node *node = lcd->dev->dev.of_node; + struct s6e8aa0_platform_data *data; + const __be32 *prop_data; + int ret; + + data = devm_kzalloc(&lcd->dev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&lcd->dev->dev, "failed to allocate platform data.\n"); + return NULL; + } + + ret = of_get_videomode(node, &data->mode, 0); + if (ret) { + dev_err(&lcd->dev->dev, "failed to read video mode from DT\n"); + return NULL; + } + + lcd->reset_gpio = of_get_named_gpio(node, "reset-gpio", 0); + if (lcd->reset_gpio < 0) + return NULL; + + prop_data = of_get_property(node, "reset-delay", NULL); + if (!prop_data) + return NULL; + data->reset_delay = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "power-on-delay", NULL); + if (!prop_data) + return NULL; + data->power_on_delay = be32_to_cpu(*prop_data); + + prop_data = of_get_property(node, "init-delay", NULL); + if (!prop_data) + return NULL; + data->init_delay = be32_to_cpu(*prop_data); + + if (of_find_property(node, "flip-horizontal", NULL)) + data->flip_horizontal = true; + + if (of_find_property(node, "flip-vertical", NULL)) + data->flip_vertical = true; + + data->reset = s6e8aa0_generic_reset; + + return data; +} + +static struct of_device_id s6e8aa0_of_match[] = { + { .compatible = "samsung,s6e8aa0" }, + { } +}; + +MODULE_DEVICE_TABLE(of, s6e8aa0_of_match); +#else +static struct s6e8aa0_platform_data *s6e8aa0_parse_dt(struct s6e8aa0 *lcd) +{ + return NULL; +} +#endif + +static int s6e8aa0_power_on(struct s6e8aa0 *lcd) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(lcd->supplies), lcd->supplies); + if (ret) + return ret; + + msleep(lcd->pdata->power_on_delay); + + /* lcd reset */ + if (lcd->pdata->reset) { + ret = lcd->pdata->reset(&lcd->dev->dev); + if (ret) + goto err; + } + + msleep(lcd->pdata->reset_delay); + ret = mipi_dsi_set_power(lcd->dev, 1); + if (ret) + goto err; + + ret = s6e8aa0_set_sequence(lcd); + if (!ret) + return 0; + + mipi_dsi_set_power(lcd->dev, 0); +err: + regulator_bulk_disable(ARRAY_SIZE(lcd->supplies), lcd->supplies); + + return ret; +} + +static void s6e8aa0_power_off(struct s6e8aa0 *lcd) +{ + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, MIPI_DCS_ENTER_SLEEP_MODE); + mipi_dsi_dcs_write_static_seq(lcd->dev, 0, MIPI_DCS_SET_DISPLAY_OFF); + + mipi_dsi_set_power(lcd->dev, 0); + + regulator_bulk_disable(ARRAY_SIZE(lcd->supplies), lcd->supplies); +} + +static int s6e8aa0_set_stream(struct s6e8aa0 *lcd, bool on) +{ + if (on == lcd->streaming) + return 0; + + if (on) { + s6e8aa0_power_on(lcd); + mipi_dsi_set_stream(lcd->dev, true); + } else { + mipi_dsi_set_stream(lcd->dev, false); + s6e8aa0_power_off(lcd); + } + + lcd->streaming = on; + + return 0; +} + +static int s6e8aa0_probe(struct mipi_dsi_device *pdev) +{ + struct s6e8aa0 *lcd; + int ret; + + lcd = kzalloc(sizeof(struct s6e8aa0), GFP_KERNEL); + if (!lcd) { + dev_err(&pdev->dev, "failed to allocate s6e8aa0 structure.\n"); + return -ENOMEM; + } + + mipi_dsi_set_drvdata(pdev, lcd); + + lcd->dev = pdev; + lcd->pdata = (struct s6e8aa0_platform_data *)pdev->dev.platform_data; + + if (!lcd->pdata) { + lcd->pdata = s6e8aa0_parse_dt(lcd); + if (!lcd->pdata) { + dev_err(&pdev->dev, "failed to find platform data\n"); + return -ENODEV; + } + } + + lcd->supplies[0].supply = "vdd3"; + lcd->supplies[1].supply = "vci"; + ret = regulator_bulk_get(&pdev->dev, + ARRAY_SIZE(lcd->supplies), lcd->supplies); + if (ret) { + dev_err(&pdev->dev, "Failed to get regulators: %d\n", ret); + goto err_regulator_bulk_get; + } + + lcd->ld = lcd_device_register("s6e8aa0", &pdev->dev, lcd, + &s6e8aa0_lcd_ops); + if (IS_ERR(lcd->ld)) { + dev_err(&lcd->dev->dev, "failed to register lcd ops.\n"); + ret = PTR_ERR(lcd->ld); + goto err_lcd_register; + } + + lcd->bd = backlight_device_register("s6e8aa0-bl", &pdev->dev, lcd, + &s6e8aa0_backlight_ops, NULL); + if (IS_ERR(lcd->bd)) { + dev_err(&pdev->dev, "failed to register backlight ops.\n"); + ret = PTR_ERR(lcd->bd); + goto err_backlight_register; + } + + mutex_init(&lcd->mutex); + + lcd->bd->props.max_brightness = GAMMA_LEVEL_NUM - 1; + lcd->bd->props.brightness = GAMMA_LEVEL_NUM - 1; + lcd->brightness = GAMMA_LEVEL_NUM - 1; + + dev_dbg(&pdev->dev, "probed s6e8aa0 panel driver.\n"); + pdev->vm = lcd->pdata->mode; + + pdev->params = (struct mipi_dsi_interface_params) { + .format = DSI_FMT_RGB888, + .mode = DSI_MODE_VIDEO | DSI_MODE_VIDEO_BURST + | DSI_MODE_VIDEO_HFP | DSI_MODE_VIDEO_HBP + | DSI_MODE_VIDEO_HSA | DSI_MODE_EOT_PACKET + | DSI_MODE_VSYNC_FLUSH, + .data_lanes = 0xf, + .hs_clk_freq = 500000000, + .esc_clk_freq = 20000000, + }; + + s6e8aa0_set_stream(lcd, true); + + return 0; + +err_backlight_register: + lcd_device_unregister(lcd->ld); +err_lcd_register: + regulator_bulk_free(ARRAY_SIZE(lcd->supplies), lcd->supplies); +err_regulator_bulk_get: + kfree(lcd); + + return ret; +} + +static int s6e8aa0_remove(struct mipi_dsi_device *pdev) +{ + struct s6e8aa0 *lcd = mipi_dsi_get_drvdata(pdev); + + backlight_device_unregister(lcd->bd); + lcd_device_unregister(lcd->ld); + mipi_dsi_set_drvdata(pdev, NULL); + regulator_bulk_free(ARRAY_SIZE(lcd->supplies), lcd->supplies); + kfree(lcd); + + return 0; +} + +static int s6e8aa0_suspend(struct device *dev) +{ + struct s6e8aa0 *lcd = dev_get_drvdata(dev); + + dev_err(dev, "%s: %d\n", __func__, lcd->power); + + if (lcd->power != FB_BLANK_UNBLANK) + return 0; + + mipi_dsi_set_stream(lcd->dev, 0); + s6e8aa0_power_off(lcd); + + return 0; +} + +static int s6e8aa0_resume(struct device *dev) +{ + struct s6e8aa0 *lcd = dev_get_drvdata(dev); + int ret; + + dev_err(dev, "%s: %d\n", __func__, lcd->power); + + if (lcd->power != FB_BLANK_UNBLANK) + return 0; + + s6e8aa0_power_on(lcd); + + ret = mipi_dsi_set_stream(lcd->dev, 1); + dev_err(dev, "%s: dsi_set_stream=%d\n", __func__, ret); + + return 0; +} + +static struct dev_pm_ops s6e8aa0_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(s6e8aa0_suspend, s6e8aa0_resume) +}; + +static const struct mipi_dsi_device_id s6e8aa0_id[] = { + { "s6e8aa0", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(mipi_dsi, s6e8aa0_id); + +static struct mipi_dsi_driver s6e8aa0_driver = { + .probe = s6e8aa0_probe, + .remove = s6e8aa0_remove, + .driver = { + .name = "panel_s6e8aa0", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(s6e8aa0_of_match), + .pm = &s6e8aa0_pm_ops, + }, + .id_table = s6e8aa0_id +}; +module_mipi_dsi_driver(s6e8aa0_driver); + +MODULE_ALIAS("mipi-dsi:s6e8aa0"); +MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>"); +MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); +MODULE_AUTHOR("Joongmock Shin <jmock.shin@samsung.com>"); +MODULE_AUTHOR("Eunchul Kim <chulspro.kim@samsung.com>"); +MODULE_AUTHOR("Tomasz Figa <t.figa@samsung.com>"); +MODULE_DESCRIPTION("MIPI-DSI based s6e8aa0 AMOLED LCD Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-s6e8aa0.h b/include/video/panel-s6e8aa0.h new file mode 100644 index 0000000..8a32de8 --- /dev/null +++ b/include/video/panel-s6e8aa0.h @@ -0,0 +1,42 @@ +/* + * Renesas R61505-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.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. + */ + +#ifndef __PANEL_S6E8AX0_H__ +#define __PANEL_S6E8AX0_H__ + +#include <video/videomode.h> + +struct s6e8aa0_platform_data { + unsigned long width; /* Panel width in mm */ + unsigned long height; /* Panel height in mm */ + struct videomode mode; + + /* reset lcd panel device. */ + int (*reset)(struct device *dev); + + /* it indicates whether lcd panel was enabled + from bootloader or not. */ + int lcd_enabled; + /* it means delay for stable time when it becomes low to high + or high to low that is dependent on whether reset gpio is + low active or high active. */ + unsigned int reset_delay; + /* stable time needing to become lcd power on. */ + unsigned int power_on_delay; + /* stable time needing to become lcd power off. */ + unsigned int init_delay; + /* panel is reversed */ + bool flip_vertical; + bool flip_horizontal; +}; + +#endif /* __PANEL_S6E8AX0_H__ */ -- 1.8.1.2 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC PATCH 4/4] ARM: dts: exynos4210-trats: add panel and dsi nodes 2013-09-24 14:23 ` Andrzej Hajda @ 2013-09-24 14:23 ` Andrzej Hajda -1 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park The patch adds mipi-dsi-exynos bus master node and s6e8aa0 panel subnode to trats device. Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Conflicts: arch/arm/boot/dts/exynos4210-trats.dts --- arch/arm/boot/dts/exynos4210-trats.dts | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/arch/arm/boot/dts/exynos4210-trats.dts b/arch/arm/boot/dts/exynos4210-trats.dts index 94eebff..6f1a034 100644 --- a/arch/arm/boot/dts/exynos4210-trats.dts +++ b/arch/arm/boot/dts/exynos4210-trats.dts @@ -301,4 +301,58 @@ clock-frequency = <24000000>; }; }; + + dsi_0: dsi@11C80000 { + samsung,pll-stable-time = <500>; + samsung,stop-holding-count = <0x7ff>; + samsung,bta-timeout = <0xff>; + samsung,rx-timeout = <0xffff>; + samsung,pll-clk-freq = <24000000>; + samsung,cmd-allow = <0xf>; + vdd11-supply = <&vusb_reg>; + vdd18-supply = <&vmipi_reg>; + status = "okay"; + + fimd0_lcd: panel { + compatible = "samsung,s6e8aa0"; + reset-gpio = <&gpy4 5 0>; + power-on-delay= <50>; + reset-delay = <100>; + init-delay = <100>; + vdd3-supply = <&vcclcd_reg>; + vci-supply = <&vlcd_reg>; + video-source = <&dsi_0>; + flip-horizontal; + flip-vertical; + samsung,panel-width-mm = <58>; + samsung,panel-height-mm = <103>; + + display-timings { + native-mode = <&timing0>; + + timing0: timing-0 { + clock-frequency = <0>; + hactive = <720>; + vactive = <1280>; + hfront-porch = <5>; + hback-porch = <5>; + hsync-len = <5>; + vfront-porch = <13>; + vback-porch = <1>; + vsync-len = <2>; + }; + }; + }; + }; + + fimd@11c00000 { + samsung,fimd-display = <&fimd0_lcd>; + samsung,fimd-vidout-rgb; + samsung,fimd-inv-vclk; + samsung,fimd-frame-rate = <60>; + samsung,default-window = <3>; + samsung,fimd-win-bpp = <32>; + status = "okay"; + }; + }; -- 1.8.1.2 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [RFC PATCH 4/4] ARM: dts: exynos4210-trats: add panel and dsi nodes @ 2013-09-24 14:23 ` Andrzej Hajda 0 siblings, 0 replies; 14+ messages in thread From: Andrzej Hajda @ 2013-09-24 14:23 UTC (permalink / raw) To: Laurent Pinchart Cc: Andrzej Hajda, dri-devel, linux-fbdev, linux-media, Kyungmin Park The patch adds mipi-dsi-exynos bus master node and s6e8aa0 panel subnode to trats device. Signed-off-by: Andrzej Hajda <a.hajda@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Conflicts: arch/arm/boot/dts/exynos4210-trats.dts --- arch/arm/boot/dts/exynos4210-trats.dts | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/arch/arm/boot/dts/exynos4210-trats.dts b/arch/arm/boot/dts/exynos4210-trats.dts index 94eebff..6f1a034 100644 --- a/arch/arm/boot/dts/exynos4210-trats.dts +++ b/arch/arm/boot/dts/exynos4210-trats.dts @@ -301,4 +301,58 @@ clock-frequency = <24000000>; }; }; + + dsi_0: dsi@11C80000 { + samsung,pll-stable-time = <500>; + samsung,stop-holding-count = <0x7ff>; + samsung,bta-timeout = <0xff>; + samsung,rx-timeout = <0xffff>; + samsung,pll-clk-freq = <24000000>; + samsung,cmd-allow = <0xf>; + vdd11-supply = <&vusb_reg>; + vdd18-supply = <&vmipi_reg>; + status = "okay"; + + fimd0_lcd: panel { + compatible = "samsung,s6e8aa0"; + reset-gpio = <&gpy4 5 0>; + power-on-delay= <50>; + reset-delay = <100>; + init-delay = <100>; + vdd3-supply = <&vcclcd_reg>; + vci-supply = <&vlcd_reg>; + video-source = <&dsi_0>; + flip-horizontal; + flip-vertical; + samsung,panel-width-mm = <58>; + samsung,panel-height-mm = <103>; + + display-timings { + native-mode = <&timing0>; + + timing0: timing-0 { + clock-frequency = <0>; + hactive = <720>; + vactive = <1280>; + hfront-porch = <5>; + hback-porch = <5>; + hsync-len = <5>; + vfront-porch = <13>; + vback-porch = <1>; + vsync-len = <2>; + }; + }; + }; + }; + + fimd@11c00000 { + samsung,fimd-display = <&fimd0_lcd>; + samsung,fimd-vidout-rgb; + samsung,fimd-inv-vclk; + samsung,fimd-frame-rate = <60>; + samsung,default-window = <3>; + samsung,fimd-win-bpp = <32>; + status = "okay"; + }; + }; -- 1.8.1.2 ^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2013-10-10 9:57 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2013-09-24 14:23 [RFC PATCH 0/4] CDFv3: MIPI DSI bus implementation Andrzej Hajda 2013-09-24 14:23 ` Andrzej Hajda 2013-09-24 14:23 ` [RFC PATCH 1/4] mipi-dsi-bus: add MIPI DSI bus support Andrzej Hajda 2013-09-24 14:23 ` Andrzej Hajda 2013-10-07 10:47 ` Bert Kenward 2013-10-07 10:47 ` Bert Kenward 2013-10-10 9:57 ` Andrzej Hajda 2013-10-10 9:57 ` Andrzej Hajda 2013-09-24 14:23 ` [RFC PATCH 2/4] mipi-dsi-exynos: add driver Andrzej Hajda 2013-09-24 14:23 ` Andrzej Hajda 2013-09-24 14:23 ` [RFC PATCH 3/4] panel-s6e8aa0: " Andrzej Hajda 2013-09-24 14:23 ` Andrzej Hajda 2013-09-24 14:23 ` [RFC PATCH 4/4] ARM: dts: exynos4210-trats: add panel and dsi nodes Andrzej Hajda 2013-09-24 14:23 ` Andrzej Hajda
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.