1
0
Fork 0

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
steinar/wifi_calib_4_9_kernel
Dave Airlie 2016-09-13 10:24:52 +10:00
commit b4eac5465b
11 changed files with 365 additions and 51 deletions

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -14,6 +14,7 @@
#include <linux/regmap.h>
#include "sun4i_tcon.h"
#include "sun4i_dotclock.h"
struct sun4i_dclk {
struct clk_hw hw;

View File

@ -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);

View File

@ -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)
{

View File

@ -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;

View File

@ -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", &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);

View File

@ -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);

View File

@ -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");