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 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJX1cWUAAoJEBx+YmzsjxAgKfcP/ibcVnQa6dVhjegcMw/2pKow 0C2BIixZoiXnd1HybIE0wn/TCL15AyluQne6frhrPfgnl8AFSO60ZVHxFNDCCOq+ qqQvsUARpL0DUc31ThXyCaLRZc9u5KUFyxwX/QA8ut3vHI7wdIr+F1HNJqMG/Drd TJ1JqA3/n6Z7cuU8nuIAn0Xl14/pXKZk6HOBvwF3y+J/vi5XadYZPjYlaKFW6NZ0 4EyLOjLdpjzAeJaSnn9HIuOim2p48S9vo/sdYvjZVJkKUZsSuYewfvGCNyVXrDmN sVbGBnieUOUhnR0hDZxWDz9+Jgvgd7ApzfvVDv5TOK6avfHOr0TncYNk05xdHsYz XkpNrG8MAMK9h/bzVotZiT/nhEvIU3SPXrnJHbvcm2DXirN4wX3v5svA7OmTp6Rs PzeW8/ni0tGrKvKFONcAgpK2y1KlxfZPogfGvX703XFj6Dq6dIwDz1Ar8HgGCq7v j+n9PN3t6reMZVZjHKyuSzJJ+hozYQ5aolS76MsIc4/1KNhjTDVL53krTdA24iA7 Zrk6xpUbgnYsXOrqsRJ2Luq1aXP6KwKWjZxxF2k/jZUcMZ5HO6X6oIAOimimHabl rjesEhn17MIuBB5SfgXAReIVNDE1oWTIrLGjTOPVLG9WYKYJC2a6KafBpWm9TP+Q gO5xAJ3VsqMD209ZDlXD =532q -----END PGP SIGNATURE----- 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
This commit is contained in:
commit
b4eac5465b
|
@ -26,13 +26,14 @@ TCON
|
|||
The TCON acts as a timing controller for RGB, LVDS and TV interfaces.
|
||||
|
||||
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
|
||||
- interrupts: interrupt associated to this IP
|
||||
- clocks: phandles to the clocks feeding the TCON. Three are needed:
|
||||
- 'ahb': the interface clocks
|
||||
- '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
|
||||
- "lcd": the reset line for the TCON channel 0
|
||||
|
||||
|
@ -49,6 +50,33 @@ Required properties:
|
|||
second the block connected to the TCON channel 1 (usually the TV
|
||||
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
|
||||
----------------------
|
||||
|
@ -59,6 +87,7 @@ system.
|
|||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun5i-a13-display-backend
|
||||
* allwinner,sun8i-a33-display-backend
|
||||
- reg: base address and size of the memory-mapped region.
|
||||
- clocks: phandles to the clocks feeding the frontend and backend
|
||||
* ahb: the backend interface clock
|
||||
|
@ -71,6 +100,14 @@ Required properties:
|
|||
Documentation/devicetree/bindings/media/video-interfaces.txt. The
|
||||
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
|
||||
-----------------------
|
||||
|
||||
|
@ -80,6 +117,7 @@ deinterlacing and color space conversion.
|
|||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun5i-a13-display-frontend
|
||||
* allwinner,sun8i-a33-display-frontend
|
||||
- reg: base address and size of the memory-mapped region.
|
||||
- interrupts: interrupt associated to this IP
|
||||
- clocks: phandles to the clocks feeding the frontend and backend
|
||||
|
@ -104,6 +142,7 @@ extra node.
|
|||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun5i-a13-display-engine
|
||||
* allwinner,sun8i-a33-display-engine
|
||||
|
||||
- allwinner,pipelines: list of phandle to the display engine
|
||||
frontends available.
|
||||
|
|
|
@ -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_backend.o
|
||||
|
||||
obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o
|
||||
obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o
|
||||
|
|
|
@ -217,6 +217,51 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend,
|
|||
}
|
||||
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 = {
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
|
@ -291,6 +336,15 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
|
|||
}
|
||||
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 */
|
||||
for (i = 0x800; i < 0x1000; i += 4)
|
||||
regmap_write(backend->regs, i, 0);
|
||||
|
@ -306,6 +360,8 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
|
|||
|
||||
return 0;
|
||||
|
||||
err_disable_ram_clk:
|
||||
clk_disable_unprepare(backend->ram_clk);
|
||||
err_disable_mod_clk:
|
||||
clk_disable_unprepare(backend->mod_clk);
|
||||
err_disable_bus_clk:
|
||||
|
@ -320,6 +376,10 @@ static void sun4i_backend_unbind(struct device *dev, struct device *master,
|
|||
{
|
||||
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->mod_clk);
|
||||
clk_disable_unprepare(backend->bus_clk);
|
||||
|
@ -345,6 +405,7 @@ static int sun4i_backend_remove(struct platform_device *pdev)
|
|||
|
||||
static const struct of_device_id sun4i_backend_of_table[] = {
|
||||
{ .compatible = "allwinner,sun5i-a13-display-backend" },
|
||||
{ .compatible = "allwinner,sun8i-a33-display-backend" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun4i_backend_of_table);
|
||||
|
|
|
@ -146,6 +146,9 @@ struct sun4i_backend {
|
|||
struct clk *bus_clk;
|
||||
struct clk *mod_clk;
|
||||
struct clk *ram_clk;
|
||||
|
||||
struct clk *sat_clk;
|
||||
struct reset_control *sat_reset;
|
||||
};
|
||||
|
||||
void sun4i_backend_apply_color_correction(struct sun4i_backend *backend);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/regmap.h>
|
||||
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sun4i_dotclock.h"
|
||||
|
||||
struct sun4i_dclk {
|
||||
struct clk_hw hw;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
return of_device_is_compatible(node,
|
||||
"allwinner,sun5i-a13-display-frontend");
|
||||
return of_device_is_compatible(node, "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)
|
||||
{
|
||||
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)
|
||||
|
@ -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
|
||||
* panel, and will not be part of the
|
||||
* If the node is our TCON, the first port is used for
|
||||
* panel or bridges, and will not be part of the
|
||||
* component framework.
|
||||
*/
|
||||
if (sun4i_drv_node_is_tcon(node)) {
|
||||
|
@ -321,6 +322,7 @@ static int sun4i_drv_remove(struct platform_device *pdev)
|
|||
|
||||
static const struct of_device_id sun4i_drv_of_table[] = {
|
||||
{ .compatible = "allwinner,sun5i-a13-display-engine" },
|
||||
{ .compatible = "allwinner,sun8i-a33-display-engine" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun4i_drv_of_table);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <drm/drmP.h>
|
||||
|
||||
#include "sun4i_drv.h"
|
||||
#include "sun4i_framebuffer.h"
|
||||
|
||||
static void sun4i_de_output_poll_changed(struct drm_device *drm)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "sun4i_drv.h"
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sun4i_rgb.h"
|
||||
|
||||
struct sun4i_rgb {
|
||||
struct drm_connector connector;
|
||||
|
@ -151,7 +152,14 @@ static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder)
|
|||
|
||||
DRM_DEBUG_DRIVER("Enabling RGB output\n");
|
||||
|
||||
drm_panel_enable(tcon->panel);
|
||||
if (!IS_ERR(tcon->panel)) {
|
||||
drm_panel_prepare(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);
|
||||
}
|
||||
|
||||
|
@ -164,7 +172,14 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
|
|||
DRM_DEBUG_DRIVER("Disabling RGB output\n");
|
||||
|
||||
sun4i_tcon_channel_disable(tcon, 0);
|
||||
drm_panel_disable(tcon->panel);
|
||||
|
||||
/* 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_unprepare(tcon->panel);
|
||||
}
|
||||
}
|
||||
|
||||
static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
|
||||
|
@ -203,17 +218,22 @@ int sun4i_rgb_init(struct drm_device *drm)
|
|||
{
|
||||
struct sun4i_drv *drv = drm->dev_private;
|
||||
struct sun4i_tcon *tcon = drv->tcon;
|
||||
struct drm_encoder *encoder;
|
||||
struct sun4i_rgb *rgb;
|
||||
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);
|
||||
if (!rgb)
|
||||
return -ENOMEM;
|
||||
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,
|
||||
&sun4i_rgb_enc_helper_funcs);
|
||||
|
@ -230,19 +250,38 @@ int sun4i_rgb_init(struct drm_device *drm)
|
|||
/* The RGB encoder can only work with the TCON channel 0 */
|
||||
rgb->encoder.possible_crtcs = BIT(0);
|
||||
|
||||
drm_connector_helper_add(&rgb->connector,
|
||||
&sun4i_rgb_con_helper_funcs);
|
||||
ret = drm_connector_init(drm, &rgb->connector,
|
||||
&sun4i_rgb_con_funcs,
|
||||
DRM_MODE_CONNECTOR_Unknown);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "Couldn't initialise the rgb connector\n");
|
||||
goto err_cleanup_connector;
|
||||
if (!IS_ERR(tcon->panel)) {
|
||||
drm_connector_helper_add(&rgb->connector,
|
||||
&sun4i_rgb_con_helper_funcs);
|
||||
ret = drm_connector_init(drm, &rgb->connector,
|
||||
&sun4i_rgb_con_funcs,
|
||||
DRM_MODE_CONNECTOR_Unknown);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "Couldn't initialise the rgb connector\n");
|
||||
goto err_cleanup_connector;
|
||||
}
|
||||
|
||||
drm_mode_connector_attach_encoder(&rgb->connector,
|
||||
&rgb->encoder);
|
||||
|
||||
ret = drm_panel_attach(tcon->panel, &rgb->connector);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "Couldn't attach our panel\n");
|
||||
goto err_cleanup_connector;
|
||||
}
|
||||
}
|
||||
|
||||
drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder);
|
||||
if (!IS_ERR(encoder->bridge)) {
|
||||
encoder->bridge->encoder = &rgb->encoder;
|
||||
|
||||
drm_panel_attach(tcon->panel, &rgb->connector);
|
||||
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;
|
||||
|
||||
|
|
|
@ -59,11 +59,13 @@ void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
|
|||
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
|
||||
SUN4I_TCON0_CTL_TCON_ENABLE, 0);
|
||||
clk_disable_unprepare(tcon->dclk);
|
||||
} else if (channel == 1) {
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE, 0);
|
||||
clk_disable_unprepare(tcon->sclk1);
|
||||
return;
|
||||
}
|
||||
|
||||
WARN_ON(!tcon->has_channel_1);
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE, 0);
|
||||
clk_disable_unprepare(tcon->sclk1);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_channel_disable);
|
||||
|
||||
|
@ -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);
|
||||
clk_prepare_enable(tcon->dclk);
|
||||
} else if (channel == 1) {
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE);
|
||||
clk_prepare_enable(tcon->sclk1);
|
||||
return;
|
||||
}
|
||||
|
||||
WARN_ON(!tcon->has_channel_1);
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE);
|
||||
clk_prepare_enable(tcon->sclk1);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_channel_enable);
|
||||
|
||||
|
@ -198,6 +202,8 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
|
|||
u8 clk_delay;
|
||||
u32 val;
|
||||
|
||||
WARN_ON(!tcon->has_channel_1);
|
||||
|
||||
/* Adjust clock delay */
|
||||
clk_delay = sun4i_tcon_get_clk_delay(mode, 1);
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
|
||||
|
@ -321,10 +327,12 @@ static int sun4i_tcon_init_clocks(struct device *dev,
|
|||
return PTR_ERR(tcon->sclk0);
|
||||
}
|
||||
|
||||
tcon->sclk1 = devm_clk_get(dev, "tcon-ch1");
|
||||
if (IS_ERR(tcon->sclk1)) {
|
||||
dev_err(dev, "Couldn't get the TCON channel 1 clock\n");
|
||||
return PTR_ERR(tcon->sclk1);
|
||||
if (tcon->has_channel_1) {
|
||||
tcon->sclk1 = devm_clk_get(dev, "tcon-ch1");
|
||||
if (IS_ERR(tcon->sclk1)) {
|
||||
dev_err(dev, "Couldn't get the TCON channel 1 clock\n");
|
||||
return PTR_ERR(tcon->sclk1);
|
||||
}
|
||||
}
|
||||
|
||||
return sun4i_dclk_create(dev, tcon);
|
||||
|
@ -374,10 +382,8 @@ static int sun4i_tcon_init_regmap(struct device *dev,
|
|||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
regs = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(regs)) {
|
||||
dev_err(dev, "Couldn't map the TCON registers\n");
|
||||
if (IS_ERR(regs))
|
||||
return PTR_ERR(regs);
|
||||
}
|
||||
|
||||
tcon->regs = devm_regmap_init_mmio(dev, regs,
|
||||
&sun4i_tcon_regmap_config);
|
||||
|
@ -398,7 +404,7 @@ static int sun4i_tcon_init_regmap(struct device *dev,
|
|||
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 *end_node = NULL;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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", ®);
|
||||
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,
|
||||
void *data)
|
||||
{
|
||||
|
@ -446,9 +486,15 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
|
|||
dev_set_drvdata(dev, tcon);
|
||||
drv->tcon = tcon;
|
||||
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_channel_1 = true;
|
||||
} else {
|
||||
tcon->has_mux = false;
|
||||
tcon->has_channel_1 = false;
|
||||
}
|
||||
|
||||
tcon->lcd_rst = devm_reset_control_get(dev, "lcd");
|
||||
if (IS_ERR(tcon->lcd_rst)) {
|
||||
|
@ -484,12 +530,6 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
|
|||
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);
|
||||
if (ret < 0)
|
||||
goto err_free_clocks;
|
||||
|
@ -519,19 +559,22 @@ static struct component_ops sun4i_tcon_ops = {
|
|||
static int sun4i_tcon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct drm_bridge *bridge;
|
||||
struct drm_panel *panel;
|
||||
|
||||
/*
|
||||
* The panel is not ready.
|
||||
* Neither the bridge or the panel is ready.
|
||||
* Defer the probe.
|
||||
*/
|
||||
panel = sun4i_tcon_find_panel(node);
|
||||
bridge = sun4i_tcon_find_bridge(node);
|
||||
|
||||
/*
|
||||
* If we don't have a panel endpoint, just go on
|
||||
*/
|
||||
if (PTR_ERR(panel) == -EPROBE_DEFER) {
|
||||
DRM_DEBUG_DRIVER("Still waiting for our panel. Deferring...\n");
|
||||
if ((PTR_ERR(panel) == -EPROBE_DEFER) &&
|
||||
(PTR_ERR(bridge) == -EPROBE_DEFER)) {
|
||||
DRM_DEBUG_DRIVER("Still waiting for our panel/bridge. Deferring...\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
|
@ -547,6 +590,7 @@ static int sun4i_tcon_remove(struct platform_device *pdev)
|
|||
|
||||
static const struct of_device_id sun4i_tcon_of_table[] = {
|
||||
{ .compatible = "allwinner,sun5i-a13-tcon" },
|
||||
{ .compatible = "allwinner,sun8i-a33-tcon" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table);
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
#define SUN4I_TCON_MAX_CHANNELS 2
|
||||
|
||||
struct sun4i_tcon {
|
||||
struct device *dev;
|
||||
struct drm_device *drm;
|
||||
struct regmap *regs;
|
||||
|
||||
|
@ -163,8 +164,13 @@ struct sun4i_tcon {
|
|||
bool has_mux;
|
||||
|
||||
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 */
|
||||
void sun4i_tcon_disable(struct sun4i_tcon *tcon);
|
||||
void sun4i_tcon_enable(struct sun4i_tcon *tcon);
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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");
|
Loading…
Reference in New Issue