This series is based on exynos-drm-next-todo branch of Inki Dae's tree at: git://git.kernel.org/pub/scm/linux/kernel/git/daeinki/drm-exynos.git
This set of drm patches are needed to support bridge chips and eDP/LVDS panels with exynos_dp.
Bridge chip driver for parade DP to LVDS converter is also added.
For testing, I have used exynos5250-snow, and exynos5420-peach-pit boards along with a few local arch side patches.
Andrew Bresticker (1): [PATCH 1/9] drm/exynos: dp: support hotplug detection via GPIO
Ajay Kumar (7): [PATCH 2/9] drm/panel: add pre_enable and post_disable routines [PATCH 3/9] drm/panel: Add driver for exynos_dp based panels [PATCH 4/9] drm/exynos: add exynos_dp_panel driver registration to drm driver [PATCH 5/9] drm/exynos: dp: modify driver to support drm_panel [PATCH 7/9] drm/bridge: ptn3460: add drm_panel controls [PATCH 8/9] drm/bridge: Add PS8622 bridge driver [PATCH 9/9] drm/exynos: Add ps8622 lvds bridge discovery to DP driver
Rahul Sharma (1): [PATCH 6/9] drm/bridge: ptn3460: enable polling based detection
Major changes since V1: Add post_disable callback and add bridge chip driver for parade DP to LVDS converter
.../devicetree/bindings/panel/exynos-dp-panel.txt | 45 ++ .../devicetree/bindings/video/exynos_dp.txt | 4 + drivers/gpu/drm/bridge/Kconfig | 8 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ps8622.c | 566 ++++++++++++++++++++ drivers/gpu/drm/bridge/ptn3460.c | 21 +- drivers/gpu/drm/exynos/Kconfig | 1 + drivers/gpu/drm/exynos/exynos_dp_core.c | 75 ++- drivers/gpu/drm/exynos/exynos_dp_core.h | 2 + drivers/gpu/drm/exynos/exynos_dp_reg.c | 44 +- drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 + drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-exynos-dp.c | 251 +++++++++ include/drm/bridge/ps8622.h | 42 ++ include/drm/bridge/ptn3460.h | 6 +- include/drm/drm_panel.h | 18 + 18 files changed, 1088 insertions(+), 22 deletions(-) create mode 100644 Documentation/devicetree/bindings/panel/exynos-dp-panel.txt create mode 100644 drivers/gpu/drm/bridge/ps8622.c create mode 100644 drivers/gpu/drm/panel/panel-exynos-dp.c create mode 100644 include/drm/bridge/ps8622.h
From: Andrew Bresticker abrestic@chromium.org
Certain bridge chips use a GPIO to indicate the cable status instead of the I_DP_HPD pin. This adds an optional device-tree property, "samsung,hpd-gpio", to the exynos-dp controller which indicates that the specified GPIO should be used for hotplug detection. The GPIO is then set up as an edge-triggered interrupt where the rising edge indicates hotplug-in and the falling edge indicates hotplug-out.
Signed-off-by: Andrew Bresticker abrestic@chromium.org Signed-off-by: Rahul Sharma rahul.sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: Address reiew comments from Jingoo Han
.../devicetree/bindings/video/exynos_dp.txt | 4 ++ drivers/gpu/drm/exynos/exynos_dp_core.c | 32 ++++++++++++-- drivers/gpu/drm/exynos/exynos_dp_core.h | 1 + drivers/gpu/drm/exynos/exynos_dp_reg.c | 44 ++++++++++++++------ 4 files changed, 66 insertions(+), 15 deletions(-)
diff --git a/Documentation/devicetree/bindings/video/exynos_dp.txt b/Documentation/devicetree/bindings/video/exynos_dp.txt index 57ccdde..53dbccf 100644 --- a/Documentation/devicetree/bindings/video/exynos_dp.txt +++ b/Documentation/devicetree/bindings/video/exynos_dp.txt @@ -62,6 +62,10 @@ Optional properties for dp-controller: -hsync-active-high: HSYNC polarity configuration. High if defined, Low if not defined + -samsung,hpd-gpio: + Hotplug detect GPIO. + Indicates which GPIO should be used for hotplug + detection
Example:
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index 1cc3981..18fd9c5 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -18,6 +18,8 @@ #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/gpio.h> #include <linux/component.h> #include <linux/phy/phy.h> #include <video/of_display_timing.h> @@ -1226,6 +1228,7 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data) struct drm_device *drm_dev = data; struct resource *res; struct exynos_dp_device *dp; + unsigned int irq_flags;
int ret = 0;
@@ -1265,7 +1268,30 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data) if (IS_ERR(dp->reg_base)) return PTR_ERR(dp->reg_base);
- dp->irq = platform_get_irq(pdev, 0); + dp->hpd_gpio = of_get_named_gpio(dev->of_node, "samsung,hpd-gpio", 0); + + if (gpio_is_valid(dp->hpd_gpio)) { + /* + * Set up the hotplug GPIO from the device tree as an interrupt. + * Simply specifying a different interrupt in the device tree + * doesn't work since we handle hotplug rather differently when + * using a GPIO. We also need the actual GPIO specifier so + * that we can get the current state of the GPIO. + */ + ret = devm_gpio_request_one(&pdev->dev, dp->hpd_gpio, GPIOF_IN, + "hpd_gpio"); + if (ret) { + dev_err(&pdev->dev, "failed to get hpd gpio\n"); + return ret; + } + dp->irq = gpio_to_irq(dp->hpd_gpio); + irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + } else { + dp->hpd_gpio = -ENODEV; + dp->irq = platform_get_irq(pdev, 0); + irq_flags = 0; + } + if (dp->irq == -ENXIO) { dev_err(&pdev->dev, "failed to get irq\n"); return -ENODEV; @@ -1277,8 +1303,8 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
exynos_dp_init_dp(dp);
- ret = devm_request_irq(&pdev->dev, dp->irq, exynos_dp_irq_handler, 0, - "exynos-dp", dp); + ret = devm_request_irq(&pdev->dev, dp->irq, exynos_dp_irq_handler, + irq_flags, "exynos-dp", dp); if (ret) { dev_err(&pdev->dev, "failed to request irq\n"); return ret; diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.h b/drivers/gpu/drm/exynos/exynos_dp_core.h index d6a900d..56fa43e 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.h +++ b/drivers/gpu/drm/exynos/exynos_dp_core.h @@ -159,6 +159,7 @@ struct exynos_dp_device { struct work_struct hotplug_work; struct phy *phy; int dpms_mode; + int hpd_gpio;
struct exynos_drm_panel_info panel; }; diff --git a/drivers/gpu/drm/exynos/exynos_dp_reg.c b/drivers/gpu/drm/exynos/exynos_dp_reg.c index b70da50..79291a2 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_reg.c +++ b/drivers/gpu/drm/exynos/exynos_dp_reg.c @@ -13,6 +13,7 @@ #include <linux/device.h> #include <linux/io.h> #include <linux/delay.h> +#include <linux/gpio.h>
#include "exynos_dp_core.h" #include "exynos_dp_reg.h" @@ -326,6 +327,9 @@ void exynos_dp_clear_hotplug_interrupts(struct exynos_dp_device *dp) { u32 reg;
+ if (gpio_is_valid(dp->hpd_gpio)) + return; + reg = HOTPLUG_CHG | HPD_LOST | PLUG; writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4);
@@ -337,6 +341,9 @@ void exynos_dp_init_hpd(struct exynos_dp_device *dp) { u32 reg;
+ if (gpio_is_valid(dp->hpd_gpio)) + return; + exynos_dp_clear_hotplug_interrupts(dp);
reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); @@ -348,19 +355,27 @@ enum dp_irq_type exynos_dp_get_irq_type(struct exynos_dp_device *dp) { u32 reg;
- /* Parse hotplug interrupt status register */ - reg = readl(dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); + if (gpio_is_valid(dp->hpd_gpio)) { + reg = gpio_get_value(dp->hpd_gpio); + if (reg) + return DP_IRQ_TYPE_HP_CABLE_IN; + else + return DP_IRQ_TYPE_HP_CABLE_OUT; + } else { + /* Parse hotplug interrupt status register */ + reg = readl(dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4);
- if (reg & PLUG) - return DP_IRQ_TYPE_HP_CABLE_IN; + if (reg & PLUG) + return DP_IRQ_TYPE_HP_CABLE_IN;
- if (reg & HPD_LOST) - return DP_IRQ_TYPE_HP_CABLE_OUT; + if (reg & HPD_LOST) + return DP_IRQ_TYPE_HP_CABLE_OUT;
- if (reg & HOTPLUG_CHG) - return DP_IRQ_TYPE_HP_CHANGE; + if (reg & HOTPLUG_CHG) + return DP_IRQ_TYPE_HP_CHANGE;
- return DP_IRQ_TYPE_UNKNOWN; + return DP_IRQ_TYPE_UNKNOWN; + } }
void exynos_dp_reset_aux(struct exynos_dp_device *dp) @@ -402,9 +417,14 @@ int exynos_dp_get_plug_in_status(struct exynos_dp_device *dp) { u32 reg;
- reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); - if (reg & HPD_STATUS) - return 0; + if (gpio_is_valid(dp->hpd_gpio)) { + if (gpio_get_value(dp->hpd_gpio)) + return 0; + } else { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + if (reg & HPD_STATUS) + return 0; + }
return -EINVAL; }
On Tuesday, April 22, 2014 7:39 AM, Ajay Kumar wrote:
From: Andrew Bresticker abrestic@chromium.org
Certain bridge chips use a GPIO to indicate the cable status instead of the I_DP_HPD pin. This adds an optional device-tree property, "samsung,hpd-gpio", to the exynos-dp controller which indicates that the specified GPIO should be used for hotplug detection. The GPIO is then set up as an edge-triggered interrupt where the rising edge indicates hotplug-in and the falling edge indicates hotplug-out.
Signed-off-by: Andrew Bresticker abrestic@chromium.org Signed-off-by: Rahul Sharma rahul.sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Acked-by: Jingoo Han jg1.han@samsung.com
Best regards, Jingoo Han
Changes since V1: Address reiew comments from Jingoo Han
.../devicetree/bindings/video/exynos_dp.txt | 4 ++ drivers/gpu/drm/exynos/exynos_dp_core.c | 32 ++++++++++++-- drivers/gpu/drm/exynos/exynos_dp_core.h | 1 + drivers/gpu/drm/exynos/exynos_dp_reg.c | 44 ++++++++++++++------ 4 files changed, 66 insertions(+), 15 deletions(-)
[.....]
On Tuesday, April 22, 2014 4:14 PM, Jingoo Han wrote:
On Tuesday, April 22, 2014 7:39 AM, Ajay Kumar wrote:
From: Andrew Bresticker abrestic@chromium.org
Certain bridge chips use a GPIO to indicate the cable status instead of the I_DP_HPD pin. This adds an optional device-tree property, "samsung,hpd-gpio", to the exynos-dp controller which indicates that the specified GPIO should be used for hotplug detection. The GPIO is then set up as an edge-triggered interrupt where the rising edge indicates hotplug-in and the falling edge indicates hotplug-out.
Signed-off-by: Andrew Bresticker abrestic@chromium.org Signed-off-by: Rahul Sharma rahul.sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Acked-by: Jingoo Han jg1.han@samsung.com
Hi Inki Dae,
Actually, this patch is NOT related to other patches for bridge chip support. So, would you apply this patch into your exynos drm git with my Acked-by?
Best regards, Jingoo Han
Changes since V1: Address reiew comments from Jingoo Han
.../devicetree/bindings/video/exynos_dp.txt | 4 ++ drivers/gpu/drm/exynos/exynos_dp_core.c | 32 ++++++++++++-- drivers/gpu/drm/exynos/exynos_dp_core.h | 1 + drivers/gpu/drm/exynos/exynos_dp_reg.c | 44 ++++++++++++++------ 4 files changed, 66 insertions(+), 15 deletions(-)
[.....]
Most of the panels need an init sequence as mentioned below: -- poweron LCD unit/LCD_EN -- start video data -- poweron LED unit/BL_EN And, a de-init sequence as mentioned below: -- poweroff LED unit/BL_EN -- stop video data -- poweroff LCD unit/LCD_EN With existing callbacks for drm panel, we cannot accomodate such panels, since only two callbacks, i.e "panel_enable" and panel_disable are supported.
This patch adds: -- "pre_enable" callback which can be called before the actual video data is on, and then call the "enable" callback after the video data is available.
-- "post_disable" callback which can be called after the video data is off, and use "disable" callback to do something before switching off the video data.
Now, we can easily map the above scenario as shown below: poweron LCD unit/LCD_EN = "pre_enable" callback poweron LED unit/BL_EN = "enable" callback poweroff LED unit/BL_EN = "disable" callback poweroff LCD unit/LCD_EN = "post_disable" callback
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: Added post_disable callback
include/drm/drm_panel.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h index c2ab77a..bf191df 100644 --- a/include/drm/drm_panel.h +++ b/include/drm/drm_panel.h @@ -31,7 +31,9 @@ struct drm_device; struct drm_panel;
struct drm_panel_funcs { + int (*post_disable)(struct drm_panel *panel); int (*disable)(struct drm_panel *panel); + int (*pre_enable)(struct drm_panel *panel); int (*enable)(struct drm_panel *panel); int (*get_modes)(struct drm_panel *panel); }; @@ -46,6 +48,14 @@ struct drm_panel { struct list_head list; };
+static inline int drm_panel_post_disable(struct drm_panel *panel) +{ + if (panel && panel->funcs && panel->funcs->post_disable) + return panel->funcs->post_disable(panel); + + return panel ? -ENOSYS : -EINVAL; +} + static inline int drm_panel_disable(struct drm_panel *panel) { if (panel && panel->funcs && panel->funcs->disable) @@ -54,6 +64,14 @@ static inline int drm_panel_disable(struct drm_panel *panel) return panel ? -ENOSYS : -EINVAL; }
+static inline int drm_panel_pre_enable(struct drm_panel *panel) +{ + if (panel && panel->funcs && panel->funcs->pre_enable) + return panel->funcs->pre_enable(panel); + + return panel ? -ENOSYS : -EINVAL; +} + static inline int drm_panel_enable(struct drm_panel *panel) { if (panel && panel->funcs && panel->funcs->enable)
On Tue, Apr 22, 2014 at 04:09:11AM +0530, Ajay Kumar wrote:
Most of the panels need an init sequence as mentioned below: -- poweron LCD unit/LCD_EN -- start video data -- poweron LED unit/BL_EN And, a de-init sequence as mentioned below: -- poweroff LED unit/BL_EN -- stop video data -- poweroff LCD unit/LCD_EN With existing callbacks for drm panel, we cannot accomodate such panels, since only two callbacks, i.e "panel_enable" and panel_disable are supported.
This patch adds: -- "pre_enable" callback which can be called before the actual video data is on, and then call the "enable" callback after the video data is available.
-- "post_disable" callback which can be called after the video data is off, and use "disable" callback to do something before switching off the video data.
Now, we can easily map the above scenario as shown below: poweron LCD unit/LCD_EN = "pre_enable" callback poweron LED unit/BL_EN = "enable" callback poweroff LED unit/BL_EN = "disable" callback poweroff LCD unit/LCD_EN = "post_disable" callback
I don't like this. What happens when the next panel comes around that has a yet slightly different requirement? Will we introduce a new pre_pre_enable() and post_post_disable() function then?
There's got to be a better way to solve this.
Thierry
Hi Thierry,
On Tue, Apr 22, 2014 at 1:49 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:11AM +0530, Ajay Kumar wrote:
Most of the panels need an init sequence as mentioned below: -- poweron LCD unit/LCD_EN -- start video data -- poweron LED unit/BL_EN And, a de-init sequence as mentioned below: -- poweroff LED unit/BL_EN -- stop video data -- poweroff LCD unit/LCD_EN With existing callbacks for drm panel, we cannot accomodate such panels, since only two callbacks, i.e "panel_enable" and panel_disable are
supported.
This patch adds: -- "pre_enable" callback which can be called before the actual video data is on, and then call the "enable" callback after the video data is available.
-- "post_disable" callback which can be called after the video data is off, and use "disable" callback to do something before switching off the video data.
Now, we can easily map the above scenario as shown below: poweron LCD unit/LCD_EN = "pre_enable" callback poweron LED unit/BL_EN = "enable" callback poweroff LED unit/BL_EN = "disable" callback poweroff LCD unit/LCD_EN = "post_disable" callback
I don't like this. What happens when the next panel comes around that has a yet slightly different requirement? Will we introduce a new pre_pre_enable() and post_post_disable() function then?
As I have already explained, these 2 callbacks are sufficient enough to take care the power up/down sequence for LVDS and eDP panels. And, definitely having just 2 callbacks "enable" and "disable" is not at all sufficient.
I initially thought of using panel_simple_enable from panel-simple driver. But it doesn't go well with case wherein there are 2 regulators(one for LCD and one for LED) a BL_EN signal etc. And, often(Believe me, I have referred to both eDP panel datasheets and LVDS panel datasheets) proper powerup sequence for such panels is as mentioned below:
powerup: -- power up the supply (LCD_VCC). -- start video data. -- enable backlight.
powerdown -- disable backlight. -- stop video data. -- power off the supply (LCD VCC)
For the above cases, if I have to somehow fit in all the required settings, it breaks the sequence and I end up getting glitches during bootup/DPMS.
Also, when the drm_bridge can support pre_enable and post_disable, why not drm_panel provide that both should work in tandem?
There's got to be a better way to solve this.
Thierry
Thanks and Regards, Ajay Kumar
On Tue, Apr 22, 2014 at 08:06:19PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 1:49 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:11AM +0530, Ajay Kumar wrote:
Most of the panels need an init sequence as mentioned below: -- poweron LCD unit/LCD_EN -- start video data -- poweron LED unit/BL_EN And, a de-init sequence as mentioned below: -- poweroff LED unit/BL_EN -- stop video data -- poweroff LCD unit/LCD_EN With existing callbacks for drm panel, we cannot accomodate such panels, since only two callbacks, i.e "panel_enable" and panel_disable are
supported.
This patch adds: -- "pre_enable" callback which can be called before the actual video data is on, and then call the "enable" callback after the video data is available.
-- "post_disable" callback which can be called after the video data is off, and use "disable" callback to do something before switching off the video data.
Now, we can easily map the above scenario as shown below: poweron LCD unit/LCD_EN = "pre_enable" callback poweron LED unit/BL_EN = "enable" callback poweroff LED unit/BL_EN = "disable" callback poweroff LCD unit/LCD_EN = "post_disable" callback
I don't like this. What happens when the next panel comes around that has a yet slightly different requirement? Will we introduce a new pre_pre_enable() and post_post_disable() function then?
As I have already explained, these 2 callbacks are sufficient enough to take care the power up/down sequence for LVDS and eDP panels. And, definitely having just 2 callbacks "enable" and "disable" is not at all sufficient.
I initially thought of using panel_simple_enable from panel-simple driver. But it doesn't go well with case wherein there are 2 regulators(one for LCD and one for LED) a BL_EN signal etc. And, often(Believe me, I have referred to both eDP panel datasheets and LVDS panel datasheets) proper powerup sequence for such panels is as mentioned below:
powerup: -- power up the supply (LCD_VCC). -- start video data. -- enable backlight.
powerdown -- disable backlight. -- stop video data. -- power off the supply (LCD VCC)
For the above cases, if I have to somehow fit in all the required settings, it breaks the sequence and I end up getting glitches during bootup/DPMS.
Also, when the drm_bridge can support pre_enable and post_disable, why not drm_panel provide that both should work in tandem?
Imo this makes sense, especially if we go through with the idea talked about yesterday of creating a drm_bridge to wrap-up a drm_panel with sufficient isolation between all components. -Daniel
On Wed, Apr 23, 2014 at 09:29:15AM +0200, Daniel Vetter wrote:
On Tue, Apr 22, 2014 at 08:06:19PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 1:49 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:11AM +0530, Ajay Kumar wrote:
Most of the panels need an init sequence as mentioned below: -- poweron LCD unit/LCD_EN -- start video data -- poweron LED unit/BL_EN And, a de-init sequence as mentioned below: -- poweroff LED unit/BL_EN -- stop video data -- poweroff LCD unit/LCD_EN With existing callbacks for drm panel, we cannot accomodate such panels, since only two callbacks, i.e "panel_enable" and panel_disable are
supported.
This patch adds: -- "pre_enable" callback which can be called before the actual video data is on, and then call the "enable" callback after the video data is available.
-- "post_disable" callback which can be called after the video data is off, and use "disable" callback to do something before switching off the video data.
Now, we can easily map the above scenario as shown below: poweron LCD unit/LCD_EN = "pre_enable" callback poweron LED unit/BL_EN = "enable" callback poweroff LED unit/BL_EN = "disable" callback poweroff LCD unit/LCD_EN = "post_disable" callback
I don't like this. What happens when the next panel comes around that has a yet slightly different requirement? Will we introduce a new pre_pre_enable() and post_post_disable() function then?
As I have already explained, these 2 callbacks are sufficient enough to take care the power up/down sequence for LVDS and eDP panels. And, definitely having just 2 callbacks "enable" and "disable" is not at all sufficient.
I initially thought of using panel_simple_enable from panel-simple driver. But it doesn't go well with case wherein there are 2 regulators(one for LCD and one for LED) a BL_EN signal etc. And, often(Believe me, I have referred to both eDP panel datasheets and LVDS panel datasheets) proper powerup sequence for such panels is as mentioned below:
powerup: -- power up the supply (LCD_VCC). -- start video data. -- enable backlight.
powerdown -- disable backlight. -- stop video data. -- power off the supply (LCD VCC)
For the above cases, if I have to somehow fit in all the required settings, it breaks the sequence and I end up getting glitches during bootup/DPMS.
Also, when the drm_bridge can support pre_enable and post_disable, why not drm_panel provide that both should work in tandem?
Imo this makes sense, especially if we go through with the idea talked about yesterday of creating a drm_bridge to wrap-up a drm_panel with sufficient isolation between all components.
I'm not at all comfortable with these. The names are totally confusing. Next somebody will need to do something after the panel has been enabled and we'll have to introduce .post_enable() and .pre_disable() functions. And worse, what if somebody needs something to be done between .pre_enable() and .enable()? .post_pre_enable()? Where does it end?
According to the above description the only reason why we need this is to avoid visible glitches when the panel is already on, but the video stream hasn't started yet. If that's the only reason why we need this, then perhaps adding a way for a panel to expose the associated backlight would be better?
Thierry
Thierry,
On Wed, Apr 23, 2014 at 1:12 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Wed, Apr 23, 2014 at 09:29:15AM +0200, Daniel Vetter wrote:
On Tue, Apr 22, 2014 at 08:06:19PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 1:49 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:11AM +0530, Ajay Kumar wrote:
Most of the panels need an init sequence as mentioned below: -- poweron LCD unit/LCD_EN -- start video data -- poweron LED unit/BL_EN And, a de-init sequence as mentioned below: -- poweroff LED unit/BL_EN -- stop video data -- poweroff LCD unit/LCD_EN With existing callbacks for drm panel, we cannot accomodate such panels, since only two callbacks, i.e "panel_enable" and panel_disable are
supported.
This patch adds: -- "pre_enable" callback which can be called before the actual video data is on, and then call the "enable" callback after the video data is available.
-- "post_disable" callback which can be called after the video data is off, and use "disable" callback to do something before switching off the video data.
Now, we can easily map the above scenario as shown below: poweron LCD unit/LCD_EN = "pre_enable" callback poweron LED unit/BL_EN = "enable" callback poweroff LED unit/BL_EN = "disable" callback poweroff LCD unit/LCD_EN = "post_disable" callback
I don't like this. What happens when the next panel comes around that has a yet slightly different requirement? Will we introduce a new pre_pre_enable() and post_post_disable() function then?
As I have already explained, these 2 callbacks are sufficient enough to take care the power up/down sequence for LVDS and eDP panels. And, definitely having just 2 callbacks "enable" and "disable" is not at all sufficient.
I initially thought of using panel_simple_enable from panel-simple driver. But it doesn't go well with case wherein there are 2 regulators(one for LCD and one for LED) a BL_EN signal etc. And, often(Believe me, I have referred to both eDP panel datasheets and LVDS panel datasheets) proper powerup sequence for such panels is as mentioned below:
powerup: -- power up the supply (LCD_VCC). -- start video data. -- enable backlight.
powerdown -- disable backlight. -- stop video data. -- power off the supply (LCD VCC)
For the above cases, if I have to somehow fit in all the required settings, it breaks the sequence and I end up getting glitches during bootup/DPMS.
Also, when the drm_bridge can support pre_enable and post_disable, why not drm_panel provide that both should work in tandem?
Imo this makes sense, especially if we go through with the idea talked about yesterday of creating a drm_bridge to wrap-up a drm_panel with sufficient isolation between all components.
I'm not at all comfortable with these. The names are totally confusing. Next somebody will need to do something after the panel has been enabled and we'll have to introduce .post_enable() and .pre_disable() functions. And worse, what if somebody needs something to be done between .pre_enable() and .enable()? .post_pre_enable()? Where does it end?
According to the above description the only reason why we need this is to avoid visible glitches when the panel is already on, but the video stream hasn't started yet. If that's the only reason why we need this, then perhaps adding a way for a panel to expose the associated backlight would be better?
Actually, we need not expose the entire backlight device. AFAIK, the glitch is caused when you enable BL_EN before there is valid video data. So, one way to mask off the glitch is to follow this sequence: -- power up the panel. -- start video data, (start PWM here or) -- (start PWM here), enable backlight
The problem is that the above scenario cannot be mapped to panel-simple driver. IMO, panel_simple should provide enable/disable controls both for LCD and backlight. something like panel_simple_lcd_enable/panel_simple_led_enable, and panel_simple_lcd_disable/panel_simple_led_disable.
Then we can call panel_simple_lcd_enable before video stream is present, And call panel_simple_led_enable after the video stream is present. In that way we can accomodate majority of LVDS and eDP panels inside panel_simple driver without having to worry about the glitch :)
Thanks and regards, Ajay Kumar
On Thu, Apr 24, 2014 at 12:56:02AM +0530, Ajay kumar wrote:
Thierry,
On Wed, Apr 23, 2014 at 1:12 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Wed, Apr 23, 2014 at 09:29:15AM +0200, Daniel Vetter wrote:
[...]
Imo this makes sense, especially if we go through with the idea talked about yesterday of creating a drm_bridge to wrap-up a drm_panel with sufficient isolation between all components.
I'm not at all comfortable with these. The names are totally confusing. Next somebody will need to do something after the panel has been enabled and we'll have to introduce .post_enable() and .pre_disable() functions. And worse, what if somebody needs something to be done between .pre_enable() and .enable()? .post_pre_enable()? Where does it end?
According to the above description the only reason why we need this is to avoid visible glitches when the panel is already on, but the video stream hasn't started yet. If that's the only reason why we need this, then perhaps adding a way for a panel to expose the associated backlight would be better?
Actually, we need not expose the entire backlight device. AFAIK, the glitch is caused when you enable BL_EN before there is valid video data. So, one way to mask off the glitch is to follow this sequence: -- power up the panel. -- start video data, (start PWM here or) -- (start PWM here), enable backlight
That's very difficult to get right, isn't it? Even if you have fine- grained control over what to enable you still need a way to determine _when_ it's safe to enable the backlight. Typically I guess that would be the duration of one frame (or perhaps 2, depending on when the panel syncs to the video signal).
Perhaps it could even by sync'ed to the VBLANK?
The problem is that the above scenario cannot be mapped to panel-simple driver. IMO, panel_simple should provide enable/disable controls both for LCD and backlight. something like panel_simple_lcd_enable/panel_simple_led_enable, and panel_simple_lcd_disable/panel_simple_led_disable.
That's not what the simple panel driver can do. If we want this it needs to be solved in a generic way for all panels since they all need to use the drm_panel_*() functions to abstract away the details from drivers that use the panels.
Thierry
On Friday, April 25, 2014, Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Apr 24, 2014 at 12:56:02AM +0530, Ajay kumar wrote:
Thierry,
On Wed, Apr 23, 2014 at 1:12 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Wed, Apr 23, 2014 at 09:29:15AM +0200, Daniel Vetter wrote:
[...]
Imo this makes sense, especially if we go through with the idea talked about yesterday of creating a drm_bridge to wrap-up a drm_panel with sufficient isolation between all components.
I'm not at all comfortable with these. The names are totally confusing. Next somebody will need to do something after the panel has been enabled and we'll have to introduce .post_enable() and .pre_disable() functions. And worse, what if somebody needs something to be done between .pre_enable() and .enable()? .post_pre_enable()? Where does it end?
According to the above description the only reason why we need this is to avoid visible glitches when the panel is already on, but the video stream hasn't started yet. If that's the only reason why we need this, then perhaps adding a way for a panel to expose the associated backlight would be better?
Actually, we need not expose the entire backlight device. AFAIK, the glitch is caused when you enable BL_EN before there is valid video data. So, one way to mask off the glitch is to follow this sequence: -- power up the panel. -- start video data, (start PWM here or) -- (start PWM here), enable backlight
That's very difficult to get right, isn't it? Even if you have fine- grained control over what to enable you still need a way to determine _when_ it's safe to enable the backlight. Typically I guess that would be the duration of one frame (or perhaps 2, depending on when the panel syncs to the video signal).
We need not "determine", its already present in LVDS datasheet. The LVDS datasheet says at least 200ms delay is needed from "Valid data" to "BL on".
Perhaps it could even by sync'ed to the VBLANK?
No. vblanks are related to crtc. And the bridge/panel driver should be independent of vblank.
The problem is that the above scenario cannot be mapped to panel-simple driver. IMO, panel_simple should provide enable/disable controls both for LCD and backlight. something like panel_simple_lcd_enable/panel_simple_led_enable, and panel_simple_lcd_disable/panel_simple_led_disable.
That's not what the simple panel driver can do. If we want this it needs to be solved in a generic way for all panels since they all need to use the drm_panel_*() functions to abstract away the details from drivers that use the panels.
Right. So only I have added pre_enable and post_disable callbacks. Using that name won't harm existing panel drivers and still addresses our requirement.
Regards, Ajay
On Fri, Apr 25, 2014 at 11:34 AM, Ajay kumar ajaynumb@gmail.com wrote:
On Friday, April 25, 2014, Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Apr 24, 2014 at 12:56:02AM +0530, Ajay kumar wrote:
Thierry,
On Wed, Apr 23, 2014 at 1:12 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Wed, Apr 23, 2014 at 09:29:15AM +0200, Daniel Vetter wrote:
[...]
Imo this makes sense, especially if we go through with the idea talked about yesterday of creating a drm_bridge to wrap-up a drm_panel with sufficient isolation between all components.
I'm not at all comfortable with these. The names are totally confusing. Next somebody will need to do something after the panel has been enabled and we'll have to introduce .post_enable() and .pre_disable() functions. And worse, what if somebody needs something to be done between .pre_enable() and .enable()? .post_pre_enable()? Where does it end?
According to the above description the only reason why we need this is to avoid visible glitches when the panel is already on, but the video stream hasn't started yet. If that's the only reason why we need this, then perhaps adding a way for a panel to expose the associated backlight would be better?
Actually, we need not expose the entire backlight device. AFAIK, the glitch is caused when you enable BL_EN before there is valid video data. So, one way to mask off the glitch is to follow this sequence: -- power up the panel. -- start video data, (start PWM here or) -- (start PWM here), enable backlight
That's very difficult to get right, isn't it? Even if you have fine- grained control over what to enable you still need a way to determine _when_ it's safe to enable the backlight. Typically I guess that would be the duration of one frame (or perhaps 2, depending on when the panel syncs to the video signal).
We need not "determine", its already present in LVDS datasheet. The LVDS datasheet says at least 200ms delay is needed from "Valid data" to "BL on".
Perhaps it could even by sync'ed to the VBLANK?
No. vblanks are related to crtc. And the bridge/panel driver should be independent of vblank.
The problem is that the above scenario cannot be mapped to panel-simple driver. IMO, panel_simple should provide enable/disable controls both for LCD and backlight. something like panel_simple_lcd_enable/panel_simple_led_enable, and panel_simple_lcd_disable/panel_simple_led_disable.
That's not what the simple panel driver can do. If we want this it needs to be solved in a generic way for all panels since they all need to use the drm_panel_*() functions to abstract away the details from drivers that use the panels.
Right. So only I have added pre_enable and post_disable callbacks. Using that name won't harm existing panel drivers and still addresses our requirement.
Regards, Ajay
Thierry : Are you really ok with the new callback names? like pre_enable and post_disable? Adding those new callbacks really won't harm the existing panels anyhow.
Daniel, Rob : I think I have given sufficient amount of information as to why the concept of drm_panel_bridge fails and why we cannot have callbacks for drm_panel inside crtc helpers or any other generic place.
Please let me know your final opinion so that I can start reworking on this series.
Thanks and regards, Ajay Kumar
ping!
On Fri, Apr 25, 2014 at 11:46 PM, Ajay kumar ajaynumb@gmail.com wrote:
On Fri, Apr 25, 2014 at 11:34 AM, Ajay kumar ajaynumb@gmail.com wrote:
On Friday, April 25, 2014, Thierry Reding thierry.reding@gmail.com wrote:
On Thu, Apr 24, 2014 at 12:56:02AM +0530, Ajay kumar wrote:
Thierry,
On Wed, Apr 23, 2014 at 1:12 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Wed, Apr 23, 2014 at 09:29:15AM +0200, Daniel Vetter wrote:
[...]
Imo this makes sense, especially if we go through with the idea talked about yesterday of creating a drm_bridge to wrap-up a drm_panel with sufficient isolation between all components.
I'm not at all comfortable with these. The names are totally confusing. Next somebody will need to do something after the panel has been enabled and we'll have to introduce .post_enable() and .pre_disable() functions. And worse, what if somebody needs something to be done between .pre_enable() and .enable()? .post_pre_enable()? Where does it end?
According to the above description the only reason why we need this is to avoid visible glitches when the panel is already on, but the video stream hasn't started yet. If that's the only reason why we need this, then perhaps adding a way for a panel to expose the associated backlight would be better?
Actually, we need not expose the entire backlight device. AFAIK, the glitch is caused when you enable BL_EN before there is valid video data. So, one way to mask off the glitch is to follow this sequence: -- power up the panel. -- start video data, (start PWM here or) -- (start PWM here), enable backlight
That's very difficult to get right, isn't it? Even if you have fine- grained control over what to enable you still need a way to determine _when_ it's safe to enable the backlight. Typically I guess that would be the duration of one frame (or perhaps 2, depending on when the panel syncs to the video signal).
We need not "determine", its already present in LVDS datasheet. The LVDS datasheet says at least 200ms delay is needed from "Valid data" to "BL on".
Perhaps it could even by sync'ed to the VBLANK?
No. vblanks are related to crtc. And the bridge/panel driver should be independent of vblank.
The problem is that the above scenario cannot be mapped to panel-simple driver. IMO, panel_simple should provide enable/disable controls both for LCD and backlight. something like panel_simple_lcd_enable/panel_simple_led_enable, and panel_simple_lcd_disable/panel_simple_led_disable.
That's not what the simple panel driver can do. If we want this it needs to be solved in a generic way for all panels since they all need to use the drm_panel_*() functions to abstract away the details from drivers that use the panels.
Right. So only I have added pre_enable and post_disable callbacks. Using that name won't harm existing panel drivers and still addresses our requirement.
Regards, Ajay
Thierry : Are you really ok with the new callback names? like pre_enable and post_disable? Adding those new callbacks really won't harm the existing panels anyhow.
Daniel, Rob : I think I have given sufficient amount of information as to why the concept of drm_panel_bridge fails and why we cannot have callbacks for drm_panel inside crtc helpers or any other generic place.
Please let me know your final opinion so that I can start reworking on this series.
Thanks and regards, Ajay Kumar
Daniel,
On Wed, Apr 23, 2014 at 12:59 PM, Daniel Vetter daniel@ffwll.ch wrote:
On Tue, Apr 22, 2014 at 08:06:19PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 1:49 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:11AM +0530, Ajay Kumar wrote:
Most of the panels need an init sequence as mentioned below: -- poweron LCD unit/LCD_EN -- start video data -- poweron LED unit/BL_EN And, a de-init sequence as mentioned below: -- poweroff LED unit/BL_EN -- stop video data -- poweroff LCD unit/LCD_EN With existing callbacks for drm panel, we cannot accomodate such panels, since only two callbacks, i.e "panel_enable" and panel_disable are
supported.
This patch adds: -- "pre_enable" callback which can be called before the actual video data is on, and then call the "enable" callback after the video data is available.
-- "post_disable" callback which can be called after the video data is off, and use "disable" callback to do something before switching off the video data.
Now, we can easily map the above scenario as shown below: poweron LCD unit/LCD_EN = "pre_enable" callback poweron LED unit/BL_EN = "enable" callback poweroff LED unit/BL_EN = "disable" callback poweroff LCD unit/LCD_EN = "post_disable" callback
I don't like this. What happens when the next panel comes around that has a yet slightly different requirement? Will we introduce a new pre_pre_enable() and post_post_disable() function then?
As I have already explained, these 2 callbacks are sufficient enough to take care the power up/down sequence for LVDS and eDP panels. And, definitely having just 2 callbacks "enable" and "disable" is not at all sufficient.
I initially thought of using panel_simple_enable from panel-simple driver. But it doesn't go well with case wherein there are 2 regulators(one for LCD and one for LED) a BL_EN signal etc. And, often(Believe me, I have referred to both eDP panel datasheets and LVDS panel datasheets) proper powerup sequence for such panels is as mentioned below:
powerup: -- power up the supply (LCD_VCC). -- start video data. -- enable backlight.
powerdown -- disable backlight. -- stop video data. -- power off the supply (LCD VCC)
For the above cases, if I have to somehow fit in all the required settings, it breaks the sequence and I end up getting glitches during bootup/DPMS.
Also, when the drm_bridge can support pre_enable and post_disable, why not drm_panel provide that both should work in tandem?
Imo this makes sense, especially if we go through with the idea talked about yesterday of creating a drm_bridge to wrap-up a drm_panel with sufficient isolation between all components. -Daniel
Actually, I tried implementing this for ptn3460. But, it breaks the working. As explained in the other patch(reply for Rob), we cannot truly ISOLATE the panel controls from bridge controls and keep them as seperate. They should be kept together, in the individual bridge chip drivers, so that the bridge chip driver can decide which panel control to call at what point.
So, I think combining bridge chip and panel controls was really a bad idea. I will implement the basic panel controls required by the bridge as optional bridge properties via DT. By that way, at least the driver remains robust.
Thanks and regards, Ajay Kumar
This patch adds a simple driver to handle all the LCD and LED powerup/down routines needed to support eDP/eDP-LVDS panels supported on exynos boards.
The LCD and LED units are usually powered up via regulators, and almost on all boards, we will have a BL_EN pin to enable/ disable the backlight. Sometimes, we can have LCD_EN switches as well. The routines in this driver can be used to control panel power sequence on such boards.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: Added routine for post_disable, removed few unwanted headers. Refactored a lot of code.
.../devicetree/bindings/panel/exynos-dp-panel.txt | 45 ++++ drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-exynos-dp.c | 251 ++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/exynos-dp-panel.txt create mode 100644 drivers/gpu/drm/panel/panel-exynos-dp.c
diff --git a/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt b/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt new file mode 100644 index 0000000..3fbca54 --- /dev/null +++ b/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt @@ -0,0 +1,45 @@ +exynos_DP_panel/DP_to_LVDS_panel + +Required properties: + - compatible: "samsung,exynos-dp-panel" + +Optional properties: + -samsung,lcd-en-gpio: + eDP panel LCD poweron GPIO. + Indicates which GPIO needs to be powered up as output + to powerup/enable the switch to the LCD panel. + -samsung,led-en-gpio: + eDP panel LED enable GPIO. + Indicates which GPIO needs to be powered up as output + to enable the backlight. + -samsung,panel-pre-enable-delay: + delay value in ms required for panel_pre_enable process + Delay in ms needed for the eDP panel LCD unit to + powerup, and delay needed between panel_VCC and + video_enable. + -samsung,panel-enable-delay: + delay value in ms required for panel_enable process + Delay in ms needed for the eDP panel backlight/LED unit + to powerup, and delay needed between video_enable and + BL_EN. + samsung,panel-disable-delay: + delay value in ms required for panel_disable process + Delay in ms needed for the eDP panel backlight/LED unit + powerdown, and delay needed between BL_DISABLE and + video_disable. + samsung,panel-post-disable-delay: + delay value in ms required for panel_post_disable process + Delay in ms needed for the eDP panel LCD unit to + to powerdown, and delay between video_disable and + panel_VCC going down. + +Example: + + dp-panel { + compatible = "samsung,exynos-dp-panel"; + samsung,led-en-gpio = <&gpx3 0 1>; + samsung,panel-pre-enable-delay = <40>; + samsung,panel-enable-delay = <20>; + samsung,panel-disable-delay = <20>; + samsung,panel-post-disable-delay = <30>; + }; diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 4ec874d..ea9d5ac 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -30,4 +30,13 @@ config DRM_PANEL_S6E8AA0 select DRM_MIPI_DSI select VIDEOMODE_HELPERS
+config DRM_PANEL_EXYNOS_DP + tristate "support for DP panels" + depends on OF && DRM_PANEL && DRM_EXYNOS_DP + help + DRM panel driver for DP panels and LVDS connected via DP bridges + that need at most a regulator for LCD unit, a regulator for LED unit + and/or enable GPIOs for LCD or LED units. Delay values can also be + specified to support powerup and powerdown process. + endmenu diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 8b92921..30311a4 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o obj-$(CONFIG_DRM_PANEL_LD9040) += panel-ld9040.o obj-$(CONFIG_DRM_PANEL_S6E8AA0) += panel-s6e8aa0.o +obj-$(CONFIG_DRM_PANEL_EXYNOS_DP) += panel-exynos-dp.o diff --git a/drivers/gpu/drm/panel/panel-exynos-dp.c b/drivers/gpu/drm/panel/panel-exynos-dp.c new file mode 100644 index 0000000..5568d6a --- /dev/null +++ b/drivers/gpu/drm/panel/panel-exynos-dp.c @@ -0,0 +1,251 @@ +/* + * Exynos DP panel driver + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * + * Ajay Kumar ajaykumar.rs@samsung.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_panel.h> + +enum panel_state { + PRE_ENABLE, + ENABLE, + DISABLE, + POST_DISABLE, +}; + +struct panel_exynos_dp { + struct drm_panel base; + struct regulator *backlight_fet; + struct regulator *lcd_fet; + int led_en_gpio; + int lcd_en_gpio; + int panel_pre_enable_delay; + int panel_enable_delay; + int panel_disable_delay; + int panel_post_disable_delay; + enum panel_state panel_state; +}; + +static inline struct panel_exynos_dp *to_panel(struct drm_panel *panel) +{ + return container_of(panel, struct panel_exynos_dp, base); +} + +static int panel_exynos_dp_post_disable(struct drm_panel *panel) +{ + struct panel_exynos_dp *dp_panel = to_panel(panel); + struct device *dev = dp_panel->base.dev; + + if (dp_panel->panel_state != DISABLE) { + dev_err(dev, "invoking panel_exynos_dp_post_disable" \ + "from an improper state\n"); + return -EINVAL; + } + + if (gpio_is_valid(dp_panel->lcd_en_gpio)) + gpio_set_value(dp_panel->lcd_en_gpio, 0); + + if (!IS_ERR_OR_NULL(dp_panel->lcd_fet)) + regulator_disable(dp_panel->lcd_fet); + + msleep(dp_panel->panel_post_disable_delay); + + dp_panel->panel_state = POST_DISABLE; + + return 0; +} + +static int panel_exynos_dp_disable(struct drm_panel *panel) +{ + struct panel_exynos_dp *dp_panel = to_panel(panel); + struct device *dev = dp_panel->base.dev; + + if (dp_panel->panel_state != ENABLE) { + dev_err(dev, "invoking panel_exynos_dp_disable" \ + "from an improper state\n"); + return -EINVAL; + } + + if (gpio_is_valid(dp_panel->led_en_gpio)) + gpio_set_value(dp_panel->led_en_gpio, 0); + + if (!IS_ERR_OR_NULL(dp_panel->backlight_fet)) + regulator_disable(dp_panel->backlight_fet); + + msleep(dp_panel->panel_disable_delay); + + dp_panel->panel_state = DISABLE; + + return 0; +} + +static int panel_exynos_dp_pre_enable(struct drm_panel *panel) +{ + struct panel_exynos_dp *dp_panel = to_panel(panel); + struct device *dev = dp_panel->base.dev; + + if (dp_panel->panel_state != POST_DISABLE) { + dev_err(dev, "invoking panel_exynos_dp_pre_enable" \ + "from an improper state\n"); + return -EINVAL; + } + + if (!IS_ERR_OR_NULL(dp_panel->lcd_fet)) + if (regulator_enable(dp_panel->lcd_fet)) + DRM_ERROR("Failed to enable LCD fet\n"); + + if (gpio_is_valid(dp_panel->lcd_en_gpio)) + gpio_set_value(dp_panel->lcd_en_gpio, 1); + + msleep(dp_panel->panel_pre_enable_delay); + + dp_panel->panel_state = PRE_ENABLE; + + return 0; +} + +static int panel_exynos_dp_enable(struct drm_panel *panel) +{ + struct panel_exynos_dp *dp_panel = to_panel(panel); + struct device *dev = dp_panel->base.dev; + + if (dp_panel->panel_state != PRE_ENABLE) { + dev_err(dev, "invoking panel_exynos_dp_enable" \ + "from an improper state\n"); + return -EINVAL; + } + + if (!IS_ERR_OR_NULL(dp_panel->backlight_fet)) + if (regulator_enable(dp_panel->backlight_fet)) + DRM_ERROR("Failed to enable LED fet\n"); + + msleep(dp_panel->panel_enable_delay); + + if (gpio_is_valid(dp_panel->led_en_gpio)) + gpio_set_value(dp_panel->led_en_gpio, 1); + + dp_panel->panel_state = ENABLE; + + return 0; +} + +static const struct drm_panel_funcs panel_exynos_dp_funcs = { + .post_disable = panel_exynos_dp_post_disable, + .disable = panel_exynos_dp_disable, + .pre_enable = panel_exynos_dp_pre_enable, + .enable = panel_exynos_dp_enable, +}; + +static int panel_exynos_dp_probe(struct platform_device *pdev) +{ + struct panel_exynos_dp *dp_panel; + struct device *dev = &pdev->dev; + int ret; + + dp_panel = devm_kzalloc(dev, sizeof(*dp_panel), GFP_KERNEL); + if (!dp_panel) + return -ENOMEM; + + dp_panel->panel_state = POST_DISABLE; + + dp_panel->lcd_en_gpio = of_get_named_gpio(dev->of_node, + "samsung,lcd-en-gpio", 0); + dp_panel->led_en_gpio = of_get_named_gpio(dev->of_node, + "samsung,led-en-gpio", 0); + + of_property_read_u32(dev->of_node, "samsung,panel-pre-enable-delay", + &dp_panel->panel_pre_enable_delay); + of_property_read_u32(dev->of_node, "samsung,panel-enable-delay", + &dp_panel->panel_enable_delay); + of_property_read_u32(dev->of_node, "samsung,panel-disable-delay", + &dp_panel->panel_disable_delay); + of_property_read_u32(dev->of_node, "samsung,panel-post-disable-delay", + &dp_panel->panel_post_disable_delay); + + dp_panel->lcd_fet = devm_regulator_get(dev, "lcd_vdd"); + if (IS_ERR(dp_panel->lcd_fet)) + return PTR_ERR(dp_panel->lcd_fet); + + dp_panel->backlight_fet = devm_regulator_get(dev, "vcd_led"); + if (IS_ERR(dp_panel->backlight_fet)) + return PTR_ERR(dp_panel->backlight_fet); + + if (gpio_is_valid(dp_panel->lcd_en_gpio)) { + ret = devm_gpio_request_one(dev, dp_panel->lcd_en_gpio, + GPIOF_OUT_INIT_LOW, "lcd_en_gpio"); + if (ret) { + DRM_ERROR("failed to get lcd-en gpio [%d]\n", ret); + return ret; + } + } else { + dp_panel->lcd_en_gpio = -ENODEV; + } + + if (gpio_is_valid(dp_panel->led_en_gpio)) { + ret = devm_gpio_request_one(dev, dp_panel->led_en_gpio, + GPIOF_OUT_INIT_LOW, "led_en_gpio"); + if (ret) { + DRM_ERROR("failed to get led-en gpio [%d]\n", ret); + return ret; + } + } else { + dp_panel->led_en_gpio = -ENODEV; + } + + dp_panel->panel_state = POST_DISABLE; + drm_panel_init(&dp_panel->base); + dp_panel->base.dev = dev; + dp_panel->base.funcs = &panel_exynos_dp_funcs; + + ret = drm_panel_add(&dp_panel->base); + if (ret < 0) + return ret; + + dev_set_drvdata(dev, dp_panel); + + return 0; +} + +static int panel_exynos_dp_remove(struct platform_device *pdev) +{ + struct panel_exynos_dp *dp_panel = dev_get_drvdata(&pdev->dev); + + dp_panel->panel_state = ENABLE; + panel_exynos_dp_disable(&dp_panel->base); + panel_exynos_dp_post_disable(&dp_panel->base); + + drm_panel_detach(&dp_panel->base); + drm_panel_remove(&dp_panel->base); + + return 0; +} + +static const struct of_device_id exynos_dp_panel_dt_match[] = { + { .compatible = "samsung,exynos-dp-panel" }, + {}, +}; + +struct platform_driver exynos_dp_panel_driver = { + .driver = { + .name = "exynos-dp-panel", + .owner = THIS_MODULE, + .of_match_table = exynos_dp_panel_dt_match, + }, + .probe = panel_exynos_dp_probe, + .remove = panel_exynos_dp_remove, +};
On Tuesday, April 22, 2014 7:39 AM, Ajay Kumar wrote:
This patch adds a simple driver to handle all the LCD and LED powerup/down routines needed to support eDP/eDP-LVDS panels supported on exynos boards.
The LCD and LED units are usually powered up via regulators, and almost on all boards, we will have a BL_EN pin to enable/ disable the backlight. Sometimes, we can have LCD_EN switches as well. The routines in this driver can be used to control panel power sequence on such boards.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added routine for post_disable, removed few unwanted headers. Refactored a lot of code.
.../devicetree/bindings/panel/exynos-dp-panel.txt | 45 ++++ drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-exynos-dp.c | 251 ++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/exynos-dp-panel.txt create mode 100644 drivers/gpu/drm/panel/panel-exynos-dp.c
diff --git a/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt b/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt new file mode 100644 index 0000000..3fbca54 --- /dev/null +++ b/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt @@ -0,0 +1,45 @@ +exynos_DP_panel/DP_to_LVDS_panel
Please fix it as below.
+Exynos DP panel/DP to LVDS panel
[....]
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 4ec874d..ea9d5ac 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -30,4 +30,13 @@ config DRM_PANEL_S6E8AA0 select DRM_MIPI_DSI select VIDEOMODE_HELPERS
+config DRM_PANEL_EXYNOS_DP
- tristate "support for DP panels"
It looks very general. Please fix it as below.
+ tristate "support for Exynos DP panels"
Best regards, Jingoo Han
- depends on OF && DRM_PANEL && DRM_EXYNOS_DP
- help
DRM panel driver for DP panels and LVDS connected via DP bridges
that need at most a regulator for LCD unit, a regulator for LED unit
and/or enable GPIOs for LCD or LED units. Delay values can also be
specified to support powerup and powerdown process.
[.....]
Hi Jingoo,
On Tue, Apr 22, 2014 at 12:52 PM, Jingoo Han jg1.han@samsung.com wrote:
On Tuesday, April 22, 2014 7:39 AM, Ajay Kumar wrote:
This patch adds a simple driver to handle all the LCD and LED powerup/down routines needed to support eDP/eDP-LVDS panels supported on exynos boards.
The LCD and LED units are usually powered up via regulators, and almost on all boards, we will have a BL_EN pin to enable/ disable the backlight. Sometimes, we can have LCD_EN switches as well. The routines in this driver can be used to control panel power sequence on such boards.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added routine for post_disable, removed few unwanted headers. Refactored a lot of code.
.../devicetree/bindings/panel/exynos-dp-panel.txt | 45 ++++ drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-exynos-dp.c | 251 ++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/exynos-dp-panel.txt create mode 100644 drivers/gpu/drm/panel/panel-exynos-dp.c
diff --git a/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt b/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt new file mode 100644 index 0000000..3fbca54 --- /dev/null +++ b/Documentation/devicetree/bindings/panel/exynos-dp-panel.txt @@ -0,0 +1,45 @@ +exynos_DP_panel/DP_to_LVDS_panel
Please fix it as below.
+Exynos DP panel/DP to LVDS panel
Ok.
[....]
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 4ec874d..ea9d5ac 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -30,4 +30,13 @@ config DRM_PANEL_S6E8AA0 select DRM_MIPI_DSI select VIDEOMODE_HELPERS
+config DRM_PANEL_EXYNOS_DP
tristate "support for DP panels"
It looks very general. Please fix it as below.
tristate "support for Exynos DP panels"
Ok.
Best regards, Jingoo Han
depends on OF && DRM_PANEL && DRM_EXYNOS_DP
help
DRM panel driver for DP panels and LVDS connected via DP bridges
that need at most a regulator for LCD unit, a regulator for LED unit
and/or enable GPIOs for LCD or LED units. Delay values can also be
specified to support powerup and powerdown process.
[.....]
Thanks and Regards, Ajay Kumar
On Tue, Apr 22, 2014 at 04:09:12AM +0530, Ajay Kumar wrote:
This patch adds a simple driver to handle all the LCD and LED powerup/down routines needed to support eDP/eDP-LVDS panels supported on exynos boards.
The LCD and LED units are usually powered up via regulators, and almost on all boards, we will have a BL_EN pin to enable/ disable the backlight. Sometimes, we can have LCD_EN switches as well. The routines in this driver can be used to control panel power sequence on such boards.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added routine for post_disable, removed few unwanted headers. Refactored a lot of code.
.../devicetree/bindings/panel/exynos-dp-panel.txt | 45 ++++ drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-exynos-dp.c | 251 ++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/exynos-dp-panel.txt create mode 100644 drivers/gpu/drm/panel/panel-exynos-dp.c
What this patch does is in no way Exynos specific. It is also not eDP specific.
This conflates panel drivers with board drivers in a strange way. Panel drivers should be for *panels*, not for a given SoC or specific outputs on that SoC.
Thierry
Hi Thierry,
On Tue, Apr 22, 2014 at 1:56 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:12AM +0530, Ajay Kumar wrote:
This patch adds a simple driver to handle all the LCD and LED powerup/down routines needed to support eDP/eDP-LVDS panels supported on exynos boards.
The LCD and LED units are usually powered up via regulators, and almost on all boards, we will have a BL_EN pin to enable/ disable the backlight. Sometimes, we can have LCD_EN switches as well. The routines in this driver can be used to control panel power sequence on such boards.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added routine for post_disable, removed few unwanted headers. Refactored a lot of code.
.../devicetree/bindings/panel/exynos-dp-panel.txt | 45 ++++ drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-exynos-dp.c | 251 ++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/exynos-dp-panel.txt create mode 100644 drivers/gpu/drm/panel/panel-exynos-dp.c
What this patch does is in no way Exynos specific. It is also not eDP specific.
Right. This is not at all writing into any "exynos" specific register, but can't this be just a placeholder for all the panel controls which can serve boards which use exynos_dp?
This conflates panel drivers with board drivers in a strange way. Panel drivers should be for *panels*, not for a given SoC or specific outputs on that SoC.
Right. But for that matter, even the "panel-simple" driver is doing the same, which is just a placeholder for "generic" panel controls which serves multiple boards. I din't choose to reuse that because the boards which I have expect more control over the panel sequence as mentioned before. If you don't mind, I can fit in all these settings into panel-simple driver itself ;) But again, I should be able to register the panel driver much before exynos_dp gets probed, That's the actual catch!
Thanks and regards, Ajay Kumar
On Tue, Apr 22, 2014 at 08:23:03PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 1:56 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:12AM +0530, Ajay Kumar wrote:
This patch adds a simple driver to handle all the LCD and LED powerup/down routines needed to support eDP/eDP-LVDS panels supported on exynos boards.
The LCD and LED units are usually powered up via regulators, and almost on all boards, we will have a BL_EN pin to enable/ disable the backlight. Sometimes, we can have LCD_EN switches as well. The routines in this driver can be used to control panel power sequence on such boards.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added routine for post_disable, removed few unwanted headers. Refactored a lot of code.
.../devicetree/bindings/panel/exynos-dp-panel.txt | 45 ++++ drivers/gpu/drm/panel/Kconfig | 9 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-exynos-dp.c | 251 ++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/exynos-dp-panel.txt create mode 100644 drivers/gpu/drm/panel/panel-exynos-dp.c
What this patch does is in no way Exynos specific. It is also not eDP specific.
Right. This is not at all writing into any "exynos" specific register, but can't this be just a placeholder for all the panel controls which can serve boards which use exynos_dp?
No, it shouldn't. Like I said, if I have a panel that happens to be used on an Exynos board but I use it on a different board, then I don't want to have to know that the panel might be supported by Exynos so that I know which driver to use.
So ideally there should be one driver per panel. panel-simple was introduced because of the five panels that I had access to at the time, five panels could be supported using the same code.
This conflates panel drivers with board drivers in a strange way. Panel drivers should be for *panels*, not for a given SoC or specific outputs on that SoC.
Right. But for that matter, even the "panel-simple" driver is doing the same, which is just a placeholder for "generic" panel controls which serves multiple boards.
panel-simple is meant for panels that require only the parameters that are specified for those. Anything that needs more is by definition no longer "simple".
And the difference here is that panel-simple is a true wildcard, but it isn't specific to one SoC. And the name doesn't imply that either. Also each panel is still identified by the specific compatible value, which makes it easier to find out which driver supports the panel.
Thierry
Register exynos_dp_panel before the list of exynos crtcs and connectors are probed.
This is needed because exynos_dp_panel should be registered to the drm_panel list via panel-exynos-dp probe, i.e much before exynos_dp_bind calls of_drm_find_panel().
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: Added platform_driver_unregister(&exynos_dp_panel_driver) to exynos_drm_platform_remove as per Jingoo Han's correction
drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + 2 files changed, 16 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 1d653f8..2db7f67 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) goto err_unregister_ipp_drv; #endif
+#ifdef CONFIG_DRM_PANEL_EXYNOS_DP + ret = platform_driver_register(&exynos_dp_panel_driver); + if (ret < 0) + goto err_unregister_dp_panel; +#endif + ret = component_master_add(&pdev->dev, &exynos_drm_ops); if (ret < 0) DRM_DEBUG_KMS("re-tried by last sub driver probed later.\n");
return 0;
+#ifdef CONFIG_DRM_PANEL_EXYNOS_DP + platform_driver_unregister(&exynos_dp_panel_driver); +err_unregister_dp_panel: +#endif + #ifdef CONFIG_DRM_EXYNOS_IPP err_unregister_ipp_drv: platform_driver_unregister(&ipp_driver); @@ -587,6 +598,10 @@ err_unregister_fimd_drv:
static int exynos_drm_platform_remove(struct platform_device *pdev) { +#ifdef CONFIG_DRM_PANEL_EXYNOS_DP + platform_driver_unregister(&exynos_dp_panel_driver); +#endif + #ifdef CONFIG_DRM_EXYNOS_IPP exynos_platform_device_ipp_unregister(); platform_driver_unregister(&ipp_driver); diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index fc15fe6..b33050d 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -368,4 +368,5 @@ extern struct platform_driver fimc_driver; extern struct platform_driver rotator_driver; extern struct platform_driver gsc_driver; extern struct platform_driver ipp_driver; +extern struct platform_driver exynos_dp_panel_driver; #endif
On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote:
Register exynos_dp_panel before the list of exynos crtcs and connectors are probed.
This is needed because exynos_dp_panel should be registered to the drm_panel list via panel-exynos-dp probe, i.e much before exynos_dp_bind calls of_drm_find_panel().
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added platform_driver_unregister(&exynos_dp_panel_driver) to exynos_drm_platform_remove as per Jingoo Han's correction
drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + 2 files changed, 16 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 1d653f8..2db7f67 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) goto err_unregister_ipp_drv; #endif
+#ifdef CONFIG_DRM_PANEL_EXYNOS_DP
- ret = platform_driver_register(&exynos_dp_panel_driver);
- if (ret < 0)
goto err_unregister_dp_panel;
+#endif
No, this is not how you're supposed to use DRM panel drivers. The idea is that you write a standalone driver for a given panel.
What you do here has a number of problems. For one it's a driver that's tightly coupled to Exynos SoCs. But if I have a different SoC that uses the same panel I want to be able to use the same driver, and not have to rewrite the driver for my SoC.
Another problem is that you're assuming here that the driver is built in and it will break if you try to build either Exynos DRM or the panel driver as a module. This is perhaps nothing you care about right now, but eventually people will want to ship a single kernel that can run on a number of SoCs. But if we keep adding things like this, that kernel will keep growing in size until it no longer fits in any kind of memory.
Thierry
Hi Thierry,
On Tue, Apr 22, 2014 at 2:03 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote:
Register exynos_dp_panel before the list of exynos crtcs and connectors are probed.
This is needed because exynos_dp_panel should be registered to the drm_panel list via panel-exynos-dp probe, i.e much before exynos_dp_bind calls of_drm_find_panel().
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added platform_driver_unregister(&exynos_dp_panel_driver) to exynos_drm_platform_remove as per Jingoo Han's correction
drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + 2 files changed, 16 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 1d653f8..2db7f67 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) goto err_unregister_ipp_drv; #endif
+#ifdef CONFIG_DRM_PANEL_EXYNOS_DP
ret = platform_driver_register(&exynos_dp_panel_driver);
if (ret < 0)
goto err_unregister_dp_panel;
+#endif
No, this is not how you're supposed to use DRM panel drivers. The idea is that you write a standalone driver for a given panel.
What you do here has a number of problems. For one it's a driver that's tightly coupled to Exynos SoCs. But if I have a different SoC that uses the same panel I want to be able to use the same driver, and not have to rewrite the driver for my SoC.
Another problem is that you're assuming here that the driver is built in and it will break if you try to build either Exynos DRM or the panel driver as a module. This is perhaps nothing you care about right now, but eventually people will want to ship a single kernel that can run on a number of SoCs. But if we keep adding things like this, that kernel will keep growing in size until it no longer fits in any kind of memory.
Thierry
I completely agree with you in this!
Yes, this is not acceptable, but I want to know an "acceptable" workaround for the situation below: I register the driver using module_init(). And, exynos_drm gets probed much before the panel driver probe happens. So, the panel driver hasn't probed yet, but exynos_dp via exynos_drm tries to call "of_drm_find_panel" which always returns NULL.
Thanks and regards, Ajay Kumar
On Tue, Apr 22, 2014 at 08:33:23PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 2:03 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote:
Register exynos_dp_panel before the list of exynos crtcs and connectors are probed.
This is needed because exynos_dp_panel should be registered to the drm_panel list via panel-exynos-dp probe, i.e much before exynos_dp_bind calls of_drm_find_panel().
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added platform_driver_unregister(&exynos_dp_panel_driver) to exynos_drm_platform_remove as per Jingoo Han's correction
drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + 2 files changed, 16 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 1d653f8..2db7f67 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) goto err_unregister_ipp_drv; #endif
+#ifdef CONFIG_DRM_PANEL_EXYNOS_DP
ret = platform_driver_register(&exynos_dp_panel_driver);
if (ret < 0)
goto err_unregister_dp_panel;
+#endif
No, this is not how you're supposed to use DRM panel drivers. The idea is that you write a standalone driver for a given panel.
What you do here has a number of problems. For one it's a driver that's tightly coupled to Exynos SoCs. But if I have a different SoC that uses the same panel I want to be able to use the same driver, and not have to rewrite the driver for my SoC.
Another problem is that you're assuming here that the driver is built in and it will break if you try to build either Exynos DRM or the panel driver as a module. This is perhaps nothing you care about right now, but eventually people will want to ship a single kernel that can run on a number of SoCs. But if we keep adding things like this, that kernel will keep growing in size until it no longer fits in any kind of memory.
Thierry
I completely agree with you in this!
Yes, this is not acceptable, but I want to know an "acceptable" workaround for the situation below: I register the driver using module_init(). And, exynos_drm gets probed much before the panel driver probe happens. So, the panel driver hasn't probed yet, but exynos_dp via exynos_drm tries to call "of_drm_find_panel" which always returns NULL.
That's a situation that your driver needs to be able to deal with. The driver registration order doesn't matter one bit. It may happen to work most of the time, but as soon as one of the resources that your panel driver needs isn't there when the panel is probed, then it won't be registered and of_drm_find_panel() will still return NULL.
Usually the right thing to do in that case would be to return (and propagate) -EPROBE_DEFER so that your driver's probe is deferred and retried when other drivers have been probed. That way it should eventually get a non-NULL panel.
Thierry
On Tue, Apr 22, 2014 at 8:26 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 08:33:23PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 2:03 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote:
Register exynos_dp_panel before the list of exynos crtcs and connectors are probed.
This is needed because exynos_dp_panel should be registered to the drm_panel list via panel-exynos-dp probe, i.e much before exynos_dp_bind calls of_drm_find_panel().
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added platform_driver_unregister(&exynos_dp_panel_driver) to exynos_drm_platform_remove as per Jingoo Han's correction
drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + 2 files changed, 16 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 1d653f8..2db7f67 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) goto err_unregister_ipp_drv; #endif
+#ifdef CONFIG_DRM_PANEL_EXYNOS_DP
ret = platform_driver_register(&exynos_dp_panel_driver);
if (ret < 0)
goto err_unregister_dp_panel;
+#endif
No, this is not how you're supposed to use DRM panel drivers. The idea is that you write a standalone driver for a given panel.
What you do here has a number of problems. For one it's a driver that's tightly coupled to Exynos SoCs. But if I have a different SoC that uses the same panel I want to be able to use the same driver, and not have to rewrite the driver for my SoC.
Another problem is that you're assuming here that the driver is built in and it will break if you try to build either Exynos DRM or the panel driver as a module. This is perhaps nothing you care about right now, but eventually people will want to ship a single kernel that can run on a number of SoCs. But if we keep adding things like this, that kernel will keep growing in size until it no longer fits in any kind of memory.
Thierry
I completely agree with you in this!
Yes, this is not acceptable, but I want to know an "acceptable" workaround for the situation below: I register the driver using module_init(). And, exynos_drm gets probed much before the panel driver probe happens. So, the panel driver hasn't probed yet, but exynos_dp via exynos_drm tries to call "of_drm_find_panel" which always returns NULL.
That's a situation that your driver needs to be able to deal with. The driver registration order doesn't matter one bit. It may happen to work most of the time, but as soon as one of the resources that your panel driver needs isn't there when the panel is probed, then it won't be registered and of_drm_find_panel() will still return NULL.
Usually the right thing to do in that case would be to return (and propagate) -EPROBE_DEFER so that your driver's probe is deferred and retried when other drivers have been probed. That way it should eventually get a non-NULL panel.
So I just gave this (drm_panel + probe deferring) a shot on exynos, and correctly reacting to -EPROBE_DEFER postpones DP initialization by approximately 1.5 second. Is there a good way to handle that? As it stands, this isn't usable.
Stéphane
On Tue, Aug 19, 2014 at 09:02:39PM -0700, Stéphane Marchesin wrote:
On Tue, Apr 22, 2014 at 8:26 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 08:33:23PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 2:03 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote:
Register exynos_dp_panel before the list of exynos crtcs and connectors are probed.
This is needed because exynos_dp_panel should be registered to the drm_panel list via panel-exynos-dp probe, i.e much before exynos_dp_bind calls of_drm_find_panel().
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added platform_driver_unregister(&exynos_dp_panel_driver) to exynos_drm_platform_remove as per Jingoo Han's correction
drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + 2 files changed, 16 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 1d653f8..2db7f67 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) goto err_unregister_ipp_drv; #endif
+#ifdef CONFIG_DRM_PANEL_EXYNOS_DP
ret = platform_driver_register(&exynos_dp_panel_driver);
if (ret < 0)
goto err_unregister_dp_panel;
+#endif
No, this is not how you're supposed to use DRM panel drivers. The idea is that you write a standalone driver for a given panel.
What you do here has a number of problems. For one it's a driver that's tightly coupled to Exynos SoCs. But if I have a different SoC that uses the same panel I want to be able to use the same driver, and not have to rewrite the driver for my SoC.
Another problem is that you're assuming here that the driver is built in and it will break if you try to build either Exynos DRM or the panel driver as a module. This is perhaps nothing you care about right now, but eventually people will want to ship a single kernel that can run on a number of SoCs. But if we keep adding things like this, that kernel will keep growing in size until it no longer fits in any kind of memory.
Thierry
I completely agree with you in this!
Yes, this is not acceptable, but I want to know an "acceptable" workaround for the situation below: I register the driver using module_init(). And, exynos_drm gets probed much before the panel driver probe happens. So, the panel driver hasn't probed yet, but exynos_dp via exynos_drm tries to call "of_drm_find_panel" which always returns NULL.
That's a situation that your driver needs to be able to deal with. The driver registration order doesn't matter one bit. It may happen to work most of the time, but as soon as one of the resources that your panel driver needs isn't there when the panel is probed, then it won't be registered and of_drm_find_panel() will still return NULL.
Usually the right thing to do in that case would be to return (and propagate) -EPROBE_DEFER so that your driver's probe is deferred and retried when other drivers have been probed. That way it should eventually get a non-NULL panel.
So I just gave this (drm_panel + probe deferring) a shot on exynos, and correctly reacting to -EPROBE_DEFER postpones DP initialization by approximately 1.5 second. Is there a good way to handle that? As it stands, this isn't usable.
How much is 1.5 seconds compared to the overall boot time of the device? What exactly is causing this 1.5 second delay?
This really is a fundamental issue with deferred probing and the issue has come up several times in the past. A couple of possible solutions have been proposed, with the latest being here[0] I think. That ended in a bit of a debacle, unfortunately, but on of the outcomes was that a lot of the ordering problems could be fixed by using phandle references to track dependencies. I'm not aware of anyone working on that right now, presumably because everyone is busy getting features merged rather than optimizing boot speed.
Thierry
On Thu, Aug 21, 2014 at 12:36 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Aug 19, 2014 at 09:02:39PM -0700, Stéphane Marchesin wrote:
On Tue, Apr 22, 2014 at 8:26 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 08:33:23PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 2:03 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote:
Register exynos_dp_panel before the list of exynos crtcs and connectors are probed.
This is needed because exynos_dp_panel should be registered to the drm_panel list via panel-exynos-dp probe, i.e much before exynos_dp_bind calls of_drm_find_panel().
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Added platform_driver_unregister(&exynos_dp_panel_driver) to exynos_drm_platform_remove as per Jingoo Han's correction
drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + 2 files changed, 16 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 1d653f8..2db7f67 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) goto err_unregister_ipp_drv; #endif
+#ifdef CONFIG_DRM_PANEL_EXYNOS_DP
ret = platform_driver_register(&exynos_dp_panel_driver);
if (ret < 0)
goto err_unregister_dp_panel;
+#endif
No, this is not how you're supposed to use DRM panel drivers. The idea is that you write a standalone driver for a given panel.
What you do here has a number of problems. For one it's a driver that's tightly coupled to Exynos SoCs. But if I have a different SoC that uses the same panel I want to be able to use the same driver, and not have to rewrite the driver for my SoC.
Another problem is that you're assuming here that the driver is built in and it will break if you try to build either Exynos DRM or the panel driver as a module. This is perhaps nothing you care about right now, but eventually people will want to ship a single kernel that can run on a number of SoCs. But if we keep adding things like this, that kernel will keep growing in size until it no longer fits in any kind of memory.
Thierry
I completely agree with you in this!
Yes, this is not acceptable, but I want to know an "acceptable" workaround for the situation below: I register the driver using module_init(). And, exynos_drm gets probed much before the panel driver probe happens. So, the panel driver hasn't probed yet, but exynos_dp via exynos_drm tries to call "of_drm_find_panel" which always returns NULL.
That's a situation that your driver needs to be able to deal with. The driver registration order doesn't matter one bit. It may happen to work most of the time, but as soon as one of the resources that your panel driver needs isn't there when the panel is probed, then it won't be registered and of_drm_find_panel() will still return NULL.
Usually the right thing to do in that case would be to return (and propagate) -EPROBE_DEFER so that your driver's probe is deferred and retried when other drivers have been probed. That way it should eventually get a non-NULL panel.
So I just gave this (drm_panel + probe deferring) a shot on exynos, and correctly reacting to -EPROBE_DEFER postpones DP initialization by approximately 1.5 second. Is there a good way to handle that? As it stands, this isn't usable.
How much is 1.5 seconds compared to the overall boot time of the device?
1.5s is 15-20% of my boot time (if you count the boot time from firmware start to login prompt, otherwise it's more). Note that on other platforms, we've seen this take as much as 5 or 6s, but for the exynos case it is "only" 1.5s.
What exactly is causing this 1.5 second delay?
A regulator isn't ready, and then drm_panel returns defer. Then the whole drm driver init is deferred.
This really is a fundamental issue with deferred probing and the issue has come up several times in the past. A couple of possible solutions have been proposed, with the latest being here[0] I think. That ended in a bit of a debacle, unfortunately, but on of the outcomes was that a lot of the ordering problems could be fixed by using phandle references to track dependencies. I'm not aware of anyone working on that right now, presumably because everyone is busy getting features merged rather than optimizing boot speed.
Yes, I don't believe boot time ordering will ever happen upstream, but then the current implementation with EPROBE_DEFER isn't usable either. Any ideas? ATM it seems like the only way out is to just write my own dt format for the panel and ignore drm_panel.
Stéphane
On Thu, Aug 21, 2014 at 1:55 PM, Stéphane Marchesin stephane.marchesin@gmail.com wrote:
On Thu, Aug 21, 2014 at 12:36 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Aug 19, 2014 at 09:02:39PM -0700, Stéphane Marchesin wrote:
On Tue, Apr 22, 2014 at 8:26 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 08:33:23PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 2:03 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote: > Register exynos_dp_panel before the list of exynos crtcs and > connectors are probed. > > This is needed because exynos_dp_panel should be registered to > the drm_panel list via panel-exynos-dp probe, i.e much before > exynos_dp_bind calls of_drm_find_panel(). > > Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com > --- > Changes since V1: > Added platform_driver_unregister(&exynos_dp_panel_driver) to > exynos_drm_platform_remove as per Jingoo Han's correction > > drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ > drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + > 2 files changed, 16 insertions(+) > > diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c > index 1d653f8..2db7f67 100644 > --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c > +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c > @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) > goto err_unregister_ipp_drv; > #endif > > +#ifdef CONFIG_DRM_PANEL_EXYNOS_DP > + ret = platform_driver_register(&exynos_dp_panel_driver); > + if (ret < 0) > + goto err_unregister_dp_panel; > +#endif
No, this is not how you're supposed to use DRM panel drivers. The idea is that you write a standalone driver for a given panel.
What you do here has a number of problems. For one it's a driver that's tightly coupled to Exynos SoCs. But if I have a different SoC that uses the same panel I want to be able to use the same driver, and not have to rewrite the driver for my SoC.
Another problem is that you're assuming here that the driver is built in and it will break if you try to build either Exynos DRM or the panel driver as a module. This is perhaps nothing you care about right now, but eventually people will want to ship a single kernel that can run on a number of SoCs. But if we keep adding things like this, that kernel will keep growing in size until it no longer fits in any kind of memory.
Thierry
I completely agree with you in this!
Yes, this is not acceptable, but I want to know an "acceptable" workaround for the situation below: I register the driver using module_init(). And, exynos_drm gets probed much before the panel driver probe happens. So, the panel driver hasn't probed yet, but exynos_dp via exynos_drm tries to call "of_drm_find_panel" which always returns NULL.
That's a situation that your driver needs to be able to deal with. The driver registration order doesn't matter one bit. It may happen to work most of the time, but as soon as one of the resources that your panel driver needs isn't there when the panel is probed, then it won't be registered and of_drm_find_panel() will still return NULL.
Usually the right thing to do in that case would be to return (and propagate) -EPROBE_DEFER so that your driver's probe is deferred and retried when other drivers have been probed. That way it should eventually get a non-NULL panel.
So I just gave this (drm_panel + probe deferring) a shot on exynos, and correctly reacting to -EPROBE_DEFER postpones DP initialization by approximately 1.5 second. Is there a good way to handle that? As it stands, this isn't usable.
How much is 1.5 seconds compared to the overall boot time of the device?
1.5s is 15-20% of my boot time (if you count the boot time from firmware start to login prompt, otherwise it's more). Note that on other platforms, we've seen this take as much as 5 or 6s, but for the exynos case it is "only" 1.5s.
What exactly is causing this 1.5 second delay?
A regulator isn't ready, and then drm_panel returns defer. Then the whole drm driver init is deferred.
This really is a fundamental issue with deferred probing and the issue has come up several times in the past. A couple of possible solutions have been proposed, with the latest being here[0] I think. That ended in a bit of a debacle, unfortunately, but on of the outcomes was that a lot of the ordering problems could be fixed by using phandle references to track dependencies. I'm not aware of anyone working on that right now, presumably because everyone is busy getting features merged rather than optimizing boot speed.
Yes, I don't believe boot time ordering will ever happen upstream, but then the current implementation with EPROBE_DEFER isn't usable either. Any ideas? ATM it seems like the only way out is to just write my own dt format for the panel and ignore drm_panel.
Something like this: https://chromium.googlesource.com/chromiumos/third_party/kernel/+/chromeos-3...
With this way also, we would expect the regulator to come up early(before drm).
Ajay
On Thu, Aug 21, 2014 at 02:57:21PM +0530, Ajay kumar wrote:
On Thu, Aug 21, 2014 at 1:55 PM, Stéphane Marchesin stephane.marchesin@gmail.com wrote:
On Thu, Aug 21, 2014 at 12:36 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Aug 19, 2014 at 09:02:39PM -0700, Stéphane Marchesin wrote:
On Tue, Apr 22, 2014 at 8:26 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 08:33:23PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 2:03 PM, Thierry Reding thierry.reding@gmail.com wrote: > On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote: >> Register exynos_dp_panel before the list of exynos crtcs and >> connectors are probed. >> >> This is needed because exynos_dp_panel should be registered to >> the drm_panel list via panel-exynos-dp probe, i.e much before >> exynos_dp_bind calls of_drm_find_panel(). >> >> Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com >> --- >> Changes since V1: >> Added platform_driver_unregister(&exynos_dp_panel_driver) to >> exynos_drm_platform_remove as per Jingoo Han's correction >> >> drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ >> drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + >> 2 files changed, 16 insertions(+) >> >> diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c >> index 1d653f8..2db7f67 100644 >> --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c >> +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c >> @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) >> goto err_unregister_ipp_drv; >> #endif >> >> +#ifdef CONFIG_DRM_PANEL_EXYNOS_DP >> + ret = platform_driver_register(&exynos_dp_panel_driver); >> + if (ret < 0) >> + goto err_unregister_dp_panel; >> +#endif > > No, this is not how you're supposed to use DRM panel drivers. The idea > is that you write a standalone driver for a given panel. > > What you do here has a number of problems. For one it's a driver that's > tightly coupled to Exynos SoCs. But if I have a different SoC that uses > the same panel I want to be able to use the same driver, and not have to > rewrite the driver for my SoC. > > Another problem is that you're assuming here that the driver is built in > and it will break if you try to build either Exynos DRM or the panel > driver as a module. This is perhaps nothing you care about right now, > but eventually people will want to ship a single kernel that can run on > a number of SoCs. But if we keep adding things like this, that kernel > will keep growing in size until it no longer fits in any kind of memory. > > Thierry
I completely agree with you in this!
Yes, this is not acceptable, but I want to know an "acceptable" workaround for the situation below: I register the driver using module_init(). And, exynos_drm gets probed much before the panel driver probe happens. So, the panel driver hasn't probed yet, but exynos_dp via exynos_drm tries to call "of_drm_find_panel" which always returns NULL.
That's a situation that your driver needs to be able to deal with. The driver registration order doesn't matter one bit. It may happen to work most of the time, but as soon as one of the resources that your panel driver needs isn't there when the panel is probed, then it won't be registered and of_drm_find_panel() will still return NULL.
Usually the right thing to do in that case would be to return (and propagate) -EPROBE_DEFER so that your driver's probe is deferred and retried when other drivers have been probed. That way it should eventually get a non-NULL panel.
So I just gave this (drm_panel + probe deferring) a shot on exynos, and correctly reacting to -EPROBE_DEFER postpones DP initialization by approximately 1.5 second. Is there a good way to handle that? As it stands, this isn't usable.
How much is 1.5 seconds compared to the overall boot time of the device?
1.5s is 15-20% of my boot time (if you count the boot time from firmware start to login prompt, otherwise it's more). Note that on other platforms, we've seen this take as much as 5 or 6s, but for the exynos case it is "only" 1.5s.
What exactly is causing this 1.5 second delay?
A regulator isn't ready, and then drm_panel returns defer. Then the whole drm driver init is deferred.
This really is a fundamental issue with deferred probing and the issue has come up several times in the past. A couple of possible solutions have been proposed, with the latest being here[0] I think. That ended in a bit of a debacle, unfortunately, but on of the outcomes was that a lot of the ordering problems could be fixed by using phandle references to track dependencies. I'm not aware of anyone working on that right now, presumably because everyone is busy getting features merged rather than optimizing boot speed.
Yes, I don't believe boot time ordering will ever happen upstream, but then the current implementation with EPROBE_DEFER isn't usable either. Any ideas? ATM it seems like the only way out is to just write my own dt format for the panel and ignore drm_panel.
Something like this: https://chromium.googlesource.com/chromiumos/third_party/kernel/+/chromeos-3...
With this way also, we would expect the regulator to come up early(before drm).
Well, that's pretty much what Stéphane proposed. You don't use DRM panel at all and instead rely on some "platform data" to get the information that you need. You're basically throwing everything we've been working towards with DT for the past three years out the door. Also as soon as you encounter the first device that's not covered by lcd_vdd, vcd_led regulators and lcd_en, led_en GPIOs you have a problem. There's also no reuse across SoCs whatsoever.
Really, we should be finding ways to fix problems, not work around them in every driver because the real fixes look too daunting at the moment.
Thierry
Adding Boris and Ludovic since this topic came up in a different thread as well.
On Thu, Aug 21, 2014 at 01:25:07AM -0700, Stéphane Marchesin wrote:
On Thu, Aug 21, 2014 at 12:36 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Aug 19, 2014 at 09:02:39PM -0700, Stéphane Marchesin wrote:
On Tue, Apr 22, 2014 at 8:26 AM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 08:33:23PM +0530, Ajay kumar wrote:
Hi Thierry,
On Tue, Apr 22, 2014 at 2:03 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:13AM +0530, Ajay Kumar wrote: > Register exynos_dp_panel before the list of exynos crtcs and > connectors are probed. > > This is needed because exynos_dp_panel should be registered to > the drm_panel list via panel-exynos-dp probe, i.e much before > exynos_dp_bind calls of_drm_find_panel(). > > Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com > --- > Changes since V1: > Added platform_driver_unregister(&exynos_dp_panel_driver) to > exynos_drm_platform_remove as per Jingoo Han's correction > > drivers/gpu/drm/exynos/exynos_drm_drv.c | 15 +++++++++++++++ > drivers/gpu/drm/exynos/exynos_drm_drv.h | 1 + > 2 files changed, 16 insertions(+) > > diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c > index 1d653f8..2db7f67 100644 > --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c > +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c > @@ -530,12 +530,23 @@ static int exynos_drm_platform_probe(struct platform_device *pdev) > goto err_unregister_ipp_drv; > #endif > > +#ifdef CONFIG_DRM_PANEL_EXYNOS_DP > + ret = platform_driver_register(&exynos_dp_panel_driver); > + if (ret < 0) > + goto err_unregister_dp_panel; > +#endif
No, this is not how you're supposed to use DRM panel drivers. The idea is that you write a standalone driver for a given panel.
What you do here has a number of problems. For one it's a driver that's tightly coupled to Exynos SoCs. But if I have a different SoC that uses the same panel I want to be able to use the same driver, and not have to rewrite the driver for my SoC.
Another problem is that you're assuming here that the driver is built in and it will break if you try to build either Exynos DRM or the panel driver as a module. This is perhaps nothing you care about right now, but eventually people will want to ship a single kernel that can run on a number of SoCs. But if we keep adding things like this, that kernel will keep growing in size until it no longer fits in any kind of memory.
Thierry
I completely agree with you in this!
Yes, this is not acceptable, but I want to know an "acceptable" workaround for the situation below: I register the driver using module_init(). And, exynos_drm gets probed much before the panel driver probe happens. So, the panel driver hasn't probed yet, but exynos_dp via exynos_drm tries to call "of_drm_find_panel" which always returns NULL.
That's a situation that your driver needs to be able to deal with. The driver registration order doesn't matter one bit. It may happen to work most of the time, but as soon as one of the resources that your panel driver needs isn't there when the panel is probed, then it won't be registered and of_drm_find_panel() will still return NULL.
Usually the right thing to do in that case would be to return (and propagate) -EPROBE_DEFER so that your driver's probe is deferred and retried when other drivers have been probed. That way it should eventually get a non-NULL panel.
So I just gave this (drm_panel + probe deferring) a shot on exynos, and correctly reacting to -EPROBE_DEFER postpones DP initialization by approximately 1.5 second. Is there a good way to handle that? As it stands, this isn't usable.
How much is 1.5 seconds compared to the overall boot time of the device?
1.5s is 15-20% of my boot time (if you count the boot time from firmware start to login prompt, otherwise it's more). Note that on other platforms, we've seen this take as much as 5 or 6s, but for the exynos case it is "only" 1.5s.
What exactly is causing this 1.5 second delay?
A regulator isn't ready, and then drm_panel returns defer. Then the whole drm driver init is deferred.
That is the correct way to do this currently. It's unfortunate that it causes such long delays.
This really is a fundamental issue with deferred probing and the issue has come up several times in the past. A couple of possible solutions have been proposed, with the latest being here[0] I think. That ended in a bit of a debacle, unfortunately, but on of the outcomes was that a lot of the ordering problems could be fixed by using phandle references to track dependencies. I'm not aware of anyone working on that right now, presumably because everyone is busy getting features merged rather than optimizing boot speed.
Yes, I don't believe boot time ordering will ever happen upstream,
Maybe explicit boot time ordering won't happen upstream, but I don't think anyone would object if you posted patches that decrease boot time significantly, provided that they're still a generic solution. Tracking dependencies parse from a device tree sounds like an option that would be acceptable.
but then the current implementation with EPROBE_DEFER isn't usable either.
"Isn't usable" is pretty strong. It does get you a working panel, right? It's perhaps not optimized, but it gives you something that's guaranteed to work (unless you have an actual bug somewhere), and something that works in the general case, for everyone.
Any ideas? ATM it seems like the only way out is to just write my own dt format for the panel and ignore drm_panel.
Well, since probe order is currently dependent on device instantiation order, one possibility would be to rearrange the DT nodes in a way that dependencies are listed first. So in your case I'd expect the regulator provider to be listed first, followed by the panel and finally the "DRM" device. Providing none of them trigger deferred probing themselves they should probe in exactly that order.
Of course there are no guarantees that the probing order will stay that way forever and you will only be solving this problem for one device at a time rather than in general (which is the primary reason for deferred probing in the first place).
One other possibility I guess would be not to rely on deferred probing but to enable DRM/KMS to hotplug panels. Theoretically this should be possible already but it comes at a cost. If I'm not mistaken that's what Boris implemented for the Atmel HLCDC driver. However if the panel isn't available at DRM driver ->load() time, then it relies on the hotplugging mechanism to detect the panel later on, which can itself introduce more delays. I suspect this could be improved in various ways, but one of the more complicated issues is that it causes problems when you want to use the framebuffer console. The issue is that the fbdev helper code is set up very early and if no outputs with valid modes are found it will default to a 1024x768 resolution for the framebuffer console. At the same time it will prevent any outputs that are hotplugged later on to increase the resolution again. This works fine for monitors which usually support a number of different modes (1024x768 being standard enough that probably every monitor in existence supports it). But for panels this won't work, since they usually support only a single mode.
There are a number of ways that this could be solved I guess. One would be to defer fbdev creation itself until all outputs have been detected. This could prove difficult because it may not always be possible to determine which outputs to wait for. Another solution would be to allow the framebuffer console to be resized, but I've been told in the past that it's impractical if not impossible.
Of course if you don't need a framebuffer console (you could use kmscon or none at all), then things should work for you if you simply consider the panel as hotpluggable monitor.
Thierry
This patch attaches the dp connector to exynos_dp_panel, and adds calls to drm_panel functions to control panel power sequence.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: Addressed a comment from Jingoo Han. Also added post_disable control to exynos_dp driver.
drivers/gpu/drm/exynos/Kconfig | 1 + drivers/gpu/drm/exynos/exynos_dp_core.c | 18 ++++++++++++++++++ drivers/gpu/drm/exynos/exynos_dp_core.h | 1 + 3 files changed, 20 insertions(+)
diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 5bf5bca..56af433 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -52,6 +52,7 @@ config DRM_EXYNOS_DP bool "EXYNOS DRM DP driver support" depends on DRM_EXYNOS && ARCH_EXYNOS default DRM_EXYNOS + select DRM_PANEL help This enables support for DP device.
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index 18fd9c5..dbc5ccc 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -28,6 +28,7 @@ #include <drm/drmP.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> #include <drm/bridge/ptn3460.h>
#include "exynos_drm_drv.h" @@ -1028,6 +1029,9 @@ static int exynos_dp_create_connector(struct exynos_drm_display *display, drm_sysfs_connector_add(connector); drm_mode_connector_attach_encoder(connector, encoder);
+ if (dp->drm_panel) + drm_panel_attach(dp->drm_panel, &dp->connector); + return 0; }
@@ -1062,10 +1066,12 @@ static void exynos_dp_poweron(struct exynos_dp_device *dp) if (dp->dpms_mode == DRM_MODE_DPMS_ON) return;
+ drm_panel_pre_enable(dp->drm_panel); clk_prepare_enable(dp->clock); exynos_dp_phy_init(dp); exynos_dp_init_dp(dp); enable_irq(dp->irq); + drm_panel_enable(dp->drm_panel); }
static void exynos_dp_poweroff(struct exynos_dp_device *dp) @@ -1073,10 +1079,12 @@ static void exynos_dp_poweroff(struct exynos_dp_device *dp) if (dp->dpms_mode != DRM_MODE_DPMS_ON) return;
+ drm_panel_disable(dp->drm_panel); disable_irq(dp->irq); flush_work(&dp->hotplug_work); exynos_dp_phy_exit(dp); clk_disable_unprepare(dp->clock); + drm_panel_post_disable(dp->drm_panel); }
static void exynos_dp_dpms(struct exynos_drm_display *display, int mode) @@ -1225,6 +1233,7 @@ static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp) static int exynos_dp_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); + struct device_node *panel_node; struct drm_device *drm_dev = data; struct resource *res; struct exynos_dp_device *dp; @@ -1299,6 +1308,15 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
INIT_WORK(&dp->hotplug_work, exynos_dp_hotplug);
+ panel_node = of_find_compatible_node(NULL, NULL, + "samsung,exynos-dp-panel"); + if (panel_node) { + dp->drm_panel = of_drm_find_panel(panel_node); + of_node_put(panel_node); + if (!dp->drm_panel) + return -ENODEV; + } + exynos_dp_phy_init(dp);
exynos_dp_init_dp(dp); diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.h b/drivers/gpu/drm/exynos/exynos_dp_core.h index 56fa43e..9dc7991 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.h +++ b/drivers/gpu/drm/exynos/exynos_dp_core.h @@ -148,6 +148,7 @@ struct exynos_dp_device { struct drm_device *drm_dev; struct drm_connector connector; struct drm_encoder *encoder; + struct drm_panel *drm_panel; struct clk *clock; unsigned int irq; void __iomem *reg_base;
On Tue, Apr 22, 2014 at 04:09:14AM +0530, Ajay Kumar wrote: [...]
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c
[...]
@@ -1299,6 +1308,15 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
INIT_WORK(&dp->hotplug_work, exynos_dp_hotplug);
- panel_node = of_find_compatible_node(NULL, NULL,
"samsung,exynos-dp-panel");
No, please don't do this. It will break as soon as you have two panels of the same type in one system.
Also the compatible value of a panel should describe the specific panel in use (e.g. "samsung,s6e8aa0"), so you shouldn't rely on "magic" like this. Use a phandle to find the panel's struct device_node.
[...]
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.h b/drivers/gpu/drm/exynos/exynos_dp_core.h index 56fa43e..9dc7991 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.h +++ b/drivers/gpu/drm/exynos/exynos_dp_core.h @@ -148,6 +148,7 @@ struct exynos_dp_device { struct drm_device *drm_dev; struct drm_connector connector; struct drm_encoder *encoder;
- struct drm_panel *drm_panel;
I don't think you need the drm_ prefix here.
Thierry
From: Rahul Sharma Rahul.Sharma@samsung.com
Add DRM_CONNECTOR_POLL_HPD to the set of connector flags while registering drm_connector for ptn3460.
Signed-off-by: Rahul Sharma Rahul.Sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: No Change since V1
drivers/gpu/drm/bridge/ptn3460.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/gpu/drm/bridge/ptn3460.c b/drivers/gpu/drm/bridge/ptn3460.c index b171901..f1d2afc 100644 --- a/drivers/gpu/drm/bridge/ptn3460.c +++ b/drivers/gpu/drm/bridge/ptn3460.c @@ -326,6 +326,7 @@ int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
bridge->driver_private = ptn_bridge; encoder->bridge = bridge; + ptn_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(dev, &ptn_bridge->connector, &ptn3460_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
On Tue, Apr 22, 2014 at 04:09:15AM +0530, Ajay Kumar wrote:
From: Rahul Sharma Rahul.Sharma@samsung.com
Add DRM_CONNECTOR_POLL_HPD to the set of connector flags while registering drm_connector for ptn3460.
Signed-off-by: Rahul Sharma Rahul.Sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
This needs to explain *why* you think this change is necessary. As far as I can see, the PTN3460 bridge doesn't support hotplug detection at all, in which case setting DRM_CONNECTOR_POLL_HPD would be completely wrong.
Thierry
attach ptn3460 connector to drm_panel and support drm_panel routines, if a valid drm_panel object is passed to ptn3460_init.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: Address few coding style comments from Jingoo Han
drivers/gpu/drm/bridge/Kconfig | 1 + drivers/gpu/drm/bridge/ptn3460.c | 20 +++++++++++++++++++- drivers/gpu/drm/exynos/exynos_dp_core.c | 16 ++++++++++++---- include/drm/bridge/ptn3460.h | 6 ++++-- 4 files changed, 36 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 884923f..3bc6845 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -2,4 +2,5 @@ config DRM_PTN3460 tristate "PTN3460 DP/LVDS bridge" depends on DRM select DRM_KMS_HELPER + select DRM_PANEL ---help--- diff --git a/drivers/gpu/drm/bridge/ptn3460.c b/drivers/gpu/drm/bridge/ptn3460.c index f1d2afc..3920202 100644 --- a/drivers/gpu/drm/bridge/ptn3460.c +++ b/drivers/gpu/drm/bridge/ptn3460.c @@ -19,6 +19,7 @@ #include <linux/i2c.h> #include <linux/gpio.h> #include <linux/delay.h> +#include <drm/drm_panel.h>
#include "drmP.h" #include "drm_edid.h" @@ -38,6 +39,7 @@ struct ptn3460_bridge { struct i2c_client *client; struct drm_encoder *encoder; struct drm_bridge *bridge; + struct drm_panel *panel; struct edid *edid; int gpio_pd_n; int gpio_rst_n; @@ -126,6 +128,8 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge) gpio_set_value(ptn_bridge->gpio_rst_n, 1); }
+ drm_panel_pre_enable(ptn_bridge->panel); + /* * There's a bug in the PTN chip where it falsely asserts hotplug before * it is fully functional. We're forced to wait for the maximum start up @@ -142,6 +146,10 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge)
static void ptn3460_enable(struct drm_bridge *bridge) { + struct ptn3460_bridge *ptn_bridge = bridge->driver_private; + + if (ptn_bridge->enabled) + drm_panel_enable(ptn_bridge->panel); }
static void ptn3460_disable(struct drm_bridge *bridge) @@ -153,6 +161,9 @@ static void ptn3460_disable(struct drm_bridge *bridge)
ptn_bridge->enabled = false;
+ drm_panel_disable(ptn_bridge->panel); + drm_panel_post_disable(ptn_bridge->panel); + if (gpio_is_valid(ptn_bridge->gpio_rst_n)) gpio_set_value(ptn_bridge->gpio_rst_n, 1);
@@ -198,6 +209,7 @@ int ptn3460_get_modes(struct drm_connector *connector)
power_off = !ptn_bridge->enabled; ptn3460_pre_enable(ptn_bridge->bridge); + ptn3460_enable(ptn_bridge->bridge);
edid = kmalloc(EDID_LENGTH, GFP_KERNEL); if (!edid) { @@ -265,7 +277,8 @@ struct drm_connector_funcs ptn3460_connector_funcs = { };
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, - struct i2c_client *client, struct device_node *node) + struct i2c_client *client, struct device_node *node, + struct drm_panel *panel) { int ret; struct drm_bridge *bridge; @@ -324,6 +337,11 @@ int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, goto err; }
+ if (panel) { + ptn_bridge->panel = panel; + drm_panel_attach(ptn_bridge->panel, &ptn_bridge->connector); + } + bridge->driver_private = ptn_bridge; encoder->bridge = bridge; ptn_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD; diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index dbc5ccc..4853f31 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -989,13 +989,14 @@ static bool find_bridge(const char *compat, struct bridge_init *bridge)
/* returns the number of bridges attached */ static int exynos_drm_attach_lcd_bridge(struct drm_device *dev, - struct drm_encoder *encoder) + struct drm_encoder *encoder, struct drm_panel *panel) { struct bridge_init bridge; int ret;
if (find_bridge("nxp,ptn3460", &bridge)) { - ret = ptn3460_init(dev, encoder, bridge.client, bridge.node); + ret = ptn3460_init(dev, encoder, bridge.client, bridge.node, + panel); if (!ret) return 1; } @@ -1012,9 +1013,16 @@ static int exynos_dp_create_connector(struct exynos_drm_display *display, dp->encoder = encoder;
/* Pre-empt DP connector creation if there's a bridge */ - ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder); - if (ret) + ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder, dp->drm_panel); + if (ret) { + /* + * Also set "dp->drm_panel = NULL" so that we don't end up + * controlling panel power both in exynos_dp and bridge + * DPMS routines. + */ + dp->drm_panel = NULL; return 0; + }
connector->polled = DRM_CONNECTOR_POLL_HPD;
diff --git a/include/drm/bridge/ptn3460.h b/include/drm/bridge/ptn3460.h index ff62344..570cebb 100644 --- a/include/drm/bridge/ptn3460.h +++ b/include/drm/bridge/ptn3460.h @@ -18,16 +18,18 @@ struct drm_device; struct drm_encoder; struct i2c_client; struct device_node; +struct drm_panel;
#if defined(CONFIG_DRM_PTN3460) || defined(CONFIG_DRM_PTN3460_MODULE)
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, - struct i2c_client *client, struct device_node *node); + struct i2c_client *client, struct device_node *node, + struct drm_panel *panel); #else
static inline int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, struct i2c_client *client, - struct device_node *node) + struct device_node *node, struct drm_panel *panel) { return 0; }
On Tue, Apr 22, 2014 at 04:09:16AM +0530, Ajay Kumar wrote: [...]
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c
[...]
@@ -1012,9 +1013,16 @@ static int exynos_dp_create_connector(struct exynos_drm_display *display, dp->encoder = encoder;
/* Pre-empt DP connector creation if there's a bridge */
- ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder);
- if (ret)
- ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder, dp->drm_panel);
- if (ret) {
/*
* Also set "dp->drm_panel = NULL" so that we don't end up
* controlling panel power both in exynos_dp and bridge
* DPMS routines.
*/
return 0;dp->drm_panel = NULL;
- }
This kind of hack should set off an alarm that you're doing something wrong. I'm not sure integration of bridges and panels has been thought about or discussed a lot, but this doesn't look right to me at all.
Thierry
So what about, rather than adding drm_panel support to each bridge individually, we introduce a drm_panel_bridge (with a form of chaining).. ie:
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
... and so on.
If you don't need a real bridge, just create one of these w/ pb->bridge==NULL, otherwise create it as a wrapper for your real bridge.
BR, -R
On Mon, Apr 21, 2014 at 6:39 PM, Ajay Kumar ajaykumar.rs@samsung.com wrote:
attach ptn3460 connector to drm_panel and support drm_panel routines, if a valid drm_panel object is passed to ptn3460_init.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Address few coding style comments from Jingoo Han
drivers/gpu/drm/bridge/Kconfig | 1 + drivers/gpu/drm/bridge/ptn3460.c | 20 +++++++++++++++++++- drivers/gpu/drm/exynos/exynos_dp_core.c | 16 ++++++++++++---- include/drm/bridge/ptn3460.h | 6 ++++-- 4 files changed, 36 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 884923f..3bc6845 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -2,4 +2,5 @@ config DRM_PTN3460 tristate "PTN3460 DP/LVDS bridge" depends on DRM select DRM_KMS_HELPER
select DRM_PANEL ---help---
diff --git a/drivers/gpu/drm/bridge/ptn3460.c b/drivers/gpu/drm/bridge/ptn3460.c index f1d2afc..3920202 100644 --- a/drivers/gpu/drm/bridge/ptn3460.c +++ b/drivers/gpu/drm/bridge/ptn3460.c @@ -19,6 +19,7 @@ #include <linux/i2c.h> #include <linux/gpio.h> #include <linux/delay.h> +#include <drm/drm_panel.h>
#include "drmP.h" #include "drm_edid.h" @@ -38,6 +39,7 @@ struct ptn3460_bridge { struct i2c_client *client; struct drm_encoder *encoder; struct drm_bridge *bridge;
struct drm_panel *panel; struct edid *edid; int gpio_pd_n; int gpio_rst_n;
@@ -126,6 +128,8 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge) gpio_set_value(ptn_bridge->gpio_rst_n, 1); }
drm_panel_pre_enable(ptn_bridge->panel);
/* * There's a bug in the PTN chip where it falsely asserts hotplug before * it is fully functional. We're forced to wait for the maximum start up
@@ -142,6 +146,10 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge)
static void ptn3460_enable(struct drm_bridge *bridge) {
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
if (ptn_bridge->enabled)
drm_panel_enable(ptn_bridge->panel);
}
static void ptn3460_disable(struct drm_bridge *bridge) @@ -153,6 +161,9 @@ static void ptn3460_disable(struct drm_bridge *bridge)
ptn_bridge->enabled = false;
drm_panel_disable(ptn_bridge->panel);
drm_panel_post_disable(ptn_bridge->panel);
if (gpio_is_valid(ptn_bridge->gpio_rst_n)) gpio_set_value(ptn_bridge->gpio_rst_n, 1);
@@ -198,6 +209,7 @@ int ptn3460_get_modes(struct drm_connector *connector)
power_off = !ptn_bridge->enabled; ptn3460_pre_enable(ptn_bridge->bridge);
ptn3460_enable(ptn_bridge->bridge); edid = kmalloc(EDID_LENGTH, GFP_KERNEL); if (!edid) {
@@ -265,7 +277,8 @@ struct drm_connector_funcs ptn3460_connector_funcs = { };
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node)
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel)
{ int ret; struct drm_bridge *bridge; @@ -324,6 +337,11 @@ int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, goto err; }
if (panel) {
ptn_bridge->panel = panel;
drm_panel_attach(ptn_bridge->panel, &ptn_bridge->connector);
}
bridge->driver_private = ptn_bridge; encoder->bridge = bridge; ptn_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD;
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index dbc5ccc..4853f31 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -989,13 +989,14 @@ static bool find_bridge(const char *compat, struct bridge_init *bridge)
/* returns the number of bridges attached */ static int exynos_drm_attach_lcd_bridge(struct drm_device *dev,
struct drm_encoder *encoder)
struct drm_encoder *encoder, struct drm_panel *panel)
{ struct bridge_init bridge; int ret;
if (find_bridge("nxp,ptn3460", &bridge)) {
ret = ptn3460_init(dev, encoder, bridge.client, bridge.node);
ret = ptn3460_init(dev, encoder, bridge.client, bridge.node,
panel); if (!ret) return 1; }
@@ -1012,9 +1013,16 @@ static int exynos_dp_create_connector(struct exynos_drm_display *display, dp->encoder = encoder;
/* Pre-empt DP connector creation if there's a bridge */
ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder);
if (ret)
ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder, dp->drm_panel);
if (ret) {
/*
* Also set "dp->drm_panel = NULL" so that we don't end up
* controlling panel power both in exynos_dp and bridge
* DPMS routines.
*/
dp->drm_panel = NULL; return 0;
} connector->polled = DRM_CONNECTOR_POLL_HPD;
diff --git a/include/drm/bridge/ptn3460.h b/include/drm/bridge/ptn3460.h index ff62344..570cebb 100644 --- a/include/drm/bridge/ptn3460.h +++ b/include/drm/bridge/ptn3460.h @@ -18,16 +18,18 @@ struct drm_device; struct drm_encoder; struct i2c_client; struct device_node; +struct drm_panel;
#if defined(CONFIG_DRM_PTN3460) || defined(CONFIG_DRM_PTN3460_MODULE)
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node);
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel);
#else
static inline int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, struct i2c_client *client,
struct device_node *node)
struct device_node *node, struct drm_panel *panel)
{ return 0; } -- 1.7.9.5
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On Tue, Apr 22, 2014 at 07:34:03AM -0400, Rob Clark wrote:
So what about, rather than adding drm_panel support to each bridge individually, we introduce a drm_panel_bridge (with a form of chaining).. ie:
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
... and so on.
If you don't need a real bridge, just create one of these w/ pb->bridge==NULL, otherwise create it as a wrapper for your real bridge.
Yeah I think that's how I'd have implemented some generic abstraction for drivers using the crtc helpers. This allows you to keep bridge drivers, panel drivers and anything else you might have in your driver to feed the pixel stream to those 2 guys separate. And it also allows you to set it all up in different ways, e.g. using device tree metadata, or acpi or some other table hardcoded in a video rom somewhere.
Imo we also should have something similar to chain up drm_bridge devices. tbh I'm not terribly happy about how the current integration with the crtc modeset helpers is done ... -Daniel
BR, -R
On Mon, Apr 21, 2014 at 6:39 PM, Ajay Kumar ajaykumar.rs@samsung.com wrote:
attach ptn3460 connector to drm_panel and support drm_panel routines, if a valid drm_panel object is passed to ptn3460_init.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Address few coding style comments from Jingoo Han
drivers/gpu/drm/bridge/Kconfig | 1 + drivers/gpu/drm/bridge/ptn3460.c | 20 +++++++++++++++++++- drivers/gpu/drm/exynos/exynos_dp_core.c | 16 ++++++++++++---- include/drm/bridge/ptn3460.h | 6 ++++-- 4 files changed, 36 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 884923f..3bc6845 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -2,4 +2,5 @@ config DRM_PTN3460 tristate "PTN3460 DP/LVDS bridge" depends on DRM select DRM_KMS_HELPER
select DRM_PANEL ---help---
diff --git a/drivers/gpu/drm/bridge/ptn3460.c b/drivers/gpu/drm/bridge/ptn3460.c index f1d2afc..3920202 100644 --- a/drivers/gpu/drm/bridge/ptn3460.c +++ b/drivers/gpu/drm/bridge/ptn3460.c @@ -19,6 +19,7 @@ #include <linux/i2c.h> #include <linux/gpio.h> #include <linux/delay.h> +#include <drm/drm_panel.h>
#include "drmP.h" #include "drm_edid.h" @@ -38,6 +39,7 @@ struct ptn3460_bridge { struct i2c_client *client; struct drm_encoder *encoder; struct drm_bridge *bridge;
struct drm_panel *panel; struct edid *edid; int gpio_pd_n; int gpio_rst_n;
@@ -126,6 +128,8 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge) gpio_set_value(ptn_bridge->gpio_rst_n, 1); }
drm_panel_pre_enable(ptn_bridge->panel);
/* * There's a bug in the PTN chip where it falsely asserts hotplug before * it is fully functional. We're forced to wait for the maximum start up
@@ -142,6 +146,10 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge)
static void ptn3460_enable(struct drm_bridge *bridge) {
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
if (ptn_bridge->enabled)
drm_panel_enable(ptn_bridge->panel);
}
static void ptn3460_disable(struct drm_bridge *bridge) @@ -153,6 +161,9 @@ static void ptn3460_disable(struct drm_bridge *bridge)
ptn_bridge->enabled = false;
drm_panel_disable(ptn_bridge->panel);
drm_panel_post_disable(ptn_bridge->panel);
if (gpio_is_valid(ptn_bridge->gpio_rst_n)) gpio_set_value(ptn_bridge->gpio_rst_n, 1);
@@ -198,6 +209,7 @@ int ptn3460_get_modes(struct drm_connector *connector)
power_off = !ptn_bridge->enabled; ptn3460_pre_enable(ptn_bridge->bridge);
ptn3460_enable(ptn_bridge->bridge); edid = kmalloc(EDID_LENGTH, GFP_KERNEL); if (!edid) {
@@ -265,7 +277,8 @@ struct drm_connector_funcs ptn3460_connector_funcs = { };
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node)
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel)
{ int ret; struct drm_bridge *bridge; @@ -324,6 +337,11 @@ int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, goto err; }
if (panel) {
ptn_bridge->panel = panel;
drm_panel_attach(ptn_bridge->panel, &ptn_bridge->connector);
}
bridge->driver_private = ptn_bridge; encoder->bridge = bridge; ptn_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD;
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index dbc5ccc..4853f31 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -989,13 +989,14 @@ static bool find_bridge(const char *compat, struct bridge_init *bridge)
/* returns the number of bridges attached */ static int exynos_drm_attach_lcd_bridge(struct drm_device *dev,
struct drm_encoder *encoder)
struct drm_encoder *encoder, struct drm_panel *panel)
{ struct bridge_init bridge; int ret;
if (find_bridge("nxp,ptn3460", &bridge)) {
ret = ptn3460_init(dev, encoder, bridge.client, bridge.node);
ret = ptn3460_init(dev, encoder, bridge.client, bridge.node,
panel); if (!ret) return 1; }
@@ -1012,9 +1013,16 @@ static int exynos_dp_create_connector(struct exynos_drm_display *display, dp->encoder = encoder;
/* Pre-empt DP connector creation if there's a bridge */
ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder);
if (ret)
ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder, dp->drm_panel);
if (ret) {
/*
* Also set "dp->drm_panel = NULL" so that we don't end up
* controlling panel power both in exynos_dp and bridge
* DPMS routines.
*/
dp->drm_panel = NULL; return 0;
} connector->polled = DRM_CONNECTOR_POLL_HPD;
diff --git a/include/drm/bridge/ptn3460.h b/include/drm/bridge/ptn3460.h index ff62344..570cebb 100644 --- a/include/drm/bridge/ptn3460.h +++ b/include/drm/bridge/ptn3460.h @@ -18,16 +18,18 @@ struct drm_device; struct drm_encoder; struct i2c_client; struct device_node; +struct drm_panel;
#if defined(CONFIG_DRM_PTN3460) || defined(CONFIG_DRM_PTN3460_MODULE)
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node);
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel);
#else
static inline int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, struct i2c_client *client,
struct device_node *node)
struct device_node *node, struct drm_panel *panel)
{ return 0; } -- 1.7.9.5
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Rob,
On Tue, Apr 22, 2014 at 5:04 PM, Rob Clark robdclark@gmail.com wrote:
So what about, rather than adding drm_panel support to each bridge individually, we introduce a drm_panel_bridge (with a form of chaining).. ie:
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
We cannot call them like this from crtc helpers in the order you mentioned, since each individual bridge chip expects the panel controls at different places. I mean, sometimes panel controls needs to be done before bridge controls,
... and so on.
If you don't need a real bridge, just create one of these w/ pb->bridge==NULL, otherwise create it as a wrapper for your real bridge.
BR, -R
On Mon, Apr 21, 2014 at 6:39 PM, Ajay Kumar ajaykumar.rs@samsung.com wrote:
attach ptn3460 connector to drm_panel and support drm_panel routines, if a valid drm_panel object is passed to ptn3460_init.
Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Address few coding style comments from Jingoo Han
drivers/gpu/drm/bridge/Kconfig | 1 + drivers/gpu/drm/bridge/ptn3460.c | 20 +++++++++++++++++++- drivers/gpu/drm/exynos/exynos_dp_core.c | 16 ++++++++++++---- include/drm/bridge/ptn3460.h | 6 ++++-- 4 files changed, 36 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 884923f..3bc6845 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -2,4 +2,5 @@ config DRM_PTN3460 tristate "PTN3460 DP/LVDS bridge" depends on DRM select DRM_KMS_HELPER
select DRM_PANEL ---help---
diff --git a/drivers/gpu/drm/bridge/ptn3460.c b/drivers/gpu/drm/bridge/ptn3460.c index f1d2afc..3920202 100644 --- a/drivers/gpu/drm/bridge/ptn3460.c +++ b/drivers/gpu/drm/bridge/ptn3460.c @@ -19,6 +19,7 @@ #include <linux/i2c.h> #include <linux/gpio.h> #include <linux/delay.h> +#include <drm/drm_panel.h>
#include "drmP.h" #include "drm_edid.h" @@ -38,6 +39,7 @@ struct ptn3460_bridge { struct i2c_client *client; struct drm_encoder *encoder; struct drm_bridge *bridge;
struct drm_panel *panel; struct edid *edid; int gpio_pd_n; int gpio_rst_n;
@@ -126,6 +128,8 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge) gpio_set_value(ptn_bridge->gpio_rst_n, 1); }
drm_panel_pre_enable(ptn_bridge->panel);
/* * There's a bug in the PTN chip where it falsely asserts hotplug before * it is fully functional. We're forced to wait for the maximum start up
@@ -142,6 +146,10 @@ static void ptn3460_pre_enable(struct drm_bridge *bridge)
static void ptn3460_enable(struct drm_bridge *bridge) {
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
if (ptn_bridge->enabled)
drm_panel_enable(ptn_bridge->panel);
}
static void ptn3460_disable(struct drm_bridge *bridge) @@ -153,6 +161,9 @@ static void ptn3460_disable(struct drm_bridge *bridge)
ptn_bridge->enabled = false;
drm_panel_disable(ptn_bridge->panel);
drm_panel_post_disable(ptn_bridge->panel);
if (gpio_is_valid(ptn_bridge->gpio_rst_n)) gpio_set_value(ptn_bridge->gpio_rst_n, 1);
@@ -198,6 +209,7 @@ int ptn3460_get_modes(struct drm_connector *connector)
power_off = !ptn_bridge->enabled; ptn3460_pre_enable(ptn_bridge->bridge);
ptn3460_enable(ptn_bridge->bridge); edid = kmalloc(EDID_LENGTH, GFP_KERNEL); if (!edid) {
@@ -265,7 +277,8 @@ struct drm_connector_funcs ptn3460_connector_funcs = { };
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node)
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel)
{ int ret; struct drm_bridge *bridge; @@ -324,6 +337,11 @@ int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, goto err; }
if (panel) {
ptn_bridge->panel = panel;
drm_panel_attach(ptn_bridge->panel, &ptn_bridge->connector);
}
bridge->driver_private = ptn_bridge; encoder->bridge = bridge; ptn_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD;
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index dbc5ccc..4853f31 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -989,13 +989,14 @@ static bool find_bridge(const char *compat, struct bridge_init *bridge)
/* returns the number of bridges attached */ static int exynos_drm_attach_lcd_bridge(struct drm_device *dev,
struct drm_encoder *encoder)
struct drm_encoder *encoder, struct drm_panel *panel)
{ struct bridge_init bridge; int ret;
if (find_bridge("nxp,ptn3460", &bridge)) {
ret = ptn3460_init(dev, encoder, bridge.client, bridge.node);
ret = ptn3460_init(dev, encoder, bridge.client, bridge.node,
panel); if (!ret) return 1; }
@@ -1012,9 +1013,16 @@ static int exynos_dp_create_connector(struct exynos_drm_display *display, dp->encoder = encoder;
/* Pre-empt DP connector creation if there's a bridge */
ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder);
if (ret)
ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder, dp->drm_panel);
if (ret) {
/*
* Also set "dp->drm_panel = NULL" so that we don't end up
* controlling panel power both in exynos_dp and bridge
* DPMS routines.
*/
dp->drm_panel = NULL; return 0;
} connector->polled = DRM_CONNECTOR_POLL_HPD;
diff --git a/include/drm/bridge/ptn3460.h b/include/drm/bridge/ptn3460.h index ff62344..570cebb 100644 --- a/include/drm/bridge/ptn3460.h +++ b/include/drm/bridge/ptn3460.h @@ -18,16 +18,18 @@ struct drm_device; struct drm_encoder; struct i2c_client; struct device_node; +struct drm_panel;
#if defined(CONFIG_DRM_PTN3460) || defined(CONFIG_DRM_PTN3460_MODULE)
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node);
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel);
#else
static inline int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder, struct i2c_client *client,
struct device_node *node)
struct device_node *node, struct drm_panel *panel)
{ return 0; } -- 1.7.9.5
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Sorry for the previous reply,
Here goes the full explaination:
Rob,
On Tue, Apr 22, 2014 at 5:04 PM, Rob Clark robdclark@gmail.com wrote:
So what about, rather than adding drm_panel support to each bridge individually, we introduce a drm_panel_bridge (with a form of chaining).. ie:
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
We cannot call them like this from crtc helpers in the order you mentioned, since each individual bridge chip expects the panel controls at different places. I mean, -- sometimes panel controls needs to be done before bridge controls(ptn3460: before RST_N and PD_N) -- sometimes in between the bridge controls (ps8622: one panel control before SLP_N and one after SLP_N) -- sometimes panel controls needs to be done after bridge controls.
So, putting these drm_panel controls inside individual bridge drivers will work, but keeping them in crtc_helpers might break stuff.
Thanks and regards, Ajay Kumar
On Wed, Apr 23, 2014 at 3:02 PM, Ajay kumar ajaynumb@gmail.com wrote:
Sorry for the previous reply,
Here goes the full explaination:
Rob,
On Tue, Apr 22, 2014 at 5:04 PM, Rob Clark robdclark@gmail.com wrote:
So what about, rather than adding drm_panel support to each bridge individually, we introduce a drm_panel_bridge (with a form of chaining).. ie:
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
We cannot call them like this from crtc helpers in the order you mentioned, since each individual bridge chip expects the panel controls at different places. I mean, -- sometimes panel controls needs to be done before bridge controls(ptn3460: before RST_N and PD_N)
well, this is why bridge has pre-enable/enable/disable/post-disable hooks, so you can choose before or after..
-- sometimes in between the bridge controls (ps8622: one panel control before SLP_N and one after SLP_N) -- sometimes panel controls needs to be done after bridge controls.
I am not convinced that a generic panel/bridge adapter is not possible. Maybe we need more fine grained fxn ptr callbacks, although seems like pre+post should give you enough. It would certainly be easier than having to add panel support in each individual bridge driver (which seems like it will certainly result that only certain combinations of panel+bridge actually work as intended)
So, putting these drm_panel controls inside individual bridge drivers will work, but keeping them in crtc_helpers might break stuff.
I'm not talking about crtc changing helpers.
BR, -R
Thanks and regards, Ajay Kumar
Rob,
On Thu, Apr 24, 2014 at 9:41 PM, Rob Clark robdclark@gmail.com wrote:
On Wed, Apr 23, 2014 at 3:02 PM, Ajay kumar ajaynumb@gmail.com wrote:
Sorry for the previous reply,
Here goes the full explaination:
Rob,
On Tue, Apr 22, 2014 at 5:04 PM, Rob Clark robdclark@gmail.com wrote:
So what about, rather than adding drm_panel support to each bridge individually, we introduce a drm_panel_bridge (with a form of chaining).. ie:
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
We cannot call them like this from crtc helpers in the order you mentioned, since each individual bridge chip expects the panel controls at different places. I mean, -- sometimes panel controls needs to be done before bridge controls(ptn3460: before RST_N and PD_N)
well, this is why bridge has pre-enable/enable/disable/post-disable hooks, so you can choose before or after..
These calls are for a bridge to sync with the encoder calls. We might end up defining pre-enable/enable/disable/post-disable for a panel to sync with the bridge calls! I have explained below.
-- sometimes in between the bridge controls (ps8622: one panel control before SLP_N and one after SLP_N) -- sometimes panel controls needs to be done after bridge controls.
I am not convinced that a generic panel/bridge adapter is not possible. Maybe we need more fine grained fxn ptr callbacks, although seems like pre+post should give you enough. It would certainly be easier than having to add panel support in each individual bridge driver (which seems like it will certainly result that only certain combinations of panel+bridge actually work as intended)
Ok. Consider this case: Currently, we have the sequence as "bridge->pre_enable, encoder_enable, bridge->enable" And, the bridge should obviously be enabled in bridge->pre_enable. Now, where do you choose to call panel->pre_enable? -- as the first step of bridge->pre_enable, before we pull up/down bridge GPIOs. -- at the last step of bridge->pre_enable, after we pull up/down the bridge GPIOs.
Ideally, PTN3460 expects it to be the second case, and PS8625 expects it to be the first case. So, we will end up adding pre_pre_enable, post_pre_enable to accomodate such scenarios.
So, we leave the choice for the individual bridge chip drivers to implement panel power up/down controls in the place where they wish to.
Thanks and regards, Ajay Kumar
On Thu, Apr 24, 2014 at 12:55 PM, Ajay kumar ajaynumb@gmail.com wrote:
Rob,
On Thu, Apr 24, 2014 at 9:41 PM, Rob Clark robdclark@gmail.com wrote:
On Wed, Apr 23, 2014 at 3:02 PM, Ajay kumar ajaynumb@gmail.com wrote:
Sorry for the previous reply,
Here goes the full explaination:
Rob,
On Tue, Apr 22, 2014 at 5:04 PM, Rob Clark robdclark@gmail.com wrote:
So what about, rather than adding drm_panel support to each bridge individually, we introduce a drm_panel_bridge (with a form of chaining).. ie:
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
We cannot call them like this from crtc helpers in the order you mentioned, since each individual bridge chip expects the panel controls at different places. I mean, -- sometimes panel controls needs to be done before bridge controls(ptn3460: before RST_N and PD_N)
well, this is why bridge has pre-enable/enable/disable/post-disable hooks, so you can choose before or after..
These calls are for a bridge to sync with the encoder calls. We might end up defining pre-enable/enable/disable/post-disable for a panel to sync with the bridge calls! I have explained below.
-- sometimes in between the bridge controls (ps8622: one panel control before SLP_N and one after SLP_N) -- sometimes panel controls needs to be done after bridge controls.
I am not convinced that a generic panel/bridge adapter is not possible. Maybe we need more fine grained fxn ptr callbacks, although seems like pre+post should give you enough. It would certainly be easier than having to add panel support in each individual bridge driver (which seems like it will certainly result that only certain combinations of panel+bridge actually work as intended)
Ok. Consider this case: Currently, we have the sequence as "bridge->pre_enable, encoder_enable, bridge->enable" And, the bridge should obviously be enabled in bridge->pre_enable. Now, where do you choose to call panel->pre_enable? -- as the first step of bridge->pre_enable, before we pull up/down bridge GPIOs. -- at the last step of bridge->pre_enable, after we pull up/down the bridge GPIOs.
Ideally, PTN3460 expects it to be the second case, and PS8625 expects it to be the first case. So, we will end up adding pre_pre_enable, post_pre_enable to accomodate such scenarios.
ok, well I think we can deal with this with a slight modification of my original proposal. Split drm_panel_bridge into drm_composite_bridge and drm_panel_bridge:
struct drm_composite_bridge { struct drm_bridge base; struct drm_bridge *b1, *b2; }
static void drm_composite_bridge_enable(struct drm_bridge *bridge) { struct drm_composite_bridge *cbridge = to_composite_bridge(bridge); cbridge->b1->funcs->enable(cbridge->b1); cbridge->b2->funcs->enable(cbridge->b2); }
.. and so on ..
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; }
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pbridge = to_panel_bridge(bridge); pbridge->panel->funcs->enable(pbridge->panel); }
.. and so on..
then in initialization, order can be managed like:
if (panel_first) bridge = drm_composite_bridge_init(panel_bridge, actual_bridge) else bridge = drm_composite_bridge_init(actual_bridge, panel_bridge);
possibly parametrize drm_panel_bridge if we need to map panel->enable() to bridge->pre_enable() vs bridge->enable(), etc..
BR, -R
So, we leave the choice for the individual bridge chip drivers to implement panel power up/down controls in the place where they wish to.
Thanks and regards, Ajay Kumar
On Thu, Apr 24, 2014 at 10:55 PM, Rob Clark robdclark@gmail.com wrote:
On Thu, Apr 24, 2014 at 12:55 PM, Ajay kumar ajaynumb@gmail.com wrote:
Rob,
On Thu, Apr 24, 2014 at 9:41 PM, Rob Clark robdclark@gmail.com wrote:
On Wed, Apr 23, 2014 at 3:02 PM, Ajay kumar ajaynumb@gmail.com wrote:
Sorry for the previous reply,
Here goes the full explaination:
Rob,
On Tue, Apr 22, 2014 at 5:04 PM, Rob Clark robdclark@gmail.com wrote:
So what about, rather than adding drm_panel support to each bridge individually, we introduce a drm_panel_bridge (with a form of chaining).. ie:
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
We cannot call them like this from crtc helpers in the order you mentioned, since each individual bridge chip expects the panel controls at different places. I mean, -- sometimes panel controls needs to be done before bridge controls(ptn3460: before RST_N and PD_N)
well, this is why bridge has pre-enable/enable/disable/post-disable hooks, so you can choose before or after..
These calls are for a bridge to sync with the encoder calls. We might end up defining pre-enable/enable/disable/post-disable for a panel to sync with the bridge calls! I have explained below.
-- sometimes in between the bridge controls (ps8622: one panel control before SLP_N and one after SLP_N) -- sometimes panel controls needs to be done after bridge controls.
I am not convinced that a generic panel/bridge adapter is not possible. Maybe we need more fine grained fxn ptr callbacks, although seems like pre+post should give you enough. It would certainly be easier than having to add panel support in each individual bridge driver (which seems like it will certainly result that only certain combinations of panel+bridge actually work as intended)
Ok. Consider this case: Currently, we have the sequence as "bridge->pre_enable, encoder_enable, bridge->enable" And, the bridge should obviously be enabled in bridge->pre_enable. Now, where do you choose to call panel->pre_enable? -- as the first step of bridge->pre_enable, before we pull up/down bridge GPIOs. -- at the last step of bridge->pre_enable, after we pull up/down the bridge GPIOs.
Ideally, PTN3460 expects it to be the second case, and PS8625 expects it to be the first case. So, we will end up adding pre_pre_enable, post_pre_enable to accomodate such scenarios.
ok, well I think we can deal with this with a slight modification of my original proposal. Split drm_panel_bridge into drm_composite_bridge and drm_panel_bridge:
struct drm_composite_bridge { struct drm_bridge base; struct drm_bridge *b1, *b2; }
static void drm_composite_bridge_enable(struct drm_bridge *bridge) { struct drm_composite_bridge *cbridge = to_composite_bridge(bridge); cbridge->b1->funcs->enable(cbridge->b1); cbridge->b2->funcs->enable(cbridge->b2); }
.. and so on ..
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; }
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pbridge = to_panel_bridge(bridge); pbridge->panel->funcs->enable(pbridge->panel); }
.. and so on..
then in initialization, order can be managed like:
if (panel_first) bridge = drm_composite_bridge_init(panel_bridge, actual_bridge) else bridge = drm_composite_bridge_init(actual_bridge, panel_bridge);
possibly parametrize drm_panel_bridge if we need to map panel->enable() to bridge->pre_enable() vs bridge->enable(), etc..
Well, this really does seems complex to me. Don't you think just having a drm_panel inside drm_bridge structure is sufficient?! And, make a call for pre_panel_enable and post_panel_enable around bridge->pre_enable..and so on.?
On Thu, Apr 24, 2014 at 11:08 PM, Ajay kumar ajaynumb@gmail.com wrote:
On Thu, Apr 24, 2014 at 10:55 PM, Rob Clark robdclark@gmail.com wrote:
On Thu, Apr 24, 2014 at 12:55 PM, Ajay kumar ajaynumb@gmail.com wrote:
Rob,
On Thu, Apr 24, 2014 at 9:41 PM, Rob Clark robdclark@gmail.com wrote:
On Wed, Apr 23, 2014 at 3:02 PM, Ajay kumar ajaynumb@gmail.com wrote:
Sorry for the previous reply,
Here goes the full explaination:
Rob,
On Tue, Apr 22, 2014 at 5:04 PM, Rob Clark robdclark@gmail.com wrote: > So what about, rather than adding drm_panel support to each bridge > individually, we introduce a drm_panel_bridge (with a form of > chaining).. ie: > > struct drm_panel_bridge { > struct drm_bridge base; > struct drm_panel *panel; > struct drm_bridge *bridge; /* optional */ > }; > > static void drm_panel_bridge_enable(struct drm_bridge *bridge) > { > struct drm_panel_bridge *pb = to_panel_bridge(bridge); > if (pb->bridge) > pb->bridge->funcs->enable(pb->bridge); > pb->panel->funcs->enable(pb->panel); > } >
We cannot call them like this from crtc helpers in the order you mentioned, since each individual bridge chip expects the panel controls at different places. I mean, -- sometimes panel controls needs to be done before bridge controls(ptn3460: before RST_N and PD_N)
well, this is why bridge has pre-enable/enable/disable/post-disable hooks, so you can choose before or after..
These calls are for a bridge to sync with the encoder calls. We might end up defining pre-enable/enable/disable/post-disable for a panel to sync with the bridge calls! I have explained below.
-- sometimes in between the bridge controls (ps8622: one panel control before SLP_N and one after SLP_N) -- sometimes panel controls needs to be done after bridge controls.
I am not convinced that a generic panel/bridge adapter is not possible. Maybe we need more fine grained fxn ptr callbacks, although seems like pre+post should give you enough. It would certainly be easier than having to add panel support in each individual bridge driver (which seems like it will certainly result that only certain combinations of panel+bridge actually work as intended)
Ok. Consider this case: Currently, we have the sequence as "bridge->pre_enable, encoder_enable, bridge->enable" And, the bridge should obviously be enabled in bridge->pre_enable. Now, where do you choose to call panel->pre_enable? -- as the first step of bridge->pre_enable, before we pull up/down bridge GPIOs. -- at the last step of bridge->pre_enable, after we pull up/down the bridge GPIOs.
Ideally, PTN3460 expects it to be the second case, and PS8625 expects it to be the first case. So, we will end up adding pre_pre_enable, post_pre_enable to accomodate such scenarios.
ok, well I think we can deal with this with a slight modification of my original proposal. Split drm_panel_bridge into drm_composite_bridge and drm_panel_bridge:
struct drm_composite_bridge { struct drm_bridge base; struct drm_bridge *b1, *b2; }
static void drm_composite_bridge_enable(struct drm_bridge *bridge) { struct drm_composite_bridge *cbridge = to_composite_bridge(bridge); cbridge->b1->funcs->enable(cbridge->b1); cbridge->b2->funcs->enable(cbridge->b2); }
.. and so on ..
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; }
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pbridge = to_panel_bridge(bridge); pbridge->panel->funcs->enable(pbridge->panel); }
.. and so on..
then in initialization, order can be managed like:
if (panel_first) bridge = drm_composite_bridge_init(panel_bridge, actual_bridge) else bridge = drm_composite_bridge_init(actual_bridge, panel_bridge);
possibly parametrize drm_panel_bridge if we need to map panel->enable() to bridge->pre_enable() vs bridge->enable(), etc..
Well, this really does seems complex to me. Don't you think just having a drm_panel inside drm_bridge structure is sufficient?! And, make a call for pre_panel_enable and post_panel_enable around bridge->pre_enable..and so on.?
Adding more comments: The beauty of drm_panel is in the flexibility which it provides. We can call panel_enable/disable at the right point. Even the
Sorry for the previous reply. Here goes the full explaination.
On Fri, Apr 25, 2014 at 11:40 AM, Ajay kumar ajaynumb@gmail.com wrote:
On Thu, Apr 24, 2014 at 11:08 PM, Ajay kumar ajaynumb@gmail.com wrote:
On Thu, Apr 24, 2014 at 10:55 PM, Rob Clark robdclark@gmail.com wrote:
On Thu, Apr 24, 2014 at 12:55 PM, Ajay kumar ajaynumb@gmail.com wrote:
Rob,
On Thu, Apr 24, 2014 at 9:41 PM, Rob Clark robdclark@gmail.com wrote:
On Wed, Apr 23, 2014 at 3:02 PM, Ajay kumar ajaynumb@gmail.com wrote:
Sorry for the previous reply,
Here goes the full explaination:
> Rob, > > On Tue, Apr 22, 2014 at 5:04 PM, Rob Clark robdclark@gmail.com wrote: >> So what about, rather than adding drm_panel support to each bridge >> individually, we introduce a drm_panel_bridge (with a form of >> chaining).. ie: >> >> struct drm_panel_bridge { >> struct drm_bridge base; >> struct drm_panel *panel; >> struct drm_bridge *bridge; /* optional */ >> }; >> >> static void drm_panel_bridge_enable(struct drm_bridge *bridge) >> { >> struct drm_panel_bridge *pb = to_panel_bridge(bridge); >> if (pb->bridge) >> pb->bridge->funcs->enable(pb->bridge); >> pb->panel->funcs->enable(pb->panel); >> } >> We cannot call them like this from crtc helpers in the order you mentioned, since each individual bridge chip expects the panel controls at different places. I mean, -- sometimes panel controls needs to be done before bridge controls(ptn3460: before RST_N and PD_N)
well, this is why bridge has pre-enable/enable/disable/post-disable hooks, so you can choose before or after..
These calls are for a bridge to sync with the encoder calls. We might end up defining pre-enable/enable/disable/post-disable for a panel to sync with the bridge calls! I have explained below.
-- sometimes in between the bridge controls (ps8622: one panel control before SLP_N and one after SLP_N) -- sometimes panel controls needs to be done after bridge controls.
I am not convinced that a generic panel/bridge adapter is not possible. Maybe we need more fine grained fxn ptr callbacks, although seems like pre+post should give you enough. It would certainly be easier than having to add panel support in each individual bridge driver (which seems like it will certainly result that only certain combinations of panel+bridge actually work as intended)
Ok. Consider this case: Currently, we have the sequence as "bridge->pre_enable, encoder_enable, bridge->enable" And, the bridge should obviously be enabled in bridge->pre_enable. Now, where do you choose to call panel->pre_enable? -- as the first step of bridge->pre_enable, before we pull up/down bridge GPIOs. -- at the last step of bridge->pre_enable, after we pull up/down the bridge GPIOs.
Ideally, PTN3460 expects it to be the second case, and PS8625 expects it to be the first case. So, we will end up adding pre_pre_enable, post_pre_enable to accomodate such scenarios.
ok, well I think we can deal with this with a slight modification of my original proposal. Split drm_panel_bridge into drm_composite_bridge and drm_panel_bridge:
struct drm_composite_bridge { struct drm_bridge base; struct drm_bridge *b1, *b2; }
static void drm_composite_bridge_enable(struct drm_bridge *bridge) { struct drm_composite_bridge *cbridge = to_composite_bridge(bridge); cbridge->b1->funcs->enable(cbridge->b1); cbridge->b2->funcs->enable(cbridge->b2); }
.. and so on ..
struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; }
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pbridge = to_panel_bridge(bridge); pbridge->panel->funcs->enable(pbridge->panel); }
.. and so on..
then in initialization, order can be managed like:
if (panel_first) bridge = drm_composite_bridge_init(panel_bridge, actual_bridge) else bridge = drm_composite_bridge_init(actual_bridge, panel_bridge);
possibly parametrize drm_panel_bridge if we need to map panel->enable() to bridge->pre_enable() vs bridge->enable(), etc..
Well, this really does seems complex to me. Don't you think just having a drm_panel inside drm_bridge structure is sufficient?! And, make a call for pre_panel_enable and post_panel_enable around bridge->pre_enable..and so on.?
Adding more comments: The beauty of drm_panel is in the flexibility which it provides. We can call panel_enable/disable at the right point. Even the bridge chips expect the same. So, I am not ok with combining the bridge and panel and calling the fxn pointers from crtc_helpers. So, either we leave it the way it is in this patch (call drm_panel functions at right points) or we don't use drm_panel to control the panel sequence and instead use few DT properties and regulators, inside the bridge chip driver.
On Fri, Apr 25, 2014 at 8:17 AM, Ajay kumar ajaynumb@gmail.com wrote:
We can call panel_enable/disable at the right point. Even the bridge chips expect the same. So, I am not ok with combining the bridge and panel and calling the fxn pointers from crtc_helpers. So, either we leave it the way it is in this patch (call drm_panel functions at right points) or we don't use drm_panel to control the panel sequence and instead use few DT properties and regulators, inside the bridge chip driver.
That wasn't really suggested, but instead the idea was to provide a default drm_bridge which wraps the drm_panel so that you could more easily chain them up.
Also I'm not really happy that the bridge callbacks have been added to the drm helpers (I'd prefer if driver callbacks would bear that responsibility). But you can always create your own drm_bridge integration. In any case your concern that drivers need to control when certain callbacks are called is shared by everyone here afaics. And I also don't see any issues with Rob's proposal in this regard. -Daniel
Daniel,
On Sun, Apr 27, 2014 at 6:25 PM, Daniel Vetter daniel@ffwll.ch wrote:
On Fri, Apr 25, 2014 at 8:17 AM, Ajay kumar ajaynumb@gmail.com wrote:
We can call panel_enable/disable at the right point. Even the bridge chips expect the same. So, I am not ok with combining the bridge and panel and calling the fxn pointers from crtc_helpers. So, either we leave it the way it is in this patch (call drm_panel functions at right points) or we don't use drm_panel to control the panel sequence and instead use few DT properties and regulators, inside the bridge chip driver.
That wasn't really suggested, but instead the idea was to provide a default drm_bridge which wraps the drm_panel so that you could more easily chain them up.
What do you mean by "default" drm_bridge? I just want to clear few things. Let me explain what I have understood: What I understand is that Rob wants me to create a common structure which wraps up drm_panel and drm_bridge into a single structure: struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ ==> Really not sure why this is needed! };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
And now, the above function becomes a common function (also with an ordering issue btw panel and bridge). Where do we keep it? And, where do we call it from?
Also I'm not really happy that the bridge callbacks have been added to the drm helpers (I'd prefer if driver callbacks would bear that responsibility). But you can always create your own drm_bridge integration.
Do you mean, the bridge calls should be moved out of common drm_helper functions and called from platform specific drivers instead?
In any case your concern that drivers need to control when certain callbacks are called is shared by everyone here afaics. And I also don't see any issues with Rob's proposal in this regard.
-Daniel
Daniel Vetter Software Engineer, Intel Corporation +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Please let me know if my understanding is correct and correct me if there is a misconception.
Regards, Ajay Kumar
On Mon, Apr 28, 2014 at 9:08 AM, Ajay kumar ajaynumb@gmail.com wrote:
Daniel,
On Sun, Apr 27, 2014 at 6:25 PM, Daniel Vetter daniel@ffwll.ch wrote:
On Fri, Apr 25, 2014 at 8:17 AM, Ajay kumar ajaynumb@gmail.com wrote:
We can call panel_enable/disable at the right point. Even the bridge chips expect the same. So, I am not ok with combining the bridge and panel and calling the fxn pointers from crtc_helpers. So, either we leave it the way it is in this patch (call drm_panel functions at right points) or we don't use drm_panel to control the panel sequence and instead use few DT properties and regulators, inside the bridge chip driver.
That wasn't really suggested, but instead the idea was to provide a default drm_bridge which wraps the drm_panel so that you could more easily chain them up.
What do you mean by "default" drm_bridge? I just want to clear few things. Let me explain what I have understood: What I understand is that Rob wants me to create a common structure which wraps up drm_panel and drm_bridge into a single structure: struct drm_panel_bridge { struct drm_bridge base; struct drm_panel *panel; struct drm_bridge *bridge; /* optional */ ==> Really not sure why this is needed! };
static void drm_panel_bridge_enable(struct drm_bridge *bridge) { struct drm_panel_bridge *pb = to_panel_bridge(bridge); if (pb->bridge) pb->bridge->funcs->enable(pb->bridge); pb->panel->funcs->enable(pb->panel); }
And now, the above function becomes a common function (also with an ordering issue btw panel and bridge). Where do we keep it? And, where do we call it from?
my proposal involves no change to crtc helpers. The later variation (with composite_bridge so you could choose the order) would have a sequence along the lines of:
crtc helpers: + bridge_funcs->enable(): -> composite_bridge_enable() + cbridge->b1->funcs->enable(): -> ptn3460_bridge_enable() + cbridge->b2->funcs->enable(): -> panel_bridge_enable(); + pbridge->panel->funcs->enable(): -> foo_panel_enable()
or if the order needs to be swapped you can use ptn3460_bridge as b2 and panel bridge as b1.
BR, -R
Also I'm not really happy that the bridge callbacks have been added to the drm helpers (I'd prefer if driver callbacks would bear that responsibility). But you can always create your own drm_bridge integration.
Do you mean, the bridge calls should be moved out of common drm_helper functions and called from platform specific drivers instead?
In any case your concern that drivers need to control when certain callbacks are called is shared by everyone here afaics. And I also don't see any issues with Rob's proposal in this regard.
-Daniel
Daniel Vetter Software Engineer, Intel Corporation +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Please let me know if my understanding is correct and correct me if there is a misconception.
Regards, Ajay Kumar
This patch adds a drm_bridge driver for the PS8622 DisplayPort to LVDS bridge chip.
Signed-off-by: Andrew Bresticker abrestic@chromium.org Signed-off-by: Sean Paul seanpaul@chromium.org Signed-off-by: Rahul Sharma rahul.sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: Pushing V1 for this as V2 because this patch holds good in this series.
drivers/gpu/drm/bridge/Kconfig | 7 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ps8622.c | 566 +++++++++++++++++++++++++++++++++++++++ include/drm/bridge/ps8622.h | 42 +++ 4 files changed, 616 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ps8622.c create mode 100644 include/drm/bridge/ps8622.h
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3bc6845..aba21fc 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -4,3 +4,10 @@ config DRM_PTN3460 select DRM_KMS_HELPER select DRM_PANEL ---help--- + +config DRM_PS8622 + tristate "Parade eDP/LVDS bridge" + depends on DRM + select DRM_KMS_HELPER + select DRM_PANEL + ---help--- diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index b4733e1..d1b5daa 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,3 +1,4 @@ ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o +obj-$(CONFIG_DRM_PS8622) += ps8622.o diff --git a/drivers/gpu/drm/bridge/ps8622.c b/drivers/gpu/drm/bridge/ps8622.c new file mode 100644 index 0000000..1e6b3ca --- /dev/null +++ b/drivers/gpu/drm/bridge/ps8622.c @@ -0,0 +1,566 @@ +/* + * Parade PS8622 eDP/LVDS bridge driver + * + * Copyright (C) 2014 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> +#include <drm/drm_panel.h> + +#include "drmP.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +struct ps8622_bridge { + struct drm_connector connector; + struct drm_bridge *bridge; + struct drm_encoder *encoder; + struct drm_panel *panel; + struct i2c_client *client; + struct regulator *v12; + struct backlight_device *bl; + struct mutex enable_mutex; + + int gpio_slp_n; + int gpio_rst_n; + + u8 max_lane_count; + u8 lane_count; + + bool enabled; + + struct drm_display_mode mode; +}; + +struct ps8622_device_data { + u8 max_lane_count; +}; + +static const struct ps8622_device_data ps8622_data = { + .max_lane_count = 1, +}; + +static const struct ps8622_device_data ps8625_data = { + .max_lane_count = 2, +}; + +/* Brightness scale on the Parade chip */ +#define PS8622_MAX_BRIGHTNESS 0xff + +/* Timings taken from the version 1.7 datasheet for the PS8622/PS8625 */ +#define PS8622_POWER_RISE_T1_MIN_US 10 +#define PS8622_POWER_RISE_T1_MAX_US 10000 +#define PS8622_RST_HIGH_T2_MIN_US 3000 +#define PS8622_RST_HIGH_T2_MAX_US 30000 +#define PS8622_PWMO_END_T12_MS 200 +#define PS8622_POWER_FALL_T16_MAX_US 10000 +#define PS8622_POWER_OFF_T17_MS 500 + +#if ((PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US) > \ + (PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US)) +#error "T2.min + T1.max must be less than T2.max + T1.min" +#endif + +static int ps8622_set(struct i2c_client *client, u8 page, u8 reg, u8 val) +{ + int ret; + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg; + u8 data[] = {reg, val}; + + msg.addr = client->addr + page; + msg.flags = 0; + msg.len = sizeof(data); + msg.buf = data; + + ret = i2c_transfer(adap, &msg, 1); + if (ret != 1) + pr_warn("PS8622 I2C write (0x%02x,0x%02x,0x%02x) failed: %d\n", + client->addr + page, reg, val, ret); + return !(ret == 1); +} + +static int ps8622_send_config(struct ps8622_bridge *ps_bridge) +{ + struct i2c_client *cl = ps_bridge->client; + int err = 0; + + /* wait 20ms after power ON */ + usleep_range(20000, 30000); + + err |= ps8622_set(cl, 0x02, 0xa1, 0x01); /* HPD low */ + /* SW setting */ + err |= ps8622_set(cl, 0x04, 0x14, 0x01); /* [1:0] SW output 1.2V voltage + * is lower to 96% */ + /* RCO SS setting */ + err |= ps8622_set(cl, 0x04, 0xe3, 0x20); /* [5:4] = b01 0.5%, b10 1%, + * b11 1.5% */ + err |= ps8622_set(cl, 0x04, 0xe2, 0x80); /* [7] RCO SS enable */ + /* RPHY Setting */ + err |= ps8622_set(cl, 0x04, 0x8a, 0x0c); /* [3:2] CDR tune wait cycle + * before measure for fine tune + * b00: 1us b01: 0.5us b10:2us + * b11: 4us */ + err |= ps8622_set(cl, 0x04, 0x89, 0x08); /* [3] RFD always on */ + err |= ps8622_set(cl, 0x04, 0x71, 0x2d); /* CTN lock in/out: + * 20000ppm/80000ppm. + * Lock out 2 times. */ + /* 2.7G CDR settings */ + err |= ps8622_set(cl, 0x04, 0x7d, 0x07); /* NOF=40LSB for HBR CDR + * setting */ + err |= ps8622_set(cl, 0x04, 0x7b, 0x00); /* [1:0] Fmin=+4bands */ + err |= ps8622_set(cl, 0x04, 0x7a, 0xfd); /* [7:5] DCO_FTRNG=+-40% */ + /* 1.62G CDR settings */ + err |= ps8622_set(cl, 0x04, 0xc0, 0x12); /* [5:2]NOF=64LSB [1:0]DCO + * scale is 2/5 */ + err |= ps8622_set(cl, 0x04, 0xc1, 0x92); /* Gitune=-37% */ + err |= ps8622_set(cl, 0x04, 0xc2, 0x1c); /* Fbstep=100% */ + err |= ps8622_set(cl, 0x04, 0x32, 0x80); /* [7] LOS signal disable */ + /* RPIO Setting */ + err |= ps8622_set(cl, 0x04, 0x00, 0xb0); /* [7:4] LVDS driver bias + * current : 75% (250mV swing) + * */ + err |= ps8622_set(cl, 0x04, 0x15, 0x40); /* [7:6] Right-bar GPIO output + * strength is 8mA */ + /* EQ Training State Machine Setting */ + err |= ps8622_set(cl, 0x04, 0x54, 0x10); /* RCO calibration start */ + /* Logic, needs more than 10 I2C command */ + err |= ps8622_set(cl, 0x01, 0x02, 0x80 | ps_bridge->max_lane_count); + /* [4:0] MAX_LANE_COUNT set to + * max supported lanes */ + err |= ps8622_set(cl, 0x01, 0x21, 0x80 | ps_bridge->lane_count); + /* [4:0] LANE_COUNT_SET set to + * chosen lane count */ + err |= ps8622_set(cl, 0x00, 0x52, 0x20); + err |= ps8622_set(cl, 0x00, 0xf1, 0x03); /* HPD CP toggle enable */ + err |= ps8622_set(cl, 0x00, 0x62, 0x41); + err |= ps8622_set(cl, 0x00, 0xf6, 0x01); /* Counter number, add 1ms + * counter delay */ + err |= ps8622_set(cl, 0x00, 0x77, 0x06); /* [6]PWM function control by + * DPCD0040f[7], default is PWM + * block always works. */ + err |= ps8622_set(cl, 0x00, 0x4c, 0x04); /* 04h Adjust VTotal tolerance + * to fix the 30Hz no display + * issue */ + err |= ps8622_set(cl, 0x01, 0xc0, 0x00); /* DPCD00400='h00, Parade OUI = + * 'h001cf8 */ + err |= ps8622_set(cl, 0x01, 0xc1, 0x1c); /* DPCD00401='h1c */ + err |= ps8622_set(cl, 0x01, 0xc2, 0xf8); /* DPCD00402='hf8 */ + err |= ps8622_set(cl, 0x01, 0xc3, 0x44); /* DPCD403~408 = ASCII code + * D2SLV5='h4432534c5635 */ + err |= ps8622_set(cl, 0x01, 0xc4, 0x32); /* DPCD404 */ + err |= ps8622_set(cl, 0x01, 0xc5, 0x53); /* DPCD405 */ + err |= ps8622_set(cl, 0x01, 0xc6, 0x4c); /* DPCD406 */ + err |= ps8622_set(cl, 0x01, 0xc7, 0x56); /* DPCD407 */ + err |= ps8622_set(cl, 0x01, 0xc8, 0x35); /* DPCD408 */ + err |= ps8622_set(cl, 0x01, 0xca, 0x01); /* DPCD40A, Initial Code major + * revision '01' */ + err |= ps8622_set(cl, 0x01, 0xcb, 0x05); /* DPCD40B, Initial Code minor + * revision '05' */ + if (ps_bridge->bl) { + err |= ps8622_set(cl, 0x01, 0xa5, 0xa0); + /* DPCD720, internal PWM */ + err |= ps8622_set(cl, 0x01, 0xa7, + ps_bridge->bl->props.brightness); + /* FFh for 100% brightness, + * 0h for 0% brightness */ + } else { + err |= ps8622_set(cl, 0x01, 0xa5, 0x80); + /* DPCD720, external PWM */ + } + err |= ps8622_set(cl, 0x01, 0xcc, 0x13); /* Set LVDS output as 6bit-VESA + * mapping, single LVDS channel + * */ + err |= ps8622_set(cl, 0x02, 0xb1, 0x20); /* Enable SSC set by register + * */ + err |= ps8622_set(cl, 0x04, 0x10, 0x16); /* Set SSC enabled and +/-1% + * central spreading */ + /* Logic end */ + err |= ps8622_set(cl, 0x04, 0x59, 0x60); /* MPU Clock source: LC => RCO + * */ + err |= ps8622_set(cl, 0x04, 0x54, 0x14); /* LC -> RCO */ + err |= ps8622_set(cl, 0x02, 0xa1, 0x91); /* HPD high */ + + return err ? -EIO : 0; +} + +static int ps8622_backlight_update(struct backlight_device *bl) +{ + struct ps8622_bridge *ps_bridge = dev_get_drvdata(&bl->dev); + int ret, brightness = bl->props.brightness; + + if (bl->props.power != FB_BLANK_UNBLANK || + bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) + brightness = 0; + + mutex_lock(&ps_bridge->enable_mutex); + + if (!ps_bridge->enabled) { + ret = -EINVAL; + goto out; + } + + ret = ps8622_set(ps_bridge->client, 0x01, 0xa7, brightness); + +out: + mutex_unlock(&ps_bridge->enable_mutex); + return ret; +} + +static int ps8622_backlight_get(struct backlight_device *bl) +{ + return bl->props.brightness; +} + +static const struct backlight_ops ps8622_backlight_ops = { + .update_status = ps8622_backlight_update, + .get_brightness = ps8622_backlight_get, +}; + +static void ps8622_pre_enable(struct drm_bridge *bridge) +{ + struct ps8622_bridge *ps_bridge = bridge->driver_private; + int ret; + + mutex_lock(&ps_bridge->enable_mutex); + if (ps_bridge->enabled) + goto out; + + if (gpio_is_valid(ps_bridge->gpio_rst_n)) + gpio_set_value(ps_bridge->gpio_rst_n, 0); + + if (ps_bridge->v12) { + ret = regulator_enable(ps_bridge->v12); + if (ret) + DRM_ERROR("fails to enable ps_bridge->v12"); + } + + drm_panel_pre_enable(ps_bridge->panel); + + if (gpio_is_valid(ps_bridge->gpio_slp_n)) + gpio_set_value(ps_bridge->gpio_slp_n, 1); + + /* + * T1 is the range of time that it takes for the power to rise after we + * enable the lcd fet. T2 is the range of time in which the data sheet + * specifies we should deassert the reset pin. + * + * If it takes T1.max for the power to rise, we need to wait atleast + * T2.min before deasserting the reset pin. If it takes T1.min for the + * power to rise, we need to wait at most T2.max before deasserting the + * reset pin. + */ + usleep_range(PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US, + PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US); + + if (gpio_is_valid(ps_bridge->gpio_rst_n)) + gpio_set_value(ps_bridge->gpio_rst_n, 1); + + ret = ps8622_send_config(ps_bridge); + if (ret) + DRM_ERROR("Failed to send config to bridge (%d)\n", ret); + + ps_bridge->enabled = true; + + mutex_unlock(&ps_bridge->enable_mutex); + return; + +out: + + mutex_unlock(&ps_bridge->enable_mutex); +} + +static void ps8622_enable(struct drm_bridge *bridge) +{ + struct ps8622_bridge *ps_bridge = bridge->driver_private; + + mutex_lock(&ps_bridge->enable_mutex); + if (ps_bridge->enabled) + drm_panel_enable(ps_bridge->panel); + mutex_unlock(&ps_bridge->enable_mutex); +} + +static void ps8622_disable(struct drm_bridge *bridge) +{ + struct ps8622_bridge *ps_bridge = bridge->driver_private; + + mutex_lock(&ps_bridge->enable_mutex); + + if (!ps_bridge->enabled) + goto out; + + ps_bridge->enabled = false; + + drm_panel_disable(ps_bridge->panel); + msleep(PS8622_PWMO_END_T12_MS); + + /* + * This doesn't matter if the regulators are turned off, but something + * else might keep them on. In that case, we want to assert the slp gpio + * to lower power. + */ + if (gpio_is_valid(ps_bridge->gpio_slp_n)) + gpio_set_value(ps_bridge->gpio_slp_n, 0); + + drm_panel_post_disable(ps_bridge->panel); + if (ps_bridge->v12) + regulator_disable(ps_bridge->v12); + + /* + * Sleep for at least the amount of time that it takes the power rail to + * fall to prevent asserting the rst gpio from doing anything. + */ + usleep_range(PS8622_POWER_FALL_T16_MAX_US, + 2 * PS8622_POWER_FALL_T16_MAX_US); + if (gpio_is_valid(ps_bridge->gpio_rst_n)) + gpio_set_value(ps_bridge->gpio_rst_n, 0); + + msleep(PS8622_POWER_OFF_T17_MS); + +out: + mutex_unlock(&ps_bridge->enable_mutex); +} + +static void ps8622_post_disable(struct drm_bridge *bridge) +{ +} + +static void ps8622_destroy(struct drm_bridge *bridge) +{ + struct ps8622_bridge *ps_bridge = bridge->driver_private; + + drm_bridge_cleanup(bridge); + if (ps_bridge->bl) + backlight_device_unregister(ps_bridge->bl); + + put_device(&ps_bridge->client->dev); +} + +static const struct drm_bridge_funcs ps8622_bridge_funcs = { + .pre_enable = ps8622_pre_enable, + .enable = ps8622_enable, + .disable = ps8622_disable, + .post_disable = ps8622_post_disable, + .destroy = ps8622_destroy, +}; + +static int ps8622_get_modes(struct drm_connector *connector) +{ + struct ps8622_bridge *ps_bridge; + struct drm_display_mode *new_mode; + + ps_bridge = container_of(connector, struct ps8622_bridge, connector); + + new_mode = drm_mode_duplicate(connector->dev, &ps_bridge->mode); + if (!new_mode) { + DRM_ERROR("Failed to duplicate ps_bridge mode!\n"); + return 0; + } + drm_mode_probed_add(connector, new_mode); + return 1; +} + +static int ps8622_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct ps8622_bridge *ps_bridge; + + ps_bridge = container_of(connector, struct ps8622_bridge, connector); + + return drm_mode_equal(mode, &ps_bridge->mode) ? MODE_OK : MODE_BAD; +} + +static struct drm_encoder *ps8622_best_encoder(struct drm_connector *connector) +{ + struct ps8622_bridge *ps_bridge; + + ps_bridge = container_of(connector, struct ps8622_bridge, connector); + + return ps_bridge->encoder; +} + +static const struct drm_connector_helper_funcs ps8622_connector_helper_funcs = { + .get_modes = ps8622_get_modes, + .mode_valid = ps8622_mode_valid, + .best_encoder = ps8622_best_encoder, +}; + +static enum drm_connector_status ps8622_detect(struct drm_connector *connector, + bool force) +{ + return connector_status_connected; +} + +static void ps8622_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs ps8622_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ps8622_detect, + .destroy = ps8622_connector_destroy, +}; + +static const struct of_device_id ps8622_devices[] = { + { + .compatible = "parade,ps8622", + .data = &ps8622_data, + }, { + .compatible = "parade,ps8625", + .data = &ps8625_data, + }, { + /* end node */ + } +}; + +int ps8622_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node, + struct drm_panel *panel) +{ + int ret; + const struct of_device_id *match; + const struct ps8622_device_data *device_data; + struct drm_bridge *bridge; + struct ps8622_bridge *ps_bridge; + + node = of_find_matching_node_and_match(NULL, ps8622_devices, &match); + if (!node) + return -ENODEV; + + bridge = devm_kzalloc(dev->dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + DRM_ERROR("Failed to allocate drm bridge\n"); + return -ENOMEM; + } + + ps_bridge = devm_kzalloc(dev->dev, sizeof(*ps_bridge), GFP_KERNEL); + if (!ps_bridge) { + DRM_ERROR("could not allocate ps bridge\n"); + return -ENOMEM; + } + + mutex_init(&ps_bridge->enable_mutex); + + device_data = match->data; + ps_bridge->client = client; + ps_bridge->encoder = encoder; + ps_bridge->bridge = bridge; + ps_bridge->v12 = devm_regulator_get(&client->dev, "vdd_bridge"); + if (IS_ERR(ps_bridge->v12)) { + DRM_INFO("no 1.2v regulator found for PS8622\n"); + ps_bridge->v12 = NULL; + } + + ps_bridge->gpio_slp_n = of_get_named_gpio(node, "sleep-gpio", 0); + if (gpio_is_valid(ps_bridge->gpio_slp_n)) { + ret = devm_gpio_request_one(&client->dev, ps_bridge->gpio_slp_n, + GPIOF_OUT_INIT_HIGH, + "PS8622_SLP_N"); + if (ret) + goto err_client; + } + + ps_bridge->gpio_rst_n = of_get_named_gpio(node, "reset-gpio", 0); + if (gpio_is_valid(ps_bridge->gpio_rst_n)) { + /* + * Assert the reset pin high to avoid the bridge being + * initialized prematurely + */ + ret = devm_gpio_request_one(&client->dev, ps_bridge->gpio_rst_n, + GPIOF_OUT_INIT_HIGH, + "PS8622_RST_N"); + if (ret) + goto err_client; + } + + ps_bridge->max_lane_count = device_data->max_lane_count; + + if (of_property_read_u8(node, "lane-count", &ps_bridge->lane_count)) + ps_bridge->lane_count = ps_bridge->max_lane_count; + else if (ps_bridge->lane_count > ps_bridge->max_lane_count) { + DRM_ERROR("lane-count property is too high for DP bridge\n"); + ps_bridge->lane_count = ps_bridge->max_lane_count; + } + + if (!of_find_property(node, "use-external-pwm", NULL)) { + ps_bridge->bl = backlight_device_register("ps8622-backlight", + dev->dev, ps_bridge, &ps8622_backlight_ops, + NULL); + if (IS_ERR(ps_bridge->bl)) { + DRM_ERROR("failed to register backlight\n"); + ret = PTR_ERR(ps_bridge->bl); + ps_bridge->bl = NULL; + goto err_client; + } + ps_bridge->bl->props.max_brightness = PS8622_MAX_BRIGHTNESS; + ps_bridge->bl->props.brightness = PS8622_MAX_BRIGHTNESS; + } + + ret = of_get_drm_display_mode(node, &ps_bridge->mode, 0); + if (ret) { + DRM_ERROR("Failed to get display mode, ret=%d\n", ret); + goto err_backlight; + } + + ret = drm_bridge_init(dev, ps_bridge->bridge, &ps8622_bridge_funcs); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + goto err_backlight; + } + + if (panel) { + ps_bridge->panel = panel; + drm_panel_attach(ps_bridge->panel, &ps_bridge->connector); + } + + ps_bridge->bridge->driver_private = ps_bridge; + encoder->bridge = ps_bridge->bridge; + ps_bridge->enabled = false; + ps_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(dev, &ps_bridge->connector, + &ps8622_connector_funcs, DRM_MODE_CONNECTOR_LVDS); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + goto err_bridge; + } + drm_connector_helper_add(&ps_bridge->connector, + &ps8622_connector_helper_funcs); + drm_sysfs_connector_add(&ps_bridge->connector); + drm_mode_connector_attach_encoder(&ps_bridge->connector, encoder); + + return 0; + +err_bridge: + drm_bridge_cleanup(ps_bridge->bridge); +err_backlight: + if (ps_bridge->bl) + backlight_device_unregister(ps_bridge->bl); + +err_client: + put_device(&client->dev); + DRM_ERROR("device probe failed : %d\n", ret); + return ret; +} +EXPORT_SYMBOL(ps8622_init); diff --git a/include/drm/bridge/ps8622.h b/include/drm/bridge/ps8622.h new file mode 100644 index 0000000..4fc1959 --- /dev/null +++ b/include/drm/bridge/ps8622.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _DRM_BRIDGE_PS8622_H_ +#define _DRM_BRIDGE_PS8622_H_ + +struct drm_device; +struct drm_encoder; +struct i2c_client; +struct device_node; +struct drm_panel; + +#ifdef CONFIG_DRM_PS8622 + +extern int ps8622_init(struct drm_device *dev, struct drm_encoder *encoder, + struct i2c_client *client, struct device_node *node, + struct drm_panel *panel); + +#else + +static inline int ps8622_init(struct drm_device *dev, + struct drm_encoder *encoder, + struct i2c_client *client, + struct device_node *node, + struct drm_panel *panel) +{ + return -ENODEV; +} + +#endif + +#endif
On Tuesday, April 22, 2014 7:39 AM, Ajay Kumar wrote:
This patch adds a drm_bridge driver for the PS8622 DisplayPort to LVDS bridge chip.
Signed-off-by: Andrew Bresticker abrestic@chromium.org Signed-off-by: Sean Paul seanpaul@chromium.org Signed-off-by: Rahul Sharma rahul.sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Pushing V1 for this as V2 because this patch holds good in this series.
drivers/gpu/drm/bridge/Kconfig | 7 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ps8622.c | 566 +++++++++++++++++++++++++++++++++++++++ include/drm/bridge/ps8622.h | 42 +++ 4 files changed, 616 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ps8622.c create mode 100644 include/drm/bridge/ps8622.h
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3bc6845..aba21fc 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -4,3 +4,10 @@ config DRM_PTN3460 select DRM_KMS_HELPER select DRM_PANEL ---help---
+config DRM_PS8622
- tristate "Parade eDP/LVDS bridge"
- depends on DRM
- select DRM_KMS_HELPER
- select DRM_PANEL
Please add the following select.
+ select BACKLIGHT_LCD_SUPPORT + select BACKLIGHT_CLASS_DEVICE
Without this, the following build issues happen.
drivers/gpu/drm/bridge/ps8622.c:353: undefined reference to `backlight_device_unregister' drivers/built-in.o: In function `ps8622_init': drivers/gpu/drm/bridge/ps8622.c:559: undefined reference to `backlight_device_unregister' drivers/gpu/drm/bridge/ps8622.c:507: undefined reference to `backlight_device_register'
- ---help---
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index b4733e1..d1b5daa 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,3 +1,4 @@ ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o +obj-$(CONFIG_DRM_PS8622) += ps8622.o diff --git a/drivers/gpu/drm/bridge/ps8622.c b/drivers/gpu/drm/bridge/ps8622.c new file mode 100644 index 0000000..1e6b3ca --- /dev/null +++ b/drivers/gpu/drm/bridge/ps8622.c
[.....]
+static int ps8622_send_config(struct ps8622_bridge *ps_bridge) +{
- struct i2c_client *cl = ps_bridge->client;
- int err = 0;
- /* wait 20ms after power ON */
- usleep_range(20000, 30000);
- err |= ps8622_set(cl, 0x02, 0xa1, 0x01); /* HPD low */
- /* SW setting */
- err |= ps8622_set(cl, 0x04, 0x14, 0x01); /* [1:0] SW output 1.2V voltage
* is lower to 96% */
The comment style looks awkward. Please choose one of two options. And change all comments in this file.
1.
+ /* SW setting */ + err |= ps8622_set(cl, 0x04, 0x14, 0x01); /* [1:0] SW output 1.2V voltage is lower to 96% */
2.
+ /* SW setting */ + /* [1:0] SW output 1.2V voltage is lower to 96% */ + err |= ps8622_set(cl, 0x04, 0x14, 0x01);
- /* RCO SS setting */
- err |= ps8622_set(cl, 0x04, 0xe3, 0x20); /* [5:4] = b01 0.5%, b10 1%,
* b11 1.5% */
- err |= ps8622_set(cl, 0x04, 0xe2, 0x80); /* [7] RCO SS enable */
- /* RPHY Setting */
- err |= ps8622_set(cl, 0x04, 0x8a, 0x0c); /* [3:2] CDR tune wait cycle
* before measure for fine tune
* b00: 1us b01: 0.5us b10:2us
* b11: 4us */
- err |= ps8622_set(cl, 0x04, 0x89, 0x08); /* [3] RFD always on */
- err |= ps8622_set(cl, 0x04, 0x71, 0x2d); /* CTN lock in/out:
* 20000ppm/80000ppm.
* Lock out 2 times. */
- /* 2.7G CDR settings */
- err |= ps8622_set(cl, 0x04, 0x7d, 0x07); /* NOF=40LSB for HBR CDR
* setting */
- err |= ps8622_set(cl, 0x04, 0x7b, 0x00); /* [1:0] Fmin=+4bands */
- err |= ps8622_set(cl, 0x04, 0x7a, 0xfd); /* [7:5] DCO_FTRNG=+-40% */
- /* 1.62G CDR settings */
- err |= ps8622_set(cl, 0x04, 0xc0, 0x12); /* [5:2]NOF=64LSB [1:0]DCO
* scale is 2/5 */
- err |= ps8622_set(cl, 0x04, 0xc1, 0x92); /* Gitune=-37% */
- err |= ps8622_set(cl, 0x04, 0xc2, 0x1c); /* Fbstep=100% */
- err |= ps8622_set(cl, 0x04, 0x32, 0x80); /* [7] LOS signal disable */
- /* RPIO Setting */
- err |= ps8622_set(cl, 0x04, 0x00, 0xb0); /* [7:4] LVDS driver bias
* current : 75% (250mV swing)
* */
- err |= ps8622_set(cl, 0x04, 0x15, 0x40); /* [7:6] Right-bar GPIO output
* strength is 8mA */
- /* EQ Training State Machine Setting */
- err |= ps8622_set(cl, 0x04, 0x54, 0x10); /* RCO calibration start */
- /* Logic, needs more than 10 I2C command */
- err |= ps8622_set(cl, 0x01, 0x02, 0x80 | ps_bridge->max_lane_count);
/* [4:0] MAX_LANE_COUNT set to
* max supported lanes */
- err |= ps8622_set(cl, 0x01, 0x21, 0x80 | ps_bridge->lane_count);
/* [4:0] LANE_COUNT_SET set to
* chosen lane count */
- err |= ps8622_set(cl, 0x00, 0x52, 0x20);
- err |= ps8622_set(cl, 0x00, 0xf1, 0x03); /* HPD CP toggle enable */
- err |= ps8622_set(cl, 0x00, 0x62, 0x41);
- err |= ps8622_set(cl, 0x00, 0xf6, 0x01); /* Counter number, add 1ms
* counter delay */
- err |= ps8622_set(cl, 0x00, 0x77, 0x06); /* [6]PWM function control by
* DPCD0040f[7], default is PWM
* block always works. */
- err |= ps8622_set(cl, 0x00, 0x4c, 0x04); /* 04h Adjust VTotal tolerance
* to fix the 30Hz no display
* issue */
- err |= ps8622_set(cl, 0x01, 0xc0, 0x00); /* DPCD00400='h00, Parade OUI =
* 'h001cf8 */
- err |= ps8622_set(cl, 0x01, 0xc1, 0x1c); /* DPCD00401='h1c */
- err |= ps8622_set(cl, 0x01, 0xc2, 0xf8); /* DPCD00402='hf8 */
- err |= ps8622_set(cl, 0x01, 0xc3, 0x44); /* DPCD403~408 = ASCII code
* D2SLV5='h4432534c5635 */
- err |= ps8622_set(cl, 0x01, 0xc4, 0x32); /* DPCD404 */
- err |= ps8622_set(cl, 0x01, 0xc5, 0x53); /* DPCD405 */
- err |= ps8622_set(cl, 0x01, 0xc6, 0x4c); /* DPCD406 */
- err |= ps8622_set(cl, 0x01, 0xc7, 0x56); /* DPCD407 */
- err |= ps8622_set(cl, 0x01, 0xc8, 0x35); /* DPCD408 */
- err |= ps8622_set(cl, 0x01, 0xca, 0x01); /* DPCD40A, Initial Code major
* revision '01' */
- err |= ps8622_set(cl, 0x01, 0xcb, 0x05); /* DPCD40B, Initial Code minor
* revision '05' */
- if (ps_bridge->bl) {
err |= ps8622_set(cl, 0x01, 0xa5, 0xa0);
/* DPCD720, internal PWM */
err |= ps8622_set(cl, 0x01, 0xa7,
ps_bridge->bl->props.brightness);
/* FFh for 100% brightness,
* 0h for 0% brightness */
- } else {
err |= ps8622_set(cl, 0x01, 0xa5, 0x80);
/* DPCD720, external PWM */
- }
- err |= ps8622_set(cl, 0x01, 0xcc, 0x13); /* Set LVDS output as 6bit-VESA
* mapping, single LVDS channel
* */
- err |= ps8622_set(cl, 0x02, 0xb1, 0x20); /* Enable SSC set by register
* */
- err |= ps8622_set(cl, 0x04, 0x10, 0x16); /* Set SSC enabled and +/-1%
* central spreading */
- /* Logic end */
- err |= ps8622_set(cl, 0x04, 0x59, 0x60); /* MPU Clock source: LC => RCO
* */
- err |= ps8622_set(cl, 0x04, 0x54, 0x14); /* LC -> RCO */
- err |= ps8622_set(cl, 0x02, 0xa1, 0x91); /* HPD high */
- return err ? -EIO : 0;
+}
[.....]
+static void ps8622_pre_enable(struct drm_bridge *bridge) +{
- struct ps8622_bridge *ps_bridge = bridge->driver_private;
- int ret;
- mutex_lock(&ps_bridge->enable_mutex);
- if (ps_bridge->enabled)
goto out;
- if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 0);
- if (ps_bridge->v12) {
ret = regulator_enable(ps_bridge->v12);
if (ret)
DRM_ERROR("fails to enable ps_bridge->v12");
- }
- drm_panel_pre_enable(ps_bridge->panel);
- if (gpio_is_valid(ps_bridge->gpio_slp_n))
gpio_set_value(ps_bridge->gpio_slp_n, 1);
- /*
* T1 is the range of time that it takes for the power to rise after we
* enable the lcd fet. T2 is the range of time in which the data sheet
* specifies we should deassert the reset pin.
*
* If it takes T1.max for the power to rise, we need to wait atleast
* T2.min before deasserting the reset pin. If it takes T1.min for the
* power to rise, we need to wait at most T2.max before deasserting the
* reset pin.
*/
- usleep_range(PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US,
PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US);
- if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 1);
- ret = ps8622_send_config(ps_bridge);
- if (ret)
DRM_ERROR("Failed to send config to bridge (%d)\n", ret);
- ps_bridge->enabled = true;
- mutex_unlock(&ps_bridge->enable_mutex);
- return;
+out:
- mutex_unlock(&ps_bridge->enable_mutex);
+}
mutex_unlock() is duplicated. Also, 'return' is unnecessary. Please fix it as below.
+ ps_bridge->enabled = true; + +out: + mutex_unlock(&ps_bridge->enable_mutex); +}
+static void ps8622_enable(struct drm_bridge *bridge) +{
- struct ps8622_bridge *ps_bridge = bridge->driver_private;
- mutex_lock(&ps_bridge->enable_mutex);
- if (ps_bridge->enabled)
drm_panel_enable(ps_bridge->panel);
- mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_disable(struct drm_bridge *bridge) +{
- struct ps8622_bridge *ps_bridge = bridge->driver_private;
- mutex_lock(&ps_bridge->enable_mutex);
- if (!ps_bridge->enabled)
goto out;
- ps_bridge->enabled = false;
- drm_panel_disable(ps_bridge->panel);
- msleep(PS8622_PWMO_END_T12_MS);
- /*
* This doesn't matter if the regulators are turned off, but something
* else might keep them on. In that case, we want to assert the slp gpio
* to lower power.
*/
- if (gpio_is_valid(ps_bridge->gpio_slp_n))
gpio_set_value(ps_bridge->gpio_slp_n, 0);
- drm_panel_post_disable(ps_bridge->panel);
- if (ps_bridge->v12)
regulator_disable(ps_bridge->v12);
- /*
* Sleep for at least the amount of time that it takes the power rail to
* fall to prevent asserting the rst gpio from doing anything.
*/
- usleep_range(PS8622_POWER_FALL_T16_MAX_US,
2 * PS8622_POWER_FALL_T16_MAX_US);
- if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 0);
- msleep(PS8622_POWER_OFF_T17_MS);
+out:
- mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_post_disable(struct drm_bridge *bridge) +{ +}
How about just removing this empty function?
[.....]
Best regards, Jingoo Han
Hi Jingoo,
On Tue, Apr 22, 2014 at 2:13 PM, Jingoo Han jg1.han@samsung.com wrote:
On Tuesday, April 22, 2014 7:39 AM, Ajay Kumar wrote:
This patch adds a drm_bridge driver for the PS8622 DisplayPort to LVDS bridge chip.
Signed-off-by: Andrew Bresticker abrestic@chromium.org Signed-off-by: Sean Paul seanpaul@chromium.org Signed-off-by: Rahul Sharma rahul.sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Pushing V1 for this as V2 because this patch holds good in this
series.
drivers/gpu/drm/bridge/Kconfig | 7 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ps8622.c | 566
+++++++++++++++++++++++++++++++++++++++
include/drm/bridge/ps8622.h | 42 +++ 4 files changed, 616 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ps8622.c create mode 100644 include/drm/bridge/ps8622.h
diff --git a/drivers/gpu/drm/bridge/Kconfig
b/drivers/gpu/drm/bridge/Kconfig
index 3bc6845..aba21fc 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -4,3 +4,10 @@ config DRM_PTN3460 select DRM_KMS_HELPER select DRM_PANEL ---help---
+config DRM_PS8622
tristate "Parade eDP/LVDS bridge"
depends on DRM
select DRM_KMS_HELPER
select DRM_PANEL
Please add the following select.
select BACKLIGHT_LCD_SUPPORT
select BACKLIGHT_CLASS_DEVICE
Without this, the following build issues happen.
drivers/gpu/drm/bridge/ps8622.c:353: undefined reference to `backlight_device_unregister' drivers/built-in.o: In function `ps8622_init': drivers/gpu/drm/bridge/ps8622.c:559: undefined reference to `backlight_device_unregister' drivers/gpu/drm/bridge/ps8622.c:507: undefined reference to `backlight_device_register'
Right. I will add it.
---help---
diff --git a/drivers/gpu/drm/bridge/Makefile
b/drivers/gpu/drm/bridge/Makefile
index b4733e1..d1b5daa 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,3 +1,4 @@ ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o +obj-$(CONFIG_DRM_PS8622) += ps8622.o diff --git a/drivers/gpu/drm/bridge/ps8622.c
b/drivers/gpu/drm/bridge/ps8622.c
new file mode 100644 index 0000000..1e6b3ca --- /dev/null +++ b/drivers/gpu/drm/bridge/ps8622.c
[.....]
+static int ps8622_send_config(struct ps8622_bridge *ps_bridge) +{
struct i2c_client *cl = ps_bridge->client;
int err = 0;
/* wait 20ms after power ON */
usleep_range(20000, 30000);
err |= ps8622_set(cl, 0x02, 0xa1, 0x01); /* HPD low */
/* SW setting */
err |= ps8622_set(cl, 0x04, 0x14, 0x01); /* [1:0] SW output 1.2V
voltage
* is lower to 96% */
The comment style looks awkward. Please choose one of two options. And change all comments in this file.
/* SW setting */
err |= ps8622_set(cl, 0x04, 0x14, 0x01); /* [1:0] SW output 1.2V
voltage is lower to 96% */
/* SW setting */
/* [1:0] SW output 1.2V voltage is lower to 96% */
err |= ps8622_set(cl, 0x04, 0x14, 0x01);
Will use this.
/* RCO SS setting */
err |= ps8622_set(cl, 0x04, 0xe3, 0x20); /* [5:4] = b01 0.5%, b10
1%,
* b11 1.5% */
err |= ps8622_set(cl, 0x04, 0xe2, 0x80); /* [7] RCO SS enable */
/* RPHY Setting */
err |= ps8622_set(cl, 0x04, 0x8a, 0x0c); /* [3:2] CDR tune wait
cycle
* before measure for
fine tune
* b00: 1us b01: 0.5us
b10:2us
* b11: 4us */
err |= ps8622_set(cl, 0x04, 0x89, 0x08); /* [3] RFD always on */
err |= ps8622_set(cl, 0x04, 0x71, 0x2d); /* CTN lock in/out:
* 20000ppm/80000ppm.
* Lock out 2 times. */
/* 2.7G CDR settings */
err |= ps8622_set(cl, 0x04, 0x7d, 0x07); /* NOF=40LSB for HBR CDR
* setting */
err |= ps8622_set(cl, 0x04, 0x7b, 0x00); /* [1:0] Fmin=+4bands */
err |= ps8622_set(cl, 0x04, 0x7a, 0xfd); /* [7:5] DCO_FTRNG=+-40%
*/
/* 1.62G CDR settings */
err |= ps8622_set(cl, 0x04, 0xc0, 0x12); /* [5:2]NOF=64LSB [1:0]DCO
* scale is 2/5 */
err |= ps8622_set(cl, 0x04, 0xc1, 0x92); /* Gitune=-37% */
err |= ps8622_set(cl, 0x04, 0xc2, 0x1c); /* Fbstep=100% */
err |= ps8622_set(cl, 0x04, 0x32, 0x80); /* [7] LOS signal disable
*/
/* RPIO Setting */
err |= ps8622_set(cl, 0x04, 0x00, 0xb0); /* [7:4] LVDS driver bias
* current : 75% (250mV
swing)
* */
err |= ps8622_set(cl, 0x04, 0x15, 0x40); /* [7:6] Right-bar GPIO
output
* strength is 8mA */
/* EQ Training State Machine Setting */
err |= ps8622_set(cl, 0x04, 0x54, 0x10); /* RCO calibration start
*/
/* Logic, needs more than 10 I2C command */
err |= ps8622_set(cl, 0x01, 0x02, 0x80 |
ps_bridge->max_lane_count);
/* [4:0] MAX_LANE_COUNT
set to
* max supported lanes */
err |= ps8622_set(cl, 0x01, 0x21, 0x80 | ps_bridge->lane_count);
/* [4:0] LANE_COUNT_SET
set to
* chosen lane count */
err |= ps8622_set(cl, 0x00, 0x52, 0x20);
err |= ps8622_set(cl, 0x00, 0xf1, 0x03); /* HPD CP toggle enable */
err |= ps8622_set(cl, 0x00, 0x62, 0x41);
err |= ps8622_set(cl, 0x00, 0xf6, 0x01); /* Counter number, add 1ms
* counter delay */
err |= ps8622_set(cl, 0x00, 0x77, 0x06); /* [6]PWM function
control by
* DPCD0040f[7], default
is PWM
* block always works. */
err |= ps8622_set(cl, 0x00, 0x4c, 0x04); /* 04h Adjust VTotal
tolerance
* to fix the 30Hz no
display
* issue */
err |= ps8622_set(cl, 0x01, 0xc0, 0x00); /* DPCD00400='h00, Parade
OUI =
* 'h001cf8 */
err |= ps8622_set(cl, 0x01, 0xc1, 0x1c); /* DPCD00401='h1c */
err |= ps8622_set(cl, 0x01, 0xc2, 0xf8); /* DPCD00402='hf8 */
err |= ps8622_set(cl, 0x01, 0xc3, 0x44); /* DPCD403~408 = ASCII
code
* D2SLV5='h4432534c5635
*/
err |= ps8622_set(cl, 0x01, 0xc4, 0x32); /* DPCD404 */
err |= ps8622_set(cl, 0x01, 0xc5, 0x53); /* DPCD405 */
err |= ps8622_set(cl, 0x01, 0xc6, 0x4c); /* DPCD406 */
err |= ps8622_set(cl, 0x01, 0xc7, 0x56); /* DPCD407 */
err |= ps8622_set(cl, 0x01, 0xc8, 0x35); /* DPCD408 */
err |= ps8622_set(cl, 0x01, 0xca, 0x01); /* DPCD40A, Initial Code
major
* revision '01' */
err |= ps8622_set(cl, 0x01, 0xcb, 0x05); /* DPCD40B, Initial Code
minor
* revision '05' */
if (ps_bridge->bl) {
err |= ps8622_set(cl, 0x01, 0xa5, 0xa0);
/* DPCD720, internal PWM */
err |= ps8622_set(cl, 0x01, 0xa7,
ps_bridge->bl->props.brightness);
/* FFh for 100%
brightness,
* 0h for 0% brightness
*/
} else {
err |= ps8622_set(cl, 0x01, 0xa5, 0x80);
/* DPCD720, external PWM */
}
err |= ps8622_set(cl, 0x01, 0xcc, 0x13); /* Set LVDS output as
6bit-VESA
* mapping, single LVDS
channel
* */
err |= ps8622_set(cl, 0x02, 0xb1, 0x20); /* Enable SSC set by
register
* */
err |= ps8622_set(cl, 0x04, 0x10, 0x16); /* Set SSC enabled and
+/-1%
* central spreading */
/* Logic end */
err |= ps8622_set(cl, 0x04, 0x59, 0x60); /* MPU Clock source: LC
=> RCO
* */
err |= ps8622_set(cl, 0x04, 0x54, 0x14); /* LC -> RCO */
err |= ps8622_set(cl, 0x02, 0xa1, 0x91); /* HPD high */
return err ? -EIO : 0;
+}
[.....]
+static void ps8622_pre_enable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
int ret;
mutex_lock(&ps_bridge->enable_mutex);
if (ps_bridge->enabled)
goto out;
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 0);
if (ps_bridge->v12) {
ret = regulator_enable(ps_bridge->v12);
if (ret)
DRM_ERROR("fails to enable ps_bridge->v12");
}
drm_panel_pre_enable(ps_bridge->panel);
if (gpio_is_valid(ps_bridge->gpio_slp_n))
gpio_set_value(ps_bridge->gpio_slp_n, 1);
/*
* T1 is the range of time that it takes for the power to rise
after we
* enable the lcd fet. T2 is the range of time in which the data
sheet
* specifies we should deassert the reset pin.
*
* If it takes T1.max for the power to rise, we need to wait
atleast
* T2.min before deasserting the reset pin. If it takes T1.min for
the
* power to rise, we need to wait at most T2.max before
deasserting the
* reset pin.
*/
usleep_range(PS8622_RST_HIGH_T2_MIN_US +
PS8622_POWER_RISE_T1_MAX_US,
PS8622_RST_HIGH_T2_MAX_US +
PS8622_POWER_RISE_T1_MIN_US);
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 1);
ret = ps8622_send_config(ps_bridge);
if (ret)
DRM_ERROR("Failed to send config to bridge (%d)\n", ret);
ps_bridge->enabled = true;
mutex_unlock(&ps_bridge->enable_mutex);
return;
+out:
mutex_unlock(&ps_bridge->enable_mutex);
+}
mutex_unlock() is duplicated. Also, 'return' is unnecessary. Please fix it as below.
ps_bridge->enabled = true;
+out:
mutex_unlock(&ps_bridge->enable_mutex);
+}
Right. Will change it.
+static void ps8622_enable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
mutex_lock(&ps_bridge->enable_mutex);
if (ps_bridge->enabled)
drm_panel_enable(ps_bridge->panel);
mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_disable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
mutex_lock(&ps_bridge->enable_mutex);
if (!ps_bridge->enabled)
goto out;
ps_bridge->enabled = false;
drm_panel_disable(ps_bridge->panel);
msleep(PS8622_PWMO_END_T12_MS);
/*
* This doesn't matter if the regulators are turned off, but
something
* else might keep them on. In that case, we want to assert the
slp gpio
* to lower power.
*/
if (gpio_is_valid(ps_bridge->gpio_slp_n))
gpio_set_value(ps_bridge->gpio_slp_n, 0);
drm_panel_post_disable(ps_bridge->panel);
if (ps_bridge->v12)
regulator_disable(ps_bridge->v12);
/*
* Sleep for at least the amount of time that it takes the power
rail to
* fall to prevent asserting the rst gpio from doing anything.
*/
usleep_range(PS8622_POWER_FALL_T16_MAX_US,
2 * PS8622_POWER_FALL_T16_MAX_US);
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 0);
msleep(PS8622_POWER_OFF_T17_MS);
+out:
mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_post_disable(struct drm_bridge *bridge) +{ +}
How about just removing this empty function?
That is not possible. Because the bridge callbacks are called in this fashion: bridge->funcs->post_disable(bridge); So, if that particular callback turns NULL, it will crash!
[.....]
Best regards, Jingoo Han
Regards, Ajay Kumar
On Mon, Apr 21, 2014 at 6:39 PM, Ajay Kumar ajaykumar.rs@samsung.com wrote:
This patch adds a drm_bridge driver for the PS8622 DisplayPort to LVDS bridge chip.
Signed-off-by: Andrew Bresticker abrestic@chromium.org Signed-off-by: Sean Paul seanpaul@chromium.org Signed-off-by: Rahul Sharma rahul.sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Hi Ajay, I don't think that you should be claiming authorship of this driver, I don't see any commits from you in the chromium tree ([1] & [2]). The driver was originally written by vpalatin@chromium.org (who's completely missing from SoB), it should probably be attributed to him.
Sean
[1]- https://git.chromium.org/gitweb/?p=chromiumos/third_party/kernel-next.git;a=... [2]- https://git.chromium.org/gitweb/?p=chromiumos/third_party/kernel-next.git;a=...
Changes since V1: Pushing V1 for this as V2 because this patch holds good in this series.
drivers/gpu/drm/bridge/Kconfig | 7 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ps8622.c | 566 +++++++++++++++++++++++++++++++++++++++ include/drm/bridge/ps8622.h | 42 +++ 4 files changed, 616 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ps8622.c create mode 100644 include/drm/bridge/ps8622.h
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3bc6845..aba21fc 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -4,3 +4,10 @@ config DRM_PTN3460 select DRM_KMS_HELPER select DRM_PANEL ---help---
+config DRM_PS8622
tristate "Parade eDP/LVDS bridge"
depends on DRM
select DRM_KMS_HELPER
select DRM_PANEL
---help---
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index b4733e1..d1b5daa 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,3 +1,4 @@ ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o +obj-$(CONFIG_DRM_PS8622) += ps8622.o diff --git a/drivers/gpu/drm/bridge/ps8622.c b/drivers/gpu/drm/bridge/ps8622.c new file mode 100644 index 0000000..1e6b3ca --- /dev/null +++ b/drivers/gpu/drm/bridge/ps8622.c @@ -0,0 +1,566 @@ +/*
- Parade PS8622 eDP/LVDS bridge driver
- Copyright (C) 2014 Google, Inc.
- This software is licensed under the terms of the GNU General Public
- License version 2, as published by the Free Software Foundation, and
- may be copied, distributed, and modified under those terms.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/module.h> +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> +#include <drm/drm_panel.h>
+#include "drmP.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h"
+struct ps8622_bridge {
struct drm_connector connector;
struct drm_bridge *bridge;
struct drm_encoder *encoder;
struct drm_panel *panel;
struct i2c_client *client;
struct regulator *v12;
struct backlight_device *bl;
struct mutex enable_mutex;
int gpio_slp_n;
int gpio_rst_n;
u8 max_lane_count;
u8 lane_count;
bool enabled;
struct drm_display_mode mode;
+};
+struct ps8622_device_data {
u8 max_lane_count;
+};
+static const struct ps8622_device_data ps8622_data = {
.max_lane_count = 1,
+};
+static const struct ps8622_device_data ps8625_data = {
.max_lane_count = 2,
+};
+/* Brightness scale on the Parade chip */ +#define PS8622_MAX_BRIGHTNESS 0xff
+/* Timings taken from the version 1.7 datasheet for the PS8622/PS8625 */ +#define PS8622_POWER_RISE_T1_MIN_US 10 +#define PS8622_POWER_RISE_T1_MAX_US 10000 +#define PS8622_RST_HIGH_T2_MIN_US 3000 +#define PS8622_RST_HIGH_T2_MAX_US 30000 +#define PS8622_PWMO_END_T12_MS 200 +#define PS8622_POWER_FALL_T16_MAX_US 10000 +#define PS8622_POWER_OFF_T17_MS 500
+#if ((PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US) > \
(PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US))
+#error "T2.min + T1.max must be less than T2.max + T1.min" +#endif
+static int ps8622_set(struct i2c_client *client, u8 page, u8 reg, u8 val) +{
int ret;
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
u8 data[] = {reg, val};
msg.addr = client->addr + page;
msg.flags = 0;
msg.len = sizeof(data);
msg.buf = data;
ret = i2c_transfer(adap, &msg, 1);
if (ret != 1)
pr_warn("PS8622 I2C write (0x%02x,0x%02x,0x%02x) failed: %d\n",
client->addr + page, reg, val, ret);
return !(ret == 1);
+}
+static int ps8622_send_config(struct ps8622_bridge *ps_bridge) +{
struct i2c_client *cl = ps_bridge->client;
int err = 0;
/* wait 20ms after power ON */
usleep_range(20000, 30000);
err |= ps8622_set(cl, 0x02, 0xa1, 0x01); /* HPD low */
/* SW setting */
err |= ps8622_set(cl, 0x04, 0x14, 0x01); /* [1:0] SW output 1.2V voltage
* is lower to 96% */
/* RCO SS setting */
err |= ps8622_set(cl, 0x04, 0xe3, 0x20); /* [5:4] = b01 0.5%, b10 1%,
* b11 1.5% */
err |= ps8622_set(cl, 0x04, 0xe2, 0x80); /* [7] RCO SS enable */
/* RPHY Setting */
err |= ps8622_set(cl, 0x04, 0x8a, 0x0c); /* [3:2] CDR tune wait cycle
* before measure for fine tune
* b00: 1us b01: 0.5us b10:2us
* b11: 4us */
err |= ps8622_set(cl, 0x04, 0x89, 0x08); /* [3] RFD always on */
err |= ps8622_set(cl, 0x04, 0x71, 0x2d); /* CTN lock in/out:
* 20000ppm/80000ppm.
* Lock out 2 times. */
/* 2.7G CDR settings */
err |= ps8622_set(cl, 0x04, 0x7d, 0x07); /* NOF=40LSB for HBR CDR
* setting */
err |= ps8622_set(cl, 0x04, 0x7b, 0x00); /* [1:0] Fmin=+4bands */
err |= ps8622_set(cl, 0x04, 0x7a, 0xfd); /* [7:5] DCO_FTRNG=+-40% */
/* 1.62G CDR settings */
err |= ps8622_set(cl, 0x04, 0xc0, 0x12); /* [5:2]NOF=64LSB [1:0]DCO
* scale is 2/5 */
err |= ps8622_set(cl, 0x04, 0xc1, 0x92); /* Gitune=-37% */
err |= ps8622_set(cl, 0x04, 0xc2, 0x1c); /* Fbstep=100% */
err |= ps8622_set(cl, 0x04, 0x32, 0x80); /* [7] LOS signal disable */
/* RPIO Setting */
err |= ps8622_set(cl, 0x04, 0x00, 0xb0); /* [7:4] LVDS driver bias
* current : 75% (250mV swing)
* */
err |= ps8622_set(cl, 0x04, 0x15, 0x40); /* [7:6] Right-bar GPIO output
* strength is 8mA */
/* EQ Training State Machine Setting */
err |= ps8622_set(cl, 0x04, 0x54, 0x10); /* RCO calibration start */
/* Logic, needs more than 10 I2C command */
err |= ps8622_set(cl, 0x01, 0x02, 0x80 | ps_bridge->max_lane_count);
/* [4:0] MAX_LANE_COUNT set to
* max supported lanes */
err |= ps8622_set(cl, 0x01, 0x21, 0x80 | ps_bridge->lane_count);
/* [4:0] LANE_COUNT_SET set to
* chosen lane count */
err |= ps8622_set(cl, 0x00, 0x52, 0x20);
err |= ps8622_set(cl, 0x00, 0xf1, 0x03); /* HPD CP toggle enable */
err |= ps8622_set(cl, 0x00, 0x62, 0x41);
err |= ps8622_set(cl, 0x00, 0xf6, 0x01); /* Counter number, add 1ms
* counter delay */
err |= ps8622_set(cl, 0x00, 0x77, 0x06); /* [6]PWM function control by
* DPCD0040f[7], default is PWM
* block always works. */
err |= ps8622_set(cl, 0x00, 0x4c, 0x04); /* 04h Adjust VTotal tolerance
* to fix the 30Hz no display
* issue */
err |= ps8622_set(cl, 0x01, 0xc0, 0x00); /* DPCD00400='h00, Parade OUI =
* 'h001cf8 */
err |= ps8622_set(cl, 0x01, 0xc1, 0x1c); /* DPCD00401='h1c */
err |= ps8622_set(cl, 0x01, 0xc2, 0xf8); /* DPCD00402='hf8 */
err |= ps8622_set(cl, 0x01, 0xc3, 0x44); /* DPCD403~408 = ASCII code
* D2SLV5='h4432534c5635 */
err |= ps8622_set(cl, 0x01, 0xc4, 0x32); /* DPCD404 */
err |= ps8622_set(cl, 0x01, 0xc5, 0x53); /* DPCD405 */
err |= ps8622_set(cl, 0x01, 0xc6, 0x4c); /* DPCD406 */
err |= ps8622_set(cl, 0x01, 0xc7, 0x56); /* DPCD407 */
err |= ps8622_set(cl, 0x01, 0xc8, 0x35); /* DPCD408 */
err |= ps8622_set(cl, 0x01, 0xca, 0x01); /* DPCD40A, Initial Code major
* revision '01' */
err |= ps8622_set(cl, 0x01, 0xcb, 0x05); /* DPCD40B, Initial Code minor
* revision '05' */
if (ps_bridge->bl) {
err |= ps8622_set(cl, 0x01, 0xa5, 0xa0);
/* DPCD720, internal PWM */
err |= ps8622_set(cl, 0x01, 0xa7,
ps_bridge->bl->props.brightness);
/* FFh for 100% brightness,
* 0h for 0% brightness */
} else {
err |= ps8622_set(cl, 0x01, 0xa5, 0x80);
/* DPCD720, external PWM */
}
err |= ps8622_set(cl, 0x01, 0xcc, 0x13); /* Set LVDS output as 6bit-VESA
* mapping, single LVDS channel
* */
err |= ps8622_set(cl, 0x02, 0xb1, 0x20); /* Enable SSC set by register
* */
err |= ps8622_set(cl, 0x04, 0x10, 0x16); /* Set SSC enabled and +/-1%
* central spreading */
/* Logic end */
err |= ps8622_set(cl, 0x04, 0x59, 0x60); /* MPU Clock source: LC => RCO
* */
err |= ps8622_set(cl, 0x04, 0x54, 0x14); /* LC -> RCO */
err |= ps8622_set(cl, 0x02, 0xa1, 0x91); /* HPD high */
return err ? -EIO : 0;
+}
+static int ps8622_backlight_update(struct backlight_device *bl) +{
struct ps8622_bridge *ps_bridge = dev_get_drvdata(&bl->dev);
int ret, brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK ||
bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
brightness = 0;
mutex_lock(&ps_bridge->enable_mutex);
if (!ps_bridge->enabled) {
ret = -EINVAL;
goto out;
}
ret = ps8622_set(ps_bridge->client, 0x01, 0xa7, brightness);
+out:
mutex_unlock(&ps_bridge->enable_mutex);
return ret;
+}
+static int ps8622_backlight_get(struct backlight_device *bl) +{
return bl->props.brightness;
+}
+static const struct backlight_ops ps8622_backlight_ops = {
.update_status = ps8622_backlight_update,
.get_brightness = ps8622_backlight_get,
+};
+static void ps8622_pre_enable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
int ret;
mutex_lock(&ps_bridge->enable_mutex);
if (ps_bridge->enabled)
goto out;
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 0);
if (ps_bridge->v12) {
ret = regulator_enable(ps_bridge->v12);
if (ret)
DRM_ERROR("fails to enable ps_bridge->v12");
}
drm_panel_pre_enable(ps_bridge->panel);
if (gpio_is_valid(ps_bridge->gpio_slp_n))
gpio_set_value(ps_bridge->gpio_slp_n, 1);
/*
* T1 is the range of time that it takes for the power to rise after we
* enable the lcd fet. T2 is the range of time in which the data sheet
* specifies we should deassert the reset pin.
*
* If it takes T1.max for the power to rise, we need to wait atleast
* T2.min before deasserting the reset pin. If it takes T1.min for the
* power to rise, we need to wait at most T2.max before deasserting the
* reset pin.
*/
usleep_range(PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US,
PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US);
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 1);
ret = ps8622_send_config(ps_bridge);
if (ret)
DRM_ERROR("Failed to send config to bridge (%d)\n", ret);
ps_bridge->enabled = true;
mutex_unlock(&ps_bridge->enable_mutex);
return;
+out:
mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_enable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
mutex_lock(&ps_bridge->enable_mutex);
if (ps_bridge->enabled)
drm_panel_enable(ps_bridge->panel);
mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_disable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
mutex_lock(&ps_bridge->enable_mutex);
if (!ps_bridge->enabled)
goto out;
ps_bridge->enabled = false;
drm_panel_disable(ps_bridge->panel);
msleep(PS8622_PWMO_END_T12_MS);
/*
* This doesn't matter if the regulators are turned off, but something
* else might keep them on. In that case, we want to assert the slp gpio
* to lower power.
*/
if (gpio_is_valid(ps_bridge->gpio_slp_n))
gpio_set_value(ps_bridge->gpio_slp_n, 0);
drm_panel_post_disable(ps_bridge->panel);
if (ps_bridge->v12)
regulator_disable(ps_bridge->v12);
/*
* Sleep for at least the amount of time that it takes the power rail to
* fall to prevent asserting the rst gpio from doing anything.
*/
usleep_range(PS8622_POWER_FALL_T16_MAX_US,
2 * PS8622_POWER_FALL_T16_MAX_US);
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 0);
msleep(PS8622_POWER_OFF_T17_MS);
+out:
mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_post_disable(struct drm_bridge *bridge) +{ +}
+static void ps8622_destroy(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
drm_bridge_cleanup(bridge);
if (ps_bridge->bl)
backlight_device_unregister(ps_bridge->bl);
put_device(&ps_bridge->client->dev);
+}
+static const struct drm_bridge_funcs ps8622_bridge_funcs = {
.pre_enable = ps8622_pre_enable,
.enable = ps8622_enable,
.disable = ps8622_disable,
.post_disable = ps8622_post_disable,
.destroy = ps8622_destroy,
+};
+static int ps8622_get_modes(struct drm_connector *connector) +{
struct ps8622_bridge *ps_bridge;
struct drm_display_mode *new_mode;
ps_bridge = container_of(connector, struct ps8622_bridge, connector);
new_mode = drm_mode_duplicate(connector->dev, &ps_bridge->mode);
if (!new_mode) {
DRM_ERROR("Failed to duplicate ps_bridge mode!\n");
return 0;
}
drm_mode_probed_add(connector, new_mode);
return 1;
+}
+static int ps8622_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
+{
struct ps8622_bridge *ps_bridge;
ps_bridge = container_of(connector, struct ps8622_bridge, connector);
return drm_mode_equal(mode, &ps_bridge->mode) ? MODE_OK : MODE_BAD;
+}
+static struct drm_encoder *ps8622_best_encoder(struct drm_connector *connector) +{
struct ps8622_bridge *ps_bridge;
ps_bridge = container_of(connector, struct ps8622_bridge, connector);
return ps_bridge->encoder;
+}
+static const struct drm_connector_helper_funcs ps8622_connector_helper_funcs = {
.get_modes = ps8622_get_modes,
.mode_valid = ps8622_mode_valid,
.best_encoder = ps8622_best_encoder,
+};
+static enum drm_connector_status ps8622_detect(struct drm_connector *connector,
bool force)
+{
return connector_status_connected;
+}
+static void ps8622_connector_destroy(struct drm_connector *connector) +{
drm_connector_cleanup(connector);
+}
+static const struct drm_connector_funcs ps8622_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = ps8622_detect,
.destroy = ps8622_connector_destroy,
+};
+static const struct of_device_id ps8622_devices[] = {
{
.compatible = "parade,ps8622",
.data = &ps8622_data,
}, {
.compatible = "parade,ps8625",
.data = &ps8625_data,
}, {
/* end node */
}
+};
+int ps8622_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel)
+{
int ret;
const struct of_device_id *match;
const struct ps8622_device_data *device_data;
struct drm_bridge *bridge;
struct ps8622_bridge *ps_bridge;
node = of_find_matching_node_and_match(NULL, ps8622_devices, &match);
if (!node)
return -ENODEV;
bridge = devm_kzalloc(dev->dev, sizeof(*bridge), GFP_KERNEL);
if (!bridge) {
DRM_ERROR("Failed to allocate drm bridge\n");
return -ENOMEM;
}
ps_bridge = devm_kzalloc(dev->dev, sizeof(*ps_bridge), GFP_KERNEL);
if (!ps_bridge) {
DRM_ERROR("could not allocate ps bridge\n");
return -ENOMEM;
}
mutex_init(&ps_bridge->enable_mutex);
device_data = match->data;
ps_bridge->client = client;
ps_bridge->encoder = encoder;
ps_bridge->bridge = bridge;
ps_bridge->v12 = devm_regulator_get(&client->dev, "vdd_bridge");
if (IS_ERR(ps_bridge->v12)) {
DRM_INFO("no 1.2v regulator found for PS8622\n");
ps_bridge->v12 = NULL;
}
ps_bridge->gpio_slp_n = of_get_named_gpio(node, "sleep-gpio", 0);
if (gpio_is_valid(ps_bridge->gpio_slp_n)) {
ret = devm_gpio_request_one(&client->dev, ps_bridge->gpio_slp_n,
GPIOF_OUT_INIT_HIGH,
"PS8622_SLP_N");
if (ret)
goto err_client;
}
ps_bridge->gpio_rst_n = of_get_named_gpio(node, "reset-gpio", 0);
if (gpio_is_valid(ps_bridge->gpio_rst_n)) {
/*
* Assert the reset pin high to avoid the bridge being
* initialized prematurely
*/
ret = devm_gpio_request_one(&client->dev, ps_bridge->gpio_rst_n,
GPIOF_OUT_INIT_HIGH,
"PS8622_RST_N");
if (ret)
goto err_client;
}
ps_bridge->max_lane_count = device_data->max_lane_count;
if (of_property_read_u8(node, "lane-count", &ps_bridge->lane_count))
ps_bridge->lane_count = ps_bridge->max_lane_count;
else if (ps_bridge->lane_count > ps_bridge->max_lane_count) {
DRM_ERROR("lane-count property is too high for DP bridge\n");
ps_bridge->lane_count = ps_bridge->max_lane_count;
}
if (!of_find_property(node, "use-external-pwm", NULL)) {
ps_bridge->bl = backlight_device_register("ps8622-backlight",
dev->dev, ps_bridge, &ps8622_backlight_ops,
NULL);
if (IS_ERR(ps_bridge->bl)) {
DRM_ERROR("failed to register backlight\n");
ret = PTR_ERR(ps_bridge->bl);
ps_bridge->bl = NULL;
goto err_client;
}
ps_bridge->bl->props.max_brightness = PS8622_MAX_BRIGHTNESS;
ps_bridge->bl->props.brightness = PS8622_MAX_BRIGHTNESS;
}
ret = of_get_drm_display_mode(node, &ps_bridge->mode, 0);
if (ret) {
DRM_ERROR("Failed to get display mode, ret=%d\n", ret);
goto err_backlight;
}
ret = drm_bridge_init(dev, ps_bridge->bridge, &ps8622_bridge_funcs);
if (ret) {
DRM_ERROR("Failed to initialize bridge with drm\n");
goto err_backlight;
}
if (panel) {
ps_bridge->panel = panel;
drm_panel_attach(ps_bridge->panel, &ps_bridge->connector);
}
ps_bridge->bridge->driver_private = ps_bridge;
encoder->bridge = ps_bridge->bridge;
ps_bridge->enabled = false;
ps_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(dev, &ps_bridge->connector,
&ps8622_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
if (ret) {
DRM_ERROR("Failed to initialize connector with drm\n");
goto err_bridge;
}
drm_connector_helper_add(&ps_bridge->connector,
&ps8622_connector_helper_funcs);
drm_sysfs_connector_add(&ps_bridge->connector);
drm_mode_connector_attach_encoder(&ps_bridge->connector, encoder);
return 0;
+err_bridge:
drm_bridge_cleanup(ps_bridge->bridge);
+err_backlight:
if (ps_bridge->bl)
backlight_device_unregister(ps_bridge->bl);
+err_client:
put_device(&client->dev);
DRM_ERROR("device probe failed : %d\n", ret);
return ret;
+} +EXPORT_SYMBOL(ps8622_init); diff --git a/include/drm/bridge/ps8622.h b/include/drm/bridge/ps8622.h new file mode 100644 index 0000000..4fc1959 --- /dev/null +++ b/include/drm/bridge/ps8622.h @@ -0,0 +1,42 @@ +/*
- Copyright (C) 2014 Google, Inc.
- This software is licensed under the terms of the GNU General Public
- License version 2, as published by the Free Software Foundation, and
- may be copied, distributed, and modified under those terms.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef _DRM_BRIDGE_PS8622_H_ +#define _DRM_BRIDGE_PS8622_H_
+struct drm_device; +struct drm_encoder; +struct i2c_client; +struct device_node; +struct drm_panel;
+#ifdef CONFIG_DRM_PS8622
+extern int ps8622_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel);
+#else
+static inline int ps8622_init(struct drm_device *dev,
struct drm_encoder *encoder,
struct i2c_client *client,
struct device_node *node,
struct drm_panel *panel)
+{
return -ENODEV;
+}
+#endif
+#endif
1.7.9.5
Hi Sean,
On Tue, Apr 22, 2014 at 7:01 PM, Sean Paul seanpaul@google.com wrote:
On Mon, Apr 21, 2014 at 6:39 PM, Ajay Kumar ajaykumar.rs@samsung.com wrote:
This patch adds a drm_bridge driver for the PS8622 DisplayPort to LVDS bridge chip.
Signed-off-by: Andrew Bresticker abrestic@chromium.org Signed-off-by: Sean Paul seanpaul@chromium.org Signed-off-by: Rahul Sharma rahul.sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Hi Ajay, I don't think that you should be claiming authorship of this driver, I don't see any commits from you in the chromium tree ([1] & [2]). The driver was originally written by vpalatin@chromium.org (who's completely missing from SoB), it should probably be attributed to him.
Sean
Oops. I am really sorry and I didn't really want to claim the authorship for this patch. I missed it while doing "git format-patch". And, thanks for letting me know that Vincent was the actual author. I will add his authorship.
Regards, Ajay Kumar
[1]- https://git.chromium.org/gitweb/?p=chromiumos/third_party/kernel-next.git;a=... [2]- https://git.chromium.org/gitweb/?p=chromiumos/third_party/kernel-next.git;a=...
Changes since V1: Pushing V1 for this as V2 because this patch holds good in this series.
drivers/gpu/drm/bridge/Kconfig | 7 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/ps8622.c | 566 +++++++++++++++++++++++++++++++++++++++ include/drm/bridge/ps8622.h | 42 +++ 4 files changed, 616 insertions(+) create mode 100644 drivers/gpu/drm/bridge/ps8622.c create mode 100644 include/drm/bridge/ps8622.h
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3bc6845..aba21fc 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -4,3 +4,10 @@ config DRM_PTN3460 select DRM_KMS_HELPER select DRM_PANEL ---help---
+config DRM_PS8622
tristate "Parade eDP/LVDS bridge"
depends on DRM
select DRM_KMS_HELPER
select DRM_PANEL
---help---
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index b4733e1..d1b5daa 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,3 +1,4 @@ ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o +obj-$(CONFIG_DRM_PS8622) += ps8622.o diff --git a/drivers/gpu/drm/bridge/ps8622.c b/drivers/gpu/drm/bridge/ps8622.c new file mode 100644 index 0000000..1e6b3ca --- /dev/null +++ b/drivers/gpu/drm/bridge/ps8622.c @@ -0,0 +1,566 @@ +/*
- Parade PS8622 eDP/LVDS bridge driver
- Copyright (C) 2014 Google, Inc.
- This software is licensed under the terms of the GNU General Public
- License version 2, as published by the Free Software Foundation, and
- may be copied, distributed, and modified under those terms.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/module.h> +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/fb.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/pm.h> +#include <linux/regulator/consumer.h> +#include <drm/drm_panel.h>
+#include "drmP.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h"
+struct ps8622_bridge {
struct drm_connector connector;
struct drm_bridge *bridge;
struct drm_encoder *encoder;
struct drm_panel *panel;
struct i2c_client *client;
struct regulator *v12;
struct backlight_device *bl;
struct mutex enable_mutex;
int gpio_slp_n;
int gpio_rst_n;
u8 max_lane_count;
u8 lane_count;
bool enabled;
struct drm_display_mode mode;
+};
+struct ps8622_device_data {
u8 max_lane_count;
+};
+static const struct ps8622_device_data ps8622_data = {
.max_lane_count = 1,
+};
+static const struct ps8622_device_data ps8625_data = {
.max_lane_count = 2,
+};
+/* Brightness scale on the Parade chip */ +#define PS8622_MAX_BRIGHTNESS 0xff
+/* Timings taken from the version 1.7 datasheet for the PS8622/PS8625 */ +#define PS8622_POWER_RISE_T1_MIN_US 10 +#define PS8622_POWER_RISE_T1_MAX_US 10000 +#define PS8622_RST_HIGH_T2_MIN_US 3000 +#define PS8622_RST_HIGH_T2_MAX_US 30000 +#define PS8622_PWMO_END_T12_MS 200 +#define PS8622_POWER_FALL_T16_MAX_US 10000 +#define PS8622_POWER_OFF_T17_MS 500
+#if ((PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US) > \
(PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US))
+#error "T2.min + T1.max must be less than T2.max + T1.min" +#endif
+static int ps8622_set(struct i2c_client *client, u8 page, u8 reg, u8 val) +{
int ret;
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
u8 data[] = {reg, val};
msg.addr = client->addr + page;
msg.flags = 0;
msg.len = sizeof(data);
msg.buf = data;
ret = i2c_transfer(adap, &msg, 1);
if (ret != 1)
pr_warn("PS8622 I2C write (0x%02x,0x%02x,0x%02x) failed: %d\n",
client->addr + page, reg, val, ret);
return !(ret == 1);
+}
+static int ps8622_send_config(struct ps8622_bridge *ps_bridge) +{
struct i2c_client *cl = ps_bridge->client;
int err = 0;
/* wait 20ms after power ON */
usleep_range(20000, 30000);
err |= ps8622_set(cl, 0x02, 0xa1, 0x01); /* HPD low */
/* SW setting */
err |= ps8622_set(cl, 0x04, 0x14, 0x01); /* [1:0] SW output 1.2V voltage
* is lower to 96% */
/* RCO SS setting */
err |= ps8622_set(cl, 0x04, 0xe3, 0x20); /* [5:4] = b01 0.5%, b10 1%,
* b11 1.5% */
err |= ps8622_set(cl, 0x04, 0xe2, 0x80); /* [7] RCO SS enable */
/* RPHY Setting */
err |= ps8622_set(cl, 0x04, 0x8a, 0x0c); /* [3:2] CDR tune wait cycle
* before measure for fine tune
* b00: 1us b01: 0.5us b10:2us
* b11: 4us */
err |= ps8622_set(cl, 0x04, 0x89, 0x08); /* [3] RFD always on */
err |= ps8622_set(cl, 0x04, 0x71, 0x2d); /* CTN lock in/out:
* 20000ppm/80000ppm.
* Lock out 2 times. */
/* 2.7G CDR settings */
err |= ps8622_set(cl, 0x04, 0x7d, 0x07); /* NOF=40LSB for HBR CDR
* setting */
err |= ps8622_set(cl, 0x04, 0x7b, 0x00); /* [1:0] Fmin=+4bands */
err |= ps8622_set(cl, 0x04, 0x7a, 0xfd); /* [7:5] DCO_FTRNG=+-40% */
/* 1.62G CDR settings */
err |= ps8622_set(cl, 0x04, 0xc0, 0x12); /* [5:2]NOF=64LSB [1:0]DCO
* scale is 2/5 */
err |= ps8622_set(cl, 0x04, 0xc1, 0x92); /* Gitune=-37% */
err |= ps8622_set(cl, 0x04, 0xc2, 0x1c); /* Fbstep=100% */
err |= ps8622_set(cl, 0x04, 0x32, 0x80); /* [7] LOS signal disable */
/* RPIO Setting */
err |= ps8622_set(cl, 0x04, 0x00, 0xb0); /* [7:4] LVDS driver bias
* current : 75% (250mV swing)
* */
err |= ps8622_set(cl, 0x04, 0x15, 0x40); /* [7:6] Right-bar GPIO output
* strength is 8mA */
/* EQ Training State Machine Setting */
err |= ps8622_set(cl, 0x04, 0x54, 0x10); /* RCO calibration start */
/* Logic, needs more than 10 I2C command */
err |= ps8622_set(cl, 0x01, 0x02, 0x80 | ps_bridge->max_lane_count);
/* [4:0] MAX_LANE_COUNT set to
* max supported lanes */
err |= ps8622_set(cl, 0x01, 0x21, 0x80 | ps_bridge->lane_count);
/* [4:0] LANE_COUNT_SET set to
* chosen lane count */
err |= ps8622_set(cl, 0x00, 0x52, 0x20);
err |= ps8622_set(cl, 0x00, 0xf1, 0x03); /* HPD CP toggle enable */
err |= ps8622_set(cl, 0x00, 0x62, 0x41);
err |= ps8622_set(cl, 0x00, 0xf6, 0x01); /* Counter number, add 1ms
* counter delay */
err |= ps8622_set(cl, 0x00, 0x77, 0x06); /* [6]PWM function control by
* DPCD0040f[7], default is PWM
* block always works. */
err |= ps8622_set(cl, 0x00, 0x4c, 0x04); /* 04h Adjust VTotal tolerance
* to fix the 30Hz no display
* issue */
err |= ps8622_set(cl, 0x01, 0xc0, 0x00); /* DPCD00400='h00, Parade OUI =
* 'h001cf8 */
err |= ps8622_set(cl, 0x01, 0xc1, 0x1c); /* DPCD00401='h1c */
err |= ps8622_set(cl, 0x01, 0xc2, 0xf8); /* DPCD00402='hf8 */
err |= ps8622_set(cl, 0x01, 0xc3, 0x44); /* DPCD403~408 = ASCII code
* D2SLV5='h4432534c5635 */
err |= ps8622_set(cl, 0x01, 0xc4, 0x32); /* DPCD404 */
err |= ps8622_set(cl, 0x01, 0xc5, 0x53); /* DPCD405 */
err |= ps8622_set(cl, 0x01, 0xc6, 0x4c); /* DPCD406 */
err |= ps8622_set(cl, 0x01, 0xc7, 0x56); /* DPCD407 */
err |= ps8622_set(cl, 0x01, 0xc8, 0x35); /* DPCD408 */
err |= ps8622_set(cl, 0x01, 0xca, 0x01); /* DPCD40A, Initial Code major
* revision '01' */
err |= ps8622_set(cl, 0x01, 0xcb, 0x05); /* DPCD40B, Initial Code minor
* revision '05' */
if (ps_bridge->bl) {
err |= ps8622_set(cl, 0x01, 0xa5, 0xa0);
/* DPCD720, internal PWM */
err |= ps8622_set(cl, 0x01, 0xa7,
ps_bridge->bl->props.brightness);
/* FFh for 100% brightness,
* 0h for 0% brightness */
} else {
err |= ps8622_set(cl, 0x01, 0xa5, 0x80);
/* DPCD720, external PWM */
}
err |= ps8622_set(cl, 0x01, 0xcc, 0x13); /* Set LVDS output as 6bit-VESA
* mapping, single LVDS channel
* */
err |= ps8622_set(cl, 0x02, 0xb1, 0x20); /* Enable SSC set by register
* */
err |= ps8622_set(cl, 0x04, 0x10, 0x16); /* Set SSC enabled and +/-1%
* central spreading */
/* Logic end */
err |= ps8622_set(cl, 0x04, 0x59, 0x60); /* MPU Clock source: LC => RCO
* */
err |= ps8622_set(cl, 0x04, 0x54, 0x14); /* LC -> RCO */
err |= ps8622_set(cl, 0x02, 0xa1, 0x91); /* HPD high */
return err ? -EIO : 0;
+}
+static int ps8622_backlight_update(struct backlight_device *bl) +{
struct ps8622_bridge *ps_bridge = dev_get_drvdata(&bl->dev);
int ret, brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK ||
bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
brightness = 0;
mutex_lock(&ps_bridge->enable_mutex);
if (!ps_bridge->enabled) {
ret = -EINVAL;
goto out;
}
ret = ps8622_set(ps_bridge->client, 0x01, 0xa7, brightness);
+out:
mutex_unlock(&ps_bridge->enable_mutex);
return ret;
+}
+static int ps8622_backlight_get(struct backlight_device *bl) +{
return bl->props.brightness;
+}
+static const struct backlight_ops ps8622_backlight_ops = {
.update_status = ps8622_backlight_update,
.get_brightness = ps8622_backlight_get,
+};
+static void ps8622_pre_enable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
int ret;
mutex_lock(&ps_bridge->enable_mutex);
if (ps_bridge->enabled)
goto out;
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 0);
if (ps_bridge->v12) {
ret = regulator_enable(ps_bridge->v12);
if (ret)
DRM_ERROR("fails to enable ps_bridge->v12");
}
drm_panel_pre_enable(ps_bridge->panel);
if (gpio_is_valid(ps_bridge->gpio_slp_n))
gpio_set_value(ps_bridge->gpio_slp_n, 1);
/*
* T1 is the range of time that it takes for the power to rise after we
* enable the lcd fet. T2 is the range of time in which the data sheet
* specifies we should deassert the reset pin.
*
* If it takes T1.max for the power to rise, we need to wait atleast
* T2.min before deasserting the reset pin. If it takes T1.min for the
* power to rise, we need to wait at most T2.max before deasserting the
* reset pin.
*/
usleep_range(PS8622_RST_HIGH_T2_MIN_US + PS8622_POWER_RISE_T1_MAX_US,
PS8622_RST_HIGH_T2_MAX_US + PS8622_POWER_RISE_T1_MIN_US);
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 1);
ret = ps8622_send_config(ps_bridge);
if (ret)
DRM_ERROR("Failed to send config to bridge (%d)\n", ret);
ps_bridge->enabled = true;
mutex_unlock(&ps_bridge->enable_mutex);
return;
+out:
mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_enable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
mutex_lock(&ps_bridge->enable_mutex);
if (ps_bridge->enabled)
drm_panel_enable(ps_bridge->panel);
mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_disable(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
mutex_lock(&ps_bridge->enable_mutex);
if (!ps_bridge->enabled)
goto out;
ps_bridge->enabled = false;
drm_panel_disable(ps_bridge->panel);
msleep(PS8622_PWMO_END_T12_MS);
/*
* This doesn't matter if the regulators are turned off, but something
* else might keep them on. In that case, we want to assert the slp gpio
* to lower power.
*/
if (gpio_is_valid(ps_bridge->gpio_slp_n))
gpio_set_value(ps_bridge->gpio_slp_n, 0);
drm_panel_post_disable(ps_bridge->panel);
if (ps_bridge->v12)
regulator_disable(ps_bridge->v12);
/*
* Sleep for at least the amount of time that it takes the power rail to
* fall to prevent asserting the rst gpio from doing anything.
*/
usleep_range(PS8622_POWER_FALL_T16_MAX_US,
2 * PS8622_POWER_FALL_T16_MAX_US);
if (gpio_is_valid(ps_bridge->gpio_rst_n))
gpio_set_value(ps_bridge->gpio_rst_n, 0);
msleep(PS8622_POWER_OFF_T17_MS);
+out:
mutex_unlock(&ps_bridge->enable_mutex);
+}
+static void ps8622_post_disable(struct drm_bridge *bridge) +{ +}
+static void ps8622_destroy(struct drm_bridge *bridge) +{
struct ps8622_bridge *ps_bridge = bridge->driver_private;
drm_bridge_cleanup(bridge);
if (ps_bridge->bl)
backlight_device_unregister(ps_bridge->bl);
put_device(&ps_bridge->client->dev);
+}
+static const struct drm_bridge_funcs ps8622_bridge_funcs = {
.pre_enable = ps8622_pre_enable,
.enable = ps8622_enable,
.disable = ps8622_disable,
.post_disable = ps8622_post_disable,
.destroy = ps8622_destroy,
+};
+static int ps8622_get_modes(struct drm_connector *connector) +{
struct ps8622_bridge *ps_bridge;
struct drm_display_mode *new_mode;
ps_bridge = container_of(connector, struct ps8622_bridge, connector);
new_mode = drm_mode_duplicate(connector->dev, &ps_bridge->mode);
if (!new_mode) {
DRM_ERROR("Failed to duplicate ps_bridge mode!\n");
return 0;
}
drm_mode_probed_add(connector, new_mode);
return 1;
+}
+static int ps8622_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
+{
struct ps8622_bridge *ps_bridge;
ps_bridge = container_of(connector, struct ps8622_bridge, connector);
return drm_mode_equal(mode, &ps_bridge->mode) ? MODE_OK : MODE_BAD;
+}
+static struct drm_encoder *ps8622_best_encoder(struct drm_connector *connector) +{
struct ps8622_bridge *ps_bridge;
ps_bridge = container_of(connector, struct ps8622_bridge, connector);
return ps_bridge->encoder;
+}
+static const struct drm_connector_helper_funcs ps8622_connector_helper_funcs = {
.get_modes = ps8622_get_modes,
.mode_valid = ps8622_mode_valid,
.best_encoder = ps8622_best_encoder,
+};
+static enum drm_connector_status ps8622_detect(struct drm_connector *connector,
bool force)
+{
return connector_status_connected;
+}
+static void ps8622_connector_destroy(struct drm_connector *connector) +{
drm_connector_cleanup(connector);
+}
+static const struct drm_connector_funcs ps8622_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = ps8622_detect,
.destroy = ps8622_connector_destroy,
+};
+static const struct of_device_id ps8622_devices[] = {
{
.compatible = "parade,ps8622",
.data = &ps8622_data,
}, {
.compatible = "parade,ps8625",
.data = &ps8625_data,
}, {
/* end node */
}
+};
+int ps8622_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel)
+{
int ret;
const struct of_device_id *match;
const struct ps8622_device_data *device_data;
struct drm_bridge *bridge;
struct ps8622_bridge *ps_bridge;
node = of_find_matching_node_and_match(NULL, ps8622_devices, &match);
if (!node)
return -ENODEV;
bridge = devm_kzalloc(dev->dev, sizeof(*bridge), GFP_KERNEL);
if (!bridge) {
DRM_ERROR("Failed to allocate drm bridge\n");
return -ENOMEM;
}
ps_bridge = devm_kzalloc(dev->dev, sizeof(*ps_bridge), GFP_KERNEL);
if (!ps_bridge) {
DRM_ERROR("could not allocate ps bridge\n");
return -ENOMEM;
}
mutex_init(&ps_bridge->enable_mutex);
device_data = match->data;
ps_bridge->client = client;
ps_bridge->encoder = encoder;
ps_bridge->bridge = bridge;
ps_bridge->v12 = devm_regulator_get(&client->dev, "vdd_bridge");
if (IS_ERR(ps_bridge->v12)) {
DRM_INFO("no 1.2v regulator found for PS8622\n");
ps_bridge->v12 = NULL;
}
ps_bridge->gpio_slp_n = of_get_named_gpio(node, "sleep-gpio", 0);
if (gpio_is_valid(ps_bridge->gpio_slp_n)) {
ret = devm_gpio_request_one(&client->dev, ps_bridge->gpio_slp_n,
GPIOF_OUT_INIT_HIGH,
"PS8622_SLP_N");
if (ret)
goto err_client;
}
ps_bridge->gpio_rst_n = of_get_named_gpio(node, "reset-gpio", 0);
if (gpio_is_valid(ps_bridge->gpio_rst_n)) {
/*
* Assert the reset pin high to avoid the bridge being
* initialized prematurely
*/
ret = devm_gpio_request_one(&client->dev, ps_bridge->gpio_rst_n,
GPIOF_OUT_INIT_HIGH,
"PS8622_RST_N");
if (ret)
goto err_client;
}
ps_bridge->max_lane_count = device_data->max_lane_count;
if (of_property_read_u8(node, "lane-count", &ps_bridge->lane_count))
ps_bridge->lane_count = ps_bridge->max_lane_count;
else if (ps_bridge->lane_count > ps_bridge->max_lane_count) {
DRM_ERROR("lane-count property is too high for DP bridge\n");
ps_bridge->lane_count = ps_bridge->max_lane_count;
}
if (!of_find_property(node, "use-external-pwm", NULL)) {
ps_bridge->bl = backlight_device_register("ps8622-backlight",
dev->dev, ps_bridge, &ps8622_backlight_ops,
NULL);
if (IS_ERR(ps_bridge->bl)) {
DRM_ERROR("failed to register backlight\n");
ret = PTR_ERR(ps_bridge->bl);
ps_bridge->bl = NULL;
goto err_client;
}
ps_bridge->bl->props.max_brightness = PS8622_MAX_BRIGHTNESS;
ps_bridge->bl->props.brightness = PS8622_MAX_BRIGHTNESS;
}
ret = of_get_drm_display_mode(node, &ps_bridge->mode, 0);
if (ret) {
DRM_ERROR("Failed to get display mode, ret=%d\n", ret);
goto err_backlight;
}
ret = drm_bridge_init(dev, ps_bridge->bridge, &ps8622_bridge_funcs);
if (ret) {
DRM_ERROR("Failed to initialize bridge with drm\n");
goto err_backlight;
}
if (panel) {
ps_bridge->panel = panel;
drm_panel_attach(ps_bridge->panel, &ps_bridge->connector);
}
ps_bridge->bridge->driver_private = ps_bridge;
encoder->bridge = ps_bridge->bridge;
ps_bridge->enabled = false;
ps_bridge->connector.polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(dev, &ps_bridge->connector,
&ps8622_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
if (ret) {
DRM_ERROR("Failed to initialize connector with drm\n");
goto err_bridge;
}
drm_connector_helper_add(&ps_bridge->connector,
&ps8622_connector_helper_funcs);
drm_sysfs_connector_add(&ps_bridge->connector);
drm_mode_connector_attach_encoder(&ps_bridge->connector, encoder);
return 0;
+err_bridge:
drm_bridge_cleanup(ps_bridge->bridge);
+err_backlight:
if (ps_bridge->bl)
backlight_device_unregister(ps_bridge->bl);
+err_client:
put_device(&client->dev);
DRM_ERROR("device probe failed : %d\n", ret);
return ret;
+} +EXPORT_SYMBOL(ps8622_init); diff --git a/include/drm/bridge/ps8622.h b/include/drm/bridge/ps8622.h new file mode 100644 index 0000000..4fc1959 --- /dev/null +++ b/include/drm/bridge/ps8622.h @@ -0,0 +1,42 @@ +/*
- Copyright (C) 2014 Google, Inc.
- This software is licensed under the terms of the GNU General Public
- License version 2, as published by the Free Software Foundation, and
- may be copied, distributed, and modified under those terms.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#ifndef _DRM_BRIDGE_PS8622_H_ +#define _DRM_BRIDGE_PS8622_H_
+struct drm_device; +struct drm_encoder; +struct i2c_client; +struct device_node; +struct drm_panel;
+#ifdef CONFIG_DRM_PS8622
+extern int ps8622_init(struct drm_device *dev, struct drm_encoder *encoder,
struct i2c_client *client, struct device_node *node,
struct drm_panel *panel);
+#else
+static inline int ps8622_init(struct drm_device *dev,
struct drm_encoder *encoder,
struct i2c_client *client,
struct device_node *node,
struct drm_panel *panel)
+{
return -ENODEV;
+}
+#endif
+#endif
1.7.9.5
This patch adds ps8622 lvds bridge discovery code to the dp driver.
Signed-off-by: Rahul Sharma Rahul.Sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com --- Changes since V1: Pushing V1 for this as V2 because this patch holds good in this series.
drivers/gpu/drm/exynos/exynos_dp_core.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index 4853f31..0006412 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -30,6 +30,7 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_panel.h> #include <drm/bridge/ptn3460.h> +#include <drm/bridge/ps8622.h>
#include "exynos_drm_drv.h" #include "exynos_dp_core.h" @@ -999,7 +1000,15 @@ static int exynos_drm_attach_lcd_bridge(struct drm_device *dev, panel); if (!ret) return 1; + } else if (find_bridge("parade,ps8625", &bridge)) { + + ret = ps8622_init(dev, encoder, bridge.client, bridge.node, + panel); + + if (!ret) + return 1; } + return 0; }
On Tue, Apr 22, 2014 at 04:09:18AM +0530, Ajay Kumar wrote:
This patch adds ps8622 lvds bridge discovery code to the dp driver.
Signed-off-by: Rahul Sharma Rahul.Sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Pushing V1 for this as V2 because this patch holds good in this series.
drivers/gpu/drm/exynos/exynos_dp_core.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index 4853f31..0006412 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -30,6 +30,7 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_panel.h> #include <drm/bridge/ptn3460.h> +#include <drm/bridge/ps8622.h>
#include "exynos_drm_drv.h" #include "exynos_dp_core.h" @@ -999,7 +1000,15 @@ static int exynos_drm_attach_lcd_bridge(struct drm_device *dev, panel); if (!ret) return 1;
- } else if (find_bridge("parade,ps8625", &bridge)) {
So this is where the inspiration for the of_find_compatible_node() in the earlier patch came from.
ret = ps8622_init(dev, encoder, bridge.client, bridge.node,
panel);
Long-term I don't think this is going to scale very well. In my opinion it would be much more useful to have the bridge driver initialize what it can and then have the DRM driver call a generic initialization function to bind it to the DRM device or encoder. Otherwise we have to keep knowledge about the type of bridge in each driver that uses it, whereas the goal (I think) would be to create a proper abstraction so that the DRM driver doesn't have to know that kind of detail.
Thierry
Hi Thierry,
On Tue, Apr 22, 2014 at 2:57 PM, Thierry Reding thierry.reding@gmail.com wrote:
On Tue, Apr 22, 2014 at 04:09:18AM +0530, Ajay Kumar wrote:
This patch adds ps8622 lvds bridge discovery code to the dp driver.
Signed-off-by: Rahul Sharma Rahul.Sharma@samsung.com Signed-off-by: Ajay Kumar ajaykumar.rs@samsung.com
Changes since V1: Pushing V1 for this as V2 because this patch holds good in this series.
drivers/gpu/drm/exynos/exynos_dp_core.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c index 4853f31..0006412 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ b/drivers/gpu/drm/exynos/exynos_dp_core.c @@ -30,6 +30,7 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_panel.h> #include <drm/bridge/ptn3460.h> +#include <drm/bridge/ps8622.h>
#include "exynos_drm_drv.h" #include "exynos_dp_core.h" @@ -999,7 +1000,15 @@ static int exynos_drm_attach_lcd_bridge(struct drm_device *dev, panel); if (!ret) return 1;
} else if (find_bridge("parade,ps8625", &bridge)) {
So this is where the inspiration for the of_find_compatible_node() in the earlier patch came from.
I shall use phandle for that as suggested by you for previous patches.
ret = ps8622_init(dev, encoder, bridge.client, bridge.node,
panel);
Long-term I don't think this is going to scale very well. In my opinion it would be much more useful to have the bridge driver initialize what it can and then have the DRM driver call a generic initialization function to bind it to the DRM device or encoder. Otherwise we have to keep knowledge about the type of bridge in each driver that uses it, whereas the goal (I think) would be to create a proper abstraction so that the DRM driver doesn't have to know that kind of detail.
Well, having a generic initialization function makes more sense when we have multiple platforms supporting the 2 available bridge chips. Then we can let the bridge chip initialize first, and then call a drm_bridge search and bind function to search the bridge_list to find the bridge which has already got registered. But, this again poses a challenge that the bridge chip driver should be probed before the corresponding drm_encoder is trying to bind the bridge with drm_device/encoder.
So, I think the current way of explicitly calling bridgeXXX_init is fine, at least till multiple platforms start keeping track of all possible bridges they support, inside their respective drivers.
Thanks and regards, Ajay Kumar
dri-devel@lists.freedesktop.org