Hi,
Here is an preliminary version of the MIPI-DSI support for the Allwinner SoCs.
This controller can be found on a number of recent SoCs, such as the A31, A33 or the A64.
Given the sparse documentation, there's a number of obscure areas, but the current implementation has been tested with a 4-lanes DSI panel on an A33.
The support is a bit rough around the edges at the time, and some artifacts are still shown on the screen for some reasons. Wider testing with different display will hopefully nail those down.
Let me know what you think, Maxime
Changes from v1: - Rebased on 4.16-rc1 - Constified a few function arguments and structures - Reworked the DT binding example a bit - Reworked the panel driver to check for DSI return codes, and use DCS helpers when possible
Maxime Ripard (10): regmap: mmio: Add function to attach a clock drm/sun4i: tcon: Add TRI finish interrupt for vblank drm/sun4i: Protect the TCON pixel clocks dt-bindings: display: Add Allwinner MIPI-DSI bindings drm/sun4i: Add Allwinner A31 MIPI-DSI controller support dt-bindings: vendor: Add Huarui Lighting dt-bindings: panel: Add Huarui LHR050H41 panel documentation drm/panel: Add Huarui LHR050H41 panel driver arm: dts: sun8i: a33: Add the DSI-related nodes [DO NOT MERGE] arm: dts: sun8i: bpi-m2m: Add DSI display
Documentation/devicetree/bindings/display/panel/huarui,lhr050h41.txt | 19 +- Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt | 84 +++++- Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +- arch/arm/boot/dts/sun8i-a33.dtsi | 35 ++- arch/arm/boot/dts/sun8i-r16-bananapi-m2m.dts | 39 +++- drivers/base/regmap/regmap-mmio.c | 24 ++- drivers/gpu/drm/panel/Kconfig | 9 +- drivers/gpu/drm/panel/Makefile | 1 +- drivers/gpu/drm/panel/panel-huarui-lhr050h41.c | 506 +++++++++++++++++++++++++++++++++- drivers/gpu/drm/sun4i/Kconfig | 10 +- drivers/gpu/drm/sun4i/Makefile | 4 +- drivers/gpu/drm/sun4i/sun4i_tcon.c | 115 ++++++- drivers/gpu/drm/sun4i/sun4i_tcon.h | 46 +++- drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c | 297 +++++++++++++++++++- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 1111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h | 67 ++++- include/linux/regmap.h | 3 +- 17 files changed, 2366 insertions(+), 5 deletions(-) create mode 100644 Documentation/devicetree/bindings/display/panel/huarui,lhr050h41.txt create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt create mode 100644 drivers/gpu/drm/panel/panel-huarui-lhr050h41.c create mode 100644 drivers/gpu/drm/sun4i/sun6i_mipi_dphy.c create mode 100644 drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c create mode 100644 drivers/gpu/drm/sun4i/sun6i_mipi_dsi.h
base-commit: 91ab883eb21325ad80f3473633f794c78ac87f51
From: Maxime Ripard maxime.ripard@free-electrons.com
regmap_init_mmio_clk allows to specify a clock that needs to be enabled while accessing the registers.
However, that clock is retrieved through its clock ID, which means it will lookup that clock based on the current device that registers the regmap, and, in the DT case, will only look in that device OF node.
This might be problematic if the clock to enable is stored in another node. Let's add a function that allows to attach a clock that has already been retrieved to a regmap in order to fix this.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/base/regmap/regmap-mmio.c | 24 ++++++++++++++++++++++++ include/linux/regmap.h | 3 +++ 2 files changed, 27 insertions(+)
diff --git a/drivers/base/regmap/regmap-mmio.c b/drivers/base/regmap/regmap-mmio.c index 5189fd6182f6..5cadfd3394d8 100644 --- a/drivers/base/regmap/regmap-mmio.c +++ b/drivers/base/regmap/regmap-mmio.c @@ -28,6 +28,8 @@ struct regmap_mmio_context { void __iomem *regs; unsigned val_bytes; + + bool attached_clk; struct clk *clk;
void (*reg_write)(struct regmap_mmio_context *ctx, @@ -363,4 +365,26 @@ struct regmap *__devm_regmap_init_mmio_clk(struct device *dev, } EXPORT_SYMBOL_GPL(__devm_regmap_init_mmio_clk);
+int regmap_mmio_attach_clk(struct regmap *map, struct clk *clk) +{ + struct regmap_mmio_context *ctx = map->bus_context; + + ctx->clk = clk; + ctx->attached_clk = true; + + return clk_prepare(ctx->clk); +} +EXPORT_SYMBOL_GPL(regmap_mmio_attach_clk); + +void regmap_mmio_detach_clk(struct regmap *map) +{ + struct regmap_mmio_context *ctx = map->bus_context; + + clk_unprepare(ctx->clk); + + ctx->attached_clk = false; + ctx->clk = NULL; +} +EXPORT_SYMBOL_GPL(regmap_mmio_detach_clk); + MODULE_LICENSE("GPL v2"); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 6a3aeba40e9e..5f7ad0552c03 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -21,6 +21,7 @@ #include <linux/lockdep.h>
struct module; +struct clk; struct device; struct i2c_client; struct irq_domain; @@ -905,6 +906,8 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); __regmap_lockdep_wrapper(__devm_regmap_init_sdw, #config, \ sdw, config)
+int regmap_mmio_attach_clk(struct regmap *map, struct clk *clk); +void regmap_mmio_detach_clk(struct regmap *map); void regmap_exit(struct regmap *map); int regmap_reinit_cache(struct regmap *map, const struct regmap_config *config);
The patch
regmap: mmio: Add function to attach a clock
has been applied to the regmap tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 31895662f9ba81e8ea9ef05abf8edcb29d4b9c18 Mon Sep 17 00:00:00 2001
From: Maxime Ripard maxime.ripard@free-electrons.com Date: Wed, 21 Feb 2018 10:20:25 +0100 Subject: [PATCH] regmap: mmio: Add function to attach a clock
regmap_init_mmio_clk allows to specify a clock that needs to be enabled while accessing the registers.
However, that clock is retrieved through its clock ID, which means it will lookup that clock based on the current device that registers the regmap, and, in the DT case, will only look in that device OF node.
This might be problematic if the clock to enable is stored in another node. Let's add a function that allows to attach a clock that has already been retrieved to a regmap in order to fix this.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com Signed-off-by: Mark Brown broonie@kernel.org --- drivers/base/regmap/regmap-mmio.c | 24 ++++++++++++++++++++++++ include/linux/regmap.h | 3 +++ 2 files changed, 27 insertions(+)
diff --git a/drivers/base/regmap/regmap-mmio.c b/drivers/base/regmap/regmap-mmio.c index 5189fd6182f6..5cadfd3394d8 100644 --- a/drivers/base/regmap/regmap-mmio.c +++ b/drivers/base/regmap/regmap-mmio.c @@ -28,6 +28,8 @@ struct regmap_mmio_context { void __iomem *regs; unsigned val_bytes; + + bool attached_clk; struct clk *clk;
void (*reg_write)(struct regmap_mmio_context *ctx, @@ -363,4 +365,26 @@ struct regmap *__devm_regmap_init_mmio_clk(struct device *dev, } EXPORT_SYMBOL_GPL(__devm_regmap_init_mmio_clk);
+int regmap_mmio_attach_clk(struct regmap *map, struct clk *clk) +{ + struct regmap_mmio_context *ctx = map->bus_context; + + ctx->clk = clk; + ctx->attached_clk = true; + + return clk_prepare(ctx->clk); +} +EXPORT_SYMBOL_GPL(regmap_mmio_attach_clk); + +void regmap_mmio_detach_clk(struct regmap *map) +{ + struct regmap_mmio_context *ctx = map->bus_context; + + clk_unprepare(ctx->clk); + + ctx->attached_clk = false; + ctx->clk = NULL; +} +EXPORT_SYMBOL_GPL(regmap_mmio_detach_clk); + MODULE_LICENSE("GPL v2"); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 6a3aeba40e9e..5f7ad0552c03 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -21,6 +21,7 @@ #include <linux/lockdep.h>
struct module; +struct clk; struct device; struct i2c_client; struct irq_domain; @@ -905,6 +906,8 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); __regmap_lockdep_wrapper(__devm_regmap_init_sdw, #config, \ sdw, config)
+int regmap_mmio_attach_clk(struct regmap *map, struct clk *clk); +void regmap_mmio_detach_clk(struct regmap *map); void regmap_exit(struct regmap *map); int regmap_reinit_cache(struct regmap *map, const struct regmap_config *config);
On Wed, Feb 21, 2018 at 10:20:25AM +0100, Maxime Ripard wrote:
From: Maxime Ripard maxime.ripard@free-electrons.com
regmap_init_mmio_clk allows to specify a clock that needs to be enabled while accessing the registers.
The following changes since commit 7928b2cbe55b2a410a0f5c1f154610059c57b1b2:
Linux 4.16-rc1 (2018-02-11 15:04:29 -0800)
are available in the Git repository at:
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git tags/mmio-clk-config
for you to fetch changes up to 31895662f9ba81e8ea9ef05abf8edcb29d4b9c18:
regmap: mmio: Add function to attach a clock (2018-02-26 11:05:44 +0000)
---------------------------------------------------------------- regmap: MMIO regmap clock configuration
This patch provides a mechanism for specifying a different clock to be used with the regmap clock integration.
---------------------------------------------------------------- Maxime Ripard (1): regmap: mmio: Add function to attach a clock
drivers/base/regmap/regmap-mmio.c | 24 ++++++++++++++++++++++++ include/linux/regmap.h | 3 +++ 2 files changed, 27 insertions(+)
From: Maxime Ripard maxime.ripard@free-electrons.com
The "CPU" (or Intel 8080) interface uses a different interrupt called TRI_FINISH (most likely TRI being for trigger) to notify the end of frames, and hence the VBLANK period.
And that interrupt to the possible VBLANK interrupts source.
Reviewed-by: Chen-Yu Tsai wens@csie.org Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 9 ++++++--- drivers/gpu/drm/sun4i/sun4i_tcon.h | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 3c15cf24b503..b73acab74867 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -196,7 +196,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis");
mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) | - SUN4I_TCON_GINT0_VBLANK_ENABLE(1); + SUN4I_TCON_GINT0_VBLANK_ENABLE(1) | + SUN4I_TCON_GINT0_TCON0_TRI_FINISH_ENABLE;
if (enable) val = mask; @@ -545,7 +546,8 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private) regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status);
if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | - SUN4I_TCON_GINT0_VBLANK_INT(1)))) + SUN4I_TCON_GINT0_VBLANK_INT(1) | + SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT))) return IRQ_NONE;
drm_crtc_handle_vblank(&scrtc->crtc); @@ -554,7 +556,8 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private) /* Acknowledge the interrupt */ regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, SUN4I_TCON_GINT0_VBLANK_INT(0) | - SUN4I_TCON_GINT0_VBLANK_INT(1), + SUN4I_TCON_GINT0_VBLANK_INT(1) | + SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT, 0);
return IRQ_HANDLED; diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index b761c7b823c5..067183076807 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -28,7 +28,11 @@
#define SUN4I_TCON_GINT0_REG 0x4 #define SUN4I_TCON_GINT0_VBLANK_ENABLE(pipe) BIT(31 - (pipe)) +#define SUN4I_TCON_GINT0_TCON0_TRI_FINISH_ENABLE BIT(27) +#define SUN4I_TCON_GINT0_TCON0_TRI_COUNTER_ENABLE BIT(26) #define SUN4I_TCON_GINT0_VBLANK_INT(pipe) BIT(15 - (pipe)) +#define SUN4I_TCON_GINT0_TCON0_TRI_FINISH_INT BIT(11) +#define SUN4I_TCON_GINT0_TCON0_TRI_COUNTER_INT BIT(10)
#define SUN4I_TCON_GINT1_REG 0x8 #define SUN4I_TCON_FRM_CTL_REG 0x10
From: Maxime Ripard maxime.ripard@free-electrons.com
Both TCON clocks are very sensitive to clock changes, since any change might lead to improper timings.
Make sure our rate is never changed.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index b73acab74867..cbe87cee13d1 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -261,7 +261,7 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, const struct drm_display_mode *mode) { /* Configure the dot clock */ - clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + clk_set_rate_exclusive(tcon->dclk, mode->crtc_clock * 1000);
/* Set the resolution */ regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, @@ -419,7 +419,7 @@ static void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, WARN_ON(!tcon->quirks->has_channel_1);
/* Configure the dot clock */ - clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); + clk_set_rate_exclusive(tcon->sclk1, mode->crtc_clock * 1000);
/* Adjust clock delay */ clk_delay = sun4i_tcon_get_clk_delay(mode, 1);
From: Maxime Ripard maxime.ripard@free-electrons.com
Both TCON clocks are very sensitive to clock changes, since any change might lead to improper timings.
Make sure our rate is never changed.
Tested-by: Giulio Benetti giulio.benetti@micronovasrl.com Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index b73acab74867..cbe87cee13d1 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -261,7 +261,7 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, const struct drm_display_mode *mode) { /* Configure the dot clock */ - clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + clk_set_rate_exclusive(tcon->dclk, mode->crtc_clock * 1000);
/* Set the resolution */ regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, @@ -419,7 +419,7 @@ static void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, WARN_ON(!tcon->quirks->has_channel_1);
/* Configure the dot clock */ - clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); + clk_set_rate_exclusive(tcon->sclk1, mode->crtc_clock * 1000);
/* Adjust clock delay */ clk_delay = sun4i_tcon_get_clk_delay(mode, 1);
Il 21/02/2018 10:20, Maxime Ripard ha scritto:
From: Maxime Ripard maxime.ripard@free-electrons.com
Both TCON clocks are very sensitive to clock changes, since any change might lead to improper timings.
Make sure our rate is never changed.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Tested-by: Giulio Benetti giulio.benetti@micronovasrl.com
Tested on A20-LiNova1-4_3i-ctp with Mali r6p2 installed. Before Mali changed dotclock, now it doesn't anymore.
drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index b73acab74867..cbe87cee13d1 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -261,7 +261,7 @@ static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon, const struct drm_display_mode *mode) { /* Configure the dot clock */
- clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
clk_set_rate_exclusive(tcon->dclk, mode->crtc_clock * 1000);
/* Set the resolution */ regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG,
@@ -419,7 +419,7 @@ static void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, WARN_ON(!tcon->quirks->has_channel_1);
/* Configure the dot clock */
- clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
clk_set_rate_exclusive(tcon->sclk1, mode->crtc_clock * 1000);
/* Adjust clock delay */ clk_delay = sun4i_tcon_get_clk_delay(mode, 1);
On Sat, Feb 24, 2018 at 12:31:36AM +0100, Giulio Benetti wrote:
Il 21/02/2018 10:20, Maxime Ripard ha scritto:
From: Maxime Ripard maxime.ripard@free-electrons.com
Both TCON clocks are very sensitive to clock changes, since any change might lead to improper timings.
Make sure our rate is never changed.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Tested-by: Giulio Benetti giulio.benetti@micronovasrl.com
Tested on A20-LiNova1-4_3i-ctp with Mali r6p2 installed. Before Mali changed dotclock, now it doesn't anymore.
Applied, thanks! Maxime
From: Maxime Ripard maxime.ripard@free-electrons.com
The Allwinner SoCs usually come with a DSI encoder. Add a binding for it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt | 84 +++++++- 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt b/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt new file mode 100644 index 000000000000..cbcc673b9bcc --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt @@ -0,0 +1,84 @@ +Allwinner A31 DSI Encoder +========================= + +The DSI pipeline consists of two separate blocks: the DSI controller +itself, and its associated D-PHY. + +DSI Encoder +----------- + +The DSI Encoder generates the DSI signal from the TCON's. + +Required properties: + - compatible: value must be one of: + * allwinner,sun6i-a31-mipi-dsi + - reg: base address and size of memory-mapped region + - interrupts: interrupt associated to this IP + - clocks: phandles to the clocks feeding the DSI encoder + * bus: the DSI interface clock + * mod: the DSI module clock + - clock-names: the clock names mentioned above + - phys: phandle to the D-PHY + - phy-names: must be "dphy" + - resets: phandle to the reset controller driving the encoder + + - ports: A ports node with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. The + port should be the input endpoint, usually coming from the + associated TCON. + +Any MIPI-DSI device attached to this should be described according to +the bindings defined in ../mipi-dsi-bus.txt + +D-PHY +----- + +Required properties: + - compatible: value must be one of: + * allwinner,sun6i-a31-mipi-dphy + - reg: base address and size of memory-mapped region + - clocks: phandles to the clocks feeding the DSI encoder + * bus: the DSI interface clock + * mod: the DSI module clock + - clock-names: the clock names mentioned above + - resets: phandle to the reset controller driving the encoder + +Example: + +dsi0: dsi@1ca0000 { + compatible = "allwinner,sun6i-a31-mipi-dsi"; + reg = <0x01ca0000 0x1000>; + interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&ccu CLK_BUS_MIPI_DSI>, + <&ccu CLK_DSI_SCLK>; + clock-names = "bus", "mod"; + resets = <&ccu RST_BUS_MIPI_DSI>; + phys = <&dphy0>; + phy-names = "dphy"; + #address-cells = <1>; + #size-cells = <0>; + + panel@0 { + compatible = "huarui,lhr050h41"; + reg = <0>; + power-gpios = <&pio 1 7 GPIO_ACTIVE_HIGH>; /* PB07 */ + reset-gpios = <&r_pio 0 5 GPIO_ACTIVE_LOW>; /* PL05 */ + backlight = <&pwm_bl>; + }; + + port { + dsi0_in_tcon0: endpoint { + remote-endpoint = <&tcon0_out_dsi0>; + }; + }; +}; + +dphy0: d-phy@1ca1000 { + compatible = "allwinner,sun6i-a31-mipi-dphy"; + reg = <0x01ca1000 0x1000>; + clocks = <&ccu CLK_BUS_MIPI_DSI>, + <&ccu CLK_DSI_DPHY>; + clock-names = "bus", "mod"; + resets = <&ccu RST_BUS_MIPI_DSI>; + #phy-cells = <0>; +};
On Wed, Feb 21, 2018 at 10:20:28AM +0100, Maxime Ripard wrote:
From: Maxime Ripard maxime.ripard@free-electrons.com
The Allwinner SoCs usually come with a DSI encoder. Add a binding for it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt | 84 +++++++- 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt b/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt new file mode 100644 index 000000000000..cbcc673b9bcc --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt @@ -0,0 +1,84 @@ +Allwinner A31 DSI Encoder +=========================
+The DSI pipeline consists of two separate blocks: the DSI controller +itself, and its associated D-PHY.
+DSI Encoder +-----------
+The DSI Encoder generates the DSI signal from the TCON's.
+Required properties:
- compatible: value must be one of:
- allwinner,sun6i-a31-mipi-dsi
- reg: base address and size of memory-mapped region
- interrupts: interrupt associated to this IP
- clocks: phandles to the clocks feeding the DSI encoder
- bus: the DSI interface clock
- mod: the DSI module clock
- clock-names: the clock names mentioned above
- phys: phandle to the D-PHY
- phy-names: must be "dphy"
- resets: phandle to the reset controller driving the encoder
- ports: A ports node with endpoint definitions as defined in
- Documentation/devicetree/bindings/media/video-interfaces.txt. The
- port should be the input endpoint, usually coming from the
- associated TCON.
+Any MIPI-DSI device attached to this should be described according to +the bindings defined in ../mipi-dsi-bus.txt
+D-PHY +-----
+Required properties:
- compatible: value must be one of:
- allwinner,sun6i-a31-mipi-dphy
- reg: base address and size of memory-mapped region
- clocks: phandles to the clocks feeding the DSI encoder
- bus: the DSI interface clock
- mod: the DSI module clock
- clock-names: the clock names mentioned above
- resets: phandle to the reset controller driving the encoder
+Example:
+dsi0: dsi@1ca0000 {
- compatible = "allwinner,sun6i-a31-mipi-dsi";
- reg = <0x01ca0000 0x1000>;
- interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&ccu CLK_BUS_MIPI_DSI>,
<&ccu CLK_DSI_SCLK>;
- clock-names = "bus", "mod";
- resets = <&ccu RST_BUS_MIPI_DSI>;
- phys = <&dphy0>;
- phy-names = "dphy";
- #address-cells = <1>;
- #size-cells = <0>;
- panel@0 {
compatible = "huarui,lhr050h41";
reg = <0>;
power-gpios = <&pio 1 7 GPIO_ACTIVE_HIGH>; /* PB07 */
reset-gpios = <&r_pio 0 5 GPIO_ACTIVE_LOW>; /* PL05 */
backlight = <&pwm_bl>;
- };
- port {
dsi0_in_tcon0: endpoint {
remote-endpoint = <&tcon0_out_dsi0>;
};
- };
Kind of odd to have a mixture of panel and port. I guess that's valid though. I'd recommend you put port under ports node so if you ever have another port you are not mixing unit-address spaces.
With that,
Reviewed-by: Rob Herring robh@kernel.org
+};
+dphy0: d-phy@1ca1000 {
- compatible = "allwinner,sun6i-a31-mipi-dphy";
- reg = <0x01ca1000 0x1000>;
- clocks = <&ccu CLK_BUS_MIPI_DSI>,
<&ccu CLK_DSI_DPHY>;
- clock-names = "bus", "mod";
- resets = <&ccu RST_BUS_MIPI_DSI>;
- #phy-cells = <0>;
+};
git-series 0.9.1
Hi Rob,
On Thu, Mar 01, 2018 at 04:15:43PM -0600, Rob Herring wrote:
On Wed, Feb 21, 2018 at 10:20:28AM +0100, Maxime Ripard wrote:
From: Maxime Ripard maxime.ripard@free-electrons.com
The Allwinner SoCs usually come with a DSI encoder. Add a binding for it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt | 84 +++++++- 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt b/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt new file mode 100644 index 000000000000..cbcc673b9bcc --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sun6i-dsi.txt @@ -0,0 +1,84 @@ +Allwinner A31 DSI Encoder +=========================
+The DSI pipeline consists of two separate blocks: the DSI controller +itself, and its associated D-PHY.
+DSI Encoder +-----------
+The DSI Encoder generates the DSI signal from the TCON's.
+Required properties:
- compatible: value must be one of:
- allwinner,sun6i-a31-mipi-dsi
- reg: base address and size of memory-mapped region
- interrupts: interrupt associated to this IP
- clocks: phandles to the clocks feeding the DSI encoder
- bus: the DSI interface clock
- mod: the DSI module clock
- clock-names: the clock names mentioned above
- phys: phandle to the D-PHY
- phy-names: must be "dphy"
- resets: phandle to the reset controller driving the encoder
- ports: A ports node with endpoint definitions as defined in
- Documentation/devicetree/bindings/media/video-interfaces.txt. The
- port should be the input endpoint, usually coming from the
- associated TCON.
+Any MIPI-DSI device attached to this should be described according to +the bindings defined in ../mipi-dsi-bus.txt
+D-PHY +-----
+Required properties:
- compatible: value must be one of:
- allwinner,sun6i-a31-mipi-dphy
- reg: base address and size of memory-mapped region
- clocks: phandles to the clocks feeding the DSI encoder
- bus: the DSI interface clock
- mod: the DSI module clock
- clock-names: the clock names mentioned above
- resets: phandle to the reset controller driving the encoder
+Example:
+dsi0: dsi@1ca0000 {
- compatible = "allwinner,sun6i-a31-mipi-dsi";
- reg = <0x01ca0000 0x1000>;
- interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&ccu CLK_BUS_MIPI_DSI>,
<&ccu CLK_DSI_SCLK>;
- clock-names = "bus", "mod";
- resets = <&ccu RST_BUS_MIPI_DSI>;
- phys = <&dphy0>;
- phy-names = "dphy";
- #address-cells = <1>;
- #size-cells = <0>;
- panel@0 {
compatible = "huarui,lhr050h41";
reg = <0>;
power-gpios = <&pio 1 7 GPIO_ACTIVE_HIGH>; /* PB07 */
reset-gpios = <&r_pio 0 5 GPIO_ACTIVE_LOW>; /* PL05 */
backlight = <&pwm_bl>;
- };
- port {
dsi0_in_tcon0: endpoint {
remote-endpoint = <&tcon0_out_dsi0>;
};
- };
Kind of odd to have a mixture of panel and port. I guess that's valid though.
I guess the most elegant way would have been to have the panel also part of the OF graph, but we have to conform to the generic DSI panels as well :)
I'd recommend you put port under ports node so if you ever have another port you are not mixing unit-address spaces.
Ok, I'll do that.
With that,
Reviewed-by: Rob Herring robh@kernel.org
Thanks! Maxime
From: Maxime Ripard maxime.ripard@free-electrons.com
The LHR050H41 is a 1280x700 4-lanes DSI panel.
Acked-by: Rob Herring robh@kernel.org Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- Documentation/devicetree/bindings/display/panel/huarui,lhr050h41.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/panel/huarui,lhr050h41.txt
diff --git a/Documentation/devicetree/bindings/display/panel/huarui,lhr050h41.txt b/Documentation/devicetree/bindings/display/panel/huarui,lhr050h41.txt new file mode 100644 index 000000000000..b254db4d29c5 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/huarui,lhr050h41.txt @@ -0,0 +1,19 @@ +Huarui LHR050H41 MIPI-DSI panel + +Required properties: + - compatible: "huarui,lhr050h41" + - reg: DSI virtual channel used by that screen + - power-gpios: a GPIO phandle for the power pin + - reset-gpios: a GPIO phandle for the reset pin + +Optional properties: + - backlight: phandle to the backlight used + +Example: +panel@0 { + compatible = "huarui,lhr050h41"; + reg = <0>; + power-gpios = <&pio 1 7 GPIO_ACTIVE_HIGH>; /* PB07 */ + reset-gpios = <&r_pio 0 5 GPIO_ACTIVE_LOW>; /* PL05 */ + backlight = <&pwm_bl>; +};
From: Maxime Ripard maxime.ripard@free-electrons.com
The LHR050H41 panel is the panel shipped with the BananaPi M2-Magic. Add a driver for it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- drivers/gpu/drm/panel/Kconfig | 9 +- drivers/gpu/drm/panel/Makefile | 1 +- drivers/gpu/drm/panel/panel-huarui-lhr050h41.c | 506 ++++++++++++++++++- 3 files changed, 516 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-huarui-lhr050h41.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 6ba4031f3919..965310fd129a 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -28,6 +28,15 @@ config DRM_PANEL_SIMPLE that it can be automatically turned off when the panel goes into a low power state.
+config DRM_PANEL_HUARUI_LHR050H41 + tristate "Huarui LHR050H41 panel" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y if you want to enable support for the Huarui Lighting + LHR05041 DSI panel. The panel has a 1280x720 resolution. + config DRM_PANEL_ILITEK_IL9322 tristate "Ilitek ILI9322 320x240 QVGA panels" depends on OF && SPI diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 6d251ebc568c..0eda133d26bb 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o +obj-$(CONFIG_DRM_PANEL_HUARUI_LHR050H41) += panel-huarui-lhr050h41.o obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o obj-$(CONFIG_DRM_PANEL_INNOLUX_P079ZCA) += panel-innolux-p079zca.o obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o diff --git a/drivers/gpu/drm/panel/panel-huarui-lhr050h41.c b/drivers/gpu/drm/panel/panel-huarui-lhr050h41.c new file mode 100644 index 000000000000..f73d484a695b --- /dev/null +++ b/drivers/gpu/drm/panel/panel-huarui-lhr050h41.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017, Free Electrons + * Author: Maxime Ripard maxime.ripard@free-electrons.com + */ + +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include <linux/gpio/consumer.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +struct lhr050h41 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct backlight_device *backlight; + struct gpio_desc *power; + struct gpio_desc *reset; +}; + +enum lhr050h41_op { + LHR050H41_SWITCH_PAGE, + LHR050H41_COMMAND, +}; + +struct lhr050h41_instr { + enum lhr050h41_op op; + + union arg { + struct cmd { + u8 cmd; + u8 data; + } cmd; + u8 page; + } arg; +}; + +#define LHR050H41_SWITCH_PAGE_INSTR(_page) \ + { \ + .op = LHR050H41_SWITCH_PAGE, \ + .arg = { \ + .page = (_page), \ + }, \ + } + +#define LHR050H41_COMMAND_INSTR(_cmd, _data) \ + { \ + .op = LHR050H41_COMMAND, \ + .arg = { \ + .cmd = { \ + .cmd = (_cmd), \ + .data = (_data), \ + }, \ + }, \ + } + +struct lhr050h41_instr lhr050h41_init[] = { + LHR050H41_SWITCH_PAGE_INSTR(3), + LHR050H41_COMMAND_INSTR(0x01, 0x00), + LHR050H41_COMMAND_INSTR(0x02, 0x00), + LHR050H41_COMMAND_INSTR(0x03, 0x73), + LHR050H41_COMMAND_INSTR(0x04, 0x03), + LHR050H41_COMMAND_INSTR(0x05, 0x00), + LHR050H41_COMMAND_INSTR(0x06, 0x06), + LHR050H41_COMMAND_INSTR(0x07, 0x06), + LHR050H41_COMMAND_INSTR(0x08, 0x00), + LHR050H41_COMMAND_INSTR(0x09, 0x18), + LHR050H41_COMMAND_INSTR(0x0a, 0x04), + LHR050H41_COMMAND_INSTR(0x0b, 0x00), + LHR050H41_COMMAND_INSTR(0x0c, 0x02), + LHR050H41_COMMAND_INSTR(0x0d, 0x03), + LHR050H41_COMMAND_INSTR(0x0e, 0x00), + LHR050H41_COMMAND_INSTR(0x0f, 0x25), + LHR050H41_COMMAND_INSTR(0x10, 0x25), + LHR050H41_COMMAND_INSTR(0x11, 0x00), + LHR050H41_COMMAND_INSTR(0x12, 0x00), + LHR050H41_COMMAND_INSTR(0x13, 0x00), + LHR050H41_COMMAND_INSTR(0x14, 0x00), + LHR050H41_COMMAND_INSTR(0x15, 0x00), + LHR050H41_COMMAND_INSTR(0x16, 0x0C), + LHR050H41_COMMAND_INSTR(0x17, 0x00), + LHR050H41_COMMAND_INSTR(0x18, 0x00), + LHR050H41_COMMAND_INSTR(0x19, 0x00), + LHR050H41_COMMAND_INSTR(0x1a, 0x00), + LHR050H41_COMMAND_INSTR(0x1b, 0x00), + LHR050H41_COMMAND_INSTR(0x1c, 0x00), + LHR050H41_COMMAND_INSTR(0x1d, 0x00), + LHR050H41_COMMAND_INSTR(0x1e, 0xC0), + LHR050H41_COMMAND_INSTR(0x1f, 0x80), + LHR050H41_COMMAND_INSTR(0x20, 0x04), + LHR050H41_COMMAND_INSTR(0x21, 0x01), + LHR050H41_COMMAND_INSTR(0x22, 0x00), + LHR050H41_COMMAND_INSTR(0x23, 0x00), + LHR050H41_COMMAND_INSTR(0x24, 0x00), + LHR050H41_COMMAND_INSTR(0x25, 0x00), + LHR050H41_COMMAND_INSTR(0x26, 0x00), + LHR050H41_COMMAND_INSTR(0x27, 0x00), + LHR050H41_COMMAND_INSTR(0x28, 0x33), + LHR050H41_COMMAND_INSTR(0x29, 0x03), + LHR050H41_COMMAND_INSTR(0x2a, 0x00), + LHR050H41_COMMAND_INSTR(0x2b, 0x00), + LHR050H41_COMMAND_INSTR(0x2c, 0x00), + LHR050H41_COMMAND_INSTR(0x2d, 0x00), + LHR050H41_COMMAND_INSTR(0x2e, 0x00), + LHR050H41_COMMAND_INSTR(0x2f, 0x00), + LHR050H41_COMMAND_INSTR(0x30, 0x00), + LHR050H41_COMMAND_INSTR(0x31, 0x00), + LHR050H41_COMMAND_INSTR(0x32, 0x00), + LHR050H41_COMMAND_INSTR(0x33, 0x00), + LHR050H41_COMMAND_INSTR(0x34, 0x04), + LHR050H41_COMMAND_INSTR(0x35, 0x00), + LHR050H41_COMMAND_INSTR(0x36, 0x00), + LHR050H41_COMMAND_INSTR(0x37, 0x00), + LHR050H41_COMMAND_INSTR(0x38, 0x3C), + LHR050H41_COMMAND_INSTR(0x39, 0x00), + LHR050H41_COMMAND_INSTR(0x3a, 0x00), + LHR050H41_COMMAND_INSTR(0x3b, 0x00), + LHR050H41_COMMAND_INSTR(0x3c, 0x00), + LHR050H41_COMMAND_INSTR(0x3d, 0x00), + LHR050H41_COMMAND_INSTR(0x3e, 0x00), + LHR050H41_COMMAND_INSTR(0x3f, 0x00), + LHR050H41_COMMAND_INSTR(0x40, 0x00), + LHR050H41_COMMAND_INSTR(0x41, 0x00), + LHR050H41_COMMAND_INSTR(0x42, 0x00), + LHR050H41_COMMAND_INSTR(0x43, 0x00), + LHR050H41_COMMAND_INSTR(0x44, 0x00), + LHR050H41_COMMAND_INSTR(0x50, 0x01), + LHR050H41_COMMAND_INSTR(0x51, 0x23), + LHR050H41_COMMAND_INSTR(0x52, 0x45), + LHR050H41_COMMAND_INSTR(0x53, 0x67), + LHR050H41_COMMAND_INSTR(0x54, 0x89), + LHR050H41_COMMAND_INSTR(0x55, 0xab), + LHR050H41_COMMAND_INSTR(0x56, 0x01), + LHR050H41_COMMAND_INSTR(0x57, 0x23), + LHR050H41_COMMAND_INSTR(0x58, 0x45), + LHR050H41_COMMAND_INSTR(0x59, 0x67), + LHR050H41_COMMAND_INSTR(0x5a, 0x89), + LHR050H41_COMMAND_INSTR(0x5b, 0xab), + LHR050H41_COMMAND_INSTR(0x5c, 0xcd), + LHR050H41_COMMAND_INSTR(0x5d, 0xef), + LHR050H41_COMMAND_INSTR(0x5e, 0x11), + LHR050H41_COMMAND_INSTR(0x5f, 0x02), + LHR050H41_COMMAND_INSTR(0x60, 0x02), + LHR050H41_COMMAND_INSTR(0x61, 0x02), + LHR050H41_COMMAND_INSTR(0x62, 0x02), + LHR050H41_COMMAND_INSTR(0x63, 0x02), + LHR050H41_COMMAND_INSTR(0x64, 0x02), + LHR050H41_COMMAND_INSTR(0x65, 0x02), + LHR050H41_COMMAND_INSTR(0x66, 0x02), + LHR050H41_COMMAND_INSTR(0x67, 0x02), + LHR050H41_COMMAND_INSTR(0x68, 0x02), + LHR050H41_COMMAND_INSTR(0x69, 0x02), + LHR050H41_COMMAND_INSTR(0x6a, 0x0C), + LHR050H41_COMMAND_INSTR(0x6b, 0x02), + LHR050H41_COMMAND_INSTR(0x6c, 0x0F), + LHR050H41_COMMAND_INSTR(0x6d, 0x0E), + LHR050H41_COMMAND_INSTR(0x6e, 0x0D), + LHR050H41_COMMAND_INSTR(0x6f, 0x06), + LHR050H41_COMMAND_INSTR(0x70, 0x07), + LHR050H41_COMMAND_INSTR(0x71, 0x02), + LHR050H41_COMMAND_INSTR(0x72, 0x02), + LHR050H41_COMMAND_INSTR(0x73, 0x02), + LHR050H41_COMMAND_INSTR(0x74, 0x02), + LHR050H41_COMMAND_INSTR(0x75, 0x02), + LHR050H41_COMMAND_INSTR(0x76, 0x02), + LHR050H41_COMMAND_INSTR(0x77, 0x02), + LHR050H41_COMMAND_INSTR(0x78, 0x02), + LHR050H41_COMMAND_INSTR(0x79, 0x02), + LHR050H41_COMMAND_INSTR(0x7a, 0x02), + LHR050H41_COMMAND_INSTR(0x7b, 0x02), + LHR050H41_COMMAND_INSTR(0x7c, 0x02), + LHR050H41_COMMAND_INSTR(0x7d, 0x02), + LHR050H41_COMMAND_INSTR(0x7e, 0x02), + LHR050H41_COMMAND_INSTR(0x7f, 0x02), + LHR050H41_COMMAND_INSTR(0x80, 0x0C), + LHR050H41_COMMAND_INSTR(0x81, 0x02), + LHR050H41_COMMAND_INSTR(0x82, 0x0F), + LHR050H41_COMMAND_INSTR(0x83, 0x0E), + LHR050H41_COMMAND_INSTR(0x84, 0x0D), + LHR050H41_COMMAND_INSTR(0x85, 0x06), + LHR050H41_COMMAND_INSTR(0x86, 0x07), + LHR050H41_COMMAND_INSTR(0x87, 0x02), + LHR050H41_COMMAND_INSTR(0x88, 0x02), + LHR050H41_COMMAND_INSTR(0x89, 0x02), + LHR050H41_COMMAND_INSTR(0x8A, 0x02), + LHR050H41_SWITCH_PAGE_INSTR(4), + LHR050H41_COMMAND_INSTR(0x6C, 0x15), + LHR050H41_COMMAND_INSTR(0x6E, 0x22), + LHR050H41_COMMAND_INSTR(0x6F, 0x33), + LHR050H41_COMMAND_INSTR(0x3A, 0xA4), + LHR050H41_COMMAND_INSTR(0x8D, 0x0D), + LHR050H41_COMMAND_INSTR(0x87, 0xBA), + LHR050H41_COMMAND_INSTR(0x26, 0x76), + LHR050H41_COMMAND_INSTR(0xB2, 0xD1), + LHR050H41_SWITCH_PAGE_INSTR(1), + LHR050H41_COMMAND_INSTR(0x22, 0x0A), + LHR050H41_COMMAND_INSTR(0x53, 0xDC), + LHR050H41_COMMAND_INSTR(0x55, 0xA7), + LHR050H41_COMMAND_INSTR(0x50, 0x78), + LHR050H41_COMMAND_INSTR(0x51, 0x78), + LHR050H41_COMMAND_INSTR(0x31, 0x02), + LHR050H41_COMMAND_INSTR(0x60, 0x14), + LHR050H41_COMMAND_INSTR(0xA0, 0x2A), + LHR050H41_COMMAND_INSTR(0xA1, 0x39), + LHR050H41_COMMAND_INSTR(0xA2, 0x46), + LHR050H41_COMMAND_INSTR(0xA3, 0x0e), + LHR050H41_COMMAND_INSTR(0xA4, 0x12), + LHR050H41_COMMAND_INSTR(0xA5, 0x25), + LHR050H41_COMMAND_INSTR(0xA6, 0x19), + LHR050H41_COMMAND_INSTR(0xA7, 0x1d), + LHR050H41_COMMAND_INSTR(0xA8, 0xa6), + LHR050H41_COMMAND_INSTR(0xA9, 0x1C), + LHR050H41_COMMAND_INSTR(0xAA, 0x29), + LHR050H41_COMMAND_INSTR(0xAB, 0x85), + LHR050H41_COMMAND_INSTR(0xAC, 0x1C), + LHR050H41_COMMAND_INSTR(0xAD, 0x1B), + LHR050H41_COMMAND_INSTR(0xAE, 0x51), + LHR050H41_COMMAND_INSTR(0xAF, 0x22), + LHR050H41_COMMAND_INSTR(0xB0, 0x2d), + LHR050H41_COMMAND_INSTR(0xB1, 0x4f), + LHR050H41_COMMAND_INSTR(0xB2, 0x59), + LHR050H41_COMMAND_INSTR(0xB3, 0x3F), + LHR050H41_COMMAND_INSTR(0xC0, 0x2A), + LHR050H41_COMMAND_INSTR(0xC1, 0x3a), + LHR050H41_COMMAND_INSTR(0xC2, 0x45), + LHR050H41_COMMAND_INSTR(0xC3, 0x0e), + LHR050H41_COMMAND_INSTR(0xC4, 0x11), + LHR050H41_COMMAND_INSTR(0xC5, 0x24), + LHR050H41_COMMAND_INSTR(0xC6, 0x1a), + LHR050H41_COMMAND_INSTR(0xC7, 0x1c), + LHR050H41_COMMAND_INSTR(0xC8, 0xaa), + LHR050H41_COMMAND_INSTR(0xC9, 0x1C), + LHR050H41_COMMAND_INSTR(0xCA, 0x29), + LHR050H41_COMMAND_INSTR(0xCB, 0x96), + LHR050H41_COMMAND_INSTR(0xCC, 0x1C), + LHR050H41_COMMAND_INSTR(0xCD, 0x1B), + LHR050H41_COMMAND_INSTR(0xCE, 0x51), + LHR050H41_COMMAND_INSTR(0xCF, 0x22), + LHR050H41_COMMAND_INSTR(0xD0, 0x2b), + LHR050H41_COMMAND_INSTR(0xD1, 0x4b), + LHR050H41_COMMAND_INSTR(0xD2, 0x59), + LHR050H41_COMMAND_INSTR(0xD3, 0x3F), +}; + +static inline struct lhr050h41 *panel_to_lhr050h41(struct drm_panel *panel) +{ + return container_of(panel, struct lhr050h41, panel); +} + +static int lhr050h41_switch_page(struct lhr050h41 *ctx, u8 page) +{ + u8 buf[4] = { 0xff, 0x98, 0x81, page }; + int ret; + + ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf)); + if (ret < 0) + return ret; + + return 0; +} + +static int lhr050h41_send_cmd_data(struct lhr050h41 *ctx, u8 cmd, u8 data) +{ + u8 buf[2] = { cmd, data }; + int ret; + + ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf)); + if (ret < 0) + return ret; + + return 0; +} + +static int lhr050h41_prepare(struct drm_panel *panel) +{ + struct lhr050h41 *ctx = panel_to_lhr050h41(panel); + int i, ret; + + /* Power the panel */ + gpiod_set_value(ctx->power, 1); + mdelay(5); + + /* And reset it */ + gpiod_set_value(ctx->reset, 1); + mdelay(20); + + gpiod_set_value(ctx->reset, 0); + mdelay(20); + + for (i = 0; i < ARRAY_SIZE(lhr050h41_init); i++) { + struct lhr050h41_instr *instr = &lhr050h41_init[i]; + + if (instr->op == LHR050H41_SWITCH_PAGE) + ret = lhr050h41_switch_page(ctx, instr->arg.page); + else if (instr->op == LHR050H41_COMMAND) + ret = lhr050h41_send_cmd_data(ctx, instr->arg.cmd.cmd, + instr->arg.cmd.data); + + if (ret) + return ret; + } + + ret = lhr050h41_switch_page(ctx, 0); + if (ret) + return ret; + + ret = mipi_dsi_dcs_set_tear_on(ctx->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK); + if (ret) + return ret; + + mipi_dsi_dcs_exit_sleep_mode(ctx->dsi); + if (ret) + return ret; + + return 0; +} + +static void lhr050h41_enable_bl(struct lhr050h41 *ctx, bool enable) +{ + if (!ctx->backlight) + return; + + if (enable) { + ctx->backlight->props.state &= ~BL_CORE_FBBLANK; + ctx->backlight->props.power = FB_BLANK_UNBLANK; + } else { + ctx->backlight->props.power = FB_BLANK_POWERDOWN; + ctx->backlight->props.state |= BL_CORE_FBBLANK; + } + + backlight_update_status(ctx->backlight); +} + +static int lhr050h41_enable(struct drm_panel *panel) +{ + struct lhr050h41 *ctx = panel_to_lhr050h41(panel); + + mdelay(120); + + mipi_dsi_dcs_set_display_on(ctx->dsi); + + lhr050h41_enable_bl(ctx, true); + + return 0; +} + +static int lhr050h41_disable(struct drm_panel *panel) +{ + struct lhr050h41 *ctx = panel_to_lhr050h41(panel); + + lhr050h41_enable_bl(ctx, false); + + return mipi_dsi_dcs_set_display_off(ctx->dsi); +} + +static int lhr050h41_unprepare(struct drm_panel *panel) +{ + struct lhr050h41 *ctx = panel_to_lhr050h41(panel); + + mipi_dsi_dcs_enter_sleep_mode(ctx->dsi); + gpiod_set_value(ctx->power, 0); + gpiod_set_value(ctx->reset, 1); + + return 0; +} + +static const struct drm_display_mode default_mode = { + .clock = 62000, + .vrefresh = 60, + + .hdisplay = 720, + .hsync_start = 720 + 10, + .hsync_end = 720 + 10 + 20, + .htotal = 720 + 10 + 20 + 30, + + .vdisplay = 1280, + .vsync_start = 1280 + 10, + .vsync_end = 1280 + 10 + 10, + .vtotal = 1280 + 10 + 10 + 20, +}; + +static int lhr050h41_get_modes(struct drm_panel *panel) +{ + struct drm_connector *connector = panel->connector; + struct lhr050h41 *ctx = panel_to_lhr050h41(panel); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(panel->drm, &default_mode); + if (!mode) { + dev_err(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n", + default_mode.hdisplay, default_mode.vdisplay, + default_mode.vrefresh); + return -ENOMEM; + } + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + panel->connector->display_info.width_mm = 62; + panel->connector->display_info.height_mm = 110; + + return 1; +} + +static const struct drm_panel_funcs lhr050h41_funcs = { + .prepare = lhr050h41_prepare, + .unprepare = lhr050h41_unprepare, + .enable = lhr050h41_enable, + .disable = lhr050h41_disable, + .get_modes = lhr050h41_get_modes, +}; + +static int lhr050h41_dsi_probe(struct mipi_dsi_device *dsi) +{ + struct device_node *np; + struct lhr050h41 *ctx; + int ret; + + ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + mipi_dsi_set_drvdata(dsi, ctx); + ctx->dsi = dsi; + + drm_panel_init(&ctx->panel); + ctx->panel.dev = &dsi->dev; + ctx->panel.funcs = &lhr050h41_funcs; + + ctx->power = devm_gpiod_get(&dsi->dev, "power", GPIOD_OUT_LOW); + if (IS_ERR(ctx->power)) { + dev_err(&dsi->dev, "Couldn't get our power GPIO\n"); + return PTR_ERR(ctx->power); + } + + ctx->reset = devm_gpiod_get(&dsi->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->reset)) { + dev_err(&dsi->dev, "Couldn't get our reset GPIO\n"); + return PTR_ERR(ctx->reset); + } + + np = of_parse_phandle(dsi->dev.of_node, "backlight", 0); + if (np) { + ctx->backlight = of_find_backlight_by_node(np); + of_node_put(np); + + if (!ctx->backlight) + return -EPROBE_DEFER; + } + + ret = drm_panel_add(&ctx->panel); + if (ret < 0) + return ret; + + dsi->mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = 4; + + return mipi_dsi_attach(dsi); +} + +static int lhr050h41_dsi_remove(struct mipi_dsi_device *dsi) +{ + struct lhr050h41 *ctx = mipi_dsi_get_drvdata(dsi); + + mipi_dsi_detach(dsi); + drm_panel_remove(&ctx->panel); + + if (ctx->backlight) + put_device(&ctx->backlight->dev); + + return 0; +} + +static const struct of_device_id lhr050h41_of_match[] = { + { .compatible = "huarui,lhr050h41" }, + { } +}; +MODULE_DEVICE_TABLE(of, lhr050h41_of_match); + +static struct mipi_dsi_driver lhr050h41_dsi_driver = { + .probe = lhr050h41_dsi_probe, + .remove = lhr050h41_dsi_remove, + .driver = { + .name = "lhr050h41-dsi", + .of_match_table = lhr050h41_of_match, + }, +}; +module_mipi_dsi_driver(lhr050h41_dsi_driver); + +MODULE_AUTHOR("Maxime Ripard maxime.ripard@free-electrons.com"); +MODULE_DESCRIPTION("Huarui LHR050H41 LCD Driver"); +MODULE_LICENSE("GPL v2");
Hi Maxime,
I love your patch! Perhaps something to improve:
[auto build test WARNING on ]
url: https://github.com/0day-ci/linux/commits/Maxime-Ripard/drm-sun4i-Allwinner-M... base: reproduce: # apt-get install sparse make ARCH=x86_64 allmodconfig make C=1 CF=-D__CHECK_ENDIAN__
sparse warnings: (new ones prefixed by >>)
drivers/gpu/drm/panel/panel-huarui-lhr050h41.c:69:24: sparse: symbol 'lhr050h41_init' was not declared. Should it be
Please review and possibly fold the followup patch.
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Fixes: dcfac3b68dfb ("drm/panel: Add Huarui LHR050H41 panel driver") Signed-off-by: Fengguang Wu fengguang.wu@intel.com --- panel-huarui-lhr050h41.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/panel/panel-huarui-lhr050h41.c b/drivers/gpu/drm/panel/panel-huarui-lhr050h41.c index f73d484..f71030e 100644 --- a/drivers/gpu/drm/panel/panel-huarui-lhr050h41.c +++ b/drivers/gpu/drm/panel/panel-huarui-lhr050h41.c @@ -66,7 +66,7 @@ struct lhr050h41_instr { }, \ }
-struct lhr050h41_instr lhr050h41_init[] = { +static struct lhr050h41_instr lhr050h41_init[] = { LHR050H41_SWITCH_PAGE_INSTR(3), LHR050H41_COMMAND_INSTR(0x01, 0x00), LHR050H41_COMMAND_INSTR(0x02, 0x00),
Hi,
On Wed, Feb 21, 2018 at 5:20 PM, Maxime Ripard maxime.ripard@bootlin.com wrote:
From: Maxime Ripard maxime.ripard@free-electrons.com
The LHR050H41 panel is the panel shipped with the BananaPi M2-Magic. Add a driver for it.
So I distinctly remember questioning the vendor name the first time. I would just use Bananapi as the vendor name instead.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com
drivers/gpu/drm/panel/Kconfig | 9 +- drivers/gpu/drm/panel/Makefile | 1 +- drivers/gpu/drm/panel/panel-huarui-lhr050h41.c | 506 ++++++++++++++++++- 3 files changed, 516 insertions(+) create mode 100644 drivers/gpu/drm/panel/panel-huarui-lhr050h41.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 6ba4031f3919..965310fd129a 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -28,6 +28,15 @@ config DRM_PANEL_SIMPLE that it can be automatically turned off when the panel goes into a low power state.
+config DRM_PANEL_HUARUI_LHR050H41
tristate "Huarui LHR050H41 panel"
depends on OF
depends on DRM_MIPI_DSI
depends on BACKLIGHT_CLASS_DEVICE
help
Say Y if you want to enable support for the Huarui Lighting
LHR05041 DSI panel. The panel has a 1280x720 resolution.
And it seems this panel is driven by an ILI9881C from Ilitek. So maybe you could make the panel driver more like the IL9322, as in having common code for the driver IC, then a data structure tied to actual panel compatible strings to handle any quirks.
The datasheet can be found simply by googling the part ID, or here:
http://en.startek-lcd.com/res/starteklcden/pdres/201706/20170617115241070.pd...
This should help with the init command sequence.
I also found this:
http://www.ampdisplay.com/documents/pdf/AM-7201280ETZQW-00H.pdf
which might or might not be the same panel.
Now the IL9332 driver simply uses the device model (Dlink DIR-685) as part of the compatible string.
Regards ChenYu
config DRM_PANEL_ILITEK_IL9322 tristate "Ilitek ILI9322 320x240 QVGA panels" depends on OF && SPI
Hi,
On Wed, Feb 21, 2018 at 11:36:10PM +0800, Chen-Yu Tsai wrote:
On Wed, Feb 21, 2018 at 5:20 PM, Maxime Ripard maxime.ripard@bootlin.com wrote:
From: Maxime Ripard maxime.ripard@free-electrons.com
The LHR050H41 panel is the panel shipped with the BananaPi M2-Magic. Add a driver for it.
So I distinctly remember questioning the vendor name the first time. I would just use Bananapi as the vendor name instead.
Ack.
+config DRM_PANEL_HUARUI_LHR050H41
tristate "Huarui LHR050H41 panel"
depends on OF
depends on DRM_MIPI_DSI
depends on BACKLIGHT_CLASS_DEVICE
help
Say Y if you want to enable support for the Huarui Lighting
LHR05041 DSI panel. The panel has a 1280x720 resolution.
And it seems this panel is driven by an ILI9881C from Ilitek. So maybe you could make the panel driver more like the IL9322, as in having common code for the driver IC, then a data structure tied to actual panel compatible strings to handle any quirks.
The datasheet can be found simply by googling the part ID, or here:
http://en.startek-lcd.com/res/starteklcden/pdres/201706/20170617115241070.pd...
This should help with the init command sequence.
I also found this:
http://www.ampdisplay.com/documents/pdf/AM-7201280ETZQW-00H.pdf
which might or might not be the same panel.
Now the IL9332 driver simply uses the device model (Dlink DIR-685) as part of the compatible string.
I guess we can create an ili9881c driver then, with the lhr050h41 compatible. I'm not sure there's much more we can do at this point, since in order to know the set of quirks to associate to each compatible, we'd need to have a second panel.
Thanks! Maxime
From: Maxime Ripard maxime.ripard@free-electrons.com
The A33 has a MIPI-DSI block, along with its D-PHY. Let's add it in order to use it in the relevant boards.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- arch/arm/boot/dts/sun8i-a33.dtsi | 35 +++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-a33.dtsi b/arch/arm/boot/dts/sun8i-a33.dtsi index 50eb84fa246a..20400c87d611 100644 --- a/arch/arm/boot/dts/sun8i-a33.dtsi +++ b/arch/arm/boot/dts/sun8i-a33.dtsi @@ -236,6 +236,11 @@ #address-cells = <1>; #size-cells = <0>; reg = <1>; + + tcon0_out_dsi0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dsi0_in_tcon0>; + }; }; }; }; @@ -280,6 +285,36 @@ #io-channel-cells = <0>; };
+ dsi0: dsi@1ca0000 { + compatible = "allwinner,sun6i-a31-mipi-dsi"; + reg = <0x01ca0000 0x1000>; + interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&ccu CLK_BUS_MIPI_DSI>, + <&ccu CLK_DSI_SCLK>; + clock-names = "bus", "mod"; + resets = <&ccu RST_BUS_MIPI_DSI>; + phys = <&dphy0>; + phy-names = "dphy"; + status = "disabled"; + + port { + dsi0_in_tcon0: endpoint { + remote-endpoint = <&tcon0_out_dsi0>; + }; + }; + }; + + dphy0: d-phy@1ca1000 { + compatible = "allwinner,sun6i-a31-mipi-dphy"; + reg = <0x01ca1000 0x1000>; + clocks = <&ccu CLK_BUS_MIPI_DSI>, + <&ccu CLK_DSI_DPHY>; + clock-names = "bus", "mod"; + resets = <&ccu RST_BUS_MIPI_DSI>; + status = "disabled"; + #phy-cells = <0>; + }; + fe0: display-frontend@1e00000 { compatible = "allwinner,sun8i-a33-display-frontend"; reg = <0x01e00000 0x20000>;
From: Maxime Ripard maxime.ripard@free-electrons.com
The BananaPi M2M has an optional 1280x720 DSI panel. Since that panel is optional, we can only show a DT patch that would show how to enable it.
Signed-off-by: Maxime Ripard maxime.ripard@free-electrons.com --- arch/arm/boot/dts/sun8i-r16-bananapi-m2m.dts | 39 +++++++++++++++++++++- 1 file changed, 39 insertions(+)
diff --git a/arch/arm/boot/dts/sun8i-r16-bananapi-m2m.dts b/arch/arm/boot/dts/sun8i-r16-bananapi-m2m.dts index eaf09666720d..11a4dfde2679 100644 --- a/arch/arm/boot/dts/sun8i-r16-bananapi-m2m.dts +++ b/arch/arm/boot/dts/sun8i-r16-bananapi-m2m.dts @@ -44,6 +44,7 @@ #include "sun8i-a33.dtsi"
#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/pwm/pwm.h>
/ { model = "BananaPi M2 Magic"; @@ -81,6 +82,14 @@ }; };
+ pwm_bl: backlight { + compatible = "pwm-backlight"; + pwms = <&pwm 0 50000 PWM_POLARITY_INVERTED>; + brightness-levels = <1 2 4 8 16 32 64 128 255>; + default-brightness-level = <8>; + enable-gpios = <&r_pio 0 4 GPIO_ACTIVE_HIGH>; /* PG10 */ + }; + reg_vcc5v0: vcc5v0 { compatible = "regulator-fixed"; regulator-name = "vcc5v0"; @@ -120,6 +129,26 @@ status = "okay"; };
+&de { + status = "okay"; +}; + +&dphy0 { + status = "okay"; +}; + +&dsi0 { + status = "okay"; + + panel@0 { + compatible = "huarui,lhr050h41"; + reg = <0>; + power-gpios = <&pio 1 7 GPIO_ACTIVE_HIGH>; /* PB07 */ + reset-gpios = <&r_pio 0 5 GPIO_ACTIVE_LOW>; /* PL05 */ + backlight = <&pwm_bl>; + }; +}; + &ehci0 { status = "okay"; }; @@ -179,6 +208,12 @@ status = "okay"; };
+&pwm { + pinctrl-names = "default"; + pinctrl-0 = <&pwm0_pins>; + status = "okay"; +}; + &r_rsb { status = "okay";
@@ -291,6 +326,10 @@ status = "okay"; };
+&tcon0 { + status = "okay"; +}; + &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins_b>;
dri-devel@lists.freedesktop.org