Commit b4eac546 authored by Dave Airlie's avatar Dave Airlie

Merge tag 'sunxi-drm-for-4.9' of...

Merge tag 'sunxi-drm-for-4.9' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux into drm-next

Allwinner DRM changes for 4.9

This tag adds the support of a new SoC to sun4i-drm (the Allwinner A33),
and the usual few fixes and enhancements

* tag 'sunxi-drm-for-4.9' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux:
  drm/sun4i: add missing header dependencies
  drm/sun4i: Add a DRC driver
  drm/sun4i: backend: Handle the SAT
  drm/sun4i: support A33 tcon
  drm/sun4i: support TCONs without channel 1
  drm/sun4i: Clear encoder->bridge if a bridge is not found
  drm/sun4i: rgb: add missing calls to drm_panel_{prepare,unprepare}
  drm/sun4i: Remove redundant dev_err call in sun4i_tcon_init_regmap()
  drm/sun4i: Add bridge support
  drm/sun4i: Move panel retrieval in RGB connector
  drm/sun4i: Store TCON's device structure pointer
parents 1f8ee720 0c3ff44c
...@@ -26,13 +26,14 @@ TCON ...@@ -26,13 +26,14 @@ TCON
The TCON acts as a timing controller for RGB, LVDS and TV interfaces. The TCON acts as a timing controller for RGB, LVDS and TV interfaces.
Required properties: Required properties:
- compatible: value should be "allwinner,sun5i-a13-tcon". - compatible: value must be either:
* allwinner,sun5i-a13-tcon
* allwinner,sun8i-a33-tcon
- reg: base address and size of memory-mapped region - reg: base address and size of memory-mapped region
- interrupts: interrupt associated to this IP - interrupts: interrupt associated to this IP
- clocks: phandles to the clocks feeding the TCON. Three are needed: - clocks: phandles to the clocks feeding the TCON. Three are needed:
- 'ahb': the interface clocks - 'ahb': the interface clocks
- 'tcon-ch0': The clock driving the TCON channel 0 - 'tcon-ch0': The clock driving the TCON channel 0
- 'tcon-ch1': The clock driving the TCON channel 1
- resets: phandles to the reset controllers driving the encoder - resets: phandles to the reset controllers driving the encoder
- "lcd": the reset line for the TCON channel 0 - "lcd": the reset line for the TCON channel 0
...@@ -49,6 +50,33 @@ Required properties: ...@@ -49,6 +50,33 @@ Required properties:
second the block connected to the TCON channel 1 (usually the TV second the block connected to the TCON channel 1 (usually the TV
encoder) encoder)
On the A13, there is one more clock required:
- 'tcon-ch1': The clock driving the TCON channel 1
DRC
---
The DRC (Dynamic Range Controller), found in the latest Allwinner SoCs
(A31, A23, A33), allows to dynamically adjust pixel
brightness/contrast based on histogram measurements for LCD content
adaptive backlight control.
Required properties:
- compatible: value must be one of:
* allwinner,sun8i-a33-drc
- reg: base address and size of the memory-mapped region.
- interrupts: interrupt associated to this IP
- clocks: phandles to the clocks feeding the DRC
* ahb: the DRC interface clock
* mod: the DRC module clock
* ram: the DRC DRAM clock
- clock-names: the clock names mentioned above
- resets: phandles to the reset line driving the DRC
- ports: A ports node with endpoint definitions as defined in
Documentation/devicetree/bindings/media/video-interfaces.txt. The
first port should be the input endpoints, the second one the outputs
Display Engine Backend Display Engine Backend
---------------------- ----------------------
...@@ -59,6 +87,7 @@ system. ...@@ -59,6 +87,7 @@ system.
Required properties: Required properties:
- compatible: value must be one of: - compatible: value must be one of:
* allwinner,sun5i-a13-display-backend * allwinner,sun5i-a13-display-backend
* allwinner,sun8i-a33-display-backend
- reg: base address and size of the memory-mapped region. - reg: base address and size of the memory-mapped region.
- clocks: phandles to the clocks feeding the frontend and backend - clocks: phandles to the clocks feeding the frontend and backend
* ahb: the backend interface clock * ahb: the backend interface clock
...@@ -71,6 +100,14 @@ Required properties: ...@@ -71,6 +100,14 @@ Required properties:
Documentation/devicetree/bindings/media/video-interfaces.txt. The Documentation/devicetree/bindings/media/video-interfaces.txt. The
first port should be the input endpoints, the second one the output first port should be the input endpoints, the second one the output
On the A33, some additional properties are required:
- reg needs to have an additional region corresponding to the SAT
- reg-names need to be set, with "be" and "sat"
- clocks and clock-names need to have a phandle to the SAT bus
clocks, whose name will be "sat"
- resets and reset-names need to have a phandle to the SAT bus
resets, whose name will be "sat"
Display Engine Frontend Display Engine Frontend
----------------------- -----------------------
...@@ -80,6 +117,7 @@ deinterlacing and color space conversion. ...@@ -80,6 +117,7 @@ deinterlacing and color space conversion.
Required properties: Required properties:
- compatible: value must be one of: - compatible: value must be one of:
* allwinner,sun5i-a13-display-frontend * allwinner,sun5i-a13-display-frontend
* allwinner,sun8i-a33-display-frontend
- reg: base address and size of the memory-mapped region. - reg: base address and size of the memory-mapped region.
- interrupts: interrupt associated to this IP - interrupts: interrupt associated to this IP
- clocks: phandles to the clocks feeding the frontend and backend - clocks: phandles to the clocks feeding the frontend and backend
...@@ -104,6 +142,7 @@ extra node. ...@@ -104,6 +142,7 @@ extra node.
Required properties: Required properties:
- compatible: value must be one of: - compatible: value must be one of:
* allwinner,sun5i-a13-display-engine * allwinner,sun5i-a13-display-engine
* allwinner,sun8i-a33-display-engine
- allwinner,pipelines: list of phandle to the display engine - allwinner,pipelines: list of phandle to the display engine
frontends available. frontends available.
......
...@@ -9,5 +9,5 @@ sun4i-tcon-y += sun4i_dotclock.o ...@@ -9,5 +9,5 @@ sun4i-tcon-y += sun4i_dotclock.o
obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o
obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o
obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o
obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o
...@@ -217,6 +217,51 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, ...@@ -217,6 +217,51 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend,
} }
EXPORT_SYMBOL(sun4i_backend_update_layer_buffer); EXPORT_SYMBOL(sun4i_backend_update_layer_buffer);
static int sun4i_backend_init_sat(struct device *dev) {
struct sun4i_backend *backend = dev_get_drvdata(dev);
int ret;
backend->sat_reset = devm_reset_control_get(dev, "sat");
if (IS_ERR(backend->sat_reset)) {
dev_err(dev, "Couldn't get the SAT reset line\n");
return PTR_ERR(backend->sat_reset);
}
ret = reset_control_deassert(backend->sat_reset);
if (ret) {
dev_err(dev, "Couldn't deassert the SAT reset line\n");
return ret;
}
backend->sat_clk = devm_clk_get(dev, "sat");
if (IS_ERR(backend->sat_clk)) {
dev_err(dev, "Couldn't get our SAT clock\n");
ret = PTR_ERR(backend->sat_clk);
goto err_assert_reset;
}
ret = clk_prepare_enable(backend->sat_clk);
if (ret) {
dev_err(dev, "Couldn't enable the SAT clock\n");
return ret;
}
return 0;
err_assert_reset:
reset_control_assert(backend->sat_reset);
return ret;
}
static int sun4i_backend_free_sat(struct device *dev) {
struct sun4i_backend *backend = dev_get_drvdata(dev);
clk_disable_unprepare(backend->sat_clk);
reset_control_assert(backend->sat_reset);
return 0;
}
static struct regmap_config sun4i_backend_regmap_config = { static struct regmap_config sun4i_backend_regmap_config = {
.reg_bits = 32, .reg_bits = 32,
.val_bits = 32, .val_bits = 32,
...@@ -291,6 +336,15 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, ...@@ -291,6 +336,15 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
} }
clk_prepare_enable(backend->ram_clk); clk_prepare_enable(backend->ram_clk);
if (of_device_is_compatible(dev->of_node,
"allwinner,sun8i-a33-display-backend")) {
ret = sun4i_backend_init_sat(dev);
if (ret) {
dev_err(dev, "Couldn't init SAT resources\n");
goto err_disable_ram_clk;
}
}
/* Reset the registers */ /* Reset the registers */
for (i = 0x800; i < 0x1000; i += 4) for (i = 0x800; i < 0x1000; i += 4)
regmap_write(backend->regs, i, 0); regmap_write(backend->regs, i, 0);
...@@ -306,6 +360,8 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, ...@@ -306,6 +360,8 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
return 0; return 0;
err_disable_ram_clk:
clk_disable_unprepare(backend->ram_clk);
err_disable_mod_clk: err_disable_mod_clk:
clk_disable_unprepare(backend->mod_clk); clk_disable_unprepare(backend->mod_clk);
err_disable_bus_clk: err_disable_bus_clk:
...@@ -320,6 +376,10 @@ static void sun4i_backend_unbind(struct device *dev, struct device *master, ...@@ -320,6 +376,10 @@ static void sun4i_backend_unbind(struct device *dev, struct device *master,
{ {
struct sun4i_backend *backend = dev_get_drvdata(dev); struct sun4i_backend *backend = dev_get_drvdata(dev);
if (of_device_is_compatible(dev->of_node,
"allwinner,sun8i-a33-display-backend"))
sun4i_backend_free_sat(dev);
clk_disable_unprepare(backend->ram_clk); clk_disable_unprepare(backend->ram_clk);
clk_disable_unprepare(backend->mod_clk); clk_disable_unprepare(backend->mod_clk);
clk_disable_unprepare(backend->bus_clk); clk_disable_unprepare(backend->bus_clk);
...@@ -345,6 +405,7 @@ static int sun4i_backend_remove(struct platform_device *pdev) ...@@ -345,6 +405,7 @@ static int sun4i_backend_remove(struct platform_device *pdev)
static const struct of_device_id sun4i_backend_of_table[] = { static const struct of_device_id sun4i_backend_of_table[] = {
{ .compatible = "allwinner,sun5i-a13-display-backend" }, { .compatible = "allwinner,sun5i-a13-display-backend" },
{ .compatible = "allwinner,sun8i-a33-display-backend" },
{ } { }
}; };
MODULE_DEVICE_TABLE(of, sun4i_backend_of_table); MODULE_DEVICE_TABLE(of, sun4i_backend_of_table);
......
...@@ -146,6 +146,9 @@ struct sun4i_backend { ...@@ -146,6 +146,9 @@ struct sun4i_backend {
struct clk *bus_clk; struct clk *bus_clk;
struct clk *mod_clk; struct clk *mod_clk;
struct clk *ram_clk; struct clk *ram_clk;
struct clk *sat_clk;
struct reset_control *sat_reset;
}; };
void sun4i_backend_apply_color_correction(struct sun4i_backend *backend); void sun4i_backend_apply_color_correction(struct sun4i_backend *backend);
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/regmap.h> #include <linux/regmap.h>
#include "sun4i_tcon.h" #include "sun4i_tcon.h"
#include "sun4i_dotclock.h"
struct sun4i_dclk { struct sun4i_dclk {
struct clk_hw hw; struct clk_hw hw;
......
...@@ -200,13 +200,14 @@ static const struct component_master_ops sun4i_drv_master_ops = { ...@@ -200,13 +200,14 @@ static const struct component_master_ops sun4i_drv_master_ops = {
static bool sun4i_drv_node_is_frontend(struct device_node *node) static bool sun4i_drv_node_is_frontend(struct device_node *node)
{ {
return of_device_is_compatible(node, return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
"allwinner,sun5i-a13-display-frontend"); of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend");
} }
static bool sun4i_drv_node_is_tcon(struct device_node *node) static bool sun4i_drv_node_is_tcon(struct device_node *node)
{ {
return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon"); return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") ||
of_device_is_compatible(node, "allwinner,sun8i-a33-tcon");
} }
static int compare_of(struct device *dev, void *data) static int compare_of(struct device *dev, void *data)
...@@ -258,8 +259,8 @@ static int sun4i_drv_add_endpoints(struct device *dev, ...@@ -258,8 +259,8 @@ static int sun4i_drv_add_endpoints(struct device *dev,
} }
/* /*
* If the node is our TCON, the first port is used for our * If the node is our TCON, the first port is used for
* panel, and will not be part of the * panel or bridges, and will not be part of the
* component framework. * component framework.
*/ */
if (sun4i_drv_node_is_tcon(node)) { if (sun4i_drv_node_is_tcon(node)) {
...@@ -321,6 +322,7 @@ static int sun4i_drv_remove(struct platform_device *pdev) ...@@ -321,6 +322,7 @@ static int sun4i_drv_remove(struct platform_device *pdev)
static const struct of_device_id sun4i_drv_of_table[] = { static const struct of_device_id sun4i_drv_of_table[] = {
{ .compatible = "allwinner,sun5i-a13-display-engine" }, { .compatible = "allwinner,sun5i-a13-display-engine" },
{ .compatible = "allwinner,sun8i-a33-display-engine" },
{ } { }
}; };
MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); MODULE_DEVICE_TABLE(of, sun4i_drv_of_table);
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <drm/drmP.h> #include <drm/drmP.h>
#include "sun4i_drv.h" #include "sun4i_drv.h"
#include "sun4i_framebuffer.h"
static void sun4i_de_output_poll_changed(struct drm_device *drm) static void sun4i_de_output_poll_changed(struct drm_device *drm)
{ {
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "sun4i_drv.h" #include "sun4i_drv.h"
#include "sun4i_tcon.h" #include "sun4i_tcon.h"
#include "sun4i_rgb.h"
struct sun4i_rgb { struct sun4i_rgb {
struct drm_connector connector; struct drm_connector connector;
...@@ -151,7 +152,14 @@ static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) ...@@ -151,7 +152,14 @@ static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder)
DRM_DEBUG_DRIVER("Enabling RGB output\n"); DRM_DEBUG_DRIVER("Enabling RGB output\n");
if (!IS_ERR(tcon->panel)) {
drm_panel_prepare(tcon->panel);
drm_panel_enable(tcon->panel); drm_panel_enable(tcon->panel);
}
/* encoder->bridge can be NULL; drm_bridge_enable checks for it */
drm_bridge_enable(encoder->bridge);
sun4i_tcon_channel_enable(tcon, 0); sun4i_tcon_channel_enable(tcon, 0);
} }
...@@ -164,7 +172,14 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) ...@@ -164,7 +172,14 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
DRM_DEBUG_DRIVER("Disabling RGB output\n"); DRM_DEBUG_DRIVER("Disabling RGB output\n");
sun4i_tcon_channel_disable(tcon, 0); sun4i_tcon_channel_disable(tcon, 0);
/* encoder->bridge can be NULL; drm_bridge_disable checks for it */
drm_bridge_disable(encoder->bridge);
if (!IS_ERR(tcon->panel)) {
drm_panel_disable(tcon->panel); drm_panel_disable(tcon->panel);
drm_panel_unprepare(tcon->panel);
}
} }
static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
...@@ -203,17 +218,22 @@ int sun4i_rgb_init(struct drm_device *drm) ...@@ -203,17 +218,22 @@ int sun4i_rgb_init(struct drm_device *drm)
{ {
struct sun4i_drv *drv = drm->dev_private; struct sun4i_drv *drv = drm->dev_private;
struct sun4i_tcon *tcon = drv->tcon; struct sun4i_tcon *tcon = drv->tcon;
struct drm_encoder *encoder;
struct sun4i_rgb *rgb; struct sun4i_rgb *rgb;
int ret; int ret;
/* If we don't have a panel, there's no point in going on */
if (IS_ERR(tcon->panel))
return -ENODEV;
rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL);
if (!rgb) if (!rgb)
return -ENOMEM; return -ENOMEM;
rgb->drv = drv; rgb->drv = drv;
encoder = &rgb->encoder;
tcon->panel = sun4i_tcon_find_panel(tcon->dev->of_node);
encoder->bridge = sun4i_tcon_find_bridge(tcon->dev->of_node);
if (IS_ERR(tcon->panel) && IS_ERR(encoder->bridge)) {
dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n");
return 0;
}
drm_encoder_helper_add(&rgb->encoder, drm_encoder_helper_add(&rgb->encoder,
&sun4i_rgb_enc_helper_funcs); &sun4i_rgb_enc_helper_funcs);
...@@ -230,6 +250,7 @@ int sun4i_rgb_init(struct drm_device *drm) ...@@ -230,6 +250,7 @@ int sun4i_rgb_init(struct drm_device *drm)
/* The RGB encoder can only work with the TCON channel 0 */ /* The RGB encoder can only work with the TCON channel 0 */
rgb->encoder.possible_crtcs = BIT(0); rgb->encoder.possible_crtcs = BIT(0);
if (!IS_ERR(tcon->panel)) {
drm_connector_helper_add(&rgb->connector, drm_connector_helper_add(&rgb->connector,
&sun4i_rgb_con_helper_funcs); &sun4i_rgb_con_helper_funcs);
ret = drm_connector_init(drm, &rgb->connector, ret = drm_connector_init(drm, &rgb->connector,
...@@ -240,9 +261,27 @@ int sun4i_rgb_init(struct drm_device *drm) ...@@ -240,9 +261,27 @@ int sun4i_rgb_init(struct drm_device *drm)
goto err_cleanup_connector; goto err_cleanup_connector;
} }
drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder); drm_mode_connector_attach_encoder(&rgb->connector,
&rgb->encoder);
drm_panel_attach(tcon->panel, &rgb->connector); ret = drm_panel_attach(tcon->panel, &rgb->connector);
if (ret) {
dev_err(drm->dev, "Couldn't attach our panel\n");
goto err_cleanup_connector;
}
}
if (!IS_ERR(encoder->bridge)) {
encoder->bridge->encoder = &rgb->encoder;
ret = drm_bridge_attach(drm, encoder->bridge);
if (ret) {
dev_err(drm->dev, "Couldn't attach our bridge\n");
goto err_cleanup_connector;
}
} else {
encoder->bridge = NULL;
}
return 0; return 0;
......
...@@ -59,11 +59,13 @@ void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) ...@@ -59,11 +59,13 @@ void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
SUN4I_TCON0_CTL_TCON_ENABLE, 0); SUN4I_TCON0_CTL_TCON_ENABLE, 0);
clk_disable_unprepare(tcon->dclk); clk_disable_unprepare(tcon->dclk);
} else if (channel == 1) { return;
}
WARN_ON(!tcon->has_channel_1);
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
SUN4I_TCON1_CTL_TCON_ENABLE, 0); SUN4I_TCON1_CTL_TCON_ENABLE, 0);
clk_disable_unprepare(tcon->sclk1); clk_disable_unprepare(tcon->sclk1);
}
} }
EXPORT_SYMBOL(sun4i_tcon_channel_disable); EXPORT_SYMBOL(sun4i_tcon_channel_disable);
...@@ -75,12 +77,14 @@ void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) ...@@ -75,12 +77,14 @@ void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
SUN4I_TCON0_CTL_TCON_ENABLE, SUN4I_TCON0_CTL_TCON_ENABLE,
SUN4I_TCON0_CTL_TCON_ENABLE); SUN4I_TCON0_CTL_TCON_ENABLE);
clk_prepare_enable(tcon->dclk); clk_prepare_enable(tcon->dclk);
} else if (channel == 1) { return;
}
WARN_ON(!tcon->has_channel_1);
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
SUN4I_TCON1_CTL_TCON_ENABLE, SUN4I_TCON1_CTL_TCON_ENABLE,
SUN4I_TCON1_CTL_TCON_ENABLE); SUN4I_TCON1_CTL_TCON_ENABLE);
clk_prepare_enable(tcon->sclk1); clk_prepare_enable(tcon->sclk1);
}
} }
EXPORT_SYMBOL(sun4i_tcon_channel_enable); EXPORT_SYMBOL(sun4i_tcon_channel_enable);
...@@ -198,6 +202,8 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, ...@@ -198,6 +202,8 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
u8 clk_delay; u8 clk_delay;
u32 val; u32 val;
WARN_ON(!tcon->has_channel_1);
/* Adjust clock delay */ /* Adjust clock delay */
clk_delay = sun4i_tcon_get_clk_delay(mode, 1); clk_delay = sun4i_tcon_get_clk_delay(mode, 1);
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
...@@ -321,11 +327,13 @@ static int sun4i_tcon_init_clocks(struct device *dev, ...@@ -321,11 +327,13 @@ static int sun4i_tcon_init_clocks(struct device *dev,
return PTR_ERR(tcon->sclk0); return PTR_ERR(tcon->sclk0);
} }
if (tcon->has_channel_1) {
tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); tcon->sclk1 = devm_clk_get(dev, "tcon-ch1");
if (IS_ERR(tcon->sclk1)) { if (IS_ERR(tcon->sclk1)) {
dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); dev_err(dev, "Couldn't get the TCON channel 1 clock\n");
return PTR_ERR(tcon->sclk1); return PTR_ERR(tcon->sclk1);
} }
}
return sun4i_dclk_create(dev, tcon); return sun4i_dclk_create(dev, tcon);
} }
...@@ -374,10 +382,8 @@ static int sun4i_tcon_init_regmap(struct device *dev, ...@@ -374,10 +382,8 @@ static int sun4i_tcon_init_regmap(struct device *dev,
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res); regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs)) { if (IS_ERR(regs))
dev_err(dev, "Couldn't map the TCON registers\n");
return PTR_ERR(regs); return PTR_ERR(regs);
}
tcon->regs = devm_regmap_init_mmio(dev, regs, tcon->regs = devm_regmap_init_mmio(dev, regs,
&sun4i_tcon_regmap_config); &sun4i_tcon_regmap_config);
...@@ -398,7 +404,7 @@ static int sun4i_tcon_init_regmap(struct device *dev, ...@@ -398,7 +404,7 @@ static int sun4i_tcon_init_regmap(struct device *dev,
return 0; return 0;
} }
static struct drm_panel *sun4i_tcon_find_panel(struct device_node *node) struct drm_panel *sun4i_tcon_find_panel(struct device_node *node)
{ {
struct device_node *port, *remote, *child; struct device_node *port, *remote, *child;
struct device_node *end_node = NULL; struct device_node *end_node = NULL;
...@@ -432,6 +438,40 @@ static struct drm_panel *sun4i_tcon_find_panel(struct device_node *node) ...@@ -432,6 +438,40 @@ static struct drm_panel *sun4i_tcon_find_panel(struct device_node *node)
return of_drm_find_panel(remote) ?: ERR_PTR(-EPROBE_DEFER); return of_drm_find_panel(remote) ?: ERR_PTR(-EPROBE_DEFER);
} }
struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node)
{
struct device_node *port, *remote, *child;
struct device_node *end_node = NULL;
/* Inputs are listed first, then outputs */
port = of_graph_get_port_by_id(node, 1);
/*
* Our first output is the RGB interface where the panel will
* be connected.
*/
for_each_child_of_node(port, child) {
u32 reg;
of_property_read_u32(child, "reg", &reg);
if (reg == 0)
end_node = child;
}
if (!end_node) {
DRM_DEBUG_DRIVER("Missing bridge endpoint\n");
return ERR_PTR(-ENODEV);
}
remote = of_graph_get_remote_port_parent(end_node);
if (!remote) {
DRM_DEBUG_DRIVER("Enable to parse remote node\n");
return ERR_PTR(-EINVAL);
}
return of_drm_find_bridge(remote) ?: ERR_PTR(-EPROBE_DEFER);
}
static int sun4i_tcon_bind(struct device *dev, struct device *master, static int sun4i_tcon_bind(struct device *dev, struct device *master,
void *data) void *data)
{ {
...@@ -446,9 +486,15 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, ...@@ -446,9 +486,15 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
dev_set_drvdata(dev, tcon); dev_set_drvdata(dev, tcon);
drv->tcon = tcon; drv->tcon = tcon;
tcon->drm = drm; tcon->drm = drm;
tcon->dev = dev;
if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a13-tcon")) if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a13-tcon")) {
tcon->has_mux = true; tcon->has_mux = true;
tcon->has_channel_1 = true;
} else {
tcon->has_mux = false;
tcon->has_channel_1 = false;
}
tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); tcon->lcd_rst = devm_reset_control_get(dev, "lcd");
if (IS_ERR(tcon->lcd_rst)) { if (IS_ERR(tcon->lcd_rst)) {
...@@ -484,12 +530,6 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, ...@@ -484,12 +530,6 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
goto err_free_clocks; goto err_free_clocks;
} }
tcon->panel = sun4i_tcon_find_panel(dev->of_node);
if (IS_ERR(tcon->panel)) {
dev_info(dev, "No panel found... RGB output disabled\n");
return 0;
}
ret = sun4i_rgb_init(drm); ret = sun4i_rgb_init(drm);
if (ret < 0) if (ret < 0)
goto err_free_clocks; goto err_free_clocks;
...@@ -519,19 +559,22 @@ static struct component_ops sun4i_tcon_ops = { ...@@ -519,19 +559,22 @@ static struct component_ops sun4i_tcon_ops = {
static int sun4i_tcon_probe(struct platform_device *pdev) static int sun4i_tcon_probe(struct platform_device *pdev)
{ {
struct device_node *node = pdev->dev.of_node; struct device_node *node = pdev->dev.of_node;
struct drm_bridge *bridge;
struct drm_panel *panel; struct drm_panel *panel;
/* /*
* The panel is not ready. * Neither the bridge or the panel is ready.
* Defer the probe. * Defer the probe.
*/ */
panel = sun4i_tcon_find_panel(node); panel = sun4i_tcon_find_panel(node);
bridge = sun4i_tcon_find_bridge(node);
/* /*
* If we don't have a panel endpoint, just go on * If we don't have a panel endpoint, just go on
*/ */
if (PTR_ERR(panel) == -EPROBE_DEFER) { if ((PTR_ERR(panel) == -EPROBE_DEFER) &&
DRM_DEBUG_DRIVER("Still waiting for our panel. Deferring...\n"); (PTR_ERR(bridge) == -EPROBE_DEFER)) {
DRM_DEBUG_DRIVER("Still waiting for our panel/bridge. Deferring...\n");
return -EPROBE_DEFER; return -EPROBE_DEFER;
} }
...@@ -547,6 +590,7 @@ static int sun4i_tcon_remove(struct platform_device *pdev) ...@@ -547,6 +590,7 @@ static int sun4i_tcon_remove(struct platform_device *pdev)
static const struct of_device_id sun4i_tcon_of_table[] = { static const struct of_device_id sun4i_tcon_of_table[] = {
{ .compatible = "allwinner,sun5i-a13-tcon" }, { .compatible = "allwinner,sun5i-a13-tcon" },
{ .compatible = "allwinner,sun8i-a33-tcon" },
{ } { }
}; };
MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table);
......
...@@ -143,6 +143,7 @@ ...@@ -143,6 +143,7 @@
#define SUN4I_TCON_MAX_CHANNELS 2 #define SUN4I_TCON_MAX_CHANNELS 2
struct sun4i_tcon { struct sun4i_tcon {
struct device *dev;
struct drm_device *drm; struct drm_device *drm;
struct regmap *regs; struct regmap *regs;
...@@ -163,8 +164,13 @@ struct sun4i_tcon { ...@@ -163,8 +164,13 @@ struct sun4i_tcon {
bool has_mux; bool has_mux;
struct drm_panel *panel; struct drm_panel *panel;
bool has_channel_1;
}; };
struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node);
struct drm_panel *sun4i_tcon_find_panel(struct device_node *node);
/* Global Control */ /* Global Control */
void sun4i_tcon_disable(struct sun4i_tcon *tcon); void sun4i_tcon_disable(struct sun4i_tcon *tcon);
void sun4i_tcon_enable(struct sun4i_tcon *tcon); void sun4i_tcon_enable(struct sun4i_tcon *tcon);
......
/*
* Copyright (C) 2016 Free Electrons
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
struct sun6i_drc {
struct clk *bus_clk;
struct clk *mod_clk;
struct reset_control *reset;
};
static int sun6i_drc_bind(struct device *dev, struct device *master,
void *data)
{
struct sun6i_drc *drc;
int ret;
drc = devm_kzalloc(dev, sizeof(*drc), GFP_KERNEL);
if (!drc)
return -ENOMEM;
dev_set_drvdata(dev, drc);
drc->reset = devm_reset_control_get(dev, NULL);
if (IS_ERR(drc->reset)) {
dev_err(dev, "Couldn't get our reset line\n");
return PTR_ERR(drc->reset);
}
ret = reset_control_deassert(drc->reset);
if (ret) {
dev_err(dev, "Couldn't deassert our reset line\n");
return ret;
}
drc->bus_clk = devm_clk_get(dev, "ahb");
if (IS_ERR(drc->bus_clk)) {
dev_err(dev, "Couldn't get our bus clock\n");
ret = PTR_ERR(drc->bus_clk);
goto err_assert_reset;
}
clk_prepare_enable(drc->bus_clk);
drc->mod_clk = devm_clk_get(dev, "mod");
if (IS_ERR(drc->mod_clk)) {
dev_err(dev, "Couldn't get our mod clock\n");
ret = PTR_ERR(drc->mod_clk);
goto err_disable_bus_clk;
}
clk_prepare_enable(drc->mod_clk);
return 0;
err_disable_bus_clk:
clk_disable_unprepare(drc->bus_clk);
err_assert_reset:
reset_control_assert(drc->reset);
return ret;
}
static void sun6i_drc_unbind(struct device *dev, struct device *master,
void *data)
{
struct sun6i_drc *drc = dev_get_drvdata(dev);
clk_disable_unprepare(drc->mod_clk);
clk_disable_unprepare(drc->bus_clk);
reset_control_assert(drc->reset);
}
static struct component_ops sun6i_drc_ops = {
.bind = sun6i_drc_bind,
.unbind = sun6i_drc_unbind,
};
static int sun6i_drc_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &sun6i_drc_ops);
}
static int sun6i_drc_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &sun6i_drc_ops);
return 0;
}
static const struct of_device_id sun6i_drc_of_table[] = {
{ .compatible = "allwinner,sun8i-a33-drc" },
{ }
};
MODULE_DEVICE_TABLE(of, sun6i_drc_of_table);
static struct platform_driver sun6i_drc_platform_driver = {
.probe = sun6i_drc_probe,
.remove = sun6i_drc_remove,
.driver = {
.name = "sun6i-drc",
.of_match_table = sun6i_drc_of_table,
},
};
module_platform_driver(sun6i_drc_platform_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Allwinner A31 Dynamic Range Control (DRC) Driver");
MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment