* [RFC 4/5] video: panel: Add R61505 panel support
From: Laurent Pinchart @ 2012-08-17 0:49 UTC (permalink / raw)
To: linux-fbdev, dri-devel, linux-leds
Cc: linux-media, Bryan Wu, Richard Purdie, Tomi Valkeinen,
Marcus Lorentzon, Sumit Semwal, Archit Taneja, Sebastien Guiriec,
Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>
The R61505 is a SYS-80 bus panel controller from Renesas.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
drivers/video/panel/Kconfig | 9 +
drivers/video/panel/Makefile | 1 +
drivers/video/panel/panel-r61505.c | 520 ++++++++++++++++++++++++++++++++++++
include/video/panel-r61505.h | 27 ++
4 files changed, 557 insertions(+), 0 deletions(-)
create mode 100644 drivers/video/panel/panel-r61505.c
create mode 100644 include/video/panel-r61505.h
diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig
index fd0b3cf..12d7712 100644
--- a/drivers/video/panel/Kconfig
+++ b/drivers/video/panel/Kconfig
@@ -16,4 +16,13 @@ config DISPLAY_PANEL_DBI
tristate
default n
+config DISPLAY_PANEL_R61505
+ tristate "Renesas R61505-based Display Panel"
+ select DISPLAY_PANEL_DBI
+ ---help---
+ Support panels based on the Renesas R61505 panel controller.
+ Those panels are controlled through a MIPI DBI interface.
+
+ If you are in doubt, say N.
+
endif # DISPLAY_PANEL
diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile
index 2ab0520..e4fb9fe 100644
--- a/drivers/video/panel/Makefile
+++ b/drivers/video/panel/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_DISPLAY_PANEL) += panel.o
obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o
obj-$(CONFIG_DISPLAY_PANEL_DBI) += panel-dbi.o
+obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o
diff --git a/drivers/video/panel/panel-r61505.c b/drivers/video/panel/panel-r61505.c
new file mode 100644
index 0000000..e09455e
--- /dev/null
+++ b/drivers/video/panel/panel-r61505.c
@@ -0,0 +1,520 @@
+/*
+ * Renesas R61505-based Display Panels
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ * Based on SuperH MigoR Quarter VGA LCD Panel
+ * Copyright (C) 2008 Magnus Damm
+ * Based on lcd_powertip.c from Kenati Technologies Pvt Ltd.
+ * Copyright (c) 2007 Ujjwal Pande
+ *
+ * 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.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <video/panel-r61505.h>
+#include <video/panel-dbi.h>
+
+#define R61505_DEVICE_CODE 0x0000
+#define R61505_DEVICE_CODE_VALUE 0x1505
+#define R61505_DRIVER_OUTPUT_CONTROL 0x0001
+#define R61505_DRIVER_OUTPUT_CONTROL_SM (1 << 10)
+#define R61505_DRIVER_OUTPUT_CONTROL_SS (1 << 8)
+#define R61505_LCD_WAVEFORM 0x0002
+#define R61505_LCD_WAVEFORM_BC0 (1 << 9)
+#define R61505_LCD_WAVEFORM_EOR (1 << 8)
+#define R61505_ENTRY_MODE 0x0003
+#define R61505_ENTRY_MODE_TRIREG (1 << 15)
+#define R61505_ENTRY_MODE_DFM (1 << 14)
+#define R61505_ENTRY_MODE_BGR (1 << 12)
+#define R61505_ENTRY_MODE_HWM (1 << 9)
+#define R61505_ENTRY_MODE_ORG (1 << 7)
+#define R61505_ENTRY_MODE_ID1 (1 << 5)
+#define R61505_ENTRY_MODE_ID0 (1 << 4)
+#define R61505_ENTRY_MODE_AM (1 << 3)
+#define R61505_RESIZE_CONTROL 0x0004
+#define R61505_RESIZE_CONTROL_RCV(n) (((n) & 3) << 8)
+#define R61505_RESIZE_CONTROL_RCH(n) (((n) & 3) << 4)
+#define R61505_RESIZE_CONTROL_RSZ_4 (3 << 0)
+#define R61505_RESIZE_CONTROL_RSZ_2 (1 << 0)
+#define R61505_RESIZE_CONTROL_RSZ_1 (0 << 0)
+#define R61505_DISPLAY_CONTROL1 0x0007
+#define R61505_DISPLAY_CONTROL1_PTDE1 (1 << 13)
+#define R61505_DISPLAY_CONTROL1_PTDE0 (1 << 12)
+#define R61505_DISPLAY_CONTROL1_BASEE (1 << 8)
+#define R61505_DISPLAY_CONTROL1_VON (1 << 6)
+#define R61505_DISPLAY_CONTROL1_GON (1 << 5)
+#define R61505_DISPLAY_CONTROL1_DTE (1 << 4)
+#define R61505_DISPLAY_CONTROL1_COL (1 << 3)
+#define R61505_DISPLAY_CONTROL1_D1 (1 << 1)
+#define R61505_DISPLAY_CONTROL1_D0 (1 << 0)
+#define R61505_DISPLAY_CONTROL2 0x0008
+#define R61505_DISPLAY_CONTROL2_FP(n) (((n) & 0xf) << 8)
+#define R61505_DISPLAY_CONTROL2_BP(n) (((n) & 0xf) << 0)
+#define R61505_DISPLAY_CONTROL3 0x0009
+#define R61505_DISPLAY_CONTROL3_PTS(n) (((n) & 7) << 8)
+#define R61505_DISPLAY_CONTROL3_PTG(n) (((n) & 3) << 3)
+#define R61505_DISPLAY_CONTROL3_ICS(n) (((n) & 0xf) << 0)
+#define R61505_DISPLAY_CONTROL4 0x000a
+#define R61505_DISPLAY_CONTROL4_FMARKOE (1 << 3)
+#define R61505_DISPLAY_CONTROL4_FMI_6 (5 << 0)
+#define R61505_DISPLAY_CONTROL4_FMI_4 (3 << 0)
+#define R61505_DISPLAY_CONTROL4_FMI_2 (1 << 0)
+#define R61505_DISPLAY_CONTROL4_FMI_1 (0 << 0)
+#define R61505_EXT_DISPLAY_IF_CONTROL1 0x000c
+#define R61505_EXT_DISPLAY_IF_CONTROL1_ENC(n) (((n) & 7) << 12)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_RM (1 << 8)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_VSYNC (2 << 4)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_RGB (1 << 4)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_ICLK (0 << 4)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_6 (2 << 0)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_16 (1 << 0)
+#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_18 (0 << 0)
+#define R61505_FRAME_MARKER_CONTROL 0x000d
+#define R61505_FRAME_MARKER_CONTROL_FMP(n) (((n) & 0x1ff) << 0)
+#define R61505_EXT_DISPLAY_IF_CONTROL2 0x000f
+#define R61505_POWER_CONTROL1 0x0010
+#define R61505_POWER_CONTROL1_SAP (1 << 12)
+#define R61505_POWER_CONTROL1_BT(n) (((n) & 0xf) << 8)
+#define R61505_POWER_CONTROL1_APE (1 << 7)
+#define R61505_POWER_CONTROL1_AP_100 (3 << 4)
+#define R61505_POWER_CONTROL1_AP_075 (2 << 4)
+#define R61505_POWER_CONTROL1_AP_050 (1 << 4)
+#define R61505_POWER_CONTROL1_AP_HALT (0 << 4)
+#define R61505_POWER_CONTROL1_DSTB (1 << 2)
+#define R61505_POWER_CONTROL1_SLP (1 << 1)
+#define R61505_POWER_CONTROL2 0x0011
+#define R61505_POWER_CONTROL2_DC1_HALT (6 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_256 (4 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_128 (3 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_64 (2 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_32 (1 << 8)
+#define R61505_POWER_CONTROL2_DC1_FOSC_16 (0 << 8)
+#define R61505_POWER_CONTROL2_DC0_HALT (6 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC_16 (4 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC_8 (3 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC_4 (2 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC_2 (1 << 4)
+#define R61505_POWER_CONTROL2_DC0_FOSC (0 << 4)
+#define R61505_POWER_CONTROL2_VC_100 (7 << 0)
+#define R61505_POWER_CONTROL2_VC_076 (4 << 0)
+#define R61505_POWER_CONTROL2_VC_089 (1 << 0)
+#define R61505_POWER_CONTROL2_VC_094 (0 << 0)
+#define R61505_POWER_CONTROL3 0x0012
+#define R61505_POWER_CONTROL3_VCMR (1 << 8)
+#define R61505_POWER_CONTROL3_PSON (1 << 5)
+#define R61505_POWER_CONTROL3_PON (1 << 4)
+#define R61505_POWER_CONTROL3_VRH(n) (((n) & 0xf) << 0)
+#define R61505_POWER_CONTROL4 0x0013
+#define R61505_POWER_CONTROL4_VDV(n) (((n) & 0xf) << 8)
+#define R61505_POWER_CONTROL5 0x0015
+#define R61505_POWER_CONTROL5_BLDM (1 << 12)
+#define R61505_POWER_CONTROL6 0x0017
+#define R61505_POWER_CONTROL6_PSE (1 << 0)
+#define R61505_RAM_ADDR_HORZ 0x0020
+#define R61505_RAM_ADDR_VERT 0x0021
+#define R61505_RAM_DATA 0x0022
+#define R61505_POWER_CONTROL7 0x0029
+#define R61505_POWER_CONTROL7_VCM1(n) (((n) & 0x1f) << 0)
+#define R61505_GAMMA_CONTROL1 0x0030
+#define R61505_GAMMA_CONTROL2 0x0031
+#define R61505_GAMMA_CONTROL3 0x0032
+#define R61505_GAMMA_CONTROL4 0x0033
+#define R61505_GAMMA_CONTROL5 0x0034
+#define R61505_GAMMA_CONTROL6 0x0035
+#define R61505_GAMMA_CONTROL7 0x0036
+#define R61505_GAMMA_CONTROL8 0x0037
+#define R61505_GAMMA_CONTROL9 0x0038
+#define R61505_GAMMA_CONTROL10 0x0039
+#define R61505_GAMMA_CONTROL11 0x003a
+#define R61505_GAMMA_CONTROL12 0x003b
+#define R61505_GAMMA_CONTROL13 0x003c
+#define R61505_GAMMA_CONTROL14 0x003d
+#define R61505_WINDOW_HORZ_START 0x0050
+#define R61505_WINDOW_HORZ_END 0x0051
+#define R61505_WINDOW_VERT_START 0x0052
+#define R61505_WINDOW_VERT_END 0x0053
+#define R61505_DRIVER_OUTPUT_CONTROL2 0x0060
+#define R61505_DRIVER_OUTPUT_CONTROL2_GS (1 << 15)
+#define R61505_DRIVER_OUTPUT_CONTROL2_NL(n) (((n) & 0x3f) << 8)
+#define R61505_DRIVER_OUTPUT_CONTROL2_SCN(n) (((n) & 0x3f) << 0)
+#define R61505_BASE_IMG_DISPLAY_CONTROL 0x0061
+#define R61505_BASE_IMG_DISPLAY_CONTROL_NDL (1 << 2)
+#define R61505_BASE_IMG_DISPLAY_CONTROL_VLE (1 << 1)
+#define R61505_BASE_IMG_DISPLAY_CONTROL_REV (1 << 0)
+#define R61505_VERTICAL_SCROLL_CONTROL 0x006a
+#define R61505_PANEL_IF_CONTROL1 0x0090
+#define R61505_PANEL_IF_CONTROL1_DIVI(n) (((n) & 3) << 8)
+#define R61505_PANEL_IF_CONTROL1_RTNI(n) (((n) & 0x1f) << 0)
+#define R61505_PANEL_IF_CONTROL2 0x0092
+#define R61505_PANEL_IF_CONTROL2_NOWI(n) (((n) & 7) << 8)
+#define R61505_PANEL_IF_CONTROL3 0x0093
+#define R61505_PANEL_IF_CONTROL3_MCP(n) (((n) & 7) << 8)
+#define R61505_PANEL_IF_CONTROL4 0x0095
+#define R61505_PANEL_IF_CONTROL5 0x0097
+#define R61505_PANEL_IF_CONTROL6 0x0098
+#define R61505_OSCILLATION_CONTROL 0x00a4
+#define R61505_OSCILLATION_CONTROL_CALB (1 << 0)
+
+struct r61505 {
+ struct panel panel;
+ struct panel_dbi_device *dbi;
+ const struct panel_r61505_platform_data *pdata;
+};
+
+#define to_panel(p) container_of(p, struct r61505, panel)
+
+/* -----------------------------------------------------------------------------
+ * Read, write and reset
+ */
+
+/* DB0-DB7 are connected to D1-D8, and DB8-DB15 to D10-D17 */
+
+static unsigned long adjust_reg18(u16 data)
+{
+ return (((data << 1) | 0x00000001) & 0x000001ff) |
+ (((data << 2) | 0x00000200) & 0x0003fe00);
+}
+
+static void r61505_write_command(struct r61505 *panel, u16 reg)
+{
+ panel_dbi_write_command(panel->dbi, adjust_reg18(reg));
+}
+
+static void r61505_write(struct r61505 *panel, u16 reg, u16 data)
+{
+ panel_dbi_write_command(panel->dbi, adjust_reg18(reg));
+ panel_dbi_write_data(panel->dbi, adjust_reg18(data));
+}
+
+static u16 r61505_read(struct r61505 *panel, u16 reg)
+{
+ unsigned long data;
+
+ panel_dbi_write_command(panel->dbi, adjust_reg18(reg));
+ data = panel_dbi_read_data(panel->dbi);
+
+ return ((data >> 1) & 0xff) | ((data >> 2) & 0xff00);
+}
+
+static void r61505_write_array(struct r61505 *panel,
+ const u16 *data, unsigned int len)
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i += 2)
+ r61505_write(panel, data[i], data[i + 1]);
+}
+
+static void r61505_reset(struct r61505 *panel)
+{
+ if (panel->pdata->reset < 0)
+ return;
+
+ gpio_set_value(panel->pdata->reset, 0);
+ usleep_range(2000, 2500);
+ gpio_set_value(panel->pdata->reset, 1);
+ usleep_range(1000, 1500);
+}
+
+/* -----------------------------------------------------------------------------
+ * Configuration
+ */
+
+static const unsigned short sync_data[] = {
+ 0x0000, 0x0000,
+ 0x0000, 0x0000,
+ 0x0000, 0x0000,
+ 0x0000, 0x0000,
+};
+
+static const unsigned short magic0_data[] = {
+ R61505_DISPLAY_CONTROL2, R61505_DISPLAY_CONTROL2_FP(8) |
+ R61505_DISPLAY_CONTROL2_BP(8),
+ R61505_PANEL_IF_CONTROL1, R61505_PANEL_IF_CONTROL1_RTNI(26),
+ R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_D0,
+ R61505_POWER_CONTROL6, R61505_POWER_CONTROL6_PSE,
+ 0x0019, 0x0000,
+ R61505_POWER_CONTROL1, R61505_POWER_CONTROL1_SAP |
+ R61505_POWER_CONTROL1_BT(7) |
+ R61505_POWER_CONTROL1_APE |
+ R61505_POWER_CONTROL1_AP_100,
+ R61505_POWER_CONTROL2, R61505_POWER_CONTROL2_DC1_FOSC_32 |
+ R61505_POWER_CONTROL2_DC0_FOSC_2 | 6,
+ R61505_POWER_CONTROL3, R61505_POWER_CONTROL3_VCMR | 0x80 |
+ R61505_POWER_CONTROL3_PON |
+ R61505_POWER_CONTROL3_VRH(8),
+ R61505_POWER_CONTROL4, 0x1000 | R61505_POWER_CONTROL4_VDV(4),
+ R61505_POWER_CONTROL7, R61505_POWER_CONTROL7_VCM1(12),
+ R61505_POWER_CONTROL3, R61505_POWER_CONTROL3_VCMR | 0x80 |
+ R61505_POWER_CONTROL3_PSON |
+ R61505_POWER_CONTROL3_PON |
+ R61505_POWER_CONTROL3_VRH(8),
+};
+
+static const unsigned short magic1_data[] = {
+ R61505_GAMMA_CONTROL1, 0x0307,
+ R61505_GAMMA_CONTROL2, 0x0303,
+ R61505_GAMMA_CONTROL3, 0x0603,
+ R61505_GAMMA_CONTROL4, 0x0202,
+ R61505_GAMMA_CONTROL5, 0x0202,
+ R61505_GAMMA_CONTROL6, 0x0202,
+ R61505_GAMMA_CONTROL7, 0x1f1f,
+ R61505_GAMMA_CONTROL8, 0x0303,
+ R61505_GAMMA_CONTROL9, 0x0303,
+ R61505_GAMMA_CONTROL10, 0x0603,
+ R61505_GAMMA_CONTROL11, 0x0202,
+ R61505_GAMMA_CONTROL12, 0x0102,
+ R61505_GAMMA_CONTROL13, 0x0204,
+ R61505_GAMMA_CONTROL14, 0x0000,
+ R61505_DRIVER_OUTPUT_CONTROL, R61505_DRIVER_OUTPUT_CONTROL_SS,
+ R61505_LCD_WAVEFORM, R61505_LCD_WAVEFORM_BC0 |
+ R61505_LCD_WAVEFORM_EOR,
+ R61505_ENTRY_MODE, R61505_ENTRY_MODE_DFM |
+ R61505_ENTRY_MODE_BGR |
+ R61505_ENTRY_MODE_ID1 |
+ R61505_ENTRY_MODE_AM,
+ R61505_RAM_ADDR_HORZ, 239,
+ R61505_RAM_ADDR_VERT, 0,
+ R61505_RESIZE_CONTROL, R61505_RESIZE_CONTROL_RCV(0) |
+ R61505_RESIZE_CONTROL_RCH(0) |
+ R61505_RESIZE_CONTROL_RSZ_1,
+ R61505_DISPLAY_CONTROL3, R61505_DISPLAY_CONTROL3_PTS(0) |
+ R61505_DISPLAY_CONTROL3_PTG(0) |
+ R61505_DISPLAY_CONTROL3_ICS(0),
+ R61505_DISPLAY_CONTROL4, R61505_DISPLAY_CONTROL4_FMARKOE |
+ R61505_DISPLAY_CONTROL4_FMI_1,
+ R61505_EXT_DISPLAY_IF_CONTROL1, R61505_EXT_DISPLAY_IF_CONTROL1_ENC(0) |
+ R61505_EXT_DISPLAY_IF_CONTROL1_DM_ICLK |
+ R61505_EXT_DISPLAY_IF_CONTROL1_RIM_18,
+ R61505_FRAME_MARKER_CONTROL, R61505_FRAME_MARKER_CONTROL_FMP(0),
+ R61505_POWER_CONTROL5, 0x8000,
+};
+
+static const unsigned short magic2_data[] = {
+ R61505_BASE_IMG_DISPLAY_CONTROL, R61505_BASE_IMG_DISPLAY_CONTROL_REV,
+ R61505_PANEL_IF_CONTROL2, R61505_PANEL_IF_CONTROL2_NOWI(1),
+ R61505_PANEL_IF_CONTROL3, R61505_PANEL_IF_CONTROL3_MCP(1),
+ R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_GON |
+ R61505_DISPLAY_CONTROL1_D0,
+};
+
+static const unsigned short magic3_data[] = {
+ R61505_POWER_CONTROL1, R61505_POWER_CONTROL1_SAP |
+ R61505_POWER_CONTROL1_BT(6) |
+ R61505_POWER_CONTROL1_APE |
+ R61505_POWER_CONTROL1_AP_100,
+ R61505_POWER_CONTROL2, R61505_POWER_CONTROL2_DC1_FOSC_32 |
+ R61505_POWER_CONTROL2_DC0_FOSC_2 |
+ R61505_POWER_CONTROL2_VC_089,
+ R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_VON |
+ R61505_DISPLAY_CONTROL1_GON |
+ R61505_DISPLAY_CONTROL1_D0,
+};
+
+static void r61505_enable_panel(struct r61505 *panel)
+{
+ unsigned long xres = panel->pdata->mode->xres;
+ unsigned long yres = panel->pdata->mode->yres;
+ unsigned int i;
+
+ r61505_write_array(panel, sync_data, ARRAY_SIZE(sync_data));
+
+ r61505_write(panel, R61505_OSCILLATION_CONTROL,
+ R61505_OSCILLATION_CONTROL_CALB);
+ usleep_range(10000, 11000);
+
+ r61505_write(panel, R61505_DRIVER_OUTPUT_CONTROL2,
+ R61505_DRIVER_OUTPUT_CONTROL2_NL((xres / 8) - 1));
+ r61505_write_array(panel, magic0_data, ARRAY_SIZE(magic0_data));
+ usleep_range(100000, 101000);
+
+ r61505_write_array(panel, magic1_data, ARRAY_SIZE(magic1_data));
+
+ r61505_write(panel, R61505_WINDOW_HORZ_START, 239 - (yres - 1));
+ r61505_write(panel, R61505_WINDOW_HORZ_END, 239);
+ r61505_write(panel, R61505_WINDOW_VERT_START, 0);
+ r61505_write(panel, R61505_WINDOW_VERT_END, xres - 1);
+
+ r61505_write_array(panel, magic2_data, ARRAY_SIZE(magic2_data));
+ usleep_range(10000, 11000);
+
+ r61505_write_array(panel, magic3_data, ARRAY_SIZE(magic3_data));
+ usleep_range(40000, 41000);
+
+ /* Clear GRAM to avoid displaying garbage. */
+ r61505_write(panel, R61505_RAM_ADDR_HORZ, 0);
+ r61505_write(panel, R61505_RAM_ADDR_VERT, 0);
+
+ for (i = 0; i < (xres * 256); i++) /* yes, 256 words per line */
+ r61505_write(panel, R61505_RAM_DATA, 0);
+
+ r61505_write(panel, R61505_RAM_ADDR_HORZ, 0);
+ r61505_write(panel, R61505_RAM_ADDR_VERT, 0);
+}
+
+static void r61505_disable_panel(struct r61505 *panel)
+{
+ r61505_reset(panel);
+}
+
+static void r61505_display_on(struct r61505 *panel)
+{
+ r61505_write(panel, R61505_DISPLAY_CONTROL1,
+ R61505_DISPLAY_CONTROL1_BASEE |
+ R61505_DISPLAY_CONTROL1_VON |
+ R61505_DISPLAY_CONTROL1_GON |
+ R61505_DISPLAY_CONTROL1_DTE |
+ R61505_DISPLAY_CONTROL1_D1 |
+ R61505_DISPLAY_CONTROL1_D0);
+ usleep_range(40000, 41000);
+}
+
+static void r61505_display_off(struct r61505 *panel)
+{
+ r61505_write(panel, R61505_DISPLAY_CONTROL1,
+ R61505_DISPLAY_CONTROL1_VON |
+ R61505_DISPLAY_CONTROL1_GON |
+ R61505_DISPLAY_CONTROL1_D0);
+}
+
+/* -----------------------------------------------------------------------------
+ * Panel operations
+ */
+
+static int r61505_enable(struct panel *p, enum panel_enable_mode enable)
+{
+ struct r61505 *panel = to_panel(p);
+
+ switch (enable) {
+ case PANEL_ENABLE_OFF:
+ r61505_disable_panel(panel);
+ break;
+
+ case PANEL_ENABLE_BLANK:
+ if (p->enable = PANEL_ENABLE_OFF)
+ r61505_enable_panel(panel);
+ else
+ r61505_display_off(panel);
+ break;
+
+ case PANEL_ENABLE_ON:
+ if (p->enable = PANEL_ENABLE_OFF)
+ r61505_enable_panel(panel);
+
+ r61505_display_on(panel);
+ break;
+ }
+
+ return 0;
+}
+
+static int r61505_start_transfer(struct panel *p)
+{
+ struct r61505 *panel = to_panel(p);
+
+ r61505_write_command(panel, R61505_RAM_DATA);
+ usleep_range(100000, 101000);
+
+ return 0;
+}
+
+static int r61505_get_modes(struct panel *p, const struct fb_videomode **modes)
+{
+ struct r61505 *panel = to_panel(p);
+
+ *modes = panel->pdata->mode;
+ return 1;
+}
+
+static const struct panel_ops r61505_ops = {
+ .enable = r61505_enable,
+ .start_transfer = r61505_start_transfer,
+ .get_modes = r61505_get_modes,
+};
+
+static void r61505_release(struct panel *p)
+{
+ struct r61505 *panel = to_panel(p);
+
+ kfree(panel);
+}
+
+static int r61505_remove(struct panel_dbi_device *dev)
+{
+ struct r61505 *panel = panel_dbi_get_drvdata(dev);
+
+ panel_dbi_set_drvdata(dev, NULL);
+ panel_unregister(&panel->panel);
+
+ return 0;
+}
+
+static int __devinit r61505_probe(struct panel_dbi_device *dev)
+{
+ const struct panel_r61505_platform_data *pdata = dev->dev.platform_data;
+ struct r61505 *panel;
+ int ret;
+
+ if (pdata = NULL)
+ return -ENODEV;
+
+ panel = kzalloc(sizeof(*panel), GFP_KERNEL);
+ if (panel = NULL)
+ return -ENOMEM;
+
+ panel->pdata = pdata;
+ panel->dbi = dev;
+
+ r61505_reset(panel);
+ r61505_write_array(panel, sync_data, ARRAY_SIZE(sync_data));
+
+ if (r61505_read(panel, 0) != R61505_DEVICE_CODE_VALUE) {
+ kfree(panel);
+ return -ENODEV;
+ }
+
+ panel->panel.dev = &dev->dev;
+ panel->panel.release = r61505_release;
+ panel->panel.ops = &r61505_ops;
+ panel->panel.width = pdata->width;
+ panel->panel.height = pdata->height;
+
+ ret = panel_register(&panel->panel);
+ if (ret < 0) {
+ kfree(panel);
+ return ret;
+ }
+
+ panel_dbi_set_drvdata(dev, panel);
+
+ return 0;
+}
+
+static const struct dev_pm_ops r61505_dev_pm_ops = {
+};
+
+static struct panel_dbi_driver r61505_driver = {
+ .probe = r61505_probe,
+ .remove = r61505_remove,
+ .driver = {
+ .name = "panel_r61505",
+ .owner = THIS_MODULE,
+ .pm = &r61505_dev_pm_ops,
+ },
+};
+
+module_panel_dbi_driver(r61505_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas R61505-based Display Panel");
+MODULE_LICENSE("GPL");
diff --git a/include/video/panel-r61505.h b/include/video/panel-r61505.h
new file mode 100644
index 0000000..90b3d62
--- /dev/null
+++ b/include/video/panel-r61505.h
@@ -0,0 +1,27 @@
+/*
+ * 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_R61505_H__
+#define __PANEL_R61505_H__
+
+#include <linux/fb.h>
+#include <video/panel.h>
+
+struct panel_r61505_platform_data {
+ unsigned long width; /* Panel width in mm */
+ unsigned long height; /* Panel height in mm */
+ const struct fb_videomode *mode;
+
+ int reset; /* Reset GPIO */
+};
+
+#endif /* __PANEL_R61505_H__ */
--
1.7.8.6
^ permalink raw reply related
* [RFC 5/5] video: panel: Add R61517 panel support
From: Laurent Pinchart @ 2012-08-17 0:49 UTC (permalink / raw)
To: linux-fbdev, dri-devel, linux-leds
Cc: linux-media, Bryan Wu, Richard Purdie, Tomi Valkeinen,
Marcus Lorentzon, Sumit Semwal, Archit Taneja, Sebastien Guiriec,
Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>
The R61517 is a MIPI DBI panel controller from Renesas.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
drivers/video/panel/Kconfig | 9 +
drivers/video/panel/Makefile | 1 +
drivers/video/panel/panel-r61517.c | 408 ++++++++++++++++++++++++++++++++++++
include/video/panel-r61517.h | 28 +++
4 files changed, 446 insertions(+), 0 deletions(-)
create mode 100644 drivers/video/panel/panel-r61517.c
create mode 100644 include/video/panel-r61517.h
diff --git a/drivers/video/panel/Kconfig b/drivers/video/panel/Kconfig
index 12d7712..bd643be 100644
--- a/drivers/video/panel/Kconfig
+++ b/drivers/video/panel/Kconfig
@@ -25,4 +25,13 @@ config DISPLAY_PANEL_R61505
If you are in doubt, say N.
+config DISPLAY_PANEL_R61517
+ tristate "Renesas R61517-based Display Panel"
+ select DISPLAY_PANEL_DBI
+ ---help---
+ Support panels based on the Renesas R61517 panel controller.
+ Those panels are controlled through a MIPI DBI interface.
+
+ If you are in doubt, say N.
+
endif # DISPLAY_PANEL
diff --git a/drivers/video/panel/Makefile b/drivers/video/panel/Makefile
index e4fb9fe..3c11d26 100644
--- a/drivers/video/panel/Makefile
+++ b/drivers/video/panel/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_DISPLAY_PANEL) += panel.o
obj-$(CONFIG_DISPLAY_PANEL_DUMMY) += panel-dummy.o
obj-$(CONFIG_DISPLAY_PANEL_DBI) += panel-dbi.o
obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o
+obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o
diff --git a/drivers/video/panel/panel-r61517.c b/drivers/video/panel/panel-r61517.c
new file mode 100644
index 0000000..6e8d933
--- /dev/null
+++ b/drivers/video/panel/panel-r61517.c
@@ -0,0 +1,408 @@
+/*
+ * Renesas R61517-based Display Panels
+ *
+ * Copyright (C) 2012 Renesas Solutions Corp.
+ * Based on KFR2R09 LCD panel support
+ * Copyright (C) 2009 Magnus Damm
+ * Register settings based on the out-of-tree t33fb.c driver
+ * Copyright (C) 2008 Lineo Solutions, Inc.
+ *
+ * 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.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+
+#include <video/panel-dbi.h>
+#include <video/panel-r61517.h>
+
+struct r61517 {
+ struct panel panel;
+ struct panel_dbi_device *dbi;
+ const struct panel_r61517_platform_data *pdata;
+};
+
+#define to_panel(p) container_of(p, struct r61517, panel)
+
+/* -----------------------------------------------------------------------------
+ * Read, write and reset
+ */
+
+static void r61517_write_command(struct r61517 *panel, u16 reg)
+{
+ panel_dbi_write_command(panel->dbi, reg);
+}
+
+static void r61517_write_data(struct r61517 *panel, u16 data)
+{
+ panel_dbi_write_data(panel->dbi, data);
+}
+
+static void r61517_write(struct r61517 *panel, u16 reg, u16 data)
+{
+ panel_dbi_write_command(panel->dbi, reg);
+ panel_dbi_write_data(panel->dbi, data);
+}
+
+static u16 r61517_read_data(struct r61517 *panel)
+{
+ return panel_dbi_read_data(panel->dbi);
+}
+
+static void __r61517_write_array(struct r61517 *panel, const u8 *data,
+ unsigned int len)
+{
+ unsigned int i;
+
+ for (i = 0; i < len; ++i)
+ r61517_write_data(panel, data[i]);
+}
+
+#define r61517_write_array(p, a) \
+ __r61517_write_array(p, a, ARRAY_SIZE(a))
+
+static void r61517_reset(struct r61517 *panel)
+{
+ gpio_set_value(panel->pdata->protect, 0); /* PROTECT/ -> L */
+ gpio_set_value(panel->pdata->reset, 0); /* LCD_RST/ -> L */
+ gpio_set_value(panel->pdata->protect, 1); /* PROTECT/ -> H */
+ usleep_range(1100, 1200);
+ gpio_set_value(panel->pdata->reset, 1); /* LCD_RST/ -> H */
+ usleep_range(10, 100);
+ gpio_set_value(panel->pdata->protect, 0); /* PROTECT/ -> L */
+ msleep(20);
+}
+
+/* -----------------------------------------------------------------------------
+ * Configuration
+ */
+
+static const u8 data_frame_if[] = {
+ 0x02, /* WEMODE: 1=cont, 0=one-shot */
+ 0x00, 0x00,
+ 0x00, /* EPF, DFM */
+ 0x02, /* RIM[1] : 1 (18bpp) */
+};
+
+static const u8 data_panel[] = {
+ 0x0b,
+ 0x63, /* 400 lines */
+ 0x04, 0x00, 0x00, 0x04, 0x11, 0x00, 0x00,
+};
+
+static const u8 data_timing[] = {
+ 0x00, 0x00, 0x13, 0x08, 0x08,
+};
+
+static const u8 data_timing_src[] = {
+ 0x11, 0x01, 0x00, 0x01,
+};
+
+static const u8 data_gamma[] = {
+ 0x01, 0x02, 0x08, 0x23, 0x03, 0x0c, 0x00, 0x06, 0x00, 0x00,
+ 0x01, 0x00, 0x0c, 0x23, 0x03, 0x08, 0x02, 0x06, 0x00, 0x00,
+};
+
+static const u8 data_power[] = {
+ 0x07, 0xc5, 0xdc, 0x02, 0x33, 0x0a,
+};
+
+static unsigned long r61517_read_device_code(struct r61517 *panel)
+{
+ /* access protect OFF */
+ r61517_write(panel, 0xb0, 0x00);
+
+ /* deep standby OFF */
+ r61517_write(panel, 0xb1, 0x00);
+
+ /* device code command */
+ r61517_write_command(panel, 0xbf);
+ mdelay(50);
+
+ /* dummy read */
+ r61517_read_data(panel);
+
+ /* read device code */
+ return ((r61517_read_data(panel) & 0xff) << 24) |
+ ((r61517_read_data(panel) & 0xff) << 16) |
+ ((r61517_read_data(panel) & 0xff) << 8) |
+ ((r61517_read_data(panel) & 0xff) << 0);
+}
+
+static void r61517_write_memory_start(struct r61517 *panel)
+{
+ r61517_write_command(panel, 0x2c);
+}
+
+static void r61517_clear_memory(struct r61517 *panel)
+{
+ unsigned int i;
+
+ r61517_write_memory_start(panel);
+
+ for (i = 0; i < (240 * 400); i++)
+ r61517_write_data(panel, 0);
+}
+
+static void r61517_enable_panel(struct r61517 *panel)
+{
+ /* access protect off */
+ r61517_write(panel, 0xb0, 0x00);
+
+ /* exit deep standby mode */
+ r61517_write(panel, 0xb1, 0x00);
+
+ /* frame memory I/F */
+ r61517_write_command(panel, 0xb3);
+ r61517_write_array(panel, data_frame_if);
+
+ /* display mode and frame memory write mode */
+ r61517_write(panel, 0xb4, 0x00); /* DBI, internal clock */
+
+ /* panel */
+ r61517_write_command(panel, 0xc0);
+ r61517_write_array(panel, data_panel);
+
+ /* timing (normal) */
+ r61517_write_command(panel, 0xc1);
+ r61517_write_array(panel, data_timing);
+
+ /* timing (partial) */
+ r61517_write_command(panel, 0xc2);
+ r61517_write_array(panel, data_timing);
+
+ /* timing (idle) */
+ r61517_write_command(panel, 0xc3);
+ r61517_write_array(panel, data_timing);
+
+ /* timing (source/VCOM/gate driving) */
+ r61517_write_command(panel, 0xc4);
+ r61517_write_array(panel, data_timing_src);
+
+ /* gamma (red) */
+ r61517_write_command(panel, 0xc8);
+ r61517_write_array(panel, data_gamma);
+
+ /* gamma (green) */
+ r61517_write_command(panel, 0xc9);
+ r61517_write_array(panel, data_gamma);
+
+ /* gamma (blue) */
+ r61517_write_command(panel, 0xca);
+ r61517_write_array(panel, data_gamma);
+
+ /* power (common) */
+ r61517_write_command(panel, 0xd0);
+ r61517_write_array(panel, data_power);
+
+ /* VCOM */
+ r61517_write_command(panel, 0xd1);
+ r61517_write_data(panel, 0x00);
+ r61517_write_data(panel, 0x0f);
+ r61517_write_data(panel, 0x02);
+
+ /* power (normal) */
+ r61517_write_command(panel, 0xd2);
+ r61517_write_data(panel, 0x63);
+ r61517_write_data(panel, 0x24);
+
+ /* power (partial) */
+ r61517_write_command(panel, 0xd3);
+ r61517_write_data(panel, 0x63);
+ r61517_write_data(panel, 0x24);
+
+ /* power (idle) */
+ r61517_write_command(panel, 0xd4);
+ r61517_write_data(panel, 0x63);
+ r61517_write_data(panel, 0x24);
+
+ r61517_write_command(panel, 0xd8);
+ r61517_write_data(panel, 0x77);
+ r61517_write_data(panel, 0x77);
+
+ /* TE signal */
+ r61517_write(panel, 0x35, 0x00);
+
+ /* TE signal line */
+ r61517_write_command(panel, 0x44);
+ r61517_write_data(panel, 0x00);
+ r61517_write_data(panel, 0x00);
+
+ /* column address */
+ r61517_write_command(panel, 0x2a);
+ r61517_write_data(panel, 0x00);
+ r61517_write_data(panel, 0x00);
+ r61517_write_data(panel, 0x00);
+ r61517_write_data(panel, 0xef);
+
+ /* page address */
+ r61517_write_command(panel, 0x2b);
+ r61517_write_data(panel, 0x00);
+ r61517_write_data(panel, 0x00);
+ r61517_write_data(panel, 0x01);
+ r61517_write_data(panel, 0x8f);
+
+ /* exit sleep mode */
+ r61517_write_command(panel, 0x11);
+
+ mdelay(120);
+
+ /* clear vram */
+ r61517_clear_memory(panel);
+}
+
+static void r61517_disable_panel(struct r61517 *panel)
+{
+ r61517_reset(panel);
+}
+
+static void r61517_display_on(struct r61517 *panel)
+{
+ r61517_write_command(panel, 0x29);
+ mdelay(1);
+}
+
+static void r61517_display_off(struct r61517 *panel)
+{
+ r61517_write_command(panel, 0x28);
+}
+
+/* -----------------------------------------------------------------------------
+ * Panel operations
+ */
+
+static int r61517_enable(struct panel *p, enum panel_enable_mode enable)
+{
+ struct r61517 *panel = to_panel(p);
+
+ switch (enable) {
+ case PANEL_ENABLE_OFF:
+ r61517_disable_panel(panel);
+ break;
+
+ case PANEL_ENABLE_BLANK:
+ if (p->enable = PANEL_ENABLE_OFF)
+ r61517_enable_panel(panel);
+ else
+ r61517_display_off(panel);
+ break;
+
+ case PANEL_ENABLE_ON:
+ if (p->enable = PANEL_ENABLE_OFF)
+ r61517_enable_panel(panel);
+
+ r61517_display_on(panel);
+ break;
+ }
+
+ return 0;
+}
+
+static int r61517_start_transfer(struct panel *p)
+{
+ struct r61517 *panel = to_panel(p);
+
+ r61517_write_memory_start(panel);
+ return 0;
+}
+
+static int r61517_get_modes(struct panel *p, const struct fb_videomode **modes)
+{
+ struct r61517 *panel = to_panel(p);
+
+ *modes = panel->pdata->mode;
+ return 1;
+}
+
+static const struct panel_ops r61517_ops = {
+ .enable = r61517_enable,
+ .start_transfer = r61517_start_transfer,
+ .get_modes = r61517_get_modes,
+};
+
+static void r61517_release(struct panel *p)
+{
+ struct r61517 *panel = to_panel(p);
+
+ kfree(panel);
+}
+
+static int r61517_remove(struct panel_dbi_device *dev)
+{
+ struct r61517 *panel = panel_dbi_get_drvdata(dev);
+
+ panel_dbi_set_drvdata(dev, NULL);
+ panel_unregister(&panel->panel);
+
+ return 0;
+}
+
+static int __devinit r61517_probe(struct panel_dbi_device *dev)
+{
+ const struct panel_r61517_platform_data *pdata = dev->dev.platform_data;
+ struct r61517 *panel;
+ int ret;
+
+ if (pdata = NULL)
+ return -ENODEV;
+
+ panel = kzalloc(sizeof(*panel), GFP_KERNEL);
+ if (panel = NULL)
+ return -ENOMEM;
+
+ panel->pdata = pdata;
+ panel->dbi = dev;
+
+ r61517_reset(panel);
+
+ if (r61517_read_device_code(panel) != 0x01221517) {
+ kfree(panel);
+ return -ENODEV;
+ }
+
+ pr_info("R61517 panel controller detected.\n");
+
+ panel->panel.dev = &dev->dev;
+ panel->panel.release = r61517_release;
+ panel->panel.ops = &r61517_ops;
+ panel->panel.width = pdata->width;
+ panel->panel.height = pdata->height;
+
+ ret = panel_register(&panel->panel);
+ if (ret < 0) {
+ kfree(panel);
+ return ret;
+ }
+
+ panel_dbi_set_drvdata(dev, panel);
+
+ return 0;
+}
+
+static const struct dev_pm_ops r61517_dev_pm_ops = {
+};
+
+static struct panel_dbi_driver r61517_driver = {
+ .probe = r61517_probe,
+ .remove = r61517_remove,
+ .driver = {
+ .name = "panel_r61517",
+ .owner = THIS_MODULE,
+ .pm = &r61517_dev_pm_ops,
+ },
+};
+
+module_panel_dbi_driver(r61517_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas R61517-based Display Panel");
+MODULE_LICENSE("GPL");
diff --git a/include/video/panel-r61517.h b/include/video/panel-r61517.h
new file mode 100644
index 0000000..c9e6ddf
--- /dev/null
+++ b/include/video/panel-r61517.h
@@ -0,0 +1,28 @@
+/*
+ * Renesas R61517-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_R61517_H__
+#define __PANEL_R61517_H__
+
+#include <linux/fb.h>
+#include <video/panel.h>
+
+struct panel_r61517_platform_data {
+ unsigned long width; /* Panel width in mm */
+ unsigned long height; /* Panel height in mm */
+ const struct fb_videomode *mode;
+
+ int protect; /* Protect GPIO */
+ int reset; /* Reset GPIO */
+};
+
+#endif /* __PANEL_R61517_H__ */
--
1.7.8.6
^ permalink raw reply related
* Re: [RFC 0/5] Generic panel framework
From: Jingoo Han @ 2012-08-17 1:33 UTC (permalink / raw)
To: 'Laurent Pinchart', linux-fbdev, dri-devel, linux-leds
Cc: linux-media, 'Bryan Wu', 'Richard Purdie',
'Tomi Valkeinen', 'Marcus Lorentzon',
'Sumit Semwal', 'Archit Taneja',
'Sebastien Guiriec', 'Inki Dae',
'Kyungmin Park', 'Jingoo Han'
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>
On Friday, August 17, 2012 9:50 AM Laurent Pinchart wrote:
>
> Hi everybody,
>
> While working on DT bindings for the Renesas Mobile SoC display controller
> (a.k.a. LCDC) I quickly realized that display panel implementation based on
> board code callbacks would need to be replaced by a driver-based panel
> framework.
>
> Several driver-based panel support solution already exist in the kernel.
>
> - The LCD device class is implemented in drivers/video/backlight/lcd.c and
> exposes a kernel API in include/linux/lcd.h. That API is tied to the FBDEV
> API for historical reason, uses board code callback for reset and power
> management, and doesn't include support for standard features available in
> today's "smart panels".
>
> - OMAP2+ based systems use custom panel drivers available in
> drivers/video/omap2/displays. Those drivers are based on OMAP DSS (display
> controller) specific APIs.
>
> - Similarly, Exynos based systems use custom panel drivers available in
> drivers/video/exynos. Only a single driver (s6e8ax0) is currently available.
> That driver is based on Exynos display controller specific APIs and on the
> LCD device class API.
>
Hi Laurent,
I am a Exynos DP maintainer and Samsung FB maintainer.
Actually, eDP (embedded display port) will be faced with this kind of problem.
According to the eDP standard, eDP panel can have their own specific registers
that handle extra operations. In this case, custom panel driver for this eDP panel
will be necessary.
In my opinion, the panel framework would be helpful to solve this problem.
Best regards,
Jingoo Han
> I've brought up the issue with Tomi Valkeinen (OMAP DSS maintainer) and Marcus
> Lorentzon (working on panel support for ST/Linaro), and we agreed that a
> generic panel framework for display devices is needed. These patches implement
> a first proof of concept.
>
> One of the main reasons for creating a new panel framework instead of adding
> missing features to the LCD framework is to avoid being tied to the FBDEV
> framework. Panels will be used by DRM drivers as well, and their API should
> thus be subsystem-agnostic. Note that the panel framework used the
> fb_videomode structure in its API, this will be replaced by a common video
> mode structure shared across subsystems (there's only so many hours per day).
>
> Panels, as used in these patches, are defined as physical devices combining a
> matrix of pixels and a controller capable of driving that matrix.
>
> Panel physical devices are registered as children of the control bus the panel
> controller is connected to (depending on the panel type, we can find platform
> devices for dummy panels with no control bus, or I2C, SPI, DBI, DSI, ...
> devices). The generic panel framework matches registered panel devices with
> panel drivers and call the panel drivers probe method, as done by other device
> classes in the kernel. The driver probe() method is responsible for
> instantiating a struct panel instance and registering it with the generic
> panel framework.
>
> Display drivers are panel consumers. They register a panel notifier with the
> framework, which then calls the notifier when a matching panel is registered.
> The reason for this asynchronous mode of operation, compared to how drivers
> acquire regulator or clock resources, is that the panel can use resources
> provided by the display driver. For instance a panel can be a child of the DBI
> or DSI bus controlled by the display device, or use a clock provided by that
> device. We can't defer the display device probe until the panel is registered
> and also defer the panel device probe until the display is registered. As
> most display drivers need to handle output devices hotplug (HDMI monitors for
> instance), handling panel through a notification system seemed to be the
> easiest solution.
>
> Note that this brings a different issue after registration, as display and
> panel drivers would take a reference to each other. Those circular references
> would make driver unloading impossible. I haven't found a good solution for
> that problem yet (hence the RFC state of those patches), and I would
> appreciate your input here. This might also be a hint that the framework
> design is wrong to start with. I guess I can't get everything right on the
> first try ;-)
>
> Getting hold of the panel is the most complex part. Once done, display drivers
> can call abstract operations provided by panel drivers to control the panel
> operation. These patches implement three of those operations (enable, start
> transfer and get modes). More operations will be needed, and those three
> operations will likely get modified during review. Most of the panels on
> devices I own are dumb panels with no control bus, and are thus not the best
> candidates to design a framework that needs to take complex panels' needs into
> account.
>
> In addition to the generic panel core, I've implemented MIPI DBI (Display Bus
> Interface, a parallel bus for panels that supports read/write transfers of
> commands and data) bus support, as well as three panel drivers (dummy panels
> with no control bus, and Renesas R61505- and R61517-based panels, both using
> DBI as their control bus). Only the dummy panel driver has been tested as I
> lack hardware for the two other drivers.
>
> I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If
> you can find a clever way to solve the cyclic references issue described above
> I'll buy you a beer at the next conference we will both attend. If you think
> the proposed solution is too complex, or too simple, I'm all ears. I
> personally already feel that we might need something even more generic to
> support other kinds of external devices connected to display controllers, such
> as external DSI to HDMI converters for instance. Some kind of video entity
> exposing abstract operations like the panels do would make sense, in which
> case panels would "inherit" from that video entity.
>
> Speaking of conferences, I will attend the KS/LPC in San Diego in a bit more
> than a week, and would be happy to discuss this topic face to face there.
>
> Laurent Pinchart (5):
> video: Add generic display panel core
> video: panel: Add dummy panel support
> video: panel: Add MIPI DBI bus support
> video: panel: Add R61505 panel support
> video: panel: Add R61517 panel support
>
> drivers/video/Kconfig | 1 +
> drivers/video/Makefile | 1 +
> drivers/video/panel/Kconfig | 37 +++
> drivers/video/panel/Makefile | 5 +
> drivers/video/panel/panel-dbi.c | 217 +++++++++++++++
> drivers/video/panel/panel-dummy.c | 103 +++++++
> drivers/video/panel/panel-r61505.c | 520 ++++++++++++++++++++++++++++++++++++
> drivers/video/panel/panel-r61517.c | 408 ++++++++++++++++++++++++++++
> drivers/video/panel/panel.c | 269 +++++++++++++++++++
> include/video/panel-dbi.h | 92 +++++++
> include/video/panel-dummy.h | 25 ++
> include/video/panel-r61505.h | 27 ++
> include/video/panel-r61517.h | 28 ++
> include/video/panel.h | 111 ++++++++
> 14 files changed, 1844 insertions(+), 0 deletions(-)
> create mode 100644 drivers/video/panel/Kconfig
> create mode 100644 drivers/video/panel/Makefile
> create mode 100644 drivers/video/panel/panel-dbi.c
> create mode 100644 drivers/video/panel/panel-dummy.c
> create mode 100644 drivers/video/panel/panel-r61505.c
> create mode 100644 drivers/video/panel/panel-r61517.c
> create mode 100644 drivers/video/panel/panel.c
> create mode 100644 include/video/panel-dbi.h
> create mode 100644 include/video/panel-dummy.h
> create mode 100644 include/video/panel-r61505.h
> create mode 100644 include/video/panel-r61517.h
> create mode 100644 include/video/panel.h
>
> --
> Regards,
>
> Laurent Pinchart
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v3] da8xx-fb: allow frame to complete after disabling LCDC
From: Sekhar Nori @ 2012-08-17 6:57 UTC (permalink / raw)
To: linux-fbdev
In-Reply-To: <1344950605-18945-1-git-send-email-prakash.pm@ti.com>
Hi Prakash,
On 8/14/2012 6:53 PM, Manjunathappa, Prakash wrote:
> Wait for active frame transfer to complete after disabling LCDC.
> At the same this wait is not be required when there are sync and
> underflow errors.
> More information on disable and reset sequence can be found in
> section 13.4.6 of AM335x TRM @www.ti.com/am335x.
>
> Signed-off-by: Manjunathappa, Prakash <prakash.pm@ti.com>
> ---
> Applies on top of fbdev-next of Florian Tobias Schandinat's tree.
> Since v2:
> Optimized the lcd_disable_raster function.
> Since v1:
> Changed the commit message, also added link to hardware specification.
>
> drivers/video/da8xx-fb.c | 49 ++++++++++++++++++++++++++++++++++++---------
> 1 files changed, 39 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/video/da8xx-fb.c b/drivers/video/da8xx-fb.c
> index 7ae9d53..cb696ff 100644
> --- a/drivers/video/da8xx-fb.c
> +++ b/drivers/video/da8xx-fb.c
> @@ -48,6 +48,7 @@
> #define LCD_PL_LOAD_DONE BIT(6)
> #define LCD_FIFO_UNDERFLOW BIT(5)
> #define LCD_SYNC_LOST BIT(2)
> +#define LCD_FRAME_DONE BIT(0)
>
> /* LCD DMA Control Register */
> #define LCD_DMA_BURST_SIZE(x) ((x) << 4)
> @@ -288,13 +289,41 @@ static inline void lcd_enable_raster(void)
> }
>
> /* Disable the Raster Engine of the LCD Controller */
> -static inline void lcd_disable_raster(void)
> +static inline void lcd_disable_raster(bool wait_for_frame_done)
> {
> u32 reg;
> + u32 stat_reg = LCD_STAT_REG;
> + u32 loop_cnt = 0;
>
> reg = lcdc_read(LCD_RASTER_CTRL_REG);
> if (reg & LCD_RASTER_ENABLE)
> lcdc_write(reg & ~LCD_RASTER_ENABLE, LCD_RASTER_CTRL_REG);
> +
> + if (lcd_revision = LCD_VERSION_2)
> + stat_reg = LCD_RAW_STAT_REG;
> +
> + if (wait_for_frame_done) {
> + /*
> + * 50 milli seconds should be sufficient for a frame to
> + * complete
> + */
> + loop_cnt = 50;
> + while (!(lcdc_read(stat_reg) & LCD_FRAME_DONE)) {
> + /* Handle timeout */
> + if (unlikely(0 = --loop_cnt)) {
> + pr_err("LCD Controller timed out\n");
> + break;
> + }
> + mdelay(1);
> + }
> + }
The TRM you referenced in the patch description suggests waiting for
frame done interrupt. Can we actually wait for the interrupt here
instead of busy looping?
Thanks,
Sekhar
^ permalink raw reply
* i.MX27 SLCDC driver
From: Gaëtan Carlier @ 2012-08-17 7:07 UTC (permalink / raw)
To: linux-arm-kernel
[-- Attachment #1: Type: text/plain, Size: 612 bytes --]
Hello,
I would like to write the driver for SmartLCD controller of i.MX27. This
kind of interface needs a LCD with an embedded graphic controller
partially controlled by GPIO. I want to link it with an OLED DD12832.
How have I to write this driver : One driver for SLCDC and one driver
for DD12832 ? How to link them together ?
Maybe use similar philosophy than soc-camera ?
I have written a driver for kernel 2.6.22 but SLCDC and DD12832 are in
the same driver.
Thank you for your help.
Gaëtan Carlier
ps : I attach my previous driver for a better overview (this is a
working draft for test purpose)
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: mx2fb-slcd_dd12832_oled.c --]
[-- Type: text/x-csrc; name="mx2fb-slcd_dd12832_oled.c", Size: 35409 bytes --]
/*
* Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
/*!
* @defgroup Framebuffer_MX27 Framebuffer Driver for MX27.
*/
/*!
* @file mx2fb.c
*
* @brief Frame buffer driver for MX27 ADS.
*
* @ingroup Framebuffer_MX27
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dmapool.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/arch/mxcfb.h>
#include <asm/arch/mx2fb_slcdc.h>
#include <asm/arch/pmic_power.h>
#include "dd12832_oled.h"
#ifdef CONFIG_PM
static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state);
static int mx2fb_resume(struct platform_device *pdev);
#else
#define mx2fb_suspend 0
#define mx2fb_resume 0
#endif
#define MX2FB_TYPE_BG 0
#define MX2FB_TYPE_GW 1
#define floor8(a) (a&(~0x07))
//#define iceil8(a) (((int)((a+7)/8))*8)
#define iceil8(a) ((int)((a & ~((int)0x03)) + 8))
extern void gpio_slcdc_active(void);
extern void gpio_slcdc_inactive(void);
static char *fb_mode;
static int fb_enabled;
static unsigned long default_bpp = 1;
static unsigned char brightness = 255;
static ATOMIC_NOTIFIER_HEAD(mx2fb_notifier_list);
static struct clk *slcdc_clk;
/*!
* @brief Structure containing the MX2 specific framebuffer parameters.
*/
struct mx2fb_par {
int type;
char *id;
int registered;
int blank;
/* Tell if driver compiled with rotate option enabled */
int rotate;
/* Contains displayed data in 1 byte / column (8 pixels)
* fb data are stored as 1 byte / pixel
* !! must be allocated with 128k alignment using dma_pool_create
*/
/* FrameBuffer memory map */
unsigned char* fb_vmem;
size_t fb_len;
dma_addr_t fb_pmem;
/* Oled cgram memory map */
unsigned long cgram_cmd_vaddr;
unsigned long cgram_cmd_paddr;
unsigned long cgram_cmd_len;
struct dma_pool *cgram_cmd_dma_pool;
unsigned long cgram_data_vaddr;
unsigned long cgram_data_paddr;
unsigned long cgram_data_len;
struct dma_pool *cgram_data_dma_pool;
};
/* Framebuffer APIs */
/*static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info);
static int mx2fb_set_par(struct fb_info *info);
static void _set_fix(struct fb_info *info);*/
/* Internal functions */
static int __init _init_fbinfo(struct fb_info *info,
struct platform_device *pdev);
static int __init _install_fb(struct fb_info *info,
struct platform_device *pdev);
static void __exit _uninstall_fb(struct fb_info *info);
static int _map_video_memory(struct fb_info *info);
static void _unmap_video_memory(struct fb_info *info);
/*static void _enable_lcdc(struct fb_info *info);
static void _disable_lcdc(struct fb_info *info);
static void _update_slcdc(struct fb_info *info);*/
/* Oled display information */
struct fb_videomode mxcfb_modedb[] = {
{
/* 128x32 */
"Densitron DD12832", /* name */
0, /* refresh */
128, /* xres */
32, /* yres */
0, /* pixclock */
0, /* left_margin */
0, /* right_margin */
0, /* upper_margin */
0, /* lower_margin */
0, /* hsync_len */
0, /* vsync_len */
0, /* sync */
0, /* mode */
0}, /* flag */
};
int mxcfb_modedb_sz = ARRAY_SIZE(mxcfb_modedb);
struct mx2fb_par mx2fbp_bg = {
.type = MX2FB_TYPE_BG,
.id = "DISP0 BG",
.registered = 0,
#ifdef CONFIG_FB_MXC_DENSITRON_DD12832_ROTATE
.rotate = 1,
#else
.rotate = 0,
#endif
};
/*!
* @brief Framebuffer information structures.
* There are up to 3 framebuffers: background, TVout, and graphic window.
* If graphic window is configured, it must be the last framebuffer.
*/
static struct fb_info mx2fb_info = {
.par = &mx2fbp_bg,
};
/*!
* Do a minimal setup of SLCDC to be able to send command to DD12832
*/
static void slcdc_first_init(void)
{
unsigned long val;
int i;
unsigned long *pdata;
/* Screen start address register */
__raw_writel(mx2fbp_bg.cgram_data_paddr, SLCDC_REG(SLCDC_DATABASEADDR));
__raw_writel(mx2fbp_bg.cgram_data_len, SLCDC_REG(SLCDC_DATABUFSIZE));
/* Copy command array to DMA area */
pdata = (unsigned long *)mx2fbp_bg.cgram_cmd_vaddr;
for (i = 0; i < mx2fbp_bg.cgram_cmd_len; i++) {
if ((i & 0x01) == 0) {
/* If even offset, command must be left-aligned in
* 32-bits memory space */
*pdata = (unsigned long)_ssd1305_pagecmd_array[i] << 16;
} else {
/* If odd offset, command must be right-aligned in
* 32-bits memory space */
*pdata |= (unsigned long)_ssd1305_pagecmd_array[i];
/* When "right column" is filled, go to next address */
pdata++;
}
}
/* Set Array of command for page addressing */
__raw_writel(mx2fbp_bg.cgram_cmd_paddr, SLCDC_REG(SLCDC_COMBASEADDR));
__raw_writel(mx2fbp_bg.cgram_cmd_len, SLCDC_REG(SLCDC_COMBUFSIZE));
/* Set number of command (words) that must be send to jump to a
* specific page. Size of "word" is define by WORDDEFCOM flag in
* LCDTRANSCONFIG register */
__raw_writel(PAGE_COMMAND_PACK_SIZE, SLCDC_REG(SLCDC_COMSTRINGSIZ));
/* Define DMA burst */
__raw_writel(0, SLCDC_REG(SLCDC_FIFOCONFIG));
/* Define number of column/segment in a page */
__raw_writel(OLED_WIDTH, SLCDC_REG(SLCDC_LCDCONFIG));
/* Set transfer configuration */
val = SLCDC_DATA_8BIT | SLCDC_COMMAND_8BIT | SLCDC_PARALLEL |
SLCDC_WRITEDATA_8BIT | SLCDC_TRANS_LITLENDIAN_8BIT |
SLCDC_CSPOL_LOW;
__raw_writel(val, SLCDC_REG(SLCDC_LCDTRANSCONFIG));
/* Set control register */
val = SLCDC_MODE_COMMAND | SLCDC_IRQ_DISABLE | SLCDC_IRQ_FLAGS_MASK;
__raw_writel(val, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
/* Set SLCDC clock divider = HCLK_SLCDC * val / 128 */
val = 40;
__raw_writel(val, SLCDC_REG(SLCDC_LCDCLOCKCONFIG));
}
/*
* Send one command to oled via SLCDC
*/
static int _slcdc_sendcmd_single(unsigned char cmd)
{
while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK);
__raw_writel((u32)cmd | WRITE_LCDCMD, SLCDC_REG(SLCDC_LCDWRITEDATA));
return 0;
}
/*
* Send one data to oled via SLCDC
*/
static int _slcdc_senddata_single(unsigned char cmd)
{
while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK);
__raw_writel((u32)cmd | WRITE_LCDDATA, SLCDC_REG(SLCDC_LCDWRITEDATA));
return 0;
}
/*
* Set Start line for Pan function
*/
static void dd12832_set_start_line(unsigned char y)
{
_slcdc_sendcmd_single(SSD1305_CMD_ROWADDR | (y & 0x3F));
}
/*
* Set page address
*/
static void dd12832_set_yaddr(unsigned char y)
{
_slcdc_sendcmd_single(SSD1305_CMD_PAGEADDR | (y & 0x07));
}
/*
* Set segment address
*/
static void dd12832_set_xaddr(unsigned char x)
{
#ifndef CONFIG_FB_MXC_DENSITRON_DD12832_ROTATE
_slcdc_sendcmd_single(SSD1305_CMD_HIGHCOLADDR | (x >> 4));
_slcdc_sendcmd_single(SSD1305_CMD_LOWCOLADDR | (x & 0x0F));
#else
x += SSD1305_WIDTH - OLED_WIDTH;
_slcdc_sendcmd_single(SSD1305_CMD_HIGHCOLADDR | (x >> 4));
_slcdc_sendcmd_single(SSD1305_CMD_LOWCOLADDR | (x & 0x0F));
#endif
}
/*
* Modify contrast value
*/
static void dd12832_set_brightness(unsigned char level)
{
if (level == 0) {
/* If level 0 asked, display must be turned off
* because Display is still ON when Contrast
* is set to 0 */
_slcdc_sendcmd_single(SSD1305_CMD_DISPLAY_POWER_OFF);
} else {
/* Apply new contrast */
_slcdc_sendcmd_single(SSD1305_CMD_BRIGHTNESS_MODE);
_slcdc_sendcmd_single(level);
/* Be sure that DISPLAY is ON */
_slcdc_sendcmd_single(SSD1305_CMD_DISPLAY_POWER_ON);
}
}
/*
* Clear internal RAM of controller
*/
static void dd12832_clear_lcd(void)
{
unsigned long status;
/* Clear OLED RAM mirror buffer */
char *pdata = (char *)mx2fbp_bg.cgram_data_vaddr;
memset(pdata, 0, mx2fbp_bg.cgram_data_len);
/* Clear FrameBuffer mirror buffer */
pdata = (char __force *) mx2fbp_bg.fb_vmem;
memset(pdata, 0, mx2fbp_bg.fb_len);
dd12832_set_yaddr(0);
dd12832_set_xaddr(0);
while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK);
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status &= ~(SLCDC_SLCDCCTRLSTAT_AUTOMODE_MASK);
status |= SLCDC_MODE_DATA | SLCDC_START_TRANSFERT;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
}
/*
* Send init sequence to display controller
*/
static int dd12832_init_controller(void)
{
/* Use SLCDC to send command block */
unsigned long i;
for (i = 0; i < sizeof(_ssd1305_init_array); i++) {
_slcdc_sendcmd_single(_ssd1305_init_array[i]);
}
dd12832_clear_lcd();
return 0;
}
/*!
* @brief Enable LCD controller.
* @param info framebuffer information pointer
*/
static void _enable_slcdc(struct fb_info *info)
{
if (!fb_enabled) {
fb_enabled++;
if (fb_mode) {
unsigned long mode = 0;
if (mode == 0) {
dd12832_set_brightness(brightness);
}
}
}
}
/*!
* @brief Disable LCD controller.
* @param info framebuffer information pointer
*/
static void _disable_slcdc(struct fb_info *info)
{
if (fb_enabled) {
dd12832_set_brightness(0);
fb_enabled = 0;
}
}
/*!
* @brief Update SLCDC registers
* @param info framebuffer information pointer
*/
static void _update_slcdc(struct fb_info *info)
{
unsigned long base;
unsigned long val;
struct fb_var_screeninfo *var = &info->var;
base = (var->yoffset * var->xres_virtual + var->xoffset);
base += (unsigned long)info->screen_base;
/* Set number of command (words) that must be send to jump to a
* specific page. Size of "word" is define by WORDDEFCOM flag in
* LCDTRANSCONFIG register */
__raw_writel(PAGE_COMMAND_PACK_SIZE, SLCDC_REG(SLCDC_COMSTRINGSIZ));
/* Define DMA burst */
__raw_writel(0, SLCDC_REG(SLCDC_FIFOCONFIG));
/* Define number of column/segment in a page */
__raw_writel(OLED_WIDTH, SLCDC_REG(SLCDC_LCDCONFIG));
/* Set transfer configuration */
val = SLCDC_DATA_8BIT | SLCDC_COMMAND_8BIT | SLCDC_PARALLEL |
SLCDC_WRITEDATA_8BIT | SLCDC_TRANS_LITLENDIAN_8BIT |
SLCDC_CSPOL_LOW;
__raw_writel(val, SLCDC_REG(SLCDC_LCDTRANSCONFIG));
/* Set SLCDC clock divider = HCLK_SLCDC * val / 128 */
val = 60;
__raw_writel(val, SLCDC_REG(SLCDC_LCDCLOCKCONFIG));
return;
}
/*!
* @brief Blanks the display.
*
* @param blank_mode The blank mode we want.
* @param info Frame buffer structure that represents a single frame buffer
*
* @return Negative errno on error, or zero on success.
*
* Blank the screen if blank_mode != 0, else unblank. Return 0 if blanking
* succeeded, != 0 if un-/blanking failed.
* blank_mode == 2: suspend vsync
* blank_mode == 3: suspend hsync
* blank_mode == 4: powerdown
*/
static int mx2fb_blank(int blank_mode, struct fb_info *info)
{
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
dev_dbg(info->device, "blank mode = %d\n", blank_mode);
mx2fbp->blank = blank_mode;
switch (blank_mode) {
case FB_BLANK_POWERDOWN:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_NORMAL:
_disable_slcdc(info);
break;
case FB_BLANK_UNBLANK:
_enable_slcdc(info);
break;
}
return 0;
}
/*
* here we start the process of spliting out the fb update into
* individual blocks of pixels. we end up spliting into 64x64 blocks
* and finally down to 64x8 pages.
*/
static void dd12832_cgram_update(struct fb_info *info, unsigned int dx,
unsigned int dy, unsigned int w, unsigned int h)
{
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
unsigned int startpage, endpage, startseg, endseg;
unsigned int curpage, curseg;
unsigned long status;
int i;
unsigned char myseg;
char *cgram;
unsigned char *ppixel;
/* align the request first */
/* Get first line of the page where starting pixel is located */
startpage = floor8(dy) / 8;
/* Get first line of the next page where last pixel to update
* is located */
endpage = h + dy - 1;
endpage = iceil8(endpage) / 8;
/* First segment to update */
startseg = dx;
/* Last segment to update */
endseg = dx + (w - 1);
for (curpage = startpage; curpage < endpage; curpage++) {
cgram = (char*)mx2fbp->cgram_data_vaddr;
cgram += curpage * info->fix.line_length;
cgram += startseg;
for (curseg = startseg; curseg <= endseg; curseg++) {
/* Get segment to update in video memory */
myseg = 0;
ppixel = (unsigned char __force *) mx2fbp->fb_vmem;
/* There is 8 lines per page */
ppixel += curpage * info->fix.line_length * 8;
/* Points to upper pixel of the current segment */
ppixel += curseg;
for (i = 0; i < 8; i++) {
if (*ppixel != 0) {
myseg |= 1 << i;
}
/* Jump to next line to get pixel below */
ppixel += info->fix.line_length;
}
*cgram = myseg;
cgram++;
}
}
dd12832_set_xaddr(0);
dd12832_set_yaddr(0);
while (__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT)) & SLCDC_BUSY_MASK);
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status &= ~(SLCDC_SLCDCCTRLSTAT_AUTOMODE_MASK);
status |= SLCDC_MODE_DATA | SLCDC_START_TRANSFERT;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
}
static void mx2fb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
sys_fillrect(info, rect);
/* update the physical lcd */
dd12832_cgram_update(info, rect->dx, rect->dy, rect->width, rect->height);
}
static void mx2fb_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
sys_copyarea(info, area);
/* update the physical lcd */
dd12832_cgram_update(info, area->dx, area->dy, area->width, area->height);
}
static void mx2fb_imageblit(struct fb_info *info, const struct fb_image *image)
{
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
char *ppixel;
const char *pdata;
int j;
if (image->depth == 1) {
/* Draw image */
pdata = image->data;
ppixel = mx2fbp->fb_vmem;
ppixel += image->dy * info->fix.line_length;
ppixel += image->dx;
for (j = 0; j < image->height; j++) {
memcpy(ppixel, pdata, image->width);
pdata += image->width;
ppixel += info->fix.line_length;
}
/* update the physical lcd */
dd12832_cgram_update(info, image->dx, image->dy, image->width, image->height);
}
}
/*!
* @brief Pans the display.
*
* @param var Frame buffer variable screen structure
* @param info Frame buffer structure that represents a single frame buffer
*
* @return Negative errno on error, or zero on success.
*
* Pan (or wrap, depending on the `vmode' field) the display using the
* 'xoffset' and 'yoffset' fields of the 'var' structure. If the values
* don't fit, return -EINVAL.
*/
static int mx2fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
if (info->var.yoffset == var->yoffset) {
return 0; /* No change, do nothing */
}
if ((var->vmode & FB_VMODE_YWRAP) && (var->yoffset < OLED_WIDTH)
&& (info->var.yres <= OLED_WIDTH)) {
dd12832_set_start_line(var->yoffset);
info->var.yoffset = var->yoffset;
return 0;
}
return -EINVAL;
}
static int mx2fb_sync(struct fb_info *info)
{
dd12832_cgram_update(info, 0, 0, info->fix.line_length, info->var.yres_virtual);
return 0;
}
/*!
* @brief Ioctl function to support customized ioctl operations.
*
* @param info Framebuffer structure that represents a single frame buffer
* @param cmd The command number
* @param arg Argument which depends on cmd
*
* @return Negative errno on error, or zero on success.
*/
static int mx2fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
unsigned char level;
switch (cmd) {
case MX2FB_SET_BRIGHTNESS:
if (copy_from_user((void *)&level, (void *)arg, sizeof(level)))
return -EFAULT;
brightness = level;
dd12832_set_brightness(level);
break;
case MX2FB_FORCE_SYNC:
mx2fb_sync(info);
break;
default:
dev_dbg(info->device, "Unknown ioctl command (0x%08X)\n", cmd);
return -EINVAL;
}
return 0;
}
/*!
* @brief Set fixed framebuffer parameters based on variable settings.
*
* @param info framebuffer information pointer
* @return Negative errno on error, or zero on success.
*/
static void _set_fix(struct fb_info *info)
{
struct fb_fix_screeninfo *fix = &info->fix;
struct fb_var_screeninfo *var = &info->var;
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
strncpy(fix->id, mx2fbp->id, strlen(mx2fbp->id));
if (var->bits_per_pixel < 8)
fix->line_length = var->xres_virtual;
else
fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
fix->type = FB_TYPE_PACKED_PIXELS;
fix->accel = FB_ACCEL_NONE;
fix->visual = FB_VISUAL_MONO10;
fix->xpanstep = 0;
fix->ypanstep = 1;
fix->ywrapstep = 0;
}
/*!
* @brief Validates a var passed in.
*
* @param var Frame buffer variable screen structure
* @param info Frame buffer structure that represents a single frame buffer
*
* @return Negative errno on error, or zero on success.
*
* Checks to see if the hardware supports the state requested by var passed
* in. This function does not alter the hardware state! If the var passed in
* is slightly off by what the hardware can support then we alter the var
* PASSED in to what we can do. If the hardware doesn't support mode change
* a -EINVAL will be returned by the upper layers.
*
*/
static int mx2fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
if (var->xres_virtual < var->xres)
var->xres_virtual = var->xres;
if (var->yres_virtual < var->yres)
var->yres_virtual = var->yres;
if (var->xoffset < 0)
var->xoffset = 0;
if (var->yoffset < 0)
var->yoffset = 0;
if (var->xoffset + info->var.xres > info->var.xres_virtual)
var->xoffset = info->var.xres_virtual - info->var.xres;
if (var->yoffset + info->var.yres > info->var.yres_virtual)
var->yoffset = info->var.yres_virtual - info->var.yres;
if (var->bits_per_pixel != default_bpp)
var->bits_per_pixel = default_bpp;
if (var->pixclock != 0)
var->pixclock = 0;
var->red.length = var->green.length = var->blue.length = 1;
var->red.offset = var->green.offset = var->blue.offset = 0;
var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0;
var->transp.length = 0;
var->transp.offset = 0;
var->transp.msb_right = 0;
var->height = -1;
var->width = -1;
var->grayscale = 0;
/* Copy nonstd field to/from sync for fbset usage */
var->sync |= var->nonstd;
var->nonstd |= var->sync;
return 0;
}
/*!
* @brief Alters the hardware state.
*
* @param info Frame buffer structure that represents a single frame buffer
*
* @return Zero on success others on failure
*
* Using the fb_var_screeninfo in fb_info we set the resolution of this
* particular framebuffer. This function alters the fb_fix_screeninfo stored
* in fb_info. It doesn't not alter var in fb_info since we are using that
* data. This means we depend on the data in var inside fb_info to be
* supported by the hardware. mx2fb_check_var is always called before
* mx2fb_set_par to ensure this.
*/
static int mx2fb_set_par(struct fb_info *info)
{
unsigned long len;
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
_set_fix(info);
len = info->var.yres_virtual * info->fix.line_length;
if (len > info->fix.smem_len) {
if (info->fix.smem_start)
_unmap_video_memory(info);
/* Memory allocation for framebuffer */
if (_map_video_memory(info)) {
dev_err(info->device, "Unable to allocate fb memory\n");
return -ENOMEM;
}
}
_update_slcdc(info);
mx2fb_blank(mx2fbp->blank, info);
return 0;
}
/*
* this is the access path from userspace. they can seek and write to
* the fb. it's inefficient for them to do anything less than 64*8
* writes since we update the lcd in each write() anyway.
*/
static ssize_t mx2fb_write(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos)
{
/* modded from epson 1355 */
unsigned long p;
int err=-EINVAL;
unsigned int fbmemlength, x, y, w, h, startpage, endpage;
struct arcfb_par *par;
unsigned int xres;
p = *ppos;
par = info->par;
xres = info->var.xres;
fbmemlength = info->fix.smem_len;
if (p > fbmemlength)
return -ENOSPC;
err = 0;
if ((count + p) > fbmemlength) {
count = fbmemlength - p;
err = -ENOSPC;
}
if (count) {
char *base_addr;
base_addr = (char __force *)info->screen_base;
count -= copy_from_user(base_addr + p, buf, count);
*ppos += count;
err = -EFAULT;
}
/* Check how many page are affected */
startpage = floor8(p) / 8;
endpage = iceil8((p + count)) / 8;
x = p % info->fix.line_length;
y = p / info->fix.line_length;
w = count % info->fix.line_length;
h = count / info->fix.line_length;
if (startpage != (endpage - 1)) {
/* If several page affected, update complete line
* of all affected pages */
dd12832_cgram_update(info, 0, y, info->fix.line_length, h);
} else {
/* One page has been affected, only updates modified segment
*/
dd12832_cgram_update(info, x, y, w, h);
}
if (p+count > info->fix.line_length) {
/* Several line must be updated */
}
if (count)
return count;
return err;
}
/* fb_mmap
* Map video memory in user space. We don't use the generic fb_mmap method
* mainly
* to allow the use of the TLB streaming flag (CCA=6)
*/
int mx2fb_mmap(struct fb_info *info /*fbi*/, struct vm_area_struct *vma)
{
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
unsigned int len;
unsigned long start=0, off;
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) {
return -EINVAL;
}
start = mx2fbp->fb_pmem & PAGE_MASK;
len = PAGE_ALIGN((start & ~PAGE_MASK) + mx2fbp->fb_len);
off = vma->vm_pgoff << PAGE_SHIFT;
if ((vma->vm_end - vma->vm_start + off) > len) {
return -EINVAL;
}
off += start;
vma->vm_pgoff = off >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
pgprot_val(vma->vm_page_prot) |= (6 << 9);
vma->vm_flags |= VM_IO;
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,
vma->vm_page_prot)) {
return -EAGAIN;
}
return 0;
}
/*!
* @brief Framebuffer file operations
*/
static struct fb_ops mx2fb_ops = {
.owner = THIS_MODULE,
//.fb_read = fb_sys_read,
.fb_write = mx2fb_write,
.fb_check_var = mx2fb_check_var,
.fb_set_par = mx2fb_set_par,
.fb_blank = mx2fb_blank,
.fb_pan_display = mx2fb_pan_display,
.fb_fillrect = mx2fb_fillrect,
.fb_copyarea = mx2fb_copyarea,
.fb_imageblit = mx2fb_imageblit,
.fb_ioctl = mx2fb_ioctl,
.fb_sync = mx2fb_sync,
.fb_mmap = mx2fb_mmap,
};
/*!
* @brief Initialize framebuffer information structure.
*
* @param info framebuffer information pointer
* @param pdev pointer to struct device
* @return Negative errno on error, or zero on success.
*/
static int __init _init_fbinfo(struct fb_info *info,
struct platform_device *pdev)
{
info->device = &pdev->dev;
info->var.activate = FB_ACTIVATE_NOW;
info->fbops = &mx2fb_ops;
info->flags = FBINFO_FLAG_DEFAULT;
info->pseudo_palette = NULL;
return 0;
}
/*!
* @brief Install framebuffer into the system.
*
* @param info framebuffer information pointer
* @param pdev pointer to struct device
* @return Negative errno on error, or zero on success.
*/
static int __init _install_fb(struct fb_info *info,
struct platform_device *pdev)
{
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
if (_init_fbinfo(info, pdev))
return -EINVAL;
if (fb_mode == 0)
fb_mode = pdev->dev.platform_data;
if (!fb_find_mode(&info->var, info, fb_mode, mxcfb_modedb,
mxcfb_modedb_sz, NULL, default_bpp)) {
return -EBUSY;
}
/* Default Y virtual size is 2x panel size */
/* info->var.yres_virtual = info->var.yres << 1; */
if (mx2fbp->type == MX2FB_TYPE_GW)
mx2fbp->blank = FB_BLANK_NORMAL;
else
mx2fbp->blank = FB_BLANK_UNBLANK;
if (mx2fb_set_par(info)) {
return -EINVAL;
}
if (register_framebuffer(info) < 0) {
_unmap_video_memory(info);
return -EINVAL;
}
mx2fbp->registered = 1;
dev_info(info->device, "fb%d registered successfully on %s (rotate = %d).\n",
info->node, fb_mode, mx2fbp->rotate);
return 0;
}
/*!
* @brief Uninstall framebuffer from the system.
*
* @param info framebuffer information pointer
*/
static void __exit _uninstall_fb(struct fb_info *info)
{
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
if (!mx2fbp->registered)
return;
unregister_framebuffer(info);
_unmap_video_memory(info);
if (&info->cmap)
fb_dealloc_cmap(&info->cmap);
mx2fbp->registered = 0;
}
/*!
* @brief Allocate memory for framebuffer.
*
* @param info framebuffer information pointer
* @return Negative errno on error, or zero on success.
*/
static int _map_video_memory(struct fb_info *info)
{
unsigned long page;
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
mx2fbp->fb_len = info->fix.line_length * info->var.yres_virtual;
mx2fbp->fb_vmem = dma_alloc_coherent(info->device, PAGE_ALIGN(mx2fbp->fb_len),
&mx2fbp->fb_pmem, GFP_KERNEL);
if (mx2fbp->fb_vmem == 0) {
dev_err(info->device, "fail to allocate frambuffer (size: %dK))",
mx2fbp->fb_len / 1024);
return -ENOMEM;
}
info->screen_base = mx2fbp->fb_vmem;
info->fix.smem_start = mx2fbp->fb_pmem;
info->fix.smem_len = mx2fbp->fb_len;
dev_dbg(info->device, "Allocated fb @ paddr=0x%08lX, size=%d.\n",
info->fix.smem_start, info->fix.smem_len);
info->screen_size = info->fix.smem_len;
/* Clear the screen */
memset((char *)mx2fbp->fb_vmem, 0x00, mx2fbp->fb_len);
/*
* Set page reserved so that mmap will work. This is necessary
* since we'll be remapping normal memory.
*/
for (page = (unsigned long)mx2fbp->fb_vmem;
page < PAGE_ALIGN((unsigned long)mx2fbp->fb_vmem + mx2fbp->fb_len);
page += PAGE_SIZE) {
#ifdef CONFIG_DMA_NONCOHERENT
SetPageReserved(virt_to_page(CAC_ADDR(page)));
#else
SetPageReserved(virt_to_page(page));
#endif
}
return 0;
}
/*!
* @brief Release memory for framebuffer.
* @param info framebuffer information pointer
*/
static void _unmap_video_memory(struct fb_info *info)
{
struct mx2fb_par *mx2fbp = (struct mx2fb_par *)info->par;
if (mx2fbp->fb_vmem) {
dma_free_noncoherent(info->device, mx2fbp->fb_len, mx2fbp->fb_vmem, mx2fbp->fb_pmem);
}
mx2fbp->fb_len = 0;
mx2fbp->fb_pmem = 0;
mx2fbp->fb_vmem = 0;
info->screen_base = 0;
info->fix.smem_start = 0;
info->fix.smem_len = 0;
/* Free DMA of SLCDC transfert */
dma_pool_free(mx2fbp->cgram_data_dma_pool, (void *)mx2fbp->cgram_data_vaddr, (dma_addr_t) mx2fbp->cgram_data_paddr);
dma_pool_destroy(mx2fbp->cgram_data_dma_pool);
mx2fbp->cgram_data_dma_pool = 0;
mx2fbp->cgram_data_vaddr = 0;
mx2fbp->cgram_data_paddr = 0;
mx2fbp->cgram_data_len = 0;
/* Free DMA of SLCDC transfert */
dma_pool_free(mx2fbp->cgram_cmd_dma_pool, (void *)mx2fbp->cgram_cmd_vaddr, (dma_addr_t) mx2fbp->cgram_cmd_paddr);
dma_pool_destroy(mx2fbp->cgram_cmd_dma_pool);
mx2fbp->cgram_cmd_dma_pool = 0;
mx2fbp->cgram_cmd_vaddr = 0;
mx2fbp->cgram_cmd_paddr = 0;
mx2fbp->cgram_cmd_len = 0;
}
/*
* @brief LCDC interrupt handler
*/
static irqreturn_t mx2fb_isr(int irq, void *dev_id)
{
struct fb_event event;
unsigned long status;
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status &= SLCDC_IRQ_FLAGS_MASK;
if (status & SLCDC_IRQ_FLAG) {
printk("Oled xfer done : ");
if (status == SLCDC_IRQ_FLAG) {
printk("Successful\n");
}
if (status & SLCDC_IRQ_TEA_FLAG) {
printk("DMA error\n");
event.info = &mx2fb_info;
atomic_notifier_call_chain(&mx2fb_notifier_list,
FB_EVENT_MXC_DMA_ERROR, &event);
}
if (status & SLCDC_IRQ_UNDRFLOW_FLAG) {
printk("Underflow occurs\n");
event.info = &mx2fb_info;
atomic_notifier_call_chain(&mx2fb_notifier_list,
FB_EVENT_MXC_UNDERFLOW, &event);
}
}
/* Write 1 to clear the status */
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status |= SLCDC_IRQ_FLAGS_MASK;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
return IRQ_HANDLED;
}
/*!
* @brief Config and request LCDC interrupt
*/
static void _request_irq(void)
{
unsigned long status;
unsigned long flags;
/* Write 1 to clear the status */
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status |= SLCDC_IRQ_FLAGS_MASK;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
if (request_irq(INT_SLCDC, mx2fb_isr, 0, "SLCDC", 0))
pr_info("Request LCDC IRQ failed.\n");
else {
spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);
/* Enable interrupt in case client has registered */
//if (mx2fb_notifier_list.head != NULL) {
//unsigned long status;
//unsigned long ints = MX2FB_INT_EOF;
//ints |= MX2FB_INT_GW_EOF;
/* Enable interrupt in case client has registered */
//if (mx2fb_notifier_list.head != NULL) {
/* Write 1 to clear the status */
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status |= SLCDC_IRQ_FLAGS_MASK;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
/* Enable SLCDC interrupt */
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status |= SLCDC_IRQ_ENABLE;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
//}
//}
spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);
}
}
/*!
* @brief Free LCDC interrupt handler
*/
static void _free_irq(void)
{
unsigned long status;
status =__raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
/* Write 1 to clear the status */
status |= SLCDC_IRQ_FLAGS_MASK;
/* Disable all LCDC interrupt */
status &= ~SLCDC_IRQ_ENABLE;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
free_irq(INT_SLCDC, 0);
}
/*!
* @brief Register a client notifier
* @param nb notifier block to callback on events
*/
int mx2fb_register_client(struct notifier_block *nb)
{
unsigned long flags, status;
int ret;
ret = atomic_notifier_chain_register(&mx2fb_notifier_list, nb);
spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);
/* Enable interrupt in case client has registered */
if (mx2fb_notifier_list.head != NULL) {
/* Write 1 to clear the status */
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status |= SLCDC_IRQ_FLAGS_MASK;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
/* Enable SLCDC interrupt */
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status |= SLCDC_IRQ_ENABLE;
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
}
spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);
return ret;
}
/*!
* @brief Unregister a client notifier
* @param nb notifier block to callback on events
*/
int mx2fb_unregister_client(struct notifier_block *nb)
{
unsigned long flags, status;
int ret;
ret = atomic_notifier_chain_unregister(&mx2fb_notifier_list, nb);
spin_lock_irqsave(&mx2fb_notifier_list.lock, flags);
/* Mask interrupt in case no client registered */
if (mx2fb_notifier_list.head == NULL) {
/* Enable SLCDC interrupt */
status = __raw_readl(SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
status &= ~(SLCDC_IRQ_ENABLE);
__raw_writel(status, SLCDC_REG(SLCDC_SLCDCCTRLSTAT));
}
spin_unlock_irqrestore(&mx2fb_notifier_list.lock, flags);
return ret;
}
#ifdef CONFIG_PM
/*
* Power management hooks. Note that we won't be called from IRQ context,
* unlike the blank functions above, so we may sleep.
*/
/*!
* @brief Suspends the framebuffer and blanks the screen.
* Power management support
*/
#ifdef CONFIG_FB_MXC_EPSON_L4F0024
static int mx2fb_spi_suspend(struct spi_device *spi, pm_message_t state)
{
#else
static int mx2fb_suspend(struct platform_device *pdev, pm_message_t state)
{
#endif
_disable_slcdc(&mx2fb_info);
return 0;
}
/*!
* @brief Resumes the framebuffer and unblanks the screen.
* Power management support
*/
#ifdef CONFIG_FB_MXC_EPSON_L4F0024
static int mx2fb_spi_resume(struct spi_device *spi)
#else
static int mx2fb_resume(struct platform_device *pdev)
#endif
{
_enable_slcdc(&mx2fb_info);
return 0;
}
#endif /* CONFIG_PM */
/*!
* @brief Probe routine for the framebuffer driver. It is called during the
* driver binding process.
*
* @return Appropriate error code to the kernel common code
*/
static int mx2fb_probe(struct platform_device *pdev)
{
int ret;
slcdc_clk = clk_get(&pdev->dev, "slcdc_clk");
clk_enable(slcdc_clk);
gpio_slcdc_active();
/* Memory allocation of display data for DMA transfert */
/* convert 1 byte / pixel length to 1 byte / column (8 pixels) */
mx2fbp_bg.cgram_data_len = OLED_WIDTH*OLED_HEIGHT / 8;
/* Allocate memory for CGRAM mirror with 128k boundary
* (cf note SLCDC in Ch. 44.2 of i.MX27 RM) aligned on 1 byte */
mx2fbp_bg.cgram_data_dma_pool = dma_pool_create("SLCDC_DMA_DATA", &pdev->dev, mx2fbp_bg.cgram_data_len, 1, 128*1024);
if (mx2fbp_bg.cgram_data_dma_pool == NULL) {
dev_err(&pdev->dev, "Unable to allocated DMA Pool.\n");
return -ENOMEM;
}
mx2fbp_bg.cgram_data_vaddr = (unsigned long)dma_pool_alloc(mx2fbp_bg.cgram_data_dma_pool, GFP_DMA | GFP_KERNEL, (dma_addr_t *) &mx2fbp_bg.cgram_data_paddr);
if ((void *)mx2fbp_bg.cgram_data_vaddr == NULL) {
dev_err(&pdev->dev, "Unable to allocated DMA memory.\n");
return -ENOMEM;
}
/* Memory allocation of display commands for DMA transfert */
/* a page address on LCD is defined by 3 three commands.
* Each command must be joined with a byte containing the state
* of RS pin to apply (cf Fig 44-5 from SLCDC chapter in i.MX27 RM).
* Array must be defined as unsigned short */
mx2fbp_bg.cgram_cmd_len = sizeof(_ssd1305_pagecmd_array)/sizeof(_ssd1305_pagecmd_array[0]);
/* Allocate memory for CGRAM mirror with 128k boundary
* (cf note SLCDC in Ch. 44.2 of i.MX27 RM) aligned on 2 bytes (array of shorts) */
mx2fbp_bg.cgram_cmd_dma_pool = dma_pool_create("SLCDC_DMA_CMD", &pdev->dev, mx2fbp_bg.cgram_cmd_len/(sizeof(int)/sizeof(_ssd1305_pagecmd_array[0])), 2, 128*1024);
if (mx2fbp_bg.cgram_cmd_dma_pool == NULL) {
dev_err(&pdev->dev, "Unable to allocated DMA Pool.\n");
return -ENOMEM;
}
mx2fbp_bg.cgram_cmd_vaddr = (unsigned long)dma_pool_alloc(mx2fbp_bg.cgram_cmd_dma_pool, GFP_DMA | GFP_KERNEL, (dma_addr_t *) &mx2fbp_bg.cgram_cmd_paddr);
if ((void *)mx2fbp_bg.cgram_cmd_vaddr == NULL) {
dev_err(&pdev->dev, "Unable to allocated DMA memory.\n");
return -ENOMEM;
}
slcdc_first_init();
dd12832_init_controller();
ret = _install_fb(&mx2fb_info, pdev);
if (ret) {
dev_err(&pdev->dev,
"Failed to register framebuffer\n");
return ret;
}
//_request_irq();
return 0;
}
/*!
* @brief This structure contains pointers to the power management
* callback functions.
*/
static struct platform_driver mx2fb_driver = {
.driver = {
.name = "mxc_sdc_fb",
.owner = THIS_MODULE,
.bus = &platform_bus_type,
},
.probe = mx2fb_probe,
.suspend = mx2fb_suspend,
.resume = mx2fb_resume,
};
/*!
* @brief Initialization
*/
int __init mx2fb_init(void)
{
int ret = 0;
ret = platform_driver_register(&mx2fb_driver);
return ret;
}
/*!
* @brief Cleanup
*/
void __exit mx2fb_exit(void)
{
_free_irq();
_uninstall_fb(&mx2fb_info);
platform_driver_unregister(&mx2fb_driver);
}
/* Modularization */
module_init(mx2fb_init);
module_exit(mx2fb_exit);
EXPORT_SYMBOL(mx2_gw_set);
EXPORT_SYMBOL(mx2fb_register_client);
EXPORT_SYMBOL(mx2fb_unregister_client);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MX2 framebuffer driver");
MODULE_LICENSE("GPL");
^ permalink raw reply
* Re: [RFC 0/5] Generic panel framework
From: Tomi Valkeinen @ 2012-08-17 8:38 UTC (permalink / raw)
To: Laurent Pinchart
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-1-git-send-email-laurent.pinchart@ideasonboard.com>
[-- Attachment #1: Type: text/plain, Size: 4980 bytes --]
Hi,
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
> I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If
Oookay, where to start... ;)
A few cosmetic/general comments first.
I find the file naming a bit strange. You have panel.c, which is the
core framework, panel-dbi.c, which is the DBI bus, panel-r61517.c, which
is driver for r61517 panel...
Perhaps something in this direction (in order): panel-core.c,
mipi-dbi-bus.c, panel-r61517.c? And we probably end up with quite a lot
of panel drivers, perhaps we should already divide these into separate
directories, and then we wouldn't need to prefix each panel with
"panel-" at all.
---
Should we aim for DT only solution from the start? DT is the direction
we are going, and I feel the older platform data stuff would be
deprecated soon.
---
Something missing from the intro is how this whole thing should be used.
It doesn't help if we know how to turn on the panel, we also need to
display something on it =). So I think some kind of diagram/example of
how, say, drm would use this thing, and also how the SoC specific DBI
bus driver would be done, would clarify things.
---
We have discussed face to face about the different hardware setups and
scenarios that we should support, but I'll list some of them here for
others:
1) We need to support chains of external display chips and panels. A
simple example is a chip that takes DSI in, and outputs DPI. In that
case we'd have a chain of SoC -> DSI2DPI -> DPI panel.
In final products I think two external devices is the maximum (at least
I've never seen three devices in a row), but in theory and in
development environments the chain can be arbitrarily long. Also the
connections are not necessarily 1-to-1, but a device can take one input
while it has two outputs, or a device can take two inputs.
Now, I think two external devices is a must requirement. I'm not sure if
supporting more is an important requirement. However, if we support two
devices, it could be that it's trivial to change the framework to
support n devices.
2) Panels and display chips are all but standard. They very often have
their own sequences how to do things, have bugs, or implement some
feature in slightly different way than some other panel. This is why the
panel driver should be able to control or define the way things happen.
As an example, Sharp LQ043T1DG01 panel
(www.sharpsme.com/download/LQ043T1DG01-SP-072106pdf). It is enabled with
the following sequence:
- Enable VCC and AVDD regulators
- Wait min 50ms
- Enable full video stream (pck, syncs, pixels) from SoC
- Wait min 0.5ms
- Set DISP GPIO, which turns on the display panel
Here we could split the enabling of panel to two parts, prepare (in this
case starts regulators and waits 50ms) and finish (wait 0.5ms and set
DISP GPIO), and the upper layer would start the video stream in between.
I realize this could be done with the PANEL_ENABLE_* levels in your RFC,
but I don't think the concepts quite match:
- PANEL_ENABLE_BLANK level is needed for "smart panels", as we need to
configure them and send the initial frame at that operating level. With
dummy panels there's really no such level, there's just one enable
sequence that is always done right away.
- I find waiting at the beginning of a function very ugly (what are we
waiting for?) and we'd need that when changing the panel to
PANEL_ENABLE_ON level.
- It's still limited if the panel is a stranger one (see following
example).
Consider the following theoretical panel enable example, taken to absurd
level just to show the general problem:
- Enable regulators
- Enable video stream
- Wait 50ms
- Disable video stream
- Set enable GPIO
- Enable video stream
This one would be rather impossible with the upper layer handling the
enabling of the video stream. Thus I see that the panel driver needs to
control the sequences, and the Sharp panel driver's enable would look
something like:
regulator_enable(...);
sleep();
dpi_enable_video();
sleep();
gpip_set(..);
Note that even with this model we still need the PANEL_ENABLE levels you
have.
---
I'm not sure I understand the panel unload problem you mentioned. Nobody
should have direct references to the panel functions, so there shouldn't
be any automatic references that would prevent module unloading. So when
the user does rmmod panel-mypanel, the panel driver's remove will be
called. It'll unregister itself from the panel framework, which causes
notifications and the display driver will stop using the panel. After
that nobody has pointers to the panel, and it can safely be unloaded.
It could cause some locking issues, though. First the panel's remove
could take a lock, but the remove sequence would cause the display
driver to call disable on the panel, which could again try to take the
same lock...
Tomi
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Alex Courbot @ 2012-08-17 8:52 UTC (permalink / raw)
To: Stephen Warren
Cc: Stephen Warren, Thierry Reding, Simon Glass, Grant Likely,
Rob Herring, Mark Brown, Anton Vorontsov, David Woodhouse,
Arnd Bergmann, Leela Krishna Amudala, linux-tegra@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-fbdev@vger.kernel.org,
devicetree-discuss@lists.ozlabs.org, linux-doc@vger.kernel.org
In-Reply-To: <502D3E29.1010501@wwwdotorg.org>
On 08/17/2012 03:38 AM, Stephen Warren wrote:
> On 08/16/2012 12:08 AM, Alexandre Courbot wrote:
>> Some device drivers (panel backlights especially) need to follow precise
>> sequences for powering on and off, involving gpios, regulators, PWMs
>> with a precise powering order and delays to respect between each steps.
>> These sequences are board-specific, and do not belong to a particular
>> driver - therefore they have been performed by board-specific hook
>> functions to far.
>>
>> With the advent of the device tree and of ARM kernels that are not
>> board-tied, we cannot rely on these board-specific hooks anymore but
>> need a way to implement these sequences in a portable manner. This patch
>> introduces a simple interpreter that can execute such power sequences
>> encoded either as platform data or within the device tree.
>
>> diff --git a/Documentation/devicetree/bindings/power_seq/power_seq.txt b/Documentation/devicetree/bindings/power_seq/power_seq.txt
>
>> +Specifying Power Sequences in the Device Tree
>> +======================>> +In the device tree, power sequences are specified as sub-nodes of the device
>> +node and reference resources declared by that device.
>> +
>> +For an introduction about runtime interpreted power sequences, see
>> +Documentation/power/power_seq.txt and include/linux/power_seq.h.
>
> Device tree bindings shouldn't reference Linux documentation; the
> bindings are supposed to be OS-agnostic.
Ok, I should be able to do without this reference anyway.
>
>> +Power Sequences Structure
>> +-------------------------
>> +Power sequences are sub-nodes that are named such as the device driver can find
>> +them. The driver's documentation should list the sequence names it recognizes.
>
> That's a little roundabout. That might be better as simply:
>
> Valid power sequence names are defined by each device's binding. For a
> power sequence named "foo", a node named "foo-power-sequence" defines
> that sequence.
>
>> +Inside a power sequence node are sub-nodes that describe the different steps
>> +of the sequence. Each step must be named sequentially, with the first step
>> +named step0, the second step1, etc. Failure to follow this rule will result in a
>> +parsing error.
>
> Node names shouldn't be interpreted by the code; nodes should all be
> named after the type of object the represent. Hence, every step should
> be named just "step" for example.
>
> The node's unit address (@0) should be used to distinguish the nodes.
> This requires reg properties within each node to match the unit address,
> and hence #address-cells and #size-cells properties in the power
> sequence itself.
While this logic is perfectly suitable and adapted for devices, I think
we should keep in mind that power sequences and their steps are not
devices, but just arbitrary bits of information. Having adress cells and
reg properties is useful when one needs to reference a node through a
phandle, but this is *never* going to happen with sequence steps (unless
we go insane and decide to allow goto-like statements :P). So having
#address-cells, #size-cells, and reg serve absolutely no purpose here
but cluttering the DT. If there is a hard rule that needs to be
enforced, then let that be, but the other discussion you started (thanks
for that by the way) does not seem to suggest so.
> Note that this somewhat conflicts with accessing the top-level power
> sequence by name too; perhaps that should be re-thought too. I must
> admit this DT rule kinda sucks.
>
>> +Power Sequences Steps
>> +---------------------
>> +Every step of a sequence describes an action to be performed on a resource. It
>> +generally includes a property named after the resource type, and which value
>> +references the resource to be used. Depending on the resource type, additional
>> +properties can be defined to control the action to be performed.
>
> I think you need to add a property that indicates what type of resource
> each step applies to. Sure, this is implicit in that if a "gpio"
> property exists, the step is a GPIO step, but in order to make that
> work, you'd have to search each node for each possible resource type's
> property name. It'd be far better to read a single type="gpio" property,
> then parse the step based on that.
Indeed right now all resource types must be checked. Having a type
property would make that easier. I like to keep the DT as compact and
expressive as possible, but I guess one more property per step would not
hurt and is perhaps easier to understand too.
>> +Example
>> +-------
>> +Here are example sequences declared within a backlight device that use all the
>> +supported resources types:
>> +
>> + backlight {
>> + compatible = "pwm-backlight";
>> + ...
>> +
>> + /* resources used by the sequences */
>> + pwms = <&pwm 2 5000000>;
>> + pwm-names = "backlight";
>> + power-supply = <&backlight_reg>;
>> + enable-gpio = <&gpio 28 0>;
>> +
>> + power-on-sequence {
>> + step0 {
>> + regulator = "power";
>> + enable;
>> + };
>> + step1 {
>> + delay = <10000>;
>> + };
>> + step2 {
>> + pwm = "backlight";
>> + enable;
>> + };
>> + step3 {
>> + gpio = "enable";
>> + enable;
>> + };
>> + };
>> +
>> + power-off-sequence {
>> + step0 {
>> + gpio = "enable";
>> + disable;
>> + };
>> + step1 {
>> + pwm = "backlight";
>> + disable;
>> + };
>> + step2 {
>> + delay = <10000>;
>> + };
>> + step3 {
>> + regulator = "power";
>> + disable;
>> + };
>> + };
>> + };
>
> I notice that for clocks, pwms, and interrupts, the initial step of the
> lookup is via a single property that lists all know resources of that
> type. Regulators and GPIOs don't follow this style though. Using the
> same mechanism for power-sequences would yield something like the
> following, which would avoid the "node names must be significant" issue
> I mention above, although it does make everything rather more wordy.
>
> [start my proposal]
>> backlight {
>> compatible = "pwm-backlight";
>>
>> /* resources used by the sequences */
>> pwms = <&pwm 2 5000000>;
>> pwm-names = "backlight";
>> power-supply = <&backlight_reg>;
>> bl-enable-gpio = <&gpio 28 0>;
>> pwr-seqs = <&bl_on &bl_off>;
>> pwr-seq-names = "on", "off";
>>
>> #address-cells = <1>;
>> #size-cells = <0>;
>>
>> bl_on: pwr-seq@0 {
>> reg = <0>;
>> #address-cells = <1>;
>> #size-cells = <0>;
>> step@0 {
>> reg = <0>;
>> type = "regulator";
>> regulator = "power";
>> enable;
>> };
>> step@1 {
>> reg = <1>;
>> type = "delay";
>> delay = <10000>;
>> };
>> step@2 {
>> reg = <2>;
>> type = "pwm";
>> pwm = "backlight";
>> enable;
>> };
>> step@3 {
>> reg = <3>;
>> type = "gpio";
>> gpio = "bl-enable";
>> enable;
>> };
>> };
>>
>> bl_off: pwr-seq@1 {
>> reg = <1>;
>> #address-cells = <1>;
>> #size-cells = <0>;
>> step@0 {
>> reg = <0>;
>> type = "gpio";
>> gpio = "bl-enable";
>> disable;
>> };
>> step@1 {
>> reg = <1>;
>> type = "pwm";
>> pwm = "backlight";
>> disable;
>> };
>> step@2 {
>> reg = <2>;
>> type = "delay";
>> delay = <10000>;
>> };
>> step@3 {
>> reg = <3>;
>> type = "regulator";
>> regulator = "power";
>> disable;
>> };
>> };
>> };
>>
> [end my proposal]
Mmmm, so looking at this, what strikes me is that the amount of
unused/redundant information is actually higher than the amount of
information the driver will effectively use. If these conventions can be
ignored here, we definitely should do that.
>
>> diff --git a/Documentation/power/power_seq.txt b/Documentation/power/power_seq.txt
>
>> +Usage by Drivers and Resources Management
>> +-----------------------------------------
>> +Power sequences make use of resources that must be properly allocated and
>> +managed. The power_seq_build() function builds a power sequence from the
>> +platform data. It also takes care of resolving and allocating the resources
>> +referenced by the sequence if needed:
>> +
>> + struct power_seq *power_seq_build(struct device *dev, struct list_head *ress,
>> + struct platform_power_seq *pseq);
>> +
>> +The 'dev' argument is the device in the name of which the resources are to be
>> +allocated.
>> +
>> +The 'ress' argument is a list to which the resolved resources are appended. This
>> +avoids allocating a resource referenced in several power sequences multiple
>> +times.
>
> I see in other parts of the thread, there has been discussion re:
> needing the separate ress parameter, and requiring the driver to pass
> this in to multiple power_seq_build calls.
>
> The way the pinctrl subsystem solved this was to have a single function
> that parsed all pinctrl setting (c.f. all power sequences) at once, and
> return a single handle. Later, the driver passes this handle
> pinctrl_lookup_state(), along with the requested state (c.f. sequence
> name) to search for, and finally passes that handle to
> pinctrl_select_state(). Doing something similar here would result in:
>
> struct power_seqs *power_seq_get(struct device *dev);
> void power_seq_put(struct power_seqs *seqs);
> struct power_seq *power_seq_lookup(struct power_seqs *seqs,
> const char *name);
> int power_seq_executestruct power_seq *seq);
>
> struct power_seqs *devm_power_seq_get(struct device *dev);
> void devm_power_seq_put(struct power_seqs *seqs);
Well, if both of you bring this then I have no choice but seriously
consider that. :) If other subsystems follow the same scheme then this
is an additional point for this.
>
>> +On success, the function returns a devm allocated resolved sequence that is
>
> Perhaps the function should be named devm_power_seq_build(), in order to
> make this obvious to people who only read the client code, and not this
> documentation.
Agreed.
Thanks,
Alex.
^ permalink raw reply
* Re: [PATCH v4 0/3] Runtime Interpreted Power Sequences
From: Alex Courbot @ 2012-08-17 8:54 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Stephen Warren, Thierry Reding, Simon Glass, Grant Likely,
Rob Herring, Mark Brown, Anton Vorontsov, David Woodhouse,
Arnd Bergmann, Leela Krishna Amudala, linux-tegra@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-fbdev@vger.kernel.org,
devicetree-discuss@lists.ozlabs.org, linux-doc@vger.kernel.org
In-Reply-To: <201208162347.34810.rjw@sisk.pl>
On 08/17/2012 06:47 AM, Rafael J. Wysocki wrote:
> On Thursday, August 16, 2012, Alexandre Courbot wrote:
>> Overdue revision of this new feature, some changes required additional thought
>> and rework.
>>
>> The most important change is in the way power sequences are expressed in the
>> device tree. In order to avoid having to specify #address-cells, #size-cells and
>> reg properties, the @ notation in the step names is dropped, and instead a
>> fixed, sequential naming is adopted. The type of the resource used by a step is
>> decided by the presence of some recognized properties:
>>
>> power-on-sequence {
>> step0 {
>> regulator = "power";
>> enable;
>> };
>> step1 {
>> delay = <10000>;
>> };
>> step2 {
>> pwm = "backlight";
>> enable;
>> };
>> ...
>>
>> To me this looks safe, clear and close to the platform data representation, but
>> needs approval from DT experts.
>>
>> Resources are still referenced by name instead of having their phandles defined
>> directly inside the sequences, as previous discussion came to the conclusion
>> that doing so would require controversial changes to the regulator and PWM
>> frameworks, and that having the resources declared at the device level was
>> making sense logically speaking.
>>
>> Other changes/fixes since last revision:
>> * Move to drivers/power/ (hope this is ok with the maintainers?)
>> * Use microseconds for delay
>> * Use devm for PWM resources and remove cleanup function as all resources are
>> devm-managed
>> * Remove "-gpio" suffix for GPIO reference in the driver
>> * Remove params structure
>> * Make power_seq structure private
>> * Number of steps in a sequence is explicitly stated instead of resorting to a
>> "stop" sequence step
>> * Delays are a step instead of being a step parameter
>> * Use flexible member arrays to limit number of memory allocations
>> * Add documentation to DT bindings
>>
>> There was a lot of feedback on the previous version (thanks!) so if I forgot
>> to address some important point, please bring it to my attention again.
>>
>> Alexandre Courbot (3):
>> Runtime Interpreted Power Sequences
>> pwm_backlight: use power sequences
>> tegra: add pwm backlight device tree nodes
>
> May I ask that the next version of this patchset be CCed to
> linux-pm@vger.kernel.org?
Will do.
Alex.
^ permalink raw reply
* Re: [RFC 3/5] video: panel: Add MIPI DBI bus support
From: Tomi Valkeinen @ 2012-08-17 9:03 UTC (permalink / raw)
To: Laurent Pinchart
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <1345164583-18924-4-git-send-email-laurent.pinchart@ideasonboard.com>
[-- Attachment #1: Type: text/plain, Size: 3118 bytes --]
On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
> +/* -----------------------------------------------------------------------------
> + * Bus operations
> + */
> +
> +void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long cmd)
> +{
> + dev->bus->ops->write_command(dev->bus, cmd);
> +}
> +EXPORT_SYMBOL_GPL(panel_dbi_write_command);
> +
> +void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long data)
> +{
> + dev->bus->ops->write_data(dev->bus, data);
> +}
> +EXPORT_SYMBOL_GPL(panel_dbi_write_data);
> +
> +unsigned long panel_dbi_read_data(struct panel_dbi_device *dev)
> +{
> + return dev->bus->ops->read_data(dev->bus);
> +}
> +EXPORT_SYMBOL_GPL(panel_dbi_read_data);
I'm not that familiar with how to implement bus drivers, can you
describe in pseudo code how the SoC's DBI driver would register these?
I think write/read data functions are a bit limited. Shouldn't they be
something like write_data(const u8 *buf, int size) and read_data(u8
*buf, int len)?
Something that's totally missing is configuring the DBI bus. There are a
bunch of timing related values that need to be configured. See
include/video/omapdss.h struct rfbi_timings. While the struct is OMAP
specific, if I recall right most of the values match to the MIPI DBI
spec.
And this makes me wonder, you use DBI bus for SYS-80 panel. The busses
may look similar in operation, but are they really so similar when you
take into account the timings (and perhaps something else, it's been
years since I read the MIPI DBI spec)?
Then there's the start_transfer. This is something I'm not sure what is
the best way to handle it, but the same problems that I mentioned in the
previous post related to enable apply here also. For example, what if
the panel needs to be update in two parts? This is done in Nokia N9.
From panel's perspective, it'd be best to handle it somewhat like this
(although asynchronously, probably):
write_update_area(0, 0, xres, yres / 2);
write_memory_start()
start_pixel_transfer();
wait_transfer_done();
write_update_area(0, yres / 2, xres, yres / 2);
write_memory_start()
start_pixel_transfer();
Why I said I'm not sure about this is that it does complicate things, as
the actual pixel data often comes from the display subsystem hardware,
which should probably be controlled by the display driver.
I think there also needs to be some kind of transfer_done notifier, for
both the display driver and the panel driver. Although if the display
driver handles starting the actual pixel transfer, then it'll get the
transfer_done via whatever interrupt the SoC has.
Also as food for thought, videomode timings does not really make sense
for DBI panels, at least when you just consider the DBI side. There's
really just the resolution of the display, and then the DBI timings. No
pck, syncs, etc. Of course in the end there's the actual panel, which
does have these video timings. But often they cannot be configured, and
often you don't even know them as the specs don't tell them.
Tomi
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [RFC 3/5] video: panel: Add MIPI DBI bus support
From: Laurent Pinchart @ 2012-08-17 10:02 UTC (permalink / raw)
To: Tomi Valkeinen
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <1345194182.3158.66.camel@deskari>
Hi Tomi,
Thank you for the review.
On Friday 17 August 2012 12:03:02 Tomi Valkeinen wrote:
> On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
> > +/*
> > -------------------------------------------------------------------------
> > ---- + * Bus operations
> > + */
> > +
> > +void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long
> > cmd) +{
> > + dev->bus->ops->write_command(dev->bus, cmd);
> > +}
> > +EXPORT_SYMBOL_GPL(panel_dbi_write_command);
> > +
> > +void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long
> > data) +{
> > + dev->bus->ops->write_data(dev->bus, data);
> > +}
> > +EXPORT_SYMBOL_GPL(panel_dbi_write_data);
> > +
> > +unsigned long panel_dbi_read_data(struct panel_dbi_device *dev)
> > +{
> > + return dev->bus->ops->read_data(dev->bus);
> > +}
> > +EXPORT_SYMBOL_GPL(panel_dbi_read_data);
>
> I'm not that familiar with how to implement bus drivers, can you
> describe in pseudo code how the SoC's DBI driver would register these?
Sure.
The DBI bus driver first needs to create a panel_dbi_bus_ops instance:
static const struct panel_dbi_bus_ops sh_mobile_lcdc_dbi_bus_ops = {
.write_command = lcdc_dbi_write_command,
.write_data = lcdc_dbi_write_data,
.read_data = lcdc_dbi_read_data,
};
and a panel_dbi_bus instance, usually embedded in its private driver data
structure, and initialize it by setting its dev and ops fields:
ch->dbi_bus.dev = ch->lcdc->dev;
ch->dbi_bus.ops = &sh_mobile_lcdc_dbi_bus_ops;
In my current implementation, the panel_dbi_device is created in board code:
static struct panel_dbi_device migor_panel_device = {
.name = "r61505",
.id = 0,
.dev = {
.platform_data = &migor_panel_info,
},
};
A pointer to that structure is passed to the DBI master driver, which then
registers the device:
panel_dbi_device_register(dbi, &ch->dbi_bus);
With a DT-based solution the DBI core will expose a function to register DBI
devices from OF nodes.
The bus itself is currently not registered with the DBI code because there was
no need to.
> I think write/read data functions are a bit limited. Shouldn't they be
> something like write_data(const u8 *buf, int size) and read_data(u8
> *buf, int len)?
Good point. My hardware doesn't support multi-byte read/write operations
directly so I haven't thought about adding those.
Can your hardware group command + data writes in a single operation ? If so we
should expose that at the API level as well.
Is DBI limited to 8-bit data transfers for commands ? Pixels can be
transferred 16-bit at a time, commands might as well. While DCS only specifies
8-bit command/data, DBI panels that are not DCS compliant can use 16-bit
command/data (the R61505 panel, albeit a SYS-80 panel, does so).
> Something that's totally missing is configuring the DBI bus. There are a
> bunch of timing related values that need to be configured. See
> include/video/omapdss.h struct rfbi_timings. While the struct is OMAP
> specific, if I recall right most of the values match to the MIPI DBI
> spec.
I've left that out currently, and thought about passing that information as
platform data to the DBI bus driver. That's the easiest solution, but I agree
that it's a hack. Panel should expose their timing requirements to the DBI
host. API wise that wouldn't be difficult (we only need to add timing
information to the panel platform data and add a function to the DBI API to
retrieve it), one of challenges might be to express it in a way that's both
universal enough and easy to use for DBI bus drivers.
> And this makes me wonder, you use DBI bus for SYS-80 panel. The busses
> may look similar in operation, but are they really so similar when you
> take into account the timings (and perhaps something else, it's been
> years since I read the MIPI DBI spec)?
I'll have to check all the details. SYS-80 is similar to DBI-B, but supports a
wider bus width of 18 bits. I think the interfaces are similar enough to use a
single bus implementation, possibly with quirks and/or options (see SCCB
support in I2C for instance, with flags to ignore acks, force a stop bit
generation, ...). We would duplicate lots of code if we had two different
implementations, and would prevent a DBI panel to be attached to a SYS-80 host
and vice-versa (the format is known to work).
> Then there's the start_transfer. This is something I'm not sure what is
> the best way to handle it, but the same problems that I mentioned in the
> previous post related to enable apply here also. For example, what if
> the panel needs to be update in two parts? This is done in Nokia N9.
> From panel's perspective, it'd be best to handle it somewhat like this
> (although asynchronously, probably):
>
> write_update_area(0, 0, xres, yres / 2);
> write_memory_start()
> start_pixel_transfer();
>
> wait_transfer_done();
>
> write_update_area(0, yres / 2, xres, yres / 2);
> write_memory_start()
> start_pixel_transfer();
>
> Why I said I'm not sure about this is that it does complicate things, as
> the actual pixel data often comes from the display subsystem hardware,
> which should probably be controlled by the display driver.
I have no solution for this at the moment. That's an advanced (but definitely
required) feature, I've tried to concentrate on the basics first.
> I think there also needs to be some kind of transfer_done notifier, for
> both the display driver and the panel driver. Although if the display
> driver handles starting the actual pixel transfer, then it'll get the
> transfer_done via whatever interrupt the SoC has.
>
> Also as food for thought, videomode timings does not really make sense
> for DBI panels, at least when you just consider the DBI side. There's
> really just the resolution of the display, and then the DBI timings. No
> pck, syncs, etc. Of course in the end there's the actual panel, which
> does have these video timings. But often they cannot be configured, and
> often you don't even know them as the specs don't tell them.
We might just need to provide fake timings. Video mode timings are at the core
of display support in all drivers so we can't just get rid of them. The h/v
front/back porch and sync won't be used by display drivers for DBI/DSI panels
anyway.
--
Regards,
Laurent Pinchart
^ permalink raw reply
* [PATCH 0/3] OMAPDSS: Miscellaneous cleanup patches
From: Archit Taneja @ 2012-08-17 10:51 UTC (permalink / raw)
To: tomi.valkeinen; +Cc: linux-omap, linux-fbdev, Archit Taneja
In-Reply-To: <343817088-29645-1-git-send-email-archit@ti.com>
These are minor cleanup patches which will be useful for the future series on
outputs. It's harder to add output entities in DSS when there are more
omap_dss_device references in the driver. The first 2 reduces that a bit. The
third patch just removes some left over fields from omap_dss_device
Refenence Tree:
git://gitorious.org/~boddob/linux-omap-dss2/archit-dss2-clone.git 1-misc-clean-for-outputs
Archit Taneja (3):
OMAPDSS: DSI: Pass dsi platform device wherever possible
OMAPDSS: APPLY: Remove omap_dss_device references in wait_for_go
functions
OMAPDSS: Remove unnecessary acb/acbi pin fields from omap_dss_device
drivers/video/omap2/dss/apply.c | 32 +++++++++-----
drivers/video/omap2/dss/dsi.c | 89 +++++++++++++++++----------------------
include/video/omapdss.h | 4 --
3 files changed, 60 insertions(+), 65 deletions(-)
--
1.7.9.5
^ permalink raw reply
* [PATCH 1/3] OMAPDSS: DSI: Pass dsi platform device wherever possible
From: Archit Taneja @ 2012-08-17 10:51 UTC (permalink / raw)
To: tomi.valkeinen; +Cc: linux-omap, linux-fbdev, Archit Taneja
In-Reply-To: <1345200551-28712-1-git-send-email-archit@ti.com>
Many of the DSI functions receive the connected panel's omap_dss_device pointer
as an argument. The platform device pointer is then derived via omap_dss_device
pointers.
Most of these functions don't really require omap_dss_device pointer anymore
since we now keep copies of parameters in the driver data which were previously
available only via omap_dss_device. Replace the arguments with platform device
pointers for such functions.
Signed-off-by: Archit Taneja <archit@ti.com>
---
drivers/video/omap2/dss/dsi.c | 89 ++++++++++++++++++-----------------------
1 file changed, 39 insertions(+), 50 deletions(-)
diff --git a/drivers/video/omap2/dss/dsi.c b/drivers/video/omap2/dss/dsi.c
index 96d0024..659b6cd 100644
--- a/drivers/video/omap2/dss/dsi.c
+++ b/drivers/video/omap2/dss/dsi.c
@@ -2014,9 +2014,8 @@ static unsigned dsi_get_line_buf_size(struct platform_device *dsidev)
}
}
-static int dsi_set_lane_config(struct omap_dss_device *dssdev)
+static int dsi_set_lane_config(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
static const u8 offsets[] = { 0, 4, 8, 12, 16 };
static const enum dsi_lane_function functions[] = {
@@ -2151,10 +2150,9 @@ static void dsi_cio_timings(struct platform_device *dsidev)
}
/* lane masks have lane 0 at lsb. mask_p for positive lines, n for negative */
-static void dsi_cio_enable_lane_override(struct omap_dss_device *dssdev,
+static void dsi_cio_enable_lane_override(struct platform_device *dsidev,
unsigned mask_p, unsigned mask_n)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
int i;
u32 l;
@@ -2201,9 +2199,8 @@ static void dsi_cio_disable_lane_override(struct platform_device *dsidev)
REG_FLD_MOD(dsidev, DSI_DSIPHY_CFG10, 0, 22, 17);
}
-static int dsi_cio_wait_tx_clk_esc_reset(struct omap_dss_device *dssdev)
+static int dsi_cio_wait_tx_clk_esc_reset(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
int t, i;
bool in_use[DSI_MAX_NR_LANES];
@@ -2251,9 +2248,8 @@ static int dsi_cio_wait_tx_clk_esc_reset(struct omap_dss_device *dssdev)
}
/* return bitmask of enabled lanes, lane0 being the lsb */
-static unsigned dsi_get_lane_mask(struct omap_dss_device *dssdev)
+static unsigned dsi_get_lane_mask(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
unsigned mask = 0;
int i;
@@ -2266,16 +2262,15 @@ static unsigned dsi_get_lane_mask(struct omap_dss_device *dssdev)
return mask;
}
-static int dsi_cio_init(struct omap_dss_device *dssdev)
+static int dsi_cio_init(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
int r;
u32 l;
DSSDBGF();
- r = dss_dsi_enable_pads(dsi->module_id, dsi_get_lane_mask(dssdev));
+ r = dss_dsi_enable_pads(dsi->module_id, dsi_get_lane_mask(dsidev));
if (r)
return r;
@@ -2292,7 +2287,7 @@ static int dsi_cio_init(struct omap_dss_device *dssdev)
goto err_scp_clk_dom;
}
- r = dsi_set_lane_config(dssdev);
+ r = dsi_set_lane_config(dsidev);
if (r)
goto err_scp_clk_dom;
@@ -2327,7 +2322,7 @@ static int dsi_cio_init(struct omap_dss_device *dssdev)
mask_p |= 1 << i;
}
- dsi_cio_enable_lane_override(dssdev, mask_p, 0);
+ dsi_cio_enable_lane_override(dsidev, mask_p, 0);
}
r = dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_ON);
@@ -2344,7 +2339,7 @@ static int dsi_cio_init(struct omap_dss_device *dssdev)
dsi_if_enable(dsidev, false);
REG_FLD_MOD(dsidev, DSI_CLK_CTRL, 1, 20, 20); /* LP_CLK_ENABLE */
- r = dsi_cio_wait_tx_clk_esc_reset(dssdev);
+ r = dsi_cio_wait_tx_clk_esc_reset(dsidev);
if (r)
goto err_tx_clk_esc_rst;
@@ -2385,13 +2380,12 @@ err_cio_pwr:
dsi_cio_disable_lane_override(dsidev);
err_scp_clk_dom:
dsi_disable_scp_clk(dsidev);
- dss_dsi_disable_pads(dsi->module_id, dsi_get_lane_mask(dssdev));
+ dss_dsi_disable_pads(dsi->module_id, dsi_get_lane_mask(dsidev));
return r;
}
-static void dsi_cio_uninit(struct omap_dss_device *dssdev)
+static void dsi_cio_uninit(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
/* DDR_CLK_ALWAYS_ON */
@@ -2399,7 +2393,7 @@ static void dsi_cio_uninit(struct omap_dss_device *dssdev)
dsi_cio_power(dsidev, DSI_COMPLEXIO_POWER_OFF);
dsi_disable_scp_clk(dsidev);
- dss_dsi_disable_pads(dsi->module_id, dsi_get_lane_mask(dssdev));
+ dss_dsi_disable_pads(dsi->module_id, dsi_get_lane_mask(dsidev));
}
static void dsi_config_tx_fifo(struct platform_device *dsidev,
@@ -2992,10 +2986,9 @@ int dsi_vc_send_null(struct omap_dss_device *dssdev, int channel)
}
EXPORT_SYMBOL(dsi_vc_send_null);
-static int dsi_vc_write_nosync_common(struct omap_dss_device *dssdev,
+static int dsi_vc_write_nosync_common(struct platform_device *dsidev,
int channel, u8 *data, int len, enum dss_dsi_content_type type)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
int r;
if (len = 0) {
@@ -3026,7 +3019,9 @@ static int dsi_vc_write_nosync_common(struct omap_dss_device *dssdev,
int dsi_vc_dcs_write_nosync(struct omap_dss_device *dssdev, int channel,
u8 *data, int len)
{
- return dsi_vc_write_nosync_common(dssdev, channel, data, len,
+ struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
+
+ return dsi_vc_write_nosync_common(dsidev, channel, data, len,
DSS_DSI_CONTENT_DCS);
}
EXPORT_SYMBOL(dsi_vc_dcs_write_nosync);
@@ -3034,7 +3029,9 @@ EXPORT_SYMBOL(dsi_vc_dcs_write_nosync);
int dsi_vc_generic_write_nosync(struct omap_dss_device *dssdev, int channel,
u8 *data, int len)
{
- return dsi_vc_write_nosync_common(dssdev, channel, data, len,
+ struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
+
+ return dsi_vc_write_nosync_common(dsidev, channel, data, len,
DSS_DSI_CONTENT_GENERIC);
}
EXPORT_SYMBOL(dsi_vc_generic_write_nosync);
@@ -3045,7 +3042,7 @@ static int dsi_vc_write_common(struct omap_dss_device *dssdev, int channel,
struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
int r;
- r = dsi_vc_write_nosync_common(dssdev, channel, data, len, type);
+ r = dsi_vc_write_nosync_common(dsidev, channel, data, len, type);
if (r)
goto err;
@@ -3123,10 +3120,9 @@ int dsi_vc_generic_write_2(struct omap_dss_device *dssdev, int channel,
}
EXPORT_SYMBOL(dsi_vc_generic_write_2);
-static int dsi_vc_dcs_send_read_request(struct omap_dss_device *dssdev,
+static int dsi_vc_dcs_send_read_request(struct platform_device *dsidev,
int channel, u8 dcs_cmd)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
int r;
@@ -3144,10 +3140,9 @@ static int dsi_vc_dcs_send_read_request(struct omap_dss_device *dssdev,
return 0;
}
-static int dsi_vc_generic_send_read_request(struct omap_dss_device *dssdev,
+static int dsi_vc_generic_send_read_request(struct platform_device *dsidev,
int channel, u8 *reqdata, int reqlen)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
u16 data;
u8 data_type;
@@ -3296,7 +3291,7 @@ int dsi_vc_dcs_read(struct omap_dss_device *dssdev, int channel, u8 dcs_cmd,
struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
int r;
- r = dsi_vc_dcs_send_read_request(dssdev, channel, dcs_cmd);
+ r = dsi_vc_dcs_send_read_request(dsidev, channel, dcs_cmd);
if (r)
goto err;
@@ -3327,7 +3322,7 @@ static int dsi_vc_generic_read(struct omap_dss_device *dssdev, int channel,
struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
int r;
- r = dsi_vc_generic_send_read_request(dssdev, channel, reqdata, reqlen);
+ r = dsi_vc_generic_send_read_request(dsidev, channel, reqdata, reqlen);
if (r)
return r;
@@ -3609,14 +3604,12 @@ static void dsi_set_hs_tx_timeout(struct platform_device *dsidev,
(total_ticks * 1000) / (fck / 1000 / 1000));
}
-static void dsi_config_vp_num_line_buffers(struct omap_dss_device *dssdev)
+static void dsi_config_vp_num_line_buffers(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
int num_line_buffers;
if (dsi->mode = OMAP_DSS_DSI_VIDEO_MODE) {
- struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
int bpp = dsi_get_pixel_size(dsi->pix_fmt);
unsigned line_buf_size = dsi_get_line_buf_size(dsidev);
struct omap_video_timings *timings = &dsi->timings;
@@ -3637,9 +3630,8 @@ static void dsi_config_vp_num_line_buffers(struct omap_dss_device *dssdev)
REG_FLD_MOD(dsidev, DSI_CTRL, num_line_buffers, 13, 12);
}
-static void dsi_config_vp_sync_events(struct omap_dss_device *dssdev)
+static void dsi_config_vp_sync_events(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
bool vsync_end = dsi->vm_timings.vp_vsync_end;
bool hsync_end = dsi->vm_timings.vp_hsync_end;
@@ -3656,9 +3648,8 @@ static void dsi_config_vp_sync_events(struct omap_dss_device *dssdev)
dsi_write_reg(dsidev, DSI_CTRL, r);
}
-static void dsi_config_blanking_modes(struct omap_dss_device *dssdev)
+static void dsi_config_blanking_modes(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
int blanking_mode = dsi->vm_timings.blanking_mode;
int hfp_blanking_mode = dsi->vm_timings.hfp_blanking_mode;
@@ -3913,11 +3904,11 @@ static int dsi_proto_config(struct omap_dss_device *dssdev)
dsi_write_reg(dsidev, DSI_CTRL, r);
- dsi_config_vp_num_line_buffers(dssdev);
+ dsi_config_vp_num_line_buffers(dsidev);
if (dsi->mode = OMAP_DSS_DSI_VIDEO_MODE) {
- dsi_config_vp_sync_events(dssdev);
- dsi_config_blanking_modes(dssdev);
+ dsi_config_vp_sync_events(dsidev);
+ dsi_config_blanking_modes(dsidev);
dsi_config_cmd_mode_interleaving(dssdev);
}
@@ -3929,9 +3920,8 @@ static int dsi_proto_config(struct omap_dss_device *dssdev)
return 0;
}
-static void dsi_proto_timings(struct omap_dss_device *dssdev)
+static void dsi_proto_timings(struct platform_device *dsidev)
{
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
unsigned tlpx, tclk_zero, tclk_prepare, tclk_trail;
unsigned tclk_pre, tclk_post;
@@ -4312,8 +4302,7 @@ static void dsi_framedone_timeout_work_callback(struct work_struct *work)
static void dsi_framedone_irq_callback(void *data, u32 mask)
{
- struct omap_dss_device *dssdev = (struct omap_dss_device *) data;
- struct platform_device *dsidev = dsi_get_dsidev_from_dssdev(dssdev);
+ struct platform_device *dsidev = (struct platform_device *) data;
struct dsi_data *dsi = dsi_get_dsidrv_data(dsidev);
/* Note: We get FRAMEDONE when DISPC has finished sending pixels and
@@ -4397,7 +4386,7 @@ static int dsi_display_init_dispc(struct omap_dss_device *dssdev)
irq = dispc_mgr_get_framedone_irq(dssdev->manager->id);
r = omap_dispc_register_isr(dsi_framedone_irq_callback,
- (void *) dssdev, irq);
+ (void *) dsidev, irq);
if (r) {
DSSERR("can't get FRAMEDONE irq\n");
goto err;
@@ -4438,7 +4427,7 @@ static int dsi_display_init_dispc(struct omap_dss_device *dssdev)
err1:
if (dsi->mode = OMAP_DSS_DSI_CMD_MODE)
omap_dispc_unregister_isr(dsi_framedone_irq_callback,
- (void *) dssdev, irq);
+ (void *) dsidev, irq);
err:
return r;
}
@@ -4454,7 +4443,7 @@ static void dsi_display_uninit_dispc(struct omap_dss_device *dssdev)
irq = dispc_mgr_get_framedone_irq(dssdev->manager->id);
omap_dispc_unregister_isr(dsi_framedone_irq_callback,
- (void *) dssdev, irq);
+ (void *) dsidev, irq);
}
}
@@ -4504,13 +4493,13 @@ static int dsi_display_init_dsi(struct omap_dss_device *dssdev)
DSSDBG("PLL OK\n");
- r = dsi_cio_init(dssdev);
+ r = dsi_cio_init(dsidev);
if (r)
goto err2;
_dsi_print_reset_status(dsidev);
- dsi_proto_timings(dssdev);
+ dsi_proto_timings(dsidev);
dsi_set_lp_clk_divisor(dssdev);
if (1)
@@ -4530,7 +4519,7 @@ static int dsi_display_init_dsi(struct omap_dss_device *dssdev)
return 0;
err3:
- dsi_cio_uninit(dssdev);
+ dsi_cio_uninit(dsidev);
err2:
dss_select_dispc_clk_source(OMAP_DSS_CLK_SRC_FCK);
dss_select_dsi_clk_source(dsi->module_id, OMAP_DSS_CLK_SRC_FCK);
@@ -4561,7 +4550,7 @@ static void dsi_display_uninit_dsi(struct omap_dss_device *dssdev,
dss_select_dispc_clk_source(OMAP_DSS_CLK_SRC_FCK);
dss_select_dsi_clk_source(dsi->module_id, OMAP_DSS_CLK_SRC_FCK);
dss_select_lcd_clk_source(dssdev->manager->id, OMAP_DSS_CLK_SRC_FCK);
- dsi_cio_uninit(dssdev);
+ dsi_cio_uninit(dsidev);
dsi_pll_uninit(dsidev, disconnect_lanes);
}
--
1.7.9.5
^ permalink raw reply related
* [PATCH 2/3] OMAPDSS: APPLY: Remove omap_dss_device references in wait_for_go functions
From: Archit Taneja @ 2012-08-17 10:51 UTC (permalink / raw)
To: tomi.valkeinen; +Cc: linux-omap, linux-fbdev, Archit Taneja
In-Reply-To: <1345200551-28712-1-git-send-email-archit@ti.com>
The functions dss_mgr_wait_for_go() and dss_mgr_wait_for_go_ovl() check if there
is an enabled display connected to the manager before trying to see the state of
the GO bit.
The checks related to the display can be replaced by checking the state of the
manager, i.e, whether the manager is enabled or not. This makes more sense than
checking with the connected display as the GO bit behaviour is more connected
with the manager state rather than the display state. A GO bit can only be set
if the manager is enabled. If a manager isn't enabled, we can safely assume that
the GO bit is not set.
Signed-off-by: Archit Taneja <archit@ti.com>
---
drivers/video/omap2/dss/apply.c | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/drivers/video/omap2/dss/apply.c b/drivers/video/omap2/dss/apply.c
index 52a5940..74f1a58 100644
--- a/drivers/video/omap2/dss/apply.c
+++ b/drivers/video/omap2/dss/apply.c
@@ -424,17 +424,23 @@ static void wait_pending_extra_info_updates(void)
int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr)
{
unsigned long timeout = msecs_to_jiffies(500);
- struct mgr_priv_data *mp;
+ struct mgr_priv_data *mp = get_mgr_priv(mgr);
u32 irq;
+ unsigned long flags;
int r;
int i;
- struct omap_dss_device *dssdev = mgr->device;
- if (!dssdev || dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
+ if (mgr_manual_update(mgr))
return 0;
- if (mgr_manual_update(mgr))
+ spin_lock_irqsave(&data_lock, flags);
+
+ if (!mp->enabled) {
+ spin_unlock_irqrestore(&data_lock, flags);
return 0;
+ }
+
+ spin_unlock_irqrestore(&data_lock, flags);
r = dispc_runtime_get();
if (r)
@@ -442,10 +448,8 @@ int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr)
irq = dispc_mgr_get_vsync_irq(mgr->id);
- mp = get_mgr_priv(mgr);
i = 0;
while (1) {
- unsigned long flags;
bool shadow_dirty, dirty;
spin_lock_irqsave(&data_lock, flags);
@@ -489,21 +493,28 @@ int dss_mgr_wait_for_go_ovl(struct omap_overlay *ovl)
{
unsigned long timeout = msecs_to_jiffies(500);
struct ovl_priv_data *op;
- struct omap_dss_device *dssdev;
+ struct mgr_priv_data *mp;
u32 irq;
+ unsigned long flags;
int r;
int i;
if (!ovl->manager)
return 0;
- dssdev = ovl->manager->device;
+ mp = get_mgr_priv(ovl->manager);
- if (!dssdev || dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
+ if (ovl_manual_update(ovl))
return 0;
- if (ovl_manual_update(ovl))
+ spin_lock_irqsave(&data_lock, flags);
+
+ if (!mp->enabled) {
+ spin_unlock_irqrestore(&data_lock, flags);
return 0;
+ }
+
+ spin_unlock_irqrestore(&data_lock, flags);
r = dispc_runtime_get();
if (r)
@@ -514,7 +525,6 @@ int dss_mgr_wait_for_go_ovl(struct omap_overlay *ovl)
op = get_ovl_priv(ovl);
i = 0;
while (1) {
- unsigned long flags;
bool shadow_dirty, dirty;
spin_lock_irqsave(&data_lock, flags);
--
1.7.9.5
^ permalink raw reply related
* [PATCH 3/3] OMAPDSS: Remove unnecessary acb/acbi pin fields from omap_dss_device
From: Archit Taneja @ 2012-08-17 10:51 UTC (permalink / raw)
To: tomi.valkeinen; +Cc: linux-omap, linux-fbdev, Archit Taneja
In-Reply-To: <1345200551-28712-1-git-send-email-archit@ti.com>
Passive matrix support was removed recently. The acb and acbi pin declarations
in omap_dss_device struct weren't removed by accident. Remove these fields
from omap_dss_device.
Signed-off-by: Archit Taneja <archit@ti.com>
---
include/video/omapdss.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/include/video/omapdss.h b/include/video/omapdss.h
index b868123..bc686f4 100644
--- a/include/video/omapdss.h
+++ b/include/video/omapdss.h
@@ -558,10 +558,6 @@ struct omap_dss_device {
struct {
struct omap_video_timings timings;
- int acbi; /* ac-bias pin transitions per interrupt */
- /* Unit: line clocks */
- int acb; /* ac-bias pin frequency */
-
enum omap_dss_dsi_pixel_format dsi_pix_fmt;
enum omap_dss_dsi_mode dsi_mode;
struct omap_dss_dsi_videomode_timings dsi_vm_timings;
--
1.7.9.5
^ permalink raw reply related
* Re: [RFC 3/5] video: panel: Add MIPI DBI bus support
From: Tomi Valkeinen @ 2012-08-17 10:51 UTC (permalink / raw)
To: Laurent Pinchart
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <1682445.1yJVVY1ksn@avalon>
[-- Attachment #1: Type: text/plain, Size: 7009 bytes --]
On Fri, 2012-08-17 at 12:02 +0200, Laurent Pinchart wrote:
> Hi Tomi,
>
> Thank you for the review.
>
> On Friday 17 August 2012 12:03:02 Tomi Valkeinen wrote:
> > On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
> > > +/*
> > > -------------------------------------------------------------------------
> > > ---- + * Bus operations
> > > + */
> > > +
> > > +void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned long
> > > cmd) +{
> > > + dev->bus->ops->write_command(dev->bus, cmd);
> > > +}
> > > +EXPORT_SYMBOL_GPL(panel_dbi_write_command);
> > > +
> > > +void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long
> > > data) +{
> > > + dev->bus->ops->write_data(dev->bus, data);
> > > +}
> > > +EXPORT_SYMBOL_GPL(panel_dbi_write_data);
> > > +
> > > +unsigned long panel_dbi_read_data(struct panel_dbi_device *dev)
> > > +{
> > > + return dev->bus->ops->read_data(dev->bus);
> > > +}
> > > +EXPORT_SYMBOL_GPL(panel_dbi_read_data);
> >
> > I'm not that familiar with how to implement bus drivers, can you
> > describe in pseudo code how the SoC's DBI driver would register these?
>
> Sure.
>
> The DBI bus driver first needs to create a panel_dbi_bus_ops instance:
>
> static const struct panel_dbi_bus_ops sh_mobile_lcdc_dbi_bus_ops = {
> .write_command = lcdc_dbi_write_command,
> .write_data = lcdc_dbi_write_data,
> .read_data = lcdc_dbi_read_data,
> };
Thanks for the example, I think it cleared up things a bit.
As I mentioned earlier, I really think "panel" is not right here. While
the whole framework may be called panel framework, the bus drivers are
not panels, and we should support external chips also, which are not
panels either.
> > I think write/read data functions are a bit limited. Shouldn't they be
> > something like write_data(const u8 *buf, int size) and read_data(u8
> > *buf, int len)?
>
> Good point. My hardware doesn't support multi-byte read/write operations
> directly so I haven't thought about adding those.
OMAP HW doesn't support it either. Well, not quite true, as OMAP's
system DMA could be used to write a buffer to the DBI output. But that's
really the same as doing the write with a a loop with CPU.
But first, the data type should be byte, not unsigned long. How would
you write 8 bits or 16 bits with your API? And second, if the function
takes just u8, you'd need lots of calls to do simple writes.
> Can your hardware group command + data writes in a single operation ? If so we
> should expose that at the API level as well.
No it can't. But with DCS that is a common operation, so we could have
some helpers to send command + data with one call. Then again, I'd hope
to have DCS somehow as a separate library, which would then use
DBI/DSI/whatnot to actually send the data.
I'm not quite sure how easy that is because of the differences between
the busses.
> Is DBI limited to 8-bit data transfers for commands ? Pixels can be
> transferred 16-bit at a time, commands might as well. While DCS only specifies
> 8-bit command/data, DBI panels that are not DCS compliant can use 16-bit
> command/data (the R61505 panel, albeit a SYS-80 panel, does so).
I have to say I don't remember much about DBI =). Looking at OMAP's
driver, which was made for omap2 and hasn't been much updated since, I
see that there are 4 modes, 8/9/12/16 bits. I think that defines how
many of the parallel data lines are used.
However, I don't think that matters for the panel driver when it wants
to send data. The panel driver should just call dbi_write(buf, buf_len),
and the dbi driver would send the data in the buffer according to the
bus width.
Also note that some chips need to change the bus width on the fly. The
chip used on N800 wants configuration to be done with 8-bits, and pixel
transfers with 16-bits. Who knows why...
So I think this, and generally most of the configuration, should be
somewhat dynamic, so that the panel driver can change them when it
needs.
> > Something that's totally missing is configuring the DBI bus. There are a
> > bunch of timing related values that need to be configured. See
> > include/video/omapdss.h struct rfbi_timings. While the struct is OMAP
> > specific, if I recall right most of the values match to the MIPI DBI
> > spec.
>
> I've left that out currently, and thought about passing that information as
> platform data to the DBI bus driver. That's the easiest solution, but I agree
> that it's a hack. Panel should expose their timing requirements to the DBI
> host. API wise that wouldn't be difficult (we only need to add timing
> information to the panel platform data and add a function to the DBI API to
> retrieve it), one of challenges might be to express it in a way that's both
> universal enough and easy to use for DBI bus drivers.
As I pointed above, I think the panel driver shouldn't expose it, but
the panel driver should somehow set it. Or at least allowed to change it
in some manner. This is actually again, the same problem as with enable
and transfer: who controls what's going on.
How I think it should work is something like:
mipi_dbi_set_timings(dbi_dev, mytimings);
mipi_dbi_set_bus_width(dbi_dev, 8);
mipi_dbi_write(dbi_dev, ...);
mipi_dbi_set_bus_width(dbi_dev, 16);
start_frame_transfer(dbi_dev, ...);
> > And this makes me wonder, you use DBI bus for SYS-80 panel. The busses
> > may look similar in operation, but are they really so similar when you
> > take into account the timings (and perhaps something else, it's been
> > years since I read the MIPI DBI spec)?
>
> I'll have to check all the details. SYS-80 is similar to DBI-B, but supports a
> wider bus width of 18 bits. I think the interfaces are similar enough to use a
> single bus implementation, possibly with quirks and/or options (see SCCB
> support in I2C for instance, with flags to ignore acks, force a stop bit
> generation, ...). We would duplicate lots of code if we had two different
> implementations, and would prevent a DBI panel to be attached to a SYS-80 host
> and vice-versa (the format is known to work).
Ah ok, if a DBI panel can be connected to SYS-80 output and vice versa,
then I agree they are similar enough.
> We might just need to provide fake timings. Video mode timings are at the core
> of display support in all drivers so we can't just get rid of them. The h/v
> front/back porch and sync won't be used by display drivers for DBI/DSI panels
> anyway.
Right. But we should probably think if we can, at the panel level,
easily separate conventional panels and smart panels. Then this
framework wouldn't need to fake the timings, and it'd be up to the
higher level to decide if and how to fake them. Then again, this is no
biggie. Just thought that at the lowest level it'd be nice to be
"correct" and leave faking to upper layers =).
Tomi
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [RFC 0/5] Generic panel framework
From: Laurent Pinchart @ 2012-08-17 11:10 UTC (permalink / raw)
To: Tomi Valkeinen
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <1345192694.3158.49.camel@deskari>
Hi Tomi,
Thanks a lot for the review.
On Friday 17 August 2012 11:38:14 Tomi Valkeinen wrote:
> On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
> > I will appreciate all reviews, comments, criticisms, ideas, remarks, ...
> > If
>
> Oookay, where to start... ;)
>
> A few cosmetic/general comments first.
>
> I find the file naming a bit strange. You have panel.c, which is the
> core framework, panel-dbi.c, which is the DBI bus, panel-r61517.c, which
> is driver for r61517 panel...
>
> Perhaps something in this direction (in order): panel-core.c,
> mipi-dbi-bus.c, panel-r61517.c?
That looks good to me. I'll then rename panel_dbi_* to mipi_dbi_*.
> And we probably end up with quite a lot of panel drivers, perhaps we should
> already divide these into separate directories, and then we wouldn't need to
> prefix each panel with "panel-" at all.
What kind of directory structure do you have in mind ? Panels are already
isolated in drivers/video/panel/ so we could already ditch the panel- prefix
in drivers.
Would you also create include/video/panel/ ?
> ---
>
> Should we aim for DT only solution from the start? DT is the direction we
> are going, and I feel the older platform data stuff would be deprecated
> soon.
Don't forget about non-ARM architectures :-/ We need panel drivers for SH as
well, which doesn't use DT. I don't think that would be a big issue, a DT-
compliant solution should be easy to use through board code and platform data
as well.
> ---
>
> Something missing from the intro is how this whole thing should be used.
> It doesn't help if we know how to turn on the panel, we also need to
> display something on it =). So I think some kind of diagram/example of
> how, say, drm would use this thing, and also how the SoC specific DBI
> bus driver would be done, would clarify things.
Of course. If I had all that information already I would have shared it :-)
This is really a first RFC, my goal is to make sure that I'm going in the
right direction.
> ---
>
> We have discussed face to face about the different hardware setups and
> scenarios that we should support, but I'll list some of them here for
> others:
>
> 1) We need to support chains of external display chips and panels. A
> simple example is a chip that takes DSI in, and outputs DPI. In that
> case we'd have a chain of SoC -> DSI2DPI -> DPI panel.
>
> In final products I think two external devices is the maximum (at least
> I've never seen three devices in a row), but in theory and in
> development environments the chain can be arbitrarily long. Also the
> connections are not necessarily 1-to-1, but a device can take one input
> while it has two outputs, or a device can take two inputs.
>
> Now, I think two external devices is a must requirement. I'm not sure if
> supporting more is an important requirement. However, if we support two
> devices, it could be that it's trivial to change the framework to
> support n devices.
>
> 2) Panels and display chips are all but standard. They very often have
> their own sequences how to do things, have bugs, or implement some
> feature in slightly different way than some other panel. This is why the
> panel driver should be able to control or define the way things happen.
>
> As an example, Sharp LQ043T1DG01 panel
> (www.sharpsme.com/download/LQ043T1DG01-SP-072106pdf). It is enabled with
> the following sequence:
>
> - Enable VCC and AVDD regulators
> - Wait min 50ms
> - Enable full video stream (pck, syncs, pixels) from SoC
> - Wait min 0.5ms
> - Set DISP GPIO, which turns on the display panel
>
> Here we could split the enabling of panel to two parts, prepare (in this
> case starts regulators and waits 50ms) and finish (wait 0.5ms and set
> DISP GPIO), and the upper layer would start the video stream in between.
>
> I realize this could be done with the PANEL_ENABLE_* levels in your RFC,
> but I don't think the concepts quite match:
>
> - PANEL_ENABLE_BLANK level is needed for "smart panels", as we need to
> configure them and send the initial frame at that operating level. With
> dummy panels there's really no such level, there's just one enable
> sequence that is always done right away.
>
> - I find waiting at the beginning of a function very ugly (what are we
> waiting for?) and we'd need that when changing the panel to
> PANEL_ENABLE_ON level.
>
> - It's still limited if the panel is a stranger one (see following
> example).
>
> Consider the following theoretical panel enable example, taken to absurd
> level just to show the general problem:
>
> - Enable regulators
> - Enable video stream
> - Wait 50ms
> - Disable video stream
> - Set enable GPIO
> - Enable video stream
>
> This one would be rather impossible with the upper layer handling the
> enabling of the video stream. Thus I see that the panel driver needs to
> control the sequences, and the Sharp panel driver's enable would look
> something like:
>
> regulator_enable(...);
> sleep();
> dpi_enable_video();
> sleep();
> gpip_set(..);
I have to admit I have no better solution to propose at the moment, even if I
don't really like making the panel control the video stream. When several
devices will be present in the chain all of them might have similar annoying
requirements, and my feeling is that the resulting code will be quite messy.
At the end of the day the only way to really find out is to write an
implementation.
> Note that even with this model we still need the PANEL_ENABLE levels you
> have.
>
> ---
>
> I'm not sure I understand the panel unload problem you mentioned. Nobody
> should have direct references to the panel functions, so there shouldn't
> be any automatic references that would prevent module unloading. So when
> the user does rmmod panel-mypanel, the panel driver's remove will be
> called. It'll unregister itself from the panel framework, which causes
> notifications and the display driver will stop using the panel. After
> that nobody has pointers to the panel, and it can safely be unloaded.
>
> It could cause some locking issues, though. First the panel's remove
> could take a lock, but the remove sequence would cause the display
> driver to call disable on the panel, which could again try to take the
> same lock...
We have two possible ways of calling panel operations, either directly (panel-
>bus->ops->enable(...)) or indirectly (panel_enable(...)).
The former is what V4L2 currently does with subdevs, and requires display
drivers to hold a reference to the panel. The later can do without a direct
reference only if we use a global lock, which is something I would like to
avoid. A panel-wide lock wouldn't work, as the access function would need to
take the lock on a panel instance that can be removed at any time.
Note that this issue is not specific to panels, V4L2 will need a solution as
well when V4L2 subdevs will be instantiated from the DT.
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [RFC 0/5] Generic panel framework
From: Tomi Valkeinen @ 2012-08-17 11:42 UTC (permalink / raw)
To: Laurent Pinchart
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <15644929.x7ZB0fPYJx@avalon>
[-- Attachment #1: Type: text/plain, Size: 4644 bytes --]
On Fri, 2012-08-17 at 13:10 +0200, Laurent Pinchart wrote:
> What kind of directory structure do you have in mind ? Panels are already
> isolated in drivers/video/panel/ so we could already ditch the panel- prefix
> in drivers.
The same directory also contains files for the framework and buses. But
perhaps there's no need for additional directories if the amount of
non-panel files is small. And you can easily see from the name that they
are not panel drivers (e.g. mipi_dbi_bus.c).
> Would you also create include/video/panel/ ?
Perhaps that would be good. Well, having all the files prefixed with
panel- is not bad as such, but just feel extra.
> > ---
> >
> > Should we aim for DT only solution from the start? DT is the direction we
> > are going, and I feel the older platform data stuff would be deprecated
> > soon.
>
> Don't forget about non-ARM architectures :-/ We need panel drivers for SH as
> well, which doesn't use DT. I don't think that would be a big issue, a DT-
> compliant solution should be easy to use through board code and platform data
> as well.
I didn't forget about them as I didn't even think about them ;). I
somehow had the impression that other architectures would also use DT,
sooner or later. I could be mistaken, though.
And true, it's not a big issue to support both DT and non-DT versions,
but I've been porting omap stuff for DT and keeping the old platform
data stuff also there, and it just feels burdensome. For very simple
panels it's easy, but when you've passing lots of parameters the code
starts to get longer.
> > This one would be rather impossible with the upper layer handling the
> > enabling of the video stream. Thus I see that the panel driver needs to
> > control the sequences, and the Sharp panel driver's enable would look
> > something like:
> >
> > regulator_enable(...);
> > sleep();
> > dpi_enable_video();
> > sleep();
> > gpip_set(..);
>
> I have to admit I have no better solution to propose at the moment, even if I
> don't really like making the panel control the video stream. When several
> devices will be present in the chain all of them might have similar annoying
> requirements, and my feeling is that the resulting code will be quite messy.
> At the end of the day the only way to really find out is to write an
> implementation.
If we have a chain of devices, and each device uses the bus interface
from the previous device in the chain, there shouldn't be a problem. In
that model each device can handle the task however is best for it.
I think the problems come from the divided control we'll have. I mean,
if the panel driver would decide itself what to send to its output, and
it would provide the data (think of an i2c device), this would be very
simple. And it actually is for things like configuration data etc, but
not so for video stream.
> > It could cause some locking issues, though. First the panel's remove
> > could take a lock, but the remove sequence would cause the display
> > driver to call disable on the panel, which could again try to take the
> > same lock...
>
> We have two possible ways of calling panel operations, either directly (panel-
> >bus->ops->enable(...)) or indirectly (panel_enable(...)).
>
> The former is what V4L2 currently does with subdevs, and requires display
> drivers to hold a reference to the panel. The later can do without a direct
> reference only if we use a global lock, which is something I would like to
Wouldn't panel_enable() just do the same panel->bus->ops->enable()
anyway, and both require a panel reference? I don't see the difference.
> avoid. A panel-wide lock wouldn't work, as the access function would need to
> take the lock on a panel instance that can be removed at any time.
Can't this be handled with some kind of get/put refcounting? If there's
a ref, it can't be removed.
Generally about locks, if we define that panel ops may only be called
exclusively, does it simplify things? I think we can make such
requirements, as there should be only one display framework that handles
the panel. Then we don't need locking for things like enable/disable.
Of course we need to be careful about things where calls come from
"outside" the display framework. I guess one such thing is rmmod, but if
that causes a notification to the display framework, which again handles
locking, it shouldn't be a problem.
Another thing to be careful about is if the panel internally uses irqs,
workqueues, sysfs files or such. In that case it needs to handle
locking.
Tomi
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [RFC 3/5] video: panel: Add MIPI DBI bus support
From: Laurent Pinchart @ 2012-08-17 12:33 UTC (permalink / raw)
To: Tomi Valkeinen
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <1345200709.11073.27.camel@lappyti>
Hi Tomi,
On Friday 17 August 2012 13:51:49 Tomi Valkeinen wrote:
> On Fri, 2012-08-17 at 12:02 +0200, Laurent Pinchart wrote:
> > On Friday 17 August 2012 12:03:02 Tomi Valkeinen wrote:
> > > On Fri, 2012-08-17 at 02:49 +0200, Laurent Pinchart wrote:
> > > > +/*
> > > > ----------------------------------------------------------------------
> > > > ---
> > > > ---- + * Bus operations
> > > > + */
> > > > +
> > > > +void panel_dbi_write_command(struct panel_dbi_device *dev, unsigned
> > > > long
> > > > cmd) +{
> > > > + dev->bus->ops->write_command(dev->bus, cmd);
> > > > +}
> > > > +EXPORT_SYMBOL_GPL(panel_dbi_write_command);
> > > > +
> > > > +void panel_dbi_write_data(struct panel_dbi_device *dev, unsigned long
> > > > data) +{
> > > > + dev->bus->ops->write_data(dev->bus, data);
> > > > +}
> > > > +EXPORT_SYMBOL_GPL(panel_dbi_write_data);
> > > > +
> > > > +unsigned long panel_dbi_read_data(struct panel_dbi_device *dev)
> > > > +{
> > > > + return dev->bus->ops->read_data(dev->bus);
> > > > +}
> > > > +EXPORT_SYMBOL_GPL(panel_dbi_read_data);
> > >
> > > I'm not that familiar with how to implement bus drivers, can you
> > > describe in pseudo code how the SoC's DBI driver would register these?
> >
> > Sure.
> >
> > The DBI bus driver first needs to create a panel_dbi_bus_ops instance:
> >
> > static const struct panel_dbi_bus_ops sh_mobile_lcdc_dbi_bus_ops = {
> >
> > .write_command = lcdc_dbi_write_command,
> > .write_data = lcdc_dbi_write_data,
> > .read_data = lcdc_dbi_read_data,
> >
> > };
>
> Thanks for the example, I think it cleared up things a bit.
>
> As I mentioned earlier, I really think "panel" is not right here. While
> the whole framework may be called panel framework, the bus drivers are
> not panels, and we should support external chips also, which are not
> panels either.
I agree. I've renamed panel_dbi_* to mipi_dbi_*.
> > > I think write/read data functions are a bit limited. Shouldn't they be
> > > something like write_data(const u8 *buf, int size) and read_data(u8
> > > *buf, int len)?
> >
> > Good point. My hardware doesn't support multi-byte read/write operations
> > directly so I haven't thought about adding those.
>
> OMAP HW doesn't support it either. Well, not quite true, as OMAP's
> system DMA could be used to write a buffer to the DBI output. But that's
> really the same as doing the write with a a loop with CPU.
>
> But first, the data type should be byte, not unsigned long. How would
> you write 8 bits or 16 bits with your API?
u8 and u16 both fit in an unsigned long :-) Please see below.
> And second, if the function takes just u8, you'd need lots of calls to do
> simple writes.
I agree, an array write function is a good idea.
> > Can your hardware group command + data writes in a single operation ? If
> > so we should expose that at the API level as well.
>
> No it can't. But with DCS that is a common operation, so we could have
> some helpers to send command + data with one call.
Agreed.
> Then again, I'd hope to have DCS somehow as a separate library, which would
> then use DBI/DSI/whatnot to actually send the data.
>
> I'm not quite sure how easy that is because of the differences between
> the busses.
>
> > Is DBI limited to 8-bit data transfers for commands ? Pixels can be
> > transferred 16-bit at a time, commands might as well. While DCS only
> > specifies 8-bit command/data, DBI panels that are not DCS compliant can
> > use 16-bit command/data (the R61505 panel, albeit a SYS-80 panel, does
> > so).
>
> I have to say I don't remember much about DBI =). Looking at OMAP's
> driver, which was made for omap2 and hasn't been much updated since, I
> see that there are 4 modes, 8/9/12/16 bits. I think that defines how
> many of the parallel data lines are used.
SYS-80 also has an 18-bits mode, where bits 0 and 9 are always ignored when
transferring instructions and data other than pixels (for pixels the 18-bits
bus width can be used to transfer RGB666 in a single clock cycle).
See page 87 of
http://read.pudn.com/downloads91/sourcecode/others/348230/e61505_103a.pdf.
> However, I don't think that matters for the panel driver when it wants
> to send data. The panel driver should just call dbi_write(buf, buf_len),
> and the dbi driver would send the data in the buffer according to the
> bus width.
According to the DCS specification, commands and parameters are transferred
using 8-bit data. Non-DCS panels can however use wider commands and parameters
(all commands and parameters are 16-bits wide for the R61505 for instance).
We can add an API to switch the DBI bus width on the fly. For Renesas hardware
this would "just" require shifting bits around to output the 8-bit or 16-bit
commands on the right data lines (the R61505 uses D17-D9 in 8-bit mode, while
the DCS specification mentions D7-D0) based on how the panel is connected and
on which lines the panel expects data.
As commands can be expressed on either 8 or 16 bits I would use a 16 type for
them.
For parameters, we can either express everything as u8 * in the DBI bus
operations, or use a union similar to i2c_smbus_data
union i2c_smbus_data {
__u8 byte;
__u16 word;
__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
/* and one more for user-space compatibility */
};
Helper functions would be available to perform 8-bit, 16-bit or n*8 bits
transfers.
Would that work for your use cases ?
> Also note that some chips need to change the bus width on the fly. The
> chip used on N800 wants configuration to be done with 8-bits, and pixel
> transfers with 16-bits. Who knows why...
On which data lines is configuration performed ? D7-D0 ?
> So I think this, and generally most of the configuration, should be
> somewhat dynamic, so that the panel driver can change them when it
> needs.
>
> > > Something that's totally missing is configuring the DBI bus. There are a
> > > bunch of timing related values that need to be configured. See
> > > include/video/omapdss.h struct rfbi_timings. While the struct is OMAP
> > > specific, if I recall right most of the values match to the MIPI DBI
> > > spec.
> >
> > I've left that out currently, and thought about passing that information
> > as platform data to the DBI bus driver. That's the easiest solution, but I
> > agree that it's a hack. Panel should expose their timing requirements to
> > the DBI host. API wise that wouldn't be difficult (we only need to add
> > timing information to the panel platform data and add a function to the
> > DBI API to retrieve it), one of challenges might be to express it in a
> > way that's both universal enough and easy to use for DBI bus drivers.
>
> As I pointed above, I think the panel driver shouldn't expose it, but
> the panel driver should somehow set it. Or at least allowed to change it
> in some manner. This is actually again, the same problem as with enable
> and transfer: who controls what's going on.
>
> How I think it should work is something like:
>
> mipi_dbi_set_timings(dbi_dev, mytimings);
> mipi_dbi_set_bus_width(dbi_dev, 8);
> mipi_dbi_write(dbi_dev, ...);
> mipi_dbi_set_bus_width(dbi_dev, 16);
> start_frame_transfer(dbi_dev, ...);
I'll first implement bus width setting.
> > > And this makes me wonder, you use DBI bus for SYS-80 panel. The busses
> > > may look similar in operation, but are they really so similar when you
> > > take into account the timings (and perhaps something else, it's been
> > > years since I read the MIPI DBI spec)?
> >
> > I'll have to check all the details. SYS-80 is similar to DBI-B, but
> > supports a wider bus width of 18 bits. I think the interfaces are similar
> > enough to use a single bus implementation, possibly with quirks and/or
> > options (see SCCB support in I2C for instance, with flags to ignore acks,
> > force a stop bit generation, ...). We would duplicate lots of code if we
> > had two different implementations, and would prevent a DBI panel to be
> > attached to a SYS-80 host and vice-versa (the format is known to work).
>
> Ah ok, if a DBI panel can be connected to SYS-80 output and vice versa,
> then I agree they are similar enough.
Not all combination will work (a SYS panel that requires 16-bit transfers
won't work with a DBI host that only supports 8-bit), but some do.
> > We might just need to provide fake timings. Video mode timings are at the
> > core of display support in all drivers so we can't just get rid of them.
> > The h/v front/back porch and sync won't be used by display drivers for
> > DBI/DSI panels anyway.
>
> Right. But we should probably think if we can, at the panel level, easily
> separate conventional panels and smart panels. Then this framework wouldn't
> need to fake the timings, and it'd be up to the higher level to decide if
> and how to fake them. Then again, this is no biggie. Just thought that at
> the lowest level it'd be nice to be "correct" and leave faking to upper
> layers =).
But we would then have two different APIs at the lower level depending on the
panel type. I'm not sure that's a good thing.
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH 2/3] OMAPDSS: APPLY: Remove omap_dss_device references in wait_for_go functions
From: Tomi Valkeinen @ 2012-08-17 12:35 UTC (permalink / raw)
To: Archit Taneja; +Cc: linux-omap, linux-fbdev
In-Reply-To: <1345200551-28712-3-git-send-email-archit@ti.com>
[-- Attachment #1: Type: text/plain, Size: 3338 bytes --]
On Fri, 2012-08-17 at 16:19 +0530, Archit Taneja wrote:
> The functions dss_mgr_wait_for_go() and dss_mgr_wait_for_go_ovl() check if there
> is an enabled display connected to the manager before trying to see the state of
> the GO bit.
>
> The checks related to the display can be replaced by checking the state of the
> manager, i.e, whether the manager is enabled or not. This makes more sense than
> checking with the connected display as the GO bit behaviour is more connected
> with the manager state rather than the display state. A GO bit can only be set
> if the manager is enabled. If a manager isn't enabled, we can safely assume that
> the GO bit is not set.
>
> Signed-off-by: Archit Taneja <archit@ti.com>
> ---
> drivers/video/omap2/dss/apply.c | 32 +++++++++++++++++++++-----------
> 1 file changed, 21 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/video/omap2/dss/apply.c b/drivers/video/omap2/dss/apply.c
> index 52a5940..74f1a58 100644
> --- a/drivers/video/omap2/dss/apply.c
> +++ b/drivers/video/omap2/dss/apply.c
> @@ -424,17 +424,23 @@ static void wait_pending_extra_info_updates(void)
> int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr)
> {
> unsigned long timeout = msecs_to_jiffies(500);
> - struct mgr_priv_data *mp;
> + struct mgr_priv_data *mp = get_mgr_priv(mgr);
> u32 irq;
> + unsigned long flags;
> int r;
> int i;
> - struct omap_dss_device *dssdev = mgr->device;
>
> - if (!dssdev || dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
> + if (mgr_manual_update(mgr))
This needs to be inside the spinlock also.
> return 0;
>
> - if (mgr_manual_update(mgr))
> + spin_lock_irqsave(&data_lock, flags);
> +
> + if (!mp->enabled) {
> + spin_unlock_irqrestore(&data_lock, flags);
> return 0;
> + }
> +
> + spin_unlock_irqrestore(&data_lock, flags);
>
> r = dispc_runtime_get();
> if (r)
> @@ -442,10 +448,8 @@ int dss_mgr_wait_for_go(struct omap_overlay_manager *mgr)
>
> irq = dispc_mgr_get_vsync_irq(mgr->id);
>
> - mp = get_mgr_priv(mgr);
> i = 0;
> while (1) {
> - unsigned long flags;
> bool shadow_dirty, dirty;
>
> spin_lock_irqsave(&data_lock, flags);
> @@ -489,21 +493,28 @@ int dss_mgr_wait_for_go_ovl(struct omap_overlay *ovl)
> {
> unsigned long timeout = msecs_to_jiffies(500);
> struct ovl_priv_data *op;
> - struct omap_dss_device *dssdev;
> + struct mgr_priv_data *mp;
> u32 irq;
> + unsigned long flags;
> int r;
> int i;
>
> if (!ovl->manager)
> return 0;
And this should be inside spinlock (yes, you didn't change that, but now
that you're changing these... =)
> - dssdev = ovl->manager->device;
> + mp = get_mgr_priv(ovl->manager);
>
> - if (!dssdev || dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
> + if (ovl_manual_update(ovl))
Inside spinlock here too.
Actually, shouldn't the whole wait_for functions be locked with the
apply mutex? Otherwise the output can be disabled/changed while waiting.
On the other hand, that could be quite a long lock, and I don't see
anything in the code that could really break if the output is disabled
or similar. Perhaps it's fine to just hit the timeout in case something
has been changed. If we add a mutex, we risk breaking something that
currently works =).
Tomi
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [RFC 3/5] video: panel: Add MIPI DBI bus support
From: Tomi Valkeinen @ 2012-08-17 13:06 UTC (permalink / raw)
To: Laurent Pinchart
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <2019849.eCaIrHMssh@avalon>
[-- Attachment #1: Type: text/plain, Size: 6024 bytes --]
On Fri, 2012-08-17 at 14:33 +0200, Laurent Pinchart wrote:
> > But first, the data type should be byte, not unsigned long. How would
> > you write 8 bits or 16 bits with your API?
>
> u8 and u16 both fit in an unsigned long :-) Please see below.
Ah, I see, so the driver would just discard 24 bits or 16 bits from the
ulong. I somehow thought that if you have 8 bit bus, and you call the
write with ulong, 4 bytes will be written.
> > Then again, I'd hope to have DCS somehow as a separate library, which would
> > then use DBI/DSI/whatnot to actually send the data.
> >
> > I'm not quite sure how easy that is because of the differences between
> > the busses.
> >
> > > Is DBI limited to 8-bit data transfers for commands ? Pixels can be
> > > transferred 16-bit at a time, commands might as well. While DCS only
> > > specifies 8-bit command/data, DBI panels that are not DCS compliant can
> > > use 16-bit command/data (the R61505 panel, albeit a SYS-80 panel, does
> > > so).
> >
> > I have to say I don't remember much about DBI =). Looking at OMAP's
> > driver, which was made for omap2 and hasn't been much updated since, I
> > see that there are 4 modes, 8/9/12/16 bits. I think that defines how
> > many of the parallel data lines are used.
>
> SYS-80 also has an 18-bits mode, where bits 0 and 9 are always ignored when
> transferring instructions and data other than pixels (for pixels the 18-bits
> bus width can be used to transfer RGB666 in a single clock cycle).
>
> See page 87 of
> http://read.pudn.com/downloads91/sourcecode/others/348230/e61505_103a.pdf.
>
> > However, I don't think that matters for the panel driver when it wants
> > to send data. The panel driver should just call dbi_write(buf, buf_len),
> > and the dbi driver would send the data in the buffer according to the
> > bus width.
>
> According to the DCS specification, commands and parameters are transferred
> using 8-bit data. Non-DCS panels can however use wider commands and parameters
> (all commands and parameters are 16-bits wide for the R61505 for instance).
>
> We can add an API to switch the DBI bus width on the fly. For Renesas hardware
> this would "just" require shifting bits around to output the 8-bit or 16-bit
> commands on the right data lines (the R61505 uses D17-D9 in 8-bit mode, while
> the DCS specification mentions D7-D0) based on how the panel is connected and
> on which lines the panel expects data.
>
> As commands can be expressed on either 8 or 16 bits I would use a 16 type for
> them.
I won't put my head on the block, but I don't think DBI has any
restriction on the size of the command. A "command" just means a data
transfer while keeping the D/CX line low, and "data" when the line is
high. Similar transfers for n bytes can be done in both modes.
> For parameters, we can either express everything as u8 * in the DBI bus
> operations, or use a union similar to i2c_smbus_data
>
> union i2c_smbus_data {
> __u8 byte;
> __u16 word;
> __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
> /* and one more for user-space compatibility */
> };
There's no DBI_BLOCK_MAX, so at least identical union won't work. I
think it's simplest to have u8 * function as a base, and then a few
helpers to write the most common datatypes.
So we could have on the lowest level something like:
dbi_write_command(u8 *buf, size_t size);
dbi_write_data(u8 *buf, size_t size);
And possible helpers:
dbi_write_data(u8 *cmd_buf, size_t cmd_size, u8 *data_buf, size_t
data_size);
dbi_write_dcs(u8 cmd, u8 *data, size_t size);
And variations:
dbi_write_dcs_0(u8 cmd);
dbi_write_dcs_1(u8 cmd, u8 data);
etc. So a simple helper to send 16 bits would be:
dbi_write_data(u16 data)
{
// or are the bytes the other way around...
u8 buf[2] = { data & 0xff, (data >> 8) & 0xff };
return dbi_write_data(buf, 2);
}
> Helper functions would be available to perform 8-bit, 16-bit or n*8 bits
> transfers.
>
> Would that work for your use cases ?
>
> > Also note that some chips need to change the bus width on the fly. The
> > chip used on N800 wants configuration to be done with 8-bits, and pixel
> > transfers with 16-bits. Who knows why...
>
> On which data lines is configuration performed ? D7-D0 ?
I guess so, but does it matter? All the bus driver needs to know is how
to send 8/16/.. bit data. On OMAP we just write the data to a 32 bit
register, and the HW takes the lowest n bits. Do the bits represent the
data lines directly on Renesans?
The omap driver actually only implements 8 and 16 bit modes, not the 9
and 12 bit modes. I'm not sure what kind of shifting is needed for
those.
> > > We might just need to provide fake timings. Video mode timings are at the
> > > core of display support in all drivers so we can't just get rid of them.
> > > The h/v front/back porch and sync won't be used by display drivers for
> > > DBI/DSI panels anyway.
> >
> > Right. But we should probably think if we can, at the panel level, easily
> > separate conventional panels and smart panels. Then this framework wouldn't
> > need to fake the timings, and it'd be up to the higher level to decide if
> > and how to fake them. Then again, this is no biggie. Just thought that at
> > the lowest level it'd be nice to be "correct" and leave faking to upper
> > layers =).
>
> But we would then have two different APIs at the lower level depending on the
> panel type. I'm not sure that's a good thing.
Different API for what? Why anyway need panel type specific functions.
In the panel struct we could just have an union of the different types
of parameters for different types of panels.
But if this complicates things, it's not a biggie. Just something that
has been in my mind when dealing with smart panels and assigning dummy
video timings for them =).
Tomi
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [PATCH V4 3/6] OMAPDSS: DSS: Cleanup cpu_is_xxxx checks
From: Tomi Valkeinen @ 2012-08-17 13:54 UTC (permalink / raw)
To: Chandrabhanu Mahapatra; +Cc: linux-omap, linux-fbdev
In-Reply-To: <1345115913-6773-1-git-send-email-cmahapatra@ti.com>
[-- Attachment #1: Type: text/plain, Size: 5554 bytes --]
On Thu, 2012-08-16 at 16:48 +0530, Chandrabhanu Mahapatra wrote:
> All the cpu_is checks have been moved to dss_init_features function providing a
> much more generic and cleaner interface. The OMAP version and revision specific
> initializations in various functions are cleaned and the necessary data are
> moved to dss_features structure which is local to dss.c.
>
> Signed-off-by: Chandrabhanu Mahapatra <cmahapatra@ti.com>
> +static int __init dss_init_features(struct device *dev)
> +{
> + dss.feat = devm_kzalloc(dev, sizeof(*dss.feat), GFP_KERNEL);
> + if (!dss.feat) {
> + dev_err(dev, "Failed to allocate local DSS Features\n");
> + return -ENOMEM;
> + }
> +
> + if (cpu_is_omap24xx())
> + dss.feat = &omap24xx_dss_features;
> + else if (cpu_is_omap34xx())
> + dss.feat = &omap34xx_dss_features;
> + else if (cpu_is_omap3630())
> + dss.feat = &omap3630_dss_features;
> + else if (cpu_is_omap44xx())
> + dss.feat = &omap44xx_dss_features;
> + else
> + return -ENODEV;
> +
> + return 0;
> +}
This is not correct (and same problem in dispc). You allocate the feat
struct and assign the pointer to dss.feat, but then overwrite dss.feat
pointer with the pointer to omap24xx_dss_features (which is freed
later). You need to memcpy it.
I also get a crash on omap3 overo board when loading omapdss:
loading nfs/work/linux/drivers/video/omap2/dss/omapdss.ko debug=y def_disp=lcd43
[ 20.411224] Unable to handle kernel NULL pointer dereference at virtual address 00000008
[ 20.419921] pgd = ce8a8000
[ 20.422790] [00000008] *pgd=8e8c5831, *pte=00000000, *ppte=00000000
[ 20.429473] Internal error: Oops: 17 [#1] SMP ARM
[ 20.434448] Modules linked in: omapdss(+)
[ 20.438690] CPU: 0 Tainted: G W (3.5.0-rc2-00058-g1c1e55c #93)
[ 20.446350] PC is at omap_dsshw_probe+0xa4/0x290 [omapdss]
[ 20.452148] LR is at 0x2e39
[ 20.455108] pc : [<bf043288>] lr : [<00002e39>] psr: 80000013
[ 20.455108] sp : ce89ddd0 ip : c0b797e0 fp : 00006133
[ 20.467224] r10: 00000028 r9 : c0c5c07c r8 : bf02eadc
[ 20.472717] r7 : 00000000 r6 : c06e9644 r5 : cf0cf808 r4 : bf02f430
[ 20.479614] r3 : cf0cf808 r2 : 00000000 r1 : 00000000 r0 : 00000000
[ 20.486511] Flags: Nzcv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
[ 20.494049] Control: 10c5387d Table: 8e8a8019 DAC: 00000015
[ 20.500091] Process insmod (pid: 664, stack limit = 0xce89c2f8)
[ 20.506347] Stack: (0xce89ddd0 to 0xce89e000)
[ 20.510955] ddc0: cf0cf808 c0c96ed8 c0c96ee8 c02c22e4
[ 20.519592] dde0: c02c22cc c02c0f28 22222222 cf0cf808 bf02eadc cf0cf83c 00000000 00000001
[ 20.528198] de00: 00000028 c02c113c bf02eadc c02c10a8 00000000 c02bf6c8 cf0192a8 cf0cec10
[ 20.536834] de20: bf02eadc c072fab8 cf3e7440 c02c05dc bf0249e0 00000000 cf04ce40 bf02eadc
[ 20.545471] de40: c0748880 ce89c000 00000000 00000001 c0c5c07c 00000028 00006133 c02c1670
[ 20.554107] de60: 00000000 bf02eac8 c0748880 ce89c000 00000000 00000001 00000028 c02c26e0
[ 20.562744] de80: 00000003 00000000 c0748880 bf043124 00000000 c0748880 ce89c000 00000000
[ 20.571380] dea0: 00000001 c0008730 bf02f29c 00000001 00000001 bf0430cc c071bcd0 00000000
[ 20.580017] dec0: bf02f29c c006823c 00000000 ce827ec0 cf0001c0 00000000 bf02f29c 00000001
[ 20.588623] dee0: ce851480 00000001 c0c5c07c 00000028 00006133 c0099d20 bf02f2a8 00007fff
[ 20.597259] df00: c0098aa4 c012480c 00000000 c0098890 bf02f3f0 ce89c000 c06d32d8 d08fe09c
[ 20.605895] df20: d0a19624 000a7008 d08ce000 001f2ab7 d0a18bd4 d0a18948 d0aba984 00030ed0
[ 20.614532] df40: 0003b0e0 00000000 00000000 00000042 00000043 00000026 0000002a 00000014
[ 20.623138] df60: 00000000 bf022024 00000043 00000000 00000000 00000000 00000000 c0623b14
[ 20.631774] df80: 001f2ab7 001f2ab7 00000004 beb48e7c 00000080 c0013f28 ce89c000 00000000
[ 20.640411] dfa0: 00000000 c0013d60 001f2ab7 00000004 b6c49008 001f2ab7 000a7008 beb48e7c
[ 20.649047] dfc0: 001f2ab7 00000004 beb48e7c 00000080 000a47f8 00000000 b6f80000 00000000
[ 20.657684] dfe0: beb48bb8 beb48ba8 00019dfc b6f10020 60000010 b6c49008 00000000 00000000
[ 20.666442] [<bf043288>] (omap_dsshw_probe+0xa4/0x290 [omapdss]) from [<c02c22e4>] (platform_drv_
probe+0x18/0x1c)
[ 20.677276] [<c02c22e4>] (platform_drv_probe+0x18/0x1c) from [<c02c0f28>] (driver_probe_device+0x
9c/0x21c)
[ 20.687499] [<c02c0f28>] (driver_probe_device+0x9c/0x21c) from [<c02c113c>] (__driver_attach+0x94
/0x98)
[ 20.697418] [<c02c113c>] (__driver_attach+0x94/0x98) from [<c02bf6c8>] (bus_for_each_dev+0x50/0x7
c)
[ 20.706970] [<c02bf6c8>] (bus_for_each_dev+0x50/0x7c) from [<c02c05dc>] (bus_add_driver+0xa0/0x2a
8)
[ 20.716522] [<c02c05dc>] (bus_add_driver+0xa0/0x2a8) from [<c02c1670>] (driver_register+0x78/0x17
4)
[ 20.726074] [<c02c1670>] (driver_register+0x78/0x174) from [<c02c26e0>] (platform_driver_probe+0x
18/0x9c)
[ 20.736267] [<c02c26e0>] (platform_driver_probe+0x18/0x9c) from [<bf043124>] (omap_dss_init+0x58/
0x118 [omapdss])
[ 20.747192] [<bf043124>] (omap_dss_init+0x58/0x118 [omapdss]) from [<c0008730>] (do_one_initcall+
0x34/0x194)
[ 20.757568] [<c0008730>] (do_one_initcall+0x34/0x194) from [<c0099d20>] (sys_init_module+0xdc/0x1
cc4)
[ 20.767333] [<c0099d20>] (sys_init_module+0xdc/0x1cc4) from [<c0013d60>] (ret_fast_syscall+0x0/0x
3c)
[ 20.776947] Code: ea00000d e5941248 e3a00000 e584600c (e5911008)
[ 20.783599] ---[ end trace bcb6e89e4ea810ae ]---
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [RFC 3/5] video: panel: Add MIPI DBI bus support
From: Laurent Pinchart @ 2012-08-17 14:06 UTC (permalink / raw)
To: Tomi Valkeinen
Cc: linux-fbdev, dri-devel, linux-leds, linux-media, Bryan Wu,
Richard Purdie, Marcus Lorentzon, Sumit Semwal, Archit Taneja,
Sebastien Guiriec, Inki Dae, Kyungmin Park
In-Reply-To: <1345208790.3158.133.camel@deskari>
Hi Tomi,
On Friday 17 August 2012 16:06:30 Tomi Valkeinen wrote:
> On Fri, 2012-08-17 at 14:33 +0200, Laurent Pinchart wrote:
> > > But first, the data type should be byte, not unsigned long. How would
> > > you write 8 bits or 16 bits with your API?
> >
> > u8 and u16 both fit in an unsigned long :-) Please see below.
>
> Ah, I see, so the driver would just discard 24 bits or 16 bits from the
> ulong.
That's right.
> I somehow thought that if you have 8 bit bus, and you call the write with
> ulong, 4 bytes will be written.
>
> > > Then again, I'd hope to have DCS somehow as a separate library, which
> > > would then use DBI/DSI/whatnot to actually send the data.
> > >
> > > I'm not quite sure how easy that is because of the differences between
> > > the busses.
> > >
> > > > Is DBI limited to 8-bit data transfers for commands ? Pixels can be
> > > > transferred 16-bit at a time, commands might as well. While DCS only
> > > > specifies 8-bit command/data, DBI panels that are not DCS compliant
> > > > can use 16-bit command/data (the R61505 panel, albeit a SYS-80 panel,
> > > > does so).
> > >
> > > I have to say I don't remember much about DBI =). Looking at OMAP's
> > > driver, which was made for omap2 and hasn't been much updated since, I
> > > see that there are 4 modes, 8/9/12/16 bits. I think that defines how
> > > many of the parallel data lines are used.
> >
> > SYS-80 also has an 18-bits mode, where bits 0 and 9 are always ignored
> > when transferring instructions and data other than pixels (for pixels the
> > 18-bits bus width can be used to transfer RGB666 in a single clock cycle).
> >
> > See page 87 of
> > http://read.pudn.com/downloads91/sourcecode/others/348230/e61505_103a.pdf.
> >
> > > However, I don't think that matters for the panel driver when it wants
> > > to send data. The panel driver should just call dbi_write(buf, buf_len),
> > > and the dbi driver would send the data in the buffer according to the
> > > bus width.
> >
> > According to the DCS specification, commands and parameters are
> > transferred using 8-bit data. Non-DCS panels can however use wider
> > commands and parameters (all commands and parameters are 16-bits wide for
> > the R61505 for instance).
> >
> > We can add an API to switch the DBI bus width on the fly. For Renesas
> > hardware this would "just" require shifting bits around to output the
> > 8-bit or 16-bit commands on the right data lines (the R61505 uses D17-D9
> > in 8-bit mode, while the DCS specification mentions D7-D0) based on how
> > the panel is connected and on which lines the panel expects data.
> >
> > As commands can be expressed on either 8 or 16 bits I would use a 16 type
> > for them.
>
> I won't put my head on the block, but I don't think DBI has any
> restriction on the size of the command. A "command" just means a data
> transfer while keeping the D/CX line low, and "data" when the line is
> high. Similar transfers for n bytes can be done in both modes.
Right. I'll see if the API could be simplified by having a single write
callback function with a data/command parameter.
> > For parameters, we can either express everything as u8 * in the DBI bus
> > operations, or use a union similar to i2c_smbus_data
> >
> > union i2c_smbus_data {
> >
> > __u8 byte;
> > __u16 word;
> > __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for
> > length */
> >
> > /* and one more for user-space
> > compatibility */
> >
> > };
>
> There's no DBI_BLOCK_MAX, so at least identical union won't work. I
> think it's simplest to have u8 * function as a base, and then a few
> helpers to write the most common datatypes.
OK, that sounds good to me.
> So we could have on the lowest level something like:
>
> dbi_write_command(u8 *buf, size_t size);
> dbi_write_data(u8 *buf, size_t size);
>
> And possible helpers:
>
> dbi_write_data(u8 *cmd_buf, size_t cmd_size, u8 *data_buf, size_t
> data_size);
>
> dbi_write_dcs(u8 cmd, u8 *data, size_t size);
>
> And variations:
>
> dbi_write_dcs_0(u8 cmd);
> dbi_write_dcs_1(u8 cmd, u8 data);
>
> etc. So a simple helper to send 16 bits would be:
>
> dbi_write_data(u16 data)
> {
> // or are the bytes the other way around...
> u8 buf[2] = { data & 0xff, (data >> 8) & 0xff };
> return dbi_write_data(buf, 2);
> }
>
> > Helper functions would be available to perform 8-bit, 16-bit or n*8 bits
> > transfers.
> >
> > Would that work for your use cases ?
> >
> > > Also note that some chips need to change the bus width on the fly. The
> > > chip used on N800 wants configuration to be done with 8-bits, and pixel
> > > transfers with 16-bits. Who knows why...
> >
> > On which data lines is configuration performed ? D7-D0 ?
>
> I guess so, but does it matter? All the bus driver needs to know is how
> to send 8/16/.. bit data. On OMAP we just write the data to a 32 bit
> register, and the HW takes the lowest n bits. Do the bits represent the
> data lines directly on Renesans?
Yes they do. For a SYS-80 panel configured in 18-bits mode, I'll have to write
((data & 0xff00) << 2) | ((data & 0x00ff) << 1)
(d15:8 -> D17:10, d7:0 -> D8:1 where d is the word to be written, and D the
physical lines)
to the hardware data register and trigger the write. In 8 bits mode, there
would be two write operations with
(data & 0xff00) << 2
(data & 0x00ff) << 10
(d15:8 -> D17:10, d7:0 -> D17:10)
However, when writing a 8-bit command to a DBI panel in either 16- or 8-bits
mode, there would be a single write with
d7:0 -> D7:0
How to shift the data thus depends both on the bus width and on which data
lines the panel expects data to be present.
I wrote drivers for two DBI panels based on existing board code, the R61505
and the R61517.
The R61505 datasheet is available online, the panel is a SYS-80 panel that
supports 8-, 9-, 16- and 18-bits bus widths. It aligns data towards the MSB
when using a bus width lower than 18.
The R61517 datasheet doesn't seem to be freely available. The panel seems to
be DBI-compliant as it uses a subset of the DCS commands and a wide range of
panel-specific commands. The panel is connected using a 16-bit bus, all
commands and parameters are 8-bits wide and aligned towards the LSB.
To properly transfer commands and parameters, the DBI host will need to know
on how many bits to perform transfers, and how to align data on the bus. For
the former, your mipi_dbi_set_bus_width() function could be used, although
probably not out of the box. The R61505 panel would call
mipi_dbi_set_bus_width() to set the bus width to 16 (as commands and
parameters are 16-bits wide), but if the panel is connected using only 8 or 9
data lines, the host would need to split the 16-bits writes into two 8-bits
writes. Should that be done transparently ? mipi_dbi_set_bus_width() could
possibly act as a mipi_dbi_set_max_bus_width(), but that might be a bit too
hackish.
I'd like to hide as much of the complexity as possible in mipi-dbi-bus.c but I
don't know whether that's possible.
> The omap driver actually only implements 8 and 16 bit modes, not the 9 and
> 12 bit modes. I'm not sure what kind of shifting is needed for those.
There's no 12-bits mode in DBI-2 as far as I can tell.
We will need to support 8-, 9- and 16-bits modes for DBI-2, and additionally
18-bits mode for SYS-80.
> > > > We might just need to provide fake timings. Video mode timings are at
> > > > the core of display support in all drivers so we can't just get rid of
> > > > them. The h/v front/back porch and sync won't be used by display
> > > > drivers for DBI/DSI panels anyway.
> > >
> > > Right. But we should probably think if we can, at the panel level,
> > > easily separate conventional panels and smart panels. Then this
> > > framework wouldn't need to fake the timings, and it'd be up to the
> > > higher level to decide if and how to fake them. Then again, this is no
> > > biggie. Just thought that at the lowest level it'd be nice to be
> > > "correct" and leave faking to upper layers =).
> >
> > But we would then have two different APIs at the lower level depending on
> > the panel type. I'm not sure that's a good thing.
>
> Different API for what? Why anyway need panel type specific functions.
> In the panel struct we could just have an union of the different types
> of parameters for different types of panels.
>
> But if this complicates things, it's not a biggie. Just something that
> has been in my mind when dealing with smart panels and assigning dummy
> video timings for them =).
Please feel free to make a proposal for this when I'll post v2. A patch would
be nice :-)
--
Regards,
Laurent Pinchart
^ permalink raw reply
* [PATCH] fbdev: Make the switch from generic to native driver less alarming
From: Adam Jackson @ 2012-08-17 17:10 UTC (permalink / raw)
To: linux-fbdev
Calling this "conflicting" just makes people think there's a problem
when there's not.
Signed-off-by: Adam Jackson <ajax@redhat.com>
---
drivers/video/fbmem.c | 3 +--
1 files changed, 1 insertions(+), 2 deletions(-)
diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c
index 0dff12a..42be978 100644
--- a/drivers/video/fbmem.c
+++ b/drivers/video/fbmem.c
@@ -1582,8 +1582,7 @@ static void do_remove_conflicting_framebuffers(struct apertures_struct *a,
(primary && gen_aper && gen_aper->count &&
gen_aper->ranges[0].base = VGA_FB_PHYS)) {
- printk(KERN_INFO "fb: conflicting fb hw usage "
- "%s vs %s - removing generic driver\n",
+ printk(KERN_INFO "fb: switching to %s from %s\n",
name, registered_fb[i]->fix.id);
do_unregister_framebuffer(registered_fb[i]);
}
--
1.7.7.6
^ permalink raw reply related
* Re: [PATCH v4 1/3] Runtime Interpreted Power Sequences
From: Mark Brown @ 2012-08-17 23:04 UTC (permalink / raw)
To: Mitch Bradley
Cc: Stephen Warren, Alexandre Courbot, linux-fbdev, Stephen Warren,
linux-doc, linux-kernel, Rob Herring, Anton Vorontsov,
linux-tegra, David Woodhouse, devicetree-discuss
In-Reply-To: <502D61C6.5050101@firmworks.com>
[-- Attachment #1: Type: text/plain, Size: 1206 bytes --]
On Thu, Aug 16, 2012 at 11:10:30AM -1000, Mitch Bradley wrote:
> On 8/16/2012 8:38 AM, Stephen Warren wrote:
> > Device tree bindings shouldn't reference Linux documentation; the
> > bindings are supposed to be OS-agnostic.
> While it is true that bindings should try to be OS-agnostic, there is
> the practical matter of where to put documentation so that it is widely
> accessible. The Linux source tree is one of the most accessible things
> there is, considering how widely it is replicated.
> As the original instigator of the policy that the device tree should
> describe the hardware "OS-neutrally", I personally don't have a problem
> with bindings referring to Linux documentation. I wouldn't like
> references to proprietary and inaccessible documentation.
OS agnosticness isn't the only issue here - the other problem with using
Linux documentation is that except for things that are specifically
userspace interfaces and the DT bindings nothing is intended to be
stable so bindings defined in terms of Linux documentation may randomly
change. We're not doing an awesome job of that with DT right now but we
should try and so we ought to avoid including non-ABI things in ABIs
like this.
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [RFC 0/5] Generic panel framework
From: Laurent Pinchart @ 2012-08-18 1:16 UTC (permalink / raw)
To: Tomi Valkeinen
Cc: linux-fbdev, Marcus Lorentzon, dri-devel, Kyungmin Park,
Richard Purdie, Sebastien Guiriec, Bryan Wu, linux-leds,
linux-media
In-Reply-To: <1345203751.3158.99.camel@deskari>
Hi Tomi,
On Friday 17 August 2012 14:42:31 Tomi Valkeinen wrote:
> On Fri, 2012-08-17 at 13:10 +0200, Laurent Pinchart wrote:
> > What kind of directory structure do you have in mind ? Panels are already
> > isolated in drivers/video/panel/ so we could already ditch the panel-
> > prefix in drivers.
>
> The same directory also contains files for the framework and buses. But
> perhaps there's no need for additional directories if the amount of
> non-panel files is small. And you can easily see from the name that they
> are not panel drivers (e.g. mipi_dbi_bus.c).
I don't expect the directory to contain many non-panel files, so let's keep it
as-is for now.
mipi-dbi-bus might not belong to include/video/panel/ though, as it can be
used for non-panel devices (at least in theory). The future mipi-dsi-bus
certainly will.
> > Would you also create include/video/panel/ ?
>
> Perhaps that would be good. Well, having all the files prefixed with
> panel- is not bad as such, but just feel extra.
>
> > > ---
> > >
> > > Should we aim for DT only solution from the start? DT is the direction
> > > we are going, and I feel the older platform data stuff would be
> > > deprecated soon.
> >
> > Don't forget about non-ARM architectures :-/ We need panel drivers for SH
> > as well, which doesn't use DT. I don't think that would be a big issue, a
> > DT- compliant solution should be easy to use through board code and
> > platform data as well.
>
> I didn't forget about them as I didn't even think about them ;). I
> somehow had the impression that other architectures would also use DT,
> sooner or later. I could be mistaken, though.
>
> And true, it's not a big issue to support both DT and non-DT versions,
> but I've been porting omap stuff for DT and keeping the old platform
> data stuff also there, and it just feels burdensome. For very simple
> panels it's easy, but when you've passing lots of parameters the code
> starts to get longer.
>
> > > This one would be rather impossible with the upper layer handling the
> > > enabling of the video stream. Thus I see that the panel driver needs to
> > > control the sequences, and the Sharp panel driver's enable would look
> > > something like:
> > >
> > > regulator_enable(...);
> > > sleep();
> > > dpi_enable_video();
> > > sleep();
> > > gpip_set(..);
> >
> > I have to admit I have no better solution to propose at the moment, even
> > if I don't really like making the panel control the video stream. When
> > several devices will be present in the chain all of them might have
> > similar annoying requirements, and my feeling is that the resulting code
> > will be quite messy. At the end of the day the only way to really find
> > out is to write an implementation.
>
> If we have a chain of devices, and each device uses the bus interface
> from the previous device in the chain, there shouldn't be a problem. In
> that model each device can handle the task however is best for it.
>
> I think the problems come from the divided control we'll have. I mean,
> if the panel driver would decide itself what to send to its output, and
> it would provide the data (think of an i2c device), this would be very
> simple. And it actually is for things like configuration data etc, but
> not so for video stream.
Would you be able to send incremental patches on top of v2 to implement the
solution you have in mind ? It would be neat if you could also implement mipi-
dsi-bus for the OMAP DSS and test the code with a real device :-)
> > > It could cause some locking issues, though. First the panel's remove
> > > could take a lock, but the remove sequence would cause the display
> > > driver to call disable on the panel, which could again try to take the
> > > same lock...
> >
> > We have two possible ways of calling panel operations, either directly
> > (panel->bus->ops->enable(...)) or indirectly (panel_enable(...)).
> >
> > The former is what V4L2 currently does with subdevs, and requires display
> > drivers to hold a reference to the panel. The later can do without a
> > direct reference only if we use a global lock, which is something I would
> > like to
>
> Wouldn't panel_enable() just do the same panel->bus->ops->enable()
> anyway, and both require a panel reference? I don't see the difference.
Indeed, you're right. I'm not sure what I was thinking about.
> > avoid. A panel-wide lock wouldn't work, as the access function would need
> > to take the lock on a panel instance that can be removed at any time.
>
> Can't this be handled with some kind of get/put refcounting? If there's
> a ref, it can't be removed.
Trouble will come when the display driver will hold a reference to the panel,
and the panel will hold a reference to the display driver (for instance
because the display driver provides the DBI/DSI bus, or because it provides a
clock used by the panel).
> Generally about locks, if we define that panel ops may only be called
> exclusively, does it simplify things? I think we can make such
> requirements, as there should be only one display framework that handles
> the panel. Then we don't need locking for things like enable/disable.
Pushing locking to callers would indeed simplify panel drivers, but we need to
make sure we won't need to expose a panel to several callers in the future.
> Of course we need to be careful about things where calls come from
> "outside" the display framework. I guess one such thing is rmmod, but if
> that causes a notification to the display framework, which again handles
> locking, it shouldn't be a problem.
>
> Another thing to be careful about is if the panel internally uses irqs,
> workqueues, sysfs files or such. In that case it needs to handle
> locking.
Of course panels will need to manage concurrency for their own infrastructure.
--
Regards,
Laurent Pinchart
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox