Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1594016 > unrolled thread
| Started by | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| First post | 2017-03-07 10:10 +0100 |
| Last post | 2017-03-08 04:50 +0100 |
| Articles | 11 on this page of 31 — 4 participants |
Back to article view | Back to linux.kernel
[PATCH 0/15] drm: sun4i: Add support for the HDMI controller Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:10 +0100
[PATCH 11/15] drm/sun4i: tcon: Fix tcon channel 1 backporch calculation Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:20 +0100
Re: [PATCH 11/15] drm/sun4i: tcon: Fix tcon channel 1 backporch calculation Chen-Yu Tsai <wens@csie.org> - 2017-03-08 05:40 +0100
Re: [PATCH 11/15] drm/sun4i: tcon: Fix tcon channel 1 backporch calculation Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-08 23:00 +0100
[PATCH 10/15] drm/sun4i: tcon: Switch mux on only for composite Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:20 +0100
Re: [PATCH 10/15] drm/sun4i: tcon: Switch mux on only for composite Chen-Yu Tsai <wens@csie.org> - 2017-03-08 05:10 +0100
Re: [PATCH 10/15] drm/sun4i: tcon: Switch mux on only for composite Stefan Monnier <monnier@iro.umontreal.ca> - 2017-03-08 06:00 +0100
Re: [PATCH 10/15] drm/sun4i: tcon: Switch mux on only for composite Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-09 12:00 +0100
Re: [linux-sunxi] Re: [PATCH 10/15] drm/sun4i: tcon: Switch mux on only for composite Chen-Yu Tsai <wens@csie.org> - 2017-03-09 12:40 +0100
Re: [linux-sunxi] Re: [PATCH 10/15] drm/sun4i: tcon: Switch mux on only for composite Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-09 16:00 +0100
[PATCH 8/15] drm/sun4i: tcon: Add channel debug Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:20 +0100
Re: [PATCH 8/15] drm/sun4i: tcon: Add channel debug Chen-Yu Tsai <wens@csie.org> - 2017-03-08 05:00 +0100
[PATCH 9/15] drm/sun4i: tcon: Pass the encoder to the mode set functions Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:20 +0100
[PATCH 15/15] ARM: sun5i: a10s-olinuxino: Enable HDMI Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:20 +0100
Re: [PATCH 15/15] ARM: sun5i: a10s-olinuxino: Enable HDMI Chen-Yu Tsai <wens@csie.org> - 2017-03-08 04:50 +0100
[PATCH 14/15] ARM: sun5i: a10s: Add the HDMI controller node Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:30 +0100
Re: [linux-sunxi] [PATCH 14/15] ARM: sun5i: a10s: Add the HDMI controller node Chen-Yu Tsai <wens@csie.org> - 2017-03-08 05:10 +0100
Re: [linux-sunxi] [PATCH 14/15] ARM: sun5i: a10s: Add the HDMI controller node Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-09 12:10 +0100
Re: [linux-sunxi] [PATCH 14/15] ARM: sun5i: a10s: Add the HDMI controller node Chen-Yu Tsai <wens@csie.org> - 2017-03-09 12:20 +0100
[PATCH 2/15] clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:40 +0100
[PATCH 1/15] clk: divider: Make divider_round_rate take the parent clock Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:40 +0100
Re: [PATCH 1/15] clk: divider: Make divider_round_rate take the parent clock Stephen Boyd <sboyd@codeaurora.org> - 2017-03-07 15:20 +0100
Re: [PATCH 1/15] clk: divider: Make divider_round_rate take the parent clock Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-09 12:20 +0100
[PATCH 12/15] drm/sun4i: tcon: multiply the vtotal when not in interlace Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:40 +0100
Re: [PATCH 12/15] drm/sun4i: tcon: multiply the vtotal when not in interlace Chen-Yu Tsai <wens@csie.org> - 2017-03-07 11:10 +0100
Re: [PATCH 12/15] drm/sun4i: tcon: multiply the vtotal when not in interlace Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-09 12:20 +0100
[PATCH 13/15] drm/sun4i: Add HDMI support Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:40 +0100
[PATCH 7/15] dt-bindings: display: sun4i: Add allwinner,tcon-channel property Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:40 +0100
Re: [PATCH 7/15] dt-bindings: display: sun4i: Add allwinner,tcon-channel property Chen-Yu Tsai <wens@csie.org> - 2017-03-08 04:50 +0100
[PATCH 6/15] dt-bindings: display: sun4i: Add HDMI display bindings Maxime Ripard <maxime.ripard@free-electrons.com> - 2017-03-07 10:40 +0100
Re: [PATCH 6/15] dt-bindings: display: sun4i: Add HDMI display bindings Chen-Yu Tsai <wens@csie.org> - 2017-03-08 04:50 +0100
Page 2 of 2 — ← Prev page 1 [2]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-03-07 10:40 +0100 |
| Subject | [PATCH 1/15] clk: divider: Make divider_round_rate take the parent clock |
| Message-ID | <tijL4-5vy-11@gated-at.bofh.it> |
| In reply to | #1594016 |
So far, divider_round_rate only considers the parent clock returned by
clk_hw_get_parent.
This works fine on clocks that have a single parents, this doesn't work on
muxes, since we will only consider the first parent, while other parents
may totally be able to provide a better combination.
Clocks in that case cannot use divider_round_rate, so would have to come up
with a very similar logic to work around it. Instead of having to do
something like this, and duplicate that logic everywhere, give an
additional parameter for the parent clock to consider.
Current users have been converted using the following coccinelle script
@@
identifier hw, rate, prate, table, width, flags;
@@
-long divider_round_rate(struct clk_hw *hw,
+long divider_round_rate(struct clk_hw *hw, struct clk_hw *parent,
unsigned long rate,
unsigned long *prate,
const struct clk_div_table *table,
u8 width,
unsigned long flags) { ... }
@@
identifier fn, hw;
expression E2, E3, E4, E5, E6;
@@
fn (struct clk_hw *hw, ...) {
<...
-divider_round_rate(hw, E2, E3, E4, E5, E6)
+divider_round_rate(hw, clk_hw_get_parent(hw), E2, E3, E4, E5, E6)
...>
}
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Cc: Carlo Caione <carlo@caione.org>
Cc: Kevin Hilman <khilman@baylibre.com>
Cc: Vladimir Zapolskiy <vz@mleia.com>
Cc: Sylvain Lemieux <slemieux.tyco@gmail.com>
Cc: Andy Gross <andy.gross@linaro.org>
Cc: David Brown <david.brown@linaro.org>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@free-electrons.com>
Cc: linux-amlogic@lists.infradead.org
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-soc@vger.kernel.org
Cc: rtc-linux@googlegroups.com
---
drivers/clk/clk-divider.c | 18 ++++++++++--------
drivers/clk/hisilicon/clkdivider-hi6220.c | 5 +++--
drivers/clk/meson/clk-cpu.c | 5 +++--
drivers/clk/nxp/clk-lpc32xx.c | 5 +++--
drivers/clk/qcom/clk-alpha-pll.c | 5 +++--
drivers/clk/qcom/clk-regmap-divider.c | 3 ++-
drivers/clk/sunxi-ng/ccu_div.c | 6 +++---
drivers/rtc/rtc-ac100.c | 6 ++++--
include/linux/clk-provider.h | 5 +++--
9 files changed, 34 insertions(+), 24 deletions(-)
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index 96386ffc8483..d8d7dc84956a 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -275,7 +275,8 @@ static int _next_div(const struct clk_div_table *table, int div,
return div;
}
-static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
+static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent,
+ unsigned long rate,
unsigned long *best_parent_rate,
const struct clk_div_table *table, u8 width,
unsigned long flags)
@@ -314,8 +315,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
*best_parent_rate = parent_rate_saved;
return i;
}
- parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
- rate * i);
+ parent_rate = clk_hw_round_rate(parent, rate * i);
now = DIV_ROUND_UP_ULL((u64)parent_rate, i);
if (_is_best_div(rate, now, best, flags)) {
bestdiv = i;
@@ -326,19 +326,20 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
if (!bestdiv) {
bestdiv = _get_maxdiv(table, width, flags);
- *best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
+ *best_parent_rate = clk_hw_round_rate(parent, 1);
}
return bestdiv;
}
-long divider_round_rate(struct clk_hw *hw, unsigned long rate,
+long divider_round_rate(struct clk_hw *hw, struct clk_hw *parent,
+ unsigned long rate,
unsigned long *prate, const struct clk_div_table *table,
u8 width, unsigned long flags)
{
int div;
- div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
+ div = clk_divider_bestdiv(hw, parent, rate, prate, table, width, flags);
return DIV_ROUND_UP_ULL((u64)*prate, div);
}
@@ -359,8 +360,9 @@ static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
return DIV_ROUND_UP_ULL((u64)*prate, bestdiv);
}
- return divider_round_rate(hw, rate, prate, divider->table,
- divider->width, divider->flags);
+ return divider_round_rate(hw, clk_hw_get_parent(hw), rate, prate,
+ divider->table, divider->width,
+ divider->flags);
}
int divider_get_val(unsigned long rate, unsigned long parent_rate,
diff --git a/drivers/clk/hisilicon/clkdivider-hi6220.c b/drivers/clk/hisilicon/clkdivider-hi6220.c
index a1c1f684ad58..deaa72902555 100644
--- a/drivers/clk/hisilicon/clkdivider-hi6220.c
+++ b/drivers/clk/hisilicon/clkdivider-hi6220.c
@@ -64,8 +64,9 @@ static long hi6220_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate,
{
struct hi6220_clk_divider *dclk = to_hi6220_clk_divider(hw);
- return divider_round_rate(hw, rate, prate, dclk->table,
- dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
+ return divider_round_rate(hw, clk_hw_get_parent(hw), rate, prate,
+ dclk->table, dclk->width,
+ CLK_DIVIDER_ROUND_CLOSEST);
}
static int hi6220_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/drivers/clk/meson/clk-cpu.c b/drivers/clk/meson/clk-cpu.c
index f8b2b7efd016..63c44d8c7ea4 100644
--- a/drivers/clk/meson/clk-cpu.c
+++ b/drivers/clk/meson/clk-cpu.c
@@ -59,8 +59,9 @@ static long meson_clk_cpu_round_rate(struct clk_hw *hw, unsigned long rate,
{
struct meson_clk_cpu *clk_cpu = to_meson_clk_cpu_hw(hw);
- return divider_round_rate(hw, rate, prate, clk_cpu->div_table,
- MESON_N_WIDTH, CLK_DIVIDER_ROUND_CLOSEST);
+ return divider_round_rate(hw, clk_hw_get_parent(hw), rate, prate,
+ clk_cpu->div_table, MESON_N_WIDTH,
+ CLK_DIVIDER_ROUND_CLOSEST);
}
static int meson_clk_cpu_set_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/drivers/clk/nxp/clk-lpc32xx.c b/drivers/clk/nxp/clk-lpc32xx.c
index 5b98ff9076f3..73e83a7da7d6 100644
--- a/drivers/clk/nxp/clk-lpc32xx.c
+++ b/drivers/clk/nxp/clk-lpc32xx.c
@@ -975,8 +975,9 @@ static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
return DIV_ROUND_UP(*prate, bestdiv);
}
- return divider_round_rate(hw, rate, prate, divider->table,
- divider->width, divider->flags);
+ return divider_round_rate(hw, clk_hw_get_parent(hw), rate, prate,
+ divider->table, divider->width,
+ divider->flags);
}
static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c
index 47a1da3739ce..7c08afecc811 100644
--- a/drivers/clk/qcom/clk-alpha-pll.c
+++ b/drivers/clk/qcom/clk-alpha-pll.c
@@ -478,8 +478,9 @@ clk_alpha_pll_postdiv_round_rate(struct clk_hw *hw, unsigned long rate,
{
struct clk_alpha_pll_postdiv *pll = to_clk_alpha_pll_postdiv(hw);
- return divider_round_rate(hw, rate, prate, clk_alpha_div_table,
- pll->width, CLK_DIVIDER_POWER_OF_TWO);
+ return divider_round_rate(hw, clk_hw_get_parent(hw), rate, prate,
+ clk_alpha_div_table, pll->width,
+ CLK_DIVIDER_POWER_OF_TWO);
}
static int clk_alpha_pll_postdiv_set_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/drivers/clk/qcom/clk-regmap-divider.c b/drivers/clk/qcom/clk-regmap-divider.c
index 53484912301e..63489f0f2a02 100644
--- a/drivers/clk/qcom/clk-regmap-divider.c
+++ b/drivers/clk/qcom/clk-regmap-divider.c
@@ -28,7 +28,8 @@ static long div_round_rate(struct clk_hw *hw, unsigned long rate,
{
struct clk_regmap_div *divider = to_clk_regmap_div(hw);
- return divider_round_rate(hw, rate, prate, NULL, divider->width,
+ return divider_round_rate(hw, clk_hw_get_parent(hw), rate, prate,
+ NULL, divider->width,
CLK_DIVIDER_ROUND_CLOSEST);
}
diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index 4057e6021aa9..92855c2b30bb 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -78,10 +78,10 @@ static int ccu_div_determine_rate(struct clk_hw *hw,
struct ccu_div *cd = hw_to_ccu_div(hw);
if (clk_hw_get_num_parents(hw) == 1) {
- req->rate = divider_round_rate(hw, req->rate,
+ req->rate = divider_round_rate(hw, clk_hw_get_parent(hw),
+ req->rate,
&req->best_parent_rate,
- cd->div.table,
- cd->div.width,
+ cd->div.table, cd->div.width,
cd->div.flags);
req->best_parent_hw = clk_hw_get_parent(hw);
diff --git a/drivers/rtc/rtc-ac100.c b/drivers/rtc/rtc-ac100.c
index 9e336184491c..0cad1bc41aa7 100644
--- a/drivers/rtc/rtc-ac100.c
+++ b/drivers/rtc/rtc-ac100.c
@@ -153,13 +153,15 @@ static long ac100_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
int i;
if (prate == AC100_RTC_32K_RATE)
- return divider_round_rate(hw, rate, &prate, NULL,
+ return divider_round_rate(hw, clk_hw_get_parent(hw), rate,
+ &prate, NULL,
AC100_CLKOUT_DIV_WIDTH,
CLK_DIVIDER_POWER_OF_TWO);
for (i = 0; ac100_clkout_prediv[i].div; i++) {
tmp_prate = DIV_ROUND_UP(prate, ac100_clkout_prediv[i].val);
- tmp_rate = divider_round_rate(hw, rate, &tmp_prate, NULL,
+ tmp_rate = divider_round_rate(hw, clk_hw_get_parent(hw), rate,
+ &tmp_prate, NULL,
AC100_CLKOUT_DIV_WIDTH,
CLK_DIVIDER_POWER_OF_TWO);
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index a428aec36ace..e17d4cc7660c 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -412,8 +412,9 @@ extern const struct clk_ops clk_divider_ro_ops;
unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
unsigned int val, const struct clk_div_table *table,
unsigned long flags);
-long divider_round_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long *prate, const struct clk_div_table *table,
+long divider_round_rate(struct clk_hw *hw, struct clk_hw *parent,
+ unsigned long rate, unsigned long *prate,
+ const struct clk_div_table *table,
u8 width, unsigned long flags);
int divider_get_val(unsigned long rate, unsigned long parent_rate,
const struct clk_div_table *table, u8 width,
--
git-series 0.8.11
[toc] | [prev] | [next] | [standalone]
| From | Stephen Boyd <sboyd@codeaurora.org> |
|---|---|
| Date | 2017-03-07 15:20 +0100 |
| Subject | Re: [PATCH 1/15] clk: divider: Make divider_round_rate take the parent clock |
| Message-ID | <tio81-ag-3@gated-at.bofh.it> |
| In reply to | #1594042 |
On 03/07, Maxime Ripard wrote:
> So far, divider_round_rate only considers the parent clock returned by
> clk_hw_get_parent.
>
> This works fine on clocks that have a single parents, this doesn't work on
> muxes, since we will only consider the first parent, while other parents
> may totally be able to provide a better combination.
>
> Clocks in that case cannot use divider_round_rate, so would have to come up
> with a very similar logic to work around it. Instead of having to do
> something like this, and duplicate that logic everywhere, give an
> additional parameter for the parent clock to consider.
>
> Current users have been converted using the following coccinelle script
>
> @@
> identifier hw, rate, prate, table, width, flags;
> @@
>
> -long divider_round_rate(struct clk_hw *hw,
> +long divider_round_rate(struct clk_hw *hw, struct clk_hw *parent,
> unsigned long rate,
> unsigned long *prate,
> const struct clk_div_table *table,
> u8 width,
> unsigned long flags) { ... }
>
> @@
> identifier fn, hw;
> expression E2, E3, E4, E5, E6;
> @@
> fn (struct clk_hw *hw, ...) {
> <...
> -divider_round_rate(hw, E2, E3, E4, E5, E6)
> +divider_round_rate(hw, clk_hw_get_parent(hw), E2, E3, E4, E5, E6)
> ...>
> }
Why not introduce another function like
divider_round_rate_parent()
divider_round_rate_mux()
that takes the extra parent argument? Technically, a divider is
considered to only have one parent, and if it has more than one
parent, then it is a mux and a divider.
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-03-09 12:20 +0100 |
| Subject | Re: [PATCH 1/15] clk: divider: Make divider_round_rate take the parent clock |
| Message-ID | <tj4gW-4pf-15@gated-at.bofh.it> |
| In reply to | #1594263 |
[Multipart message — attachments visible in raw view] — view raw
Hi Stephen,
On Tue, Mar 07, 2017 at 06:11:57AM -0800, Stephen Boyd wrote:
> On 03/07, Maxime Ripard wrote:
> > So far, divider_round_rate only considers the parent clock returned by
> > clk_hw_get_parent.
> >
> > This works fine on clocks that have a single parents, this doesn't work on
> > muxes, since we will only consider the first parent, while other parents
> > may totally be able to provide a better combination.
> >
> > Clocks in that case cannot use divider_round_rate, so would have to come up
> > with a very similar logic to work around it. Instead of having to do
> > something like this, and duplicate that logic everywhere, give an
> > additional parameter for the parent clock to consider.
> >
> > Current users have been converted using the following coccinelle script
> >
> > @@
> > identifier hw, rate, prate, table, width, flags;
> > @@
> >
> > -long divider_round_rate(struct clk_hw *hw,
> > +long divider_round_rate(struct clk_hw *hw, struct clk_hw *parent,
> > unsigned long rate,
> > unsigned long *prate,
> > const struct clk_div_table *table,
> > u8 width,
> > unsigned long flags) { ... }
> >
> > @@
> > identifier fn, hw;
> > expression E2, E3, E4, E5, E6;
> > @@
> > fn (struct clk_hw *hw, ...) {
> > <...
> > -divider_round_rate(hw, E2, E3, E4, E5, E6)
> > +divider_round_rate(hw, clk_hw_get_parent(hw), E2, E3, E4, E5, E6)
> > ...>
> > }
>
> Why not introduce another function like
>
> divider_round_rate_parent()
> divider_round_rate_mux()
>
> that takes the extra parent argument? Technically, a divider is
> considered to only have one parent, and if it has more than one
> parent, then it is a mux and a divider.
Yes, that would work too, without needing the cross tree change. I'll
do that in the next version.
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-03-07 10:40 +0100 |
| Subject | [PATCH 12/15] drm/sun4i: tcon: multiply the vtotal when not in interlace |
| Message-ID | <tijL4-5vy-15@gated-at.bofh.it> |
| In reply to | #1594016 |
It appears that the total vertical resolution needs to be doubled when
we're not in interlaced. Make sure that is the case.
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
drivers/gpu/drm/sun4i/sun4i_tcon.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index e44217fb4f6f..515fa56c1e6a 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -248,10 +248,13 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, struct drm_encoder *encoder,
SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
bp = mode->crtc_vtotal - mode->crtc_vsync_start;
+ val = mode->crtc_vtotal;
+ if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
+ val = val * 2;
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_TOTAL(val) |
SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
/* Set Hsync and Vsync length */
--
git-series 0.8.11
[toc] | [prev] | [next] | [standalone]
| From | Chen-Yu Tsai <wens@csie.org> |
|---|---|
| Date | 2017-03-07 11:10 +0100 |
| Subject | Re: [PATCH 12/15] drm/sun4i: tcon: multiply the vtotal when not in interlace |
| Message-ID | <tike6-5Vf-17@gated-at.bofh.it> |
| In reply to | #1594043 |
Hi,
On Tue, Mar 7, 2017 at 4:56 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> It appears that the total vertical resolution needs to be doubled when
> we're not in interlaced. Make sure that is the case.
This is true for both channels, though we handle them differently.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
> drivers/gpu/drm/sun4i/sun4i_tcon.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index e44217fb4f6f..515fa56c1e6a 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -248,10 +248,13 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, struct drm_encoder *encoder,
> SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
>
> bp = mode->crtc_vtotal - mode->crtc_vsync_start;
> + val = mode->crtc_vtotal;
> + if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
> + val = val * 2;
> 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_TOTAL(val) |
For channel 0, the SUN4I_TCON0_BASIC2_V_TOTAL macro multiplies the passed in
value by 2. I think we should do the same for channel 1, and instead halve the
value passed in if we are outputting interlaced data. I think this makes more
sense because:
1) The register description for both channels are the same. Handling them
consistently will result in less confusion, such as this one.
2) The definition of interlaced modes is a frame is separated into odd and
even fields, with each field contains half the number of lines of the
full frame. One field is displayed during each VSYNC cycle. The TCON does
not know whether it's interlaced video or not. It only knows the display
timings. In this case, the number of horizontal lines per cycle is key.
Regards
ChenYu
> SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
>
> /* Set Hsync and Vsync length */
> --
> git-series 0.8.11
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-03-09 12:20 +0100 |
| Subject | Re: [PATCH 12/15] drm/sun4i: tcon: multiply the vtotal when not in interlace |
| Message-ID | <tj4gW-4pf-7@gated-at.bofh.it> |
| In reply to | #1594065 |
[Multipart message — attachments visible in raw view] — view raw
On Tue, Mar 07, 2017 at 06:05:17PM +0800, Chen-Yu Tsai wrote:
> Hi,
>
> On Tue, Mar 7, 2017 at 4:56 PM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
> > It appears that the total vertical resolution needs to be doubled when
> > we're not in interlaced. Make sure that is the case.
>
> This is true for both channels, though we handle them differently.
>
> >
> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> > ---
> > drivers/gpu/drm/sun4i/sun4i_tcon.c | 5 ++++-
> > 1 file changed, 4 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> > index e44217fb4f6f..515fa56c1e6a 100644
> > --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> > +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> > @@ -248,10 +248,13 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, struct drm_encoder *encoder,
> > SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
> >
> > bp = mode->crtc_vtotal - mode->crtc_vsync_start;
> > + val = mode->crtc_vtotal;
> > + if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
> > + val = val * 2;
> > 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_TOTAL(val) |
>
> For channel 0, the SUN4I_TCON0_BASIC2_V_TOTAL macro multiplies the passed in
> value by 2. I think we should do the same for channel 1, and instead halve the
> value passed in if we are outputting interlaced data. I think this makes more
> sense because:
>
> 1) The register description for both channels are the same. Handling them
> consistently will result in less confusion, such as this one.
>
> 2) The definition of interlaced modes is a frame is separated into odd and
> even fields, with each field contains half the number of lines of the
> full frame. One field is displayed during each VSYNC cycle. The TCON does
> not know whether it's interlaced video or not. It only knows the display
> timings. In this case, the number of horizontal lines per cycle is key.
Hmm, yes, you're right. I'll fix it in the next release.
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-03-07 10:40 +0100 |
| Subject | [PATCH 13/15] drm/sun4i: Add HDMI support |
| Message-ID | <tijL4-5vy-5@gated-at.bofh.it> |
| In reply to | #1594016 |
The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
controller.
That HDMI controller is able to do audio and CEC, but those have been left
out for now.
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
drivers/gpu/drm/sun4i/Makefile | 5 +-
drivers/gpu/drm/sun4i/sun4i_hdmi.h | 124 ++++++-
drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 128 ++++++-
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 449 +++++++++++++++++++++-
drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 236 +++++++++++-
5 files changed, 942 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 59b757350a1f..68a0f6244a59 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -7,7 +7,12 @@ sun4i-tcon-y += sun4i_dotclock.o
sun4i-tcon-y += sun4i_crtc.o
sun4i-tcon-y += sun4i_layer.o
+sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
+sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
+sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
+
obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o
obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o
obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o
+obj-$(CONFIG_DRM_SUN4I) += sun4i-drm-hdmi.o
obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
new file mode 100644
index 000000000000..2ad25b8fd3cd
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef _SUN4I_HDMI_H_
+#define _SUN4I_HDMI_H_
+
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+#define SUN4I_HDMI_CTRL_REG 0x004
+#define SUN4I_HDMI_CTRL_ENABLE BIT(31)
+
+#define SUN4I_HDMI_IRQ_REG 0x008
+#define SUN4I_HDMI_IRQ_STA_MASK 0x73
+#define SUN4I_HDMI_IRQ_STA_FIFO_OF BIT(1)
+#define SUN4I_HDMI_IRQ_STA_FIFO_UF BIT(0)
+
+#define SUN4I_HDMI_HPD_REG 0x00c
+#define SUN4I_HDMI_HPD_HIGH BIT(0)
+
+#define SUN4I_HDMI_VID_CTRL_REG 0x010
+#define SUN4I_HDMI_VID_CTRL_ENABLE BIT(31)
+#define SUN4I_HDMI_VID_CTRL_HDMI_MODE BIT(30)
+
+#define SUN4I_HDMI_VID_TIMING_ACT_REG 0x014
+#define SUN4I_HDMI_VID_TIMING_BP_REG 0x018
+#define SUN4I_HDMI_VID_TIMING_FP_REG 0x01c
+#define SUN4I_HDMI_VID_TIMING_SPW_REG 0x020
+
+#define SUN4I_HDMI_VID_TIMING_X(x) ((((x) - 1) & GENMASK(11, 0)))
+#define SUN4I_HDMI_VID_TIMING_Y(y) ((((y) - 1) & GENMASK(11, 0)) << 16)
+
+#define SUN4I_HDMI_VID_TIMING_POL_REG 0x024
+#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK (0x3e0 << 16)
+#define SUN4I_HDMI_VID_TIMING_POL_VSYNC BIT(1)
+#define SUN4I_HDMI_VID_TIMING_POL_HSYNC BIT(0)
+
+#define SUN4I_HDMI_AVI_INFOFRAME_REG(n) (0x080 + (n))
+
+#define SUN4I_HDMI_PAD_CTRL0_REG 0x200
+
+#define SUN4I_HDMI_PAD_CTRL1_REG 0x204
+#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK BIT(6)
+
+#define SUN4I_HDMI_PLL_CTRL_REG 0x208
+#define SUN4I_HDMI_PLL_CTRL_DIV(n) ((n) << 4)
+#define SUN4I_HDMI_PLL_CTRL_DIV_MASK GENMASK(7, 4)
+
+#define SUN4I_HDMI_PLL_DBG0_REG 0x20c
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n) (((n) & 1) << 21)
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21)
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21
+
+#define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n)))
+#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4))
+
+#define SUN4I_HDMI_UNKNOWN_REG 0x300
+#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC BIT(27)
+
+#define SUN4I_HDMI_DDC_CTRL_REG 0x500
+#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31)
+#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30)
+#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0)
+
+#define SUN4I_HDMI_DDC_ADDR_REG 0x504
+#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24)
+#define SUN4I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16)
+#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8)
+#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff)
+
+#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510
+#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31)
+
+#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518
+#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c
+
+#define SUN4I_HDMI_DDC_CMD_REG 0x520
+#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6
+
+#define SUN4I_HDMI_DDC_CLK_REG 0x528
+#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3)
+#define SUN4I_HDMI_DDC_CLK_N(n) ((n) & 0x7)
+
+#define SUN4I_HDMI_DDC_LINE_CTRL_REG 0x540
+#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE BIT(9)
+#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE BIT(8)
+
+#define SUN4I_HDMI_DDC_FIFO_SIZE 16
+
+enum sun4i_hdmi_pkt_type {
+ SUN4I_HDMI_PKT_AVI = 2,
+ SUN4I_HDMI_PKT_END = 15,
+};
+
+struct sun4i_hdmi {
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+ struct device *dev;
+
+ void __iomem *base;
+ struct clk *bus_clk;
+ struct clk *ddc_clk;
+ struct clk *mod_clk;
+ struct clk *pll0_clk;
+ struct clk *pll1_clk;
+ struct clk *tmds_clk;
+
+ struct sun4i_drv *drv;
+
+ bool hdmi_monitor;
+};
+
+int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
+int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
+
+#endif /* _SUN4I_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
new file mode 100644
index 000000000000..5125b14ea7a5
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 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-provider.h>
+
+#include "sun4i_tcon.h"
+#include "sun4i_hdmi.h"
+
+struct sun4i_ddc {
+ struct clk_hw hw;
+ struct sun4i_hdmi *hdmi;
+};
+
+static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
+{
+ return container_of(hw, struct sun4i_ddc, hw);
+}
+
+static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
+ unsigned long parent_rate,
+ u8 *m, u8 *n)
+{
+ unsigned long best_rate = 0;
+ u8 best_m = 0, best_n = 0, _m, _n;
+
+ for (_m = 0; _m < 8; _m++) {
+ for (_n = 0; _n < 8; _n++) {
+ unsigned long tmp_rate;
+
+ tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
+
+ if (tmp_rate > rate)
+ continue;
+
+ if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
+ best_rate = tmp_rate;
+ best_m = _m;
+ best_n = _n;
+ }
+ }
+ }
+
+ if (m && n) {
+ *m = best_m;
+ *n = best_n;
+ }
+
+ return best_rate;
+}
+
+static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
+}
+
+static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct sun4i_ddc *ddc = hw_to_ddc(hw);
+ u32 reg;
+ u8 m, n;
+
+ reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+ m = (reg >> 3) & 0x7;
+ n = reg & 0x7;
+
+ return (((parent_rate / 2) / 10) >> n) / (m + 1);
+}
+
+static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sun4i_ddc *ddc = hw_to_ddc(hw);
+ u8 div_m, div_n;
+
+ sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
+
+ writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
+ ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+
+ return 0;
+}
+
+static const struct clk_ops sun4i_ddc_ops = {
+ .recalc_rate = sun4i_ddc_recalc_rate,
+ .round_rate = sun4i_ddc_round_rate,
+ .set_rate = sun4i_ddc_set_rate,
+};
+
+int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
+{
+ struct clk_init_data init;
+ struct sun4i_ddc *ddc;
+ const char *parent_name;
+
+ parent_name = __clk_get_name(parent);
+ if (!parent_name)
+ return -ENODEV;
+
+ ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
+ if (!ddc)
+ return -ENOMEM;
+
+ init.name = "hdmi-ddc";
+ init.ops = &sun4i_ddc_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_PARENT;
+
+ ddc->hdmi = hdmi;
+ ddc->hw.init = &init;
+
+ hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
+ if (IS_ERR(hdmi->ddc_clk))
+ return PTR_ERR(hdmi->ddc_clk);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
new file mode 100644
index 000000000000..33175308c2ed
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_panel.h>
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/iopoll.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include "sun4i_backend.h"
+#include "sun4i_drv.h"
+#include "sun4i_hdmi.h"
+#include "sun4i_tcon.h"
+
+static inline struct sun4i_hdmi *
+drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
+{
+ return container_of(encoder, struct sun4i_hdmi,
+ encoder);
+}
+
+static inline struct sun4i_hdmi *
+drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
+{
+ return container_of(connector, struct sun4i_hdmi,
+ connector);
+}
+
+static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ struct hdmi_avi_infoframe frame;
+ u8 buffer[17];
+ int i, ret;
+
+ ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+ if (ret < 0) {
+ DRM_ERROR("Failed to get infoframes from mode\n");
+ return ret;
+ }
+
+ ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
+ if (ret < 0) {
+ DRM_ERROR("Failed to pack infoframes\n");
+ return ret;
+ }
+
+ for (i = 0; i < sizeof(buffer); i++)
+ writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
+
+ return 0;
+}
+
+static void sun4i_hdmi_disable(struct drm_encoder *encoder)
+{
+ struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+ struct sun4i_drv *drv = hdmi->drv;
+ struct sun4i_tcon *tcon = drv->tcon;
+ u32 val;
+
+ DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
+
+ val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+ val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
+ writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+
+ sun4i_tcon_channel_disable(tcon, 1);
+}
+
+static void sun4i_hdmi_enable(struct drm_encoder *encoder)
+{
+ struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
+ struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+ struct sun4i_drv *drv = hdmi->drv;
+ struct sun4i_tcon *tcon = drv->tcon;
+ u32 val = 0;
+
+ DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
+
+ sun4i_tcon_channel_enable(tcon, 1);
+
+ sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
+ val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
+ val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
+ writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
+
+ val = SUN4I_HDMI_VID_CTRL_ENABLE;
+ if (hdmi->hdmi_monitor)
+ val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
+
+ writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+}
+
+static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+ struct sun4i_drv *drv = hdmi->drv;
+ struct sun4i_tcon *tcon = drv->tcon;
+ unsigned int x, y;
+ u32 val;
+
+ sun4i_tcon1_mode_set(tcon, encoder, mode);
+ clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+ clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
+
+ /* Set input sync enable */
+ writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
+ hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
+
+ /* Setup timing registers */
+ writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
+ SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
+ hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
+
+ x = mode->htotal - mode->hsync_start;
+ y = mode->vtotal - mode->vsync_start;
+ writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+ hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
+
+ x = mode->hsync_start - mode->hdisplay;
+ y = mode->vsync_start - mode->vdisplay;
+ writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+ hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
+
+ x = mode->hsync_end - mode->hsync_start;
+ y = mode->vsync_end - mode->vsync_start;
+ writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+ hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
+
+ val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
+
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
+
+ writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
+}
+
+static struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
+ .disable = sun4i_hdmi_disable,
+ .enable = sun4i_hdmi_enable,
+ .mode_set = sun4i_hdmi_mode_set,
+};
+
+static struct drm_encoder_funcs sun4i_hdmi_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
+ unsigned int blk, unsigned int offset,
+ u8 *buf, unsigned int count)
+{
+ unsigned long reg;
+ int i;
+
+ reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
+ writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
+ hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
+ writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
+ SUN4I_HDMI_DDC_ADDR_EDDC(0x60) |
+ SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
+ SUN4I_HDMI_DDC_ADDR_SLAVE(0x50),
+ hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
+
+ writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
+ writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
+ hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
+
+ reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+ writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
+ hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+
+ if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
+ !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
+ 100, 2000))
+ return -EIO;
+
+ for (i = 0; i < count; i++)
+ buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
+
+ return 0;
+}
+
+static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
+ size_t length)
+{
+ struct sun4i_hdmi *hdmi = data;
+ int retry = 2, i;
+
+ do {
+ for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
+ unsigned char offset = blk * EDID_LENGTH + i;
+ unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
+ length - i);
+ int ret;
+
+ ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
+ buf + i, count);
+ if (ret)
+ return ret;
+ }
+ } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
+
+ return 0;
+}
+
+static int sun4i_hdmi_get_modes(struct drm_connector *connector)
+{
+ struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
+ unsigned long reg;
+ struct edid *edid;
+ int ret;
+
+ /* Reset i2c controller */
+ writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
+ hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+ if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
+ !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
+ 100, 2000))
+ return -EIO;
+
+ writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
+ SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
+ hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
+
+ clk_set_rate(hdmi->ddc_clk, 100000);
+
+ edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
+ if (!edid)
+ return 0;
+
+ hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
+ DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
+ hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
+
+ drm_mode_connector_update_edid_property(connector, edid);
+ ret = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+
+ return ret;
+}
+
+static struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
+ .get_modes = sun4i_hdmi_get_modes,
+};
+
+static enum drm_connector_status
+sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
+ unsigned long reg;
+
+ if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
+ reg & SUN4I_HDMI_HPD_HIGH,
+ 0, 500000))
+ return connector_status_disconnected;
+
+ return connector_status_connected;
+}
+
+static struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .detect = sun4i_hdmi_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .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 int sun4i_hdmi_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct drm_device *drm = data;
+ struct sun4i_drv *drv = drm->dev_private;
+ struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
+ int ret;
+
+ hdmi->drv = drv;
+ drm_encoder_helper_add(&hdmi->encoder,
+ &sun4i_hdmi_helper_funcs);
+ ret = drm_encoder_init(drm,
+ &hdmi->encoder,
+ &sun4i_hdmi_funcs,
+ DRM_MODE_ENCODER_TMDS,
+ NULL);
+ if (ret) {
+ dev_err(dev, "Couldn't initialise the HDMI encoder\n");
+ return ret;
+ }
+
+ hdmi->encoder.possible_crtcs = BIT(0);
+
+ drm_connector_helper_add(&hdmi->connector,
+ &sun4i_hdmi_connector_helper_funcs);
+ ret = drm_connector_init(drm, &hdmi->connector,
+ &sun4i_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret) {
+ dev_err(dev,
+ "Couldn't initialise the Composite connector\n");
+ goto err_cleanup_connector;
+ }
+ hdmi->connector.interlace_allowed = true;
+
+ drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
+
+ return 0;
+
+err_cleanup_connector:
+ drm_encoder_cleanup(&hdmi->encoder);
+ return ret;
+}
+
+static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
+
+ drm_connector_cleanup(&hdmi->connector);
+ drm_encoder_cleanup(&hdmi->encoder);
+}
+
+static struct component_ops sun4i_hdmi_ops = {
+ .bind = sun4i_hdmi_bind,
+ .unbind = sun4i_hdmi_unbind,
+};
+
+static int sun4i_hdmi_probe(struct platform_device *pdev)
+{
+ struct sun4i_hdmi *hdmi;
+ struct resource *res;
+ int ret;
+
+ hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
+ if (!hdmi)
+ return -ENOMEM;
+ dev_set_drvdata(&pdev->dev, hdmi);
+ hdmi->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hdmi->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(hdmi->base)) {
+ dev_err(&pdev->dev, "Couldn't map the HDMI encoder registers\n");
+ return PTR_ERR(hdmi->base);
+ }
+
+ hdmi->bus_clk = devm_clk_get(&pdev->dev, "ahb");
+ if (IS_ERR(hdmi->bus_clk)) {
+ dev_err(&pdev->dev, "Couldn't get the HDMI bus clock\n");
+ return PTR_ERR(hdmi->bus_clk);
+ }
+ clk_prepare_enable(hdmi->bus_clk);
+
+ hdmi->mod_clk = devm_clk_get(&pdev->dev, "mod");
+ if (IS_ERR(hdmi->mod_clk)) {
+ dev_err(&pdev->dev, "Couldn't get the HDMI mod clock\n");
+ return PTR_ERR(hdmi->mod_clk);
+ }
+ clk_prepare_enable(hdmi->mod_clk);
+
+ hdmi->pll0_clk = devm_clk_get(&pdev->dev, "pll-0");
+ if (IS_ERR(hdmi->pll0_clk)) {
+ dev_err(&pdev->dev, "Couldn't get the HDMI PLL 0 clock\n");
+ return PTR_ERR(hdmi->pll0_clk);
+ }
+
+ hdmi->pll1_clk = devm_clk_get(&pdev->dev, "pll-1");
+ if (IS_ERR(hdmi->pll1_clk)) {
+ dev_err(&pdev->dev, "Couldn't get the HDMI PLL 1 clock\n");
+ return PTR_ERR(hdmi->pll1_clk);
+ }
+
+ ret = sun4i_tmds_create(hdmi);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't create the TMDS clock\n");
+ return ret;
+ }
+
+ writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
+
+#define SUN4I_HDMI_PAD_CTRL0 0xfe800000
+
+ writel(SUN4I_HDMI_PAD_CTRL0, hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
+
+ /* TODO: defines */
+ writel((6 << 3) | (2 << 10) | BIT(14) | BIT(15) |
+ BIT(19) | BIT(20) | BIT(22) | BIT(23),
+ hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+
+ /* TODO: defines */
+ writel((8 << 0) | (7 << 8) | (239 << 12) | (7 << 17) | (4 << 20) |
+ BIT(25) | BIT(27) | BIT(28) | BIT(29) | BIT(30) | BIT(31),
+ hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+
+ ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "Couldn't create the DDC clock\n");
+ return ret;
+ }
+
+ return component_add(&pdev->dev, &sun4i_hdmi_ops);
+}
+
+static int sun4i_hdmi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &sun4i_hdmi_ops);
+
+ return 0;
+}
+
+static const struct of_device_id sun4i_hdmi_of_table[] = {
+ { .compatible = "allwinner,sun5i-a10s-hdmi" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
+
+static struct platform_driver sun4i_hdmi_driver = {
+ .probe = sun4i_hdmi_probe,
+ .remove = sun4i_hdmi_remove,
+ .driver = {
+ .name = "sun4i-hdmi",
+ .of_match_table = sun4i_hdmi_of_table,
+ },
+};
+module_platform_driver(sun4i_hdmi_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
new file mode 100644
index 000000000000..40f48f1d4685
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 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-provider.h>
+
+#include "sun4i_tcon.h"
+#include "sun4i_hdmi.h"
+
+struct sun4i_tmds {
+ struct clk_hw hw;
+ struct sun4i_hdmi *hdmi;
+};
+
+static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
+{
+ return container_of(hw, struct sun4i_tmds, hw);
+}
+
+
+static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
+ unsigned long parent_rate,
+ u8 *div,
+ bool *half)
+{
+ unsigned long best_rate = 0;
+ u8 best_m = 0, m;
+ bool is_double;
+
+ for (m = 1; m < 16; m++) {
+ u8 d;
+
+ for (d = 1; d < 3; d++) {
+ unsigned long tmp_rate;
+
+ tmp_rate = parent_rate / m / d;
+
+ if (tmp_rate > rate)
+ continue;
+
+ if (!best_rate ||
+ (rate - tmp_rate) < (rate - best_rate)) {
+ best_rate = tmp_rate;
+ best_m = m;
+ is_double = d;
+ }
+ }
+ }
+
+ if (div && half) {
+ *div = best_m;
+ *half = is_double;
+ }
+
+ return best_rate;
+}
+
+
+static int sun4i_tmds_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_hw *parent;
+ unsigned long best_parent = 0;
+ unsigned long rate = req->rate;
+ int best_div = 1, best_half = 1;
+ int i, j;
+
+ printk("%s %d rate %lu\n", __func__, __LINE__, rate);
+
+ /*
+ * We only consider PLL3, since the TCON is very likely to be
+ * clocked from it, and to have the same rate than our HDMI
+ * clock, so we should not need to do anything.
+ */
+
+ parent = clk_hw_get_parent_by_index(hw, 0);
+ if (!parent)
+ return -EINVAL;
+
+ for (i = 1; i < 3; i++) {
+ for (j = 1; j < 16; j++) {
+ unsigned long ideal = rate * i * j;
+ unsigned long rounded;
+
+ rounded = clk_hw_round_rate(parent, ideal);
+
+ if (rounded == ideal) {
+ best_parent = rounded;
+ best_half = i;
+ best_div = j;
+ goto out;
+ }
+
+ if (abs(rate - rounded / i) <
+ abs(rate - best_parent / best_div)) {
+ best_parent = rounded;
+ best_div = i;
+ }
+ }
+ }
+
+out:
+ req->rate = best_parent / best_half / best_div;
+ req->best_parent_rate = best_parent;
+ req->best_parent_hw = parent;
+
+ printk("%s %d rate %lu parent rate %lu (%s) div %d half %d\n",
+ __func__, __LINE__, req->rate, req->best_parent_rate,
+ clk_hw_get_name(req->best_parent_hw),
+ best_div, best_half);
+
+ return 0;
+}
+
+static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct sun4i_tmds *tmds = hw_to_tmds(hw);
+ u32 reg;
+
+ reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+ if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
+ parent_rate /= 2;
+
+ reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+ reg = (reg >> 4) & 0xf;
+ if (!reg)
+ reg = 1;
+
+ return parent_rate / reg;
+}
+
+static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sun4i_tmds *tmds = hw_to_tmds(hw);
+ bool half;
+ u32 reg;
+ u8 div;
+
+ sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
+
+ printk("%s %d rate %lu parent rate %lu div %d half %s\n",
+ __func__, __LINE__, rate, parent_rate, div,
+ half ? "yes" : "no");
+
+ reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+ reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+ if (half)
+ reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+ writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+
+ reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+ reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
+ writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
+ tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+
+ return 0;
+}
+
+static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
+{
+ struct sun4i_tmds *tmds = hw_to_tmds(hw);
+ u32 reg;
+
+ reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+ return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
+ SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
+}
+
+static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct sun4i_tmds *tmds = hw_to_tmds(hw);
+ u32 reg;
+
+ if (index > 1)
+ return -EINVAL;
+
+ reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+ reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
+ writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
+ tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+
+ return 0;
+}
+
+static const struct clk_ops sun4i_tmds_ops = {
+ .determine_rate = sun4i_tmds_determine_rate,
+ .recalc_rate = sun4i_tmds_recalc_rate,
+ .set_rate = sun4i_tmds_set_rate,
+
+ .get_parent = sun4i_tmds_get_parent,
+ .set_parent = sun4i_tmds_set_parent,
+};
+
+int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
+{
+ struct clk_init_data init;
+ struct sun4i_tmds *tmds;
+ const char *parents[2];
+
+ parents[0] = __clk_get_name(hdmi->pll0_clk);
+ if (!parents[0])
+ return -ENODEV;
+
+ parents[1] = __clk_get_name(hdmi->pll1_clk);
+ if (!parents[1])
+ return -ENODEV;
+
+ tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
+ if (!tmds)
+ return -ENOMEM;
+
+ init.name = "hdmi-tmds";
+ init.ops = &sun4i_tmds_ops;
+ init.parent_names = parents;
+ init.num_parents = 2;
+ init.flags = CLK_SET_RATE_PARENT;
+
+ tmds->hdmi = hdmi;
+ tmds->hw.init = &init;
+
+ hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
+ if (IS_ERR(hdmi->tmds_clk))
+ return PTR_ERR(hdmi->tmds_clk);
+
+ return 0;
+}
--
git-series 0.8.11
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-03-07 10:40 +0100 |
| Subject | [PATCH 7/15] dt-bindings: display: sun4i: Add allwinner,tcon-channel property |
| Message-ID | <tijL4-5vy-7@gated-at.bofh.it> |
| In reply to | #1594016 |
The Allwinner Timings Controller has two, mutually exclusive, channels.
When the binding has been introduced, it was assumed that there would be
only a single user per channel in the system.
While this is likely for the channel 0 which only connects to LCD displays,
it turns out that the channel 1 can be connected to multiple controllers in
the SoC (HDMI and TV encoders for example). And while the simultaneous use
of HDMI and TV outputs cannot be achieved, switching from one to the other
at runtime definitely sounds plausible.
Add an extra property, allwinner,tcon-channel, to specify for a given
endpoint which TCON channel it is connected to, while falling back to the
previous mechanism if that property is missing.
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 11 ++++---
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
index 4b280672658e..18d445723c3e 100644
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
@@ -68,10 +68,13 @@ Required properties:
Documentation/devicetree/bindings/media/video-interfaces.txt. The
first port should be the input endpoint, the second one the output
- The output should have two endpoints. The first is the block
- connected to the TCON channel 0 (usually a panel or a bridge), the
- second the block connected to the TCON channel 1 (usually the TV
- encoder)
+ The output may have multiple endpoints. The TCON has two channels,
+ usually with the first channel being used for the panels interfaces
+ (RGB, LVDS, etc.), and the second being used for the outputs that
+ require another controller (TV Encoder, HDMI, etc.). The endpoints
+ will take an extra property, allwinner,tcon-channel, to specify the
+ channel the endpoint is associated to. If that property is not
+ present, the endpoint number will be used as the channel number.
On SoCs other than the A33, there is one more clock required:
- 'tcon-ch1': The clock driving the TCON channel 1
--
git-series 0.8.11
[toc] | [prev] | [next] | [standalone]
| From | Chen-Yu Tsai <wens@csie.org> |
|---|---|
| Date | 2017-03-08 04:50 +0100 |
| Subject | Re: [PATCH 7/15] dt-bindings: display: sun4i: Add allwinner,tcon-channel property |
| Message-ID | <tiALT-GB-11@gated-at.bofh.it> |
| In reply to | #1594046 |
On Tue, Mar 7, 2017 at 4:56 PM, Maxime Ripard <maxime.ripard@free-electrons.com> wrote: > The Allwinner Timings Controller has two, mutually exclusive, channels. > When the binding has been introduced, it was assumed that there would be > only a single user per channel in the system. > > While this is likely for the channel 0 which only connects to LCD displays, > it turns out that the channel 1 can be connected to multiple controllers in > the SoC (HDMI and TV encoders for example). And while the simultaneous use > of HDMI and TV outputs cannot be achieved, switching from one to the other > at runtime definitely sounds plausible. > > Add an extra property, allwinner,tcon-channel, to specify for a given > endpoint which TCON channel it is connected to, while falling back to the > previous mechanism if that property is missing. > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Chen-Yu Tsai <wens@csie.org>
[toc] | [prev] | [next] | [standalone]
| From | Maxime Ripard <maxime.ripard@free-electrons.com> |
|---|---|
| Date | 2017-03-07 10:40 +0100 |
| Subject | [PATCH 6/15] dt-bindings: display: sun4i: Add HDMI display bindings |
| Message-ID | <tijL4-5vy-21@gated-at.bofh.it> |
| In reply to | #1594016 |
One of the possible output of the display pipeline, on the SoCs that have it, is the HDMI controller. Add a binding for it. Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 21 +++++++- 1 file changed, 21 insertions(+), 0 deletions(-) diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt index b82c00449468..4b280672658e 100644 --- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -4,6 +4,27 @@ Allwinner A10 Display Pipeline The Allwinner A10 Display pipeline is composed of several components that are going to be documented below: +HDMI Encoder +------------ + +The HDMI Encoder supports the HDMI video and audio outputs, and does +CEC. It is one end of the pipeline. + +Required properties: + - compatible: value must be one of: + * allwinner,sun5i-a10s-hdmi + - reg: base address and size of memory-mapped region + - clocks: phandles to the clocks feeding the HDMI encoder + * ahb: the HDMI interface clock + * mod: the HDMI module clock + * pll-0: the first video PLL + * pll-1: the second video PLL + - clock-names: the clock names mentioned above + + - 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. + TV Encoder ---------- -- git-series 0.8.11
[toc] | [prev] | [next] | [standalone]
| From | Chen-Yu Tsai <wens@csie.org> |
|---|---|
| Date | 2017-03-08 04:50 +0100 |
| Subject | Re: [PATCH 6/15] dt-bindings: display: sun4i: Add HDMI display bindings |
| Message-ID | <tiALU-GB-13@gated-at.bofh.it> |
| In reply to | #1594047 |
On Tue, Mar 7, 2017 at 4:56 PM, Maxime Ripard <maxime.ripard@free-electrons.com> wrote: > One of the possible output of the display pipeline, on the SoCs that have > it, is the HDMI controller. > > Add a binding for it. > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Chen-Yu Tsai <wens@csie.org> TODO: A31 will also need a DDC clock.
[toc] | [prev] | [standalone]
Page 2 of 2 — ← Prev page 1 [2]
Back to top | Article view | linux.kernel
csiph-web