diff options
Diffstat (limited to 'drivers/gpu/drm/sun4i')
-rw-r--r-- | drivers/gpu/drm/sun4i/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_backend.c | 74 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_backend.h | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_dotclock.c | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_drv.c | 19 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_framebuffer.c | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_layer.c | 56 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_rgb.c | 71 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.c | 98 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.h | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tv.c | 46 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun6i_drc.c | 118 |
12 files changed, 407 insertions, 99 deletions
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..32c0584e3c35 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -83,8 +83,13 @@ void sun4i_backend_layer_enable(struct sun4i_backend *backend, } EXPORT_SYMBOL(sun4i_backend_layer_enable); -static int sun4i_backend_drm_format_to_layer(u32 format, u32 *mode) +static int sun4i_backend_drm_format_to_layer(struct drm_plane *plane, + u32 format, u32 *mode) { + if ((plane->type == DRM_PLANE_TYPE_PRIMARY) && + (format == DRM_FORMAT_ARGB8888)) + format = DRM_FORMAT_XRGB8888; + switch (format) { case DRM_FORMAT_ARGB8888: *mode = SUN4I_BACKEND_LAY_FBFMT_ARGB8888; @@ -164,7 +169,7 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, DRM_DEBUG_DRIVER("Switching display backend interlaced mode %s\n", interlaced ? "on" : "off"); - ret = sun4i_backend_drm_format_to_layer(fb->pixel_format, &val); + ret = sun4i_backend_drm_format_to_layer(plane, fb->pixel_format, &val); if (ret) { DRM_DEBUG_DRIVER("Invalid format\n"); return val; @@ -217,6 +222,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, @@ -243,10 +293,8 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, 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 backend registers\n"); + if (IS_ERR(regs)) return PTR_ERR(regs); - } backend->regs = devm_regmap_init_mmio(dev, regs, &sun4i_backend_regmap_config); @@ -291,6 +339,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 +363,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 +379,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 +408,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..83e63cc702b4 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -52,8 +52,8 @@ #define SUN4I_BACKEND_LAYFB_L32ADD_REG(l) (0x850 + (0x4 * (l))) #define SUN4I_BACKEND_LAYFB_H4ADD_REG 0x860 -#define SUN4I_BACKEND_LAYFB_H4ADD_MSK(l) GENMASK(3 + ((l) * 8), 0) -#define SUN4I_BACKEND_LAYFB_H4ADD(l, val) ((val) << ((l) * 8)) +#define SUN4I_BACKEND_LAYFB_H4ADD_MSK(l) GENMASK(3 + ((l) * 8), (l) * 8) +#define SUN4I_BACKEND_LAYFB_H4ADD(l, val) ((val) << ((l) * 8)) #define SUN4I_BACKEND_REGBUFFCTL_REG 0x870 #define SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS BIT(1) @@ -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..d401156490f3 100644 --- a/drivers/gpu/drm/sun4i/sun4i_dotclock.c +++ b/drivers/gpu/drm/sun4i/sun4i_dotclock.c @@ -14,6 +14,7 @@ #include <linux/regmap.h> #include "sun4i_tcon.h" +#include "sun4i_dotclock.h" struct sun4i_dclk { struct clk_hw hw; @@ -61,7 +62,7 @@ static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw, regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); val >>= SUN4I_TCON0_DCLK_DIV_SHIFT; - val &= SUN4I_TCON0_DCLK_DIV_WIDTH; + val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1; if (!val) val = 1; @@ -76,7 +77,7 @@ static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, u8 best_div = 1; int i; - for (i = 6; i < 127; i++) { + for (i = 6; i <= 127; i++) { unsigned long ideal = rate * i; unsigned long rounded; @@ -89,7 +90,8 @@ static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, goto out; } - if ((rounded < ideal) && (rounded > best_parent)) { + if (abs(rate - rounded / i) < + abs(rate - best_parent / best_div)) { best_parent = rounded; best_div = i; } diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 7092daaf6c43..0da9862ad8ed 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -17,6 +17,7 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_helper.h> #include "sun4i_crtc.h" #include "sun4i_drv.h" @@ -109,7 +110,7 @@ static void sun4i_remove_framebuffers(void) ap->ranges[0].base = 0; ap->ranges[0].size = ~0; - remove_conflicting_framebuffers(ap, "sun4i-drm-fb", false); + drm_fb_helper_remove_conflicting_framebuffers(ap, "sun4i-drm-fb", false); kfree(ap); } @@ -120,8 +121,8 @@ static int sun4i_drv_bind(struct device *dev) int ret; drm = drm_dev_alloc(&sun4i_drv_driver, dev); - if (!drm) - return -ENOMEM; + if (IS_ERR(drm)) + return PTR_ERR(drm); drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); if (!drv) { @@ -199,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) @@ -257,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)) { @@ -320,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 <drm/drmP.h> #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_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c index 068ab806309b..f0035bf5efea 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.c +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -19,7 +19,12 @@ #include "sun4i_drv.h" #include "sun4i_layer.h" -#define SUN4I_NUM_LAYERS 2 +struct sun4i_plane_desc { + enum drm_plane_type type; + u8 pipe; + const uint32_t *formats; + uint32_t nformats; +}; static int sun4i_backend_layer_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) @@ -65,14 +70,35 @@ static const struct drm_plane_funcs sun4i_backend_layer_funcs = { .update_plane = drm_atomic_helper_update_plane, }; -static const uint32_t sun4i_backend_layer_formats[] = { +static const uint32_t sun4i_backend_layer_formats_primary[] = { DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGB888, DRM_FORMAT_XRGB8888, +}; + +static const uint32_t sun4i_backend_layer_formats_overlay[] = { + DRM_FORMAT_ARGB8888, DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888, +}; + +static const struct sun4i_plane_desc sun4i_backend_planes[] = { + { + .type = DRM_PLANE_TYPE_PRIMARY, + .pipe = 0, + .formats = sun4i_backend_layer_formats_primary, + .nformats = ARRAY_SIZE(sun4i_backend_layer_formats_primary), + }, + { + .type = DRM_PLANE_TYPE_OVERLAY, + .pipe = 1, + .formats = sun4i_backend_layer_formats_overlay, + .nformats = ARRAY_SIZE(sun4i_backend_layer_formats_overlay), + }, }; static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, - enum drm_plane_type type) + const struct sun4i_plane_desc *plane) { struct sun4i_drv *drv = drm->dev_private; struct sun4i_layer *layer; @@ -84,10 +110,8 @@ static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, ret = drm_universal_plane_init(drm, &layer->plane, BIT(0), &sun4i_backend_layer_funcs, - sun4i_backend_layer_formats, - ARRAY_SIZE(sun4i_backend_layer_formats), - type, - NULL); + plane->formats, plane->nformats, + plane->type, NULL); if (ret) { dev_err(drm->dev, "Couldn't initialize layer\n"); return ERR_PTR(ret); @@ -97,7 +121,7 @@ static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, &sun4i_backend_layer_helper_funcs); layer->drv = drv; - if (type == DRM_PLANE_TYPE_PRIMARY) + if (plane->type == DRM_PLANE_TYPE_PRIMARY) drv->primary = &layer->plane; return layer; @@ -109,8 +133,8 @@ struct sun4i_layer **sun4i_layers_init(struct drm_device *drm) struct sun4i_layer **layers; int i; - layers = devm_kcalloc(drm->dev, SUN4I_NUM_LAYERS, sizeof(**layers), - GFP_KERNEL); + layers = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes), + sizeof(**layers), GFP_KERNEL); if (!layers) return ERR_PTR(-ENOMEM); @@ -135,13 +159,11 @@ struct sun4i_layer **sun4i_layers_init(struct drm_device *drm) * SoCs that support it, sprites could fill the need for more * layers. */ - for (i = 0; i < SUN4I_NUM_LAYERS; i++) { - enum drm_plane_type type = (i == 0) - ? DRM_PLANE_TYPE_PRIMARY - : DRM_PLANE_TYPE_OVERLAY; + for (i = 0; i < ARRAY_SIZE(sun4i_backend_planes); i++) { + const struct sun4i_plane_desc *plane = &sun4i_backend_planes[i]; struct sun4i_layer *layer = layers[i]; - layer = sun4i_layer_init_one(drm, type); + layer = sun4i_layer_init_one(drm, plane); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", i ? "overlay" : "primary"); @@ -149,10 +171,10 @@ struct sun4i_layer **sun4i_layers_init(struct drm_device *drm) }; DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n", - i ? "overlay" : "primary", i); + i ? "overlay" : "primary", plane->pipe); regmap_update_bits(drv->backend->regs, SUN4I_BACKEND_ATTCTL_REG0(i), SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK, - SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(i)); + SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(plane->pipe)); layer->id = i; }; 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/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index b84147896294..1dd3d9eabf2e 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -161,10 +161,10 @@ struct tv_mode { bool dac3_en; bool dac_bit25_en; - struct color_gains *color_gains; - struct burst_levels *burst_levels; - struct video_levels *video_levels; - struct resync_parameters *resync_params; + const struct color_gains *color_gains; + const struct burst_levels *burst_levels; + const struct video_levels *video_levels; + const struct resync_parameters *resync_params; }; struct sun4i_tv { @@ -178,39 +178,39 @@ struct sun4i_tv { struct sun4i_drv *drv; }; -struct video_levels ntsc_video_levels = { +static const struct video_levels ntsc_video_levels = { .black = 282, .blank = 240, }; -struct video_levels pal_video_levels = { +static const struct video_levels pal_video_levels = { .black = 252, .blank = 252, }; -struct burst_levels ntsc_burst_levels = { +static const struct burst_levels ntsc_burst_levels = { .cb = 79, .cr = 0, }; -struct burst_levels pal_burst_levels = { +static const struct burst_levels pal_burst_levels = { .cb = 40, .cr = 40, }; -struct color_gains ntsc_color_gains = { +static const struct color_gains ntsc_color_gains = { .cb = 160, .cr = 160, }; -struct color_gains pal_color_gains = { +static const struct color_gains pal_color_gains = { .cb = 224, .cr = 224, }; -struct resync_parameters ntsc_resync_parameters = { +static const struct resync_parameters ntsc_resync_parameters = { .field = false, .line = 14, .pixel = 12, }; -struct resync_parameters pal_resync_parameters = { +static const struct resync_parameters pal_resync_parameters = { .field = true, .line = 13, .pixel = 12, }; -struct tv_mode tv_modes[] = { +static const struct tv_mode tv_modes[] = { { .name = "NTSC", .mode = SUN4I_TVE_CFG0_RES_480i, @@ -289,13 +289,13 @@ drm_connector_to_sun4i_tv(struct drm_connector *connector) * So far, it doesn't seem to be preserved when the mode is passed by * to mode_set for some reason. */ -static struct tv_mode *sun4i_tv_find_tv_by_mode(struct drm_display_mode *mode) +static const struct tv_mode *sun4i_tv_find_tv_by_mode(const struct drm_display_mode *mode) { int i; /* First try to identify the mode by name */ for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { - struct tv_mode *tv_mode = &tv_modes[i]; + const struct tv_mode *tv_mode = &tv_modes[i]; DRM_DEBUG_DRIVER("Comparing mode %s vs %s", mode->name, tv_mode->name); @@ -306,7 +306,7 @@ static struct tv_mode *sun4i_tv_find_tv_by_mode(struct drm_display_mode *mode) /* Then by number of lines */ for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { - struct tv_mode *tv_mode = &tv_modes[i]; + const struct tv_mode *tv_mode = &tv_modes[i]; DRM_DEBUG_DRIVER("Comparing mode %s vs %s (X: %d vs %d)", mode->name, tv_mode->name, @@ -319,7 +319,7 @@ static struct tv_mode *sun4i_tv_find_tv_by_mode(struct drm_display_mode *mode) return NULL; } -static void sun4i_tv_mode_to_drm_mode(struct tv_mode *tv_mode, +static void sun4i_tv_mode_to_drm_mode(const struct tv_mode *tv_mode, struct drm_display_mode *mode) { DRM_DEBUG_DRIVER("Creating mode %s\n", mode->name); @@ -386,7 +386,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder, struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); struct sun4i_drv *drv = tv->drv; struct sun4i_tcon *tcon = drv->tcon; - struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); + const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); sun4i_tcon1_mode_set(tcon, mode); @@ -507,8 +507,14 @@ static int sun4i_tv_comp_get_modes(struct drm_connector *connector) int i; for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { - struct drm_display_mode *mode = drm_mode_create(connector->dev); - struct tv_mode *tv_mode = &tv_modes[i]; + struct drm_display_mode *mode; + const struct tv_mode *tv_mode = &tv_modes[i]; + + mode = drm_mode_create(connector->dev); + if (!mode) { + DRM_ERROR("Failed to create a new display mode\n"); + return 0; + } strcpy(mode->name, tv_mode->name); 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 <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"); |