diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index df8f4aeefe4c..b95696d748c7 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -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. diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 58cd55149827..d625a82a6e5f 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -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 diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index 3ab560450a82..91a702225ded 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -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); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h index 7070bb3434e5..e00718627748 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -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); diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.c b/drivers/gpu/drm/sun4i/sun4i_dotclock.c index 5b3463197c48..4332da48b1b3 100644 --- a/drivers/gpu/drm/sun4i/sun4i_dotclock.c +++ b/drivers/gpu/drm/sun4i/sun4i_dotclock.c @@ -14,6 +14,7 @@ #include #include "sun4i_tcon.h" +#include "sun4i_dotclock.h" struct sun4i_dclk { struct clk_hw hw; diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 8913c151b37f..9059e3ef9786 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -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); diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c index 70688febd7ac..8b6ce619ad81 100644 --- a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -15,6 +15,7 @@ #include #include "sun4i_drv.h" +#include "sun4i_framebuffer.h" static void sun4i_de_output_poll_changed(struct drm_device *drm) { diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c index f5bbac6efb4c..c3ff10f559cc 100644 --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -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; diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 652385f09735..cadacb517f95 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -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); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 0e0b11db401b..12bd48925f4d 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -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); diff --git a/drivers/gpu/drm/sun4i/sun6i_drc.c b/drivers/gpu/drm/sun4i/sun6i_drc.c new file mode 100644 index 000000000000..bf6d846d8132 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun6i_drc.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 Free Electrons + * + * Maxime Ripard + * + * 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 +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("Allwinner A31 Dynamic Range Control (DRC) Driver"); +MODULE_LICENSE("GPL");