Hi everyone,
The Allwinner SoCs (except for the very latest ones) all share the same set of controllers, loosely coupled together to form the display pipeline.
Depending on the SoC, the number of instances of the controller will change (2 instances of each in the A10, only one in the A13, for example), and the output availables will change too (HDMI, composite, VGA on the A20, none of them on the A13).
On most featured SoCs, it looks like that:
+--------------------------------------------+ | RAM | +--------------------------------------------+ | | | | v | | v +----------------+ | | +----------------+ | Frontend | | | | Frontend | +----------------+ | | +----------------+ | | | | v | | v +----------------+ | | +----------------+ | Backend |<+ +>| Backend | +----------------+ +----------------+ | | v v +----------------+ +----------------+---> LVDS | TCON | | TCON |---> RGB +----------------+ +----------------+ | +---+ +---+ | | | | | v v v v +------------+ +------------+ +------------+---> VGA | TV Encoder | | HDMI | | TV Encoder |---> Composite +------------+ +------------+ +------------+
The current code only assumes that there is a single instance of all the controllers. It also supports only the RGB and Composite interfaces.
Let me know what you think, Maxime
Changes from v1: - Rebased on top of 4.4
- Merged the clock drivers for the display and TCON channel 0 clocks - Replaced the container_of calls in the display reset clocks to an inline function - Checked the return code of of_clk_parent_fill in the clocks drivers - Checked the return code of of_clk_add_provider in the tcon-ch1 and PLL3 clocks - Added missing clocks headers - Created a composite clock unregister function
- Moved the binding documentation to Documentation/devicetree/bindings/display - Added the clocks binding documentation - Added the Olimex vendor to the list of DT vendors - Moved to the OF graph representation and the component framework
- Moved the reset cells count check into the reset framework to avoid duplicating the code in every xlate implementation. - Made the reset_ops const
- Reworked the DRM cmdline mode parsing code to allow named mode - Fixed the TV mode lookup when the mode name is not present (for example because it was given by the userspace)
- Made the driver outputs optional (to avoid crashing when a board doesn't have either a panel or a composite output enabled) - Added multiple plane support with transparency - Moved the backend registers writes commit in the CRTC atomic_flush callback - Removed the load / unload functions - Removed the enabled booleans in my private structure and removed the implicit call to disable_unused_functions in the DRM core to push it in the drivers. - Fixed a few bitmasks on some bitfields definition - Fixed the RGB connector mode validation that was not testing the right values
Matthias Brugger (1): clk: Add regmap support
Maxime Ripard (25): reset: Move DT cell size check to the core reset: Make reset_control_ops const clk: composite: Add unregister function clk: sunxi: Add display and TCON0 clocks driver clk: sunxi: Add PLL3 clock clk: sunxi: Add TCON channel1 clock clk: sun5i: add DRAM gates ARM: sun5i: dt: Add pll3 and pll7 clocks ARM: sun5i: a13: Add display and TCON clocks ARM: sun5i: Add DRAM gates ARM: sun5i: Add TV encoder gate to the DTSI drm/fb_cma_helper: Remove implicit call to disable_unused_functions drm/modes: Rewrite the command line parser drm/modes: Support modes names on the command line drm: Add Allwinner A10 Display Engine support drm: sun4i: Add DT bindings documentation drm: sun4i: Add RGB output drm: sun4i: Add composite output drm: sun4i: tv: Add PAL output standard drm: sun4i: tv: Add NTSC output standard ARM: sun5i: r8: Add display blocks to the DTSI ARM: sun5i: chip: Enable the TV Encoder devicetree: Add olimex vendor prefix drm/panel: simple: Add timings for the Olimex LCD-OLinuXino-4.3TS DO NOT MERGE: ARM: sun5i: chip: Enable the LCD panel
Documentation/devicetree/bindings/clock/sunxi.txt | 5 + .../display/panel/olimex,lcd-olinuxino-43-ts.txt | 7 + .../bindings/display/sunxi/sun4i-drm.txt | 228 +++++++ .../devicetree/bindings/vendor-prefixes.txt | 1 + arch/arm/boot/dts/sun5i-a10s.dtsi | 7 +- arch/arm/boot/dts/sun5i-a13.dtsi | 46 +- arch/arm/boot/dts/sun5i-r8-chip.dts | 17 + arch/arm/boot/dts/sun5i-r8.dtsi | 139 ++++- arch/arm/boot/dts/sun5i.dtsi | 62 ++ drivers/clk/Makefile | 1 + drivers/clk/clk-composite.c | 15 + drivers/clk/clk-divider.c | 68 ++- drivers/clk/clk-gate.c | 54 +- drivers/clk/clk-io.c | 48 ++ drivers/clk/clk-io.h | 22 + drivers/clk/clk-mux.c | 94 ++- drivers/clk/sunxi/Makefile | 3 + drivers/clk/sunxi/clk-simple-gates.c | 2 + drivers/clk/sunxi/clk-sun4i-display.c | 241 ++++++++ drivers/clk/sunxi/clk-sun4i-pll3.c | 90 +++ drivers/clk/sunxi/clk-sun4i-tcon-ch1.c | 154 +++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 3 +- drivers/gpu/drm/drm_crtc.c | 3 +- drivers/gpu/drm/drm_fb_cma_helper.c | 3 - drivers/gpu/drm/drm_fb_helper.c | 4 + drivers/gpu/drm/drm_modes.c | 327 ++++++---- drivers/gpu/drm/imx/imx-drm-core.c | 1 + drivers/gpu/drm/panel/panel-simple.c | 26 + drivers/gpu/drm/sti/sti_drv.c | 1 + drivers/gpu/drm/sun4i/Kconfig | 14 + drivers/gpu/drm/sun4i/Makefile | 12 + drivers/gpu/drm/sun4i/sun4i_backend.c | 321 ++++++++++ drivers/gpu/drm/sun4i/sun4i_backend.h | 160 +++++ drivers/gpu/drm/sun4i/sun4i_crtc.c | 120 ++++ drivers/gpu/drm/sun4i/sun4i_crtc.h | 30 + drivers/gpu/drm/sun4i/sun4i_drv.c | 325 ++++++++++ drivers/gpu/drm/sun4i/sun4i_drv.h | 30 + drivers/gpu/drm/sun4i/sun4i_framebuffer.c | 54 ++ drivers/gpu/drm/sun4i/sun4i_framebuffer.h | 19 + drivers/gpu/drm/sun4i/sun4i_layer.c | 160 +++++ drivers/gpu/drm/sun4i/sun4i_layer.h | 30 + drivers/gpu/drm/sun4i/sun4i_rgb.c | 229 +++++++ drivers/gpu/drm/sun4i/sun4i_rgb.h | 18 + drivers/gpu/drm/sun4i/sun4i_tcon.c | 521 ++++++++++++++++ drivers/gpu/drm/sun4i/sun4i_tcon.h | 177 ++++++ drivers/gpu/drm/sun4i/sun4i_tv.c | 675 +++++++++++++++++++++ drivers/gpu/drm/tilcdc/tilcdc_drv.c | 1 + drivers/reset/core.c | 6 +- include/drm/drm_modes.h | 1 + include/linux/clk-provider.h | 55 +- include/linux/reset-controller.h | 2 +- 52 files changed, 4455 insertions(+), 179 deletions(-) create mode 100644 Documentation/devicetree/bindings/display/panel/olimex,lcd-olinuxino-43-ts.txt create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt create mode 100644 drivers/clk/clk-io.c create mode 100644 drivers/clk/clk-io.h create mode 100644 drivers/clk/sunxi/clk-sun4i-display.c create mode 100644 drivers/clk/sunxi/clk-sun4i-pll3.c create mode 100644 drivers/clk/sunxi/clk-sun4i-tcon-ch1.c create mode 100644 drivers/gpu/drm/sun4i/Kconfig create mode 100644 drivers/gpu/drm/sun4i/Makefile create mode 100644 drivers/gpu/drm/sun4i/sun4i_backend.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_backend.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_crtc.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_crtc.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_drv.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_drv.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_framebuffer.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_framebuffer.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_layer.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_layer.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_rgb.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_rgb.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_tcon.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_tcon.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_tv.c
The core currently doesn't check that the DT cell size matches what the driver declares, which means that every xlate function needs to duplicate that check.
Make sure that of_reset_control_get checks for this to avoid duplication and errors.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/reset/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 7955e00d04d4..d53b2b980bdd 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -46,9 +46,6 @@ struct reset_control { static int of_reset_simple_xlate(struct reset_controller_dev *rcdev, const struct of_phandle_args *reset_spec) { - if (WARN_ON(reset_spec->args_count != rcdev->of_reset_n_cells)) - return -EINVAL; - if (reset_spec->args[0] >= rcdev->nr_resets) return -EINVAL;
@@ -182,6 +179,9 @@ struct reset_control *of_reset_control_get(struct device_node *node, return ERR_PTR(-EPROBE_DEFER); }
+ if (WARN_ON(args.args_count != rcdev->of_reset_n_cells)) + return ERR_PTR(-EINVAL); + rstc_id = rcdev->of_xlate(rcdev, &args); if (rstc_id < 0) { mutex_unlock(&reset_controller_list_mutex);
Hi Maxime,
Am Donnerstag, den 14.01.2016, 16:24 +0100 schrieb Maxime Ripard:
The core currently doesn't check that the DT cell size matches what the driver declares, which means that every xlate function needs to duplicate that check.
Make sure that of_reset_control_get checks for this to avoid duplication and errors.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
drivers/reset/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 7955e00d04d4..d53b2b980bdd 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -46,9 +46,6 @@ struct reset_control { static int of_reset_simple_xlate(struct reset_controller_dev *rcdev, const struct of_phandle_args *reset_spec) {
- if (WARN_ON(reset_spec->args_count != rcdev->of_reset_n_cells))
return -EINVAL;
- if (reset_spec->args[0] >= rcdev->nr_resets) return -EINVAL;
@@ -182,6 +179,9 @@ struct reset_control *of_reset_control_get(struct device_node *node, return ERR_PTR(-EPROBE_DEFER); }
- if (WARN_ON(args.args_count != rcdev->of_reset_n_cells))
return ERR_PTR(-EINVAL);
Applied with this fix:
- if (WARN_ON(args.args_count != rcdev->of_reset_n_cells)) + if (WARN_ON(args.args_count != rcdev->of_reset_n_cells)) { + mutex_unlock(&reset_controller_list_mutex); return ERR_PTR(-EINVAL); + }
No further action needed if you agree, otherwise let me know and I'll back it out.
best regards Philipp
The ops pointer is holding a pointer to a structure that is usually not modified. Make it const.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- include/linux/reset-controller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/reset-controller.h b/include/linux/reset-controller.h index ce6b962ffed4..a3a5bcdb1d02 100644 --- a/include/linux/reset-controller.h +++ b/include/linux/reset-controller.h @@ -38,7 +38,7 @@ struct of_phandle_args; * @nr_resets: number of reset controls in this reset controller device */ struct reset_controller_dev { - struct reset_control_ops *ops; + const struct reset_control_ops *ops; struct module *owner; struct list_head list; struct device_node *of_node;
Am Donnerstag, den 14.01.2016, 16:24 +0100 schrieb Maxime Ripard:
The ops pointer is holding a pointer to a structure that is usually not modified. Make it const.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
include/linux/reset-controller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/reset-controller.h b/include/linux/reset-controller.h index ce6b962ffed4..a3a5bcdb1d02 100644 --- a/include/linux/reset-controller.h +++ b/include/linux/reset-controller.h @@ -38,7 +38,7 @@ struct of_phandle_args;
- @nr_resets: number of reset controls in this reset controller device
*/ struct reset_controller_dev {
- struct reset_control_ops *ops;
- const struct reset_control_ops *ops; struct module *owner; struct list_head list; struct device_node *of_node;
Applied, thanks.
regards Philipp
From: Matthias Brugger matthias.bgg@gmail.com
Some devices like SoCs from Mediatek need to use the clock through a regmap interface. This patch adds regmap support for the simple multiplexer clock, the divider clock and the clock gate code.
Signed-off-by: Matthias Brugger matthias.bgg@gmail.com Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/clk/Makefile | 1 + drivers/clk/clk-divider.c | 68 +++++++++++++++++++++++++------- drivers/clk/clk-gate.c | 54 +++++++++++++++++++------ drivers/clk/clk-io.c | 48 ++++++++++++++++++++++ drivers/clk/clk-io.h | 22 +++++++++++ drivers/clk/clk-mux.c | 94 +++++++++++++++++++++++++++++++++++++------- include/linux/clk-provider.h | 54 +++++++++++++++++++++++-- 7 files changed, 298 insertions(+), 43 deletions(-) create mode 100644 drivers/clk/clk-io.c create mode 100644 drivers/clk/clk-io.h
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 820714c72d36..31a888c0c182 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_COMMON_CLK) += clk-mux.o obj-$(CONFIG_COMMON_CLK) += clk-composite.o obj-$(CONFIG_COMMON_CLK) += clk-fractional-divider.o obj-$(CONFIG_COMMON_CLK) += clk-gpio.o +obj-$(CONFIG_COMMON_CLK) += clk-io.o ifeq ($(CONFIG_OF), y) obj-$(CONFIG_COMMON_CLK) += clk-conf.o endif diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index 3ace102a2a0a..1ddb353b3fff 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -18,6 +18,8 @@ #include <linux/string.h> #include <linux/log2.h>
+#include "clk-io.h" + /* * DOC: basic adjustable divider clock that cannot gate * @@ -142,7 +144,8 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw, struct clk_divider *divider = to_clk_divider(hw); unsigned int val;
- val = clk_readl(divider->reg) >> divider->shift; + val = clk_io_readl(hw, divider->reg, divider->regmap, divider->offset); + val >>= divider->shift; val &= div_mask(divider->width);
return divider_recalc_rate(hw, parent_rate, val, divider->table, @@ -354,7 +357,10 @@ static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
/* if read only, just return current value */ if (divider->flags & CLK_DIVIDER_READ_ONLY) { - bestdiv = readl(divider->reg) >> divider->shift; + bestdiv = clk_io_readl(hw, divider->reg, divider->regmap, + divider->offset); + + bestdiv >>= divider->shift; bestdiv &= div_mask(divider->width); bestdiv = _get_div(divider->table, bestdiv, divider->flags, divider->width); @@ -400,12 +406,16 @@ static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
if (divider->flags & CLK_DIVIDER_HIWORD_MASK) { val = div_mask(divider->width) << (divider->shift + 16); + val |= value << divider->shift; + clk_io_writel(hw, divider->reg, divider->regmap, + divider->offset, val); } else { - val = clk_readl(divider->reg); - val &= ~(div_mask(divider->width) << divider->shift); + u32 mask = div_mask(divider->width) << divider->shift; + + val = value << divider->shift; + clk_io_update_bits(hw, divider->reg, divider->regmap, + divider->offset, mask, val); } - val |= value << divider->shift; - clk_writel(val, divider->reg);
if (divider->lock) spin_unlock_irqrestore(divider->lock, flags); @@ -424,9 +434,9 @@ EXPORT_SYMBOL_GPL(clk_divider_ops);
static struct clk *_register_divider(struct device *dev, const char *name, const char *parent_name, unsigned long flags, - void __iomem *reg, u8 shift, u8 width, - u8 clk_divider_flags, const struct clk_div_table *table, - spinlock_t *lock) + void __iomem *reg, struct regmap *regmap, u32 offset, + u8 shift, u8 width, u8 clk_divider_flags, + const struct clk_div_table *table, spinlock_t *lock) { struct clk_divider *div; struct clk *clk; @@ -451,7 +461,12 @@ static struct clk *_register_divider(struct device *dev, const char *name, init.num_parents = (parent_name ? 1 : 0);
/* struct clk_divider assignments */ - div->reg = reg; + if (flags & CLK_USE_REGMAP) + div->regmap = regmap; + else + div->reg = reg; + + div->offset = offset; div->shift = shift; div->width = width; div->flags = clk_divider_flags; @@ -485,8 +500,8 @@ struct clk *clk_register_divider(struct device *dev, const char *name, void __iomem *reg, u8 shift, u8 width, u8 clk_divider_flags, spinlock_t *lock) { - return _register_divider(dev, name, parent_name, flags, reg, shift, - width, clk_divider_flags, NULL, lock); + return _register_divider(dev, name, parent_name, flags, reg, NULL, 0, + shift, width, clk_divider_flags, NULL, lock); } EXPORT_SYMBOL_GPL(clk_register_divider);
@@ -510,8 +525,8 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name, u8 clk_divider_flags, const struct clk_div_table *table, spinlock_t *lock) { - return _register_divider(dev, name, parent_name, flags, reg, shift, - width, clk_divider_flags, table, lock); + return _register_divider(dev, name, parent_name, flags, reg, NULL, 0, + shift, width, clk_divider_flags, table, lock); } EXPORT_SYMBOL_GPL(clk_register_divider_table);
@@ -530,3 +545,28 @@ void clk_unregister_divider(struct clk *clk) kfree(div); } EXPORT_SYMBOL_GPL(clk_unregister_divider); + +struct clk *clk_regm_register_divider(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + struct regmap *regmap, u32 offset, u8 shift, u8 width, + u8 clk_divider_flags, spinlock_t *lock) +{ + flags |= CLK_USE_REGMAP; + + return _register_divider(dev, name, parent_name, flags, NULL, regmap, + offset, shift, width, clk_divider_flags, NULL, lock); +} +EXPORT_SYMBOL_GPL(clk_regm_register_divider); + +struct clk *clk_regm_register_divider_table(struct device *dev, + const char *name, const char *parent_name, unsigned long flags, + struct regmap *regmap, u32 offset, u8 shift, u8 width, + u8 clk_divider_flags, const struct clk_div_table *table, + spinlock_t *lock) +{ + flags |= CLK_USE_REGMAP; + + return _register_divider(dev, name, parent_name, flags, NULL, regmap, + offset, shift, width, clk_divider_flags, table, lock); +} +EXPORT_SYMBOL_GPL(clk_regm_register_divider_table); diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c index de0b322f5f58..38b558397631 100644 --- a/drivers/clk/clk-gate.c +++ b/drivers/clk/clk-gate.c @@ -16,6 +16,8 @@ #include <linux/err.h> #include <linux/string.h>
+#include "clk-io.h" + /** * DOC: basic gatable clock which can gate and ungate it's ouput * @@ -46,7 +48,7 @@ static void clk_gate_endisable(struct clk_hw *hw, int enable) struct clk_gate *gate = to_clk_gate(hw); int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0; unsigned long uninitialized_var(flags); - u32 reg; + u32 reg, mask;
set ^= enable;
@@ -59,16 +61,20 @@ static void clk_gate_endisable(struct clk_hw *hw, int enable) reg = BIT(gate->bit_idx + 16); if (set) reg |= BIT(gate->bit_idx); - } else { - reg = clk_readl(gate->reg);
+ clk_io_writel(hw, gate->reg, gate->regmap, gate->offset, reg); + } else { if (set) - reg |= BIT(gate->bit_idx); + reg = BIT(gate->bit_idx); else - reg &= ~BIT(gate->bit_idx); + reg = 0x0; + + mask = BIT(gate->bit_idx); + + clk_io_update_bits(hw, gate->reg, gate->regmap, gate->offset, + mask, reg); }
- clk_writel(reg, gate->reg);
if (gate->lock) spin_unlock_irqrestore(gate->lock, flags); @@ -93,7 +99,7 @@ static int clk_gate_is_enabled(struct clk_hw *hw) u32 reg; struct clk_gate *gate = to_clk_gate(hw);
- reg = clk_readl(gate->reg); + reg = clk_io_readl(hw, gate->reg, gate->regmap, gate->offset);
/* if a set bit disables this clk, flip it before masking */ if (gate->flags & CLK_GATE_SET_TO_DISABLE) @@ -122,10 +128,10 @@ EXPORT_SYMBOL_GPL(clk_gate_ops); * @clk_gate_flags: gate-specific flags for this clock * @lock: shared register lock for this clock */ -struct clk *clk_register_gate(struct device *dev, const char *name, +struct clk *__clk_register_gate(struct device *dev, const char *name, const char *parent_name, unsigned long flags, - void __iomem *reg, u8 bit_idx, - u8 clk_gate_flags, spinlock_t *lock) + void __iomem *reg, struct regmap *regmap, u32 offset, + u8 bit_idx, u8 clk_gate_flags, spinlock_t *lock) { struct clk_gate *gate; struct clk *clk; @@ -150,7 +156,12 @@ struct clk *clk_register_gate(struct device *dev, const char *name, init.num_parents = (parent_name ? 1 : 0);
/* struct clk_gate assignments */ - gate->reg = reg; + if (flags & CLK_USE_REGMAP) + gate->regmap = regmap; + else + gate->reg = reg; + + gate->offset = offset; gate->bit_idx = bit_idx; gate->flags = clk_gate_flags; gate->lock = lock; @@ -163,6 +174,15 @@ struct clk *clk_register_gate(struct device *dev, const char *name,
return clk; } + +struct clk *clk_register_gate(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + void __iomem *reg, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock) +{ + return __clk_register_gate(dev, name, parent_name, flags, + reg, NULL, 0, bit_idx, clk_gate_flags, lock); +} EXPORT_SYMBOL_GPL(clk_register_gate);
void clk_unregister_gate(struct clk *clk) @@ -180,3 +200,15 @@ void clk_unregister_gate(struct clk *clk) kfree(gate); } EXPORT_SYMBOL_GPL(clk_unregister_gate); + +struct clk *clk_regm_register_gate(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + struct regmap *regmap, u32 offset, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock) +{ + flags |= CLK_USE_REGMAP; + + return __clk_register_gate(dev, name, parent_name, flags, + NULL, regmap, offset, bit_idx, clk_gate_flags, lock); +} +EXPORT_SYMBOL_GPL(clk_regm_register_gate); diff --git a/drivers/clk/clk-io.c b/drivers/clk/clk-io.c new file mode 100644 index 000000000000..ceeb64e15ddb --- /dev/null +++ b/drivers/clk/clk-io.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 Matthias Brugger matthias.bgg@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/clk-provider.h> + +void clk_io_writel(struct clk_hw *hw, void __iomem *reg, struct regmap *regmap, + u32 offset, u32 val) +{ + if (__clk_get_flags(hw->clk) & CLK_USE_REGMAP) + regmap_write(regmap, offset, val); + else + clk_writel(val, reg); +} + +u32 clk_io_readl(struct clk_hw *hw, void __iomem *reg, struct regmap *regmap, + u32 offset) +{ + u32 val; + + if (__clk_get_flags(hw->clk) & CLK_USE_REGMAP) + regmap_read(regmap, offset, &val); + else + val = clk_readl(reg); + + return val; +} + +int clk_io_update_bits(struct clk_hw *hw, void __iomem *reg, + struct regmap *regmap, u32 offset, u32 mask, u32 val) +{ + unsigned int tmp; + + if (__clk_get_flags(hw->clk) & CLK_USE_REGMAP) + return regmap_update_bits(regmap, offset, mask, val); + + tmp = clk_readl(reg); + tmp &= ~mask; + tmp |= val; + clk_writel(tmp, reg); + + return 0; +} diff --git a/drivers/clk/clk-io.h b/drivers/clk/clk-io.h new file mode 100644 index 000000000000..b5599ba02084 --- /dev/null +++ b/drivers/clk/clk-io.h @@ -0,0 +1,22 @@ +/* + * linux/drivers/clk/clk-io.h + * + * Copyright (C) 2015 Matthias Brugger matthias.bgg@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __LINUX_CLK_IO_H +#define __LINUX_CLK_IO_H + +#include <linux/clk-provider.h> + +void clk_io_writel(struct clk_hw *hw, void __iomem *reg, struct regmap *regmap, + u32 offset, u32 val); +u32 clk_io_readl(struct clk_hw *hw, void __iomem *reg, struct regmap *regmap, + u32 offset); +int clk_io_update_bits(struct clk_hw *hw, void __iomem *reg, + struct regmap *regmap, u32 offset, u32 mask, u32 val); + +#endif diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c index 7129c86a79db..d3831eca8b56 100644 --- a/drivers/clk/clk-mux.c +++ b/drivers/clk/clk-mux.c @@ -16,6 +16,8 @@ #include <linux/io.h> #include <linux/err.h>
+#include "clk-io.h" + /* * DOC: basic adjustable multiplexer clock that cannot gate * @@ -41,7 +43,9 @@ static u8 clk_mux_get_parent(struct clk_hw *hw) * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so * val = 0x4 really means "bit 2, index starts at bit 0" */ - val = clk_readl(mux->reg) >> mux->shift; + val = clk_io_readl(hw, mux->reg, mux->regmap, mux->offset); + + val >>= mux->shift; val &= mux->mask;
if (mux->table) { @@ -70,6 +74,7 @@ static int clk_mux_set_parent(struct clk_hw *hw, u8 index) struct clk_mux *mux = to_clk_mux(hw); u32 val; unsigned long flags = 0; + int ret = 0;
if (mux->table) index = mux->table[index]; @@ -89,19 +94,22 @@ static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
if (mux->flags & CLK_MUX_HIWORD_MASK) { val = mux->mask << (mux->shift + 16); + val |= index << mux->shift; + clk_io_writel(hw, mux->reg, mux->regmap, mux->offset, val); } else { - val = clk_readl(mux->reg); - val &= ~(mux->mask << mux->shift); + u32 mask = mux->mask << mux->shift; + + val = index << mux->shift; + ret = clk_io_update_bits(hw, mux->reg, mux->regmap, + mux->offset, mask, val); } - val |= index << mux->shift; - clk_writel(val, mux->reg);
if (mux->lock) spin_unlock_irqrestore(mux->lock, flags); else __release(mux->lock);
- return 0; + return ret; }
const struct clk_ops clk_mux_ops = { @@ -116,10 +124,11 @@ const struct clk_ops clk_mux_ro_ops = { }; EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
-struct clk *clk_register_mux_table(struct device *dev, const char *name, +struct clk *__clk_register_mux_table(struct device *dev, const char *name, const char * const *parent_names, u8 num_parents, unsigned long flags, - void __iomem *reg, u8 shift, u32 mask, + void __iomem *reg, struct regmap *regmap, + u32 offset, u8 shift, u32 mask, u8 clk_mux_flags, u32 *table, spinlock_t *lock) { struct clk_mux *mux; @@ -152,7 +161,12 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name, init.num_parents = num_parents;
/* struct clk_mux assignments */ - mux->reg = reg; + if (flags & CLK_USE_REGMAP) + mux->regmap = regmap; + else + mux->reg = reg; + + mux->offset = offset; mux->shift = shift; mux->mask = mask; mux->flags = clk_mux_flags; @@ -167,19 +181,40 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name,
return clk; } + +struct clk *clk_register_mux_table(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, void __iomem *reg, u8 shift, u32 mask, + u8 clk_mux_flags, u32 *table, spinlock_t *lock) +{ + return __clk_register_mux_table(dev, name, parent_names, num_parents, + flags, reg, NULL, 0, + shift, mask, clk_mux_flags, + table, lock); +} EXPORT_SYMBOL_GPL(clk_register_mux_table);
-struct clk *clk_register_mux(struct device *dev, const char *name, +struct clk *__clk_register_mux(struct device *dev, const char *name, const char * const *parent_names, u8 num_parents, - unsigned long flags, - void __iomem *reg, u8 shift, u8 width, + unsigned long flags, void __iomem *reg, struct regmap *regmap, + u32 offset, u8 shift, u8 width, u8 clk_mux_flags, spinlock_t *lock) { u32 mask = BIT(width) - 1;
- return clk_register_mux_table(dev, name, parent_names, num_parents, - flags, reg, shift, mask, clk_mux_flags, - NULL, lock); + return __clk_register_mux_table(dev, name, parent_names, num_parents, + flags, reg, regmap, offset, shift, mask, + clk_mux_flags, NULL, lock); +} + +struct clk *clk_register_mux(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, void __iomem *reg, u8 shift, u8 width, + u8 clk_mux_flags, spinlock_t *lock) +{ + return __clk_register_mux(dev, name, parent_names, num_parents, flags, + reg, NULL, 0, shift, width, + clk_mux_flags, lock); } EXPORT_SYMBOL_GPL(clk_register_mux);
@@ -198,3 +233,32 @@ void clk_unregister_mux(struct clk *clk) kfree(mux); } EXPORT_SYMBOL_GPL(clk_unregister_mux); + +struct clk *clk_regm_register_mux(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, struct regmap *regmap, + u32 offset, u8 shift, u8 width, + u8 clk_mux_flags, spinlock_t *lock) +{ + flags |= CLK_USE_REGMAP; + + return __clk_register_mux(dev, name, parent_names, num_parents, flags, + NULL, regmap, offset, shift, width, + clk_mux_flags, lock); +} +EXPORT_SYMBOL_GPL(clk_regm_register_mux); + +struct clk *clk_regm_register_mux_table(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, struct regmap *regmap, + u32 offset, u8 shift, u32 mask, + u8 clk_mux_flags, u32 *table, spinlock_t *lock) +{ + flags |= CLK_USE_REGMAP; + + return __clk_register_mux_table(dev, name, parent_names, num_parents, + flags, NULL, regmap, offset, + shift, mask, clk_mux_flags, + table, lock); +} +EXPORT_SYMBOL_GPL(clk_regm_register_mux_table); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index c56988ac63f7..0c55a37cf8f7 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -13,6 +13,7 @@
#include <linux/io.h> #include <linux/of.h> +#include <linux/regmap.h>
#ifdef CONFIG_COMMON_CLK
@@ -31,6 +32,7 @@ #define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */ #define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */ #define CLK_RECALC_NEW_RATES BIT(9) /* recalc rates after notifications */ +#define CLK_USE_REGMAP BIT(10) /* uses regmap to access registers */
struct clk; struct clk_hw; @@ -290,6 +292,8 @@ void of_fixed_clk_setup(struct device_node *np); * * @hw: handle between common and hardware-specific interfaces * @reg: register controlling gate + * @regmap: regmap used to control the gate + * @offset: offset inside the regmap * @bit_idx: single bit controlling gate * @flags: hardware-specific flags * @lock: register lock @@ -307,7 +311,11 @@ void of_fixed_clk_setup(struct device_node *np); */ struct clk_gate { struct clk_hw hw; - void __iomem *reg; + union { + void __iomem *reg; + struct regmap *regmap; + }; + u32 offset; u8 bit_idx; u8 flags; spinlock_t *lock; @@ -323,6 +331,11 @@ struct clk *clk_register_gate(struct device *dev, const char *name, u8 clk_gate_flags, spinlock_t *lock); void clk_unregister_gate(struct clk *clk);
+struct clk *clk_regm_register_gate(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + struct regmap *regmap, u32 offset, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock); + struct clk_div_table { unsigned int val; unsigned int div; @@ -333,6 +346,8 @@ struct clk_div_table { * * @hw: handle between common and hardware-specific interfaces * @reg: register containing the divider + * @regmap: regmap used to access the divider + * @offest: offset inside the regmap * @shift: shift to the divider bit field * @width: width of the divider bit field * @table: array of value/divider pairs, last entry should have div = 0 @@ -367,7 +382,11 @@ struct clk_div_table { */ struct clk_divider { struct clk_hw hw; - void __iomem *reg; + union { + void __iomem *reg; + struct regmap *regmap; + }; + u32 offset; u8 shift; u8 width; u8 flags; @@ -406,11 +425,24 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name, spinlock_t *lock); void clk_unregister_divider(struct clk *clk);
+struct clk *clk_regm_register_divider(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + struct regmap *regmap, u32 offset, u8 shift, u8 width, + u8 clk_divider_flags, spinlock_t *lock); + +struct clk *clk_regm_register_divider_table(struct device *dev, + const char *name, const char *parent_name, unsigned long flags, + struct regmap *regmap, u32 offset, u8 shift, u8 width, + u8 clk_divider_flags, const struct clk_div_table *table, + spinlock_t *lock); + /** * struct clk_mux - multiplexer clock * * @hw: handle between common and hardware-specific interfaces * @reg: register controlling multiplexer + * @regmap: regmap for controlling multiplexer + * @offset: offset inside the regmap * @shift: shift to multiplexer bit field * @width: width of mutliplexer bit field * @flags: hardware-specific flags @@ -431,8 +463,12 @@ void clk_unregister_divider(struct clk *clk); */ struct clk_mux { struct clk_hw hw; - void __iomem *reg; + union { + void __iomem *reg; + struct regmap *regmap; + }; u32 *table; + u32 offset; u32 mask; u8 shift; u8 flags; @@ -462,6 +498,18 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name,
void clk_unregister_mux(struct clk *clk);
+struct clk *clk_regm_register_mux_table(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, struct regmap *regmap, + u32 offset, u8 shift, u32 mask, + u8 clk_mux_flags, u32 *table, spinlock_t *lock); + +struct clk *clk_regm_register_mux(struct device *dev, const char *name, + const char * const *parent_names, u8 num_parents, + unsigned long flags, struct regmap *regmap, + u32 offset, u8 shift, u8 width, + u8 clk_mux_flags, spinlock_t *lock); + void of_fixed_factor_clk_setup(struct device_node *node);
/**
On 01/14, Maxime Ripard wrote:
From: Matthias Brugger matthias.bgg@gmail.com
Some devices like SoCs from Mediatek need to use the clock through a regmap interface. This patch adds regmap support for the simple multiplexer clock, the divider clock and the clock gate code.
Signed-off-by: Matthias Brugger matthias.bgg@gmail.com Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Nak.
The whole anonymous union thing and clk-io.c file is not appealing at all. I'd prefer we remove clk_readl/writel, not add to it. Plus we're bloating the basic clock types and adding a bunch of parallel registration APIs to assign a regmap pointer. Really, let's stop adding stuff to the basic clock types, it's getting out of hand.
To move forward on making regmap clks generic for everyone, let's make sure that regmap clk code is a library that any driver can use. No OF_CLK_DECLARE should exist because it doesn't make any sense. No clk_register_regmap_{div,mux,gate}() functions, just a clk_register_regmap() function to assign the regmap pointer from the device. Drivers that want to use the code should need to select a Kconfig symbol, so that we don't compile in support for regmap clocks unless we really need them.
If we had a clk_regmap structure with a regmap pointer and a clk_hw inside it I think that's all we would need. Once we had that, we could let driver writers wrap that in their own structures for dividers, muxes, gates, etc. and then have them call library functions from their clk_ops that takes a regmap (or clk_regmap struct), and offset to do the get_div/set_div, get_parent/set_parent, enable/disable stuff.
The policy part can be shared with the basic clock types, because we've already started libifying them. For example, we can ask the divider code what the hardware value should be for a particular divider type and it will tell us without writing any registers. I had a patch for libifying muxes, not sure where it went. The point being to leave the I/O part to the regmap code without putting it behind another layer of indirection buried inside the basic types. Make things flat and easy to follow.
I haven't thought through making new structs to hold the data for offsets, masks, etc. but I guess we would want those so that we could assign functions directly to clk_ops and not require any boiler plate clk_ops implementations in drivers. There are a few approaches here: different regmap structs for different basic types, one mega struct that combines all the needs of the basic types, or some private void pointer inside struct clk_regmap that points to different basic type structs. Let's see how that goes.
Maybe we can lift drivers/clk/qcom/clk-regmap.c up into the drivers/clk/ directory too. In the qcom design I put the enable/disable bits (gate functionality) directly into the clk_regmap structure. That may need some more thought if it was the right idea to force enable/disable on every regmap clock though. If we remove that and introduce a clk_regmap_gate things should turn out alright.
The composite clock didn't have any unregistration function, which forced us to use clk_unregister directly on it.
While it was already not great from an API point of view, it also meant that we were leaking the clk_composite structure allocated in clk_register_composite.
Add a clk_unregister_composite function to fix this.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/clk/clk-composite.c | 15 +++++++++++++++ include/linux/clk-provider.h | 1 + 2 files changed, 16 insertions(+)
diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c index 4735de0660cc..f4c38f209594 100644 --- a/drivers/clk/clk-composite.c +++ b/drivers/clk/clk-composite.c @@ -288,3 +288,18 @@ err: kfree(composite); return clk; } + +void clk_unregister_composite(struct clk *clk) +{ + struct clk_composite *composite; + struct clk_hw *hw; + + hw = __clk_get_hw(clk); + if (!hw) + return; + + composite = to_clk_composite(hw); + + clk_unregister(clk); + kfree(composite); +} diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 0c55a37cf8f7..76c34a9622db 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -632,6 +632,7 @@ struct clk *clk_register_composite(struct device *dev, const char *name, struct clk_hw *rate_hw, const struct clk_ops *rate_ops, struct clk_hw *gate_hw, const struct clk_ops *gate_ops, unsigned long flags); +void clk_unregister_composite(struct clk *clk);
/*** * struct clk_gpio_gate - gpio gated clock
The A10 SoCs and its relatives has a special clock controller to drive the display engines (both frontend and backend), that have a lot in common with the clock to drive the first TCON channel.
Add a driver to support both.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- Documentation/devicetree/bindings/clock/sunxi.txt | 2 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-display.c | 241 ++++++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-display.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 8a47b77abfca..5360554a7d3f 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -55,6 +55,7 @@ Required properties: "allwinner,sun9i-a80-apb1-gates-clk" - for the APB1 gates on A80 "allwinner,sun6i-a31-apb2-gates-clk" - for the APB2 gates on A31 "allwinner,sun8i-a23-apb2-gates-clk" - for the APB2 gates on A23 + "allwinner,sun4i-a10-display-clk" - for the display clocks on the A10 "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 "allwinner,sun4i-a10-mmc-clk" - for the MMC clock "allwinner,sun9i-a80-mmc-clk" - for mmc module clocks on A80 @@ -64,6 +65,7 @@ Required properties: "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 "allwinner,sun7i-a20-out-clk" - for the external output clocks "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31 + "allwinner,sun4i-a10-tcon-ch0-clk" - for the TCON channel 0 clock on the A10 "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 "allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31 diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index cb4c299214ce..a991cd8ca509 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o obj-y += clk-a20-gmac.o obj-y += clk-mod0.o obj-y += clk-simple-gates.o +obj-y += clk-sun4i-display.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-display.c b/drivers/clk/sunxi/clk-sun4i-display.c new file mode 100644 index 000000000000..9dc6894f0934 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-display.c @@ -0,0 +1,241 @@ +/* + * Copyright 2015 Maxime Ripard + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk-provider.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +struct sun4i_a10_display_clk_data { + bool has_div; + bool has_rst; + u8 parents; + + u8 offset_en; + u8 offset_div; + u8 offset_mux; + u8 offset_rst; + + u8 width_div; + u8 width_mux; +}; + +struct reset_data { + void __iomem *reg; + spinlock_t *lock; + struct reset_controller_dev rcdev; + u8 offset; +}; + +static DEFINE_SPINLOCK(sun4i_a10_display_lock); + +static inline struct reset_data *rcdev_to_reset_data(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct reset_data, rcdev); +}; + +static int sun4i_a10_display_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct reset_data *data = rcdev_to_reset_data(rcdev); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(data->lock, flags); + + reg = readl(data->reg); + writel(reg & ~BIT(data->offset), data->reg); + + spin_unlock_irqrestore(data->lock, flags); + + return 0; +} + +static int sun4i_a10_display_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct reset_data *data = rcdev_to_reset_data(rcdev); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(data->lock, flags); + + reg = readl(data->reg); + writel(reg | BIT(data->offset), data->reg); + + spin_unlock_irqrestore(data->lock, flags); + + return 0; +} + +static int sun4i_a10_display_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct reset_data *data = rcdev_to_reset_data(rcdev); + + return !(readl(data->reg) & BIT(data->offset)); +} + +static const struct reset_control_ops sun4i_a10_display_reset_ops = { + .assert = sun4i_a10_display_assert, + .deassert = sun4i_a10_display_deassert, + .status = sun4i_a10_display_status, +}; + +static int sun4i_a10_display_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *spec) +{ + /* We only have a single reset signal */ + return 0; +} + +static void __init sun4i_a10_display_init(struct device_node *node, + struct sun4i_a10_display_clk_data *data) +{ + const char *parents[data->parents]; + const char *clk_name = node->name; + struct reset_data *reset_data; + struct clk_divider *div = NULL; + struct clk_gate *gate; + struct clk_mux *mux; + void __iomem *reg; + struct clk *clk; + int i; + + of_property_read_string(node, "clock-output-names", &clk_name); + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (IS_ERR(reg)) { + pr_err("%s: Could not map the clock registers\n", clk_name); + return; + } + + of_clk_parent_fill(node, parents, data->parents); + + mux = kzalloc(sizeof(*mux), GFP_KERNEL); + if (!mux) + return; + + mux->reg = reg; + mux->shift = data->offset_mux; + mux->mask = (1 << data->width_mux) - 1; + mux->lock = &sun4i_a10_display_lock; + + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) + goto free_mux; + + gate->reg = reg; + gate->bit_idx = data->offset_en; + gate->lock = &sun4i_a10_display_lock; + + if (data->has_div) { + div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) + goto free_gate; + + div->reg = reg; + div->shift = data->offset_div; + div->width = data->width_div; + div->lock = &sun4i_a10_display_lock; + } + + clk = clk_register_composite(NULL, clk_name, + parents, data->parents, + &mux->hw, &clk_mux_ops, + data->has_div ? &div->hw : NULL, + data->has_div ? &clk_divider_ops : NULL, + &gate->hw, &clk_gate_ops, + 0); + if (IS_ERR(clk)) { + pr_err("%s: Couldn't register the clock\n", clk_name); + goto free_div; + } + + of_clk_add_provider(node, of_clk_src_simple_get, clk); + + if (!data->has_rst) + return; + + reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL); + if (!reset_data) + goto free_clk; + + reset_data->reg = reg; + reset_data->offset = data->offset_rst; + reset_data->lock = &sun4i_a10_display_lock; + reset_data->rcdev.nr_resets = 1; + reset_data->rcdev.ops = &sun4i_a10_display_reset_ops; + reset_data->rcdev.of_node = node; + reset_data->rcdev.of_reset_n_cells = 0; + reset_data->rcdev.of_xlate = &sun4i_a10_display_reset_xlate; + + if (reset_controller_register(&reset_data->rcdev)) { + pr_err("%s: Couldn't register the reset controller\n", + clk_name); + goto free_reset; + } + + return; + +free_reset: + kfree(reset_data); +free_clk: + clk_unregister(clk); +free_div: + if (data->has_div) + kfree(div); +free_gate: + kfree(gate); +free_mux: + kfree(mux); +} + +static struct sun4i_a10_display_clk_data sun4i_a10_tcon_ch0_data = { + .has_rst = true, + .parents = 4, + .offset_en = 31, + .offset_rst = 29, + .offset_mux = 24, + .width_mux = 2, +}; + +static void __init sun4i_a10_tcon_ch0_setup(struct device_node *node) +{ + sun4i_a10_display_init(node, &sun4i_a10_tcon_ch0_data); +} +CLK_OF_DECLARE(sun4i_a10_tcon_ch0, "allwinner,sun4i-a10-tcon-ch0-clk", + sun4i_a10_tcon_ch0_setup); + +static struct sun4i_a10_display_clk_data sun4i_a10_display_data = { + .has_div = true, + .parents = 3, + .offset_en = 31, + .offset_rst = 30, + .offset_mux = 24, + .offset_div = 0, + .width_mux = 2, + .width_div = 4, +}; + +static void __init sun4i_a10_display_setup(struct device_node *node) +{ + sun4i_a10_display_init(node, &sun4i_a10_display_data); +} +CLK_OF_DECLARE(sun4i_a10_display, "allwinner,sun4i-a10-display-clk", + sun4i_a10_display_setup);
On Thu, Jan 14, 2016 at 04:24:48PM +0100, Maxime Ripard wrote:
The A10 SoCs and its relatives has a special clock controller to drive the display engines (both frontend and backend), that have a lot in common with the clock to drive the first TCON channel.
Add a driver to support both.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 2 +
Can't someone read the datasheet and add these compatible strings in one pass instead of one at a time?
Acked-by: Rob Herring robh@kernel.org
drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-display.c | 241 ++++++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-display.c
On Thu, 2016-01-14 at 16:24 +0100, Maxime Ripard wrote:
The A10 SoCs and its relatives has a special clock controller to drive the display engines (both frontend and backend), that have a lot in common with the clock to drive the first TCON channel.
Add a driver to support both.
...
+static void __init sun4i_a10_display_init(struct device_node *node,
struct sun4i_a10_display_clk_data *data)
+{
- const char *parents[data->parents];
- const char *clk_name = node->name;
- struct reset_data *reset_data;
- struct clk_divider *div = NULL;
- struct clk_gate *gate;
- struct clk_mux *mux;
- void __iomem *reg;
- struct clk *clk;
- int i;
warning: unused variable 'i'
- of_property_read_string(node, "clock-output-names", &clk_name);
- reg = of_io_request_and_map(node, 0, of_node_full_name(node));
- if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", clk_name);
return;
- }
...
Thanks for working on this feature ;)
Päikest, Priit Laes
Hi,
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
The A10 SoCs and its relatives has a special clock controller to drive the display engines (both frontend and backend), that have a lot in common with the clock to drive the first TCON channel.
Add a driver to support both.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 2 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-display.c | 241 ++++++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-display.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 8a47b77abfca..5360554a7d3f 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -55,6 +55,7 @@ Required properties: "allwinner,sun9i-a80-apb1-gates-clk" - for the APB1 gates on A80 "allwinner,sun6i-a31-apb2-gates-clk" - for the APB2 gates on A31 "allwinner,sun8i-a23-apb2-gates-clk" - for the APB2 gates on A23
"allwinner,sun4i-a10-display-clk" - for the display clocks on the A10 "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 "allwinner,sun4i-a10-mmc-clk" - for the MMC clock "allwinner,sun9i-a80-mmc-clk" - for mmc module clocks on A80
@@ -64,6 +65,7 @@ Required properties: "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 "allwinner,sun7i-a20-out-clk" - for the external output clocks "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31
"allwinner,sun4i-a10-tcon-ch0-clk" - for the TCON channel 0 clock on the A10 "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 "allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index cb4c299214ce..a991cd8ca509 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o obj-y += clk-a20-gmac.o obj-y += clk-mod0.o obj-y += clk-simple-gates.o +obj-y += clk-sun4i-display.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-display.c b/drivers/clk/sunxi/clk-sun4i-display.c new file mode 100644 index 000000000000..9dc6894f0934 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-display.c @@ -0,0 +1,241 @@ +/*
- Copyright 2015 Maxime Ripard
- 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.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/clk-provider.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> +#include <linux/spinlock.h>
+struct sun4i_a10_display_clk_data {
bool has_div;
bool has_rst;
u8 parents;
u8 offset_en;
u8 offset_div;
u8 offset_mux;
u8 offset_rst;
u8 width_div;
u8 width_mux;
+};
+struct reset_data {
void __iomem *reg;
spinlock_t *lock;
struct reset_controller_dev rcdev;
u8 offset;
+};
+static DEFINE_SPINLOCK(sun4i_a10_display_lock);
+static inline struct reset_data *rcdev_to_reset_data(struct reset_controller_dev *rcdev) +{
return container_of(rcdev, struct reset_data, rcdev);
+};
+static int sun4i_a10_display_assert(struct reset_controller_dev *rcdev,
unsigned long id)
+{
struct reset_data *data = rcdev_to_reset_data(rcdev);
unsigned long flags;
u32 reg;
spin_lock_irqsave(data->lock, flags);
reg = readl(data->reg);
writel(reg & ~BIT(data->offset), data->reg);
spin_unlock_irqrestore(data->lock, flags);
return 0;
+}
+static int sun4i_a10_display_deassert(struct reset_controller_dev *rcdev,
unsigned long id)
+{
struct reset_data *data = rcdev_to_reset_data(rcdev);
unsigned long flags;
u32 reg;
spin_lock_irqsave(data->lock, flags);
reg = readl(data->reg);
writel(reg | BIT(data->offset), data->reg);
spin_unlock_irqrestore(data->lock, flags);
return 0;
+}
+static int sun4i_a10_display_status(struct reset_controller_dev *rcdev,
unsigned long id)
+{
struct reset_data *data = rcdev_to_reset_data(rcdev);
return !(readl(data->reg) & BIT(data->offset));
+}
+static const struct reset_control_ops sun4i_a10_display_reset_ops = {
.assert = sun4i_a10_display_assert,
.deassert = sun4i_a10_display_deassert,
.status = sun4i_a10_display_status,
+};
+static int sun4i_a10_display_reset_xlate(struct reset_controller_dev *rcdev,
const struct of_phandle_args *spec)
+{
/* We only have a single reset signal */
return 0;
+}
+static void __init sun4i_a10_display_init(struct device_node *node,
struct sun4i_a10_display_clk_data *data)
+{
const char *parents[data->parents];
const char *clk_name = node->name;
struct reset_data *reset_data;
struct clk_divider *div = NULL;
struct clk_gate *gate;
struct clk_mux *mux;
void __iomem *reg;
struct clk *clk;
int i;
of_property_read_string(node, "clock-output-names", &clk_name);
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", clk_name);
return;
}
of_clk_parent_fill(node, parents, data->parents);
What if someone uses an incorrect DT that has less parents described?
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
return;
mux->reg = reg;
mux->shift = data->offset_mux;
mux->mask = (1 << data->width_mux) - 1;
mux->lock = &sun4i_a10_display_lock;
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
goto free_mux;
gate->reg = reg;
gate->bit_idx = data->offset_en;
gate->lock = &sun4i_a10_display_lock;
if (data->has_div) {
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
goto free_gate;
div->reg = reg;
div->shift = data->offset_div;
div->width = data->width_div;
div->lock = &sun4i_a10_display_lock;
}
clk = clk_register_composite(NULL, clk_name,
parents, data->parents,
&mux->hw, &clk_mux_ops,
data->has_div ? &div->hw : NULL,
data->has_div ? &clk_divider_ops : NULL,
&gate->hw, &clk_gate_ops,
0);
if (IS_ERR(clk)) {
pr_err("%s: Couldn't register the clock\n", clk_name);
goto free_div;
}
of_clk_add_provider(node, of_clk_src_simple_get, clk);
Check for errors?
if (!data->has_rst)
return;
reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
if (!reset_data)
goto free_clk;
reset_data->reg = reg;
reset_data->offset = data->offset_rst;
reset_data->lock = &sun4i_a10_display_lock;
reset_data->rcdev.nr_resets = 1;
reset_data->rcdev.ops = &sun4i_a10_display_reset_ops;
reset_data->rcdev.of_node = node;
reset_data->rcdev.of_reset_n_cells = 0;
reset_data->rcdev.of_xlate = &sun4i_a10_display_reset_xlate;
if (reset_controller_register(&reset_data->rcdev)) {
pr_err("%s: Couldn't register the reset controller\n",
clk_name);
goto free_reset;
}
return;
+free_reset:
kfree(reset_data);
+free_clk:
clk_unregister(clk);
+free_div:
if (data->has_div)
kfree(div);
+free_gate:
kfree(gate);
+free_mux:
kfree(mux);
Cleanup after of_io_request_and_map()?
+}
+static struct sun4i_a10_display_clk_data sun4i_a10_tcon_ch0_data = {
.has_rst = true,
.parents = 4,
.offset_en = 31,
.offset_rst = 29,
My datasheets (A10 v1.5, A20 v1.4), say bit 30 for LCD0 ch0 reset.
A13 and R8 manuals do not list this clock, but A10s does, which has bit 30 for LCD reset and bit 29 for TV encoder reset.
I suggest changing has_rst to int, and for sun5i variant, have has_rst = 2, reset_cells = 1, for 2 separate reset controls. The tcon and tv encoder bits of the DTSI should be updated as well.
I assume your tests work because U-boot already de-asserted the resets (both if you're using composite output).
.offset_mux = 24,
.width_mux = 2,
+};
+static void __init sun4i_a10_tcon_ch0_setup(struct device_node *node) +{
sun4i_a10_display_init(node, &sun4i_a10_tcon_ch0_data);
+} +CLK_OF_DECLARE(sun4i_a10_tcon_ch0, "allwinner,sun4i-a10-tcon-ch0-clk",
sun4i_a10_tcon_ch0_setup);
+static struct sun4i_a10_display_clk_data sun4i_a10_display_data = {
.has_div = true,
Missing .has_rst = true.
.parents = 3,
.offset_en = 31,
.offset_rst = 30,
.offset_mux = 24,
.offset_div = 0,
This is already implied.
.width_mux = 2,
.width_div = 4,
+};
+static void __init sun4i_a10_display_setup(struct device_node *node) +{
sun4i_a10_display_init(node, &sun4i_a10_display_data);
+} +CLK_OF_DECLARE(sun4i_a10_display, "allwinner,sun4i-a10-display-clk",
sun4i_a10_display_setup);
-- 2.6.4
Thanks for working on this!
Regards ChenYu
On Sat, Jan 16, 2016 at 11:29:41PM +0800, Chen-Yu Tsai wrote:
Hi,
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
The A10 SoCs and its relatives has a special clock controller to drive the display engines (both frontend and backend), that have a lot in common with the clock to drive the first TCON channel.
Add a driver to support both.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 2 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-display.c | 241 ++++++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-display.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 8a47b77abfca..5360554a7d3f 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -55,6 +55,7 @@ Required properties: "allwinner,sun9i-a80-apb1-gates-clk" - for the APB1 gates on A80 "allwinner,sun6i-a31-apb2-gates-clk" - for the APB2 gates on A31 "allwinner,sun8i-a23-apb2-gates-clk" - for the APB2 gates on A23
"allwinner,sun4i-a10-display-clk" - for the display clocks on the A10 "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 "allwinner,sun4i-a10-mmc-clk" - for the MMC clock "allwinner,sun9i-a80-mmc-clk" - for mmc module clocks on A80
@@ -64,6 +65,7 @@ Required properties: "allwinner,sun8i-a23-mbus-clk" - for the MBUS clock on A23 "allwinner,sun7i-a20-out-clk" - for the external output clocks "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31
"allwinner,sun4i-a10-tcon-ch0-clk" - for the TCON channel 0 clock on the A10 "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 "allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index cb4c299214ce..a991cd8ca509 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -10,6 +10,7 @@ obj-y += clk-a10-pll2.o obj-y += clk-a20-gmac.o obj-y += clk-mod0.o obj-y += clk-simple-gates.o +obj-y += clk-sun4i-display.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-display.c b/drivers/clk/sunxi/clk-sun4i-display.c new file mode 100644 index 000000000000..9dc6894f0934 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-display.c @@ -0,0 +1,241 @@ +/*
- Copyright 2015 Maxime Ripard
- 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.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/clk-provider.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> +#include <linux/spinlock.h>
+struct sun4i_a10_display_clk_data {
bool has_div;
bool has_rst;
u8 parents;
u8 offset_en;
u8 offset_div;
u8 offset_mux;
u8 offset_rst;
u8 width_div;
u8 width_mux;
+};
+struct reset_data {
void __iomem *reg;
spinlock_t *lock;
struct reset_controller_dev rcdev;
u8 offset;
+};
+static DEFINE_SPINLOCK(sun4i_a10_display_lock);
+static inline struct reset_data *rcdev_to_reset_data(struct reset_controller_dev *rcdev) +{
return container_of(rcdev, struct reset_data, rcdev);
+};
+static int sun4i_a10_display_assert(struct reset_controller_dev *rcdev,
unsigned long id)
+{
struct reset_data *data = rcdev_to_reset_data(rcdev);
unsigned long flags;
u32 reg;
spin_lock_irqsave(data->lock, flags);
reg = readl(data->reg);
writel(reg & ~BIT(data->offset), data->reg);
spin_unlock_irqrestore(data->lock, flags);
return 0;
+}
+static int sun4i_a10_display_deassert(struct reset_controller_dev *rcdev,
unsigned long id)
+{
struct reset_data *data = rcdev_to_reset_data(rcdev);
unsigned long flags;
u32 reg;
spin_lock_irqsave(data->lock, flags);
reg = readl(data->reg);
writel(reg | BIT(data->offset), data->reg);
spin_unlock_irqrestore(data->lock, flags);
return 0;
+}
+static int sun4i_a10_display_status(struct reset_controller_dev *rcdev,
unsigned long id)
+{
struct reset_data *data = rcdev_to_reset_data(rcdev);
return !(readl(data->reg) & BIT(data->offset));
+}
+static const struct reset_control_ops sun4i_a10_display_reset_ops = {
.assert = sun4i_a10_display_assert,
.deassert = sun4i_a10_display_deassert,
.status = sun4i_a10_display_status,
+};
+static int sun4i_a10_display_reset_xlate(struct reset_controller_dev *rcdev,
const struct of_phandle_args *spec)
+{
/* We only have a single reset signal */
return 0;
+}
+static void __init sun4i_a10_display_init(struct device_node *node,
struct sun4i_a10_display_clk_data *data)
+{
const char *parents[data->parents];
const char *clk_name = node->name;
struct reset_data *reset_data;
struct clk_divider *div = NULL;
struct clk_gate *gate;
struct clk_mux *mux;
void __iomem *reg;
struct clk *clk;
int i;
of_property_read_string(node, "clock-output-names", &clk_name);
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", clk_name);
return;
}
of_clk_parent_fill(node, parents, data->parents);
What if someone uses an incorrect DT that has less parents described?
Then here be dragons ? :)
I'll fix that.
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
return;
mux->reg = reg;
mux->shift = data->offset_mux;
mux->mask = (1 << data->width_mux) - 1;
mux->lock = &sun4i_a10_display_lock;
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
goto free_mux;
gate->reg = reg;
gate->bit_idx = data->offset_en;
gate->lock = &sun4i_a10_display_lock;
if (data->has_div) {
div = kzalloc(sizeof(*div), GFP_KERNEL);
if (!div)
goto free_gate;
div->reg = reg;
div->shift = data->offset_div;
div->width = data->width_div;
div->lock = &sun4i_a10_display_lock;
}
clk = clk_register_composite(NULL, clk_name,
parents, data->parents,
&mux->hw, &clk_mux_ops,
data->has_div ? &div->hw : NULL,
data->has_div ? &clk_divider_ops : NULL,
&gate->hw, &clk_gate_ops,
0);
if (IS_ERR(clk)) {
pr_err("%s: Couldn't register the clock\n", clk_name);
goto free_div;
}
of_clk_add_provider(node, of_clk_src_simple_get, clk);
Check for errors?
Yep.
if (!data->has_rst)
return;
reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
if (!reset_data)
goto free_clk;
reset_data->reg = reg;
reset_data->offset = data->offset_rst;
reset_data->lock = &sun4i_a10_display_lock;
reset_data->rcdev.nr_resets = 1;
reset_data->rcdev.ops = &sun4i_a10_display_reset_ops;
reset_data->rcdev.of_node = node;
reset_data->rcdev.of_reset_n_cells = 0;
reset_data->rcdev.of_xlate = &sun4i_a10_display_reset_xlate;
if (reset_controller_register(&reset_data->rcdev)) {
pr_err("%s: Couldn't register the reset controller\n",
clk_name);
goto free_reset;
}
return;
+free_reset:
kfree(reset_data);
+free_clk:
clk_unregister(clk);
+free_div:
if (data->has_div)
kfree(div);
+free_gate:
kfree(gate);
+free_mux:
kfree(mux);
Cleanup after of_io_request_and_map()?
Hmm, indeed...
+}
+static struct sun4i_a10_display_clk_data sun4i_a10_tcon_ch0_data = {
.has_rst = true,
.parents = 4,
.offset_en = 31,
.offset_rst = 29,
My datasheets (A10 v1.5, A20 v1.4), say bit 30 for LCD0 ch0 reset.
A13 and R8 manuals do not list this clock, but A10s does, which has bit 30 for LCD reset and bit 29 for TV encoder reset.
I suggest changing has_rst to int, and for sun5i variant, have has_rst = 2, reset_cells = 1, for 2 separate reset controls. The tcon and tv encoder bits of the DTSI should be updated as well.
I assume your tests work because U-boot already de-asserted the resets (both if you're using composite output).
You're right, I'll fix it as you suggested.
.offset_mux = 24,
.width_mux = 2,
+};
+static void __init sun4i_a10_tcon_ch0_setup(struct device_node *node) +{
sun4i_a10_display_init(node, &sun4i_a10_tcon_ch0_data);
+} +CLK_OF_DECLARE(sun4i_a10_tcon_ch0, "allwinner,sun4i-a10-tcon-ch0-clk",
sun4i_a10_tcon_ch0_setup);
+static struct sun4i_a10_display_clk_data sun4i_a10_display_data = {
.has_div = true,
Missing .has_rst = true.
Indeed
.parents = 3,
.offset_en = 31,
.offset_rst = 30,
.offset_mux = 24,
.offset_div = 0,
This is already implied.
Because the structure would already by initialised to 0?
Yeah, true, but I wanted to make that explicit that the div offset is actually 0, and not something that got forgotten.
Thanks! Maxime
The A10 SoCs and relatives have a PLL controller to drive the PLL3 and PLL7, clocked from a 3MHz oscillator, that drives the display related clocks (GPU, display engine, TCON, etc.)
Add a driver for it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- Documentation/devicetree/bindings/clock/sunxi.txt | 1 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-pll3.c | 90 +++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-pll3.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 5360554a7d3f..bb9fb78dcff8 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -10,6 +10,7 @@ Required properties: "allwinner,sun4i-a10-pll1-clk" - for the main PLL clock and PLL4 "allwinner,sun6i-a31-pll1-clk" - for the main PLL clock on A31 "allwinner,sun8i-a23-pll1-clk" - for the main PLL clock on A23 + "allwinner,sun4i-a10-pll3-clk" - for the video PLL clock on A10 "allwinner,sun9i-a80-pll4-clk" - for the peripheral PLLs on A80 "allwinner,sun4i-a10-pll5-clk" - for the PLL5 clock "allwinner,sun4i-a10-pll6-clk" - for the PLL6 clock diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index a991cd8ca509..40c32ffd912c 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -11,6 +11,7 @@ obj-y += clk-a20-gmac.o obj-y += clk-mod0.o obj-y += clk-simple-gates.o obj-y += clk-sun4i-display.o +obj-y += clk-sun4i-pll3.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-pll3.c b/drivers/clk/sunxi/clk-sun4i-pll3.c new file mode 100644 index 000000000000..6c9c2210b6b2 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-pll3.c @@ -0,0 +1,90 @@ +/* + * Copyright 2015 Maxime Ripard + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define SUN4I_A10_PLL3_GATE_BIT 31 +#define SUN4I_A10_PLL3_DIV_WIDTH 7 +#define SUN4I_A10_PLL3_DIV_SHIFT 0 + +static DEFINE_SPINLOCK(sun4i_a10_pll3_lock); + +static void __init sun4i_a10_pll3_setup(struct device_node *node) +{ + const char *clk_name = node->name, *parent; + struct clk_multiplier *mult; + struct clk_gate *gate; + void __iomem *reg; + struct clk *clk; + int ret; + + of_property_read_string(node, "clock-output-names", &clk_name); + parent = of_clk_get_parent_name(node, 0); + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (IS_ERR(reg)) { + pr_err("%s: Could not map the clock registers\n", clk_name); + return; + } + + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) + return; + + gate->reg = reg; + gate->bit_idx = SUN4I_A10_PLL3_GATE_BIT; + gate->lock = &sun4i_a10_pll3_lock; + + mult = kzalloc(sizeof(*mult), GFP_KERNEL); + if (!mult) + goto err_free_gate; + + mult->reg = reg; + mult->shift = SUN4I_A10_PLL3_DIV_SHIFT; + mult->width = SUN4I_A10_PLL3_DIV_WIDTH; + mult->lock = &sun4i_a10_pll3_lock; + + clk = clk_register_composite(NULL, clk_name, + &parent, 1, + NULL, NULL, + &mult->hw, &clk_multiplier_ops, + &gate->hw, &clk_gate_ops, + 0); + if (IS_ERR(clk)) { + pr_err("%s: Couldn't register the clock\n", clk_name); + goto err_free_mult; + } + + ret = of_clk_add_provider(node, of_clk_src_simple_get, clk); + if (WARN_ON(ret)) + goto err_clk_unregister; + + return; + +err_clk_unregister: + clk_unregister_composite(clk); +err_free_mult: + kfree(mult); +err_free_gate: + kfree(gate); +} + +CLK_OF_DECLARE(sun4i_a10_pll3, "allwinner,sun4i-a10-pll3-clk", + sun4i_a10_pll3_setup);
On Thu, Jan 14, 2016 at 04:24:49PM +0100, Maxime Ripard wrote:
The A10 SoCs and relatives have a PLL controller to drive the PLL3 and PLL7, clocked from a 3MHz oscillator, that drives the display related clocks (GPU, display engine, TCON, etc.)
Add a driver for it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 1 +
Acked-by: Rob Herring robh@kernel.org
drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-pll3.c | 90 +++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-pll3.c
Hi,
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
The A10 SoCs and relatives have a PLL controller to drive the PLL3 and PLL7, clocked from a 3MHz oscillator, that drives the display related clocks (GPU, display engine, TCON, etc.)
Add a driver for it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 1 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-pll3.c | 90 +++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-pll3.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 5360554a7d3f..bb9fb78dcff8 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -10,6 +10,7 @@ Required properties: "allwinner,sun4i-a10-pll1-clk" - for the main PLL clock and PLL4 "allwinner,sun6i-a31-pll1-clk" - for the main PLL clock on A31 "allwinner,sun8i-a23-pll1-clk" - for the main PLL clock on A23
"allwinner,sun4i-a10-pll3-clk" - for the video PLL clock on A10 "allwinner,sun9i-a80-pll4-clk" - for the peripheral PLLs on A80 "allwinner,sun4i-a10-pll5-clk" - for the PLL5 clock "allwinner,sun4i-a10-pll6-clk" - for the PLL6 clock
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index a991cd8ca509..40c32ffd912c 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -11,6 +11,7 @@ obj-y += clk-a20-gmac.o obj-y += clk-mod0.o obj-y += clk-simple-gates.o obj-y += clk-sun4i-display.o +obj-y += clk-sun4i-pll3.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-pll3.c b/drivers/clk/sunxi/clk-sun4i-pll3.c new file mode 100644 index 000000000000..6c9c2210b6b2 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-pll3.c @@ -0,0 +1,90 @@ +/*
- Copyright 2015 Maxime Ripard
- 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.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/spinlock.h>
+#define SUN4I_A10_PLL3_GATE_BIT 31 +#define SUN4I_A10_PLL3_DIV_WIDTH 7 +#define SUN4I_A10_PLL3_DIV_SHIFT 0
+static DEFINE_SPINLOCK(sun4i_a10_pll3_lock);
+static void __init sun4i_a10_pll3_setup(struct device_node *node) +{
const char *clk_name = node->name, *parent;
struct clk_multiplier *mult;
struct clk_gate *gate;
void __iomem *reg;
struct clk *clk;
int ret;
of_property_read_string(node, "clock-output-names", &clk_name);
parent = of_clk_get_parent_name(node, 0);
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", clk_name);
return;
}
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
return;
gate->reg = reg;
gate->bit_idx = SUN4I_A10_PLL3_GATE_BIT;
gate->lock = &sun4i_a10_pll3_lock;
mult = kzalloc(sizeof(*mult), GFP_KERNEL);
if (!mult)
goto err_free_gate;
mult->reg = reg;
mult->shift = SUN4I_A10_PLL3_DIV_SHIFT;
mult->width = SUN4I_A10_PLL3_DIV_WIDTH;
mult->lock = &sun4i_a10_pll3_lock;
clk = clk_register_composite(NULL, clk_name,
&parent, 1,
NULL, NULL,
&mult->hw, &clk_multiplier_ops,
&gate->hw, &clk_gate_ops,
0);
if (IS_ERR(clk)) {
pr_err("%s: Couldn't register the clock\n", clk_name);
goto err_free_mult;
}
ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
if (WARN_ON(ret))
Any particular reason for WARN_ON instead of pr_err like above?
goto err_clk_unregister;
return;
+err_clk_unregister:
clk_unregister_composite(clk);
+err_free_mult:
kfree(mult);
+err_free_gate:
kfree(gate);
Clean up after of_io_request_and_map(), otherwise
Acked-by: Chen-Yu Tsai wens@csie.org
Thanks!
+}
+CLK_OF_DECLARE(sun4i_a10_pll3, "allwinner,sun4i-a10-pll3-clk",
sun4i_a10_pll3_setup);
-- 2.6.4
On Sun, Jan 17, 2016 at 12:05:06AM +0800, Chen-Yu Tsai wrote:
Hi,
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
The A10 SoCs and relatives have a PLL controller to drive the PLL3 and PLL7, clocked from a 3MHz oscillator, that drives the display related clocks (GPU, display engine, TCON, etc.)
Add a driver for it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 1 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-pll3.c | 90 +++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-pll3.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index 5360554a7d3f..bb9fb78dcff8 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -10,6 +10,7 @@ Required properties: "allwinner,sun4i-a10-pll1-clk" - for the main PLL clock and PLL4 "allwinner,sun6i-a31-pll1-clk" - for the main PLL clock on A31 "allwinner,sun8i-a23-pll1-clk" - for the main PLL clock on A23
"allwinner,sun4i-a10-pll3-clk" - for the video PLL clock on A10 "allwinner,sun9i-a80-pll4-clk" - for the peripheral PLLs on A80 "allwinner,sun4i-a10-pll5-clk" - for the PLL5 clock "allwinner,sun4i-a10-pll6-clk" - for the PLL6 clock
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index a991cd8ca509..40c32ffd912c 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -11,6 +11,7 @@ obj-y += clk-a20-gmac.o obj-y += clk-mod0.o obj-y += clk-simple-gates.o obj-y += clk-sun4i-display.o +obj-y += clk-sun4i-pll3.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-pll3.c b/drivers/clk/sunxi/clk-sun4i-pll3.c new file mode 100644 index 000000000000..6c9c2210b6b2 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-pll3.c @@ -0,0 +1,90 @@ +/*
- Copyright 2015 Maxime Ripard
- 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.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/spinlock.h>
+#define SUN4I_A10_PLL3_GATE_BIT 31 +#define SUN4I_A10_PLL3_DIV_WIDTH 7 +#define SUN4I_A10_PLL3_DIV_SHIFT 0
+static DEFINE_SPINLOCK(sun4i_a10_pll3_lock);
+static void __init sun4i_a10_pll3_setup(struct device_node *node) +{
const char *clk_name = node->name, *parent;
struct clk_multiplier *mult;
struct clk_gate *gate;
void __iomem *reg;
struct clk *clk;
int ret;
of_property_read_string(node, "clock-output-names", &clk_name);
parent = of_clk_get_parent_name(node, 0);
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", clk_name);
return;
}
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
return;
gate->reg = reg;
gate->bit_idx = SUN4I_A10_PLL3_GATE_BIT;
gate->lock = &sun4i_a10_pll3_lock;
mult = kzalloc(sizeof(*mult), GFP_KERNEL);
if (!mult)
goto err_free_gate;
mult->reg = reg;
mult->shift = SUN4I_A10_PLL3_DIV_SHIFT;
mult->width = SUN4I_A10_PLL3_DIV_WIDTH;
mult->lock = &sun4i_a10_pll3_lock;
clk = clk_register_composite(NULL, clk_name,
&parent, 1,
NULL, NULL,
&mult->hw, &clk_multiplier_ops,
&gate->hw, &clk_gate_ops,
0);
if (IS_ERR(clk)) {
pr_err("%s: Couldn't register the clock\n", clk_name);
goto err_free_mult;
}
ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
if (WARN_ON(ret))
Any particular reason for WARN_ON instead of pr_err like above?
I guess not, I'll change that.
goto err_clk_unregister;
return;
+err_clk_unregister:
clk_unregister_composite(clk);
+err_free_mult:
kfree(mult);
+err_free_gate:
kfree(gate);
Clean up after of_io_request_and_map(), otherwise
Ack
Acked-by: Chen-Yu Tsai wens@csie.org
Thanks! Maxime
The TCON is a controller generating the timings to output videos signals, acting like both a CRTC and an encoder.
It has two channels depending on the output, each channel being driven by its own clock (and own clock controller).
Add a driver for the channel 1 clock.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- Documentation/devicetree/bindings/clock/sunxi.txt | 1 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-tcon-ch1.c | 154 ++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index bb9fb78dcff8..fe34fc56e803 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -67,6 +67,7 @@ Required properties: "allwinner,sun7i-a20-out-clk" - for the external output clocks "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31 "allwinner,sun4i-a10-tcon-ch0-clk" - for the TCON channel 0 clock on the A10 + "allwinner,sun4i-a10-tcon-ch1-clk" - for the TCON channel 1 clock on the A10 "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 "allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31 diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index 40c32ffd912c..0a20873cd103 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -12,6 +12,7 @@ obj-y += clk-mod0.o obj-y += clk-simple-gates.o obj-y += clk-sun4i-display.o obj-y += clk-sun4i-pll3.o +obj-y += clk-sun4i-tcon-ch1.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c new file mode 100644 index 000000000000..51ddc38821f7 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c @@ -0,0 +1,154 @@ +/* + * Copyright 2015 Maxime Ripard + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define SUN4I_TCON_CH1_SCLK_NAME_LEN 32 + +#define SUN4I_A10_TCON_CH1_SCLK2_PARENTS 4 + +#define SUN4I_A10_TCON_CH1_SCLK2_GATE_BIT 31 +#define SUN4I_A10_TCON_CH1_SCLK2_MUX_MASK 3 +#define SUN4I_A10_TCON_CH1_SCLK2_MUX_SHIFT 24 +#define SUN4I_A10_TCON_CH1_SCLK2_DIV_WIDTH 4 +#define SUN4I_A10_TCON_CH1_SCLK2_DIV_SHIFT 0 + +#define SUN4I_A10_TCON_CH1_SCLK1_GATE_BIT 15 +#define SUN4I_A10_TCON_CH1_SCLK1_DIV_WIDTH 1 +#define SUN4I_A10_TCON_CH1_SCLK1_DIV_SHIFT 11 + +static DEFINE_SPINLOCK(sun4i_a10_tcon_ch1_lock); + +static void __init sun4i_a10_tcon_ch1_setup(struct device_node *node) +{ + const char *sclk2_parents[SUN4I_A10_TCON_CH1_SCLK2_PARENTS]; + const char *sclk1_name = node->name; + const char *sclk2_name; + struct clk_divider *sclk1_div, *sclk2_div; + struct clk_gate *sclk1_gate, *sclk2_gate; + struct clk_mux *sclk2_mux; + struct clk *sclk1, *sclk2; + void __iomem *reg; + int i, ret; + + of_property_read_string(node, "clock-output-names", + &sclk1_name); + + sclk2_name = kasprintf(GFP_KERNEL, "%s2", sclk1_name); + if (!sclk2_name) + return; + + reg = of_io_request_and_map(node, 0, of_node_full_name(node)); + if (IS_ERR(reg)) { + pr_err("%s: Could not map the clock registers\n", sclk2_name); + return; + } + + for (i = 0; i < SUN4I_A10_TCON_CH1_SCLK2_PARENTS; i++) + sclk2_parents[i] = of_clk_get_parent_name(node, i); + + sclk2_mux = kzalloc(sizeof(*sclk2_mux), GFP_KERNEL); + if (!sclk2_mux) + return; + + sclk2_mux->reg = reg; + sclk2_mux->shift = SUN4I_A10_TCON_CH1_SCLK2_MUX_SHIFT; + sclk2_mux->mask = SUN4I_A10_TCON_CH1_SCLK2_MUX_MASK; + sclk2_mux->lock = &sun4i_a10_tcon_ch1_lock; + + sclk2_gate = kzalloc(sizeof(*sclk2_gate), GFP_KERNEL); + if (!sclk2_gate) + goto free_sclk2_mux; + + sclk2_gate->reg = reg; + sclk2_gate->bit_idx = SUN4I_A10_TCON_CH1_SCLK2_GATE_BIT; + sclk2_gate->lock = &sun4i_a10_tcon_ch1_lock; + + sclk2_div = kzalloc(sizeof(*sclk2_div), GFP_KERNEL); + if (!sclk2_div) + goto free_sclk2_gate; + + sclk2_div->reg = reg; + sclk2_div->shift = SUN4I_A10_TCON_CH1_SCLK2_DIV_SHIFT; + sclk2_div->width = SUN4I_A10_TCON_CH1_SCLK2_DIV_WIDTH; + sclk2_div->lock = &sun4i_a10_tcon_ch1_lock; + + sclk2 = clk_register_composite(NULL, sclk2_name, sclk2_parents, + SUN4I_A10_TCON_CH1_SCLK2_PARENTS, + &sclk2_mux->hw, &clk_mux_ops, & + sclk2_div->hw, &clk_divider_ops, + &sclk2_gate->hw, &clk_gate_ops, + 0); + if (IS_ERR(sclk2)) { + pr_err("%s: Couldn't register the clock\n", sclk2_name); + goto free_sclk2_div; + } + + sclk1_div = kzalloc(sizeof(*sclk1_div), GFP_KERNEL); + if (!sclk1_div) + goto free_sclk2; + + sclk1_div->reg = reg; + sclk1_div->shift = SUN4I_A10_TCON_CH1_SCLK1_DIV_SHIFT; + sclk1_div->width = SUN4I_A10_TCON_CH1_SCLK1_DIV_WIDTH; + sclk1_div->lock = &sun4i_a10_tcon_ch1_lock; + + sclk1_gate = kzalloc(sizeof(*sclk1_gate), GFP_KERNEL); + if (!sclk1_gate) + goto free_sclk1_mux; + + sclk1_gate->reg = reg; + sclk1_gate->bit_idx = SUN4I_A10_TCON_CH1_SCLK1_GATE_BIT; + sclk1_gate->lock = &sun4i_a10_tcon_ch1_lock; + + sclk1 = clk_register_composite(NULL, sclk1_name, &sclk2_name, 1, + NULL, NULL, + &sclk1_div->hw, &clk_divider_ops, + &sclk1_gate->hw, &clk_gate_ops, + 0); + if (IS_ERR(sclk1)) { + pr_err("%s: Couldn't register the clock\n", sclk1_name); + goto free_sclk1_gate; + } + + ret = of_clk_add_provider(node, of_clk_src_simple_get, sclk1); + if (WARN_ON(ret)) + goto free_sclk1; + + return; + +free_sclk1: + clk_unregister_composite(sclk1); +free_sclk1_gate: + kfree(sclk1_gate); +free_sclk1_mux: + kfree(sclk1_div); +free_sclk2: + clk_unregister(sclk2); +free_sclk2_div: + kfree(sclk2_div); +free_sclk2_gate: + kfree(sclk2_gate); +free_sclk2_mux: + kfree(sclk2_mux); +} + +CLK_OF_DECLARE(sun4i_a10_tcon_ch1, "allwinner,sun4i-a10-tcon-ch1-clk", + sun4i_a10_tcon_ch1_setup);
On Thu, Jan 14, 2016 at 04:24:50PM +0100, Maxime Ripard wrote:
The TCON is a controller generating the timings to output videos signals, acting like both a CRTC and an encoder.
It has two channels depending on the output, each channel being driven by its own clock (and own clock controller).
Add a driver for the channel 1 clock.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 1 +
Acked-by: Rob Herring robh@kernel.org
drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-tcon-ch1.c | 154 ++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
Hi,
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
The TCON is a controller generating the timings to output videos signals, acting like both a CRTC and an encoder.
It has two channels depending on the output, each channel being driven by its own clock (and own clock controller).
Add a driver for the channel 1 clock.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 1 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-tcon-ch1.c | 154 ++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index bb9fb78dcff8..fe34fc56e803 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -67,6 +67,7 @@ Required properties: "allwinner,sun7i-a20-out-clk" - for the external output clocks "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31 "allwinner,sun4i-a10-tcon-ch0-clk" - for the TCON channel 0 clock on the A10
"allwinner,sun4i-a10-tcon-ch1-clk" - for the TCON channel 1 clock on the A10 "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 "allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index 40c32ffd912c..0a20873cd103 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -12,6 +12,7 @@ obj-y += clk-mod0.o obj-y += clk-simple-gates.o obj-y += clk-sun4i-display.o obj-y += clk-sun4i-pll3.o +obj-y += clk-sun4i-tcon-ch1.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c new file mode 100644 index 000000000000..51ddc38821f7 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c @@ -0,0 +1,154 @@ +/*
- Copyright 2015 Maxime Ripard
- 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.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/spinlock.h>
+#define SUN4I_TCON_CH1_SCLK_NAME_LEN 32
+#define SUN4I_A10_TCON_CH1_SCLK2_PARENTS 4
+#define SUN4I_A10_TCON_CH1_SCLK2_GATE_BIT 31 +#define SUN4I_A10_TCON_CH1_SCLK2_MUX_MASK 3 +#define SUN4I_A10_TCON_CH1_SCLK2_MUX_SHIFT 24 +#define SUN4I_A10_TCON_CH1_SCLK2_DIV_WIDTH 4 +#define SUN4I_A10_TCON_CH1_SCLK2_DIV_SHIFT 0
+#define SUN4I_A10_TCON_CH1_SCLK1_GATE_BIT 15 +#define SUN4I_A10_TCON_CH1_SCLK1_DIV_WIDTH 1 +#define SUN4I_A10_TCON_CH1_SCLK1_DIV_SHIFT 11
+static DEFINE_SPINLOCK(sun4i_a10_tcon_ch1_lock);
+static void __init sun4i_a10_tcon_ch1_setup(struct device_node *node) +{
const char *sclk2_parents[SUN4I_A10_TCON_CH1_SCLK2_PARENTS];
const char *sclk1_name = node->name;
const char *sclk2_name;
struct clk_divider *sclk1_div, *sclk2_div;
struct clk_gate *sclk1_gate, *sclk2_gate;
struct clk_mux *sclk2_mux;
struct clk *sclk1, *sclk2;
void __iomem *reg;
int i, ret;
of_property_read_string(node, "clock-output-names",
&sclk1_name);
sclk2_name = kasprintf(GFP_KERNEL, "%s2", sclk1_name);
if (!sclk2_name)
return;
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", sclk2_name);
return;
}
for (i = 0; i < SUN4I_A10_TCON_CH1_SCLK2_PARENTS; i++)
sclk2_parents[i] = of_clk_get_parent_name(node, i);
of_clk_parent_fill?
sclk2_mux = kzalloc(sizeof(*sclk2_mux), GFP_KERNEL);
if (!sclk2_mux)
return;
sclk2_mux->reg = reg;
sclk2_mux->shift = SUN4I_A10_TCON_CH1_SCLK2_MUX_SHIFT;
sclk2_mux->mask = SUN4I_A10_TCON_CH1_SCLK2_MUX_MASK;
sclk2_mux->lock = &sun4i_a10_tcon_ch1_lock;
sclk2_gate = kzalloc(sizeof(*sclk2_gate), GFP_KERNEL);
if (!sclk2_gate)
goto free_sclk2_mux;
sclk2_gate->reg = reg;
sclk2_gate->bit_idx = SUN4I_A10_TCON_CH1_SCLK2_GATE_BIT;
sclk2_gate->lock = &sun4i_a10_tcon_ch1_lock;
sclk2_div = kzalloc(sizeof(*sclk2_div), GFP_KERNEL);
if (!sclk2_div)
goto free_sclk2_gate;
sclk2_div->reg = reg;
sclk2_div->shift = SUN4I_A10_TCON_CH1_SCLK2_DIV_SHIFT;
sclk2_div->width = SUN4I_A10_TCON_CH1_SCLK2_DIV_WIDTH;
sclk2_div->lock = &sun4i_a10_tcon_ch1_lock;
sclk2 = clk_register_composite(NULL, sclk2_name, sclk2_parents,
SUN4I_A10_TCON_CH1_SCLK2_PARENTS,
&sclk2_mux->hw, &clk_mux_ops, &
sclk2_div->hw, &clk_divider_ops,
Placement of the "&" is very weird.
&sclk2_gate->hw, &clk_gate_ops,
0);
if (IS_ERR(sclk2)) {
pr_err("%s: Couldn't register the clock\n", sclk2_name);
goto free_sclk2_div;
}
sclk1_div = kzalloc(sizeof(*sclk1_div), GFP_KERNEL);
if (!sclk1_div)
goto free_sclk2;
sclk1_div->reg = reg;
sclk1_div->shift = SUN4I_A10_TCON_CH1_SCLK1_DIV_SHIFT;
sclk1_div->width = SUN4I_A10_TCON_CH1_SCLK1_DIV_WIDTH;
sclk1_div->lock = &sun4i_a10_tcon_ch1_lock;
sclk1_gate = kzalloc(sizeof(*sclk1_gate), GFP_KERNEL);
if (!sclk1_gate)
goto free_sclk1_mux;
sclk1_gate->reg = reg;
sclk1_gate->bit_idx = SUN4I_A10_TCON_CH1_SCLK1_GATE_BIT;
sclk1_gate->lock = &sun4i_a10_tcon_ch1_lock;
sclk1 = clk_register_composite(NULL, sclk1_name, &sclk2_name, 1,
NULL, NULL,
&sclk1_div->hw, &clk_divider_ops,
&sclk1_gate->hw, &clk_gate_ops,
0);
if (IS_ERR(sclk1)) {
pr_err("%s: Couldn't register the clock\n", sclk1_name);
goto free_sclk1_gate;
}
ret = of_clk_add_provider(node, of_clk_src_simple_get, sclk1);
if (WARN_ON(ret))
goto free_sclk1;
return;
+free_sclk1:
clk_unregister_composite(sclk1);
+free_sclk1_gate:
kfree(sclk1_gate);
+free_sclk1_mux:
kfree(sclk1_div);
+free_sclk2:
clk_unregister(sclk2);
+free_sclk2_div:
kfree(sclk2_div);
+free_sclk2_gate:
kfree(sclk2_gate);
+free_sclk2_mux:
kfree(sclk2_mux);
Cleanup after of_io_request_and_map?
Regards ChenYu
+}
+CLK_OF_DECLARE(sun4i_a10_tcon_ch1, "allwinner,sun4i-a10-tcon-ch1-clk",
sun4i_a10_tcon_ch1_setup);
-- 2.6.4
Hi,
On Sun, Jan 17, 2016 at 12:36:20AM +0800, Chen-Yu Tsai wrote:
Hi,
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
The TCON is a controller generating the timings to output videos signals, acting like both a CRTC and an encoder.
It has two channels depending on the output, each channel being driven by its own clock (and own clock controller).
Add a driver for the channel 1 clock.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/clock/sunxi.txt | 1 + drivers/clk/sunxi/Makefile | 1 + drivers/clk/sunxi/clk-sun4i-tcon-ch1.c | 154 ++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index bb9fb78dcff8..fe34fc56e803 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -67,6 +67,7 @@ Required properties: "allwinner,sun7i-a20-out-clk" - for the external output clocks "allwinner,sun7i-a20-gmac-clk" - for the GMAC clock module on A20/A31 "allwinner,sun4i-a10-tcon-ch0-clk" - for the TCON channel 0 clock on the A10
"allwinner,sun4i-a10-tcon-ch1-clk" - for the TCON channel 1 clock on the A10 "allwinner,sun4i-a10-usb-clk" - for usb gates + resets on A10 / A20 "allwinner,sun5i-a13-usb-clk" - for usb gates + resets on A13 "allwinner,sun6i-a31-usb-clk" - for usb gates + resets on A31
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index 40c32ffd912c..0a20873cd103 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -12,6 +12,7 @@ obj-y += clk-mod0.o obj-y += clk-simple-gates.o obj-y += clk-sun4i-display.o obj-y += clk-sun4i-pll3.o +obj-y += clk-sun4i-tcon-ch1.o obj-y += clk-sun8i-mbus.o obj-y += clk-sun9i-core.o obj-y += clk-sun9i-mmc.o diff --git a/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c new file mode 100644 index 000000000000..51ddc38821f7 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c @@ -0,0 +1,154 @@ +/*
- Copyright 2015 Maxime Ripard
- 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.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/spinlock.h>
+#define SUN4I_TCON_CH1_SCLK_NAME_LEN 32
+#define SUN4I_A10_TCON_CH1_SCLK2_PARENTS 4
+#define SUN4I_A10_TCON_CH1_SCLK2_GATE_BIT 31 +#define SUN4I_A10_TCON_CH1_SCLK2_MUX_MASK 3 +#define SUN4I_A10_TCON_CH1_SCLK2_MUX_SHIFT 24 +#define SUN4I_A10_TCON_CH1_SCLK2_DIV_WIDTH 4 +#define SUN4I_A10_TCON_CH1_SCLK2_DIV_SHIFT 0
+#define SUN4I_A10_TCON_CH1_SCLK1_GATE_BIT 15 +#define SUN4I_A10_TCON_CH1_SCLK1_DIV_WIDTH 1 +#define SUN4I_A10_TCON_CH1_SCLK1_DIV_SHIFT 11
+static DEFINE_SPINLOCK(sun4i_a10_tcon_ch1_lock);
+static void __init sun4i_a10_tcon_ch1_setup(struct device_node *node) +{
const char *sclk2_parents[SUN4I_A10_TCON_CH1_SCLK2_PARENTS];
const char *sclk1_name = node->name;
const char *sclk2_name;
struct clk_divider *sclk1_div, *sclk2_div;
struct clk_gate *sclk1_gate, *sclk2_gate;
struct clk_mux *sclk2_mux;
struct clk *sclk1, *sclk2;
void __iomem *reg;
int i, ret;
of_property_read_string(node, "clock-output-names",
&sclk1_name);
sclk2_name = kasprintf(GFP_KERNEL, "%s2", sclk1_name);
if (!sclk2_name)
return;
reg = of_io_request_and_map(node, 0, of_node_full_name(node));
if (IS_ERR(reg)) {
pr_err("%s: Could not map the clock registers\n", sclk2_name);
return;
}
for (i = 0; i < SUN4I_A10_TCON_CH1_SCLK2_PARENTS; i++)
sclk2_parents[i] = of_clk_get_parent_name(node, i);
of_clk_parent_fill?
Indeed
sclk2_mux = kzalloc(sizeof(*sclk2_mux), GFP_KERNEL);
if (!sclk2_mux)
return;
sclk2_mux->reg = reg;
sclk2_mux->shift = SUN4I_A10_TCON_CH1_SCLK2_MUX_SHIFT;
sclk2_mux->mask = SUN4I_A10_TCON_CH1_SCLK2_MUX_MASK;
sclk2_mux->lock = &sun4i_a10_tcon_ch1_lock;
sclk2_gate = kzalloc(sizeof(*sclk2_gate), GFP_KERNEL);
if (!sclk2_gate)
goto free_sclk2_mux;
sclk2_gate->reg = reg;
sclk2_gate->bit_idx = SUN4I_A10_TCON_CH1_SCLK2_GATE_BIT;
sclk2_gate->lock = &sun4i_a10_tcon_ch1_lock;
sclk2_div = kzalloc(sizeof(*sclk2_div), GFP_KERNEL);
if (!sclk2_div)
goto free_sclk2_gate;
sclk2_div->reg = reg;
sclk2_div->shift = SUN4I_A10_TCON_CH1_SCLK2_DIV_SHIFT;
sclk2_div->width = SUN4I_A10_TCON_CH1_SCLK2_DIV_WIDTH;
sclk2_div->lock = &sun4i_a10_tcon_ch1_lock;
sclk2 = clk_register_composite(NULL, sclk2_name, sclk2_parents,
SUN4I_A10_TCON_CH1_SCLK2_PARENTS,
&sclk2_mux->hw, &clk_mux_ops, &
sclk2_div->hw, &clk_divider_ops,
Placement of the "&" is very weird.
Hmm, right. I don't know what happened here, but it's obviously wrong :)
&sclk2_gate->hw, &clk_gate_ops,
0);
if (IS_ERR(sclk2)) {
pr_err("%s: Couldn't register the clock\n", sclk2_name);
goto free_sclk2_div;
}
sclk1_div = kzalloc(sizeof(*sclk1_div), GFP_KERNEL);
if (!sclk1_div)
goto free_sclk2;
sclk1_div->reg = reg;
sclk1_div->shift = SUN4I_A10_TCON_CH1_SCLK1_DIV_SHIFT;
sclk1_div->width = SUN4I_A10_TCON_CH1_SCLK1_DIV_WIDTH;
sclk1_div->lock = &sun4i_a10_tcon_ch1_lock;
sclk1_gate = kzalloc(sizeof(*sclk1_gate), GFP_KERNEL);
if (!sclk1_gate)
goto free_sclk1_mux;
sclk1_gate->reg = reg;
sclk1_gate->bit_idx = SUN4I_A10_TCON_CH1_SCLK1_GATE_BIT;
sclk1_gate->lock = &sun4i_a10_tcon_ch1_lock;
sclk1 = clk_register_composite(NULL, sclk1_name, &sclk2_name, 1,
NULL, NULL,
&sclk1_div->hw, &clk_divider_ops,
&sclk1_gate->hw, &clk_gate_ops,
0);
if (IS_ERR(sclk1)) {
pr_err("%s: Couldn't register the clock\n", sclk1_name);
goto free_sclk1_gate;
}
ret = of_clk_add_provider(node, of_clk_src_simple_get, sclk1);
if (WARN_ON(ret))
goto free_sclk1;
return;
+free_sclk1:
clk_unregister_composite(sclk1);
+free_sclk1_gate:
kfree(sclk1_gate);
+free_sclk1_mux:
kfree(sclk1_div);
+free_sclk2:
clk_unregister(sclk2);
+free_sclk2_div:
kfree(sclk2_div);
+free_sclk2_gate:
kfree(sclk2_gate);
+free_sclk2_mux:
kfree(sclk2_mux);
Cleanup after of_io_request_and_map?
Indeed.
Thanks! Maxime
The Allwinner SoCs have a gate controller to gate the access to the DRAM clock to the some devices that need to access the DRAM directly (mostly display / image related IPs).
Use a simple gates driver to support the one found in the A13 / R8 SoCs.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com Acked-by: Chen-Yu Tsai wens@csie.org --- Documentation/devicetree/bindings/clock/sunxi.txt | 1 + drivers/clk/sunxi/clk-simple-gates.c | 2 ++ 2 files changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt index fe34fc56e803..2a76cc3718d4 100644 --- a/Documentation/devicetree/bindings/clock/sunxi.txt +++ b/Documentation/devicetree/bindings/clock/sunxi.txt @@ -57,6 +57,7 @@ Required properties: "allwinner,sun6i-a31-apb2-gates-clk" - for the APB2 gates on A31 "allwinner,sun8i-a23-apb2-gates-clk" - for the APB2 gates on A23 "allwinner,sun4i-a10-display-clk" - for the display clocks on the A10 + "allwinner,sun5i-a13-dram-gates-clk" - for the DRAM gates on A13 "allwinner,sun5i-a13-mbus-clk" - for the MBUS clock on A13 "allwinner,sun4i-a10-mmc-clk" - for the MMC clock "allwinner,sun9i-a80-mmc-clk" - for mmc module clocks on A80 diff --git a/drivers/clk/sunxi/clk-simple-gates.c b/drivers/clk/sunxi/clk-simple-gates.c index 0214c6548afd..5666c767fa14 100644 --- a/drivers/clk/sunxi/clk-simple-gates.c +++ b/drivers/clk/sunxi/clk-simple-gates.c @@ -112,6 +112,8 @@ CLK_OF_DECLARE(sun5i_a13_apb0, "allwinner,sun5i-a13-apb0-gates-clk", sunxi_simple_gates_init); CLK_OF_DECLARE(sun5i_a13_apb1, "allwinner,sun5i-a13-apb1-gates-clk", sunxi_simple_gates_init); +CLK_OF_DECLARE(sun5i_a13_dram, "allwinner,sun5i-a13-dram-gates-clk", + sunxi_simple_gates_init); CLK_OF_DECLARE(sun6i_a31_ahb1, "allwinner,sun6i-a31-ahb1-gates-clk", sunxi_simple_gates_init); CLK_OF_DECLARE(sun6i_a31_apb1, "allwinner,sun6i-a31-apb1-gates-clk",
On Thu, Jan 14, 2016 at 04:24:51PM +0100, Maxime Ripard wrote:
The Allwinner SoCs have a gate controller to gate the access to the DRAM clock to the some devices that need to access the DRAM directly (mostly display / image related IPs).
Use a simple gates driver to support the one found in the A13 / R8 SoCs.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com Acked-by: Chen-Yu Tsai wens@csie.org
Documentation/devicetree/bindings/clock/sunxi.txt | 1 +
Acked-by: Rob Herring robh@kernel.org
drivers/clk/sunxi/clk-simple-gates.c | 2 ++ 2 files changed, 3 insertions(+)
Enable the pll3 and pll7 clocks in the DT that are used to drive the display-related clocks.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com Acked-by: Chen-Yu Tsai wens@csie.org --- arch/arm/boot/dts/sun5i.dtsi | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+)
diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi index 59a9426e3bd4..0840612b5ed6 100644 --- a/arch/arm/boot/dts/sun5i.dtsi +++ b/arch/arm/boot/dts/sun5i.dtsi @@ -88,6 +88,15 @@ clock-output-names = "osc24M"; };
+ osc3M: osc3M_clk { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clock-div = <8>; + clock-mult = <1>; + clocks = <&osc24M>; + clock-output-names = "osc3M"; + }; + osc32k: clk@0 { #clock-cells = <0>; compatible = "fixed-clock"; @@ -112,6 +121,23 @@ "pll2-4x", "pll2-8x"; };
+ pll3: clk@01c20010 { + #clock-cells = <0>; + compatible = "allwinner,sun4i-a10-pll3-clk"; + reg = <0x01c20010 0x4>; + clocks = <&osc3M>; + clock-output-names = "pll3"; + }; + + pll3x2: pll3x2_clk { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clock-div = <1>; + clock-mult = <2>; + clocks = <&pll3>; + clock-output-names = "pll3-2x"; + }; + pll4: clk@01c20018 { #clock-cells = <0>; compatible = "allwinner,sun4i-a10-pll1-clk"; @@ -136,6 +162,23 @@ clock-output-names = "pll6_sata", "pll6_other", "pll6"; };
+ pll7: clk@01c20030 { + #clock-cells = <0>; + compatible = "allwinner,sun4i-a10-pll3-clk"; + reg = <0x01c20030 0x4>; + clocks = <&osc3M>; + clock-output-names = "pll7"; + }; + + pll7x2: pll7x2_clk { + compatible = "fixed-factor-clock"; + #clock-cells = <0>; + clock-div = <1>; + clock-mult = <2>; + clocks = <&pll7>; + clock-output-names = "pll7-2x"; + }; + /* dummy is 200M */ cpu: cpu@01c20054 { #clock-cells = <0>;
Enable the display and TCON (channel 0 and channel 1) clocks that are going to be needed to drive the display engine, tcon and TV encoders.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- arch/arm/boot/dts/sun5i-a13.dtsi | 38 +++++++++++++++++++++++++++++++++++++- arch/arm/boot/dts/sun5i-r8.dtsi | 5 +++-- 2 files changed, 40 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boot/dts/sun5i-a13.dtsi b/arch/arm/boot/dts/sun5i-a13.dtsi index d910d3a6c41c..9669b03f20f3 100644 --- a/arch/arm/boot/dts/sun5i-a13.dtsi +++ b/arch/arm/boot/dts/sun5i-a13.dtsi @@ -61,7 +61,8 @@ compatible = "allwinner,simple-framebuffer", "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0"; - clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 44>; + clocks = <&ahb_gates 36>, <&ahb_gates 44>, <&de_be_clk>, + <&tcon_ch0_clk>; status = "disabled"; }; }; @@ -149,6 +150,41 @@ "apb1_i2c2", "apb1_uart1", "apb1_uart3"; }; + + de_be_clk: clk@01c20104 { + #clock-cells = <0>; + #reset-cells = <0>; + compatible = "allwinner,sun4i-a10-display-clk"; + reg = <0x01c20104 0x4>; + clocks = <&pll3>, <&pll7>, <&pll5 1>; + clock-output-names = "de-be"; + }; + + de_fe_clk: clk@01c2010c { + #clock-cells = <0>; + #reset-cells = <0>; + compatible = "allwinner,sun4i-a10-display-clk"; + reg = <0x01c2010c 0x4>; + clocks = <&pll3>, <&pll7>, <&pll5 1>; + clock-output-names = "de-fe"; + }; + + tcon_ch0_clk: clk@01c20118 { + #clock-cells = <0>; + #reset-cells = <1>; + compatible = "allwinner,sun4i-a10-tcon-ch0-clk"; + reg = <0x01c20118 0x4>; + clocks = <&pll3>, <&pll7>, <&pll3x2>, <&pll7x2>; + clock-output-names = "tcon-ch0-sclk"; + }; + + tcon_ch1_clk: clk@01c2012c { + #clock-cells = <0>; + compatible = "allwinner,sun4i-a10-tcon-ch1-clk"; + reg = <0x01c2012c 0x4>; + clocks = <&pll3>, <&pll7>, <&pll3x2>, <&pll7x2>; + clock-output-names = "tcon-ch1-sclk"; + }; };
soc@01c00000 { diff --git a/arch/arm/boot/dts/sun5i-r8.dtsi b/arch/arm/boot/dts/sun5i-r8.dtsi index 0ef865601ac9..b1e4e0170d51 100644 --- a/arch/arm/boot/dts/sun5i-r8.dtsi +++ b/arch/arm/boot/dts/sun5i-r8.dtsi @@ -51,8 +51,9 @@ compatible = "allwinner,simple-framebuffer", "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0-tve0"; - clocks = <&pll5 1>, <&ahb_gates 34>, <&ahb_gates 36>, - <&ahb_gates 44>; + clocks = <&ahb_gates 34>, <&ahb_gates 36>, + <&ahb_gates 44>, <&de_be_clk>, + <&tcon_ch1_clk>; status = "disabled"; }; };
Hi,
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
Enable the display and TCON (channel 0 and channel 1) clocks that are going to be needed to drive the display engine, tcon and TV encoders.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
arch/arm/boot/dts/sun5i-a13.dtsi | 38 +++++++++++++++++++++++++++++++++++++- arch/arm/boot/dts/sun5i-r8.dtsi | 5 +++-- 2 files changed, 40 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boot/dts/sun5i-a13.dtsi b/arch/arm/boot/dts/sun5i-a13.dtsi index d910d3a6c41c..9669b03f20f3 100644 --- a/arch/arm/boot/dts/sun5i-a13.dtsi +++ b/arch/arm/boot/dts/sun5i-a13.dtsi @@ -61,7 +61,8 @@ compatible = "allwinner,simple-framebuffer", "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0";
clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 44>;
clocks = <&ahb_gates 36>, <&ahb_gates 44>, <&de_be_clk>,
<&tcon_ch0_clk>; status = "disabled"; }; };
@@ -149,6 +150,41 @@ "apb1_i2c2", "apb1_uart1", "apb1_uart3"; };
de_be_clk: clk@01c20104 {
#clock-cells = <0>;
#reset-cells = <0>;
compatible = "allwinner,sun4i-a10-display-clk";
reg = <0x01c20104 0x4>;
clocks = <&pll3>, <&pll7>, <&pll5 1>;
clock-output-names = "de-be";
};
de_fe_clk: clk@01c2010c {
#clock-cells = <0>;
#reset-cells = <0>;
compatible = "allwinner,sun4i-a10-display-clk";
reg = <0x01c2010c 0x4>;
clocks = <&pll3>, <&pll7>, <&pll5 1>;
clock-output-names = "de-fe";
};
tcon_ch0_clk: clk@01c20118 {
#clock-cells = <0>;
#reset-cells = <1>;
You got it right here...
compatible = "allwinner,sun4i-a10-tcon-ch0-clk";
reg = <0x01c20118 0x4>;
clocks = <&pll3>, <&pll7>, <&pll3x2>, <&pll7x2>;
clock-output-names = "tcon-ch0-sclk";
};
tcon_ch1_clk: clk@01c2012c {
#clock-cells = <0>;
compatible = "allwinner,sun4i-a10-tcon-ch1-clk";
reg = <0x01c2012c 0x4>;
clocks = <&pll3>, <&pll7>, <&pll3x2>, <&pll7x2>;
clock-output-names = "tcon-ch1-sclk";
};
I suggest moving these to sun5i.dtsi, as they are shared amongst them.
ChenYu
}; soc@01c00000 {
diff --git a/arch/arm/boot/dts/sun5i-r8.dtsi b/arch/arm/boot/dts/sun5i-r8.dtsi index 0ef865601ac9..b1e4e0170d51 100644 --- a/arch/arm/boot/dts/sun5i-r8.dtsi +++ b/arch/arm/boot/dts/sun5i-r8.dtsi @@ -51,8 +51,9 @@ compatible = "allwinner,simple-framebuffer", "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0-tve0";
clocks = <&pll5 1>, <&ahb_gates 34>, <&ahb_gates 36>,
<&ahb_gates 44>;
clocks = <&ahb_gates 34>, <&ahb_gates 36>,
<&ahb_gates 44>, <&de_be_clk>,
<&tcon_ch1_clk>; status = "disabled"; }; };
-- 2.6.4
Hi,
On Sun, Jan 17, 2016 at 01:06:07AM +0800, Chen-Yu Tsai wrote:
compatible = "allwinner,sun4i-a10-tcon-ch0-clk";
reg = <0x01c20118 0x4>;
clocks = <&pll3>, <&pll7>, <&pll3x2>, <&pll7x2>;
clock-output-names = "tcon-ch0-sclk";
};
tcon_ch1_clk: clk@01c2012c {
#clock-cells = <0>;
compatible = "allwinner,sun4i-a10-tcon-ch1-clk";
reg = <0x01c2012c 0x4>;
clocks = <&pll3>, <&pll7>, <&pll3x2>, <&pll7x2>;
clock-output-names = "tcon-ch1-sclk";
};
I suggest moving these to sun5i.dtsi, as they are shared amongst them.
Eventually, yes, but I don't have an a10s board handy, and I couldn't test the clocks that needs to be taken by simplefb.
Once properly tested, they can definitely be moved to sun5i.dtsi.
Thanks! Maxime
On Thu, Feb 4, 2016 at 4:31 AM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi,
On Sun, Jan 17, 2016 at 01:06:07AM +0800, Chen-Yu Tsai wrote:
compatible = "allwinner,sun4i-a10-tcon-ch0-clk";
reg = <0x01c20118 0x4>;
clocks = <&pll3>, <&pll7>, <&pll3x2>, <&pll7x2>;
clock-output-names = "tcon-ch0-sclk";
};
tcon_ch1_clk: clk@01c2012c {
#clock-cells = <0>;
compatible = "allwinner,sun4i-a10-tcon-ch1-clk";
reg = <0x01c2012c 0x4>;
clocks = <&pll3>, <&pll7>, <&pll3x2>, <&pll7x2>;
clock-output-names = "tcon-ch1-sclk";
};
I suggest moving these to sun5i.dtsi, as they are shared amongst them.
Eventually, yes, but I don't have an a10s board handy, and I couldn't test the clocks that needs to be taken by simplefb.
Once properly tested, they can definitely be moved to sun5i.dtsi.
Acked-by: Chen-Yu Tsai wens@csie.org
The DRAM gates control whether the image / display devices on the SoC have access to the DRAM clock or not.
Enable it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- arch/arm/boot/dts/sun5i-a10s.dtsi | 7 ++++--- arch/arm/boot/dts/sun5i-a13.dtsi | 2 +- arch/arm/boot/dts/sun5i-r8.dtsi | 2 +- arch/arm/boot/dts/sun5i.dtsi | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi index bddd0de88af6..52d2c79cb37b 100644 --- a/arch/arm/boot/dts/sun5i-a10s.dtsi +++ b/arch/arm/boot/dts/sun5i-a10s.dtsi @@ -66,7 +66,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0-hdmi"; clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 43>, - <&ahb_gates 44>; + <&ahb_gates 44>, <&dram_gates 26>; status = "disabled"; };
@@ -74,7 +74,8 @@ compatible = "allwinner,simple-framebuffer", "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0"; - clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 44>; + clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 44>, + <&dram_gates 26>; status = "disabled"; };
@@ -83,7 +84,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0-tve0"; clocks = <&pll5 1>, <&ahb_gates 34>, <&ahb_gates 36>, - <&ahb_gates 44>; + <&ahb_gates 44>, <&dram_gates 26>; status = "disabled"; }; }; diff --git a/arch/arm/boot/dts/sun5i-a13.dtsi b/arch/arm/boot/dts/sun5i-a13.dtsi index 9669b03f20f3..f29163650ca8 100644 --- a/arch/arm/boot/dts/sun5i-a13.dtsi +++ b/arch/arm/boot/dts/sun5i-a13.dtsi @@ -62,7 +62,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0"; clocks = <&ahb_gates 36>, <&ahb_gates 44>, <&de_be_clk>, - <&tcon_ch0_clk>; + <&tcon_ch0_clk>, <&dram_gates 26>; status = "disabled"; }; }; diff --git a/arch/arm/boot/dts/sun5i-r8.dtsi b/arch/arm/boot/dts/sun5i-r8.dtsi index b1e4e0170d51..691d3de75b35 100644 --- a/arch/arm/boot/dts/sun5i-r8.dtsi +++ b/arch/arm/boot/dts/sun5i-r8.dtsi @@ -53,7 +53,7 @@ allwinner,pipeline = "de_be0-lcd0-tve0"; clocks = <&ahb_gates 34>, <&ahb_gates 36>, <&ahb_gates 44>, <&de_be_clk>, - <&tcon_ch1_clk>; + <&tcon_ch1_clk>, <&dram_gates 26>; status = "disabled"; }; }; diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi index 0840612b5ed6..c72d94228915 100644 --- a/arch/arm/boot/dts/sun5i.dtsi +++ b/arch/arm/boot/dts/sun5i.dtsi @@ -338,6 +338,25 @@ clock-output-names = "usb_ohci0", "usb_phy"; };
+ dram_gates: clk@01c20100 { + #clock-cells = <1>; + compatible = "allwinner,sun5i-a13-dram-gates-clk"; + reg = <0x01c20100 0x4>; + clocks = <&pll5 0>; + clock-indices = <0>, + <1>, + <25>, + <26>, + <29>, + <31>; + clock-output-names = "dram_ve", + "dram_csi", + "dram_de_fe", + "dram_de_be", + "dram_ace", + "dram_iep"; + }; + codec_clk: clk@01c20140 { #clock-cells = <0>; compatible = "allwinner,sun4i-a10-codec-clk";
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
The DRAM gates control whether the image / display devices on the SoC have access to the DRAM clock or not.
Enable it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
arch/arm/boot/dts/sun5i-a10s.dtsi | 7 ++++--- arch/arm/boot/dts/sun5i-a13.dtsi | 2 +- arch/arm/boot/dts/sun5i-r8.dtsi | 2 +- arch/arm/boot/dts/sun5i.dtsi | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi index bddd0de88af6..52d2c79cb37b 100644 --- a/arch/arm/boot/dts/sun5i-a10s.dtsi +++ b/arch/arm/boot/dts/sun5i-a10s.dtsi @@ -66,7 +66,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0-hdmi"; clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 43>,
<&ahb_gates 44>;
<&ahb_gates 44>, <&dram_gates 26>; status = "disabled"; };
@@ -74,7 +74,8 @@ compatible = "allwinner,simple-framebuffer", "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0";
clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 44>;
clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 44>,
<&dram_gates 26>; status = "disabled"; };
@@ -83,7 +84,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0-tve0"; clocks = <&pll5 1>, <&ahb_gates 34>, <&ahb_gates 36>,
<&ahb_gates 44>;
<&ahb_gates 44>, <&dram_gates 26>; status = "disabled"; }; };
diff --git a/arch/arm/boot/dts/sun5i-a13.dtsi b/arch/arm/boot/dts/sun5i-a13.dtsi index 9669b03f20f3..f29163650ca8 100644 --- a/arch/arm/boot/dts/sun5i-a13.dtsi +++ b/arch/arm/boot/dts/sun5i-a13.dtsi @@ -62,7 +62,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0"; clocks = <&ahb_gates 36>, <&ahb_gates 44>, <&de_be_clk>,
<&tcon_ch0_clk>;
<&tcon_ch0_clk>, <&dram_gates 26>; status = "disabled"; }; };
diff --git a/arch/arm/boot/dts/sun5i-r8.dtsi b/arch/arm/boot/dts/sun5i-r8.dtsi index b1e4e0170d51..691d3de75b35 100644 --- a/arch/arm/boot/dts/sun5i-r8.dtsi +++ b/arch/arm/boot/dts/sun5i-r8.dtsi @@ -53,7 +53,7 @@ allwinner,pipeline = "de_be0-lcd0-tve0"; clocks = <&ahb_gates 34>, <&ahb_gates 36>, <&ahb_gates 44>, <&de_be_clk>,
<&tcon_ch1_clk>;
<&tcon_ch1_clk>, <&dram_gates 26>; status = "disabled"; }; };
diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi index 0840612b5ed6..c72d94228915 100644 --- a/arch/arm/boot/dts/sun5i.dtsi +++ b/arch/arm/boot/dts/sun5i.dtsi @@ -338,6 +338,25 @@ clock-output-names = "usb_ohci0", "usb_phy"; };
dram_gates: clk@01c20100 {
#clock-cells = <1>;
compatible = "allwinner,sun5i-a13-dram-gates-clk";
reg = <0x01c20100 0x4>;
clocks = <&pll5 0>;
clock-indices = <0>,
<1>,
According to A10s manual, bit 3 is DRAM clock for TS (transport stream decoder), while bit 5 is for the TV encoder.
The others look good.
ChenYu
<25>,
<26>,
<29>,
<31>;
clock-output-names = "dram_ve",
"dram_csi",
"dram_de_fe",
"dram_de_be",
"dram_ace",
"dram_iep";
};
codec_clk: clk@01c20140 { #clock-cells = <0>; compatible = "allwinner,sun4i-a10-codec-clk";
-- 2.6.4
On Sun, Jan 17, 2016 at 01:10:02AM +0800, Chen-Yu Tsai wrote:
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
The DRAM gates control whether the image / display devices on the SoC have access to the DRAM clock or not.
Enable it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
arch/arm/boot/dts/sun5i-a10s.dtsi | 7 ++++--- arch/arm/boot/dts/sun5i-a13.dtsi | 2 +- arch/arm/boot/dts/sun5i-r8.dtsi | 2 +- arch/arm/boot/dts/sun5i.dtsi | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi index bddd0de88af6..52d2c79cb37b 100644 --- a/arch/arm/boot/dts/sun5i-a10s.dtsi +++ b/arch/arm/boot/dts/sun5i-a10s.dtsi @@ -66,7 +66,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0-hdmi"; clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 43>,
<&ahb_gates 44>;
<&ahb_gates 44>, <&dram_gates 26>; status = "disabled"; };
@@ -74,7 +74,8 @@ compatible = "allwinner,simple-framebuffer", "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0";
clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 44>;
clocks = <&pll5 1>, <&ahb_gates 36>, <&ahb_gates 44>,
<&dram_gates 26>; status = "disabled"; };
@@ -83,7 +84,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0-tve0"; clocks = <&pll5 1>, <&ahb_gates 34>, <&ahb_gates 36>,
<&ahb_gates 44>;
<&ahb_gates 44>, <&dram_gates 26>; status = "disabled"; }; };
diff --git a/arch/arm/boot/dts/sun5i-a13.dtsi b/arch/arm/boot/dts/sun5i-a13.dtsi index 9669b03f20f3..f29163650ca8 100644 --- a/arch/arm/boot/dts/sun5i-a13.dtsi +++ b/arch/arm/boot/dts/sun5i-a13.dtsi @@ -62,7 +62,7 @@ "simple-framebuffer"; allwinner,pipeline = "de_be0-lcd0"; clocks = <&ahb_gates 36>, <&ahb_gates 44>, <&de_be_clk>,
<&tcon_ch0_clk>;
<&tcon_ch0_clk>, <&dram_gates 26>; status = "disabled"; }; };
diff --git a/arch/arm/boot/dts/sun5i-r8.dtsi b/arch/arm/boot/dts/sun5i-r8.dtsi index b1e4e0170d51..691d3de75b35 100644 --- a/arch/arm/boot/dts/sun5i-r8.dtsi +++ b/arch/arm/boot/dts/sun5i-r8.dtsi @@ -53,7 +53,7 @@ allwinner,pipeline = "de_be0-lcd0-tve0"; clocks = <&ahb_gates 34>, <&ahb_gates 36>, <&ahb_gates 44>, <&de_be_clk>,
<&tcon_ch1_clk>;
<&tcon_ch1_clk>, <&dram_gates 26>; status = "disabled"; }; };
diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi index 0840612b5ed6..c72d94228915 100644 --- a/arch/arm/boot/dts/sun5i.dtsi +++ b/arch/arm/boot/dts/sun5i.dtsi @@ -338,6 +338,25 @@ clock-output-names = "usb_ohci0", "usb_phy"; };
dram_gates: clk@01c20100 {
#clock-cells = <1>;
compatible = "allwinner,sun5i-a13-dram-gates-clk";
reg = <0x01c20100 0x4>;
clocks = <&pll5 0>;
clock-indices = <0>,
<1>,
According to A10s manual, bit 3 is DRAM clock for TS (transport stream decoder), while bit 5 is for the TV encoder.
The others look good.
I'll fix that, thanks!
Maxime
It turns out that the A13 / R8 also have a tve encoder block, and a gate for it.
Add it to the DT.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- arch/arm/boot/dts/sun5i-a13.dtsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm/boot/dts/sun5i-a13.dtsi b/arch/arm/boot/dts/sun5i-a13.dtsi index f29163650ca8..618086e0694c 100644 --- a/arch/arm/boot/dts/sun5i-a13.dtsi +++ b/arch/arm/boot/dts/sun5i-a13.dtsi @@ -111,8 +111,8 @@ <10>, <13>, <14>, <20>, <21>, <22>, - <28>, <32>, <36>, - <40>, <44>, + <28>, <32>, <34>, + <36>, <40>, <44>, <46>, <51>, <52>; clock-output-names = "ahb_usbotg", "ahb_ehci", @@ -121,8 +121,8 @@ "ahb_mmc2", "ahb_nand", "ahb_sdram", "ahb_spi0", "ahb_spi1", "ahb_spi2", - "ahb_stimer", "ahb_ve", "ahb_lcd", - "ahb_csi", "ahb_de_be", + "ahb_stimer", "ahb_ve", "ahb_tve", + "ahb_lcd", "ahb_csi", "ahb_de_be", "ahb_de_fe", "ahb_iep", "ahb_mali400"; };
On Thu, Jan 14, 2016 at 11:24 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
It turns out that the A13 / R8 also have a tve encoder block, and a gate for it.
Add it to the DT.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Acked-by: Chen-Yu Tsai wens@csie.org
arch/arm/boot/dts/sun5i-a13.dtsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
[...]
The drm_fbdev_cma_init function always calls the drm_helper_disable_unused_functions. Since it's part of the usual probe process, all the drivers using that helper will end up having their encoder and CRTC disable functions called at probe if their device has not been reported as enabled.
This could be fixed by reading out from the registers the current state of the device if it is enabled, but even that will not handle the case where the device is actually disabled.
Moreover, the drivers using the atomic modesetting expect that their enable and disable callback to be called when the device is already enabled or disabled (respectively).
We can however fix this issue by moving the call to drm_helper_disable_unused_functions out of drm_fbdev_cma_init and make the drivers needing it (all the drivers calling drm_fbdev_cma_init and not using the atomic modesetting) explicitly call it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/drm_fb_cma_helper.c | 3 --- drivers/gpu/drm/imx/imx-drm-core.c | 1 + drivers/gpu/drm/sti/sti_drv.c | 1 + drivers/gpu/drm/tilcdc/tilcdc_drv.c | 1 + 4 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c index c19a62561183..daa98d881142 100644 --- a/drivers/gpu/drm/drm_fb_cma_helper.c +++ b/drivers/gpu/drm/drm_fb_cma_helper.c @@ -348,9 +348,6 @@ struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
}
- /* disable all the possible outputs/crtcs before entering KMS mode */ - drm_helper_disable_unused_functions(dev); - ret = drm_fb_helper_initial_config(helper, preferred_bpp); if (ret < 0) { dev_err(dev->dev, "Failed to set initial hw configuration.\n"); diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c index 7b990b4e96d2..e1db57791fc9 100644 --- a/drivers/gpu/drm/imx/imx-drm-core.c +++ b/drivers/gpu/drm/imx/imx-drm-core.c @@ -312,6 +312,7 @@ static int imx_drm_driver_load(struct drm_device *drm, unsigned long flags) dev_warn(drm->dev, "Invalid legacyfb_depth. Defaulting to 16bpp\n"); legacyfb_depth = 16; } + drm_helper_disable_unused_functions(drm); imxdrm->fbhelper = drm_fbdev_cma_init(drm, legacyfb_depth, drm->mode_config.num_crtc, MAX_CRTC); if (IS_ERR(imxdrm->fbhelper)) { diff --git a/drivers/gpu/drm/sti/sti_drv.c b/drivers/gpu/drm/sti/sti_drv.c index 1469987949d8..506b5626f3ed 100644 --- a/drivers/gpu/drm/sti/sti_drv.c +++ b/drivers/gpu/drm/sti/sti_drv.c @@ -160,6 +160,7 @@ static int sti_load(struct drm_device *dev, unsigned long flags)
drm_mode_config_reset(dev);
+ drm_helper_disable_unused_functions(dev); drm_fbdev_cma_init(dev, 32, dev->mode_config.num_crtc, dev->mode_config.num_connector); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 876cad58b1f9..24be31d69701 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -294,6 +294,7 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) break; }
+ drm_helper_disable_unused_functions(dev); priv->fbdev = drm_fbdev_cma_init(dev, bpp, dev->mode_config.num_crtc, dev->mode_config.num_connector);
Hi Maxime,
Thank you for the patch.
On Thursday 14 January 2016 16:24:56 Maxime Ripard wrote:
The drm_fbdev_cma_init function always calls the drm_helper_disable_unused_functions. Since it's part of the usual probe process, all the drivers using that helper will end up having their encoder and CRTC disable functions called at probe if their device has not been reported as enabled.
This could be fixed by reading out from the registers the current state of the device if it is enabled, but even that will not handle the case where the device is actually disabled.
Moreover, the drivers using the atomic modesetting expect that their enable and disable callback to be called when the device is already enabled or disabled (respectively).
We can however fix this issue by moving the call to drm_helper_disable_unused_functions out of drm_fbdev_cma_init and make the drivers needing it (all the drivers calling drm_fbdev_cma_init and not using the atomic modesetting) explicitly call it.
I'd rather add it to all drivers that call drm_fbdev_cma_init(). All the atomic ones must have special code to cope with it that could be rendered useless by the removal of the drm_helper_disable_unused_functions() call. That code should be removed too, and it would be easier to check drivers one by one and fixing them individually (outside of this patch series, unless you insist ;-)) when removing the explicit drm_helper_disable_unused_functions() call. Other than that the patch looks fine to me.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
drivers/gpu/drm/drm_fb_cma_helper.c | 3 --- drivers/gpu/drm/imx/imx-drm-core.c | 1 + drivers/gpu/drm/sti/sti_drv.c | 1 + drivers/gpu/drm/tilcdc/tilcdc_drv.c | 1 + 4 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c index c19a62561183..daa98d881142 100644 --- a/drivers/gpu/drm/drm_fb_cma_helper.c +++ b/drivers/gpu/drm/drm_fb_cma_helper.c @@ -348,9 +348,6 @@ struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
}
- /* disable all the possible outputs/crtcs before entering KMS mode */
- drm_helper_disable_unused_functions(dev);
- ret = drm_fb_helper_initial_config(helper, preferred_bpp); if (ret < 0) { dev_err(dev->dev, "Failed to set initial hw configuration.\n");
diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c index 7b990b4e96d2..e1db57791fc9 100644 --- a/drivers/gpu/drm/imx/imx-drm-core.c +++ b/drivers/gpu/drm/imx/imx-drm-core.c @@ -312,6 +312,7 @@ static int imx_drm_driver_load(struct drm_device *drm, unsigned long flags) dev_warn(drm->dev, "Invalid legacyfb_depth. Defaulting to 16bpp\n"); legacyfb_depth = 16; }
- drm_helper_disable_unused_functions(drm); imxdrm->fbhelper = drm_fbdev_cma_init(drm, legacyfb_depth, drm->mode_config.num_crtc, MAX_CRTC); if (IS_ERR(imxdrm->fbhelper)) {
diff --git a/drivers/gpu/drm/sti/sti_drv.c b/drivers/gpu/drm/sti/sti_drv.c index 1469987949d8..506b5626f3ed 100644 --- a/drivers/gpu/drm/sti/sti_drv.c +++ b/drivers/gpu/drm/sti/sti_drv.c @@ -160,6 +160,7 @@ static int sti_load(struct drm_device *dev, unsigned long flags)
drm_mode_config_reset(dev);
- drm_helper_disable_unused_functions(dev); drm_fbdev_cma_init(dev, 32, dev->mode_config.num_crtc, dev->mode_config.num_connector);
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 876cad58b1f9..24be31d69701 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -294,6 +294,7 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) break; }
- drm_helper_disable_unused_functions(dev); priv->fbdev = drm_fbdev_cma_init(dev, bpp, dev->mode_config.num_crtc, dev->mode_config.num_connector);
On Fri, Jan 15, 2016 at 01:13:05AM +0200, Laurent Pinchart wrote:
Hi Maxime,
Thank you for the patch.
On Thursday 14 January 2016 16:24:56 Maxime Ripard wrote:
The drm_fbdev_cma_init function always calls the drm_helper_disable_unused_functions. Since it's part of the usual probe process, all the drivers using that helper will end up having their encoder and CRTC disable functions called at probe if their device has not been reported as enabled.
This could be fixed by reading out from the registers the current state of the device if it is enabled, but even that will not handle the case where the device is actually disabled.
Moreover, the drivers using the atomic modesetting expect that their enable and disable callback to be called when the device is already enabled or disabled (respectively).
We can however fix this issue by moving the call to drm_helper_disable_unused_functions out of drm_fbdev_cma_init and make the drivers needing it (all the drivers calling drm_fbdev_cma_init and not using the atomic modesetting) explicitly call it.
I'd rather add it to all drivers that call drm_fbdev_cma_init(). All the atomic ones must have special code to cope with it that could be rendered useless by the removal of the drm_helper_disable_unused_functions() call. That code should be removed too, and it would be easier to check drivers one by one and fixing them individually (outside of this patch series, unless you insist ;-)) when removing the explicit drm_helper_disable_unused_functions() call.
I had the same thought, but figured there's really no good reason ever to do this. I suspect most of that disable_unused_function stuff we have all over is supreme cargo-cult anyway ;-)
Other than that the patch looks fine to me.
So went ahead and applied to drm-misc. -Daniel
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
drivers/gpu/drm/drm_fb_cma_helper.c | 3 --- drivers/gpu/drm/imx/imx-drm-core.c | 1 + drivers/gpu/drm/sti/sti_drv.c | 1 + drivers/gpu/drm/tilcdc/tilcdc_drv.c | 1 + 4 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c index c19a62561183..daa98d881142 100644 --- a/drivers/gpu/drm/drm_fb_cma_helper.c +++ b/drivers/gpu/drm/drm_fb_cma_helper.c @@ -348,9 +348,6 @@ struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
}
- /* disable all the possible outputs/crtcs before entering KMS mode */
- drm_helper_disable_unused_functions(dev);
- ret = drm_fb_helper_initial_config(helper, preferred_bpp); if (ret < 0) { dev_err(dev->dev, "Failed to set initial hw configuration.\n");
diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c index 7b990b4e96d2..e1db57791fc9 100644 --- a/drivers/gpu/drm/imx/imx-drm-core.c +++ b/drivers/gpu/drm/imx/imx-drm-core.c @@ -312,6 +312,7 @@ static int imx_drm_driver_load(struct drm_device *drm, unsigned long flags) dev_warn(drm->dev, "Invalid legacyfb_depth. Defaulting to 16bpp\n"); legacyfb_depth = 16; }
- drm_helper_disable_unused_functions(drm); imxdrm->fbhelper = drm_fbdev_cma_init(drm, legacyfb_depth, drm->mode_config.num_crtc, MAX_CRTC); if (IS_ERR(imxdrm->fbhelper)) {
diff --git a/drivers/gpu/drm/sti/sti_drv.c b/drivers/gpu/drm/sti/sti_drv.c index 1469987949d8..506b5626f3ed 100644 --- a/drivers/gpu/drm/sti/sti_drv.c +++ b/drivers/gpu/drm/sti/sti_drv.c @@ -160,6 +160,7 @@ static int sti_load(struct drm_device *dev, unsigned long flags)
drm_mode_config_reset(dev);
- drm_helper_disable_unused_functions(dev); drm_fbdev_cma_init(dev, 32, dev->mode_config.num_crtc, dev->mode_config.num_connector);
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 876cad58b1f9..24be31d69701 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -294,6 +294,7 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) break; }
- drm_helper_disable_unused_functions(dev); priv->fbdev = drm_fbdev_cma_init(dev, bpp, dev->mode_config.num_crtc, dev->mode_config.num_connector);
-- Regards,
Laurent Pinchart
Hi Daniel,
On Friday 15 January 2016 11:17:30 Daniel Vetter wrote:
On Fri, Jan 15, 2016 at 01:13:05AM +0200, Laurent Pinchart wrote:
On Thursday 14 January 2016 16:24:56 Maxime Ripard wrote:
The drm_fbdev_cma_init function always calls the drm_helper_disable_unused_functions. Since it's part of the usual probe process, all the drivers using that helper will end up having their encoder and CRTC disable functions called at probe if their device has not been reported as enabled.
This could be fixed by reading out from the registers the current state of the device if it is enabled, but even that will not handle the case where the device is actually disabled.
Moreover, the drivers using the atomic modesetting expect that their enable and disable callback to be called when the device is already enabled or disabled (respectively).
We can however fix this issue by moving the call to drm_helper_disable_unused_functions out of drm_fbdev_cma_init and make the drivers needing it (all the drivers calling drm_fbdev_cma_init and not using the atomic modesetting) explicitly call it.
I'd rather add it to all drivers that call drm_fbdev_cma_init(). All the atomic ones must have special code to cope with it that could be rendered useless by the removal of the drm_helper_disable_unused_functions() call. That code should be removed too, and it would be easier to check drivers one by one and fixing them individually (outside of this patch series, unless you insist ;-)) when removing the explicit drm_helper_disable_unused_functions() call.
I had the same thought, but figured there's really no good reason ever to do this. I suspect most of that disable_unused_function stuff we have all over is supreme cargo-cult anyway ;-)
I'm not sure you got my point. Yes, the drm_helper_disable_unused_functions() call should be removed from atomic drivers, but that will leave support code added for the sole reason of avoiding the crash in the drivers. That code will likely not be noticed and will stay there rotting. If we added drm_helper_disable_unused_functions() calls to all atomic drivers then driver authors will hopefully check carefully if there's any support code that can be removed when removing the function call. It would act as a kind of FIXME reminder.
Other than that the patch looks fine to me.
So went ahead and applied to drm-misc.
On Mon, Jan 25, 2016 at 12:19:27AM +0200, Laurent Pinchart wrote:
Hi Daniel,
On Friday 15 January 2016 11:17:30 Daniel Vetter wrote:
On Fri, Jan 15, 2016 at 01:13:05AM +0200, Laurent Pinchart wrote:
On Thursday 14 January 2016 16:24:56 Maxime Ripard wrote:
The drm_fbdev_cma_init function always calls the drm_helper_disable_unused_functions. Since it's part of the usual probe process, all the drivers using that helper will end up having their encoder and CRTC disable functions called at probe if their device has not been reported as enabled.
This could be fixed by reading out from the registers the current state of the device if it is enabled, but even that will not handle the case where the device is actually disabled.
Moreover, the drivers using the atomic modesetting expect that their enable and disable callback to be called when the device is already enabled or disabled (respectively).
We can however fix this issue by moving the call to drm_helper_disable_unused_functions out of drm_fbdev_cma_init and make the drivers needing it (all the drivers calling drm_fbdev_cma_init and not using the atomic modesetting) explicitly call it.
I'd rather add it to all drivers that call drm_fbdev_cma_init(). All the atomic ones must have special code to cope with it that could be rendered useless by the removal of the drm_helper_disable_unused_functions() call. That code should be removed too, and it would be easier to check drivers one by one and fixing them individually (outside of this patch series, unless you insist ;-)) when removing the explicit drm_helper_disable_unused_functions() call.
I had the same thought, but figured there's really no good reason ever to do this. I suspect most of that disable_unused_function stuff we have all over is supreme cargo-cult anyway ;-)
I'm not sure you got my point. Yes, the drm_helper_disable_unused_functions() call should be removed from atomic drivers, but that will leave support code added for the sole reason of avoiding the crash in the drivers. That code will likely not be noticed and will stay there rotting. If we added drm_helper_disable_unused_functions() calls to all atomic drivers then driver authors will hopefully check carefully if there's any support code that can be removed when removing the function call. It would act as a kind of FIXME reminder.
drm_helper_disable_unused_functions() already prefers the ->disable callbacks over dpms, like atomic helpers. I don't think there's any dead code due to this change. The problem was that driver/hw blew up since it was disabled when the hw was off already. -Daniel
Hi Daniel,
On Monday 25 January 2016 08:29:38 Daniel Vetter wrote:
On Mon, Jan 25, 2016 at 12:19:27AM +0200, Laurent Pinchart wrote:
On Friday 15 January 2016 11:17:30 Daniel Vetter wrote:
On Fri, Jan 15, 2016 at 01:13:05AM +0200, Laurent Pinchart wrote:
On Thursday 14 January 2016 16:24:56 Maxime Ripard wrote:
The drm_fbdev_cma_init function always calls the drm_helper_disable_unused_functions. Since it's part of the usual probe process, all the drivers using that helper will end up having their encoder and CRTC disable functions called at probe if their device has not been reported as enabled.
This could be fixed by reading out from the registers the current state of the device if it is enabled, but even that will not handle the case where the device is actually disabled.
Moreover, the drivers using the atomic modesetting expect that their enable and disable callback to be called when the device is already enabled or disabled (respectively).
We can however fix this issue by moving the call to drm_helper_disable_unused_functions out of drm_fbdev_cma_init and make the drivers needing it (all the drivers calling drm_fbdev_cma_init and not using the atomic modesetting) explicitly call it.
I'd rather add it to all drivers that call drm_fbdev_cma_init(). All the atomic ones must have special code to cope with it that could be rendered useless by the removal of the drm_helper_disable_unused_functions() call. That code should be removed too, and it would be easier to check drivers one by one and fixing them individually (outside of this patch series, unless you insist ;-)) when removing the explicit drm_helper_disable_unused_functions() call.
I had the same thought, but figured there's really no good reason ever to do this. I suspect most of that disable_unused_function stuff we have all over is supreme cargo-cult anyway ;-)
I'm not sure you got my point. Yes, the drm_helper_disable_unused_functions() call should be removed from atomic drivers, but that will leave support code added for the sole reason of avoiding the crash in the drivers. That code will likely not be noticed and will stay there rotting. If we added drm_helper_disable_unused_functions() calls to all atomic drivers then driver authors will hopefully check carefully if there's any support code that can be removed when removing the function call. It would act as a kind of FIXME reminder.
drm_helper_disable_unused_functions() already prefers the ->disable callbacks over dpms, like atomic helpers. I don't think there's any dead code due to this change. The problem was that driver/hw blew up since it was disabled when the hw was off already.
The rcar-du-drm driver keeps an internal CRTC enabled state for just this purpose. I expect other drivers to implement something similar that can be removed after dropping the drm_helper_disable_unused_functions() calls.
Rewrite the command line parser in order to get away from the state machine parsing the video mode lines.
Hopefully, this will allow to extend it more easily to support named modes and / or properties set directly on the command line.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/drm_modes.c | 308 +++++++++++++++++++++++++++----------------- 1 file changed, 191 insertions(+), 117 deletions(-)
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index cd74a0953f42..5b1921b22dd6 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -30,6 +30,7 @@ * authorization from the copyright holder(s) and author(s). */
+#include <linux/ctype.h> #include <linux/list.h> #include <linux/list_sort.h> #include <linux/export.h> @@ -1199,6 +1200,131 @@ void drm_mode_connector_list_update(struct drm_connector *connector, } EXPORT_SYMBOL(drm_mode_connector_list_update);
+static int drm_mode_parse_cmdline_bpp(const char *str, char **end_ptr, + struct drm_cmdline_mode *mode) +{ + if (str[0] != '-') + return -EINVAL; + + mode->bpp = simple_strtol(str + 1, end_ptr, 10); + mode->bpp_specified = true; + + return 0; +} + +static int drm_mode_parse_cmdline_refresh(const char *str, char **end_ptr, + struct drm_cmdline_mode *mode) +{ + if (str[0] != '@') + return -EINVAL; + + mode->refresh = simple_strtol(str + 1, end_ptr, 10); + mode->refresh_specified = true; + + return 0; +} + +static int drm_mode_parse_cmdline_extra(const char *str, int length, + struct drm_connector *connector, + struct drm_cmdline_mode *mode) +{ + int i; + + for (i = 0; i < length; i++) { + switch (str[i]) { + case 'i': + mode->interlace = true; + break; + case 'm': + mode->margins = true; + break; + case 'D': + if (mode->force != DRM_FORCE_UNSPECIFIED) + return -EINVAL; + + if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) && + (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB)) + mode->force = DRM_FORCE_ON; + else + mode->force = DRM_FORCE_ON_DIGITAL; + break; + case 'd': + if (mode->force != DRM_FORCE_UNSPECIFIED) + return -EINVAL; + + mode->force = DRM_FORCE_OFF; + break; + case 'e': + if (mode->force != DRM_FORCE_UNSPECIFIED) + return -EINVAL; + + mode->force = DRM_FORCE_ON; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static int drm_mode_parse_cmdline_res_mode(const char *str, unsigned int length, + bool extras, + struct drm_connector *connector, + struct drm_cmdline_mode *mode) +{ + bool rb = false, cvt = false; + int xres = 0, yres = 0; + int remaining, i; + char *end_ptr; + + xres = simple_strtol(str, &end_ptr, 10); + + if (end_ptr[0] != 'x') + return -EINVAL; + end_ptr++; + + yres = simple_strtol(end_ptr, &end_ptr, 10); + + remaining = length - (end_ptr - str); + if (remaining < 0) + return -EINVAL; + + for (i = 0; i < remaining; i++) { + switch (end_ptr[i]) { + case 'M': + cvt = true; + break; + case 'R': + rb = true; + break; + default: + /* + * Try to pass that to our extras parsing + * function to handle the case where the + * extras are directly after the resolution + */ + if (extras) { + int ret = drm_mode_parse_cmdline_extra(end_ptr + i, + 1, + connector, + mode); + if (ret) + return ret; + } else { + return -EINVAL; + } + } + } + + mode->xres = xres; + mode->yres = yres; + mode->cvt = cvt; + mode->rb = rb; + + return 0; +} + /** * drm_mode_parse_command_line_for_connector - parse command line modeline for connector * @mode_option: optional per connector mode option @@ -1225,13 +1351,12 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option, struct drm_cmdline_mode *mode) { const char *name; - unsigned int namelen; - bool res_specified = false, bpp_specified = false, refresh_specified = false; - unsigned int xres = 0, yres = 0, bpp = 32, refresh = 0; - bool yres_specified = false, cvt = false, rb = false; - bool interlace = false, margins = false, was_digit = false; - int i; - enum drm_connector_force force = DRM_FORCE_UNSPECIFIED; + bool parse_extras = false; + unsigned int bpp_off = 0, refresh_off = 0; + unsigned int mode_end = 0; + char *bpp_ptr = NULL, *refresh_ptr = NULL, *extra_ptr = NULL; + char *bpp_end_ptr = NULL, *refresh_end_ptr = NULL; + int ret;
#ifdef CONFIG_FB if (!mode_option) @@ -1244,128 +1369,77 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option, }
name = mode_option; - namelen = strlen(name); - for (i = namelen-1; i >= 0; i--) { - switch (name[i]) { - case '@': - if (!refresh_specified && !bpp_specified && - !yres_specified && !cvt && !rb && was_digit) { - refresh = simple_strtol(&name[i+1], NULL, 10); - refresh_specified = true; - was_digit = false; - } else - goto done; - break; - case '-': - if (!bpp_specified && !yres_specified && !cvt && - !rb && was_digit) { - bpp = simple_strtol(&name[i+1], NULL, 10); - bpp_specified = true; - was_digit = false; - } else - goto done; - break; - case 'x': - if (!yres_specified && was_digit) { - yres = simple_strtol(&name[i+1], NULL, 10); - yres_specified = true; - was_digit = false; - } else - goto done; - break; - case '0' ... '9': - was_digit = true; - break; - case 'M': - if (yres_specified || cvt || was_digit) - goto done; - cvt = true; - break; - case 'R': - if (yres_specified || cvt || rb || was_digit) - goto done; - rb = true; - break; - case 'm': - if (cvt || yres_specified || was_digit) - goto done; - margins = true; - break; - case 'i': - if (cvt || yres_specified || was_digit) - goto done; - interlace = true; - break; - case 'e': - if (yres_specified || bpp_specified || refresh_specified || - was_digit || (force != DRM_FORCE_UNSPECIFIED)) - goto done;
- force = DRM_FORCE_ON; - break; - case 'D': - if (yres_specified || bpp_specified || refresh_specified || - was_digit || (force != DRM_FORCE_UNSPECIFIED)) - goto done; + if (!isdigit(name[0])) + return false;
- if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) && - (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB)) - force = DRM_FORCE_ON; - else - force = DRM_FORCE_ON_DIGITAL; - break; - case 'd': - if (yres_specified || bpp_specified || refresh_specified || - was_digit || (force != DRM_FORCE_UNSPECIFIED)) - goto done; + /* Try to locate the bpp and refresh specifiers, if any */ + bpp_ptr = strchr(name, '-'); + if (bpp_ptr) { + bpp_off = bpp_ptr - name; + mode->bpp_specified = true; + }
- force = DRM_FORCE_OFF; - break; - default: - goto done; - } + refresh_ptr = strchr(name, '@'); + if (refresh_ptr) { + refresh_off = refresh_ptr - name; + mode->refresh_specified = true; }
- if (i < 0 && yres_specified) { - char *ch; - xres = simple_strtol(name, &ch, 10); - if ((ch != NULL) && (*ch == 'x')) - res_specified = true; - else - i = ch - name; - } else if (!yres_specified && was_digit) { - /* catch mode that begins with digits but has no 'x' */ - i = 0; + /* Locate the end of the name / resolution, and parse it */ + if (bpp_ptr && refresh_ptr) { + mode_end = min(bpp_off, refresh_off); + } else if (bpp_ptr) { + mode_end = bpp_off; + } else if (refresh_ptr) { + mode_end = refresh_off; + } else { + mode_end = strlen(name); + parse_extras = true; } -done: - if (i >= 0) { - printk(KERN_WARNING - "parse error at position %i in video mode '%s'\n", - i, name); - mode->specified = false; + + ret = drm_mode_parse_cmdline_res_mode(name, mode_end, + parse_extras, + connector, + mode); + if (ret) return false; - } + mode->specified = true;
- if (res_specified) { - mode->specified = true; - mode->xres = xres; - mode->yres = yres; + if (bpp_ptr) { + ret = drm_mode_parse_cmdline_bpp(bpp_ptr, &bpp_end_ptr, mode); + if (ret) + return false; }
- if (refresh_specified) { - mode->refresh_specified = true; - mode->refresh = refresh; + if (refresh_ptr) { + ret = drm_mode_parse_cmdline_refresh(refresh_ptr, + &refresh_end_ptr, mode); + if (ret) + return false; }
- if (bpp_specified) { - mode->bpp_specified = true; - mode->bpp = bpp; + /* + * Locate the end of the bpp / refresh, and parse the extras + * if relevant + */ + if (bpp_ptr && refresh_ptr) + extra_ptr = max(bpp_end_ptr, refresh_end_ptr); + else if (bpp_ptr) + extra_ptr = bpp_end_ptr; + else if (refresh_ptr) + extra_ptr = refresh_end_ptr; + + if (extra_ptr) { + int remaining = strlen(name) - (extra_ptr - name); + + /* + * We still have characters to process, while + * we shouldn't have any + */ + if (remaining > 0) + return false; } - mode->rb = rb; - mode->cvt = cvt; - mode->interlace = interlace; - mode->margins = margins; - mode->force = force;
return true; } @@ -1491,4 +1565,4 @@ int drm_mode_convert_umode(struct drm_display_mode *out,
out: return ret; -} \ No newline at end of file +}
The drm subsystem also uses the video= kernel parameter, and in the documentation refers to the fbdev documentation for that parameter.
However, that documentation also says that instead of giving the mode using its resolution we can also give a name. However, DRM doesn't handle that case at the moment. Even though in most case it shouldn't make any difference, it might be useful for analog modes, where different standards might have the same resolution, but still have a few different parameters that are not encoded in the modes (NTSC vs NTSC-J vs PAL-M for example).
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/drm_crtc.c | 3 ++- drivers/gpu/drm/drm_fb_helper.c | 4 ++++ drivers/gpu/drm/drm_modes.c | 49 ++++++++++++++++++++++++++++------------- include/drm/drm_modes.h | 1 + 4 files changed, 41 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 24c5434abd1c..c3267d695e4e 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -846,8 +846,9 @@ static void drm_connector_get_cmdline_mode(struct drm_connector *connector) connector->force = mode->force; }
- DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n", + DRM_DEBUG_KMS("cmdline mode for connector %s %s %dx%d@%dHz%s%s%s\n", connector->name, + mode->name ? mode->name : "", mode->xres, mode->yres, mode->refresh_specified ? mode->refresh : 60, mode->rb ? " reduced blanking" : "", diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 69cbab5e5c81..fcb7ba796202 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -1649,6 +1649,10 @@ struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *f prefer_non_interlace = !cmdline_mode->interlace; again: list_for_each_entry(mode, &fb_helper_conn->connector->modes, head) { + /* Check (optional) mode name first */ + if (!strcmp(mode->name, cmdline_mode->name)) + return mode; + /* check width/height */ if (mode->hdisplay != cmdline_mode->xres || mode->vdisplay != cmdline_mode->yres) diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index 5b1921b22dd6..bbb23000eb5b 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -1351,7 +1351,7 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option, struct drm_cmdline_mode *mode) { const char *name; - bool parse_extras = false; + bool named_mode = false, parse_extras = false; unsigned int bpp_off = 0, refresh_off = 0; unsigned int mode_end = 0; char *bpp_ptr = NULL, *refresh_ptr = NULL, *extra_ptr = NULL; @@ -1370,8 +1370,14 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option,
name = mode_option;
+ /* + * If the first character is not a digit, then it means that + * we have a named mode. + */ if (!isdigit(name[0])) - return false; + named_mode = true; + else + named_mode = false;
/* Try to locate the bpp and refresh specifiers, if any */ bpp_ptr = strchr(name, '-'); @@ -1398,12 +1404,16 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option, parse_extras = true; }
- ret = drm_mode_parse_cmdline_res_mode(name, mode_end, - parse_extras, - connector, - mode); - if (ret) - return false; + if (named_mode) { + strncpy(mode->name, name, mode_end); + } else { + ret = drm_mode_parse_cmdline_res_mode(name, mode_end, + parse_extras, + connector, + mode); + if (ret) + return false; + } mode->specified = true;
if (bpp_ptr) { @@ -1431,14 +1441,23 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option, extra_ptr = refresh_end_ptr;
if (extra_ptr) { - int remaining = strlen(name) - (extra_ptr - name); + if (!named_mode) { + int len = strlen(name) - (extra_ptr - name);
- /* - * We still have characters to process, while - * we shouldn't have any - */ - if (remaining > 0) - return false; + ret = drm_mode_parse_cmdline_extra(extra_ptr, len, + connector, mode); + if (ret) + return false; + } else { + int remaining = strlen(name) - (extra_ptr - name); + + /* + * We still have characters to process, while + * we shouldn't have any + */ + if (remaining > 0) + return false; + } }
return true; diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h index 08a8cac9e555..709d43f3515e 100644 --- a/include/drm/drm_modes.h +++ b/include/drm/drm_modes.h @@ -151,6 +151,7 @@ struct drm_display_mode {
/* mode specified on the command line */ struct drm_cmdline_mode { + char name[DRM_DISPLAY_MODE_LEN]; bool specified; bool refresh_specified; bool bpp_specified;
The Allwinner A10 and subsequent SoCs share the same display pipeline, with variations in the number of controllers (1 or 2), or the presence or not of some output (HDMI, TV, VGA) or not.
Add a driver with a limited set of features for now, and we will hopefully support all of them eventually
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 3 +- drivers/gpu/drm/sun4i/Kconfig | 14 + drivers/gpu/drm/sun4i/Makefile | 9 + drivers/gpu/drm/sun4i/sun4i_backend.c | 321 +++++++++++++++++++ drivers/gpu/drm/sun4i/sun4i_backend.h | 160 ++++++++++ drivers/gpu/drm/sun4i/sun4i_crtc.c | 120 ++++++++ drivers/gpu/drm/sun4i/sun4i_crtc.h | 30 ++ drivers/gpu/drm/sun4i/sun4i_drv.c | 325 +++++++++++++++++++ drivers/gpu/drm/sun4i/sun4i_drv.h | 30 ++ drivers/gpu/drm/sun4i/sun4i_framebuffer.c | 54 ++++ drivers/gpu/drm/sun4i/sun4i_framebuffer.h | 19 ++ drivers/gpu/drm/sun4i/sun4i_layer.c | 160 ++++++++++ drivers/gpu/drm/sun4i/sun4i_layer.h | 30 ++ drivers/gpu/drm/sun4i/sun4i_tcon.c | 497 ++++++++++++++++++++++++++++++ drivers/gpu/drm/sun4i/sun4i_tcon.h | 175 +++++++++++ 16 files changed, 1948 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sun4i/Kconfig create mode 100644 drivers/gpu/drm/sun4i/Makefile create mode 100644 drivers/gpu/drm/sun4i/sun4i_backend.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_backend.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_crtc.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_crtc.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_drv.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_drv.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_framebuffer.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_framebuffer.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_layer.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_layer.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_tcon.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_tcon.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index c4bf9a1cf4a6..ea029f1534fe 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -239,6 +239,8 @@ source "drivers/gpu/drm/rcar-du/Kconfig"
source "drivers/gpu/drm/shmobile/Kconfig"
+source "drivers/gpu/drm/sun4i/Kconfig" + source "drivers/gpu/drm/omapdrm/Kconfig"
source "drivers/gpu/drm/tilcdc/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 1e9ff4c3e3db..d8ea25dabca9 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -1,4 +1,4 @@ -# + # Makefile for the drm device driver. This driver provides support for the # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
@@ -62,6 +62,7 @@ obj-$(CONFIG_DRM_ARMADA) += armada/ obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/ obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ +obj-$(CONFIG_DRM_SUN4I) += sun4i/ obj-$(CONFIG_DRM_OMAP) += omapdrm/ obj-y += tilcdc/ obj-$(CONFIG_DRM_QXL) += qxl/ diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig new file mode 100644 index 000000000000..99510e64e91a --- /dev/null +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -0,0 +1,14 @@ +config DRM_SUN4I + tristate "DRM Support for Allwinner A10 Display Engine" + depends on DRM && ARM + depends on ARCH_SUNXI || COMPILE_TEST + select DRM_GEM_CMA_HELPER + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_PANEL + select REGMAP_MMIO + select VIDEOMODE_HELPERS + help + Choose this option if you have an Allwinner SoC with a + Display Engine. If M is selected the module will be called + sun4i-drm. diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile new file mode 100644 index 000000000000..7b0e83bae867 --- /dev/null +++ b/drivers/gpu/drm/sun4i/Makefile @@ -0,0 +1,9 @@ +sun4i-drm-y += sun4i_crtc.o +sun4i-drm-y += sun4i_drv.o +sun4i-drm-y += sun4i_framebuffer.o +sun4i-drm-y += sun4i_layer.o + +obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o + +obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o +obj-$(CONFIG_DRM_SUN4I) += sun4i_tcon.o diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c new file mode 100644 index 000000000000..082fef60dd3e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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 <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> + +#include <linux/component.h> + +#include "sun4i_backend.h" +#include "sun4i_drv.h" + +static u32 sunxi_rgb2yuv_coef[12] = { + 0x00000107, 0x00000204, 0x00000064, 0x00000108, + 0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808, + 0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808 +}; + +void sun4i_backend_apply_color_correction(struct sun4i_backend *backend) +{ + int i; + + /* Set color correction */ + regmap_write(backend->regs, SUN4I_BACKEND_OCCTL_REG, + SUN4I_BACKEND_OCCTL_ENABLE); + + for (i = 0; i < 12; i++) + regmap_write(backend->regs, SUN4I_BACKEND_OCRCOEF_REG(i), + sunxi_rgb2yuv_coef[i]); +} + +void sun4i_backend_commit(struct sun4i_backend *backend) +{ + DRM_DEBUG_DRIVER("Committing changes\n"); + + regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG, + SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS | + SUN4I_BACKEND_REGBUFFCTL_LOADCTL); +} + +void sun4i_backend_layer_enable(struct sun4i_backend *backend, + int layer, bool enable) +{ + u32 val; + + DRM_DEBUG_DRIVER("Enabling layer %d\n", layer); + + if (enable) + val = SUN4I_BACKEND_MODCTL_LAY_EN(layer); + else + val = 0; + + regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_LAY_EN(layer), val); +} + +static int sun4i_backend_drm_format_to_layer(u32 format, u32 *mode) +{ + switch (format) { + case DRM_FORMAT_ARGB8888: + *mode = SUN4I_BACKEND_LAY_FBFMT_ARGB8888; + break; + + case DRM_FORMAT_XRGB8888: + *mode = SUN4I_BACKEND_LAY_FBFMT_XRGB8888; + break; + + case DRM_FORMAT_RGB888: + *mode = SUN4I_BACKEND_LAY_FBFMT_RGB888; + break; + + default: + return -EINVAL; + } + + return 0; +} + +int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + + DRM_DEBUG_DRIVER("Updating layer %d\n", layer); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(backend->regs, SUN4I_BACKEND_DISSIZE_REG, + SUN4I_BACKEND_DISSIZE(state->crtc_w, + state->crtc_h)); + } + + /* Set the line width */ + DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); + regmap_write(backend->regs, SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), + fb->pitches[0] * 8); + + /* Set height and width */ + DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(backend->regs, SUN4I_BACKEND_LAYSIZE_REG(layer), + SUN4I_BACKEND_LAYSIZE(state->crtc_w, + state->crtc_h)); + + /* Set base coordinates */ + DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n", + state->crtc_x, state->crtc_y); + regmap_write(backend->regs, SUN4I_BACKEND_LAYCOOR_REG(layer), + SUN4I_BACKEND_LAYCOOR(state->crtc_x, + state->crtc_y)); + + return 0; +} + +int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + bool interlaced = false; + u32 val; + int ret; + + if (plane->state->crtc) + interlaced = plane->state->crtc->state->adjusted_mode.flags + & DRM_MODE_FLAG_INTERLACE; + + regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_ITLMOD_EN, + interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0); + + DRM_DEBUG_DRIVER("Switching display backend interlaced mode %s\n", + interlaced ? "on" : "off"); + + ret = sun4i_backend_drm_format_to_layer(fb->pixel_format, &val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid format\n"); + return val; + } + + regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG1(layer), + SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val); + + return 0; +} + +int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *gem; + u32 lo_paddr, hi_paddr; + dma_addr_t paddr; + int bpp; + + /* Get the physical address of the buffer in memory */ + gem = drm_fb_cma_get_gem_obj(fb, 0); + + DRM_DEBUG_DRIVER("Using GEM @ 0x%x\n", gem->paddr); + + /* Compute the start of the displayed memory */ + bpp = drm_format_plane_cpp(fb->pixel_format, 0); + paddr = gem->paddr + fb->offsets[0]; + paddr += state->src_x * bpp; + paddr += state->src_y * fb->pitches[0]; + + DRM_DEBUG_DRIVER("Setting buffer address to 0x%x\n", paddr); + + /* Write the 32 lower bits of the address (in bits) */ + lo_paddr = paddr << 3; + DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr); + regmap_write(backend->regs, SUN4I_BACKEND_LAYFB_L32ADD_REG(layer), + lo_paddr); + + /* And the upper bits */ + hi_paddr = paddr >> 29; + DRM_DEBUG_DRIVER("Setting address high bits to 0x%x\n", hi_paddr); + regmap_update_bits(backend->regs, SUN4I_BACKEND_LAYFB_H4ADD_REG, + SUN4I_BACKEND_LAYFB_H4ADD_MSK(layer), + SUN4I_BACKEND_LAYFB_H4ADD(layer, hi_paddr)); + + return 0; +} + +static struct regmap_config sun4i_backend_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x5800, +}; + +static int sun4i_backend_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_backend *backend; + struct resource *res; + void __iomem *regs; + int i; + + backend = devm_kzalloc(dev, sizeof(*backend), GFP_KERNEL); + if (!backend) + return -ENOMEM; + dev_set_drvdata(dev, backend); + drv->backend = backend; + + 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"); + return PTR_ERR(regs); + } + + backend->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_backend_regmap_config); + if (IS_ERR(backend->regs)) { + dev_err(dev, "Couldn't create the backend0 regmap\n"); + return PTR_ERR(backend->regs); + } + + backend->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(backend->bus_clk)) { + dev_err(dev, "Couldn't get the backend bus clock\n"); + return PTR_ERR(backend->bus_clk); + } + clk_prepare_enable(backend->bus_clk); + + backend->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(backend->mod_clk)) { + dev_err(dev, "Couldn't get the backend module clock\n"); + return PTR_ERR(backend->mod_clk); + } + clk_prepare_enable(backend->mod_clk); + + backend->ram_clk = devm_clk_get(dev, "ram"); + if (IS_ERR(backend->ram_clk)) { + dev_err(dev, "Couldn't get the backend RAM clock\n"); + return PTR_ERR(backend->ram_clk); + } + clk_prepare_enable(backend->ram_clk); + + /* Reset the registers */ + for (i = 0x800; i < 0x1000; i += 4) + regmap_write(backend->regs, i, 0); + + /* Disable registers autoloading */ + regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG, + SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS); + + /* Enable the backend */ + regmap_write(backend->regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_DEBE_EN | + SUN4I_BACKEND_MODCTL_START_CTL); + + return 0; +} + +static void sun4i_backend_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_backend *backend = dev_get_drvdata(dev); + + clk_disable_unprepare(backend->ram_clk); + clk_disable_unprepare(backend->mod_clk); + clk_disable_unprepare(backend->bus_clk); +} + +static struct component_ops sun4i_backend_ops = { + .bind = sun4i_backend_bind, + .unbind = sun4i_backend_unbind, +}; + +static int sun4i_backend_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_backend_ops); +} + +static int sun4i_backend_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_backend_ops); + + return 0; +} + +static const struct of_device_id sun4i_backend_of_table[] = { + { .compatible = "allwinner,sun5i-a13-display-backend" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_backend_of_table); + +static struct platform_driver sun4i_backend_platform_driver = { + .probe = sun4i_backend_probe, + .remove = sun4i_backend_remove, + .driver = { + .name = "sun4i-backend", + .of_match_table = sun4i_backend_of_table, + }, +}; +module_platform_driver(sun4i_backend_platform_driver); + +MODULE_AUTHOR("Maxime Ripard maxime.ripard@free-electrons.com"); +MODULE_DESCRIPTION("Allwinner A10 Display Backend Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h new file mode 100644 index 000000000000..60f6a200fa73 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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. + */ + +#ifndef _SUN4I_BACKEND_H_ +#define _SUN4I_BACKEND_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> + +#define SUN4I_BACKEND_MODCTL_REG 0x800 +#define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) +#define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28) +#define SUN4I_BACKEND_MODCTL_OUT_SEL GENMASK(22, 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD (0 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_FE0 (6 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_FE1 (7 << 20) +#define SUN4I_BACKEND_MODCTL_HWC_EN BIT(16) +#define SUN4I_BACKEND_MODCTL_LAY_EN(l) BIT(8 + l) +#define SUN4I_BACKEND_MODCTL_OCSC_EN BIT(5) +#define SUN4I_BACKEND_MODCTL_DFLK_EN BIT(4) +#define SUN4I_BACKEND_MODCTL_DLP_START_CTL BIT(2) +#define SUN4I_BACKEND_MODCTL_START_CTL BIT(1) +#define SUN4I_BACKEND_MODCTL_DEBE_EN BIT(0) + +#define SUN4I_BACKEND_BACKCOLOR_REG 0x804 +#define SUN4I_BACKEND_BACKCOLOR(r, g, b) (((r) << 16) | ((g) << 8) | (b)) + +#define SUN4I_BACKEND_DISSIZE_REG 0x808 +#define SUN4I_BACKEND_DISSIZE(w, h) (((((h) - 1) & 0xffff) << 16) | \ + (((w) - 1) & 0xffff)) + +#define SUN4I_BACKEND_LAYSIZE_REG(l) (0x810 + (0x4 * (l))) +#define SUN4I_BACKEND_LAYSIZE(w, h) (((((h) - 1) & 0x1fff) << 16) | \ + (((w) - 1) & 0x1fff)) + +#define SUN4I_BACKEND_LAYCOOR_REG(l) (0x820 + (0x4 * (l))) +#define SUN4I_BACKEND_LAYCOOR(x, y) ((((u32)(y) & 0xffff) << 16) | \ + ((u32)(x) & 0xffff)) + +#define SUN4I_BACKEND_LAYLINEWIDTH_REG(l) (0x840 + (0x4 * (l))) + +#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_REGBUFFCTL_REG 0x870 +#define SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS BIT(1) +#define SUN4I_BACKEND_REGBUFFCTL_LOADCTL BIT(0) + +#define SUN4I_BACKEND_CKMAX_REG 0x880 +#define SUN4I_BACKEND_CKMIN_REG 0x884 +#define SUN4I_BACKEND_CKCFG_REG 0x888 +#define SUN4I_BACKEND_ATTCTL_REG0(l) (0x890 + (0x4 * (l))) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK BIT(15) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(x) ((x) << 15) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK GENMASK(11, 10) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(x) ((x) << 10) + +#define SUN4I_BACKEND_ATTCTL_REG1(l) (0x8a0 + (0x4 * (l))) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_HSCAFCT GENMASK(15, 14) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_WSCAFCT GENMASK(13, 12) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT GENMASK(11, 8) +#define SUN4I_BACKEND_LAY_FBFMT_1BPP (0 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_2BPP (1 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_4BPP (2 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_8BPP (3 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB655 (4 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB565 (5 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB556 (6 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB1555 (7 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGBA5551 (8 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_XRGB8888 (9 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB8888 (10 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB888 (11 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB4444 (12 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGBA4444 (13 << 8) + +#define SUN4I_BACKEND_DLCDPCTL_REG 0x8b0 +#define SUN4I_BACKEND_DLCDPFRMBUF_ADDRCTL_REG 0x8b4 +#define SUN4I_BACKEND_DLCDPCOOR_REG0 0x8b8 +#define SUN4I_BACKEND_DLCDPCOOR_REG1 0x8bc + +#define SUN4I_BACKEND_INT_EN_REG 0x8c0 +#define SUN4I_BACKEND_INT_FLAG_REG 0x8c4 +#define SUN4I_BACKEND_REG_LOAD_FINISHED BIT(1) + +#define SUN4I_BACKEND_HWCCTL_REG 0x8d8 +#define SUN4I_BACKEND_HWCFBCTL_REG 0x8e0 +#define SUN4I_BACKEND_WBCTL_REG 0x8f0 +#define SUN4I_BACKEND_WBADD_REG 0x8f4 +#define SUN4I_BACKEND_WBLINEWIDTH_REG 0x8f8 +#define SUN4I_BACKEND_SPREN_REG 0x900 +#define SUN4I_BACKEND_SPRFMTCTL_REG 0x908 +#define SUN4I_BACKEND_SPRALPHACTL_REG 0x90c +#define SUN4I_BACKEND_IYUVCTL_REG 0x920 +#define SUN4I_BACKEND_IYUVADD_REG(c) (0x930 + (0x4 * (c))) +#define SUN4I_BACKEND_IYUVLINEWITDTH_REG(c) (0x940 + (0x4 * (c))) +#define SUN4I_BACKEND_YGCOEF_REG(c) (0x950 + (0x4 * (c))) +#define SUN4I_BACKEND_YGCONS_REG 0x95c +#define SUN4I_BACKEND_URCOEF_REG(c) (0x960 + (0x4 * (c))) +#define SUN4I_BACKEND_URCONS_REG 0x96c +#define SUN4I_BACKEND_VBCOEF_REG(c) (0x970 + (0x4 * (c))) +#define SUN4I_BACKEND_VBCONS_REG 0x97c +#define SUN4I_BACKEND_KSCTL_REG 0x980 +#define SUN4I_BACKEND_KSBKCOLOR_REG 0x984 +#define SUN4I_BACKEND_KSFSTLINEWIDTH_REG 0x988 +#define SUN4I_BACKEND_KSVSCAFCT_REG 0x98c +#define SUN4I_BACKEND_KSHSCACOEF_REG(x) (0x9a0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCCTL_REG 0x9c0 +#define SUN4I_BACKEND_OCCTL_ENABLE BIT(0) + +#define SUN4I_BACKEND_OCRCOEF_REG(x) (0x9d0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCRCONS_REG 0x9dc +#define SUN4I_BACKEND_OCGCOEF_REG(x) (0x9e0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCGCONS_REG 0x9ec +#define SUN4I_BACKEND_OCBCOEF_REG(x) (0x9f0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCBCONS_REG 0x9fc +#define SUN4I_BACKEND_SPRCOORCTL_REG(s) (0xa00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRATTCTL_REG(s) (0xb00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRADD_REG(s) (0xc00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRLINEWIDTH_REG(s) (0xd00 + (0x4 * (s))) + +#define SUN4I_BACKEND_SPRPALTAB_OFF 0x4000 +#define SUN4I_BACKEND_GAMMATAB_OFF 0x4400 +#define SUN4I_BACKEND_HWCPATTERN_OFF 0x4800 +#define SUN4I_BACKEND_HWCCOLORTAB_OFF 0x4c00 +#define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p))) + +struct sun4i_backend { + struct regmap *regs; + + struct clk *bus_clk; + struct clk *mod_clk; + struct clk *ram_clk; +}; + +void sun4i_backend_apply_color_correction(struct sun4i_backend *backend); +void sun4i_backend_commit(struct sun4i_backend *backend); + +void sun4i_backend_layer_enable(struct sun4i_backend *backend, + int layer, bool enable); +int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); +int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); +int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); + +#endif /* _SUN4I_BACKEND_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c new file mode 100644 index 000000000000..82611084ef45 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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 <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_modes.h> + +#include <linux/clk-provider.h> +#include <linux/ioport.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> + +#include <video/videomode.h> + +#include "sun4i_backend.h" +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sun4i_tcon.h" + +static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct drm_pending_vblank_event *event = crtc->state->event; + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct drm_device *dev = crtc->dev; + unsigned long flags; + + if (event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&dev->event_lock, flags); + scrtc->event = event; + spin_unlock_irqrestore(&dev->event_lock, flags); + } +} + +static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct sun4i_drv *drv = scrtc->drv; + + DRM_DEBUG_DRIVER("Committing plane changes\n"); + + sun4i_backend_commit(drv->backend); +} + +static void sun4i_crtc_disable(struct drm_crtc *crtc) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct sun4i_drv *drv = scrtc->drv; + + DRM_DEBUG_DRIVER("Disabling the CRTC\n"); + + sun4i_tcon_disable(drv->tcon); +} + +static void sun4i_crtc_enable(struct drm_crtc *crtc) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct sun4i_drv *drv = scrtc->drv; + + DRM_DEBUG_DRIVER("Enabling the CRTC\n"); + + sun4i_tcon_enable(drv->tcon); +} + +static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { + .atomic_begin = sun4i_crtc_atomic_begin, + .atomic_flush = sun4i_crtc_atomic_flush, + .disable = sun4i_crtc_disable, + .enable = sun4i_crtc_enable, +}; + +static const struct drm_crtc_funcs sun4i_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, +}; + +struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_crtc *scrtc; + int ret; + + scrtc = devm_kzalloc(drm->dev, sizeof(*scrtc), GFP_KERNEL); + if (!scrtc) + return NULL; + scrtc->drv = drv; + + ret = drm_crtc_init_with_planes(drm, &scrtc->crtc, + drv->primary, + NULL, + &sun4i_crtc_funcs); + if (ret) { + dev_err(drm->dev, "Couldn't init DRM CRTC\n"); + return NULL; + } + + drm_crtc_helper_add(&scrtc->crtc, &sun4i_crtc_helper_funcs); + drm_crtc_vblank_reset(&scrtc->crtc); + + return scrtc; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.h b/drivers/gpu/drm/sun4i/sun4i_crtc.h new file mode 100644 index 000000000000..dec8ce4d9b25 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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. + */ + +#ifndef _SUN4I_CRTC_H_ +#define _SUN4I_CRTC_H_ + +struct sun4i_crtc { + struct drm_crtc crtc; + struct drm_pending_vblank_event *event; + + struct sun4i_drv *drv; +}; + +static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct sun4i_crtc, crtc); +} + +struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm); + +#endif /* _SUN4I_CRTC_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c new file mode 100644 index 000000000000..cc7dedc5d256 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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/component.h> +#include <linux/of_graph.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sun4i_framebuffer.h" +#include "sun4i_layer.h" +#include "sun4i_tcon.h" + +static int sun4i_drv_connector_plug_all(struct drm_device *drm) +{ + struct drm_connector *connector, *failed; + int ret; + + mutex_lock(&drm->mode_config.mutex); + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + ret = drm_connector_register(connector); + if (ret) { + failed = connector; + goto err; + } + } + mutex_unlock(&drm->mode_config.mutex); + return 0; + +err: + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + if (failed == connector) + break; + + drm_connector_unregister(connector); + } + mutex_unlock(&drm->mode_config.mutex); + + return ret; +} + +static int sun4i_drv_enable_vblank(struct drm_device *drm, unsigned int pipe) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Enabling VBLANK on pipe %d\n", pipe); + + sun4i_tcon_enable_vblank(tcon, true); + + return 0; +} + +static void sun4i_drv_disable_vblank(struct drm_device *drm, unsigned int pipe) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Disabling VBLANK on pipe %d\n", pipe); + + sun4i_tcon_enable_vblank(tcon, false); +} + +static const struct file_operations sun4i_drv_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct drm_driver sun4i_drv_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, + + /* Generic Operations */ + .fops = &sun4i_drv_fops, + .name = "sun4i-drm", + .desc = "Allwinner sun4i Display Engine", + .date = "20150629", + .major = 1, + .minor = 0, + + /* GEM Operations */ + .dumb_create = drm_gem_cma_dumb_create, + .dumb_destroy = drm_gem_dumb_destroy, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + + /* PRIME Operations */ + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + + /* Frame Buffer Operations */ + + /* VBlank Operations */ + .get_vblank_counter = drm_vblank_count, + .enable_vblank = sun4i_drv_enable_vblank, + .disable_vblank = sun4i_drv_disable_vblank, +}; + +static int sun4i_drv_bind(struct device *dev) +{ + struct drm_device *drm; + struct sun4i_drv *drv; + int ret; + + drm = drm_dev_alloc(&sun4i_drv_driver, dev); + if (!drm) + return -ENOMEM; + + ret = drm_dev_set_unique(drm, dev_name(drm->dev)); + if (ret) + goto free_drm; + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) { + ret = -ENOMEM; + goto free_drm; + } + drm->dev_private = drv; + + drm_vblank_init(drm, 1); + drm_mode_config_init(drm); + + ret = component_bind_all(drm->dev, drm); + if (ret) { + dev_err(drm->dev, "Couldn't bind all pipelines components\n"); + goto free_drm; + } + + /* Create our layers */ + drv->layers = sun4i_layers_init(drm); + if (!drv->layers) { + dev_err(drm->dev, "Couldn't create the planes\n"); + ret = -EINVAL; + goto free_drm; + } + + /* Create our CRTC */ + drv->crtc = sun4i_crtc_init(drm); + if (!drv->crtc) { + dev_err(drm->dev, "Couldn't create the CRTC\n"); + ret = -EINVAL; + goto free_drm; + } + + /* Create our framebuffer */ + drv->fbdev = sun4i_framebuffer_init(drm); + if (IS_ERR(drv->fbdev)) { + dev_err(drm->dev, "Couldn't create our framebuffer\n"); + ret = PTR_ERR(drv->fbdev); + goto free_drm; + } + + /* Enable connectors polling */ + drm_kms_helper_poll_init(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + goto free_drm; + + ret = sun4i_drv_connector_plug_all(drm); + if (ret) + goto unregister_drm; + + return 0; + +unregister_drm: + drm_dev_unregister(drm); +free_drm: + drm_dev_unref(drm); + return ret; +} + +static void sun4i_drv_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_dev_unregister(drm); + drm_kms_helper_poll_fini(drm); + sun4i_framebuffer_free(drm); + drm_vblank_cleanup(drm); + drm_dev_unref(drm); +} + +static const struct component_master_ops sun4i_drv_master_ops = { + .bind = sun4i_drv_bind, + .unbind = sun4i_drv_unbind, +}; + +static int compare_of(struct device *dev, void *data) +{ + DRM_DEBUG_DRIVER("Comparing of node %s with %s\n", + of_node_full_name(dev->of_node), + of_node_full_name(data)); + + return dev->of_node == data; +} + +static int sun4i_drv_add_endpoints(struct device *dev, + struct component_match **match, + struct device_node *node) +{ + struct device_node *port, *ep, *remote; + int count = 0; + + /* Add current component */ + component_match_add(dev, match, compare_of, node); + count++; + + /* Inputs are listed first, then outputs */ + port = of_graph_get_port_by_id(node, 1); + if (!port) { + DRM_DEBUG_DRIVER("No output to bind\n"); + return count; + } + + for_each_child_of_node(port, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + DRM_DEBUG_DRIVER("Error retrieving the output node\n"); + of_node_put(remote); + continue; + } + + if (!of_device_is_available(remote)) { + DRM_DEBUG_DRIVER("Output node %s disabled\n", + remote->full_name); + of_node_put(remote); + continue; + } + + /* Walk down our tree */ + count += sun4i_drv_add_endpoints(dev, match, remote); + + of_node_put(remote); + } + + return count; +} + +static int sun4i_drv_probe(struct platform_device *pdev) +{ + struct component_match *match = NULL; + struct device_node *np = pdev->dev.of_node; + int i, count = 0; + + for (i = 0;; i++) { + struct device_node *pipeline = of_parse_phandle(np, "pipelines", + i); + if (!pipeline) + break; + + if (!of_device_is_available(pipeline)) { + of_node_put(pipeline); + continue; + } + + count += sun4i_drv_add_endpoints(&pdev->dev, &match, + pipeline); + + DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n", + count, i); + } + + if (count) + return component_master_add_with_match(&pdev->dev, + &sun4i_drv_master_ops, + match); + else + return 0; +} + +static int sun4i_drv_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id sun4i_drv_of_table[] = { + { .compatible = "allwinner,sun5i-a13-display-engine" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); + +static struct platform_driver sun4i_drv_platform_driver = { + .probe = sun4i_drv_probe, + .remove = sun4i_drv_remove, + .driver = { + .name = "sun4i-drm", + .of_match_table = sun4i_drv_of_table, + }, +}; +module_platform_driver(sun4i_drv_platform_driver); + +MODULE_AUTHOR("Boris Brezillon boris.brezillon@free-electrons.com"); +MODULE_AUTHOR("Maxime Ripard maxime.ripard@free-electrons.com"); +MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.h b/drivers/gpu/drm/sun4i/sun4i_drv.h new file mode 100644 index 000000000000..597353eab728 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_drv.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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. + */ + +#ifndef _SUN4I_DRV_H_ +#define _SUN4I_DRV_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> + +struct sun4i_drv { + struct sun4i_backend *backend; + struct sun4i_crtc *crtc; + struct sun4i_tcon *tcon; + + struct drm_plane *primary; + struct drm_fbdev_cma *fbdev; + + struct sun4i_layer **layers; +}; + +#endif /* _SUN4I_DRV_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c new file mode 100644 index 000000000000..68072b8cddab --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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 <drm/drm_fb_cma_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drmP.h> + +#include "sun4i_drv.h" + +static void sun4i_de_output_poll_changed(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + + if (drv->fbdev) + drm_fbdev_cma_hotplug_event(drv->fbdev); +} + +static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = { + .output_poll_changed = sun4i_de_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .fb_create = drm_fb_cma_create, +}; + +struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm) +{ + drm_mode_config_reset(drm); + + drm->mode_config.max_width = 8192; + drm->mode_config.max_height = 8192; + + drm->mode_config.funcs = &sun4i_de_mode_config_funcs; + + return drm_fbdev_cma_init(drm, 32, + drm->mode_config.num_crtc, + drm->mode_config.num_connector); +} + +void sun4i_framebuffer_free(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + + drm_fbdev_cma_fini(drv->fbdev); + drm_mode_config_cleanup(drm); +} diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.h b/drivers/gpu/drm/sun4i/sun4i_framebuffer.h new file mode 100644 index 000000000000..3afd65252ee0 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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. + */ + +#ifndef _SUN4I_FRAMEBUFFER_H_ +#define _SUN4I_FRAMEBUFFER_H_ + +struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm); +void sun4i_framebuffer_free(struct drm_device *drm); + +#endif /* _SUN4I_FRAMEBUFFER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c new file mode 100644 index 000000000000..2af710003b30 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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 <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_plane_helper.h> +#include <drm/drmP.h> + +#include "sun4i_backend.h" +#include "sun4i_drv.h" +#include "sun4i_layer.h" + +#define SUN4I_NUM_LAYERS 2 + +static int sun4i_backend_layer_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + return 0; +} + +static void sun4i_backend_layer_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun4i_layer *layer = plane_to_sun4i_layer(plane); + struct sun4i_drv *drv = layer->drv; + struct sun4i_backend *backend = drv->backend; + + sun4i_backend_layer_enable(backend, layer->id, false); +} + +static void sun4i_backend_layer_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun4i_layer *layer = plane_to_sun4i_layer(plane); + struct sun4i_drv *drv = layer->drv; + struct sun4i_backend *backend = drv->backend; + + sun4i_backend_update_layer_coord(backend, layer->id, plane); + sun4i_backend_update_layer_formats(backend, layer->id, plane); + sun4i_backend_update_layer_buffer(backend, layer->id, plane); + sun4i_backend_layer_enable(backend, layer->id, true); +} + +static struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = { + .atomic_check = sun4i_backend_layer_atomic_check, + .atomic_disable = sun4i_backend_layer_atomic_disable, + .atomic_update = sun4i_backend_layer_atomic_update, +}; + +static const struct drm_plane_funcs sun4i_backend_layer_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .destroy = drm_plane_cleanup, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +static const uint32_t sun4i_backend_layer_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, +}; + +static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, + enum drm_plane_type type) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_layer *layer; + int ret; + + layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); + if (!layer) + return ERR_PTR(-ENOMEM); + + 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); + if (ret) { + dev_err(drm->dev, "Couldn't initialize layer\n"); + return ERR_PTR(ret); + } + + drm_plane_helper_add(&layer->plane, + &sun4i_backend_layer_helper_funcs); + layer->drv = drv; + + if (type == DRM_PLANE_TYPE_PRIMARY) + drv->primary = &layer->plane; + + return layer; +} + +struct sun4i_layer **sun4i_layers_init(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_layer **layers; + int i; + + layers = devm_kcalloc(drm->dev, SUN4I_NUM_LAYERS, sizeof(**layers), + GFP_KERNEL); + if (!layers) + return ERR_PTR(-ENOMEM); + + /* + * The hardware is a bit unusual here. + * + * Even though it supports 4 layers, it does the composition + * in two separate steps. + * + * The first one is assigning a layer to one of its two + * pipes. If more that 1 layer is assigned to the same pipe, + * and if pixels overlaps, the pipe will take the pixel from + * the layer with the highest priority. + * + * The second step is the actual alpha blending, that takes + * the two pipes as input, and uses the eventual alpha + * component to do the transparency between the two. + * + * This two steps scenario makes us unable to guarantee a + * robust alpha blending between the 4 layers in all + * situations. So we just expose two layers, one per pipe. On + * 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; + struct sun4i_layer *layer = layers[i]; + + layer = sun4i_layer_init_one(drm, type); + if (IS_ERR(layer)) { + dev_err(drm->dev, "Couldn't initialize %s plane\n", + i ? "overlay" : "primary"); + return ERR_CAST(layer); + }; + + DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n", + i ? "overlay" : "primary", i); + 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)); + + layer->id = i; + }; + + return layers; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.h b/drivers/gpu/drm/sun4i/sun4i_layer.h new file mode 100644 index 000000000000..a2f65d7a3f4e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_layer.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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. + */ + +#ifndef _SUN4I_LAYER_H_ +#define _SUN4I_LAYER_H_ + +struct sun4i_layer { + struct drm_plane plane; + struct sun4i_drv *drv; + int id; +}; + +static inline struct sun4i_layer * +plane_to_sun4i_layer(struct drm_plane *plane) +{ + return container_of(plane, struct sun4i_layer, plane); +} + +struct sun4i_layer **sun4i_layers_init(struct drm_device *drm); + +#endif /* _SUN4I_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c new file mode 100644 index 000000000000..e253818b711b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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 <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_modes.h> + +#include <linux/clk-provider.h> +#include <linux/component.h> +#include <linux/ioport.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> + +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sun4i_tcon.h" + +void sun4i_tcon_disable(struct sun4i_tcon *tcon) +{ + DRM_DEBUG_DRIVER("Disabling TCON\n"); + + /* Disable the TCON */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_TCON_ENABLE, 0); +} + +void sun4i_tcon_enable(struct sun4i_tcon *tcon) +{ + DRM_DEBUG_DRIVER("Enabling TCON\n"); + + /* Enable the TCON */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_TCON_ENABLE, + SUN4I_TCON_GCTL_TCON_ENABLE); +} + +void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) +{ + /* Disable the TCON's channel */ + if (channel == 0) { + 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); + } +} + +void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) +{ + /* Enable the TCON's channel */ + if (channel == 0) { + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + 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); + } +} + +void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) +{ + u32 mask, val = 0; + + DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis"); + + mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) | + SUN4I_TCON_GINT0_VBLANK_ENABLE(1); + + if (enable) + val = mask; + + regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, mask, val); +} + +static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, + int channel) +{ + int delay = mode->vtotal - mode->vdisplay; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + delay /= 2; + + if (channel == 1) + delay -= 2; + + delay = min(delay, 30); + + DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay); + + return delay; +} + +void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, + struct drm_display_mode *mode) +{ + unsigned int bp, hsync, vsync; + u8 clk_delay; + u32 val; + + /* Adjust clock delay */ + clk_delay = sun4i_tcon_get_clk_delay(mode, 1); + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_CLK_DELAY_MASK, + SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); + + /* Set the resolution */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, + SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | + SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); + + /* Set horizontal display timings */ + bp = mode->crtc_htotal - mode->crtc_hsync_end; + DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", + mode->crtc_htotal, bp); + regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, + SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) | + SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); + + /* Set vertical display timings */ + bp = mode->crtc_vtotal - mode->crtc_vsync_end; + DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", + mode->crtc_vtotal, bp); + regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, + SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) | + SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); + + /* Set Hsync and Vsync length */ + hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); + regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG, + SUN4I_TCON0_BASIC3_V_SYNC(vsync) | + SUN4I_TCON0_BASIC3_H_SYNC(hsync)); + + /* TODO: Fix pixel clock phase shift */ + val = SUN4I_TCON0_IO_POL_DCLK_PHASE(1); + + /* Setup the polarity of the various signals */ + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; + + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; + + regmap_write(tcon->regs, SUN4I_TCON0_IO_POL_REG, val); + + /* Map output pins to channel 0 */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_IOMAP_MASK, + SUN4I_TCON_GCTL_IOMAP_TCON0); + + /* Enable the output on the pins */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); +} + +void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, + struct drm_display_mode *mode) +{ + unsigned int bp, hsync, vsync; + u8 clk_delay; + u32 val; + + /* Adjust clock delay */ + clk_delay = sun4i_tcon_get_clk_delay(mode, 1); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_CLK_DELAY_MASK, + SUN4I_TCON1_CTL_CLK_DELAY(clk_delay)); + + /* Set interlaced mode */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + val = SUN4I_TCON1_CTL_INTERLACE_ENABLE; + else + val = 0; + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_INTERLACE_ENABLE, + val); + + /* Set the input resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG, + SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay)); + + /* Set the upscaling resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG, + SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay)); + + /* Set the output resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG, + SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); + + /* Set horizontal display timings */ + bp = mode->crtc_htotal - mode->crtc_hsync_end; + DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", + mode->htotal, bp); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, + SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | + SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); + + /* Set vertical display timings */ + bp = mode->crtc_vtotal - mode->crtc_vsync_end; + DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", + mode->vtotal, bp); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, + SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) | + SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); + + /* Set Hsync and Vsync length */ + hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG, + SUN4I_TCON1_BASIC5_V_SYNC(vsync) | + SUN4I_TCON1_BASIC5_H_SYNC(hsync)); + + /* Map output pins to channel 1 */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_IOMAP_MASK, + SUN4I_TCON_GCTL_IOMAP_TCON1); +} + +static void sun4i_tcon_finish_page_flip(struct drm_device *dev, + struct sun4i_crtc *scrtc) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (scrtc->event) { + drm_send_vblank_event(dev, 0, scrtc->event); + drm_vblank_put(dev, 0); + scrtc->event = NULL; + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static irqreturn_t sun4i_tcon_handler(int irq, void *private) +{ + struct drm_device *drm = private; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon = drv->tcon; + struct sun4i_crtc *scrtc = drv->crtc; + unsigned int status; + + regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); + + if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | + SUN4I_TCON_GINT0_VBLANK_INT(1)))) + return IRQ_NONE; + + drm_handle_vblank(scrtc->crtc.dev, 0); + sun4i_tcon_finish_page_flip(drm, scrtc); + + /* Acknowledge the interrupt */ + regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, + status); + + return IRQ_HANDLED; +} + +static int sun4i_tcon_create_pixel_clock(struct device *dev, + struct sun4i_tcon *tcon) +{ + const char *pixel_clk_name; + const char *sclk_name; + struct clk_divider *div; + struct clk_gate *gate; + + sclk_name = __clk_get_name(tcon->sclk0); + of_property_read_string_index(dev->of_node, "clock-output-names", 0, + &pixel_clk_name); + + div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL); + if (!div) + return -ENOMEM; + + div->regmap = tcon->regs; + div->offset = SUN4I_TCON0_DCLK_REG; + div->shift = SUN4I_TCON0_DCLK_DIV_SHIFT; + div->width = SUN4I_TCON0_DCLK_DIV_WIDTH; + div->flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO; + + gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); + if (!gate) + return -ENOMEM; + + gate->regmap = tcon->regs; + gate->offset = SUN4I_TCON0_DCLK_REG; + gate->bit_idx = SUN4I_TCON0_DCLK_GATE_BIT; + + tcon->dclk = clk_register_composite(dev, pixel_clk_name, + &sclk_name, 1, + NULL, NULL, + &div->hw, &clk_divider_ops, + &gate->hw, &clk_gate_ops, + CLK_USE_REGMAP); + if (IS_ERR(tcon->dclk)) + return PTR_ERR(tcon->dclk); + + return 0; +} + +static int sun4i_tcon_init_clocks(struct device *dev, + struct sun4i_tcon *tcon) +{ + tcon->clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(tcon->clk)) { + dev_err(dev, "Couldn't get the TCON bus clock\n"); + return PTR_ERR(tcon->clk); + } + clk_prepare_enable(tcon->clk); + + tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); + if (IS_ERR(tcon->sclk0)) { + dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); + 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); + } + + return sun4i_tcon_create_pixel_clock(dev, tcon); +} + +static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon) +{ + clk_unregister_composite(tcon->dclk); + clk_disable_unprepare(tcon->clk); +} + +static int sun4i_tcon_init_irq(struct device *dev, + struct sun4i_tcon *tcon) +{ + struct platform_device *pdev = to_platform_device(dev); + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); + return irq; + } + + ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, + dev_name(dev), tcon); + if (ret) { + dev_err(dev, "Couldn't request the IRQ\n"); + return ret; + } + + return 0; +} + +static struct regmap_config sun4i_tcon_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x800, +}; + +static int sun4i_tcon_init_regmap(struct device *dev, + struct sun4i_tcon *tcon) +{ + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + void __iomem *regs; + + 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"); + return PTR_ERR(regs); + } + + tcon->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_tcon_regmap_config); + if (IS_ERR(tcon->regs)) { + dev_err(dev, "Couldn't create the TCON regmap\n"); + return PTR_ERR(tcon->regs); + } + + /* Make sure the TCON is disabled and all IRQs are off */ + regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0); + regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); + regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0); + + /* Disable IO lines and set them to tristate */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0); + regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0); + + return 0; +} + +static int sun4i_tcon_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon; + int ret; + + tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); + if (!tcon) + return -ENOMEM; + dev_set_drvdata(dev, tcon); + drv->tcon = tcon; + + ret = sun4i_tcon_init_regmap(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't init our TCON regmap\n"); + return ret; + } + + ret = sun4i_tcon_init_clocks(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't init our TCON clocks\n"); + return ret; + } + + ret = sun4i_tcon_init_irq(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't init our TCON interrupts\n"); + goto err_free_clocks; + } + + return 0; + +err_free_clocks: + sun4i_tcon_free_clocks(tcon); + return ret; +} + +static void sun4i_tcon_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_tcon *tcon = dev_get_drvdata(dev); + + sun4i_tcon_free_clocks(tcon); +} + +static struct component_ops sun4i_tcon_ops = { + .bind = sun4i_tcon_bind, + .unbind = sun4i_tcon_unbind, +}; + +static int sun4i_tcon_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_tcon_ops); +} + +static int sun4i_tcon_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_tcon_ops); + + return 0; +} + +static const struct of_device_id sun4i_tcon_of_table[] = { + { .compatible = "allwinner,sun4i-a10-tcon" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); + +static struct platform_driver sun4i_tcon_platform_driver = { + .probe = sun4i_tcon_probe, + .remove = sun4i_tcon_remove, + .driver = { + .name = "sun4i-tcon", + .of_match_table = sun4i_tcon_of_table, + }, +}; +module_platform_driver(sun4i_tcon_platform_driver); + +MODULE_AUTHOR("Maxime Ripard maxime.ripard@free-electrons.com"); +MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h new file mode 100644 index 000000000000..6fa093bce974 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Boris Brezillon boris.brezillon@free-electrons.com + * 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. + */ + +#ifndef __SUN4I_TCON_H__ +#define __SUN4I_TCON_H__ + +#include <drm/drm_crtc.h> + +#include <linux/kernel.h> + +#define SUN4I_TCON_GCTL_REG 0x0 +#define SUN4I_TCON_GCTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON_GCTL_IOMAP_MASK BIT(0) +#define SUN4I_TCON_GCTL_IOMAP_TCON1 (1 << 0) +#define SUN4I_TCON_GCTL_IOMAP_TCON0 (0 << 0) + +#define SUN4I_TCON_GINT0_REG 0x4 +#define SUN4I_TCON_GINT0_VBLANK_ENABLE(pipe) BIT(31 - (pipe)) +#define SUN4I_TCON_GINT0_VBLANK_INT(pipe) BIT(15 - (pipe)) + +#define SUN4I_TCON_GINT1_REG 0x8 +#define SUN4I_TCON_FRM_CTL_REG 0x10 + +#define SUN4I_TCON0_CTL_REG 0x40 +#define SUN4I_TCON0_CTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON0_CTL_CLK_DELAY_MASK GENMASK(8, 4) +#define SUN4I_TCON0_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON0_CTL_CLK_DELAY_MASK) + +#define SUN4I_TCON0_DCLK_REG 0x44 +#define SUN4I_TCON0_DCLK_GATE_BIT (31) +#define SUN4I_TCON0_DCLK_DIV_SHIFT (0) +#define SUN4I_TCON0_DCLK_DIV_WIDTH (7) + +#define SUN4I_TCON0_BASIC0_REG 0x48 +#define SUN4I_TCON0_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON0_BASIC0_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC1_REG 0x4c +#define SUN4I_TCON0_BASIC1_H_TOTAL(total) ((((total) - 1) & 0x1fff) << 16) +#define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC2_REG 0x50 +#define SUN4I_TCON0_BASIC2_V_TOTAL(total) ((((total) * 2) & 0x1fff) << 16) +#define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC3_REG 0x54 +#define SUN4I_TCON0_BASIC3_H_SYNC(width) ((((width) - 1) & 0x7ff) << 16) +#define SUN4I_TCON0_BASIC3_V_SYNC(height) (((height) - 1) & 0x7ff) + +#define SUN4I_TCON0_HV_IF_REG 0x58 +#define SUN4I_TCON0_CPU_IF_REG 0x60 +#define SUN4I_TCON0_CPU_WR_REG 0x64 +#define SUN4I_TCON0_CPU_RD0_REG 0x68 +#define SUN4I_TCON0_CPU_RDA_REG 0x6c +#define SUN4I_TCON0_TTL0_REG 0x70 +#define SUN4I_TCON0_TTL1_REG 0x74 +#define SUN4I_TCON0_TTL2_REG 0x78 +#define SUN4I_TCON0_TTL3_REG 0x7c +#define SUN4I_TCON0_TTL4_REG 0x80 +#define SUN4I_TCON0_LVDS_IF_REG 0x84 +#define SUN4I_TCON0_IO_POL_REG 0x88 +#define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase) ((phase & 3) << 28) +#define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE BIT(25) +#define SUN4I_TCON0_IO_POL_VSYNC_POSITIVE BIT(24) + +#define SUN4I_TCON0_IO_TRI_REG 0x8c +#define SUN4I_TCON0_IO_TRI_HSYNC_DISABLE BIT(25) +#define SUN4I_TCON0_IO_TRI_VSYNC_DISABLE BIT(24) +#define SUN4I_TCON0_IO_TRI_DATA_PINS_DISABLE(pins) GENMASK(pins, 0) + +#define SUN4I_TCON1_CTL_REG 0x90 +#define SUN4I_TCON1_CTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON1_CTL_INTERLACE_ENABLE BIT(20) +#define SUN4I_TCON1_CTL_CLK_DELAY_MASK GENMASK(8, 4) +#define SUN4I_TCON1_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON1_CTL_CLK_DELAY_MASK) + +#define SUN4I_TCON1_BASIC0_REG 0x94 +#define SUN4I_TCON1_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC0_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC1_REG 0x98 +#define SUN4I_TCON1_BASIC1_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC1_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC2_REG 0x9c +#define SUN4I_TCON1_BASIC2_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC2_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC3_REG 0xa0 +#define SUN4I_TCON1_BASIC3_H_TOTAL(total) ((((total) - 1) & 0x1fff) << 16) +#define SUN4I_TCON1_BASIC3_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC4_REG 0xa4 +#define SUN4I_TCON1_BASIC4_V_TOTAL(total) (((total) & 0x1fff) << 16) +#define SUN4I_TCON1_BASIC4_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC5_REG 0xa8 +#define SUN4I_TCON1_BASIC5_H_SYNC(width) ((((width) - 1) & 0x3ff) << 16) +#define SUN4I_TCON1_BASIC5_V_SYNC(height) (((height) - 1) & 0x3ff) + +#define SUN4I_TCON1_IO_POL_REG 0xf0 +#define SUN4I_TCON1_IO_TRI_REG 0xf4 +#define SUN4I_TCON_CEU_CTL_REG 0x100 +#define SUN4I_TCON_CEU_MUL_RR_REG 0x110 +#define SUN4I_TCON_CEU_MUL_RG_REG 0x114 +#define SUN4I_TCON_CEU_MUL_RB_REG 0x118 +#define SUN4I_TCON_CEU_ADD_RC_REG 0x11c +#define SUN4I_TCON_CEU_MUL_GR_REG 0x120 +#define SUN4I_TCON_CEU_MUL_GG_REG 0x124 +#define SUN4I_TCON_CEU_MUL_GB_REG 0x128 +#define SUN4I_TCON_CEU_ADD_GC_REG 0x12c +#define SUN4I_TCON_CEU_MUL_BR_REG 0x130 +#define SUN4I_TCON_CEU_MUL_BG_REG 0x134 +#define SUN4I_TCON_CEU_MUL_BB_REG 0x138 +#define SUN4I_TCON_CEU_ADD_BC_REG 0x13c +#define SUN4I_TCON_CEU_RANGE_R_REG 0x140 +#define SUN4I_TCON_CEU_RANGE_G_REG 0x144 +#define SUN4I_TCON_CEU_RANGE_B_REG 0x148 +#define SUN4I_TCON1_FILL_CTL_REG 0x300 +#define SUN4I_TCON1_FILL_BEG0_REG 0x304 +#define SUN4I_TCON1_FILL_END0_REG 0x308 +#define SUN4I_TCON1_FILL_DATA0_REG 0x30c +#define SUN4I_TCON1_FILL_BEG1_REG 0x310 +#define SUN4I_TCON1_FILL_END1_REG 0x314 +#define SUN4I_TCON1_FILL_DATA1_REG 0x318 +#define SUN4I_TCON1_FILL_BEG2_REG 0x31c +#define SUN4I_TCON1_FILL_END2_REG 0x320 +#define SUN4I_TCON1_FILL_DATA2_REG 0x324 +#define SUN4I_TCON1_GAMMA_TABLE_REG 0x400 + +#define SUN4I_TCON_MAX_CHANNELS 2 + +struct sun4i_tcon { + struct regmap *regs; + + /* Main bus clock */ + struct clk *clk; + + /* Clocks for the TCON channels */ + struct clk *sclk0; + struct clk *sclk1; + + /* Pixel clock */ + struct clk *dclk; +}; + +/* Global Control */ +void sun4i_tcon_disable(struct sun4i_tcon *tcon); +void sun4i_tcon_enable(struct sun4i_tcon *tcon); + +/* Channel Control */ +void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel); +void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel); + +void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable); + +/* Mode Related Controls */ +void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon, + bool enable); +void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, + struct drm_display_mode *mode); +void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, + struct drm_display_mode *mode); + +#endif /* __SUN4I_TCON_H__ */
On Thu, 2016-01-14 at 16:24 +0100, Maxime Ripard wrote:
The Allwinner A10 and subsequent SoCs share the same display pipeline, with variations in the number of controllers (1 or 2), or the presence or not of some output (HDMI, TV, VGA) or not. Add a driver with a limited set of features for now, and we will hopefully support all of them eventually
[..]
diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h new file mode 100644 index 000000000000..60f6a200fa73 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -0,0 +1,160 @@ +/*
- Copyright (C) 2015 Free Electrons
- Copyright (C) 2015 NextThing Co
- 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.
- */
+#ifndef _SUN4I_BACKEND_H_ +#define _SUN4I_BACKEND_H_
+#include <linux/clk.h> +#include <linux/regmap.h>
+#define SUN4I_BACKEND_MODCTL_REG 0x800 +#define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) +#define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28 ) +#define SUN4I_BACKEND_MODCTL_OUT_SEL GENMASK( 22, 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD (0 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_FE0 (6 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_FE1 (7 << 20) +#define SUN4I_BACKEND_MODCTL_HWC_EN BIT(16) +#define SUN4I_BACKEND_MODCTL_LAY_EN(l) BIT(8
- l)
+#define SUN4I_BACKEND_MODCTL_OCSC_EN BIT(5) +#define SUN4I_BACKEND_MODCTL_DFLK_EN BIT(4) +#define SUN4I_BACKEND_MODCTL_DLP_START_CTL BIT(2) +#define SUN4I_BACKEND_MODCTL_START_CTL BIT(1) +#define SUN4I_BACKEND_MODCTL_DEBE_EN BIT(0)
+#define SUN4I_BACKEND_BACKCOLOR_REG 0x804 +#define SUN4I_BACKEND_BACKCOLOR(r, g, b) (((r) << 16) | ((g) << 8) | (b))
+#define SUN4I_BACKEND_DISSIZE_REG 0x808 +#define SUN4I_BACKEND_DISSIZE(w, h) (((((h) -
- & 0xffff) << 16) | \
(((w) - 1)
& 0xffff))
+#define SUN4I_BACKEND_LAYSIZE_REG(l) (0x810 + (0x4 * (l))) +#define SUN4I_BACKEND_LAYSIZE(w, h) (((((h) -
- & 0x1fff) << 16) | \
(((w) - 1)
& 0x1fff))
+#define SUN4I_BACKEND_LAYCOOR_REG(l) (0x820 + (0x4 * (l))) +#define SUN4I_BACKEND_LAYCOOR(x, y) ((((u32)( y) & 0xffff) << 16) | \
((u32)(x) &
0xffff))
+#define SUN4I_BACKEND_LAYLINEWIDTH_REG(l) (0x840 + (0x4 * (l)))
+#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) ((v al) << ((l) * 8))
+#define SUN4I_BACKEND_REGBUFFCTL_REG 0x870 +#define SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS BIT(1) +#define SUN4I_BACKEND_REGBUFFCTL_LOADCTL BIT(0)
+#define SUN4I_BACKEND_CKMAX_REG 0x880 +#define SUN4I_BACKEND_CKMIN_REG 0x884 +#define SUN4I_BACKEND_CKCFG_REG 0x888 +#define SUN4I_BACKEND_ATTCTL_REG0(l) (0x890 + (0x4 * (l))) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK BIT(15) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(x) ((x) << 15) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK GENMASK(11, 10) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(x) ((x) << 10)
+#define SUN4I_BACKEND_ATTCTL_REG1(l) (0x8a0 + (0x4 * (l))) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_HSCAFCT GENMASK (15, 14) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_WSCAFCT GENMASK (13, 12) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT GENMASK(1 1, 8) +#define SUN4I_BACKEND_LAY_FBFMT_1BPP (0 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_2BPP (1 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_4BPP (2 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_8BPP (3 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB655 (4 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB565 (5 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB556 (6 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB1555 (7 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGBA5551 (8 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_XRGB8888 (9 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB8888 (10 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB888 (11 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB4444 (12 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGBA4444 (13 << 8)
+#define SUN4I_BACKEND_DLCDPCTL_REG 0x8b0 +#define SUN4I_BACKEND_DLCDPFRMBUF_ADDRCTL_REG 0x8b4 +#define SUN4I_BACKEND_DLCDPCOOR_REG0 0x8b8 +#define SUN4I_BACKEND_DLCDPCOOR_REG1 0x8bc
+#define SUN4I_BACKEND_INT_EN_REG 0x8c0 +#define SUN4I_BACKEND_INT_FLAG_REG 0x8c4 +#define SUN4I_BACKEND_REG_LOAD_FINISHED BIT(1 )
+#define SUN4I_BACKEND_HWCCTL_REG 0x8d8 +#define SUN4I_BACKEND_HWCFBCTL_REG 0x8e0 +#define SUN4I_BACKEND_WBCTL_REG 0x8f0 +#define SUN4I_BACKEND_WBADD_REG 0x8f4 +#define SUN4I_BACKEND_WBLINEWIDTH_REG 0x8f8 +#define SUN4I_BACKEND_SPREN_REG 0x900 +#define SUN4I_BACKEND_SPRFMTCTL_REG 0x908 +#define SUN4I_BACKEND_SPRALPHACTL_REG 0x90c +#define SUN4I_BACKEND_IYUVCTL_REG 0x920 +#define SUN4I_BACKEND_IYUVADD_REG(c) (0x930 + (0x4 * (c))) +#define SUN4I_BACKEND_IYUVLINEWITDTH_REG(c) (0x940 + (0x4 * (c)))
WITDTH ?
This isn't used anyway...
+#define SUN4I_BACKEND_YGCOEF_REG(c) (0x950 + (0x4 * (c))) +#define SUN4I_BACKEND_YGCONS_REG 0x95c +#define SUN4I_BACKEND_URCOEF_REG(c) (0x960 + (0x4 * (c))) +#define SUN4I_BACKEND_URCONS_REG 0x96c +#define SUN4I_BACKEND_VBCOEF_REG(c) (0x970 + (0x4 * (c))) +#define SUN4I_BACKEND_VBCONS_REG 0x97c +#define SUN4I_BACKEND_KSCTL_REG 0x980 +#define SUN4I_BACKEND_KSBKCOLOR_REG 0x984 +#define SUN4I_BACKEND_KSFSTLINEWIDTH_REG 0x988 +#define SUN4I_BACKEND_KSVSCAFCT_REG 0x98c +#define SUN4I_BACKEND_KSHSCACOEF_REG(x) (0x9a0 + (0x4
- (x)))
+#define SUN4I_BACKEND_OCCTL_REG 0x9c0 +#define SUN4I_BACKEND_OCCTL_ENABLE BIT(0)
+#define SUN4I_BACKEND_OCRCOEF_REG(x) (0x9d0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCRCONS_REG 0x9dc +#define SUN4I_BACKEND_OCGCOEF_REG(x) (0x9e0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCGCONS_REG 0x9ec +#define SUN4I_BACKEND_OCBCOEF_REG(x) (0x9f0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCBCONS_REG 0x9fc +#define SUN4I_BACKEND_SPRCOORCTL_REG(s) (0xa00 + (0x4
- (s)))
+#define SUN4I_BACKEND_SPRATTCTL_REG(s) (0xb00 + (0x4
- (s)))
+#define SUN4I_BACKEND_SPRADD_REG(s) (0xc00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRLINEWIDTH_REG(s) (0xd00 + (0x4 * (s)))
+#define SUN4I_BACKEND_SPRPALTAB_OFF 0x4000 +#define SUN4I_BACKEND_GAMMATAB_OFF 0x4400 +#define SUN4I_BACKEND_HWCPATTERN_OFF 0x4800 +#define SUN4I_BACKEND_HWCCOLORTAB_OFF 0x4c00 +#define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p)))
+struct sun4i_backend {
- struct regmap *regs;
- struct clk *bus_clk;
- struct clk *mod_clk;
- struct clk *ram_clk;
+};
+void sun4i_backend_apply_color_correction(struct sun4i_backend *backend); +void sun4i_backend_commit(struct sun4i_backend *backend);
+void sun4i_backend_layer_enable(struct sun4i_backend *backend,
int layer, bool enable);
+int sun4i_backend_update_layer_coord(struct sun4i_backend *backend,
int layer, struct drm_plane
*plane); +int sun4i_backend_update_layer_formats(struct sun4i_backend *backend,
int layer, struct drm_plane
*plane); +int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend,
int layer, struct drm_plane
*plane);
+#endif /* _SUN4I_BACKEND_H_ */
[...]
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c new file mode 100644 index 000000000000..cc7dedc5d256 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -0,0 +1,325 @@ +/*
- Copyright (C) 2015 Free Electrons
- Copyright (C) 2015 NextThing Co
- 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/component.h> +#include <linux/of_graph.h>
+#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h>
+#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sun4i_framebuffer.h" +#include "sun4i_layer.h" +#include "sun4i_tcon.h"
[...]
+static struct drm_driver sun4i_drv_driver = {
- .driver_features = DRIVER_GEM | DRIVER_MODESET |
DRIVER_PRIME | DRIVER_ATOMIC,
- /* Generic Operations */
- .fops = &sun4i_drv_fops,
- .name = "sun4i-drm",
- .desc = "Allwinner sun4i Display
Engine",
- .date = "20150629",
2016something ?
- .major = 1,
- .minor = 0,
- /* GEM Operations */
- .dumb_create = drm_gem_cma_dumb_create,
- .dumb_destroy = drm_gem_dumb_destroy,
- .dumb_map_offset = drm_gem_cma_dumb_map_offset,
- .gem_free_object = drm_gem_cma_free_object,
- .gem_vm_ops = &drm_gem_cma_vm_ops,
- /* PRIME Operations */
- .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
- .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
- .gem_prime_import = drm_gem_prime_import,
- .gem_prime_export = drm_gem_prime_export,
- .gem_prime_get_sg_table =
drm_gem_cma_prime_get_sg_table,
- .gem_prime_import_sg_table =
drm_gem_cma_prime_import_sg_table,
- .gem_prime_vmap = drm_gem_cma_prime_vmap,
- .gem_prime_vunmap = drm_gem_cma_prime_vunmap,
- .gem_prime_mmap = drm_gem_cma_prime_mmap,
- /* Frame Buffer Operations */
- /* VBlank Operations */
- .get_vblank_counter = drm_vblank_count,
- .enable_vblank = sun4i_drv_enable_vblank,
- .disable_vblank = sun4i_drv_disable_vblank,
+};
[...]
diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c new file mode 100644 index 000000000000..68072b8cddab --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -0,0 +1,54 @@ +/*
- Copyright (C) 2015 Free Electrons
- Copyright (C) 2015 NextThing Co
- 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 <drm/drm_fb_cma_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drmP.h>
+#include "sun4i_drv.h"
+static void sun4i_de_output_poll_changed(struct drm_device *drm) +{
- struct sun4i_drv *drv = drm->dev_private;
- if (drv->fbdev)
drm_fbdev_cma_hotplug_event(drv->fbdev);
+}
+static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = {
- .output_poll_changed = sun4i_de_output_poll_changed,
- .atomic_check = drm_atomic_helper_check,
- .atomic_commit = drm_atomic_helper_commit,
- .fb_create = drm_fb_cma_create,
+};
+struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm) +{
- drm_mode_config_reset(drm);
- drm->mode_config.max_width = 8192;
- drm->mode_config.max_height = 8192;
Shouldn't these be 1920 x 1080 as per A10 User manual?
- drm->mode_config.funcs = &sun4i_de_mode_config_funcs;
- return drm_fbdev_cma_init(drm, 32,
drm->mode_config.num_crtc,
drm->mode_config.num_connector);
+}
+void sun4i_framebuffer_free(struct drm_device *drm) +{
- struct sun4i_drv *drv = drm->dev_private;
- drm_fbdev_cma_fini(drv->fbdev);
- drm_mode_config_cleanup(drm);
+}
[...]
Päikest, Priit Laes :)
On Sat, 2016-01-16 at 17:11 +0200, Priit Laes wrote:
On Thu, 2016-01-14 at 16:24 +0100, Maxime Ripard wrote:
The Allwinner A10 and subsequent SoCs share the same display pipeline, with variations in the number of controllers (1 or 2), or the presence or not of some output (HDMI, TV, VGA) or not.
[...]
diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c new file mode 100644 index 000000000000..68072b8cddab --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -0,0 +1,54 @@ +/*
- Copyright (C) 2015 Free Electrons
- Copyright (C) 2015 NextThing Co
- 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 <drm/drm_fb_cma_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drmP.h>
+#include "sun4i_drv.h"
+static void sun4i_de_output_poll_changed(struct drm_device *drm) +{
- struct sun4i_drv *drv = drm->dev_private;
- if (drv->fbdev)
drm_fbdev_cma_hotplug_event(drv->fbdev);
+}
+static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = {
- .output_poll_changed =
sun4i_de_output_poll_changed,
- .atomic_check = drm_atomic_helper_check,
- .atomic_commit = drm_atomic_helper_commit,
- .fb_create = drm_fb_cma_create,
+};
+struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm) +{
- drm_mode_config_reset(drm);
- drm->mode_config.max_width = 8192;
- drm->mode_config.max_height = 8192;
Shouldn't these be 1920 x 1080 as per A10 User manual?
I was wrong here. 8192x8192 it is.
Päikest, Priit Laes :)
Hi,
On Sat, Jan 16, 2016 at 05:11:44PM +0200, Priit Laes wrote:
+struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm) +{
- drm_mode_config_reset(drm);
- drm->mode_config.max_width = 8192;
- drm->mode_config.max_height = 8192;
Shouldn't these be 1920 x 1080 as per A10 User manual?
1080p is what the SoC can output. However, to create its output, it uses a combination of planes of 8192x8192, that you'll obviously have to crop. This is the size of the plane that we are exposing here, not what we can output (which is listed in the various connectors).
Maxime
The display pipeline of the Allwinner A10 is involving several loosely coupled components.
Add a documentation for the bindings.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- .../bindings/display/sunxi/sun4i-drm.txt | 228 +++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt new file mode 100644 index 000000000000..061bc87147a3 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -0,0 +1,228 @@ +Allwinner A10 Display Pipeline +============================== + +The Allwinner A10 Display pipeline is composed of several components +that are going to be documented below: + +TV Encoder +---------- + +The TV Encoder supports the composite and VGA output. It is one end of +the pipeline. + +Required properties: + - compatible: value should be "allwinner,sun4i-a10-tv-encoder". + - reg: base address and size of memory-mapped region + - clocks: the clocks driving the TV encoder + +- ports: A ports node with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. The + first port should be the input endpoint. + +TCON +---- + +The TCON acts as a timing controller for RGB, LVDS and TV interfaces. + +Required properties: + - compatible: value should be "allwinner,sun4i-a10-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 + + - clock-names: the clock names mentioned above + - clock-output-names: Name of the pixel clock created + +- ports: A ports node with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. The + first port should be the input endpoint, the second one the output + +Optional properties: + - allwinner,panel: phandle to the panel used in our RGB interface + + +Display Engine Backend +---------------------- + +The display engine backend exposes layers and sprites to the +system. + +Required properties: + - compatible: value must be one of: + * allwinner,sun5i-a13-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 + * mod: the backend module clock + * ram: the backend DRAM clock + - clock-names: the clock names mentioned above + - resets: phandles to the reset controllers driving the backend + +- 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 output + +Display Engine Frontend +----------------------- + +The display engine frontend does formats conversion, scaling, +deinterlacing and color space conversion. + +Required properties: + - compatible: value must be one of: + * allwinner,sun5i-a13-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 + * ahb: the backend interface clock + * mod: the backend module clock + * ram: the backend DRAM clock + - clock-names: the clock names mentioned above + - resets: phandles to the reset controllers driving the backend + +Display Engine Pipeline +----------------------- + +The display engine pipeline (and its entry point, since it can be +either directly the backend or the frontend) is represented as an +extra node. + +Required properties: + - compatible: value must be one of: + * allwinner,sun5i-a13-display-engine + - allwinner,pipelines: list of phandle to the entry points of the + pipelines (either to the frontend or backend) + +Example: + +panel: panel { + compatible = "olimex,lcd-olinuxino-43-ts"; +}; + +tve0: tv-encoder@01c0a000 { + compatible = "allwinner,sun4i-a10-tv-encoder"; + reg = <0x01c0a000 0x1000>; + clocks = <&ahb_gates 34>; + + port { + #address-cells = <1>; + #size-cells = <0>; + + tve0_in_tcon0: endpoint@0 { + reg = <0>; + remote-endpoint = <&tcon0_out_tve0>; + }; + }; +}; + +tcon0: lcd-controller@01c0c000 { + compatible = "allwinner,sun4i-a10-tcon"; + reg = <0x01c0c000 0x1000>; + interrupts = <44>; + clocks = <&ahb_gates 36>, + <&tcon_ch0_clk>, + <&tcon_ch1_clk>; + clock-names = "ahb", + "tcon-ch0", + "tcon-ch1"; + clock-output-names = "tcon-pixel-clock"; + allwinner,panel = <&panel>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + tcon0_in: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + tcon0_in_be0: endpoint@0 { + reg = <0>; + remote-endpoint = <&be0_out_tcon0>; + }; + }; + + tcon0_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + tcon0_out_tve0: endpoint@0 { + reg = <0>; + remote-endpoint = <&tve0_in_tcon0>; + }; + }; + }; +}; + +fe0: display-frontend@01e00000 { + compatible = "allwinner,sun5i-a13-display-frontend"; + reg = <0x01e00000 0x20000>; + interrupts = <47>; + clocks = <&ahb_gates 46>, <&de_fe_clk>, + <&dram_gates 25>; + clock-names = "ahb", "mod", + "ram"; + resets = <&de_fe_clk>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + fe0_out: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + fe0_out_be0: endpoint@0 { + reg = <0>; + remote-endpoint = <&be0_in_fe0>; + }; + }; + }; +}; + +be0: display-backend@01e60000 { + compatible = "allwinner,sun5i-a13-display-backend"; + reg = <0x01e60000 0x10000>; + clocks = <&ahb_gates 44>, <&de_be_clk>, + <&dram_gates 26>; + clock-names = "ahb", "mod", + "ram"; + resets = <&de_be_clk>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + be0_in: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + be0_in_fe0: endpoint@0 { + reg = <0>; + remote-endpoint = <&fe0_out_be0>; + }; + }; + + be0_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + be0_out_tcon0: endpoint@0 { + reg = <0>; + remote-endpoint = <&tcon0_in_be0>; + }; + }; +}; + +display-engine { + compatible = "allwinner,sun5i-a13-display-engine"; + pipelines = <&be0>; +};
On Thu, Jan 14, 2016 at 04:25:00PM +0100, Maxime Ripard wrote:
The display pipeline of the Allwinner A10 is involving several loosely coupled components.
Add a documentation for the bindings.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
.../bindings/display/sunxi/sun4i-drm.txt | 228 +++++++++++++++++++++
drm is a linuxism. It shouldn't be used for binding doc.
1 file changed, 228 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt new file mode 100644 index 000000000000..061bc87147a3 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -0,0 +1,228 @@ +Allwinner A10 Display Pipeline +==============================
+The Allwinner A10 Display pipeline is composed of several components +that are going to be documented below:
+TV Encoder +----------
+The TV Encoder supports the composite and VGA output. It is one end of +the pipeline.
+Required properties:
- compatible: value should be "allwinner,sun4i-a10-tv-encoder".
- reg: base address and size of memory-mapped region
- clocks: the clocks driving the TV encoder
+- ports: A ports node with endpoint definitions as defined in
- Documentation/devicetree/bindings/media/video-interfaces.txt. The
- first port should be the input endpoint.
+TCON +----
+The TCON acts as a timing controller for RGB, LVDS and TV interfaces.
+Required properties:
- compatible: value should be "allwinner,sun4i-a10-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
- clock-names: the clock names mentioned above
- clock-output-names: Name of the pixel clock created
+- ports: A ports node with endpoint definitions as defined in
- Documentation/devicetree/bindings/media/video-interfaces.txt. The
- first port should be the input endpoint, the second one the output
+Optional properties:
- allwinner,panel: phandle to the panel used in our RGB interface
+Display Engine Backend +----------------------
+The display engine backend exposes layers and sprites to the +system.
+Required properties:
- compatible: value must be one of:
- allwinner,sun5i-a13-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
- mod: the backend module clock
- ram: the backend DRAM clock
- clock-names: the clock names mentioned above
- resets: phandles to the reset controllers driving the backend
+- 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 output
+Display Engine Frontend +-----------------------
+The display engine frontend does formats conversion, scaling, +deinterlacing and color space conversion.
+Required properties:
- compatible: value must be one of:
- allwinner,sun5i-a13-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
- ahb: the backend interface clock
- mod: the backend module clock
- ram: the backend DRAM clock
should be frontend?
- clock-names: the clock names mentioned above
- resets: phandles to the reset controllers driving the backend
ditto
+Display Engine Pipeline +-----------------------
+The display engine pipeline (and its entry point, since it can be +either directly the backend or the frontend) is represented as an +extra node.
+Required properties:
- compatible: value must be one of:
- allwinner,sun5i-a13-display-engine
- allwinner,pipelines: list of phandle to the entry points of the
- pipelines (either to the frontend or backend)
+Example:
+panel: panel {
- compatible = "olimex,lcd-olinuxino-43-ts";
This should either be a child of what it is attached to or use of-graph.
+};
+tve0: tv-encoder@01c0a000 {
- compatible = "allwinner,sun4i-a10-tv-encoder";
- reg = <0x01c0a000 0x1000>;
- clocks = <&ahb_gates 34>;
- port {
#address-cells = <1>;
#size-cells = <0>;
tve0_in_tcon0: endpoint@0 {
reg = <0>;
I would drop the reg where you only have 1 node for ports or endpoints.
remote-endpoint = <&tcon0_out_tve0>;
};
- };
+};
+tcon0: lcd-controller@01c0c000 {
- compatible = "allwinner,sun4i-a10-tcon";
- reg = <0x01c0c000 0x1000>;
- interrupts = <44>;
- clocks = <&ahb_gates 36>,
<&tcon_ch0_clk>,
<&tcon_ch1_clk>;
- clock-names = "ahb",
"tcon-ch0",
"tcon-ch1";
- clock-output-names = "tcon-pixel-clock";
- allwinner,panel = <&panel>;
- ports {
#address-cells = <1>;
#size-cells = <0>;
tcon0_in: port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
tcon0_in_be0: endpoint@0 {
reg = <0>;
remote-endpoint = <&be0_out_tcon0>;
};
};
tcon0_out: port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
tcon0_out_tve0: endpoint@0 {
reg = <0>;
remote-endpoint = <&tve0_in_tcon0>;
};
};
- };
+};
+fe0: display-frontend@01e00000 {
- compatible = "allwinner,sun5i-a13-display-frontend";
- reg = <0x01e00000 0x20000>;
- interrupts = <47>;
- clocks = <&ahb_gates 46>, <&de_fe_clk>,
<&dram_gates 25>;
- clock-names = "ahb", "mod",
"ram";
- resets = <&de_fe_clk>;
- ports {
#address-cells = <1>;
#size-cells = <0>;
fe0_out: port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
fe0_out_be0: endpoint@0 {
reg = <0>;
remote-endpoint = <&be0_in_fe0>;
};
};
- };
+};
+be0: display-backend@01e60000 {
- compatible = "allwinner,sun5i-a13-display-backend";
- reg = <0x01e60000 0x10000>;
- clocks = <&ahb_gates 44>, <&de_be_clk>,
<&dram_gates 26>;
- clock-names = "ahb", "mod",
"ram";
- resets = <&de_be_clk>;
- ports {
#address-cells = <1>;
#size-cells = <0>;
be0_in: port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
be0_in_fe0: endpoint@0 {
reg = <0>;
remote-endpoint = <&fe0_out_be0>;
};
};
be0_out: port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
be0_out_tcon0: endpoint@0 {
reg = <0>;
remote-endpoint = <&tcon0_in_be0>;
};
- };
+};
+display-engine {
- compatible = "allwinner,sun5i-a13-display-engine";
- pipelines = <&be0>;
+};
2.6.4
-- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Rob,
On Thu, Jan 14, 2016 at 09:15:15PM -0600, Rob Herring wrote:
On Thu, Jan 14, 2016 at 04:25:00PM +0100, Maxime Ripard wrote:
The display pipeline of the Allwinner A10 is involving several loosely coupled components.
Add a documentation for the bindings.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
.../bindings/display/sunxi/sun4i-drm.txt | 228 +++++++++++++++++++++
drm is a linuxism. It shouldn't be used for binding doc.
Ack
1 file changed, 228 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt new file mode 100644 index 000000000000..061bc87147a3 --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -0,0 +1,228 @@ +Allwinner A10 Display Pipeline +==============================
+The Allwinner A10 Display pipeline is composed of several components +that are going to be documented below:
+TV Encoder +----------
+The TV Encoder supports the composite and VGA output. It is one end of +the pipeline.
+Required properties:
- compatible: value should be "allwinner,sun4i-a10-tv-encoder".
- reg: base address and size of memory-mapped region
- clocks: the clocks driving the TV encoder
+- ports: A ports node with endpoint definitions as defined in
- Documentation/devicetree/bindings/media/video-interfaces.txt. The
- first port should be the input endpoint.
+TCON +----
+The TCON acts as a timing controller for RGB, LVDS and TV interfaces.
+Required properties:
- compatible: value should be "allwinner,sun4i-a10-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
- clock-names: the clock names mentioned above
- clock-output-names: Name of the pixel clock created
+- ports: A ports node with endpoint definitions as defined in
- Documentation/devicetree/bindings/media/video-interfaces.txt. The
- first port should be the input endpoint, the second one the output
+Optional properties:
- allwinner,panel: phandle to the panel used in our RGB interface
+Display Engine Backend +----------------------
+The display engine backend exposes layers and sprites to the +system.
+Required properties:
- compatible: value must be one of:
- allwinner,sun5i-a13-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
- mod: the backend module clock
- ram: the backend DRAM clock
- clock-names: the clock names mentioned above
- resets: phandles to the reset controllers driving the backend
+- 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 output
+Display Engine Frontend +-----------------------
+The display engine frontend does formats conversion, scaling, +deinterlacing and color space conversion.
+Required properties:
- compatible: value must be one of:
- allwinner,sun5i-a13-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
- ahb: the backend interface clock
- mod: the backend module clock
- ram: the backend DRAM clock
should be frontend?
Yeah, stupid copy/paste mistake :/
- clock-names: the clock names mentioned above
- resets: phandles to the reset controllers driving the backend
ditto
+Display Engine Pipeline +-----------------------
+The display engine pipeline (and its entry point, since it can be +either directly the backend or the frontend) is represented as an +extra node.
+Required properties:
- compatible: value must be one of:
- allwinner,sun5i-a13-display-engine
- allwinner,pipelines: list of phandle to the entry points of the
- pipelines (either to the frontend or backend)
+Example:
+panel: panel {
- compatible = "olimex,lcd-olinuxino-43-ts";
This should either be a child of what it is attached to or use of-graph.
You had a similar discussion with Thierry last time I sent it, and it seemed to settle on keeping that property:
http://lists.infradead.org/pipermail/linux-arm-kernel/2015-October/382115.ht...
Has that changed?
+};
+tve0: tv-encoder@01c0a000 {
- compatible = "allwinner,sun4i-a10-tv-encoder";
- reg = <0x01c0a000 0x1000>;
- clocks = <&ahb_gates 34>;
- port {
#address-cells = <1>;
#size-cells = <0>;
tve0_in_tcon0: endpoint@0 {
reg = <0>;
I would drop the reg where you only have 1 node for ports or endpoints.
Ok
Thanks! Maxime
On Wed, Feb 3, 2016 at 1:59 PM, Maxime Ripard maxime.ripard@free-electrons.com wrote:
Hi Rob,
On Thu, Jan 14, 2016 at 09:15:15PM -0600, Rob Herring wrote:
On Thu, Jan 14, 2016 at 04:25:00PM +0100, Maxime Ripard wrote:
The display pipeline of the Allwinner A10 is involving several loosely coupled components.
Add a documentation for the bindings.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
.../bindings/display/sunxi/sun4i-drm.txt | 228 +++++++++++++++++++++
[...]
+Required properties:
- compatible: value must be one of:
- allwinner,sun5i-a13-display-engine
- allwinner,pipelines: list of phandle to the entry points of the
- pipelines (either to the frontend or backend)
+Example:
+panel: panel {
- compatible = "olimex,lcd-olinuxino-43-ts";
This should either be a child of what it is attached to or use of-graph.
You had a similar discussion with Thierry last time I sent it, and it seemed to settle on keeping that property:
http://lists.infradead.org/pipermail/linux-arm-kernel/2015-October/382115.ht...
Has that changed?
No, I still disagree. We already have 2 ways to describe panels: as a child node of its controller or with of-graph. We don't need a 3rd way even if it is simple. The problem with of-graph today is too much of the parsing is left to the individual drivers. We need to fix that and make it be handled by common code.
Rob
On Wed, Feb 03, 2016 at 02:19:31PM -0600, Rob Herring wrote:
+Required properties:
- compatible: value must be one of:
- allwinner,sun5i-a13-display-engine
- allwinner,pipelines: list of phandle to the entry points of the
- pipelines (either to the frontend or backend)
+Example:
+panel: panel {
- compatible = "olimex,lcd-olinuxino-43-ts";
This should either be a child of what it is attached to or use of-graph.
You had a similar discussion with Thierry last time I sent it, and it seemed to settle on keeping that property:
http://lists.infradead.org/pipermail/linux-arm-kernel/2015-October/382115.ht...
Has that changed?
No, I still disagree. We already have 2 ways to describe panels: as a child node of its controller or with of-graph. We don't need a 3rd way even if it is simple. The problem with of-graph today is too much of the parsing is left to the individual drivers. We need to fix that and make it be handled by common code.
Ok. I'm guessing from your answer you prefer of-graph over a child node ?
Maxime
One of the A10 display pipeline possible output is an RGB interface to drive LCD panels directly. This is done through the first channel of the TCON that will output our video signals directly.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/Makefile | 2 + drivers/gpu/drm/sun4i/sun4i_rgb.c | 229 +++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/sun4i/sun4i_rgb.h | 18 +++ drivers/gpu/drm/sun4i/sun4i_tcon.c | 26 ++++- drivers/gpu/drm/sun4i/sun4i_tcon.h | 2 + 5 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/sun4i/sun4i_rgb.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_rgb.h
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 7b0e83bae867..a7c9e7fd5a26 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -3,6 +3,8 @@ sun4i-drm-y += sun4i_drv.o sun4i-drm-y += sun4i_framebuffer.o sun4i-drm-y += sun4i_layer.o
+sun4i-drm-y += sun4i_rgb.o + obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o
obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c new file mode 100644 index 000000000000..c2b366e39749 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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 <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> + +#include "sun4i_drv.h" +#include "sun4i_tcon.h" + +struct sun4i_rgb { + struct drm_connector connector; + struct drm_encoder encoder; + + struct sun4i_drv *drv; +}; + +static inline struct sun4i_rgb * +drm_connector_to_sun4i_rgb(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_rgb, + connector); +} + +static inline struct sun4i_rgb * +drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_rgb, + encoder); +} + +static int sun4i_rgb_get_modes(struct drm_connector *connector) +{ + struct sun4i_rgb *rgb = + drm_connector_to_sun4i_rgb(connector); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + return drm_panel_get_modes(tcon->panel); +} + +static int sun4i_rgb_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + u32 hsync = mode->hsync_end - mode->hsync_start; + u32 vsync = mode->vsync_end - mode->vsync_start; + + DRM_DEBUG_DRIVER("Validating modes...\n"); + + if ((hsync < 1) || (hsync > 0x3ff) || + (mode->htotal < 1) || (mode->htotal > 0xfff)) + return MODE_H_ILLEGAL; + + DRM_DEBUG_DRIVER("Horizontal parameters OK\n"); + + if ((vsync < 1) || (vsync > 0x3ff) || + (mode->vtotal < 1) || (mode->vtotal > 0xfff)) + return MODE_V_ILLEGAL; + + DRM_DEBUG_DRIVER("Vertical parameters OK\n"); + + return MODE_OK; +} + +static struct drm_encoder * +sun4i_rgb_best_encoder(struct drm_connector *connector) +{ + struct sun4i_rgb *rgb = + drm_connector_to_sun4i_rgb(connector); + + return &rgb->encoder; +} + +static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { + .get_modes = sun4i_rgb_get_modes, + .mode_valid = sun4i_rgb_mode_valid, + .best_encoder = sun4i_rgb_best_encoder, +}; + +static enum drm_connector_status +sun4i_rgb_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void +sun4i_rgb_connector_destroy(struct drm_connector *connector) +{ + struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + drm_panel_detach(tcon->panel); + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs sun4i_rgb_con_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .detect = sun4i_rgb_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = sun4i_rgb_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) +{ + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Enabling RGB output\n"); + + drm_panel_enable(tcon->panel); + sun4i_tcon_channel_enable(tcon, 0); +} + +static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) +{ + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Disabling RGB output\n"); + + sun4i_tcon_channel_disable(tcon, 0); + drm_panel_disable(tcon->panel); +} + +static bool sun4i_rgb_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + return true; +} + +static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + sun4i_tcon0_mode_set(tcon, mode); + + clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); +} + +static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { + .mode_fixup = sun4i_rgb_encoder_mode_fixup, + .mode_set = sun4i_rgb_encoder_mode_set, + .disable = sun4i_rgb_encoder_disable, + .enable = sun4i_rgb_encoder_enable, +}; + +static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static struct drm_encoder_funcs sun4i_rgb_enc_funcs = { + .destroy = sun4i_rgb_enc_destroy, +}; + +int sun4i_rgb_init(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon = drv->tcon; + struct sun4i_rgb *rgb; + int ret; + + /* If we don't have a panel, there's no point in going on */ + if (!tcon->panel) + return -ENODEV; + + rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); + if (!rgb) + return -ENOMEM; + rgb->drv = drv; + + drm_encoder_helper_add(&rgb->encoder, + &sun4i_rgb_enc_helper_funcs); + ret = drm_encoder_init(drm, + &rgb->encoder, + &sun4i_rgb_enc_funcs, + DRM_MODE_ENCODER_NONE); + if (ret) { + dev_err(drm->dev, "Couldn't initialise the rgb encoder\n"); + goto err_out; + } + + /* 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; + } + + drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder); + + drm_panel_attach(tcon->panel, &rgb->connector); + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&rgb->encoder); +err_out: + return ret; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.h b/drivers/gpu/drm/sun4i/sun4i_rgb.h new file mode 100644 index 000000000000..7c4da4c8acdd --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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. + */ + +#ifndef _SUN4I_RGB_H_ +#define _SUN4I_RGB_H_ + +int sun4i_rgb_init(struct drm_device *drm); + +#endif /* _SUN4I_RGB_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index e253818b711b..6a6dc47a3b42 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -15,6 +15,7 @@ #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_modes.h> +#include <drm/drm_panel.h>
#include <linux/clk-provider.h> #include <linux/component.h> @@ -25,6 +26,7 @@
#include "sun4i_crtc.h" #include "sun4i_drv.h" +#include "sun4i_rgb.h" #include "sun4i_tcon.h"
void sun4i_tcon_disable(struct sun4i_tcon *tcon) @@ -418,6 +420,7 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, struct drm_device *drm = data; struct sun4i_drv *drv = drm->dev_private; struct sun4i_tcon *tcon; + struct device_node *np; int ret;
tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); @@ -444,7 +447,20 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, goto err_free_clocks; }
- return 0; + np = of_parse_phandle(dev->of_node, "allwinner,panel", 0); + if (!np) { + dev_info(dev, "No panel found... RGB output disabled\n"); + return 0; + } + + tcon->panel = of_drm_find_panel(np); + if (!tcon->panel) { + dev_err(dev, "Couldn't find our panel\n"); + ret = -ENODEV; + goto err_free_clocks; + } + + return sun4i_rgb_init(drm);
err_free_clocks: sun4i_tcon_free_clocks(tcon); @@ -466,6 +482,14 @@ static struct component_ops sun4i_tcon_ops = {
static int sun4i_tcon_probe(struct platform_device *pdev) { + struct device_node *np; + + np = of_parse_phandle(pdev->dev.of_node, "allwinner,panel", 0); + if (np) { + if (!of_drm_find_panel(np)) + return -EPROBE_DEFER; + } + return component_add(&pdev->dev, &sun4i_tcon_ops); }
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index 6fa093bce974..9496912b6484 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -152,6 +152,8 @@ struct sun4i_tcon {
/* Pixel clock */ struct clk *dclk; + + struct drm_panel *panel; };
/* Global Control */
Some Allwinner SoCs have an IP called the TV encoder that is used to output composite and VGA signals. In such a case, we need to use the second TCON channel.
Add support for that TV encoder.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/Makefile | 1 + drivers/gpu/drm/sun4i/sun4i_tv.c | 588 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 589 insertions(+) create mode 100644 drivers/gpu/drm/sun4i/sun4i_tv.c
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index a7c9e7fd5a26..c458b09181fe 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o
obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tcon.o +obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c new file mode 100644 index 000000000000..4f10f8b6b07c --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * 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/of_address.h> +#include <linux/regmap.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> + +#include "sun4i_backend.h" +#include "sun4i_drv.h" +#include "sun4i_tcon.h" + +#define SUN4I_TVE_EN_REG 0x000 +#define SUN4I_TVE_EN_DAC_MAP_MASK GENMASK(19, 4) +#define SUN4I_TVE_EN_DAC_MAP(dac, out) (((out) & 0xf) << (dac + 1) * 4) +#define SUN4I_TVE_EN_ENABLE BIT(0) + +#define SUN4I_TVE_CFG0_REG 0x004 +#define SUN4I_TVE_CFG0_DAC_CONTROL_54M BIT(26) +#define SUN4I_TVE_CFG0_CORE_DATAPATH_54M BIT(25) +#define SUN4I_TVE_CFG0_CORE_CONTROL_54M BIT(24) +#define SUN4I_TVE_CFG0_YC_EN BIT(17) +#define SUN4I_TVE_CFG0_COMP_EN BIT(16) +#define SUN4I_TVE_CFG0_RES(x) ((x) & 0xf) +#define SUN4I_TVE_CFG0_RES_480i SUN4I_TVE_CFG0_RES(0) +#define SUN4I_TVE_CFG0_RES_576i SUN4I_TVE_CFG0_RES(1) + +#define SUN4I_TVE_DAC0_REG 0x008 +#define SUN4I_TVE_DAC0_CLOCK_INVERT BIT(24) +#define SUN4I_TVE_DAC0_LUMA(x) (((x) & 3) << 20) +#define SUN4I_TVE_DAC0_LUMA_0_4 SUN4I_TVE_DAC0_LUMA(3) +#define SUN4I_TVE_DAC0_CHROMA(x) (((x) & 3) << 18) +#define SUN4I_TVE_DAC0_CHROMA_0_75 SUN4I_TVE_DAC0_CHROMA(3) +#define SUN4I_TVE_DAC0_INTERNAL_DAC(x) (((x) & 3) << 16) +#define SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS SUN4I_TVE_DAC0_INTERNAL_DAC(3) +#define SUN4I_TVE_DAC0_DAC_EN(dac) BIT(dac) + +#define SUN4I_TVE_NOTCH_REG 0x00c +#define SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(dac, x) ((4 - (x)) << (dac * 3)) + +#define SUN4I_TVE_CHROMA_FREQ_REG 0x010 + +#define SUN4I_TVE_PORCH_REG 0x014 +#define SUN4I_TVE_PORCH_BACK(x) ((x) << 16) +#define SUN4I_TVE_PORCH_FRONT(x) (x) + +#define SUN4I_TVE_LINE_REG 0x01c +#define SUN4I_TVE_LINE_FIRST(x) ((x) << 16) +#define SUN4I_TVE_LINE_NUMBER(x) (x) + +#define SUN4I_TVE_LEVEL_REG 0x020 +#define SUN4I_TVE_LEVEL_BLANK(x) ((x) << 16) +#define SUN4I_TVE_LEVEL_BLACK(x) (x) + +#define SUN4I_TVE_DAC1_REG 0x024 +#define SUN4I_TVE_DAC1_AMPLITUDE(dac, x) ((x) << (dac * 8)) + +#define SUN4I_TVE_DETECT_STA_REG 0x038 +#define SUN4I_TVE_DETECT_STA_DAC(dac) BIT((dac * 8)) +#define SUN4I_TVE_DETECT_STA_UNCONNECTED 0 +#define SUN4I_TVE_DETECT_STA_CONNECTED 1 +#define SUN4I_TVE_DETECT_STA_GROUND 2 + +#define SUN4I_TVE_CB_CR_LVL_REG 0x10c +#define SUN4I_TVE_CB_CR_LVL_CR_BURST(x) ((x) << 8) +#define SUN4I_TVE_CB_CR_LVL_CB_BURST(x) (x) + +#define SUN4I_TVE_TINT_BURST_PHASE_REG 0x110 +#define SUN4I_TVE_TINT_BURST_PHASE_CHROMA(x) (x) + +#define SUN4I_TVE_BURST_WIDTH_REG 0x114 +#define SUN4I_TVE_BURST_WIDTH_BREEZEWAY(x) ((x) << 16) +#define SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(x) ((x) << 8) +#define SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(x) (x) + +#define SUN4I_TVE_CB_CR_GAIN_REG 0x118 +#define SUN4I_TVE_CB_CR_GAIN_CR(x) ((x) << 8) +#define SUN4I_TVE_CB_CR_GAIN_CB(x) (x) + +#define SUN4I_TVE_SYNC_VBI_REG 0x11c +#define SUN4I_TVE_SYNC_VBI_SYNC(x) ((x) << 16) +#define SUN4I_TVE_SYNC_VBI_VBLANK(x) (x) + +#define SUN4I_TVE_ACTIVE_LINE_REG 0x124 +#define SUN4I_TVE_ACTIVE_LINE(x) (x) + +#define SUN4I_TVE_CHROMA_REG 0x128 +#define SUN4I_TVE_CHROMA_COMP_GAIN(x) ((x) & 3) +#define SUN4I_TVE_CHROMA_COMP_GAIN_50 SUN4I_TVE_CHROMA_COMP_GAIN(2) + +#define SUN4I_TVE_12C_REG 0x12c +#define SUN4I_TVE_12C_NOTCH_WIDTH_WIDE BIT(8) +#define SUN4I_TVE_12C_COMP_YUV_EN BIT(0) + +#define SUN4I_TVE_RESYNC_REG 0x130 +#define SUN4I_TVE_RESYNC_FIELD BIT(31) +#define SUN4I_TVE_RESYNC_LINE(x) ((x) << 16) +#define SUN4I_TVE_RESYNC_PIXEL(x) (x) + +#define SUN4I_TVE_SLAVE_REG 0x134 + +#define SUN4I_TVE_WSS_DATA2_REG 0x244 + +struct color_gains { + u16 cb; + u16 cr; +}; + +struct burst_levels { + u16 cb; + u16 cr; +}; + +struct video_levels { + u16 black; + u16 blank; +}; + +struct resync_parameters { + bool field; + u16 line; + u16 pixel; +}; + +struct tv_mode { + char *name; + + u32 mode; + u32 chroma_freq; + u16 back_porch; + u16 front_porch; + u16 line_number; + u16 vblank_level; + + u32 hdisplay; + u16 hfront_porch; + u16 hsync_len; + u16 hback_porch; + + u32 vdisplay; + u16 vfront_porch; + u16 vsync_len; + u16 vback_porch; + + bool yc_en; + 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; +}; + +struct sun4i_tv { + struct drm_connector connector; + struct drm_encoder encoder; + + struct clk *clk; + struct regmap *regs; + + struct sun4i_drv *drv; +}; + +struct tv_mode tv_modes[] = { +}; + +static inline struct sun4i_tv * +drm_encoder_to_sun4i_tv(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_tv, + encoder); +} + +static inline struct sun4i_tv * +drm_connector_to_sun4i_tv(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_tv, + connector); +} + +/* + * FIXME: If only the drm_display_mode private field was usable, this + * could go away... + * + * 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) +{ + 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]; + + DRM_DEBUG_DRIVER("Comparing mode %s vs %s", + mode->name, tv_mode->name); + + if (!strcmp(mode->name, tv_mode->name)) + return tv_mode; + } + + /* Then by number of lines */ + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + 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, + mode->vdisplay, tv_mode->vdisplay); + + if (mode->vdisplay == tv_mode->vdisplay) + return tv_mode; + } + + return NULL; +} + +static int sun4i_tv_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static void sun4i_tv_disable(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; + + DRM_DEBUG_DRIVER("Disabling the TV Output\n"); + + sun4i_tcon_channel_disable(tcon, 1); + + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_ENABLE, + 0); +} + +static void sun4i_tv_enable(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; + + DRM_DEBUG_DRIVER("Enabling the TV Output\n"); + + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_ENABLE, + SUN4I_TVE_EN_ENABLE); + + sun4i_tcon_channel_enable(tcon, 1); +} + +static void sun4i_tv_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + 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); + + sun4i_backend_apply_color_correction(drv->backend); + sun4i_tcon1_mode_set(tcon, mode); + + /* Enable and map the DAC to the output */ + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_DAC_MAP_MASK, + SUN4I_TVE_EN_DAC_MAP(0, 1) | + SUN4I_TVE_EN_DAC_MAP(1, 2) | + SUN4I_TVE_EN_DAC_MAP(2, 3) | + SUN4I_TVE_EN_DAC_MAP(3, 4)); + + /* Set PAL settings */ + regmap_write(tv->regs, SUN4I_TVE_CFG0_REG, + tv_mode->mode | + (tv_mode->yc_en ? SUN4I_TVE_CFG0_YC_EN : 0) | + SUN4I_TVE_CFG0_COMP_EN | + SUN4I_TVE_CFG0_DAC_CONTROL_54M | + SUN4I_TVE_CFG0_CORE_DATAPATH_54M | + SUN4I_TVE_CFG0_CORE_CONTROL_54M); + + /* Configure the DAC for a composite output */ + regmap_write(tv->regs, SUN4I_TVE_DAC0_REG, + SUN4I_TVE_DAC0_DAC_EN(0) | + (tv_mode->dac3_en ? SUN4I_TVE_DAC0_DAC_EN(3) : 0) | + SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS | + SUN4I_TVE_DAC0_CHROMA_0_75 | + SUN4I_TVE_DAC0_LUMA_0_4 | + SUN4I_TVE_DAC0_CLOCK_INVERT | + (tv_mode->dac_bit25_en ? BIT(25) : 0) | + BIT(30)); + + /* Configure the sample delay between DAC0 and the other DAC */ + regmap_write(tv->regs, SUN4I_TVE_NOTCH_REG, + SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(1, 0) | + SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(2, 0)); + + regmap_write(tv->regs, SUN4I_TVE_CHROMA_FREQ_REG, + tv_mode->chroma_freq); + + /* Set the front and back porch */ + regmap_write(tv->regs, SUN4I_TVE_PORCH_REG, + SUN4I_TVE_PORCH_BACK(tv_mode->back_porch) | + SUN4I_TVE_PORCH_FRONT(tv_mode->front_porch)); + + /* Set the lines setup */ + regmap_write(tv->regs, SUN4I_TVE_LINE_REG, + SUN4I_TVE_LINE_FIRST(22) | + SUN4I_TVE_LINE_NUMBER(tv_mode->line_number)); + + regmap_write(tv->regs, SUN4I_TVE_LEVEL_REG, + SUN4I_TVE_LEVEL_BLANK(tv_mode->video_levels->blank) | + SUN4I_TVE_LEVEL_BLACK(tv_mode->video_levels->black)); + + regmap_write(tv->regs, SUN4I_TVE_DAC1_REG, + SUN4I_TVE_DAC1_AMPLITUDE(0, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(1, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(2, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(3, 0x18)); + + regmap_write(tv->regs, SUN4I_TVE_CB_CR_LVL_REG, + SUN4I_TVE_CB_CR_LVL_CB_BURST(tv_mode->burst_levels->cb) | + SUN4I_TVE_CB_CR_LVL_CR_BURST(tv_mode->burst_levels->cr)); + + /* Set burst width for a composite output */ + regmap_write(tv->regs, SUN4I_TVE_BURST_WIDTH_REG, + SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(126) | + SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(68) | + SUN4I_TVE_BURST_WIDTH_BREEZEWAY(22)); + + regmap_write(tv->regs, SUN4I_TVE_CB_CR_GAIN_REG, + SUN4I_TVE_CB_CR_GAIN_CB(tv_mode->color_gains->cb) | + SUN4I_TVE_CB_CR_GAIN_CR(tv_mode->color_gains->cr)); + + regmap_write(tv->regs, SUN4I_TVE_SYNC_VBI_REG, + SUN4I_TVE_SYNC_VBI_SYNC(0x10) | + SUN4I_TVE_SYNC_VBI_VBLANK(tv_mode->vblank_level)); + + regmap_write(tv->regs, SUN4I_TVE_ACTIVE_LINE_REG, + SUN4I_TVE_ACTIVE_LINE(1440)); + + /* Set composite chroma gain to 50 % */ + regmap_write(tv->regs, SUN4I_TVE_CHROMA_REG, + SUN4I_TVE_CHROMA_COMP_GAIN_50); + + regmap_write(tv->regs, SUN4I_TVE_12C_REG, + SUN4I_TVE_12C_COMP_YUV_EN | + SUN4I_TVE_12C_NOTCH_WIDTH_WIDE); + + regmap_write(tv->regs, SUN4I_TVE_RESYNC_REG, + SUN4I_TVE_RESYNC_PIXEL(tv_mode->resync_params->pixel) | + SUN4I_TVE_RESYNC_LINE(tv_mode->resync_params->line) | + (tv_mode->resync_params->field ? + SUN4I_TVE_RESYNC_FIELD : 0)); + + regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0); + + clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); +} + +static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = { + .atomic_check = sun4i_tv_atomic_check, + .disable = sun4i_tv_disable, + .enable = sun4i_tv_enable, + .mode_set = sun4i_tv_mode_set, +}; + +static void sun4i_tv_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static struct drm_encoder_funcs sun4i_tv_funcs = { + .destroy = sun4i_tv_destroy, +}; + +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]; + + DRM_DEBUG_DRIVER("Creating mode %s\n", tv_mode->name); + + strcpy(mode->name, tv_mode->name); + mode->type = DRM_MODE_TYPE_DRIVER; + mode->clock = 13500; + mode->flags = DRM_MODE_FLAG_INTERLACE; + + mode->hdisplay = tv_mode->hdisplay; + mode->hsync_start = mode->hdisplay + tv_mode->hfront_porch; + mode->hsync_end = mode->hsync_start + tv_mode->hsync_len; + mode->htotal = mode->hsync_end + tv_mode->hback_porch; + + mode->vdisplay = tv_mode->vdisplay; + mode->vsync_start = mode->vdisplay + tv_mode->vfront_porch; + mode->vsync_end = mode->vsync_start + tv_mode->vsync_len; + mode->vtotal = mode->vsync_end + tv_mode->vback_porch; + + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int sun4i_tv_comp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* TODO */ + return MODE_OK; +} + +static struct drm_encoder * +sun4i_tv_comp_best_encoder(struct drm_connector *connector) +{ + struct sun4i_tv *tv = drm_connector_to_sun4i_tv(connector); + + return &tv->encoder; +} + +static struct drm_connector_helper_funcs sun4i_tv_comp_connector_helper_funcs = { + .get_modes = sun4i_tv_comp_get_modes, + .mode_valid = sun4i_tv_comp_mode_valid, + .best_encoder = sun4i_tv_comp_best_encoder, +}; + +static enum drm_connector_status +sun4i_tv_comp_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void +sun4i_tv_comp_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs sun4i_tv_comp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .detect = sun4i_tv_comp_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = sun4i_tv_comp_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct regmap_config sun4i_tv_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SUN4I_TVE_WSS_DATA2_REG, + .name = "tv-encoder", +}; + +static int sun4i_tv_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tv *tv; + struct resource *res; + void __iomem *regs; + int ret; + + tv = devm_kzalloc(dev, sizeof(*tv), GFP_KERNEL); + if (!tv) + return -ENOMEM; + tv->drv = drv; + + 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 TV encoder registers\n"); + return PTR_ERR(regs); + } + + tv->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_tv_regmap_config); + if (IS_ERR(tv->regs)) { + dev_err(dev, "Couldn't create the TV encoder regmap\n"); + return PTR_ERR(tv->regs); + } + + tv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(tv->clk)) { + dev_err(dev, "Couldn't get the TV encoder clock\n"); + return PTR_ERR(tv->clk); + } + clk_prepare_enable(tv->clk); + + drm_encoder_helper_add(&tv->encoder, + &sun4i_tv_helper_funcs); + ret = drm_encoder_init(drm, + &tv->encoder, + &sun4i_tv_funcs, + DRM_MODE_ENCODER_TVDAC); + if (ret) { + dev_err(dev, "Couldn't initialise the TV encoder\n"); + return ret; + } + + tv->encoder.possible_crtcs = BIT(0); + + drm_connector_helper_add(&tv->connector, + &sun4i_tv_comp_connector_helper_funcs); + ret = drm_connector_init(drm, &tv->connector, + &sun4i_tv_comp_connector_funcs, + DRM_MODE_CONNECTOR_Composite); + if (ret) { + dev_err(dev, + "Couldn't initialise the Composite connector\n"); + goto err_cleanup_connector; + } + tv->connector.interlace_allowed = true; + + drm_mode_connector_attach_encoder(&tv->connector, &tv->encoder); + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&tv->encoder); + return ret; +} + +static void sun4i_tv_unbind(struct device *dev, struct device *master, + void *data) +{ +#warning FIXME +} + +static struct component_ops sun4i_tv_ops = { + .bind = sun4i_tv_bind, + .unbind = sun4i_tv_unbind, +}; + +static int sun4i_tv_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_tv_ops); +} + +static int sun4i_tv_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_tv_ops); + + return 0; +} + +static const struct of_device_id sun4i_tv_of_table[] = { + { .compatible = "allwinner,sun4i-a10-tv-encoder" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_tv_of_table); + +static struct platform_driver sun4i_tv_platform_driver = { + .probe = sun4i_tv_probe, + .remove = sun4i_tv_remove, + .driver = { + .name = "sun4i-tve", + .of_match_table = sun4i_tv_of_table, + }, +}; +module_platform_driver(sun4i_tv_platform_driver); + +MODULE_AUTHOR("Maxime Ripard maxime.ripard@free-electrons.com"); +MODULE_DESCRIPTION("Allwinner A10 TV Encoder Driver"); +MODULE_LICENSE("GPL");
Now that we have support for the composite output, we can start adding new supported standards. Start with PAL, and we will add other eventually.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_tv.c | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+)
diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index 4f10f8b6b07c..4f369de2a1fc 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -176,7 +176,49 @@ struct sun4i_tv { struct sun4i_drv *drv; };
+struct video_levels pal_video_levels = { + .black = 252, .blank = 252, +}; + +struct burst_levels pal_burst_levels = { + .cb = 40, .cr = 40, +}; + +struct color_gains pal_color_gains = { + .cb = 224, .cr = 224, +}; + +struct resync_parameters pal_resync_parameters = { + .field = true, .line = 13, .pixel = 12, +}; + struct tv_mode tv_modes[] = { + { + .name = "PAL", + .mode = SUN4I_TVE_CFG0_RES_576i, + .chroma_freq = 0x2a098acb, + + .back_porch = 138, + .front_porch = 24, + .line_number = 625, + + .hdisplay = 720, + .hfront_porch = 3, + .hsync_len = 2, + .hback_porch = 139, + + .vdisplay = 576, + .vfront_porch = 28, + .vsync_len = 2, + .vback_porch = 19, + + .vblank_level = 252, + + .color_gains = &pal_color_gains, + .burst_levels = &pal_burst_levels, + .video_levels = &pal_video_levels, + .resync_params = &pal_resync_parameters, + }, };
static inline struct sun4i_tv *
Add the settings to support the NTSC standard.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_tv.c | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+)
diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c index 4f369de2a1fc..46331087b0e9 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tv.c +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -176,24 +176,69 @@ struct sun4i_tv { struct sun4i_drv *drv; };
+struct video_levels ntsc_video_levels = { + .black = 282, .blank = 240, +}; + struct video_levels pal_video_levels = { .black = 252, .blank = 252, };
+struct burst_levels ntsc_burst_levels = { + .cb = 79, .cr = 0, +}; + struct burst_levels pal_burst_levels = { .cb = 40, .cr = 40, };
+struct color_gains ntsc_color_gains = { + .cb = 160, .cr = 160, +}; + struct color_gains pal_color_gains = { .cb = 224, .cr = 224, };
+struct resync_parameters ntsc_resync_parameters = { + .field = false, .line = 14, .pixel = 12, +}; + struct resync_parameters pal_resync_parameters = { .field = true, .line = 13, .pixel = 12, };
struct tv_mode tv_modes[] = { { + .name = "NTSC", + .mode = SUN4I_TVE_CFG0_RES_480i, + .chroma_freq = 0x21f07c1f, + .yc_en = true, + .dac3_en = true, + .dac_bit25_en = true, + + .back_porch = 118, + .front_porch = 32, + .line_number = 525, + + .hdisplay = 720, + .hfront_porch = 18, + .hsync_len = 2, + .hback_porch = 118, + + .vdisplay = 480, + .vfront_porch = 26, + .vsync_len = 2, + .vback_porch = 17, + + .vblank_level = 240, + + .color_gains = &ntsc_color_gains, + .burst_levels = &ntsc_burst_levels, + .video_levels = &ntsc_video_levels, + .resync_params = &ntsc_resync_parameters, + }, + { .name = "PAL", .mode = SUN4I_TVE_CFG0_RES_576i, .chroma_freq = 0x2a098acb,
The TCON, tv-encoder and display engine backends and frontends are combined to create our display pipeline.
Add them to the R8 DTSI. It's supposed to be perfectly compatible with the A10s and A13, but since we haven't tested it on them yet, it's safer to just enable it on the R8. Eventually, it should be moved to sun5i.dtsi
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- arch/arm/boot/dts/sun5i-r8.dtsi | 134 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+)
diff --git a/arch/arm/boot/dts/sun5i-r8.dtsi b/arch/arm/boot/dts/sun5i-r8.dtsi index 691d3de75b35..b3bf91516a80 100644 --- a/arch/arm/boot/dts/sun5i-r8.dtsi +++ b/arch/arm/boot/dts/sun5i-r8.dtsi @@ -57,4 +57,138 @@ status = "disabled"; }; }; + + soc@01c00000 { + tve0: tv-encoder@01c0a000 { + compatible = "allwinner,sun4i-a10-tv-encoder"; + reg = <0x01c0a000 0x1000>; + clocks = <&ahb_gates 34>; + status = "disabled"; + + port { + #address-cells = <1>; + #size-cells = <0>; + + tve0_in_tcon0: endpoint@0 { + reg = <0>; + remote-endpoint = <&tcon0_out_tve0>; + }; + }; + }; + + tcon0: lcd-controller@01c0c000 { + compatible = "allwinner,sun4i-a10-tcon"; + reg = <0x01c0c000 0x1000>; + interrupts = <44>; + clocks = <&ahb_gates 36>, + <&tcon_ch0_clk>, + <&tcon_ch1_clk>; + clock-names = "ahb", + "tcon-ch0", + "tcon-ch1"; + clock-output-names = "tcon-pixel-clock"; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + tcon0_in: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + tcon0_in_be0: endpoint@0 { + reg = <0>; + remote-endpoint = <&be0_out_tcon0>; + }; + }; + + tcon0_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + tcon0_out_tve0: endpoint@0 { + reg = <0>; + remote-endpoint = <&tve0_in_tcon0>; + }; + }; + }; + }; + + fe0: display-frontend@01e00000 { + compatible = "allwinner,sun5i-a13-display-frontend"; + reg = <0x01e00000 0x20000>; + interrupts = <47>; + clocks = <&ahb_gates 46>, <&de_fe_clk>, + <&dram_gates 25>; + clock-names = "ahb", "mod", + "ram"; + resets = <&de_fe_clk>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + fe0_out: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + fe0_out_be0: endpoint@0 { + reg = <0>; + remote-endpoint = <&be0_in_fe0>; + }; + }; + }; + }; + + be0: display-backend@01e60000 { + compatible = "allwinner,sun5i-a13-display-backend"; + reg = <0x01e60000 0x10000>; + clocks = <&ahb_gates 44>, <&de_be_clk>, + <&dram_gates 26>; + clock-names = "ahb", "mod", + "ram"; + resets = <&de_be_clk>; + status = "disabled"; + + assigned-clocks = <&de_be_clk>; + assigned-clock-rates = <300000000>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + be0_in: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + be0_in_fe0: endpoint@0 { + reg = <0>; + remote-endpoint = <&fe0_out_be0>; + }; + }; + + be0_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + be0_out_tcon0: endpoint@0 { + reg = <0>; + remote-endpoint = <&tcon0_in_be0>; + }; + }; + }; + }; + }; + + display-engine { + compatible = "allwinner,sun5i-a13-display-engine"; + pipelines = <&be0>; + }; };
The CHIP has a composite output available muxed with the microphone in the micro-jack plug.
Enable the composite output in its DTS.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- arch/arm/boot/dts/sun5i-r8-chip.dts | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/arch/arm/boot/dts/sun5i-r8-chip.dts b/arch/arm/boot/dts/sun5i-r8-chip.dts index c976c7618118..c26c095b42c6 100644 --- a/arch/arm/boot/dts/sun5i-r8-chip.dts +++ b/arch/arm/boot/dts/sun5i-r8-chip.dts @@ -84,6 +84,10 @@ }; };
+&be0 { + status = "okay"; +}; + &codec { status = "okay"; }; @@ -230,6 +234,14 @@ status = "okay"; };
+&tcon0 { + status = "okay"; +}; + +&tve0 { + status = "okay"; +}; + &uart1 { pinctrl-names = "default"; pinctrl-0 = <&uart1_pins_b>;
Olimex is an open source hardware boards vendors based in Bulgaria.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- Documentation/devicetree/bindings/vendor-prefixes.txt | 1 + 1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 55df1d444e9f..de4eb72b3adc 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -161,6 +161,7 @@ nuvoton Nuvoton Technology Corporation nvidia NVIDIA nxp NXP Semiconductors okaya Okaya Electric America, Inc. +olimex Olimex onnn ON Semiconductor Corp. opencores OpenCores.org option Option NV
On Thu, Jan 14, 2016 at 04:25:07PM +0100, Maxime Ripard wrote:
Olimex is an open source hardware boards vendors based in Bulgaria.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/vendor-prefixes.txt | 1 + 1 file changed, 1 insertion(+)
Acked-by: Rob Herring robh@kernel.org
Rob Herring robh@kernel.org hat am 15. Januar 2016 um 04:15 geschrieben:
On Thu, Jan 14, 2016 at 04:25:07PM +0100, Maxime Ripard wrote:
Olimex is an open source hardware boards vendors based in Bulgaria.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/vendor-prefixes.txt | 1 + 1 file changed, 1 insertion(+)
Acked-by: Rob Herring robh@kernel.org
I don't think we need this twice.
It's already committed:
2193377bdade ("DT: add Olimex to vendor prefixes")
Regards Stefan
Hi,
On Fri, Jan 15, 2016 at 07:41:35AM +0100, Stefan Wahren wrote:
Rob Herring robh@kernel.org hat am 15. Januar 2016 um 04:15 geschrieben:
On Thu, Jan 14, 2016 at 04:25:07PM +0100, Maxime Ripard wrote:
Olimex is an open source hardware boards vendors based in Bulgaria.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/vendor-prefixes.txt | 1 + 1 file changed, 1 insertion(+)
Acked-by: Rob Herring robh@kernel.org
I don't think we need this twice.
It's already committed:
2193377bdade ("DT: add Olimex to vendor prefixes")
Hmm, indeed.
Sorry for the noise, I'll drop the commit.
Thanks! Maxime
Add support for the Olimex LCD-OLinuXino-4.3TS panel to the DRM simple panel driver.
It is a 480x272 panel connected through a 24-bits RGB interface.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- .../display/panel/olimex,lcd-olinuxino-43-ts.txt | 7 ++++++ drivers/gpu/drm/panel/panel-simple.c | 26 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/olimex,lcd-olinuxino-43-ts.txt
diff --git a/Documentation/devicetree/bindings/display/panel/olimex,lcd-olinuxino-43-ts.txt b/Documentation/devicetree/bindings/display/panel/olimex,lcd-olinuxino-43-ts.txt new file mode 100644 index 000000000000..74540a090669 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/olimex,lcd-olinuxino-43-ts.txt @@ -0,0 +1,7 @@ +Olimex 4.3" TFT LCD panel + +Required properties: +- compatible: should be "olimex,lcd-olinuxino-43-ts" + +This binding is compatible with the simple-panel binding, which is specified +in simple-panel.txt in this directory. diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index f97b73ec4713..3ae22e0a7f38 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1003,6 +1003,29 @@ static const struct panel_desc okaya_rs800480t_7x0gp = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, };
+static const struct drm_display_mode olimex_lcd_olinuxino_43ts_mode = { + .clock = 9000, + .hdisplay = 480, + .hsync_start = 480 + 5, + .hsync_end = 480 + 5 + 30, + .htotal = 480 + 5 + 30 + 10, + .vdisplay = 272, + .vsync_start = 272 + 8, + .vsync_end = 272 + 8 + 5, + .vtotal = 272 + 8 + 5 + 3, + .vrefresh = 60, +}; + +static const struct panel_desc olimex_lcd_olinuxino_43ts = { + .modes = &olimex_lcd_olinuxino_43ts_mode, + .num_modes = 1, + .size = { + .width = 105, + .height = 67, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, +}; + static const struct drm_display_mode ortustech_com43h4m85ulc_mode = { .clock = 25000, .hdisplay = 480, @@ -1179,6 +1202,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "okaya,rs800480t-7x0gp", .data = &okaya_rs800480t_7x0gp, }, { + .compatible = "olimex,lcd-olinuxino-43-ts", + .data = &olimex_lcd_olinuxino_43ts, + }, { .compatible = "ortustech,com43h4m85ulc", .data = &ortustech_com43h4m85ulc, }, {
On Thu, Jan 14, 2016 at 04:25:08PM +0100, Maxime Ripard wrote:
Add support for the Olimex LCD-OLinuXino-4.3TS panel to the DRM simple panel driver.
It is a 480x272 panel connected through a 24-bits RGB interface.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
.../display/panel/olimex,lcd-olinuxino-43-ts.txt | 7 ++++++
Acked-by: Rob Herring robh@kernel.org
drivers/gpu/drm/panel/panel-simple.c | 26 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/olimex,lcd-olinuxino-43-ts.txt
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- arch/arm/boot/dts/sun5i-r8-chip.dts | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/arch/arm/boot/dts/sun5i-r8-chip.dts b/arch/arm/boot/dts/sun5i-r8-chip.dts index c26c095b42c6..147c39106f63 100644 --- a/arch/arm/boot/dts/sun5i-r8-chip.dts +++ b/arch/arm/boot/dts/sun5i-r8-chip.dts @@ -82,6 +82,10 @@ vin0-supply = <®_ldo3>; vin1-supply = <®_ldo4>; }; + + panel: panel { + compatible = "olimex,lcd-olinuxino-43-ts"; + }; };
&be0 { @@ -236,6 +240,7 @@
&tcon0 { status = "okay"; + allwinner,panel = <&panel>; };
&tve0 {
On Thu, 2016-01-14 at 16:24 +0100, Maxime Ripard wrote:
Hi everyone,
The Allwinner SoCs (except for the very latest ones) all share the same set of controllers, loosely coupled together to form the display pipeline.
Depending on the SoC, the number of instances of the controller will change (2 instances of each in the A10, only one in the A13, for example), and the output availables will change too (HDMI, composite, VGA on the A20, none of them on the A13).
On most featured SoCs, it looks like that:
+--------------------------------------------+ | RAM | +--------------------------------------------+ | | | | v | | v +----------------+ | | +----------------+ | Frontend | | | | Frontend | +----------------+ | | +----------------+ | | | | v | | v +----------------+ | | +----------------+ | Backend |<+ +>| Backend | +----------------+ +----------------+ | | v v +----------------+ +----------------+---> LVDS | TCON | | TCON |---> RGB +----------------+ +----------------+ | +---+ +---+ | | | | | v v v v +------------+ +------------+ +------------+---> VGA | TV Encoder | | HDMI | | TV Encoder |---> Composite +------------+ +------------+ +------------+
The current code only assumes that there is a single instance of all the controllers. It also supports only the RGB and Composite interfaces.
I found some time to play with it (with intention to get it running also on sun7i and sun4i) and ran into this when trying to build sun4i_drm as module:
ERROR: "sun4i_tcon1_mode_set" [drivers/gpu/drm/sun4i/sun4i_tv.ko] undefined! ERROR: "sun4i_backend_apply_color_correction" [drivers/gpu/drm/sun4i/sun4i_tv.ko] undefined! ERROR: "sun4i_tcon_channel_disable" [drivers/gpu/drm/sun4i/sun4i_tv.ko] undefined! ERROR: "sun4i_tcon_channel_enable" [drivers/gpu/drm/sun4i/sun4i_tv.ko] undefined! ERROR: "clk_unregister_composite" [drivers/gpu/drm/sun4i/sun4i_tcon.ko] undefined! ERROR: "sun4i_rgb_init" [drivers/gpu/drm/sun4i/sun4i_tcon.ko] undefined! ERROR: "clk_register_composite" [drivers/gpu/drm/sun4i/sun4i_tcon.ko] undefined! ERROR: "sun4i_backend_update_layer_coord" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_backend_update_layer_formats" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_tcon_channel_disable" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_tcon_enable_vblank" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_backend_update_layer_buffer" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_backend_layer_enable" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_tcon_channel_enable" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_tcon_enable" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_tcon_disable" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_tcon0_mode_set" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! ERROR: "sun4i_backend_commit" [drivers/gpu/drm/sun4i/sun4i-drm.ko] undefined! scripts/Makefile.modpost:91: recipe for target '__modpost' failed
Let me know what you think, Maxime
Changes from v1: - Rebased on top of 4.4
- Merged the clock drivers for the display and TCON channel 0 clocks - Replaced the container_of calls in the display reset clocks to an inline function - Checked the return code of of_clk_parent_fill in the clocks drivers - Checked the return code of of_clk_add_provider in the tcon-ch1 and PLL3 clocks - Added missing clocks headers - Created a composite clock unregister function
- Moved the binding documentation to Documentation/devicetree/bindings/display - Added the clocks binding documentation - Added the Olimex vendor to the list of DT vendors - Moved to the OF graph representation and the component framework
- Moved the reset cells count check into the reset framework to avoid duplicating the code in every xlate implementation. - Made the reset_ops const
- Reworked the DRM cmdline mode parsing code to allow named mode - Fixed the TV mode lookup when the mode name is not present (for example because it was given by the userspace)
- Made the driver outputs optional (to avoid crashing when a board doesn't have either a panel or a composite output enabled) - Added multiple plane support with transparency - Moved the backend registers writes commit in the CRTC atomic_flush callback - Removed the load / unload functions - Removed the enabled booleans in my private structure and removed the implicit call to disable_unused_functions in the DRM core to push it in the drivers. - Fixed a few bitmasks on some bitfields definition - Fixed the RGB connector mode validation that was not testing the right values
Matthias Brugger (1): clk: Add regmap support
Maxime Ripard (25): reset: Move DT cell size check to the core reset: Make reset_control_ops const clk: composite: Add unregister function clk: sunxi: Add display and TCON0 clocks driver clk: sunxi: Add PLL3 clock clk: sunxi: Add TCON channel1 clock clk: sun5i: add DRAM gates ARM: sun5i: dt: Add pll3 and pll7 clocks ARM: sun5i: a13: Add display and TCON clocks ARM: sun5i: Add DRAM gates ARM: sun5i: Add TV encoder gate to the DTSI drm/fb_cma_helper: Remove implicit call to disable_unused_functions drm/modes: Rewrite the command line parser drm/modes: Support modes names on the command line drm: Add Allwinner A10 Display Engine support drm: sun4i: Add DT bindings documentation drm: sun4i: Add RGB output drm: sun4i: Add composite output drm: sun4i: tv: Add PAL output standard drm: sun4i: tv: Add NTSC output standard ARM: sun5i: r8: Add display blocks to the DTSI ARM: sun5i: chip: Enable the TV Encoder devicetree: Add olimex vendor prefix drm/panel: simple: Add timings for the Olimex LCD-OLinuXino-4.3TS DO NOT MERGE: ARM: sun5i: chip: Enable the LCD panel
Documentation/devicetree/bindings/clock/sunxi.txt | 5 + .../display/panel/olimex,lcd-olinuxino-43-ts.txt | 7 + .../bindings/display/sunxi/sun4i-drm.txt | 228 +++++++ .../devicetree/bindings/vendor-prefixes.txt | 1 + arch/arm/boot/dts/sun5i-a10s.dtsi | 7 +- arch/arm/boot/dts/sun5i-a13.dtsi | 46 +- arch/arm/boot/dts/sun5i-r8-chip.dts | 17 + arch/arm/boot/dts/sun5i-r8.dtsi | 139 ++++- arch/arm/boot/dts/sun5i.dtsi | 62 ++ drivers/clk/Makefile | 1 + drivers/clk/clk-composite.c | 15 + drivers/clk/clk-divider.c | 68 ++- drivers/clk/clk-gate.c | 54 +- drivers/clk/clk-io.c | 48 ++ drivers/clk/clk-io.h | 22 + drivers/clk/clk-mux.c | 94 ++- drivers/clk/sunxi/Makefile | 3 + drivers/clk/sunxi/clk-simple-gates.c | 2 + drivers/clk/sunxi/clk-sun4i-display.c | 241 ++++++++ drivers/clk/sunxi/clk-sun4i-pll3.c | 90 +++ drivers/clk/sunxi/clk-sun4i-tcon-ch1.c | 154 +++++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 3 +- drivers/gpu/drm/drm_crtc.c | 3 +- drivers/gpu/drm/drm_fb_cma_helper.c | 3 - drivers/gpu/drm/drm_fb_helper.c | 4 + drivers/gpu/drm/drm_modes.c | 327 ++++++---- drivers/gpu/drm/imx/imx-drm-core.c | 1 + drivers/gpu/drm/panel/panel-simple.c | 26 + drivers/gpu/drm/sti/sti_drv.c | 1 + drivers/gpu/drm/sun4i/Kconfig | 14 + drivers/gpu/drm/sun4i/Makefile | 12 + drivers/gpu/drm/sun4i/sun4i_backend.c | 321 ++++++++++ drivers/gpu/drm/sun4i/sun4i_backend.h | 160 +++++ drivers/gpu/drm/sun4i/sun4i_crtc.c | 120 ++++ drivers/gpu/drm/sun4i/sun4i_crtc.h | 30 + drivers/gpu/drm/sun4i/sun4i_drv.c | 325 ++++++++++ drivers/gpu/drm/sun4i/sun4i_drv.h | 30 + drivers/gpu/drm/sun4i/sun4i_framebuffer.c | 54 ++ drivers/gpu/drm/sun4i/sun4i_framebuffer.h | 19 + drivers/gpu/drm/sun4i/sun4i_layer.c | 160 +++++ drivers/gpu/drm/sun4i/sun4i_layer.h | 30 + drivers/gpu/drm/sun4i/sun4i_rgb.c | 229 +++++++ drivers/gpu/drm/sun4i/sun4i_rgb.h | 18 + drivers/gpu/drm/sun4i/sun4i_tcon.c | 521 ++++++++++++++++ drivers/gpu/drm/sun4i/sun4i_tcon.h | 177 ++++++ drivers/gpu/drm/sun4i/sun4i_tv.c | 675 +++++++++++++++++++++ drivers/gpu/drm/tilcdc/tilcdc_drv.c | 1 + drivers/reset/core.c | 6 +- include/drm/drm_modes.h | 1 + include/linux/clk-provider.h | 55 +- include/linux/reset-controller.h | 2 +- 52 files changed, 4455 insertions(+), 179 deletions(-) create mode 100644 Documentation/devicetree/bindings/display/panel/olimex,lcd-olinuxino- 43-ts.txt create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt create mode 100644 drivers/clk/clk-io.c create mode 100644 drivers/clk/clk-io.h create mode 100644 drivers/clk/sunxi/clk-sun4i-display.c create mode 100644 drivers/clk/sunxi/clk-sun4i-pll3.c create mode 100644 drivers/clk/sunxi/clk-sun4i-tcon-ch1.c create mode 100644 drivers/gpu/drm/sun4i/Kconfig create mode 100644 drivers/gpu/drm/sun4i/Makefile create mode 100644 drivers/gpu/drm/sun4i/sun4i_backend.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_backend.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_crtc.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_crtc.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_drv.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_drv.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_framebuffer.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_framebuffer.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_layer.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_layer.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_rgb.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_rgb.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_tcon.c create mode 100644 drivers/gpu/drm/sun4i/sun4i_tcon.h create mode 100644 drivers/gpu/drm/sun4i/sun4i_tv.c
-- 2.6.4
dri-devel@lists.freedesktop.org