diff options
Diffstat (limited to 'drivers')
65 files changed, 9066 insertions, 266 deletions
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index f5c05920b1..0687b3cddd 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -1,8 +1,9 @@ obj-$(CONFIG_COMMON_CLK) += clk.o clk-fixed.o clk-divider.o clk-fixed-factor.o \ - clk-mux.o clk-gate.o clk-divider-table.o + clk-mux.o clk-gate.o obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o obj-$(CONFIG_ARCH_MVEBU) += mvebu/ obj-$(CONFIG_ARCH_MXS) += mxs/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_CLK_SOCFPGA) += socfpga.o +obj-$(CONFIG_MACH_MIPS_ATH79) += clk-ar933x.o diff --git a/drivers/clk/clk-ar933x.c b/drivers/clk/clk-ar933x.c new file mode 100644 index 0000000000..d983387a93 --- /dev/null +++ b/drivers/clk/clk-ar933x.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2013 Lucas Stach <l.stach@pengutronix.de> + * + * Based on the Linux Tegra clock code + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <common.h> +#include <init.h> +#include <io.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/err.h> + +#include <mach/ath79.h> +#include <dt-bindings/clock/ar933x-clk.h> + +static struct clk *clks[AR933X_CLK_END]; +static struct clk_onecell_data clk_data; + +struct clk_ar933x { + struct clk clk; + void __iomem *base; + u32 div_shift; + u32 div_mask; + const char *parent; +}; + +static unsigned long clk_ar933x_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct clk_ar933x *f = container_of(clk, struct clk_ar933x, clk); + unsigned long rate; + unsigned long freq; + u32 clock_ctrl; + u32 cpu_config; + u32 t; + + clock_ctrl = __raw_readl(f->base + AR933X_PLL_CLOCK_CTRL_REG); + + if (clock_ctrl & AR933X_PLL_CLOCK_CTRL_BYPASS) { + rate = parent_rate; + } else { + cpu_config = __raw_readl(f->base + AR933X_PLL_CPU_CONFIG_REG); + + t = (cpu_config >> AR933X_PLL_CPU_CONFIG_REFDIV_SHIFT) & + AR933X_PLL_CPU_CONFIG_REFDIV_MASK; + freq = parent_rate / t; + + t = (cpu_config >> AR933X_PLL_CPU_CONFIG_NINT_SHIFT) & + AR933X_PLL_CPU_CONFIG_NINT_MASK; + freq *= t; + + t = (cpu_config >> AR933X_PLL_CPU_CONFIG_OUTDIV_SHIFT) & + AR933X_PLL_CPU_CONFIG_OUTDIV_MASK; + if (t == 0) + t = 1; + + freq >>= t; + + t = ((clock_ctrl >> f->div_shift) & f->div_mask) + 1; + rate = freq / t; + } + + return rate; +} + +struct clk_ops clk_ar933x_ops = { + .recalc_rate = clk_ar933x_recalc_rate, +}; + +static struct clk *clk_ar933x(const char *name, const char *parent, + void __iomem *base, u32 div_shift, u32 div_mask) +{ + struct clk_ar933x *f = xzalloc(sizeof(*f)); + + f->parent = parent; + f->base = base; + f->div_shift = div_shift; + f->div_mask = div_mask; + + f->clk.ops = &clk_ar933x_ops; + f->clk.name = name; + f->clk.parent_names = &f->parent; + f->clk.num_parents = 1; + + clk_register(&f->clk); + + return &f->clk; +} + +static void ar933x_ref_clk_init(void __iomem *base) +{ + u32 t; + unsigned long ref_rate; + + t = ath79_reset_rr(AR933X_RESET_REG_BOOTSTRAP); + if (t & AR933X_BOOTSTRAP_REF_CLK_40) + ref_rate = (40 * 1000 * 1000); + else + ref_rate = (25 * 1000 * 1000); + + clks[AR933X_CLK_REF] = clk_fixed("ref", ref_rate); +} + +static void ar933x_pll_init(void __iomem *base) +{ + clks[AR933X_CLK_UART] = clk_fixed_factor("uart", "ref", 1, 1, + CLK_SET_RATE_PARENT); + + clks[AR933X_CLK_CPU] = clk_ar933x("cpu", "ref", base, + AR933X_PLL_CLOCK_CTRL_CPU_DIV_SHIFT, + AR933X_PLL_CLOCK_CTRL_CPU_DIV_MASK); + + clks[AR933X_CLK_DDR] = clk_ar933x("ddr", "ref", base, + AR933X_PLL_CLOCK_CTRL_DDR_DIV_SHIFT, + AR933X_PLL_CLOCK_CTRL_DDR_DIV_MASK); + + clks[AR933X_CLK_AHB] = clk_ar933x("ahb", "ref", base, + AR933X_PLL_CLOCK_CTRL_AHB_DIV_SHIFT, + AR933X_PLL_CLOCK_CTRL_AHB_DIV_MASK); + + clks[AR933X_CLK_WDT] = clk_fixed_factor("wdt", "ahb", 1, 1, + CLK_SET_RATE_PARENT); +} + +static int ar933x_clk_probe(struct device_d *dev) +{ + void __iomem *base; + + base = dev_request_mem_region(dev, 0); + if (!base) + return -EBUSY; + + ar933x_ref_clk_init(base); + ar933x_pll_init(base); + + clk_data.clks = clks; + clk_data.clk_num = ARRAY_SIZE(clks); + of_clk_add_provider(dev->device_node, of_clk_src_onecell_get, + &clk_data); + + return 0; +} + +static __maybe_unused struct of_device_id ar933x_clk_dt_ids[] = { + { + .compatible = "qca,ar933x-clk", + }, { + /* sentinel */ + } +}; + +static struct driver_d ar933x_clk_driver = { + .probe = ar933x_clk_probe, + .name = "ar933x_clk", + .of_compatible = DRV_OF_COMPAT(ar933x_clk_dt_ids), +}; + +static int ar933x_clk_init(void) +{ + return platform_driver_register(&ar933x_clk_driver); +} +postcore_initcall(ar933x_clk_init); diff --git a/drivers/clk/clk-divider-table.c b/drivers/clk/clk-divider-table.c deleted file mode 100644 index fd2d3fc3c1..0000000000 --- a/drivers/clk/clk-divider-table.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * clk-divider-table.c - generic barebox clock support. Based on Linux clk support - * - * Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ -#include <common.h> -#include <io.h> -#include <malloc.h> -#include <linux/clk.h> -#include <linux/err.h> - -struct clk_divider_table { - struct clk clk; - u8 shift; - u8 width; - void __iomem *reg; - const char *parent; - const struct clk_div_table *table; - int table_size; - int max_div_index; -}; - -static int clk_divider_set_rate(struct clk *clk, unsigned long rate, - unsigned long parent_rate) -{ - struct clk_divider_table *div = - container_of(clk, struct clk_divider_table, clk); - unsigned int val; - int i, div_index = -1; - unsigned long best = 0; - - if (rate > parent_rate) - rate = parent_rate; - if (rate < parent_rate / div->table[div->max_div_index].div) - rate = parent_rate / div->table[div->max_div_index].div; - - for (i = 0; i < div->table_size; i++) { - unsigned long now = parent_rate / div->table[i].div; - - if (now <= rate && now >= best) { - best = now; - div_index = i; - } - } - - val = readl(div->reg); - val &= ~(((1 << div->width) - 1) << div->shift); - val |= div_index << div->shift; - writel(val, div->reg); - - return 0; -} - -static unsigned long clk_divider_recalc_rate(struct clk *clk, - unsigned long parent_rate) -{ - struct clk_divider_table *div = - container_of(clk, struct clk_divider_table, clk); - unsigned int val; - - val = readl(div->reg) >> div->shift; - val &= (1 << div->width) - 1; - - if (val >= div->table_size) - return 0; - - return parent_rate / div->table[val].div; -} - -static struct clk_ops clk_divider_table_ops = { - .set_rate = clk_divider_set_rate, - .recalc_rate = clk_divider_recalc_rate, -}; - -struct clk *clk_divider_table(const char *name, - const char *parent, void __iomem *reg, u8 shift, u8 width, - const struct clk_div_table *table) -{ - struct clk_divider_table *div = xzalloc(sizeof(*div)); - const struct clk_div_table *clkt; - int ret, max_div = 0; - - div->shift = shift; - div->reg = reg; - div->width = width; - div->parent = parent; - div->clk.ops = &clk_divider_table_ops; - div->clk.name = name; - div->clk.parent_names = &div->parent; - div->clk.num_parents = 1; - div->table = table; - - for (clkt = div->table; clkt->div; clkt++) { - if (clkt->div > max_div) { - max_div = clkt->div; - div->max_div_index = div->table_size; - } - div->table_size++; - } - - ret = clk_register(&div->clk); - if (ret) { - free(div); - return ERR_PTR(ret); - } - - return &div->clk; -} diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index 3bf8105a8b..67783daabb 100644 --- a/drivers/clk/clk-divider.c +++ b/drivers/clk/clk-divider.c @@ -20,65 +20,196 @@ #include <linux/clk.h> #include <linux/err.h> -static unsigned int clk_divider_maxdiv(struct clk_divider *div) +#define div_mask(d) ((1 << ((d)->width)) - 1) + +static unsigned int _get_maxdiv(struct clk_divider *divider) { - if (div->flags & CLK_DIVIDER_ONE_BASED) - return (1 << div->width) - 1; - return 1 << div->width; + if (divider->flags & CLK_DIVIDER_ONE_BASED) + return div_mask(divider); + return div_mask(divider) + 1; } -static int clk_divider_set_rate(struct clk *clk, unsigned long rate, +static unsigned int _get_table_div(const struct clk_div_table *table, + unsigned int val) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->val == val) + return clkt->div; + return 0; +} + +static unsigned int _get_div(struct clk_divider *divider, unsigned int val) +{ + if (divider->flags & CLK_DIVIDER_ONE_BASED) + return val; + if (divider->table) + return _get_table_div(divider->table, val); + return val + 1; +} + +static unsigned int _get_table_val(const struct clk_div_table *table, + unsigned int div) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->div == div) + return clkt->val; + return 0; +} + +static unsigned int _get_val(struct clk_divider *divider, unsigned int div) +{ + if (divider->flags & CLK_DIVIDER_ONE_BASED) + return div; + if (divider->table) + return _get_table_val(divider->table, div); + return div - 1; +} + +static unsigned long clk_divider_recalc_rate(struct clk *clk, unsigned long parent_rate) { - struct clk_divider *div = container_of(clk, struct clk_divider, clk); - unsigned int val, divval; + struct clk_divider *divider = container_of(clk, struct clk_divider, clk); + unsigned int div, val; + + val = readl(divider->reg) >> divider->shift; + val &= div_mask(divider); + + div = _get_div(divider, val); + + return parent_rate / div; +} + +/* + * The reverse of DIV_ROUND_UP: The maximum number which + * divided by m is r + */ +#define MULT_ROUND_UP(r, m) ((r) * (m) + (m) - 1) + +static bool _is_valid_table_div(const struct clk_div_table *table, + unsigned int div) +{ + const struct clk_div_table *clkt; + + for (clkt = table; clkt->div; clkt++) + if (clkt->div == div) + return true; + return false; +} + +static bool _is_valid_div(struct clk_divider *divider, unsigned int div) +{ + if (divider->table) + return _is_valid_table_div(divider->table, div); + return true; +} + +static int clk_divider_bestdiv(struct clk *clk, unsigned long rate, + unsigned long *best_parent_rate) +{ + struct clk_divider *divider = container_of(clk, struct clk_divider, clk); + int i, bestdiv = 0; + unsigned long parent_rate, best = 0, now, maxdiv; + unsigned long parent_rate_saved = *best_parent_rate; - if (rate > parent_rate) - rate = parent_rate; if (!rate) rate = 1; - divval = DIV_ROUND_UP(parent_rate, rate); - if (divval > clk_divider_maxdiv(div)) - divval = clk_divider_maxdiv(div); + maxdiv = _get_maxdiv(divider); - if (!(div->flags & CLK_DIVIDER_ONE_BASED)) - divval--; + if (!(clk->flags & CLK_SET_RATE_PARENT)) { + parent_rate = *best_parent_rate; + bestdiv = DIV_ROUND_UP(parent_rate, rate); + bestdiv = bestdiv == 0 ? 1 : bestdiv; + bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv; + return bestdiv; + } - val = readl(div->reg); - val &= ~(((1 << div->width) - 1) << div->shift); - val |= divval << div->shift; - writel(val, div->reg); + /* + * The maximum divider we can use without overflowing + * unsigned long in rate * i below + */ + maxdiv = min(ULONG_MAX / rate, maxdiv); - return 0; + for (i = 1; i <= maxdiv; i++) { + if (!_is_valid_div(divider, i)) + continue; + if (rate * i == parent_rate_saved) { + /* + * It's the most ideal case if the requested rate can be + * divided from parent clock without needing to change + * parent rate, so return the divider immediately. + */ + *best_parent_rate = parent_rate_saved; + return i; + } + parent_rate = clk_round_rate(clk_get_parent(clk), + MULT_ROUND_UP(rate, i)); + now = parent_rate / i; + if (now <= rate && now > best) { + bestdiv = i; + best = now; + *best_parent_rate = parent_rate; + } + } + + if (!bestdiv) { + bestdiv = _get_maxdiv(divider); + *best_parent_rate = clk_round_rate(clk_get_parent(clk), 1); + } + + return bestdiv; } -static unsigned long clk_divider_recalc_rate(struct clk *clk, - unsigned long parent_rate) +static long clk_divider_round_rate(struct clk *clk, unsigned long rate, + unsigned long *parent_rate) { - struct clk_divider *div = container_of(clk, struct clk_divider, clk); - unsigned int val; + int div; - val = readl(div->reg) >> div->shift; - val &= (1 << div->width) - 1; + div = clk_divider_bestdiv(clk, rate, parent_rate); - if (div->flags & CLK_DIVIDER_ONE_BASED) { - if (!val) - val++; + return *parent_rate / div; +} + +static int clk_divider_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_divider *divider = container_of(clk, struct clk_divider, clk); + unsigned int div, value; + u32 val; + + if (clk->flags & CLK_SET_RATE_PARENT) { + unsigned long best_parent_rate = parent_rate; + div = clk_divider_bestdiv(clk, rate, &best_parent_rate); + clk_set_rate(clk_get_parent(clk), best_parent_rate); } else { - val++; + div = parent_rate / rate; } - return parent_rate / val; + value = _get_val(divider, div); + + if (value > div_mask(divider)) + value = div_mask(divider); + + val = readl(divider->reg); + val &= ~(div_mask(divider) << divider->shift); + val |= value << divider->shift; + writel(val, divider->reg); + + return 0; } struct clk_ops clk_divider_ops = { .set_rate = clk_divider_set_rate, .recalc_rate = clk_divider_recalc_rate, + .round_rate = clk_divider_round_rate, }; struct clk *clk_divider(const char *name, const char *parent, - void __iomem *reg, u8 shift, u8 width) + void __iomem *reg, u8 shift, u8 width, unsigned flags) { struct clk_divider *div = xzalloc(sizeof(*div)); int ret; @@ -89,6 +220,7 @@ struct clk *clk_divider(const char *name, const char *parent, div->parent = parent; div->clk.ops = &clk_divider_ops; div->clk.name = name; + div->clk.flags = flags; div->clk.parent_names = &div->parent; div->clk.num_parents = 1; @@ -102,12 +234,12 @@ struct clk *clk_divider(const char *name, const char *parent, } struct clk *clk_divider_one_based(const char *name, const char *parent, - void __iomem *reg, u8 shift, u8 width) + void __iomem *reg, u8 shift, u8 width, unsigned flags) { struct clk_divider *div; struct clk *clk; - clk = clk_divider(name, parent, reg, shift, width); + clk = clk_divider(name, parent, reg, shift, width, flags); if (IS_ERR(clk)) return clk; @@ -116,3 +248,39 @@ struct clk *clk_divider_one_based(const char *name, const char *parent, return clk; } + +struct clk *clk_divider_table(const char *name, + const char *parent, void __iomem *reg, u8 shift, u8 width, + const struct clk_div_table *table, unsigned flags) +{ + struct clk_divider *div = xzalloc(sizeof(*div)); + const struct clk_div_table *clkt; + int ret, max_div = 0; + + div->shift = shift; + div->reg = reg; + div->width = width; + div->parent = parent; + div->clk.ops = &clk_divider_ops; + div->clk.name = name; + div->clk.flags = flags; + div->clk.parent_names = &div->parent; + div->clk.num_parents = 1; + div->table = table; + + for (clkt = div->table; clkt->div; clkt++) { + if (clkt->div > max_div) { + max_div = clkt->div; + div->max_div_index = div->table_size; + } + div->table_size++; + } + + ret = clk_register(&div->clk); + if (ret) { + free(div); + return ERR_PTR(ret); + } + + return &div->clk; +} diff --git a/drivers/clk/clk-fixed-factor.c b/drivers/clk/clk-fixed-factor.c index f0ecbd3e59..cb531b146b 100644 --- a/drivers/clk/clk-fixed-factor.c +++ b/drivers/clk/clk-fixed-factor.c @@ -35,12 +35,42 @@ static unsigned long clk_fixed_factor_recalc_rate(struct clk *clk, return (parent_rate / f->div) * f->mult; } +static long clk_factor_round_rate(struct clk *clk, unsigned long rate, + unsigned long *prate) +{ + struct clk_fixed_factor *fix = container_of(clk, struct clk_fixed_factor, clk); + + if (clk->flags & CLK_SET_RATE_PARENT) { + unsigned long best_parent; + + best_parent = (rate / fix->mult) * fix->div; + *prate = clk_round_rate(clk_get_parent(clk), best_parent); + } + + return (*prate / fix->div) * fix->mult; +} + +static int clk_factor_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_fixed_factor *fix = container_of(clk, struct clk_fixed_factor, clk); + + if (clk->flags & CLK_SET_RATE_PARENT) { + printk("%s: %ld -> parent %ld\n", __func__, rate, rate * fix->div / fix->mult); + return clk_set_rate(clk_get_parent(clk), rate * fix->div / fix->mult); + } + + return 0; +} + static struct clk_ops clk_fixed_factor_ops = { + .set_rate = clk_factor_set_rate, + .round_rate = clk_factor_round_rate, .recalc_rate = clk_fixed_factor_recalc_rate, }; struct clk *clk_fixed_factor(const char *name, - const char *parent, unsigned int mult, unsigned int div) + const char *parent, unsigned int mult, unsigned int div, unsigned flags) { struct clk_fixed_factor *f = xzalloc(sizeof(*f)); int ret; @@ -50,6 +80,7 @@ struct clk *clk_fixed_factor(const char *name, f->parent = parent; f->clk.ops = &clk_fixed_factor_ops; f->clk.name = name; + f->clk.flags = flags; f->clk.parent_names = &f->parent; f->clk.num_parents = 1; diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c index baec85598d..b298b193dc 100644 --- a/drivers/clk/clk-gate.c +++ b/drivers/clk/clk-gate.c @@ -77,13 +77,15 @@ static int clk_gate_is_enabled(struct clk *clk) } static struct clk_ops clk_gate_ops = { + .set_rate = clk_parent_set_rate, + .round_rate = clk_parent_round_rate, .enable = clk_gate_enable, .disable = clk_gate_disable, .is_enabled = clk_gate_is_enabled, }; struct clk *clk_gate_alloc(const char *name, const char *parent, - void __iomem *reg, u8 shift) + void __iomem *reg, u8 shift, unsigned flags) { struct clk_gate *g = xzalloc(sizeof(*g)); @@ -92,6 +94,7 @@ struct clk *clk_gate_alloc(const char *name, const char *parent, g->shift = shift; g->clk.ops = &clk_gate_ops; g->clk.name = name; + g->clk.flags = flags; g->clk.parent_names = &g->parent; g->clk.num_parents = 1; @@ -106,12 +109,12 @@ void clk_gate_free(struct clk *clk_gate) } struct clk *clk_gate(const char *name, const char *parent, void __iomem *reg, - u8 shift) + u8 shift, unsigned flags) { struct clk *g; int ret; - g = clk_gate_alloc(name , parent, reg, shift); + g = clk_gate_alloc(name , parent, reg, shift, flags); ret = clk_register(g); if (ret) { @@ -123,12 +126,12 @@ struct clk *clk_gate(const char *name, const char *parent, void __iomem *reg, } struct clk *clk_gate_inverted(const char *name, const char *parent, - void __iomem *reg, u8 shift) + void __iomem *reg, u8 shift, unsigned flags) { struct clk *clk; struct clk_gate *g; - clk = clk_gate(name, parent, reg, shift); + clk = clk_gate(name, parent, reg, shift, flags); if (IS_ERR(clk)) return clk; diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c index b22bdd1ec7..4ce86f43d1 100644 --- a/drivers/clk/clk-mux.c +++ b/drivers/clk/clk-mux.c @@ -51,12 +51,15 @@ static int clk_mux_set_parent(struct clk *clk, u8 idx) } static struct clk_ops clk_mux_ops = { + .set_rate = clk_parent_set_rate, + .round_rate = clk_parent_round_rate, .get_parent = clk_mux_get_parent, .set_parent = clk_mux_set_parent, }; struct clk *clk_mux_alloc(const char *name, void __iomem *reg, - u8 shift, u8 width, const char **parents, u8 num_parents) + u8 shift, u8 width, const char **parents, u8 num_parents, + unsigned flags) { struct clk_mux *m = xzalloc(sizeof(*m)); @@ -65,6 +68,7 @@ struct clk *clk_mux_alloc(const char *name, void __iomem *reg, m->width = width; m->clk.ops = &clk_mux_ops; m->clk.name = name; + m->clk.flags = flags; m->clk.parent_names = parents; m->clk.num_parents = num_parents; @@ -79,12 +83,12 @@ void clk_mux_free(struct clk *clk_mux) } struct clk *clk_mux(const char *name, void __iomem *reg, - u8 shift, u8 width, const char **parents, u8 num_parents) + u8 shift, u8 width, const char **parents, u8 num_parents, unsigned flags) { struct clk *m; int ret; - m = clk_mux_alloc(name, reg, shift, width, parents, num_parents); + m = clk_mux_alloc(name, reg, shift, width, parents, num_parents, flags); ret = clk_register(m); if (ret) { diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 31d73c028c..0d259413a9 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -104,9 +104,19 @@ unsigned long clk_get_rate(struct clk *clk) long clk_round_rate(struct clk *clk, unsigned long rate) { + unsigned long parent_rate = 0; + struct clk *parent; + if (IS_ERR(clk)) return 0; + parent = clk_get_parent(clk); + if (parent) + parent_rate = clk_get_rate(parent); + + if (clk->ops->round_rate) + return clk->ops->round_rate(clk, rate, &parent_rate); + return clk_get_rate(clk); } @@ -249,11 +259,31 @@ int clk_is_enabled(struct clk *clk) return clk_is_enabled(clk); } +/* + * Generic struct clk_ops callbacks + */ int clk_is_enabled_always(struct clk *clk) { return 1; } +long clk_parent_round_rate(struct clk *clk, unsigned long rate, + unsigned long *prate) +{ + if (!(clk->flags & CLK_SET_RATE_PARENT)) + return *prate; + + return clk_round_rate(clk_get_parent(clk), rate); +} + +int clk_parent_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + if (!(clk->flags & CLK_SET_RATE_PARENT)) + return 0; + return clk_set_rate(clk_get_parent(clk), rate); +} + #if defined(CONFIG_OFTREE) && defined(CONFIG_COMMON_CLK_OF_PROVIDER) /** * struct of_clk_provider - Clock provider registration structure diff --git a/drivers/clk/mvebu/common.c b/drivers/clk/mvebu/common.c index 37cc1568a3..658ce3e81a 100644 --- a/drivers/clk/mvebu/common.c +++ b/drivers/clk/mvebu/common.c @@ -87,7 +87,7 @@ int mvebu_coreclk_probe(struct device_d *dev) 2+n, &rclk_name); desc->get_clk_ratio(base, desc->ratios[n].id, &mult, &div); clk_data.clks[2+n] = clk_fixed_factor(rclk_name, cpuclk_name, - mult, div); + mult, div, 0); WARN_ON(IS_ERR(clk_data.clks[2+n])); }; @@ -188,7 +188,7 @@ int mvebu_clk_gating_probe(struct device_d *dev) (desc[n].parent) ? desc[n].parent : default_parent; gate->bit_idx = desc[n].bit_idx; gate->clk = clk_gate(desc[n].name, parent, - base, desc[n].bit_idx); + base, desc[n].bit_idx, 0); WARN_ON(IS_ERR(gate->clk)); } diff --git a/drivers/clk/mxs/clk-imx28.c b/drivers/clk/mxs/clk-imx28.c index 05b3c2a04d..934a1940fb 100644 --- a/drivers/clk/mxs/clk-imx28.c +++ b/drivers/clk/mxs/clk-imx28.c @@ -128,7 +128,7 @@ int __init mx28_clocks_init(void __iomem *regs) clks[fec] = mxs_clk_gate("fec", "fec_sleep", ENET, 30); clks[usb0_phy] = mxs_clk_gate("usb0_phy", "pll0", PLL0CTRL0, 18); clks[usb1_phy] = mxs_clk_gate("usb1_phy", "pll1", PLL1CTRL0, 18); - clks[enet_out] = clk_gate("enet_out", "pll2", ENET, 18); + clks[enet_out] = clk_gate("enet_out", "pll2", ENET, 18, 0); clks[lcdif_comp] = mxs_clk_lcdif("lcdif_comp", clks[ref_pix], clks[lcdif_div], clks[lcdif]); diff --git a/drivers/clk/mxs/clk.h b/drivers/clk/mxs/clk.h index b4fcfa0090..7bab7b5e6e 100644 --- a/drivers/clk/mxs/clk.h +++ b/drivers/clk/mxs/clk.h @@ -34,19 +34,19 @@ static inline struct clk *mxs_clk_fixed(const char *name, int rate) static inline struct clk *mxs_clk_gate(const char *name, const char *parent_name, void __iomem *reg, u8 shift) { - return clk_gate_inverted(name, parent_name, reg, shift); + return clk_gate_inverted(name, parent_name, reg, shift, 0); } static inline struct clk *mxs_clk_mux(const char *name, void __iomem *reg, u8 shift, u8 width, const char **parent_names, int num_parents) { - return clk_mux(name, reg, shift, width, parent_names, num_parents); + return clk_mux(name, reg, shift, width, parent_names, num_parents, 0); } static inline struct clk *mxs_clk_fixed_factor(const char *name, const char *parent_name, unsigned int mult, unsigned int div) { - return clk_fixed_factor(name, parent_name, mult, div); + return clk_fixed_factor(name, parent_name, mult, div, 0); } #endif /* __MXS_CLK_H */ diff --git a/drivers/clk/tegra/clk-periph.c b/drivers/clk/tegra/clk-periph.c index e969495875..c970f63afa 100644 --- a/drivers/clk/tegra/clk-periph.c +++ b/drivers/clk/tegra/clk-periph.c @@ -140,12 +140,12 @@ struct clk *_tegra_clk_register_periph(const char *name, } periph->mux = clk_mux_alloc(NULL, clk_base + reg_offset, 30, 2, - parent_names, num_parents); + parent_names, num_parents, 0); if (!periph->mux) goto out_mux; periph->gate = clk_gate_alloc(NULL, NULL, clk_base + 0x10 + - ((id >> 3) & 0xc), id & 0x1f); + ((id >> 3) & 0xc), id & 0x1f, 0); if (!periph->gate) goto out_gate; diff --git a/drivers/clk/tegra/clk-tegra20.c b/drivers/clk/tegra/clk-tegra20.c index f68c811a8d..cfb719f431 100644 --- a/drivers/clk/tegra/clk-tegra20.c +++ b/drivers/clk/tegra/clk-tegra20.c @@ -62,7 +62,7 @@ static void tegra20_osc_clk_init(void) clks[clk_32k] = clk_fixed("clk_32k", 32768); clks[pll_ref] = clk_fixed_factor("pll_ref", "clk_m", 1, - get_pll_ref_div()); + get_pll_ref_div(), 0); } /* PLL frequency tables */ diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 4ef25ec45b..43974f03c2 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -14,6 +14,29 @@ config CLOCKSOURCE_CLPS711X bool depends on ARCH_CLPS711X +config CLOCKSOURCE_DUMMY + bool "Enable dummy software-only clocksource" + help + When porting barebox to a new SoC there might be a case + of broken or absent clocksource. This causes barebox serial + console to be non functional. + To solve the problem this software-only clocksource driver is used. + WARNING!!! This clocksource doesn't provide correct timing. + To adjust this clocksource please use CONFIG_CLOCKSOURCE_DUMMY_RATE. + The bigger rate valuest makes clocksource "faster". + It's possible to add this clocksource unconditionally. + This clocksource starts very early (pure_initcall) so + real clocksource will take over. + This can help if initialization order is wrong so that + the time functions are used before the real clocksource + was initialized. + +config CLOCKSOURCE_DUMMY_RATE + int + prompt "dummy clocksource rate" + depends on CLOCKSOURCE_DUMMY + default 1000 + config CLOCKSOURCE_MVEBU bool depends on ARCH_MVEBU diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 25b7f460da..834a15d1e9 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_AMBA_SP804) += amba-sp804.o obj-$(CONFIG_ARM_SMP_TWD) += arm_smp_twd.o obj-$(CONFIG_CLOCKSOURCE_BCM2835) += bcm2835.o obj-$(CONFIG_CLOCKSOURCE_CLPS711X) += clps711x.o +obj-$(CONFIG_CLOCKSOURCE_DUMMY) += dummy.o obj-$(CONFIG_CLOCKSOURCE_MVEBU) += mvebu.o obj-$(CONFIG_CLOCKSOURCE_NOMADIK) += nomadik.o obj-$(CONFIG_CLOCKSOURCE_ORION) += orion.o diff --git a/drivers/clocksource/dummy.c b/drivers/clocksource/dummy.c new file mode 100644 index 0000000000..154a8cd672 --- /dev/null +++ b/drivers/clocksource/dummy.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com> + * + * This file is part of barebox. + * See file CREDITS for list of people who contributed to this project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <common.h> +#include <init.h> +#include <clock.h> + +static uint64_t dummy_counter; + +static uint64_t dummy_cs_read(void) +{ + static int first; + + if (!first) { + pr_warn("Warning: Using dummy clocksource\n"); + first = 1; + } + + dummy_counter += CONFIG_CLOCKSOURCE_DUMMY_RATE; + + return dummy_counter; +} + +static struct clocksource dummy_cs = { + .read = dummy_cs_read, + .mask = CLOCKSOURCE_MASK(32), +}; + +static int clocksource_init(void) +{ + dummy_counter = 0; + + clocks_calc_mult_shift(&dummy_cs.mult, &dummy_cs.shift, + 100000000, NSEC_PER_SEC, 10); + + pr_debug("clocksource_init: mult=%08x, shift=%08x\n", + dummy_cs.mult, dummy_cs.shift); + init_clock(&dummy_cs); + + return 0; +} +pure_initcall(clocksource_init); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 7c426563e7..7302955d87 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -20,6 +20,13 @@ config GPIO_CLPS711X help Say yes here to enable the GPIO driver for the CLPS711X CPUs +config GPIO_DAVINCI + bool "TI Davinci/Keystone GPIO support" + default y if ARCH_DAVINCI + depends on ARM && ARCH_DAVINCI + help + Say yes here to enable GPIO support for TI Davinci/Keystone SoCs. + config GPIO_GENERIC_PLATFORM bool "Generic memory-mapped GPIO controller support" select GPIO_GENERIC diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index b7c536d21b..68a76a3745 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib.o obj-$(CONFIG_GPIO_BCM2835) += gpio-bcm2835.o +obj-$(CONFIG_GPIO_DAVINCI) += gpio-davinci.o obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o obj-$(CONFIG_GPIO_IMX) += gpio-imx.o diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c new file mode 100644 index 0000000000..7d15b855f0 --- /dev/null +++ b/drivers/gpio/gpio-davinci.c @@ -0,0 +1,211 @@ +/* + * TI DaVinci GPIO Support + * + * Copyright (c) 2006-2007 David Brownell + * Copyright (c) 2007, MontaVista Software, Inc. <source@mvista.com> + * Copyright (c) 2014 Antony Pavlov <antonynpavlov@gmail.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 <common.h> +#include <gpio.h> +#include <init.h> +#include <io.h> +#include <linux/err.h> + +#define readl_relaxed readl +#define writel_relaxed writel + +struct davinci_gpio_regs { + u32 dir; + u32 out_data; + u32 set_data; + u32 clr_data; + u32 in_data; + u32 set_rising; + u32 clr_rising; + u32 set_falling; + u32 clr_falling; + u32 intstat; +}; + +struct davinci_gpio_controller { + struct gpio_chip chip; + /* Serialize access to GPIO registers */ + void __iomem *regs; + void __iomem *set_data; + void __iomem *clr_data; + void __iomem *in_data; +}; + +#define chip2controller(chip) \ + container_of(chip, struct davinci_gpio_controller, chip) + +static struct davinci_gpio_regs __iomem *gpio2regs(void __iomem *gpio_base, + unsigned gpio) +{ + void __iomem *ptr; + + if (gpio < 32 * 1) + ptr = gpio_base + 0x10; + else if (gpio < 32 * 2) + ptr = gpio_base + 0x38; + else if (gpio < 32 * 3) + ptr = gpio_base + 0x60; + else if (gpio < 32 * 4) + ptr = gpio_base + 0x88; + else if (gpio < 32 * 5) + ptr = gpio_base + 0xb0; + else + ptr = NULL; + return ptr; +} + +static int davinci_get_direction(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_gpio_controller *d = chip2controller(chip); + struct davinci_gpio_regs __iomem *g = d->regs; + + return ((readl_relaxed(&g->dir)) & (1 << offset)) ? + GPIOF_DIR_IN : GPIOF_DIR_OUT; +} + +static inline int __davinci_direction(struct gpio_chip *chip, + unsigned offset, bool out, int value) +{ + struct davinci_gpio_controller *d = chip2controller(chip); + struct davinci_gpio_regs __iomem *g = d->regs; + u32 temp; + u32 mask = 1 << offset; + + temp = readl_relaxed(&g->dir); + if (out) { + temp &= ~mask; + writel_relaxed(mask, value ? &g->set_data : &g->clr_data); + } else { + temp |= mask; + } + writel_relaxed(temp, &g->dir); + + return 0; +} + +static int davinci_direction_in(struct gpio_chip *chip, unsigned offset) +{ + return __davinci_direction(chip, offset, false, 0); +} + +static int +davinci_direction_out(struct gpio_chip *chip, unsigned offset, int value) +{ + return __davinci_direction(chip, offset, true, value); +} + +/* + * Read the pin's value (works even if it's set up as output); + * returns zero/nonzero. + * + * Note that changes are synched to the GPIO clock, so reading values back + * right after you've set them may give old values. + */ +static int davinci_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct davinci_gpio_controller *d = chip2controller(chip); + struct davinci_gpio_regs __iomem *g = d->regs; + + return (1 << offset) & readl_relaxed(&g->in_data); +} + +/* + * Assuming the pin is muxed as a gpio output, set its output value. + */ +static void +davinci_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct davinci_gpio_controller *d = chip2controller(chip); + struct davinci_gpio_regs __iomem *g = d->regs; + + writel_relaxed((1 << offset), value ? &g->set_data : &g->clr_data); +} + +static struct gpio_ops davinci_gpio_ops = { + .direction_input = davinci_direction_in, + .direction_output = davinci_direction_out, + .get_direction = davinci_get_direction, + .get = davinci_gpio_get, + .set = davinci_gpio_set, +}; + +static int davinci_gpio_probe(struct device_d *dev) +{ + void __iomem *gpio_base; + int ret; + u32 val; + int i, base; + unsigned ngpio; + struct davinci_gpio_controller *chips; + + ret = of_property_read_u32(dev->device_node, "ti,ngpio", &val); + if (ret) { + dev_err(dev, "could not read 'ti,ngpio' property\n"); + return -EINVAL; + } + + ngpio = val; + + if (WARN_ON(ARCH_NR_GPIOS < ngpio)) + ngpio = ARCH_NR_GPIOS; + + chips = xzalloc((ngpio / 32 + 1) * sizeof(*chips)); + + gpio_base = dev_request_mem_region(dev, 0); + if (!gpio_base) { + dev_err(dev, "could not get memory region\n"); + return -ENODEV; + } + + for (i = 0, base = 0; base < ngpio; i++, base += 32) { + struct davinci_gpio_regs __iomem *regs; + struct gpio_chip *gc; + + gc = &chips[i].chip; + gc->ops = &davinci_gpio_ops; + + gc->dev = dev; + gc->base = base; + gc->ngpio = ngpio - base; + if (gc->ngpio > 32) + gc->ngpio = 32; + + regs = gpio2regs(gpio_base, base); + chips[i].regs = regs; + chips[i].set_data = ®s->set_data; + chips[i].clr_data = ®s->clr_data; + chips[i].in_data = ®s->in_data; + + gpiochip_add(gc); + } + + return 0; +} + +static struct of_device_id davinci_gpio_ids[] = { + { .compatible = "ti,dm6441-gpio", }, + { /* sentinel */ }, +}; + +static struct driver_d davinci_gpio_driver = { + .name = "davinci_gpio", + .probe = davinci_gpio_probe, + .of_compatible = DRV_OF_COMPAT(davinci_gpio_ids), +}; + +static int davinci_gpio_drv_reg(void) +{ + return platform_driver_register(&davinci_gpio_driver); +} +coredevice_initcall(davinci_gpio_drv_reg); diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index f657c287f7..6b390478fa 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -587,4 +587,4 @@ static struct driver_d i2c_fsl_driver = { .name = DRIVER_NAME, .of_compatible = DRV_OF_COMPAT(imx_i2c_dt_ids), }; -device_platform_driver(i2c_fsl_driver); +coredevice_platform_driver(i2c_fsl_driver); diff --git a/drivers/i2c/i2c.c b/drivers/i2c/i2c.c index e289a3569b..9873957015 100644 --- a/drivers/i2c/i2c.c +++ b/drivers/i2c/i2c.c @@ -405,6 +405,17 @@ struct i2c_adapter *i2c_get_adapter(int busnum) return NULL; } +struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node) +{ + struct i2c_adapter *adap; + + list_for_each_entry(adap, &adapter_list, list) + if (adap->dev.device_node == node) + return adap; + + return NULL; +} + /** * i2c_add_numbered_adapter - declare i2c adapter, use static bus number * @adapter: the adapter to register (with adap->nr initialized) diff --git a/drivers/input/gpio_keys.c b/drivers/input/gpio_keys.c index 9486b9a786..d017594662 100644 --- a/drivers/input/gpio_keys.c +++ b/drivers/input/gpio_keys.c @@ -121,7 +121,7 @@ static int gpio_keys_probe_dt(struct gpio_keys *gk, struct device_d *dev) struct device_node *npkey, *np = dev->device_node; int i = 0, ret; - if (!IS_ENABLED(CONFIG_OFDEVICE)) + if (!IS_ENABLED(CONFIG_OFDEVICE) || !IS_ENABLED(CONFIG_OF_GPIO)) return -ENODEV; gk->nbuttons = of_get_child_count(np); diff --git a/drivers/mci/mci-core.c b/drivers/mci/mci-core.c index a18b555f27..1dca0c0137 100644 --- a/drivers/mci/mci-core.c +++ b/drivers/mci/mci-core.c @@ -1813,7 +1813,7 @@ void mci_of_parse(struct mci_host *host) break; default: dev_err(host->hw_dev, - "Invalid \"bus-width\" value %ud!\n", bus_width); + "Invalid \"bus-width\" value %u!\n", bus_width); } /* f_max is obtained from the optional "max-frequency" property */ diff --git a/drivers/mtd/nand/nand_omap_gpmc.c b/drivers/mtd/nand/nand_omap_gpmc.c index 3053a8e891..59712b8119 100644 --- a/drivers/mtd/nand/nand_omap_gpmc.c +++ b/drivers/mtd/nand/nand_omap_gpmc.c @@ -940,7 +940,7 @@ static int gpmc_nand_probe(struct device_d *pdev) switch (pdata->device_width) { case 0: - printk("probe buswidth\n"); + dev_dbg(pdev, "probing buswidth\n"); nand->options |= NAND_BUSWIDTH_AUTO; break; case 8: diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index da21f69e7e..b908fcda0b 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -104,6 +104,8 @@ struct ubi_device *ubi_get_device(int ubi_num) struct ubi_device *ubi; ubi = ubi_devices[ubi_num]; + if (!ubi) + return NULL; ubi->ref_count++; diff --git a/drivers/net/ar231x.c b/drivers/net/ar231x.c index 5c091140ac..515de17b10 100644 --- a/drivers/net/ar231x.c +++ b/drivers/net/ar231x.c @@ -419,15 +419,9 @@ static int ar231x_eth_probe(struct device_d *dev) return 0; } -static void ar231x_eth_remove(struct device_d *dev) -{ - -} - static struct driver_d ar231x_eth_driver = { .name = "ar231x_eth", .probe = ar231x_eth_probe, - .remove = ar231x_eth_remove, }; static int ar231x_eth_driver_init(void) diff --git a/drivers/net/at91_ether.c b/drivers/net/at91_ether.c index 25924cf7e2..bf2f957a36 100644 --- a/drivers/net/at91_ether.c +++ b/drivers/net/at91_ether.c @@ -357,13 +357,8 @@ static int at91_ether_probe(struct device_d *dev) return 0; } -static void at91_ether_remove(struct device_d *dev) -{ -} - static struct driver_d at91_ether_driver = { .name = "at91_ether", .probe = at91_ether_probe, - .remove = at91_ether_remove, }; device_platform_driver(at91_ether_driver); diff --git a/drivers/net/designware.c b/drivers/net/designware.c index ecb4656e9d..e706f54b31 100644 --- a/drivers/net/designware.c +++ b/drivers/net/designware.c @@ -477,10 +477,6 @@ static int dwc_ether_probe(struct device_d *dev) return 0; } -static void dwc_ether_remove(struct device_d *dev) -{ -} - static __maybe_unused struct of_device_id dwc_ether_compatible[] = { { .compatible = "snps,dwmac-3.70a", @@ -493,7 +489,6 @@ static __maybe_unused struct of_device_id dwc_ether_compatible[] = { static struct driver_d dwc_ether_driver = { .name = "designware_eth", .probe = dwc_ether_probe, - .remove = dwc_ether_remove, .of_compatible = DRV_OF_COMPAT(dwc_ether_compatible), }; device_platform_driver(dwc_ether_driver); diff --git a/drivers/of/base.c b/drivers/of/base.c index 104b6daeaf..c440a69e6c 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1841,8 +1841,11 @@ int of_device_is_stdout_path(struct device_d *dev) struct device_node *dn; const char *name; - name = of_get_property(of_chosen, "linux,stdout-path", NULL); - if (name == NULL) + name = of_get_property(of_chosen, "stdout-path", NULL); + if (!name) + name = of_get_property(of_chosen, "linux,stdout-path", NULL); + + if (!name) return 0; dn = of_find_node_by_path(name); diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 11fc155074..f51c6e6b02 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -14,6 +14,13 @@ config SERIAL_AMBA_PL011 If unsure, say N. +config DRIVER_SERIAL_AR933X + bool "AR933X serial driver" + depends on MACH_MIPS_ATH79 + help + If you have an Atheros AR933X SOC based board and want to use the + built-in UART of the SoC, say Y to this option. + config DRIVER_SERIAL_IMX depends on ARCH_IMX default y diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 93790b5349..e1865f725a 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_DRIVER_SERIAL_ARM_DCC) += arm_dcc.o obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o +obj-$(CONFIG_DRIVER_SERIAL_AR933X) += serial_ar933x.o obj-$(CONFIG_DRIVER_SERIAL_IMX) += serial_imx.o obj-$(CONFIG_DRIVER_SERIAL_STM378X) += stm-serial.o obj-$(CONFIG_DRIVER_SERIAL_ATMEL) += atmel.o diff --git a/drivers/serial/serial_ar933x.c b/drivers/serial/serial_ar933x.c new file mode 100644 index 0000000000..27cccba119 --- /dev/null +++ b/drivers/serial/serial_ar933x.c @@ -0,0 +1,204 @@ +/* + * based on linux.git/drivers/tty/serial/serial_ar933x.c + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <common.h> +#include <driver.h> +#include <init.h> +#include <malloc.h> +#include <io.h> +#include <asm-generic/div64.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include "serial_ar933x.h" + +#define AR933X_UART_MAX_SCALE 0xff +#define AR933X_UART_MAX_STEP 0xffff + +struct ar933x_uart_priv { + void __iomem *base; + struct clk *clk; +}; + +static inline void ar933x_serial_writel(struct console_device *cdev, + u32 b, int offset) +{ + struct ar933x_uart_priv *priv = cdev->dev->priv; + + cpu_writel(b, priv->base + offset); +} + +static inline u32 ar933x_serial_readl(struct console_device *cdev, + int offset) +{ + struct ar933x_uart_priv *priv = cdev->dev->priv; + + return cpu_readl(priv->base + offset); +} + +/* + * baudrate = (clk / (scale + 1)) * (step * (1 / 2^17)) + * take from linux. + */ +static unsigned long ar933x_uart_get_baud(unsigned int clk, + unsigned int scale, + unsigned int step) +{ + u64 t; + u32 div; + + div = (2 << 16) * (scale + 1); + t = clk; + t *= step; + t += (div / 2); + do_div(t, div); + + return t; +} + +static void ar933x_uart_get_scale_step(unsigned int clk, + unsigned int baud, + unsigned int *scale, + unsigned int *step) +{ + unsigned int tscale; + long min_diff; + + *scale = 0; + *step = 0; + + min_diff = baud; + for (tscale = 0; tscale < AR933X_UART_MAX_SCALE; tscale++) { + u64 tstep; + int diff; + + tstep = baud * (tscale + 1); + tstep *= (2 << 16); + do_div(tstep, clk); + + if (tstep > AR933X_UART_MAX_STEP) + break; + + diff = abs(ar933x_uart_get_baud(clk, tscale, tstep) - baud); + if (diff < min_diff) { + min_diff = diff; + *scale = tscale; + *step = tstep; + } + } +} + +static int ar933x_serial_setbaudrate(struct console_device *cdev, int baudrate) +{ + struct ar933x_uart_priv *priv = cdev->dev->priv; + unsigned int scale, step; + + ar933x_uart_get_scale_step(clk_get_rate(priv->clk), baudrate, &scale, + &step); + ar933x_serial_writel(cdev, (scale << AR933X_UART_CLOCK_SCALE_S) | step, + AR933X_UART_CLOCK_REG); + + return 0; +} + +static void ar933x_serial_putc(struct console_device *cdev, char ch) +{ + u32 data; + + /* wait transmitter ready */ + data = ar933x_serial_readl(cdev, AR933X_UART_DATA_REG); + while (!(data & AR933X_UART_DATA_TX_CSR)) + data = ar933x_serial_readl(cdev, AR933X_UART_DATA_REG); + + data = (ch & AR933X_UART_DATA_TX_RX_MASK) | AR933X_UART_DATA_TX_CSR; + ar933x_serial_writel(cdev, data, AR933X_UART_DATA_REG); +} + +static int ar933x_serial_tstc(struct console_device *cdev) +{ + u32 rdata; + + rdata = ar933x_serial_readl(cdev, AR933X_UART_DATA_REG); + + return rdata & AR933X_UART_DATA_RX_CSR; +} + +static int ar933x_serial_getc(struct console_device *cdev) +{ + u32 rdata; + + while (!ar933x_serial_tstc(cdev)) + ; + + rdata = ar933x_serial_readl(cdev, AR933X_UART_DATA_REG); + + /* remove the character from the FIFO */ + ar933x_serial_writel(cdev, AR933X_UART_DATA_RX_CSR, + AR933X_UART_DATA_REG); + + return rdata & AR933X_UART_DATA_TX_RX_MASK; +} + +static int ar933x_serial_probe(struct device_d *dev) +{ + struct console_device *cdev; + struct ar933x_uart_priv *priv; + u32 uart_cs; + + cdev = xzalloc(sizeof(struct console_device)); + priv = xzalloc(sizeof(struct ar933x_uart_priv)); + priv->base = dev_request_mem_region(dev, 0); + dev->priv = priv; + + cdev->dev = dev; + cdev->tstc = ar933x_serial_tstc; + cdev->putc = ar933x_serial_putc; + cdev->getc = ar933x_serial_getc; + cdev->setbrg = ar933x_serial_setbaudrate; + + priv->clk = clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "unable to get UART clock\n"); + return PTR_ERR(priv->clk); + } + + uart_cs = (AR933X_UART_CS_IF_MODE_DCE << AR933X_UART_CS_IF_MODE_S) + | AR933X_UART_CS_TX_READY_ORIDE + | AR933X_UART_CS_RX_READY_ORIDE; + ar933x_serial_writel(cdev, uart_cs, AR933X_UART_CS_REG); + /* FIXME: need ar933x_serial_init_port(cdev); */ + + console_register(cdev); + + return 0; +} + +static struct of_device_id ar933x_serial_dt_ids[] = { + { + .compatible = "qca,ar9330-uart", + }, { + /* sentinel */ + }, +}; + +static struct driver_d ar933x_serial_driver = { + .name = "ar933x_serial", + .probe = ar933x_serial_probe, + .of_compatible = DRV_OF_COMPAT(ar933x_serial_dt_ids), +}; +console_platform_driver(ar933x_serial_driver); diff --git a/drivers/serial/serial_ar933x.h b/drivers/serial/serial_ar933x.h new file mode 100644 index 0000000000..f55f0fa3d7 --- /dev/null +++ b/drivers/serial/serial_ar933x.h @@ -0,0 +1,69 @@ +/* + * Atheros AR933X UART defines + * + * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef __AR933X_UART_H +#define __AR933X_UART_H + +#include <linux/bitops.h> + +#define AR933X_UART_REGS_SIZE 20 +#define AR933X_UART_FIFO_SIZE 16 + +#define AR933X_UART_DATA_REG 0x00 +#define AR933X_UART_CS_REG 0x04 +#define AR933X_UART_CLOCK_REG 0x08 +#define AR933X_UART_INT_REG 0x0c +#define AR933X_UART_INT_EN_REG 0x10 + +#define AR933X_UART_DATA_TX_RX_MASK 0xff +#define AR933X_UART_DATA_RX_CSR BIT(8) +#define AR933X_UART_DATA_TX_CSR BIT(9) + +#define AR933X_UART_CS_PARITY_S 0 +#define AR933X_UART_CS_PARITY_M 0x3 +#define AR933X_UART_CS_PARITY_NONE 0 +#define AR933X_UART_CS_PARITY_ODD 1 +#define AR933X_UART_CS_PARITY_EVEN 2 +#define AR933X_UART_CS_IF_MODE_S 2 +#define AR933X_UART_CS_IF_MODE_M 0x3 +#define AR933X_UART_CS_IF_MODE_NONE 0 +#define AR933X_UART_CS_IF_MODE_DTE 1 +#define AR933X_UART_CS_IF_MODE_DCE 2 +#define AR933X_UART_CS_FLOW_CTRL_S 4 +#define AR933X_UART_CS_FLOW_CTRL_M 0x3 +#define AR933X_UART_CS_DMA_EN BIT(6) +#define AR933X_UART_CS_TX_READY_ORIDE BIT(7) +#define AR933X_UART_CS_RX_READY_ORIDE BIT(8) +#define AR933X_UART_CS_TX_READY BIT(9) +#define AR933X_UART_CS_RX_BREAK BIT(10) +#define AR933X_UART_CS_TX_BREAK BIT(11) +#define AR933X_UART_CS_HOST_INT BIT(12) +#define AR933X_UART_CS_HOST_INT_EN BIT(13) +#define AR933X_UART_CS_TX_BUSY BIT(14) +#define AR933X_UART_CS_RX_BUSY BIT(15) + +#define AR933X_UART_CLOCK_STEP_M 0xffff +#define AR933X_UART_CLOCK_SCALE_M 0xfff +#define AR933X_UART_CLOCK_SCALE_S 16 +#define AR933X_UART_CLOCK_STEP_M 0xffff + +#define AR933X_UART_INT_RX_VALID BIT(0) +#define AR933X_UART_INT_TX_READY BIT(1) +#define AR933X_UART_INT_RX_FRAMING_ERR BIT(2) +#define AR933X_UART_INT_RX_OFLOW_ERR BIT(3) +#define AR933X_UART_INT_TX_OFLOW_ERR BIT(4) +#define AR933X_UART_INT_RX_PARITY_ERR BIT(5) +#define AR933X_UART_INT_RX_BREAK_ON BIT(6) +#define AR933X_UART_INT_RX_BREAK_OFF BIT(7) +#define AR933X_UART_INT_RX_FULL BIT(8) +#define AR933X_UART_INT_TX_EMPTY BIT(9) +#define AR933X_UART_INT_ALLINTS 0x3ff + +#endif /* __AR933X_UART_H */ diff --git a/drivers/serial/serial_ns16550.c b/drivers/serial/serial_ns16550.c index f1da44b7f7..0c00eb182c 100644 --- a/drivers/serial/serial_ns16550.c +++ b/drivers/serial/serial_ns16550.c @@ -48,6 +48,7 @@ struct ns16550_priv { struct NS16550_plat plat; int access_width; struct clk *clk; + uint32_t fcrval; }; static inline struct ns16550_priv *to_ns16550_priv(struct console_device *cdev) @@ -157,18 +158,13 @@ static int ns16550_setbaudrate(struct console_device *cdev, int baud_rate) { unsigned int baud_divisor = ns16550_calc_divisor(cdev, baud_rate); struct ns16550_priv *priv = to_ns16550_priv(cdev); - struct NS16550_plat *plat = &priv->plat; ns16550_write(cdev, LCR_BKSE, lcr); ns16550_write(cdev, baud_divisor & 0xff, dll); ns16550_write(cdev, (baud_divisor >> 8) & 0xff, dlm); ns16550_write(cdev, LCRVAL, lcr); ns16550_write(cdev, MCRVAL, mcr); - - if (plat->flags & NS16650_FLAG_DISABLE_FIFO) - ns16550_write(cdev, FCRVAL & ~FCR_FIFO_EN, fcr); - else - ns16550_write(cdev, FCRVAL, fcr); + ns16550_write(cdev, priv->fcrval, fcr); return 0; } @@ -185,6 +181,15 @@ static void ns16550_serial_init_port(struct console_device *cdev) ns16550_write(cdev, 0x00, ier); } +static void ns16450_serial_init_port(struct console_device *cdev) +{ + struct ns16550_priv *priv = to_ns16550_priv(cdev); + + priv->fcrval &= ~FCR_FIFO_EN; + + ns16550_serial_init_port(cdev); +} + #define omap_mdr1 8 static void ns16550_omap_init_port(struct console_device *cdev) @@ -192,7 +197,17 @@ static void ns16550_omap_init_port(struct console_device *cdev) ns16550_serial_init_port(cdev); ns16550_write(cdev, 0x07, omap_mdr1); /* Disable */ - ns16550_write(cdev, 0x00, omap_mdr1); + ns16550_write(cdev, 0x00, omap_mdr1); +} + +#define JZ_FCR_UME 0x10 /* Uart Module Enable */ + +static void ns16550_jz_init_port(struct console_device *cdev) +{ + struct ns16550_priv *priv = to_ns16550_priv(cdev); + + priv->fcrval |= JZ_FCR_UME; + ns16550_serial_init_port(cdev); } /*********** Exposed Functions **********************************/ @@ -246,6 +261,10 @@ static void ns16550_probe_dt(struct device_d *dev, struct ns16550_priv *priv) of_property_read_u32(np, "reg-shift", &priv->plat.shift); } +static struct ns16550_drvdata ns16450_drvdata = { + .init_port = ns16450_serial_init_port, +}; + static struct ns16550_drvdata ns16550_drvdata = { .init_port = ns16550_serial_init_port, }; @@ -255,6 +274,10 @@ static __maybe_unused struct ns16550_drvdata omap_drvdata = { .linux_console_name = "ttyO", }; +static __maybe_unused struct ns16550_drvdata jz_drvdata = { + .init_port = ns16550_jz_init_port, +}; + /** * @brief Probe entry point -called on the first match for device * @@ -316,6 +339,11 @@ static int ns16550_probe(struct device_d *dev) cdev->setbrg = ns16550_setbaudrate; cdev->linux_console_name = devtype->linux_console_name; + if (plat && (plat->flags & NS16650_FLAG_DISABLE_FIFO)) + priv->fcrval = FCRVAL & ~FCR_FIFO_EN; + else + priv->fcrval = FCRVAL; + devtype->init_port(cdev); return console_register(cdev); @@ -328,6 +356,9 @@ err: static struct of_device_id ns16550_serial_dt_ids[] = { { + .compatible = "ns16450", + .data = (unsigned long)&ns16450_drvdata, + }, { .compatible = "ns16550a", .data = (unsigned long)&ns16550_drvdata, }, { @@ -346,6 +377,12 @@ static struct of_device_id ns16550_serial_dt_ids[] = { .data = (unsigned long)&omap_drvdata, }, #endif +#if IS_ENABLED(CONFIG_MACH_MIPS_XBURST) + { + .compatible = "ingenic,jz4740-uart", + .data = (unsigned long)&jz_drvdata, + }, +#endif { /* sentinel */ }, diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 11b6c186e3..71d0ecf39c 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -994,6 +994,8 @@ static struct usb_gadget_driver composite_driver = { */ int usb_composite_register(struct usb_composite_driver *driver) { + int ret; + if (!driver || !driver->dev || !driver->bind || composite) return -EINVAL; @@ -1002,7 +1004,12 @@ int usb_composite_register(struct usb_composite_driver *driver) composite_driver.function = (char *) driver->name; composite = driver; - return usb_gadget_register_driver(&composite_driver); + ret = usb_gadget_register_driver(&composite_driver); + + if (ret) + composite = NULL; + + return ret; } /** diff --git a/drivers/usb/gadget/dfu.c b/drivers/usb/gadget/dfu.c index 76b5def33c..d0f2155e49 100644 --- a/drivers/usb/gadget/dfu.c +++ b/drivers/usb/gadget/dfu.c @@ -703,7 +703,9 @@ int usb_dfu_register(struct usb_dfu_pdata *pdata) strings_dev[STRING_MANUFACTURER_IDX].s = pdata->manufacturer; strings_dev[STRING_PRODUCT_IDX].s = pdata->productname; - usb_composite_register(&dfu_driver); + ret = usb_composite_register(&dfu_driver); + if (ret) + return ret; while (1) { ret = usb_gadget_poll(); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 5539266226..34177b3f08 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -77,6 +77,8 @@ config DRIVER_VIDEO_BCM2835 help Add support for the BCM2835/VideoCore frame buffer device. +source drivers/video/imx-ipu-v3/Kconfig + config DRIVER_VIDEO_SIMPLEFB bool "Simple framebuffer support" depends on OFTREE @@ -84,4 +86,10 @@ config DRIVER_VIDEO_SIMPLEFB Add support for setting up the kernel's simple framebuffer driver based on the active barebox framebuffer. +config DRIVER_VIDEO_EDID + bool "Add EDID support" + help + This enabled support for reading and parsing EDID data from an attached + monitor. + endif diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 31edfca2b0..ae9f6e545e 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -1,4 +1,6 @@ obj-$(CONFIG_VIDEO) += fb.o +obj-$(CONFIG_DRIVER_VIDEO_EDID) += edid.o +obj-$(CONFIG_OFDEVICE) += of_display_timing.o obj-$(CONFIG_DRIVER_VIDEO_ATMEL) += atmel_lcdfb.o atmel_lcdfb_core.o obj-$(CONFIG_DRIVER_VIDEO_ATMEL_HLCD) += atmel_hlcdfb.o atmel_lcdfb_core.o @@ -11,3 +13,4 @@ obj-$(CONFIG_DRIVER_VIDEO_SDL) += sdl.o obj-$(CONFIG_DRIVER_VIDEO_OMAP) += omap.o obj-$(CONFIG_DRIVER_VIDEO_BCM2835) += bcm2835.o obj-$(CONFIG_DRIVER_VIDEO_SIMPLEFB) += simplefb.o +obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += imx-ipu-v3/ diff --git a/drivers/video/atmel_lcdfb_core.c b/drivers/video/atmel_lcdfb_core.c index bed540da9f..d03922b2b6 100644 --- a/drivers/video/atmel_lcdfb_core.c +++ b/drivers/video/atmel_lcdfb_core.c @@ -269,9 +269,9 @@ int atmel_lcdc_register(struct device_d *dev, struct atmel_lcdfb_devdata *data) info = &sinfo->info; info->priv = sinfo; info->fbops = &atmel_lcdc_ops; - info->mode_list = pdata->mode_list; - info->num_modes = pdata->num_modes; - info->mode = &info->mode_list[0]; + info->modes.modes = pdata->mode_list; + info->modes.num_modes = pdata->num_modes; + info->mode = &info->modes.modes[0]; info->xres = info->mode->xres; info->yres = info->mode->yres; info->bits_per_pixel = pdata->default_bpp; diff --git a/drivers/video/edid.c b/drivers/video/edid.c new file mode 100644 index 0000000000..828c3610ac --- /dev/null +++ b/drivers/video/edid.c @@ -0,0 +1,909 @@ +/* + * drivers/video/edid.c + * + * Copyright (C) 2002 James Simmons <jsimmons@users.sf.net> + * + * Credits: + * + * The EDID Parser is a conglomeration from the following sources: + * + * 1. SciTech SNAP Graphics Architecture + * Copyright (C) 1991-2002 SciTech Software, Inc. All rights reserved. + * + * 2. XFree86 4.3.0, interpret_edid.c + * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE> + * + * 3. John Fremlin <vii@users.sourceforge.net> and + * Ani Joshi <ajoshi@unixbox.com> + * + * Generalized Timing Formula is derived from: + * + * GTF Spreadsheet by Andy Morrish (1/5/97) + * available at http://www.vesa.org + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#define pr_fmt(fmt) "EDID: " fmt + +#include <common.h> +#include <fb.h> +#include <malloc.h> +#include <i2c/i2c.h> + +#include "edid.h" + +#define FBMON_FIX_HEADER 1 +#define FBMON_FIX_INPUT 2 +#define FBMON_FIX_TIMINGS 3 + +struct broken_edid { + u8 manufacturer[4]; + u32 model; + u32 fix; +}; + +static const unsigned char edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00 +}; + +static int edid_is_serial_block(unsigned char *block) +{ + if ((block[0] == 0x00) && (block[1] == 0x00) && + (block[2] == 0x00) && (block[3] == 0xff) && + (block[4] == 0x00)) + return 1; + else + return 0; +} + +static int edid_is_ascii_block(unsigned char *block) +{ + if ((block[0] == 0x00) && (block[1] == 0x00) && + (block[2] == 0x00) && (block[3] == 0xfe) && + (block[4] == 0x00)) + return 1; + else + return 0; +} + +static int edid_is_limits_block(unsigned char *block) +{ + if ((block[0] == 0x00) && (block[1] == 0x00) && + (block[2] == 0x00) && (block[3] == 0xfd) && + (block[4] == 0x00)) + return 1; + else + return 0; +} + +static int edid_is_monitor_block(unsigned char *block) +{ + if ((block[0] == 0x00) && (block[1] == 0x00) && + (block[2] == 0x00) && (block[3] == 0xfc) && + (block[4] == 0x00)) + return 1; + else + return 0; +} + +static int edid_is_timing_block(unsigned char *block) +{ + if ((block[0] != 0x00) || (block[1] != 0x00) || + (block[2] != 0x00) || (block[4] != 0x00)) + return 1; + else + return 0; +} + +static int check_edid(unsigned char *edid) +{ + unsigned char *block = edid + ID_MANUFACTURER_NAME, manufacturer[4]; + unsigned char *b; + u32 model; + int i, fix = 0, ret = 0; + + manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@'; + manufacturer[1] = ((block[0] & 0x03) << 3) + + ((block[1] & 0xe0) >> 5) + '@'; + manufacturer[2] = (block[1] & 0x1f) + '@'; + manufacturer[3] = 0; + model = block[2] + (block[3] << 8); + + switch (fix) { + case FBMON_FIX_HEADER: + for (i = 0; i < 8; i++) { + if (edid[i] != edid_v1_header[i]) { + ret = fix; + break; + } + } + break; + case FBMON_FIX_INPUT: + b = edid + EDID_STRUCT_DISPLAY; + /* Only if display is GTF capable will + the input type be reset to analog */ + if (b[4] & 0x01 && b[0] & 0x80) + ret = fix; + break; + case FBMON_FIX_TIMINGS: + b = edid + DETAILED_TIMING_DESCRIPTIONS_START; + ret = fix; + + for (i = 0; i < 4; i++) { + if (edid_is_limits_block(b)) { + ret = 0; + break; + } + + b += DETAILED_TIMING_DESCRIPTION_SIZE; + } + + break; + } + + if (ret) + printk("fbmon: The EDID Block of " + "Manufacturer: %s Model: 0x%x is known to " + "be broken,\n", manufacturer, model); + + return ret; +} + +static void fix_edid(unsigned char *edid, int fix) +{ + int i; + unsigned char *b, csum = 0; + + switch (fix) { + case FBMON_FIX_HEADER: + printk("fbmon: trying a header reconstruct\n"); + memcpy(edid, edid_v1_header, 8); + break; + case FBMON_FIX_INPUT: + printk("fbmon: trying to fix input type\n"); + b = edid + EDID_STRUCT_DISPLAY; + b[0] &= ~0x80; + edid[127] += 0x80; + break; + case FBMON_FIX_TIMINGS: + printk("fbmon: trying to fix monitor timings\n"); + b = edid + DETAILED_TIMING_DESCRIPTIONS_START; + for (i = 0; i < 4; i++) { + if (!(edid_is_serial_block(b) || + edid_is_ascii_block(b) || + edid_is_monitor_block(b) || + edid_is_timing_block(b))) { + b[0] = 0x00; + b[1] = 0x00; + b[2] = 0x00; + b[3] = 0xfd; + b[4] = 0x00; + b[5] = 60; /* vfmin */ + b[6] = 60; /* vfmax */ + b[7] = 30; /* hfmin */ + b[8] = 75; /* hfmax */ + b[9] = 17; /* pixclock - 170 MHz*/ + b[10] = 0; /* GTF */ + break; + } + + b += DETAILED_TIMING_DESCRIPTION_SIZE; + } + + for (i = 0; i < EDID_LENGTH - 1; i++) + csum += edid[i]; + + edid[127] = 256 - csum; + break; + } +} + +static int edid_checksum(unsigned char *edid) +{ + unsigned char csum = 0, all_null = 0; + int i, err = 0, fix = check_edid(edid); + + if (fix) + fix_edid(edid, fix); + + for (i = 0; i < EDID_LENGTH; i++) { + csum += edid[i]; + all_null |= edid[i]; + } + + if (csum == 0x00 && all_null) { + /* checksum passed, everything's good */ + err = 1; + } + + return err; +} + +static int edid_check_header(unsigned char *edid) +{ + int i, err = 1, fix = check_edid(edid); + + if (fix) + fix_edid(edid, fix); + + for (i = 0; i < 8; i++) { + if (edid[i] != edid_v1_header[i]) + err = 0; + } + + return err; +} + +/* + * VESA Generalized Timing Formula (GTF) + */ + +#define FLYBACK 550 +#define V_FRONTPORCH 1 +#define H_OFFSET 40 +#define H_SCALEFACTOR 20 +#define H_BLANKSCALE 128 +#define H_GRADIENT 600 +#define C_VAL 30 +#define M_VAL 300 + +struct __fb_timings { + u32 dclk; + u32 hfreq; + u32 vfreq; + u32 hactive; + u32 vactive; + u32 hblank; + u32 vblank; + u32 htotal; + u32 vtotal; +}; + +/** + * fb_get_vblank - get vertical blank time + * @hfreq: horizontal freq + * + * DESCRIPTION: + * vblank = right_margin + vsync_len + left_margin + * + * given: right_margin = 1 (V_FRONTPORCH) + * vsync_len = 3 + * flyback = 550 + * + * flyback * hfreq + * left_margin = --------------- - vsync_len + * 1000000 + */ +static u32 fb_get_vblank(u32 hfreq) +{ + u32 vblank; + + vblank = (hfreq * FLYBACK)/1000; + vblank = (vblank + 500)/1000; + return (vblank + V_FRONTPORCH); +} + +/** + * fb_get_hblank_by_freq - get horizontal blank time given hfreq + * @hfreq: horizontal freq + * @xres: horizontal resolution in pixels + * + * DESCRIPTION: + * + * xres * duty_cycle + * hblank = ------------------ + * 100 - duty_cycle + * + * duty cycle = percent of htotal assigned to inactive display + * duty cycle = C - (M/Hfreq) + * + * where: C = ((offset - scale factor) * blank_scale) + * -------------------------------------- + scale factor + * 256 + * M = blank_scale * gradient + * + */ +static u32 fb_get_hblank_by_hfreq(u32 hfreq, u32 xres) +{ + u32 c_val, m_val, duty_cycle, hblank; + + c_val = (((H_OFFSET - H_SCALEFACTOR) * H_BLANKSCALE)/256 + + H_SCALEFACTOR) * 1000; + m_val = (H_BLANKSCALE * H_GRADIENT)/256; + m_val = (m_val * 1000000)/hfreq; + duty_cycle = c_val - m_val; + hblank = (xres * duty_cycle)/(100000 - duty_cycle); + return (hblank); +} + +/** + * int_sqrt - rough approximation to sqrt + * @x: integer of which to calculate the sqrt + * + * A very rough approximation to the sqrt() function. + */ +unsigned long int_sqrt(unsigned long x) +{ + unsigned long b, m, y = 0; + + if (x <= 1) + return x; + + m = 1UL << (BITS_PER_LONG - 2); + while (m != 0) { + b = y + m; + y >>= 1; + + if (x >= b) { + x -= b; + y += m; + } + m >>= 2; + } + + return y; +} +EXPORT_SYMBOL(int_sqrt); + +/** + * fb_get_hfreq - estimate hsync + * @vfreq: vertical refresh rate + * @yres: vertical resolution + * + * DESCRIPTION: + * + * (yres + front_port) * vfreq * 1000000 + * hfreq = ------------------------------------- + * (1000000 - (vfreq * FLYBACK) + * + */ + +static u32 fb_get_hfreq(u32 vfreq, u32 yres) +{ + u32 divisor, hfreq; + + divisor = (1000000 - (vfreq * FLYBACK))/1000; + hfreq = (yres + V_FRONTPORCH) * vfreq * 1000; + return (hfreq/divisor); +} + +static void fb_timings_vfreq(struct __fb_timings *timings) +{ + timings->hfreq = fb_get_hfreq(timings->vfreq, timings->vactive); + timings->vblank = fb_get_vblank(timings->hfreq); + timings->vtotal = timings->vactive + timings->vblank; + timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq, + timings->hactive); + timings->htotal = timings->hactive + timings->hblank; + timings->dclk = timings->htotal * timings->hfreq; +} + +/* + * fb_get_mode - calculates video mode using VESA GTF + * @flags: if: 0 - maximize vertical refresh rate + * 1 - vrefresh-driven calculation; + * 2 - hscan-driven calculation; + * 3 - pixelclock-driven calculation; + * @val: depending on @flags, ignored, vrefresh, hsync or pixelclock + * @var: pointer to fb_var_screeninfo + * @info: pointer to fb_info + * + * DESCRIPTION: + * Calculates video mode based on monitor specs using VESA GTF. + * The GTF is best for VESA GTF compliant monitors but is + * specifically formulated to work for older monitors as well. + * + * If @flag==0, the function will attempt to maximize the + * refresh rate. Otherwise, it will calculate timings based on + * the flag and accompanying value. + * + * If FB_IGNOREMON bit is set in @flags, monitor specs will be + * ignored and @var will be filled with the calculated timings. + * + * All calculations are based on the VESA GTF Spreadsheet + * available at VESA's public ftp (http://www.vesa.org). + * + * NOTES: + * The timings generated by the GTF will be different from VESA + * DMT. It might be a good idea to keep a table of standard + * VESA modes as well. The GTF may also not work for some displays, + * such as, and especially, analog TV. + * + * REQUIRES: + * A valid info->monspecs, otherwise 'safe numbers' will be used. + */ +int fb_get_mode(int flags, u32 val, struct fb_videomode *var) +{ + struct __fb_timings *timings; + u32 interlace = 1, dscan = 1; + u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax, err = 0; + + timings = xzalloc(sizeof(struct __fb_timings)); + + /* + * If monspecs are invalid, use values that are enough + * for 640x480@60 + */ + hfmin = 29000; hfmax = 30000; + vfmin = 60; vfmax = 60; + dclkmin = 0; dclkmax = 25000000; + + timings->hactive = var->xres; + timings->vactive = var->yres; + if (var->vmode & FB_VMODE_INTERLACED) { + timings->vactive /= 2; + interlace = 2; + } + if (var->vmode & FB_VMODE_DOUBLE) { + timings->vactive *= 2; + dscan = 2; + } + + /* vrefresh driven */ + timings->vfreq = val; + fb_timings_vfreq(timings); + + if (timings->dclk) + var->pixclock = KHZ2PICOS(timings->dclk / 1000); + var->hsync_len = (timings->htotal * 8) / 100; + var->right_margin = (timings->hblank / 2) - var->hsync_len; + var->left_margin = timings->hblank - var->right_margin - + var->hsync_len; + var->vsync_len = (3 * interlace) / dscan; + var->lower_margin = (1 * interlace) / dscan; + var->upper_margin = (timings->vblank * interlace) / dscan - + (var->vsync_len + var->lower_margin); + + free(timings); + return err; +} + +static void calc_mode_timings(int xres, int yres, int refresh, + struct fb_videomode *mode) +{ + mode->xres = xres; + mode->yres = yres; + mode->refresh = refresh; + fb_get_mode(0, refresh, mode); + mode->name = asprintf("%dx%d@%d-calc", mode->xres, mode->yres, mode->refresh); + pr_debug(" %s\n", mode->name); +} + +const struct fb_videomode vesa_modes[] = { + /* 0 640x350-85 VESA */ + { NULL, 85, 640, 350, 31746, 96, 32, 60, 32, 64, 3, + FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, 0}, + /* 1 640x400-85 VESA */ + { NULL, 85, 640, 400, 31746, 96, 32, 41, 01, 64, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 0 }, + /* 2 720x400-85 VESA */ + { NULL, 85, 721, 400, 28169, 108, 36, 42, 01, 72, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 0 }, + /* 3 640x480-60 VESA */ + { NULL, 60, 640, 480, 39682, 48, 16, 33, 10, 96, 2, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 4 640x480-72 VESA */ + { NULL, 72, 640, 480, 31746, 128, 24, 29, 9, 40, 2, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 5 640x480-75 VESA */ + { NULL, 75, 640, 480, 31746, 120, 16, 16, 01, 64, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 6 640x480-85 VESA */ + { NULL, 85, 640, 480, 27777, 80, 56, 25, 01, 56, 3, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 7 800x600-56 VESA */ + { NULL, 56, 800, 600, 27777, 128, 24, 22, 01, 72, 2, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 8 800x600-60 VESA */ + { NULL, 60, 800, 600, 25000, 88, 40, 23, 01, 128, 4, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 9 800x600-72 VESA */ + { NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 10 800x600-75 VESA */ + { NULL, 75, 800, 600, 20202, 160, 16, 21, 01, 80, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 11 800x600-85 VESA */ + { NULL, 85, 800, 600, 17761, 152, 32, 27, 01, 64, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 12 1024x768i-43 VESA */ + { NULL, 43, 1024, 768, 22271, 56, 8, 41, 0, 176, 8, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_INTERLACED, 0 }, + /* 13 1024x768-60 VESA */ + { NULL, 60, 1024, 768, 15384, 160, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 14 1024x768-70 VESA */ + { NULL, 70, 1024, 768, 13333, 144, 24, 29, 3, 136, 6, + 0, FB_VMODE_NONINTERLACED, 0 }, + /* 15 1024x768-75 VESA */ + { NULL, 75, 1024, 768, 12690, 176, 16, 28, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 16 1024x768-85 VESA */ + { NULL, 85, 1024, 768, 10582, 208, 48, 36, 1, 96, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 17 1152x864-75 VESA */ + { NULL, 75, 1152, 864, 9259, 256, 64, 32, 1, 128, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 18 1280x960-60 VESA */ + { NULL, 60, 1280, 960, 9259, 312, 96, 36, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 19 1280x960-85 VESA */ + { NULL, 85, 1280, 960, 6734, 224, 64, 47, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 20 1280x1024-60 VESA */ + { NULL, 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 21 1280x1024-75 VESA */ + { NULL, 75, 1280, 1024, 7407, 248, 16, 38, 1, 144, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 22 1280x1024-85 VESA */ + { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 23 1600x1200-60 VESA */ + { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 24 1600x1200-65 VESA */ + { NULL, 65, 1600, 1200, 5698, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 25 1600x1200-70 VESA */ + { NULL, 70, 1600, 1200, 5291, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 26 1600x1200-75 VESA */ + { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 27 1600x1200-85 VESA */ + { NULL, 85, 1600, 1200, 4357, 304, 64, 46, 1, 192, 3, + FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + FB_VMODE_NONINTERLACED, 0 }, + /* 28 1792x1344-60 VESA */ + { NULL, 60, 1792, 1344, 4882, 328, 128, 46, 1, 200, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 0 }, + /* 29 1792x1344-75 VESA */ + { NULL, 75, 1792, 1344, 3831, 352, 96, 69, 1, 216, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 0 }, + /* 30 1856x1392-60 VESA */ + { NULL, 60, 1856, 1392, 4580, 352, 96, 43, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 0 }, + /* 31 1856x1392-75 VESA */ + { NULL, 75, 1856, 1392, 3472, 352, 128, 104, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 0 }, + /* 32 1920x1440-60 VESA */ + { NULL, 60, 1920, 1440, 4273, 344, 128, 56, 1, 200, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 0 }, + /* 33 1920x1440-75 VESA */ + { NULL, 75, 1920, 1440, 3367, 352, 144, 56, 1, 224, 3, + FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 0 }, +}; + +#define VESA_MODEDB_SIZE ARRAY_SIZE(vesa_modes) + +static void add_vesa_mode(struct fb_videomode *mode, int num) +{ + *mode = vesa_modes[num]; + mode->name = asprintf("%dx%d@%d-vesa", mode->xres, mode->yres, mode->refresh); + pr_debug(" %s\n", mode->name); +} + +static int get_est_timing(unsigned char *block, struct fb_videomode *mode) +{ + int num = 0; + unsigned char c; + + c = block[0]; + if (c & 0x80) + calc_mode_timings(720, 400, 70, &mode[num++]); + if (c & 0x40) + calc_mode_timings(720, 400, 88, &mode[num++]); + if (c & 0x20) + add_vesa_mode(&mode[num++], 3); + if (c & 0x10) + calc_mode_timings(640, 480, 67, &mode[num++]); + if (c & 0x08) + add_vesa_mode(&mode[num++], 4); + if (c & 0x04) + add_vesa_mode(&mode[num++], 5); + if (c & 0x02) + add_vesa_mode(&mode[num++], 7); + if (c & 0x01) + add_vesa_mode(&mode[num++], 8); + + c = block[1]; + if (c & 0x80) + add_vesa_mode(&mode[num++], 9); + if (c & 0x40) + add_vesa_mode(&mode[num++], 10); + if (c & 0x20) + calc_mode_timings(832, 624, 75, &mode[num++]); + if (c & 0x10) + add_vesa_mode(&mode[num++], 12); + if (c & 0x08) + add_vesa_mode(&mode[num++], 13); + if (c & 0x04) + add_vesa_mode(&mode[num++], 14); + if (c & 0x02) + add_vesa_mode(&mode[num++], 15); + if (c & 0x01) + add_vesa_mode(&mode[num++], 21); + c = block[2]; + + if (c & 0x80) + add_vesa_mode(&mode[num++], 17); + + pr_debug(" Manufacturer's mask: %x\n",c & 0x7F); + return num; +} + +static int get_std_timing(unsigned char *block, struct fb_videomode *mode, + int ver, int rev) +{ + int xres, yres = 0, refresh, ratio, i; + + xres = (block[0] + 31) * 8; + if (xres <= 256) + return 0; + + ratio = (block[1] & 0xc0) >> 6; + switch (ratio) { + case 0: + /* in EDID 1.3 the meaning of 0 changed to 16:10 (prior 1:1) */ + if (ver < 1 || (ver == 1 && rev < 3)) + yres = xres; + else + yres = (xres * 10) / 16; + break; + case 1: + yres = (xres * 3) / 4; + break; + case 2: + yres = (xres * 4) / 5; + break; + case 3: + yres = (xres * 9) / 16; + break; + } + refresh = (block[1] & 0x3f) + 60; + + for (i = 0; i < VESA_MODEDB_SIZE; i++) { + if (vesa_modes[i].xres == xres && + vesa_modes[i].yres == yres && + vesa_modes[i].refresh == refresh) { + add_vesa_mode(mode, i); + return 1; + } + } + + calc_mode_timings(xres, yres, refresh, mode); + + return 1; +} + +static int get_dst_timing(unsigned char *block, + struct fb_videomode *mode, int ver, int rev) +{ + int j, num = 0; + + for (j = 0; j < 6; j++, block += STD_TIMING_DESCRIPTION_SIZE) + num += get_std_timing(block, &mode[num], ver, rev); + + return num; +} + +static void get_detailed_timing(unsigned char *block, + struct fb_videomode *mode) +{ + mode->xres = H_ACTIVE; + mode->yres = V_ACTIVE; + mode->pixclock = PIXEL_CLOCK; + mode->pixclock /= 1000; + mode->pixclock = KHZ2PICOS(mode->pixclock); + mode->right_margin = H_SYNC_OFFSET; + mode->left_margin = (H_ACTIVE + H_BLANKING) - + (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); + mode->upper_margin = V_BLANKING - V_SYNC_OFFSET - + V_SYNC_WIDTH; + mode->lower_margin = V_SYNC_OFFSET; + mode->hsync_len = H_SYNC_WIDTH; + mode->vsync_len = V_SYNC_WIDTH; + if (HSYNC_POSITIVE) + mode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (VSYNC_POSITIVE) + mode->sync |= FB_SYNC_VERT_HIGH_ACT; + mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) * + (V_ACTIVE + V_BLANKING)); + if (INTERLACED) { + mode->yres *= 2; + mode->upper_margin *= 2; + mode->lower_margin *= 2; + mode->vsync_len *= 2; + mode->vmode |= FB_VMODE_INTERLACED; + } + + pr_debug(" %d MHz ", PIXEL_CLOCK/1000000); + pr_debug("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET, + H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING); + pr_debug("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET, + V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING); + pr_debug("%sHSync %sVSync\n", (HSYNC_POSITIVE) ? "+" : "-", + (VSYNC_POSITIVE) ? "+" : "-"); + + mode->name = asprintf("%dx%d@%d", mode->xres, mode->yres, mode->refresh); +} + +/** + * edid_to_display_timings - create video mode database + * @edid: EDID data + * @dbsize: database size + * + * RETURNS: struct fb_videomode, @dbsize contains length of database + * + * DESCRIPTION: + * This function builds a mode database using the contents of the EDID + * data + */ +int edid_to_display_timings(struct display_timings *timings, unsigned char *edid) +{ + struct fb_videomode *mode; + unsigned char *block; + int num = 0, i, first = 1; + int ver, rev, ret; + + ver = edid[EDID_STRUCT_VERSION]; + rev = edid[EDID_STRUCT_REVISION]; + + mode = xzalloc(50 * sizeof(struct fb_videomode)); + + if (!edid_checksum(edid) || + !edid_check_header(edid)) { + ret = -EINVAL; + goto out; + } + + pr_debug(" Detailed Timings\n"); + block = edid + DETAILED_TIMING_DESCRIPTIONS_START; + for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) { + if (!(block[0] == 0x00 && block[1] == 0x00)) { + get_detailed_timing(block, &mode[num]); + if (first) { + first = 0; + } + num++; + } + } + + pr_debug(" Supported VESA Modes\n"); + block = edid + ESTABLISHED_TIMING_1; + num += get_est_timing(block, &mode[num]); + + pr_debug(" Standard Timings\n"); + block = edid + STD_TIMING_DESCRIPTIONS_START; + for (i = 0; i < STD_TIMING; i++, block += STD_TIMING_DESCRIPTION_SIZE) + num += get_std_timing(block, &mode[num], ver, rev); + + block = edid + DETAILED_TIMING_DESCRIPTIONS_START; + for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) { + if (block[0] == 0x00 && block[1] == 0x00 && block[3] == 0xfa) + num += get_dst_timing(block + 5, &mode[num], ver, rev); + } + + /* Yikes, EDID data is totally useless */ + if (!num) { + free(mode); + return -EINVAL; + } + + timings->num_modes = num; + timings->modes = mode; + + return 0; +out: + free(timings); + free(mode); + return ret; +} + +#define DDC_ADDR 0x50 +#define DDC_SEGMENT_ADDR 0x30 + +/** + * Get EDID information via I2C. + * + * \param adapter : i2c device adaptor + * \param buf : EDID data buffer to be filled + * \param len : EDID data buffer length + * \return 0 on success or -1 on failure. + * + * Try to fetch EDID information by calling i2c driver function. + */ +static int +edid_do_read_i2c(struct i2c_adapter *adapter, unsigned char *buf, + int block, int len) +{ + unsigned char start = block * EDID_LENGTH; + unsigned char segment = block >> 1; + unsigned char xfers = segment ? 3 : 2; + int ret, retries = 5; + + /* The core i2c driver will automatically retry the transfer if the + * adapter reports EAGAIN. However, we find that bit-banging transfers + * are susceptible to errors under a heavily loaded machine and + * generate spurious NAKs and timeouts. Retrying the transfer + * of the individual block a few times seems to overcome this. + */ + do { + struct i2c_msg msgs[] = { + { + .addr = DDC_SEGMENT_ADDR, + .flags = 0, + .len = 1, + .buf = &segment, + }, { + .addr = DDC_ADDR, + .flags = 0, + .len = 1, + .buf = &start, + }, { + .addr = DDC_ADDR, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + } + }; + + /* + * Avoid sending the segment addr to not upset non-compliant ddc + * monitors. + */ + ret = i2c_transfer(adapter, &msgs[3 - xfers], xfers); + } while (ret != xfers && --retries); + + return ret == xfers ? 0 : -1; +} + +void *edid_read_i2c(struct i2c_adapter *adapter) +{ + u8 *block; + + block = xmalloc(EDID_LENGTH); + + if (edid_do_read_i2c(adapter, block, 0, EDID_LENGTH)) + goto out; + + return block; +out: + free(block); + + return NULL; +} + +void fb_edid_add_modes(struct fb_info *info) +{ + if (info->edid_i2c_adapter) + info->edid_data = edid_read_i2c(info->edid_i2c_adapter); + + if (!info->edid_data) + return; + + edid_to_display_timings(&info->edid_modes, info->edid_data); +} diff --git a/drivers/video/edid.h b/drivers/video/edid.h new file mode 100644 index 0000000000..006d9f2834 --- /dev/null +++ b/drivers/video/edid.h @@ -0,0 +1,138 @@ +/* + * drivers/video/edid.h - EDID/DDC Header + * + * Based on: + * 1. XFree86 4.3.0, edid.h + * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE> + * + * 2. John Fremlin <vii@users.sourceforge.net> and + * Ani Joshi <ajoshi@unixbox.com> + * + * DDC is a Trademark of VESA (Video Electronics Standard Association). + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. +*/ + +#ifndef __EDID_H__ +#define __EDID_H__ + +#define EDID_LENGTH 0x80 +#define EDID_HEADER 0x00 +#define EDID_HEADER_END 0x07 + +#define ID_MANUFACTURER_NAME 0x08 +#define ID_MANUFACTURER_NAME_END 0x09 +#define ID_MODEL 0x0a + +#define ID_SERIAL_NUMBER 0x0c + +#define MANUFACTURE_WEEK 0x10 +#define MANUFACTURE_YEAR 0x11 + +#define EDID_STRUCT_VERSION 0x12 +#define EDID_STRUCT_REVISION 0x13 + +#define EDID_STRUCT_DISPLAY 0x14 + +#define DPMS_FLAGS 0x18 +#define ESTABLISHED_TIMING_1 0x23 +#define ESTABLISHED_TIMING_2 0x24 +#define MANUFACTURERS_TIMINGS 0x25 + +/* standard timings supported */ +#define STD_TIMING 8 +#define STD_TIMING_DESCRIPTION_SIZE 2 +#define STD_TIMING_DESCRIPTIONS_START 0x26 + +#define DETAILED_TIMING_DESCRIPTIONS_START 0x36 +#define DETAILED_TIMING_DESCRIPTION_SIZE 18 +#define NO_DETAILED_TIMING_DESCRIPTIONS 4 + +#define DETAILED_TIMING_DESCRIPTION_1 0x36 +#define DETAILED_TIMING_DESCRIPTION_2 0x48 +#define DETAILED_TIMING_DESCRIPTION_3 0x5a +#define DETAILED_TIMING_DESCRIPTION_4 0x6c + +#define DESCRIPTOR_DATA 5 + +#define UPPER_NIBBLE( x ) \ + (((128|64|32|16) & (x)) >> 4) + +#define LOWER_NIBBLE( x ) \ + ((1|2|4|8) & (x)) + +#define COMBINE_HI_8LO( hi, lo ) \ + ( (((unsigned)hi) << 8) | (unsigned)lo ) + +#define COMBINE_HI_4LO( hi, lo ) \ + ( (((unsigned)hi) << 4) | (unsigned)lo ) + +#define PIXEL_CLOCK_LO (unsigned)block[ 0 ] +#define PIXEL_CLOCK_HI (unsigned)block[ 1 ] +#define PIXEL_CLOCK (COMBINE_HI_8LO( PIXEL_CLOCK_HI,PIXEL_CLOCK_LO )*10000) +#define H_ACTIVE_LO (unsigned)block[ 2 ] +#define H_BLANKING_LO (unsigned)block[ 3 ] +#define H_ACTIVE_HI UPPER_NIBBLE( (unsigned)block[ 4 ] ) +#define H_ACTIVE COMBINE_HI_8LO( H_ACTIVE_HI, H_ACTIVE_LO ) +#define H_BLANKING_HI LOWER_NIBBLE( (unsigned)block[ 4 ] ) +#define H_BLANKING COMBINE_HI_8LO( H_BLANKING_HI, H_BLANKING_LO ) + +#define V_ACTIVE_LO (unsigned)block[ 5 ] +#define V_BLANKING_LO (unsigned)block[ 6 ] +#define V_ACTIVE_HI UPPER_NIBBLE( (unsigned)block[ 7 ] ) +#define V_ACTIVE COMBINE_HI_8LO( V_ACTIVE_HI, V_ACTIVE_LO ) +#define V_BLANKING_HI LOWER_NIBBLE( (unsigned)block[ 7 ] ) +#define V_BLANKING COMBINE_HI_8LO( V_BLANKING_HI, V_BLANKING_LO ) + +#define H_SYNC_OFFSET_LO (unsigned)block[ 8 ] +#define H_SYNC_WIDTH_LO (unsigned)block[ 9 ] + +#define V_SYNC_OFFSET_LO UPPER_NIBBLE( (unsigned)block[ 10 ] ) +#define V_SYNC_WIDTH_LO LOWER_NIBBLE( (unsigned)block[ 10 ] ) + +#define V_SYNC_WIDTH_HI ((unsigned)block[ 11 ] & (1|2)) +#define V_SYNC_OFFSET_HI (((unsigned)block[ 11 ] & (4|8)) >> 2) + +#define H_SYNC_WIDTH_HI (((unsigned)block[ 11 ] & (16|32)) >> 4) +#define H_SYNC_OFFSET_HI (((unsigned)block[ 11 ] & (64|128)) >> 6) + +#define V_SYNC_WIDTH COMBINE_HI_4LO( V_SYNC_WIDTH_HI, V_SYNC_WIDTH_LO ) +#define V_SYNC_OFFSET COMBINE_HI_4LO( V_SYNC_OFFSET_HI, V_SYNC_OFFSET_LO ) + +#define H_SYNC_WIDTH COMBINE_HI_8LO( H_SYNC_WIDTH_HI, H_SYNC_WIDTH_LO ) +#define H_SYNC_OFFSET COMBINE_HI_8LO( H_SYNC_OFFSET_HI, H_SYNC_OFFSET_LO ) + +#define H_SIZE_LO (unsigned)block[ 12 ] +#define V_SIZE_LO (unsigned)block[ 13 ] + +#define H_SIZE_HI UPPER_NIBBLE( (unsigned)block[ 14 ] ) +#define V_SIZE_HI LOWER_NIBBLE( (unsigned)block[ 14 ] ) + +#define H_SIZE COMBINE_HI_8LO( H_SIZE_HI, H_SIZE_LO ) +#define V_SIZE COMBINE_HI_8LO( V_SIZE_HI, V_SIZE_LO ) + +#define H_BORDER (unsigned)block[ 15 ] +#define V_BORDER (unsigned)block[ 16 ] + +#define FLAGS (unsigned)block[ 17 ] + +#define INTERLACED (FLAGS&128) +#define SYNC_TYPE (FLAGS&3<<3) /* bits 4,3 */ +#define SYNC_SEPARATE (3<<3) +#define HSYNC_POSITIVE (FLAGS & 4) +#define VSYNC_POSITIVE (FLAGS & 2) + +#define V_MIN_RATE block[ 5 ] +#define V_MAX_RATE block[ 6 ] +#define H_MIN_RATE block[ 7 ] +#define H_MAX_RATE block[ 8 ] +#define MAX_PIXEL_CLOCK (((int)block[ 9 ]) * 10) +#define GTF_SUPPORT block[10] + +#define DPMS_ACTIVE_OFF (1 << 5) +#define DPMS_SUSPEND (1 << 6) +#define DPMS_STANDBY (1 << 7) + +#endif /* __EDID_H__ */ diff --git a/drivers/video/fb.c b/drivers/video/fb.c index c36b9adcea..2c8b8eb25e 100644 --- a/drivers/video/fb.c +++ b/drivers/video/fb.c @@ -49,32 +49,45 @@ static int fb_enable_set(struct param_d *param, void *priv) return 0; } -static int fb_setup_mode(struct device_d *dev, struct param_d *param, - const char *val) +static struct fb_videomode *fb_num_to_mode(struct fb_info *info, int num) { - struct fb_info *info = dev->priv; - int mode, ret; + int num_modes; + + num_modes = info->modes.num_modes + info->edid_modes.num_modes; + + if (num >= num_modes) + return NULL; + + if (num >= info->modes.num_modes) + return &info->edid_modes.modes[num - info->modes.num_modes]; + + return &info->modes.modes[num]; +} + +static int fb_setup_mode(struct fb_info *info) +{ + struct device_d *dev = &info->dev; + int ret; + struct fb_videomode *mode; if (info->enabled != 0) return -EPERM; - if (!val) - return dev_param_set_generic(dev, param, NULL); - - for (mode = 0; mode < info->num_modes; mode++) { - if (!strcmp(info->mode_list[mode].name, val)) - break; - } - if (mode >= info->num_modes) + mode = fb_num_to_mode(info, info->current_mode); + if (!mode) return -EINVAL; - info->mode = &info->mode_list[mode]; + info->mode = mode; info->xres = info->mode->xres; info->yres = info->mode->yres; info->line_length = 0; - ret = info->fbops->fb_activate_var(info); + if (info->fbops->fb_activate_var) { + ret = info->fbops->fb_activate_var(info); + if (ret) + return ret; + } if (!info->line_length) info->line_length = info->xres * (info->bits_per_pixel >> 3); @@ -85,13 +98,24 @@ static int fb_setup_mode(struct device_d *dev, struct param_d *param, dev->resource[0].start = (resource_size_t)info->screen_base; info->cdev.size = info->line_length * info->yres; dev->resource[0].end = dev->resource[0].start + info->cdev.size - 1; - dev_param_set_generic(dev, param, val); } else info->cdev.size = 0; return ret; } +static int fb_set_modename(struct param_d *param, void *priv) +{ + struct fb_info *info = priv; + int ret; + + ret = fb_setup_mode(info); + if (ret) + return ret; + + return 0; +} + static struct file_operations fb_ops = { .read = mem_read, .write = mem_write, @@ -100,22 +124,28 @@ static struct file_operations fb_ops = { .ioctl = fb_ioctl, }; -static void fb_info(struct device_d *dev) +static void fb_print_mode(struct fb_videomode *mode) +{ + printf("%-20s %dx%d@%d\n", mode->name, + mode->xres, mode->yres, mode->refresh); +} + +static void fb_print_modes(struct display_timings *modes) { - struct fb_info *info = dev->priv; int i; - if (!info->num_modes) - return; + for (i = 0; i < modes->num_modes; i++) + fb_print_mode(&modes->modes[i]); +} + +static void fb_info(struct device_d *dev) +{ + struct fb_info *info = dev->priv; printf("available modes:\n"); - for (i = 0; i < info->num_modes; i++) { - struct fb_videomode *mode = &info->mode_list[i]; - - printf("%-10s %dx%d@%d\n", mode->name, - mode->xres, mode->yres, mode->refresh); - } + fb_print_modes(&info->modes); + fb_print_modes(&info->edid_modes); printf("\n"); } @@ -124,10 +154,20 @@ int register_framebuffer(struct fb_info *info) { int id = get_free_deviceid("fb"); struct device_d *dev; - int ret; + int ret, num_modes, i; + const char **names; dev = &info->dev; + /* + * If info->mode is set at this point it's the only mode + * the fb supports. move it over to the modes list. + */ + if (info->mode) { + info->modes.modes = info->mode; + info->modes.num_modes = 1; + } + if (!info->line_length) info->line_length = info->xres * (info->bits_per_pixel >> 3); @@ -155,11 +195,22 @@ int register_framebuffer(struct fb_info *info) dev_add_param_bool(dev, "enable", fb_enable_set, NULL, &info->p_enable, info); - if (info->num_modes && (info->mode_list != NULL) && - (info->fbops->fb_activate_var != NULL)) { - dev_add_param(dev, "mode_name", fb_setup_mode, NULL, 0); - dev_set_param(dev, "mode_name", info->mode_list[0].name); - } + if (IS_ENABLED(CONFIG_DRIVER_VIDEO_EDID)) + fb_edid_add_modes(info); + + num_modes = info->modes.num_modes + info->edid_modes.num_modes; + + names = xzalloc(sizeof(char *) * num_modes); + + for (i = 0; i < info->modes.num_modes; i++) + names[i] = info->modes.modes[i].name; + for (i = 0; i < info->edid_modes.num_modes; i++) + names[i + info->modes.num_modes] = info->edid_modes.modes[i].name; + dev_add_param_enum(dev, "mode_name", fb_set_modename, NULL, &info->current_mode, names, num_modes, info); + + info->mode = fb_num_to_mode(info, 0); + + fb_setup_mode(info); ret = devfs_create(&info->cdev); if (ret) diff --git a/drivers/video/imx-ipu-fb.c b/drivers/video/imx-ipu-fb.c index 7276647382..abac812cb6 100644 --- a/drivers/video/imx-ipu-fb.c +++ b/drivers/video/imx-ipu-fb.c @@ -1007,8 +1007,8 @@ static int imxfb_probe(struct device_d *dev) fbi->disable_fractional_divider = pdata->disable_fractional_divider; info->priv = fbi; info->fbops = &imxfb_ops; - info->num_modes = pdata->num_modes; - info->mode_list = pdata->mode; + info->modes.modes = pdata->mode; + info->modes.num_modes = pdata->num_modes; imxfb_init_info(info, pdata->mode, pdata->bpp); @@ -1047,14 +1047,9 @@ static int imxfb_probe(struct device_d *dev) return ret; } -static void imxfb_remove(struct device_d *dev) -{ -} - static struct driver_d imx3fb_driver = { .name = "imx-ipu-fb", .probe = imxfb_probe, - .remove = imxfb_remove, }; device_platform_driver(imx3fb_driver); diff --git a/drivers/video/imx-ipu-v3/Kconfig b/drivers/video/imx-ipu-v3/Kconfig new file mode 100644 index 0000000000..3d656e5193 --- /dev/null +++ b/drivers/video/imx-ipu-v3/Kconfig @@ -0,0 +1,14 @@ +config DRIVER_VIDEO_IMX_IPUV3 + bool "i.MX IPUv3 driver" + help + Support the IPUv3 found on Freescale i.MX51/53/6 SoCs + +if DRIVER_VIDEO_IMX_IPUV3 + +config DRIVER_VIDEO_IMX_IPUV3_LVDS + bool "IPUv3 LVDS support" + +config DRIVER_VIDEO_IMX_IPUV3_HDMI + bool "IPUv3 HDMI support" + +endif diff --git a/drivers/video/imx-ipu-v3/Makefile b/drivers/video/imx-ipu-v3/Makefile new file mode 100644 index 0000000000..2bc0aec5ae --- /dev/null +++ b/drivers/video/imx-ipu-v3/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-common.o ipu-dmfc.o ipu-di.o +obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-dp.o ipuv3-plane.o ipufb.o +obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-dc.o +obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3_LVDS) += imx-ldb.o +obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3_HDMI) += imx-hdmi.o diff --git a/drivers/video/imx-ipu-v3/imx-hdmi.c b/drivers/video/imx-ipu-v3/imx-hdmi.c new file mode 100644 index 0000000000..4f462889a8 --- /dev/null +++ b/drivers/video/imx-ipu-v3/imx-hdmi.c @@ -0,0 +1,1297 @@ +/* + * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. + * + * 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. + * + * SH-Mobile High-Definition Multimedia Interface (HDMI) driver + * for SLISHDMI13T and SLIPHDMIT IP cores + * + * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + */ +#include <common.h> +#include <fb.h> +#include <io.h> +#include <driver.h> +#include <malloc.h> +#include <errno.h> +#include <init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <asm-generic/div64.h> +#include <linux/clk.h> +#include <i2c/i2c.h> +#include <mach/imx6-regs.h> +#include <mach/imx53-regs.h> + +#include "imx-ipu-v3.h" +#include "ipuv3-plane.h" +#include "imx-hdmi.h" + +#define HDMI_EDID_LEN 512 + +#define RGB 0 +#define YCBCR444 1 +#define YCBCR422_16BITS 2 +#define YCBCR422_8BITS 3 +#define XVYCC444 4 + +enum hdmi_colorimetry { + HDMI_COLORIMETRY_NONE, + HDMI_COLORIMETRY_ITU_601, + HDMI_COLORIMETRY_ITU_709, + HDMI_COLORIMETRY_EXTENDED, +}; + +enum hdmi_datamap { + RGB444_8B = 0x01, + RGB444_10B = 0x03, + RGB444_12B = 0x05, + RGB444_16B = 0x07, + YCbCr444_8B = 0x09, + YCbCr444_10B = 0x0B, + YCbCr444_12B = 0x0D, + YCbCr444_16B = 0x0F, + YCbCr422_8B = 0x16, + YCbCr422_10B = 0x14, + YCbCr422_12B = 0x12, +}; + +enum imx_hdmi_devtype { + IMX6Q_HDMI, + IMX6DL_HDMI, +}; + +static const u16 csc_coeff_default[3][4] = { + { 0x2000, 0x0000, 0x0000, 0x0000 }, + { 0x0000, 0x2000, 0x0000, 0x0000 }, + { 0x0000, 0x0000, 0x2000, 0x0000 } +}; + +static const u16 csc_coeff_rgb_out_eitu601[3][4] = { + { 0x2000, 0x6926, 0x74fd, 0x010e }, + { 0x2000, 0x2cdd, 0x0000, 0x7e9a }, + { 0x2000, 0x0000, 0x38b4, 0x7e3b } +}; + +static const u16 csc_coeff_rgb_out_eitu709[3][4] = { + { 0x2000, 0x7106, 0x7a02, 0x00a7 }, + { 0x2000, 0x3264, 0x0000, 0x7e6d }, + { 0x2000, 0x0000, 0x3b61, 0x7e25 } +}; + +static const u16 csc_coeff_rgb_in_eitu601[3][4] = { + { 0x2591, 0x1322, 0x074b, 0x0000 }, + { 0x6535, 0x2000, 0x7acc, 0x0200 }, + { 0x6acd, 0x7534, 0x2000, 0x0200 } +}; + +static const u16 csc_coeff_rgb_in_eitu709[3][4] = { + { 0x2dc5, 0x0d9b, 0x049e, 0x0000 }, + { 0x62f0, 0x2000, 0x7d11, 0x0200 }, + { 0x6756, 0x78ab, 0x2000, 0x0200 } +}; + +struct hdmi_vmode { + bool mdvi; + bool mhsyncpolarity; + bool mvsyncpolarity; + bool minterlaced; + bool mdataenablepolarity; + + unsigned int mpixelclock; + unsigned int mpixelrepetitioninput; + unsigned int mpixelrepetitionoutput; +}; + +struct hdmi_data_info { + unsigned int enc_in_format; + unsigned int enc_out_format; + unsigned int enc_color_depth; + unsigned int colorimetry; + unsigned int pix_repet_factor; + unsigned int hdcp_enable; + struct hdmi_vmode video_mode; +}; + +struct imx_hdmi { + enum imx_hdmi_devtype dev_type; + struct device_d *dev; + struct clk *isfr_clk; + struct clk *iahb_clk; + + bool connected; + + struct hdmi_data_info hdmi_data; + int vic; + + u8 edid[HDMI_EDID_LEN]; + bool cable_plugin; + + bool phy_enabled; + + struct regmap *regmap; + struct i2c_adapter *ddc; + void __iomem *regs; + + unsigned int sample_rate; + int ratio; + + struct ipu_output output; +}; + +static void imx_hdmi_set_ipu_di_mux(struct imx_hdmi *hdmi, int ipu_di) +{ + void __iomem *gpr3 = (void *)MX6_IOMUXC_BASE_ADDR + 0xc; + uint32_t val; + + dev_info(hdmi->dev, "setup hdmi mux to %d\n", ipu_di); + + val = readl(gpr3); + val &= ~(3 << 2); + val |= ipu_di << 2; + writel(val, gpr3); +} + +static inline void hdmi_writeb(struct imx_hdmi *hdmi, u8 val, int offset) +{ + writeb(val, hdmi->regs + offset); +} + +static inline u8 hdmi_readb(struct imx_hdmi *hdmi, int offset) +{ + return readb(hdmi->regs + offset); +} + +static void hdmi_modb(struct imx_hdmi *hdmi, u8 data, u8 mask, unsigned reg) +{ + u8 val = hdmi_readb(hdmi, reg) & ~mask; + val |= data & mask; + hdmi_writeb(hdmi, val, reg); +} + +static void hdmi_mask_writeb(struct imx_hdmi *hdmi, u8 data, unsigned int reg, + u8 shift, u8 mask) +{ + hdmi_modb(hdmi, data << shift, mask, reg); +} + +static void hdmi_set_clock_regenerator_n(struct imx_hdmi *hdmi, + unsigned int value) +{ + hdmi_writeb(hdmi, value & 0xff, HDMI_AUD_N1); + hdmi_writeb(hdmi, (value >> 8) & 0xff, HDMI_AUD_N2); + hdmi_writeb(hdmi, (value >> 16) & 0x0f, HDMI_AUD_N3); + + /* nshift factor = 0 */ + hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_N_SHIFT_MASK, HDMI_AUD_CTS3); +} + +static void hdmi_regenerate_cts(struct imx_hdmi *hdmi, unsigned int cts) +{ + /* Must be set/cleared first */ + hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3); + + hdmi_writeb(hdmi, cts & 0xff, HDMI_AUD_CTS1); + hdmi_writeb(hdmi, (cts >> 8) & 0xff, HDMI_AUD_CTS2); + hdmi_writeb(hdmi, ((cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK) | + HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3); +} + +static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk, + unsigned int ratio) +{ + unsigned int n = (128 * freq) / 1000; + + switch (freq) { + case 32000: + if (pixel_clk == 25170000) + n = (ratio == 150) ? 9152 : 4576; + else if (pixel_clk == 27020000) + n = (ratio == 150) ? 8192 : 4096; + else if (pixel_clk == 74170000 || pixel_clk == 148350000) + n = 11648; + else + n = 4096; + break; + + case 44100: + if (pixel_clk == 25170000) + n = 7007; + else if (pixel_clk == 74170000) + n = 17836; + else if (pixel_clk == 148350000) + n = (ratio == 150) ? 17836 : 8918; + else + n = 6272; + break; + + case 48000: + if (pixel_clk == 25170000) + n = (ratio == 150) ? 9152 : 6864; + else if (pixel_clk == 27020000) + n = (ratio == 150) ? 8192 : 6144; + else if (pixel_clk == 74170000) + n = 11648; + else if (pixel_clk == 148350000) + n = (ratio == 150) ? 11648 : 5824; + else + n = 6144; + break; + + case 88200: + n = hdmi_compute_n(44100, pixel_clk, ratio) * 2; + break; + + case 96000: + n = hdmi_compute_n(48000, pixel_clk, ratio) * 2; + break; + + case 176400: + n = hdmi_compute_n(44100, pixel_clk, ratio) * 4; + break; + + case 192000: + n = hdmi_compute_n(48000, pixel_clk, ratio) * 4; + break; + + default: + break; + } + + return n; +} + +static unsigned int hdmi_compute_cts(unsigned int freq, unsigned long pixel_clk, + unsigned int ratio) +{ + unsigned int cts = 0; + + pr_debug("%s: freq: %d pixel_clk: %ld ratio: %d\n", __func__, freq, + pixel_clk, ratio); + + switch (freq) { + case 32000: + if (pixel_clk == 297000000) { + cts = 222750; + break; + } + case 48000: + case 96000: + case 192000: + switch (pixel_clk) { + case 25200000: + case 27000000: + case 54000000: + case 74250000: + case 148500000: + cts = pixel_clk / 1000; + break; + case 297000000: + cts = 247500; + break; + /* + * All other TMDS clocks are not supported by + * DWC_hdmi_tx. The TMDS clocks divided or + * multiplied by 1,001 coefficients are not + * supported. + */ + default: + break; + } + break; + case 44100: + case 88200: + case 176400: + switch (pixel_clk) { + case 25200000: + cts = 28000; + break; + case 27000000: + cts = 30000; + break; + case 54000000: + cts = 60000; + break; + case 74250000: + cts = 82500; + break; + case 148500000: + cts = 165000; + break; + case 297000000: + cts = 247500; + break; + default: + break; + } + break; + default: + break; + } + if (ratio == 100) + return cts; + else + return (cts * ratio) / 100; +} + +static void hdmi_set_clk_regenerator(struct imx_hdmi *hdmi, + unsigned long pixel_clk) +{ + unsigned int clk_n, clk_cts; + + clk_n = hdmi_compute_n(hdmi->sample_rate, pixel_clk, + hdmi->ratio); + clk_cts = hdmi_compute_cts(hdmi->sample_rate, pixel_clk, + hdmi->ratio); + + if (!clk_cts) { + dev_dbg(hdmi->dev, "%s: pixel clock not supported: %lu\n", + __func__, pixel_clk); + return; + } + + dev_dbg(hdmi->dev, "%s: samplerate=%d ratio=%d pixelclk=%lu N=%d cts=%d\n", + __func__, hdmi->sample_rate, hdmi->ratio, + pixel_clk, clk_n, clk_cts); + + hdmi_set_clock_regenerator_n(hdmi, clk_n); + hdmi_regenerate_cts(hdmi, clk_cts); +} + +static void hdmi_init_clk_regenerator(struct imx_hdmi *hdmi) +{ + hdmi_set_clk_regenerator(hdmi, 74250000); +} + +/* + * this submodule is responsible for the video data synchronization. + * for example, for RGB 4:4:4 input, the data map is defined as + * pin{47~40} <==> R[7:0] + * pin{31~24} <==> G[7:0] + * pin{15~8} <==> B[7:0] + */ +static void hdmi_video_sample(struct imx_hdmi *hdmi) +{ + int color_format = 0; + u8 val; + + if (hdmi->hdmi_data.enc_in_format == RGB) { + if (hdmi->hdmi_data.enc_color_depth == 8) + color_format = 0x01; + else if (hdmi->hdmi_data.enc_color_depth == 10) + color_format = 0x03; + else if (hdmi->hdmi_data.enc_color_depth == 12) + color_format = 0x05; + else if (hdmi->hdmi_data.enc_color_depth == 16) + color_format = 0x07; + else + return; + } else if (hdmi->hdmi_data.enc_in_format == YCBCR444) { + if (hdmi->hdmi_data.enc_color_depth == 8) + color_format = 0x09; + else if (hdmi->hdmi_data.enc_color_depth == 10) + color_format = 0x0B; + else if (hdmi->hdmi_data.enc_color_depth == 12) + color_format = 0x0D; + else if (hdmi->hdmi_data.enc_color_depth == 16) + color_format = 0x0F; + else + return; + } else if (hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) { + if (hdmi->hdmi_data.enc_color_depth == 8) + color_format = 0x16; + else if (hdmi->hdmi_data.enc_color_depth == 10) + color_format = 0x14; + else if (hdmi->hdmi_data.enc_color_depth == 12) + color_format = 0x12; + else + return; + } + + val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE | + ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) & + HDMI_TX_INVID0_VIDEO_MAPPING_MASK); + hdmi_writeb(hdmi, val, HDMI_TX_INVID0); + + /* Enable TX stuffing: When DE is inactive, fix the output data to 0 */ + val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE | + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE; + hdmi_writeb(hdmi, val, HDMI_TX_INSTUFFING); + hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA1); + hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA1); + hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA0); + hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA1); +} + +static int is_color_space_conversion(struct imx_hdmi *hdmi) +{ + return (hdmi->hdmi_data.enc_in_format != + hdmi->hdmi_data.enc_out_format); +} + +static int is_color_space_decimation(struct imx_hdmi *hdmi) +{ + return ((hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS) && + (hdmi->hdmi_data.enc_in_format == RGB || + hdmi->hdmi_data.enc_in_format == YCBCR444)); +} + +static int is_color_space_interpolation(struct imx_hdmi *hdmi) +{ + return ((hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) && + (hdmi->hdmi_data.enc_out_format == RGB || + hdmi->hdmi_data.enc_out_format == YCBCR444)); +} + +static void imx_hdmi_update_csc_coeffs(struct imx_hdmi *hdmi) +{ + const u16 (*csc_coeff)[3][4] = &csc_coeff_default; + unsigned i; + u32 csc_scale = 1; + + if (is_color_space_conversion(hdmi)) { + if (hdmi->hdmi_data.enc_out_format == RGB) { + if (hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_601) + csc_coeff = &csc_coeff_rgb_out_eitu601; + else + csc_coeff = &csc_coeff_rgb_out_eitu709; + } else if (hdmi->hdmi_data.enc_in_format == RGB) { + if (hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_601) + csc_coeff = &csc_coeff_rgb_in_eitu601; + else + csc_coeff = &csc_coeff_rgb_in_eitu709; + csc_scale = 0; + } + } + + /* The CSC registers are sequential, alternating MSB then LSB */ + for (i = 0; i < ARRAY_SIZE(csc_coeff_default[0]); i++) { + u16 coeff_a = (*csc_coeff)[0][i]; + u16 coeff_b = (*csc_coeff)[1][i]; + u16 coeff_c = (*csc_coeff)[2][i]; + + hdmi_writeb(hdmi, coeff_a & 0xff, HDMI_CSC_COEF_A1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_a >> 8, HDMI_CSC_COEF_A1_MSB + i * 2); + hdmi_writeb(hdmi, coeff_b & 0xff, HDMI_CSC_COEF_B1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_b >> 8, HDMI_CSC_COEF_B1_MSB + i * 2); + hdmi_writeb(hdmi, coeff_c & 0xff, HDMI_CSC_COEF_C1_LSB + i * 2); + hdmi_writeb(hdmi, coeff_c >> 8, HDMI_CSC_COEF_C1_MSB + i * 2); + } + + hdmi_modb(hdmi, csc_scale, HDMI_CSC_SCALE_CSCSCALE_MASK, + HDMI_CSC_SCALE); +} + +static void hdmi_video_csc(struct imx_hdmi *hdmi) +{ + int color_depth = 0; + int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE; + int decimation = 0; + + /* YCC422 interpolation to 444 mode */ + if (is_color_space_interpolation(hdmi)) + interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1; + else if (is_color_space_decimation(hdmi)) + decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3; + + if (hdmi->hdmi_data.enc_color_depth == 8) + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP; + else if (hdmi->hdmi_data.enc_color_depth == 10) + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP; + else if (hdmi->hdmi_data.enc_color_depth == 12) + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP; + else if (hdmi->hdmi_data.enc_color_depth == 16) + color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP; + else + return; + + /* Configure the CSC registers */ + hdmi_writeb(hdmi, interpolation | decimation, HDMI_CSC_CFG); + hdmi_modb(hdmi, color_depth, HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK, + HDMI_CSC_SCALE); + + imx_hdmi_update_csc_coeffs(hdmi); +} + +/* + * HDMI video packetizer is used to packetize the data. + * for example, if input is YCC422 mode or repeater is used, + * data should be repacked this module can be bypassed. + */ +static void hdmi_video_packetize(struct imx_hdmi *hdmi) +{ + unsigned int color_depth = 0; + unsigned int remap_size = HDMI_VP_REMAP_YCC422_16bit; + unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP; + struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; + u8 val, vp_conf; + + if (hdmi_data->enc_out_format == RGB + || hdmi_data->enc_out_format == YCBCR444) { + if (!hdmi_data->enc_color_depth) + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + else if (hdmi_data->enc_color_depth == 8) { + color_depth = 4; + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + } else if (hdmi_data->enc_color_depth == 10) + color_depth = 5; + else if (hdmi_data->enc_color_depth == 12) + color_depth = 6; + else if (hdmi_data->enc_color_depth == 16) + color_depth = 7; + else + return; + } else if (hdmi_data->enc_out_format == YCBCR422_8BITS) { + if (!hdmi_data->enc_color_depth || + hdmi_data->enc_color_depth == 8) + remap_size = HDMI_VP_REMAP_YCC422_16bit; + else if (hdmi_data->enc_color_depth == 10) + remap_size = HDMI_VP_REMAP_YCC422_20bit; + else if (hdmi_data->enc_color_depth == 12) + remap_size = HDMI_VP_REMAP_YCC422_24bit; + else + return; + output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422; + } else + return; + + /* set the packetizer registers */ + val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) & + HDMI_VP_PR_CD_COLOR_DEPTH_MASK) | + ((hdmi_data->pix_repet_factor << + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) & + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK); + hdmi_writeb(hdmi, val, HDMI_VP_PR_CD); + + hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE, + HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF); + + /* Data from pixel repeater block */ + if (hdmi_data->pix_repet_factor > 1) { + vp_conf = HDMI_VP_CONF_PR_EN_ENABLE | + HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER; + } else { /* data from packetizer block */ + vp_conf = HDMI_VP_CONF_PR_EN_DISABLE | + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER; + } + + hdmi_modb(hdmi, vp_conf, + HDMI_VP_CONF_PR_EN_MASK | + HDMI_VP_CONF_BYPASS_SELECT_MASK, HDMI_VP_CONF); + + hdmi_modb(hdmi, 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET, + HDMI_VP_STUFF_IDEFAULT_PHASE_MASK, HDMI_VP_STUFF); + + hdmi_writeb(hdmi, remap_size, HDMI_VP_REMAP); + + if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_ENABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_ENABLE; + } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) { + vp_conf = HDMI_VP_CONF_BYPASS_EN_ENABLE | + HDMI_VP_CONF_PP_EN_DISABLE | + HDMI_VP_CONF_YCC422_EN_DISABLE; + } else { + return; + } + + hdmi_modb(hdmi, vp_conf, + HDMI_VP_CONF_BYPASS_EN_MASK | HDMI_VP_CONF_PP_EN_ENMASK | + HDMI_VP_CONF_YCC422_EN_MASK, HDMI_VP_CONF); + + hdmi_modb(hdmi, HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE | + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE, + HDMI_VP_STUFF_PP_STUFFING_MASK | + HDMI_VP_STUFF_YCC422_STUFFING_MASK, HDMI_VP_STUFF); + + hdmi_modb(hdmi, output_select, HDMI_VP_CONF_OUTPUT_SELECTOR_MASK, + HDMI_VP_CONF); +} + +static inline void hdmi_phy_test_clear(struct imx_hdmi *hdmi, + unsigned char bit) +{ + hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLR_OFFSET, + HDMI_PHY_TST0_TSTCLR_MASK, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_enable(struct imx_hdmi *hdmi, + unsigned char bit) +{ + hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTEN_OFFSET, + HDMI_PHY_TST0_TSTEN_MASK, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_clock(struct imx_hdmi *hdmi, + unsigned char bit) +{ + hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLK_OFFSET, + HDMI_PHY_TST0_TSTCLK_MASK, HDMI_PHY_TST0); +} + +static inline void hdmi_phy_test_din(struct imx_hdmi *hdmi, + unsigned char bit) +{ + hdmi_writeb(hdmi, bit, HDMI_PHY_TST1); +} + +static inline void hdmi_phy_test_dout(struct imx_hdmi *hdmi, + unsigned char bit) +{ + hdmi_writeb(hdmi, bit, HDMI_PHY_TST2); +} + +static bool hdmi_phy_wait_i2c_done(struct imx_hdmi *hdmi, int msec) +{ + unsigned char val = 0; + val = hdmi_readb(hdmi, HDMI_IH_I2CMPHY_STAT0) & 0x3; + while (!val) { + udelay(1000); + if (msec-- == 0) + return false; + val = hdmi_readb(hdmi, HDMI_IH_I2CMPHY_STAT0) & 0x3; + } + return true; +} + +static void __hdmi_phy_i2c_write(struct imx_hdmi *hdmi, unsigned short data, + unsigned char addr) +{ + hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0); + hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR); + hdmi_writeb(hdmi, (unsigned char)(data >> 8), + HDMI_PHY_I2CM_DATAO_1_ADDR); + hdmi_writeb(hdmi, (unsigned char)(data >> 0), + HDMI_PHY_I2CM_DATAO_0_ADDR); + hdmi_writeb(hdmi, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE, + HDMI_PHY_I2CM_OPERATION_ADDR); + hdmi_phy_wait_i2c_done(hdmi, 1000); +} + +static int hdmi_phy_i2c_write(struct imx_hdmi *hdmi, unsigned short data, + unsigned char addr) +{ + __hdmi_phy_i2c_write(hdmi, data, addr); + return 0; +} + +static void imx_hdmi_phy_enable_power(struct imx_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_PDZ_OFFSET, + HDMI_PHY_CONF0_PDZ_MASK); +} + +static void imx_hdmi_phy_enable_tmds(struct imx_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_ENTMDS_OFFSET, + HDMI_PHY_CONF0_ENTMDS_MASK); +} + +static void imx_hdmi_phy_gen2_pddq(struct imx_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET, + HDMI_PHY_CONF0_GEN2_PDDQ_MASK); +} + +static void imx_hdmi_phy_gen2_txpwron(struct imx_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET, + HDMI_PHY_CONF0_GEN2_TXPWRON_MASK); +} + +static void imx_hdmi_phy_sel_data_en_pol(struct imx_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SELDATAENPOL_OFFSET, + HDMI_PHY_CONF0_SELDATAENPOL_MASK); +} + +static void imx_hdmi_phy_sel_interface_control(struct imx_hdmi *hdmi, u8 enable) +{ + hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, + HDMI_PHY_CONF0_SELDIPIF_OFFSET, + HDMI_PHY_CONF0_SELDIPIF_MASK); +} + +enum { + RES_8, + RES_10, + RES_12, + RES_MAX, +}; + +struct mpll_config { + unsigned long mpixelclock; + struct { + u16 cpce; + u16 gmp; + } res[RES_MAX]; +}; + +static const struct mpll_config mpll_config[] = { + { + 45250000, { + { 0x01e0, 0x0000 }, + { 0x21e1, 0x0000 }, + { 0x41e2, 0x0000 } + }, + }, { + 92500000, { + { 0x0140, 0x0005 }, + { 0x2141, 0x0005 }, + { 0x4142, 0x0005 }, + }, + }, { + 148500000, { + { 0x00a0, 0x000a }, + { 0x20a1, 0x000a }, + { 0x40a2, 0x000a }, + }, + }, { + ~0UL, { + { 0x00a0, 0x000a }, + { 0x2001, 0x000f }, + { 0x4002, 0x000f }, + }, + } +}; + +struct curr_ctrl { + unsigned long mpixelclock; + u16 curr[RES_MAX]; +}; + +static const struct curr_ctrl curr_ctrl[] = { + /* pixelclk bpp8 bpp10 bpp12 */ + { + 54000000, { 0x091c, 0x091c, 0x06dc }, + }, { + 58400000, { 0x091c, 0x06dc, 0x06dc }, + }, { + 72000000, { 0x06dc, 0x06dc, 0x091c }, + }, { + 74250000, { 0x06dc, 0x0b5c, 0x091c }, + }, { + 118800000, { 0x091c, 0x091c, 0x06dc }, + }, { + 216000000, { 0x06dc, 0x0b5c, 0x091c }, + } +}; + +static int hdmi_phy_configure(struct imx_hdmi *hdmi, unsigned char prep, + unsigned char res, int cscon) +{ + unsigned res_idx, i; + u8 val, msec; + + if (prep) + return -EINVAL; + + switch (res) { + case 0: /* color resolution 0 is 8 bit colour depth */ + case 8: + res_idx = RES_8; + break; + case 10: + res_idx = RES_10; + break; + case 12: + res_idx = RES_12; + break; + default: + return -EINVAL; + } + + /* Enable csc path */ + if (cscon) + val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH; + else + val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS; + + hdmi_writeb(hdmi, val, HDMI_MC_FLOWCTRL); + + /* gen2 tx power off */ + imx_hdmi_phy_gen2_txpwron(hdmi, 0); + + /* gen2 pddq */ + imx_hdmi_phy_gen2_pddq(hdmi, 1); + + /* PHY reset */ + hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ); + hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_ASSERT, HDMI_MC_PHYRSTZ); + + hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST); + + hdmi_phy_test_clear(hdmi, 1); + hdmi_writeb(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2, + HDMI_PHY_I2CM_SLAVE_ADDR); + hdmi_phy_test_clear(hdmi, 0); + + /* PLL/MPLL Cfg - always match on final entry */ + for (i = 0; i < ARRAY_SIZE(mpll_config) - 1; i++) + if (hdmi->hdmi_data.video_mode.mpixelclock <= + mpll_config[i].mpixelclock) + break; + + hdmi_phy_i2c_write(hdmi, mpll_config[i].res[res_idx].cpce, 0x06); + hdmi_phy_i2c_write(hdmi, mpll_config[i].res[res_idx].gmp, 0x15); + + for (i = 0; i < ARRAY_SIZE(curr_ctrl); i++) + if (hdmi->hdmi_data.video_mode.mpixelclock <= + curr_ctrl[i].mpixelclock) + break; + + if (i >= ARRAY_SIZE(curr_ctrl)) { + dev_err(hdmi->dev, + "Pixel clock %d - unsupported by HDMI\n", + hdmi->hdmi_data.video_mode.mpixelclock); + return -EINVAL; + } + + /* CURRCTRL */ + hdmi_phy_i2c_write(hdmi, curr_ctrl[i].curr[res_idx], 0x10); + + hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */ + hdmi_phy_i2c_write(hdmi, 0x0006, 0x17); + /* RESISTANCE TERM 133Ohm Cfg */ + hdmi_phy_i2c_write(hdmi, 0x0005, 0x19); /* TXTERM */ + /* PREEMP Cgf 0.00 */ + hdmi_phy_i2c_write(hdmi, 0x800d, 0x09); /* CKSYMTXCTRL */ + /* TX/CK LVL 10 */ + hdmi_phy_i2c_write(hdmi, 0x01ad, 0x0E); /* VLEVCTRL */ + /* REMOVE CLK TERM */ + hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */ + + imx_hdmi_phy_enable_power(hdmi, 1); + + /* toggle TMDS enable */ + imx_hdmi_phy_enable_tmds(hdmi, 0); + imx_hdmi_phy_enable_tmds(hdmi, 1); + + /* gen2 tx power on */ + imx_hdmi_phy_gen2_txpwron(hdmi, 1); + imx_hdmi_phy_gen2_pddq(hdmi, 0); + + /*Wait for PHY PLL lock */ + msec = 5; + do { + val = hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; + if (!val) + break; + + if (msec == 0) { + dev_err(hdmi->dev, "PHY PLL not locked\n"); + return -ETIMEDOUT; + } + + udelay(1000); + msec--; + } while (1); + + return 0; +} + +static int imx_hdmi_phy_init(struct imx_hdmi *hdmi) +{ + int i, ret; + bool cscon = false; + + /*check csc whether needed activated in HDMI mode */ + cscon = (is_color_space_conversion(hdmi) && + !hdmi->hdmi_data.video_mode.mdvi); + + /* HDMI Phy spec says to do the phy initialization sequence twice */ + for (i = 0; i < 2; i++) { + imx_hdmi_phy_sel_data_en_pol(hdmi, 1); + imx_hdmi_phy_sel_interface_control(hdmi, 0); + imx_hdmi_phy_enable_tmds(hdmi, 0); + imx_hdmi_phy_enable_power(hdmi, 0); + + /* Enable CSC */ + ret = hdmi_phy_configure(hdmi, 0, 8, cscon); + if (ret) + return ret; + } + + hdmi->phy_enabled = true; + return 0; +} + +static void hdmi_tx_hdcp_config(struct imx_hdmi *hdmi) +{ + u8 de; + + if (hdmi->hdmi_data.video_mode.mdataenablepolarity) + de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH; + else + de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW; + + /* disable rx detect */ + hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_DISABLE, + HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0); + + hdmi_modb(hdmi, de, HDMI_A_VIDPOLCFG_DATAENPOL_MASK, HDMI_A_VIDPOLCFG); + + hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); +} + +static void imx_hdmi_phy_disable(struct imx_hdmi *hdmi) +{ + if (!hdmi->phy_enabled) + return; + + imx_hdmi_phy_enable_tmds(hdmi, 0); + imx_hdmi_phy_enable_power(hdmi, 0); + + hdmi->phy_enabled = false; +} + +/* HDMI Initialization Step B.4 */ +static void imx_hdmi_enable_video_path(struct imx_hdmi *hdmi) +{ + u8 clkdis; + + /* control period minimum duration */ + hdmi_writeb(hdmi, 12, HDMI_FC_CTRLDUR); + hdmi_writeb(hdmi, 32, HDMI_FC_EXCTRLDUR); + hdmi_writeb(hdmi, 1, HDMI_FC_EXCTRLSPAC); + + /* Set to fill TMDS data channels */ + hdmi_writeb(hdmi, 0x0B, HDMI_FC_CH0PREAM); + hdmi_writeb(hdmi, 0x16, HDMI_FC_CH1PREAM); + hdmi_writeb(hdmi, 0x21, HDMI_FC_CH2PREAM); + + /* Enable pixel clock and tmds data path */ + clkdis = 0x7F; + clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE; + hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS); + + clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE; + hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS); + + /* Enable csc path */ + if (is_color_space_conversion(hdmi)) { + clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS); + } +} + +/* Workaround to clear the overflow condition */ +static void imx_hdmi_clear_overflow(struct imx_hdmi *hdmi) +{ + int count; + u8 val; + + /* TMDS software reset */ + hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ); + + val = hdmi_readb(hdmi, HDMI_FC_INVIDCONF); + if (hdmi->dev_type == IMX6DL_HDMI) { + hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF); + return; + } + + for (count = 0; count < 4; count++) + hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF); +} + +static int imx_hdmi_setup(struct imx_hdmi *hdmi, struct fb_videomode *mode) +{ + int ret; + + hdmi->vic = 0; + + dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n"); + hdmi->hdmi_data.video_mode.mdvi = true; + + hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709; + hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0; + hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0; + + /* TODO: Get input format from IPU (via FB driver interface) */ + hdmi->hdmi_data.enc_in_format = RGB; + + hdmi->hdmi_data.enc_out_format = RGB; + + hdmi->hdmi_data.enc_color_depth = 8; + hdmi->hdmi_data.pix_repet_factor = 0; + hdmi->hdmi_data.hdcp_enable = 0; + hdmi->hdmi_data.video_mode.mdataenablepolarity = true; + + /* HDMI Initializateion Step B.2 */ + ret = imx_hdmi_phy_init(hdmi); + if (ret) + return ret; + + /* HDMI Initialization Step B.3 */ + imx_hdmi_enable_video_path(hdmi); + + /* not for DVI mode */ + dev_dbg(hdmi->dev, "%s DVI mode\n", __func__); + + hdmi_video_packetize(hdmi); + hdmi_video_csc(hdmi); + hdmi_video_sample(hdmi); + hdmi_tx_hdcp_config(hdmi); + + imx_hdmi_clear_overflow(hdmi); + + return 0; +} + +static void initialize_hdmi_ih_mutes(struct imx_hdmi *hdmi) +{ + u8 ih_mute; + + /* + * Boot up defaults are: + * HDMI_IH_MUTE = 0x03 (disabled) + * HDMI_IH_MUTE_* = 0x00 (enabled) + * + * Disable top level interrupt bits in HDMI block + */ + ih_mute = hdmi_readb(hdmi, HDMI_IH_MUTE) | + HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT; + + hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); + + /* by default mask all interrupts */ + hdmi_writeb(hdmi, 0xff, HDMI_VP_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK0); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK1); + hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK2); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_MASK0); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_INT_ADDR); + hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_CTLINT_ADDR); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_INT); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_SPDIFINT); + hdmi_writeb(hdmi, 0xff, HDMI_AUD_HBR_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_GP_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_A_APIINTMSK); + hdmi_writeb(hdmi, 0xff, HDMI_CEC_MASK); + hdmi_writeb(hdmi, 0xff, HDMI_I2CM_INT); + hdmi_writeb(hdmi, 0xff, HDMI_I2CM_CTLINT); + + /* Disable interrupts in the IH_MUTE_* registers */ + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT1); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT2); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AS_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CM_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_CEC_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_VP_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CMPHY_STAT0); + hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0); + + /* Enable top level interrupt bits in HDMI block */ + ih_mute &= ~(HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT); + hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); +} + +struct imx_hdmi_data { + unsigned ipu_mask; + enum imx_hdmi_devtype devtype; +}; + +static struct imx_hdmi_data imx6q_hdmi_data = { + .ipu_mask = 0xf, + .devtype = IMX6Q_HDMI, +}; + +static struct imx_hdmi_data imx6dl_hdmi_data = { + .ipu_mask = 0x3, + .devtype = IMX6DL_HDMI, +}; + +static struct of_device_id imx_hdmi_dt_ids[] = { + { + .compatible = "fsl,imx6q-hdmi", + .data = (unsigned long)&imx6q_hdmi_data, + }, { + .compatible = "fsl,imx6dl-hdmi", + .data = (unsigned long)&imx6dl_hdmi_data, + }, { + /* sentinel */ + } +}; + +static int imx_hdmi_prepare(struct ipu_output *output, struct fb_videomode *mode, int di) +{ + struct imx_hdmi *hdmi = container_of(output, struct imx_hdmi, output); + + imx_hdmi_set_ipu_di_mux(hdmi, di); + + return 0; +} + +static int imx_hdmi_commit(struct ipu_output *output, struct fb_videomode *mode, int di) +{ + struct imx_hdmi *hdmi = container_of(output, struct imx_hdmi, output); + + imx_hdmi_setup(hdmi, mode); + + return 0; +} + +static int imx_hdmi_disable(struct ipu_output *output) +{ + struct imx_hdmi *hdmi = container_of(output, struct imx_hdmi, output); + + imx_hdmi_phy_disable(hdmi); + + return 0; +} + +static struct ipu_output_ops imx_hdmi_ops = { + .prepare = imx_hdmi_prepare, + .enable = imx_hdmi_commit, + .disable = imx_hdmi_disable, +}; + +static int imx_hdmi_probe(struct device_d *dev) +{ + struct device_node *np = dev->device_node; + struct device_node *ddc_node; + struct imx_hdmi *hdmi; + int ret; + const struct imx_hdmi_data *devtype; + + ret = dev_get_drvdata(dev, (unsigned long *)&devtype); + if (ret) + return ret; + + hdmi = xzalloc(sizeof(*hdmi)); + + hdmi->dev = dev; + hdmi->connected = 0; + hdmi->sample_rate = 48000; + hdmi->ratio = 100; + + ret = dev_get_drvdata(dev, (unsigned long *)&hdmi->dev_type); + if (ret) + return ret; + + ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); + if (ddc_node) { + hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); + if (!hdmi->ddc) + dev_dbg(hdmi->dev, "failed to read ddc node\n"); + } else { + dev_dbg(hdmi->dev, "no ddc property found\n"); + } + + ddc_node = NULL; + + hdmi->regs = dev_request_mem_region(dev, 0); + if (!hdmi->regs) + return -EBUSY; + + hdmi->isfr_clk = clk_get(hdmi->dev, "isfr"); + if (IS_ERR(hdmi->isfr_clk)) { + ret = PTR_ERR(hdmi->isfr_clk); + dev_err(dev, + "Unable to get HDMI isfr clk: %d\n", ret); + return ret; + } + + ret = clk_enable(hdmi->isfr_clk); + if (ret) + return ret; + + hdmi->iahb_clk = clk_get(hdmi->dev, "iahb"); + if (IS_ERR(hdmi->iahb_clk)) { + ret = PTR_ERR(hdmi->iahb_clk); + dev_err(dev, + "Unable to get HDMI iahb clk: %d\n", ret); + goto err_isfr; + } + + ret = clk_enable(hdmi->iahb_clk); + if (ret) + goto err_isfr; + + /* Product and revision IDs */ + dev_info(dev, + "Detected HDMI controller 0x%x:0x%x:0x%x:0x%x\n", + hdmi_readb(hdmi, HDMI_DESIGN_ID), + hdmi_readb(hdmi, HDMI_REVISION_ID), + hdmi_readb(hdmi, HDMI_PRODUCT_ID0), + hdmi_readb(hdmi, HDMI_PRODUCT_ID1)); + + initialize_hdmi_ih_mutes(hdmi); + + /* + * To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator + * N and cts values before enabling phy + */ + hdmi_init_clk_regenerator(hdmi); + + /* + * Configure registers related to HDMI interrupt + * generation before registering IRQ. + */ + hdmi_writeb(hdmi, HDMI_PHY_HPD, HDMI_PHY_POL0); + + /* Clear Hotplug interrupts */ + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); + + hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL, + HDMI_PHY_I2CM_INT_ADDR); + + hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL | + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL, + HDMI_PHY_I2CM_CTLINT_ADDR); + + /* enable cable hot plug irq */ + hdmi_writeb(hdmi, (u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0); + + /* Unmute interrupts */ + hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); + + hdmi->output.ops = &imx_hdmi_ops; + hdmi->output.di_clkflags = IPU_DI_CLKMODE_EXT | IPU_DI_CLKMODE_SYNC; + hdmi->output.out_pixel_fmt = V4L2_PIX_FMT_RGB24; + hdmi->output.name = asprintf("hdmi-0"); + hdmi->output.ipu_mask = devtype->ipu_mask; + hdmi->output.edid_i2c_adapter = hdmi->ddc; + hdmi->output.modes = of_get_display_timings(np); + + ipu_register_output(&hdmi->output); + + return 0; + +err_isfr: + clk_disable(hdmi->isfr_clk); + + return ret; +} + +static struct driver_d imx_hdmi_driver = { + .probe = imx_hdmi_probe, + .of_compatible = imx_hdmi_dt_ids, + .name = "imx-hdmi", +}; +device_platform_driver(imx_hdmi_driver); + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_DESCRIPTION("i.MX6 HDMI transmitter driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/imx-ipu-v3/imx-hdmi.h b/drivers/video/imx-ipu-v3/imx-hdmi.h new file mode 100644 index 0000000000..39b677689d --- /dev/null +++ b/drivers/video/imx-ipu-v3/imx-hdmi.h @@ -0,0 +1,1032 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. + * + * 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 __IMX_HDMI_H__ +#define __IMX_HDMI_H__ + +/* Identification Registers */ +#define HDMI_DESIGN_ID 0x0000 +#define HDMI_REVISION_ID 0x0001 +#define HDMI_PRODUCT_ID0 0x0002 +#define HDMI_PRODUCT_ID1 0x0003 +#define HDMI_CONFIG0_ID 0x0004 +#define HDMI_CONFIG1_ID 0x0005 +#define HDMI_CONFIG2_ID 0x0006 +#define HDMI_CONFIG3_ID 0x0007 + +/* Interrupt Registers */ +#define HDMI_IH_FC_STAT0 0x0100 +#define HDMI_IH_FC_STAT1 0x0101 +#define HDMI_IH_FC_STAT2 0x0102 +#define HDMI_IH_AS_STAT0 0x0103 +#define HDMI_IH_PHY_STAT0 0x0104 +#define HDMI_IH_I2CM_STAT0 0x0105 +#define HDMI_IH_CEC_STAT0 0x0106 +#define HDMI_IH_VP_STAT0 0x0107 +#define HDMI_IH_I2CMPHY_STAT0 0x0108 +#define HDMI_IH_AHBDMAAUD_STAT0 0x0109 + +#define HDMI_IH_MUTE_FC_STAT0 0x0180 +#define HDMI_IH_MUTE_FC_STAT1 0x0181 +#define HDMI_IH_MUTE_FC_STAT2 0x0182 +#define HDMI_IH_MUTE_AS_STAT0 0x0183 +#define HDMI_IH_MUTE_PHY_STAT0 0x0184 +#define HDMI_IH_MUTE_I2CM_STAT0 0x0185 +#define HDMI_IH_MUTE_CEC_STAT0 0x0186 +#define HDMI_IH_MUTE_VP_STAT0 0x0187 +#define HDMI_IH_MUTE_I2CMPHY_STAT0 0x0188 +#define HDMI_IH_MUTE_AHBDMAAUD_STAT0 0x0189 +#define HDMI_IH_MUTE 0x01FF + +/* Video Sample Registers */ +#define HDMI_TX_INVID0 0x0200 +#define HDMI_TX_INSTUFFING 0x0201 +#define HDMI_TX_GYDATA0 0x0202 +#define HDMI_TX_GYDATA1 0x0203 +#define HDMI_TX_RCRDATA0 0x0204 +#define HDMI_TX_RCRDATA1 0x0205 +#define HDMI_TX_BCBDATA0 0x0206 +#define HDMI_TX_BCBDATA1 0x0207 + +/* Video Packetizer Registers */ +#define HDMI_VP_STATUS 0x0800 +#define HDMI_VP_PR_CD 0x0801 +#define HDMI_VP_STUFF 0x0802 +#define HDMI_VP_REMAP 0x0803 +#define HDMI_VP_CONF 0x0804 +#define HDMI_VP_STAT 0x0805 +#define HDMI_VP_INT 0x0806 +#define HDMI_VP_MASK 0x0807 +#define HDMI_VP_POL 0x0808 + +/* Frame Composer Registers */ +#define HDMI_FC_INVIDCONF 0x1000 +#define HDMI_FC_INHACTV0 0x1001 +#define HDMI_FC_INHACTV1 0x1002 +#define HDMI_FC_INHBLANK0 0x1003 +#define HDMI_FC_INHBLANK1 0x1004 +#define HDMI_FC_INVACTV0 0x1005 +#define HDMI_FC_INVACTV1 0x1006 +#define HDMI_FC_INVBLANK 0x1007 +#define HDMI_FC_HSYNCINDELAY0 0x1008 +#define HDMI_FC_HSYNCINDELAY1 0x1009 +#define HDMI_FC_HSYNCINWIDTH0 0x100A +#define HDMI_FC_HSYNCINWIDTH1 0x100B +#define HDMI_FC_VSYNCINDELAY 0x100C +#define HDMI_FC_VSYNCINWIDTH 0x100D +#define HDMI_FC_INFREQ0 0x100E +#define HDMI_FC_INFREQ1 0x100F +#define HDMI_FC_INFREQ2 0x1010 +#define HDMI_FC_CTRLDUR 0x1011 +#define HDMI_FC_EXCTRLDUR 0x1012 +#define HDMI_FC_EXCTRLSPAC 0x1013 +#define HDMI_FC_CH0PREAM 0x1014 +#define HDMI_FC_CH1PREAM 0x1015 +#define HDMI_FC_CH2PREAM 0x1016 +#define HDMI_FC_AVICONF3 0x1017 +#define HDMI_FC_GCP 0x1018 +#define HDMI_FC_AVICONF0 0x1019 +#define HDMI_FC_AVICONF1 0x101A +#define HDMI_FC_AVICONF2 0x101B +#define HDMI_FC_AVIVID 0x101C +#define HDMI_FC_AVIETB0 0x101D +#define HDMI_FC_AVIETB1 0x101E +#define HDMI_FC_AVISBB0 0x101F +#define HDMI_FC_AVISBB1 0x1020 +#define HDMI_FC_AVIELB0 0x1021 +#define HDMI_FC_AVIELB1 0x1022 +#define HDMI_FC_AVISRB0 0x1023 +#define HDMI_FC_AVISRB1 0x1024 +#define HDMI_FC_AUDICONF0 0x1025 +#define HDMI_FC_AUDICONF1 0x1026 +#define HDMI_FC_AUDICONF2 0x1027 +#define HDMI_FC_AUDICONF3 0x1028 +#define HDMI_FC_VSDIEEEID0 0x1029 +#define HDMI_FC_VSDSIZE 0x102A +#define HDMI_FC_VSDIEEEID1 0x1030 +#define HDMI_FC_VSDIEEEID2 0x1031 +#define HDMI_FC_VSDPAYLOAD0 0x1032 +#define HDMI_FC_VSDPAYLOAD1 0x1033 +#define HDMI_FC_VSDPAYLOAD2 0x1034 +#define HDMI_FC_VSDPAYLOAD3 0x1035 +#define HDMI_FC_VSDPAYLOAD4 0x1036 +#define HDMI_FC_VSDPAYLOAD5 0x1037 +#define HDMI_FC_VSDPAYLOAD6 0x1038 +#define HDMI_FC_VSDPAYLOAD7 0x1039 +#define HDMI_FC_VSDPAYLOAD8 0x103A +#define HDMI_FC_VSDPAYLOAD9 0x103B +#define HDMI_FC_VSDPAYLOAD10 0x103C +#define HDMI_FC_VSDPAYLOAD11 0x103D +#define HDMI_FC_VSDPAYLOAD12 0x103E +#define HDMI_FC_VSDPAYLOAD13 0x103F +#define HDMI_FC_VSDPAYLOAD14 0x1040 +#define HDMI_FC_VSDPAYLOAD15 0x1041 +#define HDMI_FC_VSDPAYLOAD16 0x1042 +#define HDMI_FC_VSDPAYLOAD17 0x1043 +#define HDMI_FC_VSDPAYLOAD18 0x1044 +#define HDMI_FC_VSDPAYLOAD19 0x1045 +#define HDMI_FC_VSDPAYLOAD20 0x1046 +#define HDMI_FC_VSDPAYLOAD21 0x1047 +#define HDMI_FC_VSDPAYLOAD22 0x1048 +#define HDMI_FC_VSDPAYLOAD23 0x1049 +#define HDMI_FC_SPDVENDORNAME0 0x104A +#define HDMI_FC_SPDVENDORNAME1 0x104B +#define HDMI_FC_SPDVENDORNAME2 0x104C +#define HDMI_FC_SPDVENDORNAME3 0x104D +#define HDMI_FC_SPDVENDORNAME4 0x104E +#define HDMI_FC_SPDVENDORNAME5 0x104F +#define HDMI_FC_SPDVENDORNAME6 0x1050 +#define HDMI_FC_SPDVENDORNAME7 0x1051 +#define HDMI_FC_SDPPRODUCTNAME0 0x1052 +#define HDMI_FC_SDPPRODUCTNAME1 0x1053 +#define HDMI_FC_SDPPRODUCTNAME2 0x1054 +#define HDMI_FC_SDPPRODUCTNAME3 0x1055 +#define HDMI_FC_SDPPRODUCTNAME4 0x1056 +#define HDMI_FC_SDPPRODUCTNAME5 0x1057 +#define HDMI_FC_SDPPRODUCTNAME6 0x1058 +#define HDMI_FC_SDPPRODUCTNAME7 0x1059 +#define HDMI_FC_SDPPRODUCTNAME8 0x105A +#define HDMI_FC_SDPPRODUCTNAME9 0x105B +#define HDMI_FC_SDPPRODUCTNAME10 0x105C +#define HDMI_FC_SDPPRODUCTNAME11 0x105D +#define HDMI_FC_SDPPRODUCTNAME12 0x105E +#define HDMI_FC_SDPPRODUCTNAME13 0x105F +#define HDMI_FC_SDPPRODUCTNAME14 0x1060 +#define HDMI_FC_SPDPRODUCTNAME15 0x1061 +#define HDMI_FC_SPDDEVICEINF 0x1062 +#define HDMI_FC_AUDSCONF 0x1063 +#define HDMI_FC_AUDSSTAT 0x1064 +#define HDMI_FC_DATACH0FILL 0x1070 +#define HDMI_FC_DATACH1FILL 0x1071 +#define HDMI_FC_DATACH2FILL 0x1072 +#define HDMI_FC_CTRLQHIGH 0x1073 +#define HDMI_FC_CTRLQLOW 0x1074 +#define HDMI_FC_ACP0 0x1075 +#define HDMI_FC_ACP28 0x1076 +#define HDMI_FC_ACP27 0x1077 +#define HDMI_FC_ACP26 0x1078 +#define HDMI_FC_ACP25 0x1079 +#define HDMI_FC_ACP24 0x107A +#define HDMI_FC_ACP23 0x107B +#define HDMI_FC_ACP22 0x107C +#define HDMI_FC_ACP21 0x107D +#define HDMI_FC_ACP20 0x107E +#define HDMI_FC_ACP19 0x107F +#define HDMI_FC_ACP18 0x1080 +#define HDMI_FC_ACP17 0x1081 +#define HDMI_FC_ACP16 0x1082 +#define HDMI_FC_ACP15 0x1083 +#define HDMI_FC_ACP14 0x1084 +#define HDMI_FC_ACP13 0x1085 +#define HDMI_FC_ACP12 0x1086 +#define HDMI_FC_ACP11 0x1087 +#define HDMI_FC_ACP10 0x1088 +#define HDMI_FC_ACP9 0x1089 +#define HDMI_FC_ACP8 0x108A +#define HDMI_FC_ACP7 0x108B +#define HDMI_FC_ACP6 0x108C +#define HDMI_FC_ACP5 0x108D +#define HDMI_FC_ACP4 0x108E +#define HDMI_FC_ACP3 0x108F +#define HDMI_FC_ACP2 0x1090 +#define HDMI_FC_ACP1 0x1091 +#define HDMI_FC_ISCR1_0 0x1092 +#define HDMI_FC_ISCR1_16 0x1093 +#define HDMI_FC_ISCR1_15 0x1094 +#define HDMI_FC_ISCR1_14 0x1095 +#define HDMI_FC_ISCR1_13 0x1096 +#define HDMI_FC_ISCR1_12 0x1097 +#define HDMI_FC_ISCR1_11 0x1098 +#define HDMI_FC_ISCR1_10 0x1099 +#define HDMI_FC_ISCR1_9 0x109A +#define HDMI_FC_ISCR1_8 0x109B +#define HDMI_FC_ISCR1_7 0x109C +#define HDMI_FC_ISCR1_6 0x109D +#define HDMI_FC_ISCR1_5 0x109E +#define HDMI_FC_ISCR1_4 0x109F +#define HDMI_FC_ISCR1_3 0x10A0 +#define HDMI_FC_ISCR1_2 0x10A1 +#define HDMI_FC_ISCR1_1 0x10A2 +#define HDMI_FC_ISCR2_15 0x10A3 +#define HDMI_FC_ISCR2_14 0x10A4 +#define HDMI_FC_ISCR2_13 0x10A5 +#define HDMI_FC_ISCR2_12 0x10A6 +#define HDMI_FC_ISCR2_11 0x10A7 +#define HDMI_FC_ISCR2_10 0x10A8 +#define HDMI_FC_ISCR2_9 0x10A9 +#define HDMI_FC_ISCR2_8 0x10AA +#define HDMI_FC_ISCR2_7 0x10AB +#define HDMI_FC_ISCR2_6 0x10AC +#define HDMI_FC_ISCR2_5 0x10AD +#define HDMI_FC_ISCR2_4 0x10AE +#define HDMI_FC_ISCR2_3 0x10AF +#define HDMI_FC_ISCR2_2 0x10B0 +#define HDMI_FC_ISCR2_1 0x10B1 +#define HDMI_FC_ISCR2_0 0x10B2 +#define HDMI_FC_DATAUTO0 0x10B3 +#define HDMI_FC_DATAUTO1 0x10B4 +#define HDMI_FC_DATAUTO2 0x10B5 +#define HDMI_FC_DATMAN 0x10B6 +#define HDMI_FC_DATAUTO3 0x10B7 +#define HDMI_FC_RDRB0 0x10B8 +#define HDMI_FC_RDRB1 0x10B9 +#define HDMI_FC_RDRB2 0x10BA +#define HDMI_FC_RDRB3 0x10BB +#define HDMI_FC_RDRB4 0x10BC +#define HDMI_FC_RDRB5 0x10BD +#define HDMI_FC_RDRB6 0x10BE +#define HDMI_FC_RDRB7 0x10BF +#define HDMI_FC_STAT0 0x10D0 +#define HDMI_FC_INT0 0x10D1 +#define HDMI_FC_MASK0 0x10D2 +#define HDMI_FC_POL0 0x10D3 +#define HDMI_FC_STAT1 0x10D4 +#define HDMI_FC_INT1 0x10D5 +#define HDMI_FC_MASK1 0x10D6 +#define HDMI_FC_POL1 0x10D7 +#define HDMI_FC_STAT2 0x10D8 +#define HDMI_FC_INT2 0x10D9 +#define HDMI_FC_MASK2 0x10DA +#define HDMI_FC_POL2 0x10DB +#define HDMI_FC_PRCONF 0x10E0 + +#define HDMI_FC_GMD_STAT 0x1100 +#define HDMI_FC_GMD_EN 0x1101 +#define HDMI_FC_GMD_UP 0x1102 +#define HDMI_FC_GMD_CONF 0x1103 +#define HDMI_FC_GMD_HB 0x1104 +#define HDMI_FC_GMD_PB0 0x1105 +#define HDMI_FC_GMD_PB1 0x1106 +#define HDMI_FC_GMD_PB2 0x1107 +#define HDMI_FC_GMD_PB3 0x1108 +#define HDMI_FC_GMD_PB4 0x1109 +#define HDMI_FC_GMD_PB5 0x110A +#define HDMI_FC_GMD_PB6 0x110B +#define HDMI_FC_GMD_PB7 0x110C +#define HDMI_FC_GMD_PB8 0x110D +#define HDMI_FC_GMD_PB9 0x110E +#define HDMI_FC_GMD_PB10 0x110F +#define HDMI_FC_GMD_PB11 0x1110 +#define HDMI_FC_GMD_PB12 0x1111 +#define HDMI_FC_GMD_PB13 0x1112 +#define HDMI_FC_GMD_PB14 0x1113 +#define HDMI_FC_GMD_PB15 0x1114 +#define HDMI_FC_GMD_PB16 0x1115 +#define HDMI_FC_GMD_PB17 0x1116 +#define HDMI_FC_GMD_PB18 0x1117 +#define HDMI_FC_GMD_PB19 0x1118 +#define HDMI_FC_GMD_PB20 0x1119 +#define HDMI_FC_GMD_PB21 0x111A +#define HDMI_FC_GMD_PB22 0x111B +#define HDMI_FC_GMD_PB23 0x111C +#define HDMI_FC_GMD_PB24 0x111D +#define HDMI_FC_GMD_PB25 0x111E +#define HDMI_FC_GMD_PB26 0x111F +#define HDMI_FC_GMD_PB27 0x1120 + +#define HDMI_FC_DBGFORCE 0x1200 +#define HDMI_FC_DBGAUD0CH0 0x1201 +#define HDMI_FC_DBGAUD1CH0 0x1202 +#define HDMI_FC_DBGAUD2CH0 0x1203 +#define HDMI_FC_DBGAUD0CH1 0x1204 +#define HDMI_FC_DBGAUD1CH1 0x1205 +#define HDMI_FC_DBGAUD2CH1 0x1206 +#define HDMI_FC_DBGAUD0CH2 0x1207 +#define HDMI_FC_DBGAUD1CH2 0x1208 +#define HDMI_FC_DBGAUD2CH2 0x1209 +#define HDMI_FC_DBGAUD0CH3 0x120A +#define HDMI_FC_DBGAUD1CH3 0x120B +#define HDMI_FC_DBGAUD2CH3 0x120C +#define HDMI_FC_DBGAUD0CH4 0x120D +#define HDMI_FC_DBGAUD1CH4 0x120E +#define HDMI_FC_DBGAUD2CH4 0x120F +#define HDMI_FC_DBGAUD0CH5 0x1210 +#define HDMI_FC_DBGAUD1CH5 0x1211 +#define HDMI_FC_DBGAUD2CH5 0x1212 +#define HDMI_FC_DBGAUD0CH6 0x1213 +#define HDMI_FC_DBGAUD1CH6 0x1214 +#define HDMI_FC_DBGAUD2CH6 0x1215 +#define HDMI_FC_DBGAUD0CH7 0x1216 +#define HDMI_FC_DBGAUD1CH7 0x1217 +#define HDMI_FC_DBGAUD2CH7 0x1218 +#define HDMI_FC_DBGTMDS0 0x1219 +#define HDMI_FC_DBGTMDS1 0x121A +#define HDMI_FC_DBGTMDS2 0x121B + +/* HDMI Source PHY Registers */ +#define HDMI_PHY_CONF0 0x3000 +#define HDMI_PHY_TST0 0x3001 +#define HDMI_PHY_TST1 0x3002 +#define HDMI_PHY_TST2 0x3003 +#define HDMI_PHY_STAT0 0x3004 +#define HDMI_PHY_INT0 0x3005 +#define HDMI_PHY_MASK0 0x3006 +#define HDMI_PHY_POL0 0x3007 + +/* HDMI Master PHY Registers */ +#define HDMI_PHY_I2CM_SLAVE_ADDR 0x3020 +#define HDMI_PHY_I2CM_ADDRESS_ADDR 0x3021 +#define HDMI_PHY_I2CM_DATAO_1_ADDR 0x3022 +#define HDMI_PHY_I2CM_DATAO_0_ADDR 0x3023 +#define HDMI_PHY_I2CM_DATAI_1_ADDR 0x3024 +#define HDMI_PHY_I2CM_DATAI_0_ADDR 0x3025 +#define HDMI_PHY_I2CM_OPERATION_ADDR 0x3026 +#define HDMI_PHY_I2CM_INT_ADDR 0x3027 +#define HDMI_PHY_I2CM_CTLINT_ADDR 0x3028 +#define HDMI_PHY_I2CM_DIV_ADDR 0x3029 +#define HDMI_PHY_I2CM_SOFTRSTZ_ADDR 0x302a +#define HDMI_PHY_I2CM_SS_SCL_HCNT_1_ADDR 0x302b +#define HDMI_PHY_I2CM_SS_SCL_HCNT_0_ADDR 0x302c +#define HDMI_PHY_I2CM_SS_SCL_LCNT_1_ADDR 0x302d +#define HDMI_PHY_I2CM_SS_SCL_LCNT_0_ADDR 0x302e +#define HDMI_PHY_I2CM_FS_SCL_HCNT_1_ADDR 0x302f +#define HDMI_PHY_I2CM_FS_SCL_HCNT_0_ADDR 0x3030 +#define HDMI_PHY_I2CM_FS_SCL_LCNT_1_ADDR 0x3031 +#define HDMI_PHY_I2CM_FS_SCL_LCNT_0_ADDR 0x3032 + +/* Audio Sampler Registers */ +#define HDMI_AUD_CONF0 0x3100 +#define HDMI_AUD_CONF1 0x3101 +#define HDMI_AUD_INT 0x3102 +#define HDMI_AUD_CONF2 0x3103 +#define HDMI_AUD_N1 0x3200 +#define HDMI_AUD_N2 0x3201 +#define HDMI_AUD_N3 0x3202 +#define HDMI_AUD_CTS1 0x3203 +#define HDMI_AUD_CTS2 0x3204 +#define HDMI_AUD_CTS3 0x3205 +#define HDMI_AUD_INPUTCLKFS 0x3206 +#define HDMI_AUD_SPDIFINT 0x3302 +#define HDMI_AUD_CONF0_HBR 0x3400 +#define HDMI_AUD_HBR_STATUS 0x3401 +#define HDMI_AUD_HBR_INT 0x3402 +#define HDMI_AUD_HBR_POL 0x3403 +#define HDMI_AUD_HBR_MASK 0x3404 + +/* + * Generic Parallel Audio Interface Registers + * Not used as GPAUD interface is not enabled in hw + */ +#define HDMI_GP_CONF0 0x3500 +#define HDMI_GP_CONF1 0x3501 +#define HDMI_GP_CONF2 0x3502 +#define HDMI_GP_STAT 0x3503 +#define HDMI_GP_INT 0x3504 +#define HDMI_GP_MASK 0x3505 +#define HDMI_GP_POL 0x3506 + +/* Audio DMA Registers */ +#define HDMI_AHB_DMA_CONF0 0x3600 +#define HDMI_AHB_DMA_START 0x3601 +#define HDMI_AHB_DMA_STOP 0x3602 +#define HDMI_AHB_DMA_THRSLD 0x3603 +#define HDMI_AHB_DMA_STRADDR0 0x3604 +#define HDMI_AHB_DMA_STRADDR1 0x3605 +#define HDMI_AHB_DMA_STRADDR2 0x3606 +#define HDMI_AHB_DMA_STRADDR3 0x3607 +#define HDMI_AHB_DMA_STPADDR0 0x3608 +#define HDMI_AHB_DMA_STPADDR1 0x3609 +#define HDMI_AHB_DMA_STPADDR2 0x360a +#define HDMI_AHB_DMA_STPADDR3 0x360b +#define HDMI_AHB_DMA_BSTADDR0 0x360c +#define HDMI_AHB_DMA_BSTADDR1 0x360d +#define HDMI_AHB_DMA_BSTADDR2 0x360e +#define HDMI_AHB_DMA_BSTADDR3 0x360f +#define HDMI_AHB_DMA_MBLENGTH0 0x3610 +#define HDMI_AHB_DMA_MBLENGTH1 0x3611 +#define HDMI_AHB_DMA_STAT 0x3612 +#define HDMI_AHB_DMA_INT 0x3613 +#define HDMI_AHB_DMA_MASK 0x3614 +#define HDMI_AHB_DMA_POL 0x3615 +#define HDMI_AHB_DMA_CONF1 0x3616 +#define HDMI_AHB_DMA_BUFFSTAT 0x3617 +#define HDMI_AHB_DMA_BUFFINT 0x3618 +#define HDMI_AHB_DMA_BUFFMASK 0x3619 +#define HDMI_AHB_DMA_BUFFPOL 0x361a + +/* Main Controller Registers */ +#define HDMI_MC_SFRDIV 0x4000 +#define HDMI_MC_CLKDIS 0x4001 +#define HDMI_MC_SWRSTZ 0x4002 +#define HDMI_MC_OPCTRL 0x4003 +#define HDMI_MC_FLOWCTRL 0x4004 +#define HDMI_MC_PHYRSTZ 0x4005 +#define HDMI_MC_LOCKONCLOCK 0x4006 +#define HDMI_MC_HEACPHY_RST 0x4007 + +/* Color Space Converter Registers */ +#define HDMI_CSC_CFG 0x4100 +#define HDMI_CSC_SCALE 0x4101 +#define HDMI_CSC_COEF_A1_MSB 0x4102 +#define HDMI_CSC_COEF_A1_LSB 0x4103 +#define HDMI_CSC_COEF_A2_MSB 0x4104 +#define HDMI_CSC_COEF_A2_LSB 0x4105 +#define HDMI_CSC_COEF_A3_MSB 0x4106 +#define HDMI_CSC_COEF_A3_LSB 0x4107 +#define HDMI_CSC_COEF_A4_MSB 0x4108 +#define HDMI_CSC_COEF_A4_LSB 0x4109 +#define HDMI_CSC_COEF_B1_MSB 0x410A +#define HDMI_CSC_COEF_B1_LSB 0x410B +#define HDMI_CSC_COEF_B2_MSB 0x410C +#define HDMI_CSC_COEF_B2_LSB 0x410D +#define HDMI_CSC_COEF_B3_MSB 0x410E +#define HDMI_CSC_COEF_B3_LSB 0x410F +#define HDMI_CSC_COEF_B4_MSB 0x4110 +#define HDMI_CSC_COEF_B4_LSB 0x4111 +#define HDMI_CSC_COEF_C1_MSB 0x4112 +#define HDMI_CSC_COEF_C1_LSB 0x4113 +#define HDMI_CSC_COEF_C2_MSB 0x4114 +#define HDMI_CSC_COEF_C2_LSB 0x4115 +#define HDMI_CSC_COEF_C3_MSB 0x4116 +#define HDMI_CSC_COEF_C3_LSB 0x4117 +#define HDMI_CSC_COEF_C4_MSB 0x4118 +#define HDMI_CSC_COEF_C4_LSB 0x4119 + +/* HDCP Encryption Engine Registers */ +#define HDMI_A_HDCPCFG0 0x5000 +#define HDMI_A_HDCPCFG1 0x5001 +#define HDMI_A_HDCPOBS0 0x5002 +#define HDMI_A_HDCPOBS1 0x5003 +#define HDMI_A_HDCPOBS2 0x5004 +#define HDMI_A_HDCPOBS3 0x5005 +#define HDMI_A_APIINTCLR 0x5006 +#define HDMI_A_APIINTSTAT 0x5007 +#define HDMI_A_APIINTMSK 0x5008 +#define HDMI_A_VIDPOLCFG 0x5009 +#define HDMI_A_OESSWCFG 0x500A +#define HDMI_A_TIMER1SETUP0 0x500B +#define HDMI_A_TIMER1SETUP1 0x500C +#define HDMI_A_TIMER2SETUP0 0x500D +#define HDMI_A_TIMER2SETUP1 0x500E +#define HDMI_A_100MSCFG 0x500F +#define HDMI_A_2SCFG0 0x5010 +#define HDMI_A_2SCFG1 0x5011 +#define HDMI_A_5SCFG0 0x5012 +#define HDMI_A_5SCFG1 0x5013 +#define HDMI_A_SRMVERLSB 0x5014 +#define HDMI_A_SRMVERMSB 0x5015 +#define HDMI_A_SRMCTRL 0x5016 +#define HDMI_A_SFRSETUP 0x5017 +#define HDMI_A_I2CHSETUP 0x5018 +#define HDMI_A_INTSETUP 0x5019 +#define HDMI_A_PRESETUP 0x501A +#define HDMI_A_SRM_BASE 0x5020 + +/* CEC Engine Registers */ +#define HDMI_CEC_CTRL 0x7D00 +#define HDMI_CEC_STAT 0x7D01 +#define HDMI_CEC_MASK 0x7D02 +#define HDMI_CEC_POLARITY 0x7D03 +#define HDMI_CEC_INT 0x7D04 +#define HDMI_CEC_ADDR_L 0x7D05 +#define HDMI_CEC_ADDR_H 0x7D06 +#define HDMI_CEC_TX_CNT 0x7D07 +#define HDMI_CEC_RX_CNT 0x7D08 +#define HDMI_CEC_TX_DATA0 0x7D10 +#define HDMI_CEC_TX_DATA1 0x7D11 +#define HDMI_CEC_TX_DATA2 0x7D12 +#define HDMI_CEC_TX_DATA3 0x7D13 +#define HDMI_CEC_TX_DATA4 0x7D14 +#define HDMI_CEC_TX_DATA5 0x7D15 +#define HDMI_CEC_TX_DATA6 0x7D16 +#define HDMI_CEC_TX_DATA7 0x7D17 +#define HDMI_CEC_TX_DATA8 0x7D18 +#define HDMI_CEC_TX_DATA9 0x7D19 +#define HDMI_CEC_TX_DATA10 0x7D1a +#define HDMI_CEC_TX_DATA11 0x7D1b +#define HDMI_CEC_TX_DATA12 0x7D1c +#define HDMI_CEC_TX_DATA13 0x7D1d +#define HDMI_CEC_TX_DATA14 0x7D1e +#define HDMI_CEC_TX_DATA15 0x7D1f +#define HDMI_CEC_RX_DATA0 0x7D20 +#define HDMI_CEC_RX_DATA1 0x7D21 +#define HDMI_CEC_RX_DATA2 0x7D22 +#define HDMI_CEC_RX_DATA3 0x7D23 +#define HDMI_CEC_RX_DATA4 0x7D24 +#define HDMI_CEC_RX_DATA5 0x7D25 +#define HDMI_CEC_RX_DATA6 0x7D26 +#define HDMI_CEC_RX_DATA7 0x7D27 +#define HDMI_CEC_RX_DATA8 0x7D28 +#define HDMI_CEC_RX_DATA9 0x7D29 +#define HDMI_CEC_RX_DATA10 0x7D2a +#define HDMI_CEC_RX_DATA11 0x7D2b +#define HDMI_CEC_RX_DATA12 0x7D2c +#define HDMI_CEC_RX_DATA13 0x7D2d +#define HDMI_CEC_RX_DATA14 0x7D2e +#define HDMI_CEC_RX_DATA15 0x7D2f +#define HDMI_CEC_LOCK 0x7D30 +#define HDMI_CEC_WKUPCTRL 0x7D31 + +/* I2C Master Registers (E-DDC) */ +#define HDMI_I2CM_SLAVE 0x7E00 +#define HDMI_I2CMESS 0x7E01 +#define HDMI_I2CM_DATAO 0x7E02 +#define HDMI_I2CM_DATAI 0x7E03 +#define HDMI_I2CM_OPERATION 0x7E04 +#define HDMI_I2CM_INT 0x7E05 +#define HDMI_I2CM_CTLINT 0x7E06 +#define HDMI_I2CM_DIV 0x7E07 +#define HDMI_I2CM_SEGADDR 0x7E08 +#define HDMI_I2CM_SOFTRSTZ 0x7E09 +#define HDMI_I2CM_SEGPTR 0x7E0A +#define HDMI_I2CM_SS_SCL_HCNT_1_ADDR 0x7E0B +#define HDMI_I2CM_SS_SCL_HCNT_0_ADDR 0x7E0C +#define HDMI_I2CM_SS_SCL_LCNT_1_ADDR 0x7E0D +#define HDMI_I2CM_SS_SCL_LCNT_0_ADDR 0x7E0E +#define HDMI_I2CM_FS_SCL_HCNT_1_ADDR 0x7E0F +#define HDMI_I2CM_FS_SCL_HCNT_0_ADDR 0x7E10 +#define HDMI_I2CM_FS_SCL_LCNT_1_ADDR 0x7E11 +#define HDMI_I2CM_FS_SCL_LCNT_0_ADDR 0x7E12 + +enum { +/* IH_FC_INT2 field values */ + HDMI_IH_FC_INT2_OVERFLOW_MASK = 0x03, + HDMI_IH_FC_INT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_FC_INT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* IH_FC_STAT2 field values */ + HDMI_IH_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_IH_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* IH_PHY_STAT0 field values */ + HDMI_IH_PHY_STAT0_RX_SENSE3 = 0x20, + HDMI_IH_PHY_STAT0_RX_SENSE2 = 0x10, + HDMI_IH_PHY_STAT0_RX_SENSE1 = 0x8, + HDMI_IH_PHY_STAT0_RX_SENSE0 = 0x4, + HDMI_IH_PHY_STAT0_TX_PHY_LOCK = 0x2, + HDMI_IH_PHY_STAT0_HPD = 0x1, + +/* IH_MUTE_I2CMPHY_STAT0 field values */ + HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYDONE = 0x2, + HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYERROR = 0x1, + +/* IH_AHBDMAAUD_STAT0 field values */ + HDMI_IH_AHBDMAAUD_STAT0_ERROR = 0x20, + HDMI_IH_AHBDMAAUD_STAT0_LOST = 0x10, + HDMI_IH_AHBDMAAUD_STAT0_RETRY = 0x08, + HDMI_IH_AHBDMAAUD_STAT0_DONE = 0x04, + HDMI_IH_AHBDMAAUD_STAT0_BUFFFULL = 0x02, + HDMI_IH_AHBDMAAUD_STAT0_BUFFEMPTY = 0x01, + +/* IH_MUTE_FC_STAT2 field values */ + HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_IH_MUTE_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_IH_MUTE_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* IH_MUTE_AHBDMAAUD_STAT0 field values */ + HDMI_IH_MUTE_AHBDMAAUD_STAT0_ERROR = 0x20, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_LOST = 0x10, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_RETRY = 0x08, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_DONE = 0x04, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFFULL = 0x02, + HDMI_IH_MUTE_AHBDMAAUD_STAT0_BUFFEMPTY = 0x01, + +/* IH_MUTE field values */ + HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT = 0x2, + HDMI_IH_MUTE_MUTE_ALL_INTERRUPT = 0x1, + +/* TX_INVID0 field values */ + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_MASK = 0x80, + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_ENABLE = 0x80, + HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE = 0x00, + HDMI_TX_INVID0_VIDEO_MAPPING_MASK = 0x1F, + HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET = 0, + +/* TX_INSTUFFING field values */ + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_MASK = 0x4, + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE = 0x4, + HDMI_TX_INSTUFFING_BDBDATA_STUFFING_DISABLE = 0x0, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_MASK = 0x2, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE = 0x2, + HDMI_TX_INSTUFFING_RCRDATA_STUFFING_DISABLE = 0x0, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_MASK = 0x1, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE = 0x1, + HDMI_TX_INSTUFFING_GYDATA_STUFFING_DISABLE = 0x0, + +/* VP_PR_CD field values */ + HDMI_VP_PR_CD_COLOR_DEPTH_MASK = 0xF0, + HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET = 4, + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK = 0x0F, + HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET = 0, + +/* VP_STUFF field values */ + HDMI_VP_STUFF_IDEFAULT_PHASE_MASK = 0x20, + HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET = 5, + HDMI_VP_STUFF_IFIX_PP_TO_LAST_MASK = 0x10, + HDMI_VP_STUFF_IFIX_PP_TO_LAST_OFFSET = 4, + HDMI_VP_STUFF_ICX_GOTO_P0_ST_MASK = 0x8, + HDMI_VP_STUFF_ICX_GOTO_P0_ST_OFFSET = 3, + HDMI_VP_STUFF_YCC422_STUFFING_MASK = 0x4, + HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE = 0x4, + HDMI_VP_STUFF_YCC422_STUFFING_DIRECT_MODE = 0x0, + HDMI_VP_STUFF_PP_STUFFING_MASK = 0x2, + HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE = 0x2, + HDMI_VP_STUFF_PP_STUFFING_DIRECT_MODE = 0x0, + HDMI_VP_STUFF_PR_STUFFING_MASK = 0x1, + HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE = 0x1, + HDMI_VP_STUFF_PR_STUFFING_DIRECT_MODE = 0x0, + +/* VP_CONF field values */ + HDMI_VP_CONF_BYPASS_EN_MASK = 0x40, + HDMI_VP_CONF_BYPASS_EN_ENABLE = 0x40, + HDMI_VP_CONF_BYPASS_EN_DISABLE = 0x00, + HDMI_VP_CONF_PP_EN_ENMASK = 0x20, + HDMI_VP_CONF_PP_EN_ENABLE = 0x20, + HDMI_VP_CONF_PP_EN_DISABLE = 0x00, + HDMI_VP_CONF_PR_EN_MASK = 0x10, + HDMI_VP_CONF_PR_EN_ENABLE = 0x10, + HDMI_VP_CONF_PR_EN_DISABLE = 0x00, + HDMI_VP_CONF_YCC422_EN_MASK = 0x8, + HDMI_VP_CONF_YCC422_EN_ENABLE = 0x8, + HDMI_VP_CONF_YCC422_EN_DISABLE = 0x0, + HDMI_VP_CONF_BYPASS_SELECT_MASK = 0x4, + HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER = 0x4, + HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER = 0x0, + HDMI_VP_CONF_OUTPUT_SELECTOR_MASK = 0x3, + HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS = 0x3, + HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422 = 0x1, + HDMI_VP_CONF_OUTPUT_SELECTOR_PP = 0x0, + +/* VP_REMAP field values */ + HDMI_VP_REMAP_MASK = 0x3, + HDMI_VP_REMAP_YCC422_24bit = 0x2, + HDMI_VP_REMAP_YCC422_20bit = 0x1, + HDMI_VP_REMAP_YCC422_16bit = 0x0, + +/* FC_INVIDCONF field values */ + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK = 0x80, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE = 0x80, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE = 0x00, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_MASK = 0x40, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH = 0x40, + HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_MASK = 0x20, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH = 0x20, + HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_MASK = 0x10, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH = 0x10, + HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW = 0x00, + HDMI_FC_INVIDCONF_DVI_MODEZ_MASK = 0x8, + HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE = 0x8, + HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE = 0x0, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_MASK = 0x2, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH = 0x2, + HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW = 0x0, + HDMI_FC_INVIDCONF_IN_I_P_MASK = 0x1, + HDMI_FC_INVIDCONF_IN_I_P_INTERLACED = 0x1, + HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE = 0x0, + +/* FC_AUDICONF0 field values */ + HDMI_FC_AUDICONF0_CC_OFFSET = 4, + HDMI_FC_AUDICONF0_CC_MASK = 0x70, + HDMI_FC_AUDICONF0_CT_OFFSET = 0, + HDMI_FC_AUDICONF0_CT_MASK = 0xF, + +/* FC_AUDICONF1 field values */ + HDMI_FC_AUDICONF1_SS_OFFSET = 3, + HDMI_FC_AUDICONF1_SS_MASK = 0x18, + HDMI_FC_AUDICONF1_SF_OFFSET = 0, + HDMI_FC_AUDICONF1_SF_MASK = 0x7, + +/* FC_AUDICONF3 field values */ + HDMI_FC_AUDICONF3_LFEPBL_OFFSET = 5, + HDMI_FC_AUDICONF3_LFEPBL_MASK = 0x60, + HDMI_FC_AUDICONF3_DM_INH_OFFSET = 4, + HDMI_FC_AUDICONF3_DM_INH_MASK = 0x10, + HDMI_FC_AUDICONF3_LSV_OFFSET = 0, + HDMI_FC_AUDICONF3_LSV_MASK = 0xF, + +/* FC_AUDSCHNLS0 field values */ + HDMI_FC_AUDSCHNLS0_CGMSA_OFFSET = 4, + HDMI_FC_AUDSCHNLS0_CGMSA_MASK = 0x30, + HDMI_FC_AUDSCHNLS0_COPYRIGHT_OFFSET = 0, + HDMI_FC_AUDSCHNLS0_COPYRIGHT_MASK = 0x01, + +/* FC_AUDSCHNLS3-6 field values */ + HDMI_FC_AUDSCHNLS3_OIEC_CH0_OFFSET = 0, + HDMI_FC_AUDSCHNLS3_OIEC_CH0_MASK = 0x0f, + HDMI_FC_AUDSCHNLS3_OIEC_CH1_OFFSET = 4, + HDMI_FC_AUDSCHNLS3_OIEC_CH1_MASK = 0xf0, + HDMI_FC_AUDSCHNLS4_OIEC_CH2_OFFSET = 0, + HDMI_FC_AUDSCHNLS4_OIEC_CH2_MASK = 0x0f, + HDMI_FC_AUDSCHNLS4_OIEC_CH3_OFFSET = 4, + HDMI_FC_AUDSCHNLS4_OIEC_CH3_MASK = 0xf0, + + HDMI_FC_AUDSCHNLS5_OIEC_CH0_OFFSET = 0, + HDMI_FC_AUDSCHNLS5_OIEC_CH0_MASK = 0x0f, + HDMI_FC_AUDSCHNLS5_OIEC_CH1_OFFSET = 4, + HDMI_FC_AUDSCHNLS5_OIEC_CH1_MASK = 0xf0, + HDMI_FC_AUDSCHNLS6_OIEC_CH2_OFFSET = 0, + HDMI_FC_AUDSCHNLS6_OIEC_CH2_MASK = 0x0f, + HDMI_FC_AUDSCHNLS6_OIEC_CH3_OFFSET = 4, + HDMI_FC_AUDSCHNLS6_OIEC_CH3_MASK = 0xf0, + +/* HDMI_FC_AUDSCHNLS7 field values */ + HDMI_FC_AUDSCHNLS7_ACCURACY_OFFSET = 4, + HDMI_FC_AUDSCHNLS7_ACCURACY_MASK = 0x30, + +/* HDMI_FC_AUDSCHNLS8 field values */ + HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_MASK = 0xf0, + HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_OFFSET = 4, + HDMI_FC_AUDSCHNLS8_WORDLEGNTH_MASK = 0x0f, + HDMI_FC_AUDSCHNLS8_WORDLEGNTH_OFFSET = 0, + +/* FC_AUDSCONF field values */ + HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_MASK = 0xF0, + HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_OFFSET = 4, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK = 0x1, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_OFFSET = 0, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1 = 0x1, + HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0 = 0x0, + +/* FC_STAT2 field values */ + HDMI_FC_STAT2_OVERFLOW_MASK = 0x03, + HDMI_FC_STAT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_STAT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* FC_INT2 field values */ + HDMI_FC_INT2_OVERFLOW_MASK = 0x03, + HDMI_FC_INT2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_INT2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* FC_MASK2 field values */ + HDMI_FC_MASK2_OVERFLOW_MASK = 0x03, + HDMI_FC_MASK2_LOW_PRIORITY_OVERFLOW = 0x02, + HDMI_FC_MASK2_HIGH_PRIORITY_OVERFLOW = 0x01, + +/* FC_PRCONF field values */ + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK = 0xF0, + HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET = 4, + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK = 0x0F, + HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET = 0, + +/* FC_AVICONF0-FC_AVICONF3 field values */ + HDMI_FC_AVICONF0_PIX_FMT_MASK = 0x03, + HDMI_FC_AVICONF0_PIX_FMT_RGB = 0x00, + HDMI_FC_AVICONF0_PIX_FMT_YCBCR422 = 0x01, + HDMI_FC_AVICONF0_PIX_FMT_YCBCR444 = 0x02, + HDMI_FC_AVICONF0_ACTIVE_FMT_MASK = 0x40, + HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT = 0x40, + HDMI_FC_AVICONF0_ACTIVE_FMT_NO_INFO = 0x00, + HDMI_FC_AVICONF0_BAR_DATA_MASK = 0x0C, + HDMI_FC_AVICONF0_BAR_DATA_NO_DATA = 0x00, + HDMI_FC_AVICONF0_BAR_DATA_VERT_BAR = 0x04, + HDMI_FC_AVICONF0_BAR_DATA_HORIZ_BAR = 0x08, + HDMI_FC_AVICONF0_BAR_DATA_VERT_HORIZ_BAR = 0x0C, + HDMI_FC_AVICONF0_SCAN_INFO_MASK = 0x30, + HDMI_FC_AVICONF0_SCAN_INFO_OVERSCAN = 0x10, + HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN = 0x20, + HDMI_FC_AVICONF0_SCAN_INFO_NODATA = 0x00, + + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_MASK = 0x0F, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_USE_CODED = 0x08, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_4_3 = 0x09, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_16_9 = 0x0A, + HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_14_9 = 0x0B, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_MASK = 0x30, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_NO_DATA = 0x00, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3 = 0x10, + HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9 = 0x20, + HDMI_FC_AVICONF1_COLORIMETRY_MASK = 0xC0, + HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA = 0x00, + HDMI_FC_AVICONF1_COLORIMETRY_SMPTE = 0x40, + HDMI_FC_AVICONF1_COLORIMETRY_ITUR = 0x80, + HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO = 0xC0, + + HDMI_FC_AVICONF2_SCALING_MASK = 0x03, + HDMI_FC_AVICONF2_SCALING_NONE = 0x00, + HDMI_FC_AVICONF2_SCALING_HORIZ = 0x01, + HDMI_FC_AVICONF2_SCALING_VERT = 0x02, + HDMI_FC_AVICONF2_SCALING_HORIZ_VERT = 0x03, + HDMI_FC_AVICONF2_RGB_QUANT_MASK = 0x0C, + HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT = 0x00, + HDMI_FC_AVICONF2_RGB_QUANT_LIMITED_RANGE = 0x04, + HDMI_FC_AVICONF2_RGB_QUANT_FULL_RANGE = 0x08, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_MASK = 0x70, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601 = 0x00, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709 = 0x10, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_SYCC601 = 0x20, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_ADOBE_YCC601 = 0x30, + HDMI_FC_AVICONF2_EXT_COLORIMETRY_ADOBE_RGB = 0x40, + HDMI_FC_AVICONF2_IT_CONTENT_MASK = 0x80, + HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA = 0x00, + HDMI_FC_AVICONF2_IT_CONTENT_VALID = 0x80, + + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_MASK = 0x03, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GRAPHICS = 0x00, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_PHOTO = 0x01, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_CINEMA = 0x02, + HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GAME = 0x03, + HDMI_FC_AVICONF3_QUANT_RANGE_MASK = 0x0C, + HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED = 0x00, + HDMI_FC_AVICONF3_QUANT_RANGE_FULL = 0x04, + +/* FC_DBGFORCE field values */ + HDMI_FC_DBGFORCE_FORCEAUDIO = 0x10, + HDMI_FC_DBGFORCE_FORCEVIDEO = 0x1, + +/* PHY_CONF0 field values */ + HDMI_PHY_CONF0_PDZ_MASK = 0x80, + HDMI_PHY_CONF0_PDZ_OFFSET = 7, + HDMI_PHY_CONF0_ENTMDS_MASK = 0x40, + HDMI_PHY_CONF0_ENTMDS_OFFSET = 6, + HDMI_PHY_CONF0_SPARECTRL = 0x20, + HDMI_PHY_CONF0_GEN2_PDDQ_MASK = 0x10, + HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET = 4, + HDMI_PHY_CONF0_GEN2_TXPWRON_MASK = 0x8, + HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET = 3, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK = 0x4, + HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET = 2, + HDMI_PHY_CONF0_SELDATAENPOL_MASK = 0x2, + HDMI_PHY_CONF0_SELDATAENPOL_OFFSET = 1, + HDMI_PHY_CONF0_SELDIPIF_MASK = 0x1, + HDMI_PHY_CONF0_SELDIPIF_OFFSET = 0, + +/* PHY_TST0 field values */ + HDMI_PHY_TST0_TSTCLR_MASK = 0x20, + HDMI_PHY_TST0_TSTCLR_OFFSET = 5, + HDMI_PHY_TST0_TSTEN_MASK = 0x10, + HDMI_PHY_TST0_TSTEN_OFFSET = 4, + HDMI_PHY_TST0_TSTCLK_MASK = 0x1, + HDMI_PHY_TST0_TSTCLK_OFFSET = 0, + +/* PHY_STAT0 field values */ + HDMI_PHY_RX_SENSE3 = 0x80, + HDMI_PHY_RX_SENSE2 = 0x40, + HDMI_PHY_RX_SENSE1 = 0x20, + HDMI_PHY_RX_SENSE0 = 0x10, + HDMI_PHY_HPD = 0x02, + HDMI_PHY_TX_PHY_LOCK = 0x01, + +/* PHY_I2CM_SLAVE_ADDR field values */ + HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2 = 0x69, + HDMI_PHY_I2CM_SLAVE_ADDR_HEAC_PHY = 0x49, + +/* PHY_I2CM_OPERATION_ADDR field values */ + HDMI_PHY_I2CM_OPERATION_ADDR_WRITE = 0x10, + HDMI_PHY_I2CM_OPERATION_ADDR_READ = 0x1, + +/* HDMI_PHY_I2CM_INT_ADDR */ + HDMI_PHY_I2CM_INT_ADDR_DONE_POL = 0x08, + HDMI_PHY_I2CM_INT_ADDR_DONE_MASK = 0x04, + +/* HDMI_PHY_I2CM_CTLINT_ADDR */ + HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL = 0x80, + HDMI_PHY_I2CM_CTLINT_ADDR_NAC_MASK = 0x40, + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL = 0x08, + HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_MASK = 0x04, + +/* AUD_CTS3 field values */ + HDMI_AUD_CTS3_N_SHIFT_OFFSET = 5, + HDMI_AUD_CTS3_N_SHIFT_MASK = 0xe0, + HDMI_AUD_CTS3_N_SHIFT_1 = 0, + HDMI_AUD_CTS3_N_SHIFT_16 = 0x20, + HDMI_AUD_CTS3_N_SHIFT_32 = 0x40, + HDMI_AUD_CTS3_N_SHIFT_64 = 0x60, + HDMI_AUD_CTS3_N_SHIFT_128 = 0x80, + HDMI_AUD_CTS3_N_SHIFT_256 = 0xa0, + /* note that the CTS3 MANUAL bit has been removed + from our part. Can't set it, will read as 0. */ + HDMI_AUD_CTS3_CTS_MANUAL = 0x10, + HDMI_AUD_CTS3_AUDCTS19_16_MASK = 0x0f, + +/* AHB_DMA_CONF0 field values */ + HDMI_AHB_DMA_CONF0_SW_FIFO_RST_OFFSET = 7, + HDMI_AHB_DMA_CONF0_SW_FIFO_RST_MASK = 0x80, + HDMI_AHB_DMA_CONF0_HBR = 0x10, + HDMI_AHB_DMA_CONF0_EN_HLOCK_OFFSET = 3, + HDMI_AHB_DMA_CONF0_EN_HLOCK_MASK = 0x08, + HDMI_AHB_DMA_CONF0_INCR_TYPE_OFFSET = 1, + HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK = 0x06, + HDMI_AHB_DMA_CONF0_INCR4 = 0x0, + HDMI_AHB_DMA_CONF0_INCR8 = 0x2, + HDMI_AHB_DMA_CONF0_INCR16 = 0x4, + HDMI_AHB_DMA_CONF0_BURST_MODE = 0x1, + +/* HDMI_AHB_DMA_START field values */ + HDMI_AHB_DMA_START_START_OFFSET = 0, + HDMI_AHB_DMA_START_START_MASK = 0x01, + +/* HDMI_AHB_DMA_STOP field values */ + HDMI_AHB_DMA_STOP_STOP_OFFSET = 0, + HDMI_AHB_DMA_STOP_STOP_MASK = 0x01, + +/* AHB_DMA_STAT, AHB_DMA_INT, AHB_DMA_MASK, AHB_DMA_POL field values */ + HDMI_AHB_DMA_DONE = 0x80, + HDMI_AHB_DMA_RETRY_SPLIT = 0x40, + HDMI_AHB_DMA_LOSTOWNERSHIP = 0x20, + HDMI_AHB_DMA_ERROR = 0x10, + HDMI_AHB_DMA_FIFO_THREMPTY = 0x04, + HDMI_AHB_DMA_FIFO_FULL = 0x02, + HDMI_AHB_DMA_FIFO_EMPTY = 0x01, + +/* AHB_DMA_BUFFSTAT, AHB_DMA_BUFFINT,AHB_DMA_BUFFMASK,AHB_DMA_BUFFPOL values */ + HDMI_AHB_DMA_BUFFSTAT_FULL = 0x02, + HDMI_AHB_DMA_BUFFSTAT_EMPTY = 0x01, + +/* MC_CLKDIS field values */ + HDMI_MC_CLKDIS_HDCPCLK_DISABLE = 0x40, + HDMI_MC_CLKDIS_CECCLK_DISABLE = 0x20, + HDMI_MC_CLKDIS_CSCCLK_DISABLE = 0x10, + HDMI_MC_CLKDIS_AUDCLK_DISABLE = 0x8, + HDMI_MC_CLKDIS_PREPCLK_DISABLE = 0x4, + HDMI_MC_CLKDIS_TMDSCLK_DISABLE = 0x2, + HDMI_MC_CLKDIS_PIXELCLK_DISABLE = 0x1, + +/* MC_SWRSTZ field values */ + HDMI_MC_SWRSTZ_TMDSSWRST_REQ = 0x02, + +/* MC_FLOWCTRL field values */ + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_MASK = 0x1, + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH = 0x1, + HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS = 0x0, + +/* MC_PHYRSTZ field values */ + HDMI_MC_PHYRSTZ_ASSERT = 0x0, + HDMI_MC_PHYRSTZ_DEASSERT = 0x1, + +/* MC_HEACPHY_RST field values */ + HDMI_MC_HEACPHY_RST_ASSERT = 0x1, + HDMI_MC_HEACPHY_RST_DEASSERT = 0x0, + +/* CSC_CFG field values */ + HDMI_CSC_CFG_INTMODE_MASK = 0x30, + HDMI_CSC_CFG_INTMODE_OFFSET = 4, + HDMI_CSC_CFG_INTMODE_DISABLE = 0x00, + HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1 = 0x10, + HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA2 = 0x20, + HDMI_CSC_CFG_DECMODE_MASK = 0x3, + HDMI_CSC_CFG_DECMODE_OFFSET = 0, + HDMI_CSC_CFG_DECMODE_DISABLE = 0x0, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA1 = 0x1, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA2 = 0x2, + HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3 = 0x3, + +/* CSC_SCALE field values */ + HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK = 0xF0, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP = 0x00, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP = 0x50, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP = 0x60, + HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP = 0x70, + HDMI_CSC_SCALE_CSCSCALE_MASK = 0x03, + +/* A_HDCPCFG0 field values */ + HDMI_A_HDCPCFG0_ELVENA_MASK = 0x80, + HDMI_A_HDCPCFG0_ELVENA_ENABLE = 0x80, + HDMI_A_HDCPCFG0_ELVENA_DISABLE = 0x00, + HDMI_A_HDCPCFG0_I2CFASTMODE_MASK = 0x40, + HDMI_A_HDCPCFG0_I2CFASTMODE_ENABLE = 0x40, + HDMI_A_HDCPCFG0_I2CFASTMODE_DISABLE = 0x00, + HDMI_A_HDCPCFG0_BYPENCRYPTION_MASK = 0x20, + HDMI_A_HDCPCFG0_BYPENCRYPTION_ENABLE = 0x20, + HDMI_A_HDCPCFG0_BYPENCRYPTION_DISABLE = 0x00, + HDMI_A_HDCPCFG0_SYNCRICHECK_MASK = 0x10, + HDMI_A_HDCPCFG0_SYNCRICHECK_ENABLE = 0x10, + HDMI_A_HDCPCFG0_SYNCRICHECK_DISABLE = 0x00, + HDMI_A_HDCPCFG0_AVMUTE_MASK = 0x8, + HDMI_A_HDCPCFG0_AVMUTE_ENABLE = 0x8, + HDMI_A_HDCPCFG0_AVMUTE_DISABLE = 0x0, + HDMI_A_HDCPCFG0_RXDETECT_MASK = 0x4, + HDMI_A_HDCPCFG0_RXDETECT_ENABLE = 0x4, + HDMI_A_HDCPCFG0_RXDETECT_DISABLE = 0x0, + HDMI_A_HDCPCFG0_EN11FEATURE_MASK = 0x2, + HDMI_A_HDCPCFG0_EN11FEATURE_ENABLE = 0x2, + HDMI_A_HDCPCFG0_EN11FEATURE_DISABLE = 0x0, + HDMI_A_HDCPCFG0_HDMIDVI_MASK = 0x1, + HDMI_A_HDCPCFG0_HDMIDVI_HDMI = 0x1, + HDMI_A_HDCPCFG0_HDMIDVI_DVI = 0x0, + +/* A_HDCPCFG1 field values */ + HDMI_A_HDCPCFG1_DISSHA1CHECK_MASK = 0x8, + HDMI_A_HDCPCFG1_DISSHA1CHECK_DISABLE = 0x8, + HDMI_A_HDCPCFG1_DISSHA1CHECK_ENABLE = 0x0, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_MASK = 0x4, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_ENABLE = 0x4, + HDMI_A_HDCPCFG1_PH2UPSHFTENC_DISABLE = 0x0, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK = 0x2, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE = 0x2, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_ENABLE = 0x0, + HDMI_A_HDCPCFG1_SWRESET_MASK = 0x1, + HDMI_A_HDCPCFG1_SWRESET_ASSERT = 0x0, + +/* A_VIDPOLCFG field values */ + HDMI_A_VIDPOLCFG_UNENCRYPTCONF_MASK = 0x60, + HDMI_A_VIDPOLCFG_UNENCRYPTCONF_OFFSET = 5, + HDMI_A_VIDPOLCFG_DATAENPOL_MASK = 0x10, + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH = 0x10, + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW = 0x0, + HDMI_A_VIDPOLCFG_VSYNCPOL_MASK = 0x8, + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_HIGH = 0x8, + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_LOW = 0x0, + HDMI_A_VIDPOLCFG_HSYNCPOL_MASK = 0x2, + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH = 0x2, + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW = 0x0, +}; +#endif /* __IMX_HDMI_H__ */ diff --git a/drivers/video/imx-ipu-v3/imx-ipu-v3.h b/drivers/video/imx-ipu-v3/imx-ipu-v3.h new file mode 100644 index 0000000000..7c48a7ce38 --- /dev/null +++ b/drivers/video/imx-ipu-v3/imx-ipu-v3.h @@ -0,0 +1,344 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef __DRM_IPU_H__ +#define __DRM_IPU_H__ + +#include <io.h> +#include <fb.h> +#include <video/fourcc.h> + +struct ipu_soc; + +enum ipuv3_type { + IPUV3EX, + IPUV3M, + IPUV3H, +}; + +void ipuwritel(const char *unit, uint32_t value, void __iomem *reg); +uint32_t ipureadl(void __iomem *reg); + +/* + * Bitfield of Display Interface signal polarities. + */ +struct ipu_di_signal_cfg { + unsigned datamask_en:1; + unsigned interlaced:1; + unsigned odd_field_first:1; + unsigned clksel_en:1; + unsigned clkidle_en:1; + unsigned data_pol:1; /* true = inverted */ + unsigned clk_pol:1; /* true = rising edge */ + unsigned enable_pol:1; + unsigned Hsync_pol:1; /* true = active high */ + unsigned Vsync_pol:1; + + u16 width; + u16 height; + u32 pixel_fmt; + u16 h_start_width; + u16 h_sync_width; + u16 h_end_width; + u16 v_start_width; + u16 v_sync_width; + u16 v_end_width; + u32 v_to_h_sync; + unsigned long pixelclock; +#define IPU_DI_CLKMODE_SYNC (1 << 0) +#define IPU_DI_CLKMODE_EXT (1 << 1) +#define IPU_DI_CLKMODE_NON_FRACTIONAL (1 << 2) + unsigned long clkflags; + + u8 hsync_pin; + u8 vsync_pin; +}; + +enum ipu_color_space { + IPUV3_COLORSPACE_RGB, + IPUV3_COLORSPACE_YUV, + IPUV3_COLORSPACE_UNKNOWN, +}; + +struct ipuv3_channel; + +enum ipu_channel_irq { + IPU_IRQ_EOF = 0, + IPU_IRQ_NFACK = 64, + IPU_IRQ_NFB4EOF = 128, + IPU_IRQ_EOS = 192, +}; + +int ipu_idmac_channel_irq(struct ipu_soc *ipu, struct ipuv3_channel *channel, + enum ipu_channel_irq irq); + +#define IPU_IRQ_DP_SF_START (448 + 2) +#define IPU_IRQ_DP_SF_END (448 + 3) +#define IPU_IRQ_BG_SF_END IPU_IRQ_DP_SF_END, +#define IPU_IRQ_DC_FC_0 (448 + 8) +#define IPU_IRQ_DC_FC_1 (448 + 9) +#define IPU_IRQ_DC_FC_2 (448 + 10) +#define IPU_IRQ_DC_FC_3 (448 + 11) +#define IPU_IRQ_DC_FC_4 (448 + 12) +#define IPU_IRQ_DC_FC_6 (448 + 13) +#define IPU_IRQ_VSYNC_PRE_0 (448 + 14) +#define IPU_IRQ_VSYNC_PRE_1 (448 + 15) + +/* + * IPU Image DMA Controller (idmac) functions + */ +struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned channel); +void ipu_idmac_put(struct ipuv3_channel *); + +int ipu_idmac_enable_channel(struct ipuv3_channel *channel); +int ipu_idmac_disable_channel(struct ipuv3_channel *channel); +int ipu_idmac_wait_busy(struct ipuv3_channel *channel, int ms); + +void ipu_idmac_set_double_buffer(struct ipuv3_channel *channel, + bool doublebuffer); +void ipu_idmac_select_buffer(struct ipuv3_channel *channel, u32 buf_num); + +/* + * IPU Display Controller (dc) functions + */ +struct ipu_dc; +struct ipu_di; +struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel); +void ipu_dc_put(struct ipu_dc *dc); +int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced, + u32 pixel_fmt, u32 width); +void ipu_dc_enable_channel(struct ipu_dc *dc); +void ipu_dc_disable_channel(struct ipu_dc *dc); + +/* + * IPU Display Interface (di) functions + */ +struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp); +void ipu_di_put(struct ipu_di *); +int ipu_di_disable(struct ipu_di *); +int ipu_di_enable(struct ipu_di *); +int ipu_di_get_num(struct ipu_di *); +int ipu_di_init_sync_panel(struct ipu_di *, struct ipu_di_signal_cfg *sig); + +/* + * IPU Display Multi FIFO Controller (dmfc) functions + */ +struct dmfc_channel; +int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc); +void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc); +int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc, + unsigned long bandwidth_mbs, int burstsize); +void ipu_dmfc_free_bandwidth(struct dmfc_channel *dmfc); +int ipu_dmfc_init_channel(struct dmfc_channel *dmfc, int width); +struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipuv3_channel); +void ipu_dmfc_put(struct dmfc_channel *dmfc); + +/* + * IPU Display Processor (dp) functions + */ +#define IPU_DP_FLOW_SYNC_BG 0 +#define IPU_DP_FLOW_SYNC_FG 1 +#define IPU_DP_FLOW_ASYNC0_BG 2 +#define IPU_DP_FLOW_ASYNC0_FG 3 +#define IPU_DP_FLOW_ASYNC1_BG 4 +#define IPU_DP_FLOW_ASYNC1_FG 5 + +struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow); +void ipu_dp_put(struct ipu_dp *); +int ipu_dp_enable_channel(struct ipu_dp *dp); +void ipu_dp_disable_channel(struct ipu_dp *dp); +int ipu_dp_setup_channel(struct ipu_dp *dp, + enum ipu_color_space in, enum ipu_color_space out); +int ipu_dp_set_window_pos(struct ipu_dp *, u16 x_pos, u16 y_pos); +int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, u8 alpha, + bool bg_chan); + +#define IPU_CPMEM_WORD(word, ofs, size) ((((word) * 160 + (ofs)) << 8) | (size)) + +#define IPU_FIELD_UBO IPU_CPMEM_WORD(0, 46, 22) +#define IPU_FIELD_VBO IPU_CPMEM_WORD(0, 68, 22) +#define IPU_FIELD_IOX IPU_CPMEM_WORD(0, 90, 4) +#define IPU_FIELD_RDRW IPU_CPMEM_WORD(0, 94, 1) +#define IPU_FIELD_SO IPU_CPMEM_WORD(0, 113, 1) +#define IPU_FIELD_SLY IPU_CPMEM_WORD(1, 102, 14) +#define IPU_FIELD_SLUV IPU_CPMEM_WORD(1, 128, 14) + +#define IPU_FIELD_XV IPU_CPMEM_WORD(0, 0, 10) +#define IPU_FIELD_YV IPU_CPMEM_WORD(0, 10, 9) +#define IPU_FIELD_XB IPU_CPMEM_WORD(0, 19, 13) +#define IPU_FIELD_YB IPU_CPMEM_WORD(0, 32, 12) +#define IPU_FIELD_NSB_B IPU_CPMEM_WORD(0, 44, 1) +#define IPU_FIELD_CF IPU_CPMEM_WORD(0, 45, 1) +#define IPU_FIELD_SX IPU_CPMEM_WORD(0, 46, 12) +#define IPU_FIELD_SY IPU_CPMEM_WORD(0, 58, 11) +#define IPU_FIELD_NS IPU_CPMEM_WORD(0, 69, 10) +#define IPU_FIELD_SDX IPU_CPMEM_WORD(0, 79, 7) +#define IPU_FIELD_SM IPU_CPMEM_WORD(0, 86, 10) +#define IPU_FIELD_SCC IPU_CPMEM_WORD(0, 96, 1) +#define IPU_FIELD_SCE IPU_CPMEM_WORD(0, 97, 1) +#define IPU_FIELD_SDY IPU_CPMEM_WORD(0, 98, 7) +#define IPU_FIELD_SDRX IPU_CPMEM_WORD(0, 105, 1) +#define IPU_FIELD_SDRY IPU_CPMEM_WORD(0, 106, 1) +#define IPU_FIELD_BPP IPU_CPMEM_WORD(0, 107, 3) +#define IPU_FIELD_DEC_SEL IPU_CPMEM_WORD(0, 110, 2) +#define IPU_FIELD_DIM IPU_CPMEM_WORD(0, 112, 1) +#define IPU_FIELD_BNDM IPU_CPMEM_WORD(0, 114, 3) +#define IPU_FIELD_BM IPU_CPMEM_WORD(0, 117, 2) +#define IPU_FIELD_ROT IPU_CPMEM_WORD(0, 119, 1) +#define IPU_FIELD_HF IPU_CPMEM_WORD(0, 120, 1) +#define IPU_FIELD_VF IPU_CPMEM_WORD(0, 121, 1) +#define IPU_FIELD_THE IPU_CPMEM_WORD(0, 122, 1) +#define IPU_FIELD_CAP IPU_CPMEM_WORD(0, 123, 1) +#define IPU_FIELD_CAE IPU_CPMEM_WORD(0, 124, 1) +#define IPU_FIELD_FW IPU_CPMEM_WORD(0, 125, 13) +#define IPU_FIELD_FH IPU_CPMEM_WORD(0, 138, 12) +#define IPU_FIELD_EBA0 IPU_CPMEM_WORD(1, 0, 29) +#define IPU_FIELD_EBA1 IPU_CPMEM_WORD(1, 29, 29) +#define IPU_FIELD_ILO IPU_CPMEM_WORD(1, 58, 20) +#define IPU_FIELD_NPB IPU_CPMEM_WORD(1, 78, 7) +#define IPU_FIELD_PFS IPU_CPMEM_WORD(1, 85, 4) +#define IPU_FIELD_ALU IPU_CPMEM_WORD(1, 89, 1) +#define IPU_FIELD_ALBM IPU_CPMEM_WORD(1, 90, 3) +#define IPU_FIELD_ID IPU_CPMEM_WORD(1, 93, 2) +#define IPU_FIELD_TH IPU_CPMEM_WORD(1, 95, 7) +#define IPU_FIELD_SL IPU_CPMEM_WORD(1, 102, 14) +#define IPU_FIELD_WID0 IPU_CPMEM_WORD(1, 116, 3) +#define IPU_FIELD_WID1 IPU_CPMEM_WORD(1, 119, 3) +#define IPU_FIELD_WID2 IPU_CPMEM_WORD(1, 122, 3) +#define IPU_FIELD_WID3 IPU_CPMEM_WORD(1, 125, 3) +#define IPU_FIELD_OFS0 IPU_CPMEM_WORD(1, 128, 5) +#define IPU_FIELD_OFS1 IPU_CPMEM_WORD(1, 133, 5) +#define IPU_FIELD_OFS2 IPU_CPMEM_WORD(1, 138, 5) +#define IPU_FIELD_OFS3 IPU_CPMEM_WORD(1, 143, 5) +#define IPU_FIELD_SXYS IPU_CPMEM_WORD(1, 148, 1) +#define IPU_FIELD_CRE IPU_CPMEM_WORD(1, 149, 1) +#define IPU_FIELD_DEC_SEL2 IPU_CPMEM_WORD(1, 150, 1) + +struct ipu_cpmem_word { + u32 data[5]; + u32 res[3]; +}; + +struct ipu_ch_param { + struct ipu_cpmem_word word[2]; +}; + +void ipu_ch_param_write_field(struct ipu_ch_param __iomem *base, u32 wbs, u32 v); +u32 ipu_ch_param_read_field(struct ipu_ch_param __iomem *base, u32 wbs); +struct ipu_ch_param __iomem *ipu_get_cpmem(struct ipuv3_channel *channel); +void ipu_ch_param_dump(struct ipu_ch_param __iomem *p); + +static inline void ipu_ch_param_zero(struct ipu_ch_param __iomem *p) +{ + int i; + void __iomem *base = p; + + for (i = 0; i < sizeof(*p) / sizeof(u32); i++) + ipuwritel("chp", 0, base + i * sizeof(u32)); +} + +static inline void ipu_cpmem_set_buffer(struct ipu_ch_param __iomem *p, + int bufnum, dma_addr_t buf) +{ + if (bufnum) + ipu_ch_param_write_field(p, IPU_FIELD_EBA1, buf >> 3); + else + ipu_ch_param_write_field(p, IPU_FIELD_EBA0, buf >> 3); +} + +static inline void ipu_cpmem_set_resolution(struct ipu_ch_param __iomem *p, + int xres, int yres) +{ + ipu_ch_param_write_field(p, IPU_FIELD_FW, xres - 1); + ipu_ch_param_write_field(p, IPU_FIELD_FH, yres - 1); +} + +static inline void ipu_cpmem_set_stride(struct ipu_ch_param __iomem *p, + int stride) +{ + ipu_ch_param_write_field(p, IPU_FIELD_SLY, stride - 1); +} + +void ipu_cpmem_set_high_priority(struct ipuv3_channel *channel); + +struct ipu_rgb { + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; + int bits_per_pixel; +}; + +struct ipu_rgb *drm_fourcc_to_rgb(u32 drm_fourcc); + +int ipu_cpmem_set_format_passthrough(struct ipu_ch_param __iomem *p, + int width); + +int ipu_cpmem_set_format_rgb(struct ipu_ch_param __iomem *, + const struct ipu_rgb *rgb); + +static inline void ipu_cpmem_interlaced_scan(struct ipu_ch_param *p, + int stride) +{ + ipu_ch_param_write_field(p, IPU_FIELD_SO, 1); + ipu_ch_param_write_field(p, IPU_FIELD_ILO, stride / 8); + ipu_ch_param_write_field(p, IPU_FIELD_SLY, (stride * 2) - 1); +}; + +void ipu_cpmem_set_yuv_planar(struct ipu_ch_param __iomem *p, u32 pixel_format, + int stride, int height); +void ipu_cpmem_set_yuv_interleaved(struct ipu_ch_param __iomem *p, + u32 pixel_format); +void ipu_cpmem_set_yuv_planar_full(struct ipu_ch_param __iomem *p, + u32 pixel_format, int stride, int u_offset, int v_offset); +int ipu_cpmem_set_fmt(struct ipu_ch_param __iomem *cpmem, u32 pixelformat); + +enum ipu_color_space ipu_drm_fourcc_to_colorspace(u32 drm_fourcc); +enum ipu_color_space ipu_pixelformat_to_colorspace(u32 pixelformat); + +static inline void ipu_cpmem_set_burstsize(struct ipu_ch_param __iomem *p, + int burstsize) +{ + ipu_ch_param_write_field(p, IPU_FIELD_NPB, burstsize - 1); +}; + +struct ipu_client_platformdata { + int di; + int dc; + int dp; + int dmfc; + int dma[2]; + struct device_node *device_node; +}; + +struct ipu_output; + +struct ipu_output_ops { + int (*prepare)(struct ipu_output *ipu_video_output, struct fb_videomode *mode, int di); + int (*enable)(struct ipu_output *ipu_video_output, struct fb_videomode *mode, int di); + int (*disable)(struct ipu_output *ipu_video_output); + int (*unprepare)(struct ipu_output *ipu_video_output); +}; + +struct ipu_output { + struct ipu_output_ops *ops; + struct list_head list; + unsigned int di_clkflags; + uint32_t out_pixel_fmt; + struct i2c_adapter *edid_i2c_adapter; + struct display_timings *modes; + char *name; + int ipu_mask; +}; + +int ipu_register_output(struct ipu_output *ouput); + +#endif /* __DRM_IPU_H__ */ diff --git a/drivers/video/imx-ipu-v3/imx-ldb.c b/drivers/video/imx-ipu-v3/imx-ldb.c new file mode 100644 index 0000000000..7367fbea71 --- /dev/null +++ b/drivers/video/imx-ipu-v3/imx-ldb.c @@ -0,0 +1,310 @@ +/* + * i.MX drm driver - parallel display implementation + * + * Copyright (C) 2012 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <common.h> +#include <fb.h> +#include <io.h> +#include <driver.h> +#include <malloc.h> +#include <errno.h> +#include <init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <asm-generic/div64.h> +#include <linux/clk.h> +#include <mach/imx6-regs.h> +#include <mach/imx53-regs.h> + +#include "imx-ipu-v3.h" +#include "ipuv3-plane.h" + +#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0) +#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) +#define LDB_CH0_MODE_EN_MASK (3 << 0) +#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2) +#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2) +#define LDB_CH1_MODE_EN_MASK (3 << 2) +#define LDB_SPLIT_MODE_EN (1 << 4) +#define LDB_DATA_WIDTH_CH0_24 (1 << 5) +#define LDB_BIT_MAP_CH0_JEIDA (1 << 6) +#define LDB_DATA_WIDTH_CH1_24 (1 << 7) +#define LDB_BIT_MAP_CH1_JEIDA (1 << 8) +#define LDB_DI0_VS_POL_ACT_LOW (1 << 9) +#define LDB_DI1_VS_POL_ACT_LOW (1 << 10) +#define LDB_BGREF_RMODE_INT (1 << 15) + +struct imx_ldb; + +struct imx_ldb_channel { + struct imx_ldb *ldb; + int chno; + int mode_valid; + struct display_timings *modes; + struct ipu_output output; +}; + +struct imx_ldb_data { + void __iomem *base; + int (*prepare)(struct imx_ldb_channel *imx_ldb_ch, int di); + unsigned ipu_mask; +}; + +struct imx_ldb { + struct device_d *dev; + u32 interface_pix_fmt; + int mode_valid; + struct imx_ldb_channel channel[2]; + u32 ldb_ctrl; + void __iomem *base; + const struct imx_ldb_data *soc_data; +}; + +enum { + LVDS_BIT_MAP_SPWG, + LVDS_BIT_MAP_JEIDA +}; + +static const char * const imx_ldb_bit_mappings[] = { + [LVDS_BIT_MAP_SPWG] = "spwg", + [LVDS_BIT_MAP_JEIDA] = "jeida", +}; + +static const int of_get_data_mapping(struct device_node *np) +{ + const char *bm; + int ret, i; + + ret = of_property_read_string(np, "fsl,data-mapping", &bm); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(imx_ldb_bit_mappings); i++) + if (!strcasecmp(bm, imx_ldb_bit_mappings[i])) + return i; + + return -EINVAL; +} + +static int imx_ldb_prepare(struct ipu_output *output, struct fb_videomode *mode, int di) +{ + struct imx_ldb_channel *imx_ldb_ch = container_of(output, struct imx_ldb_channel, output); + struct imx_ldb *ldb = imx_ldb_ch->ldb; + + if (PICOS2KHZ(mode->pixclock) > 85000) { + dev_warn(ldb->dev, + "%s: mode exceeds 85 MHz pixel clock\n", __func__); + } + + ldb->soc_data->prepare(imx_ldb_ch, di); + + /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */ + if (imx_ldb_ch == &ldb->channel[0]) { + if (mode->sync & FB_SYNC_VERT_HIGH_ACT) + ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW; + else + ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW; + } + + if (imx_ldb_ch == &ldb->channel[1]) { + if (mode->sync & FB_SYNC_VERT_HIGH_ACT) + ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW; + else + ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW; + } + + if (imx_ldb_ch == &ldb->channel[0]) { + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + } + + if (imx_ldb_ch == &ldb->channel[1]) { + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI1; + } + + writel(ldb->ldb_ctrl, ldb->base); + + return 0; +} + +static int imx6q_ldb_prepare(struct imx_ldb_channel *imx_ldb_ch, int di) +{ + struct clk *diclk, *ldbclk; + struct imx_ldb *ldb = imx_ldb_ch->ldb; + int ret, ipuno, dino; + char *clkname; + void __iomem *gpr3 = (void *)MX6_IOMUXC_BASE_ADDR + 0xc; + uint32_t val; + int shift; + + ipuno = ((di >> 1) & 1) + 1; + dino = di & 0x1; + + clkname = asprintf("ipu%d_di%d_sel", ipuno, dino); + diclk = clk_lookup(clkname); + free(clkname); + if (IS_ERR(diclk)) { + dev_err(ldb->dev, "failed to get di clk: %s\n", strerror(PTR_ERR(diclk))); + return PTR_ERR(diclk); + } + + clkname = asprintf("ldb_di%d_podf", imx_ldb_ch->chno); + ldbclk = clk_lookup(clkname); + free(clkname); + if (IS_ERR(ldbclk)) { + dev_err(ldb->dev, "failed to get ldb clk: %s\n", strerror(PTR_ERR(ldbclk))); + return PTR_ERR(ldbclk); + } + + ret = clk_set_parent(diclk, ldbclk); + if (ret) { + dev_err(ldb->dev, "failed to set display clock parent: %s\n", strerror(-ret)); + return ret; + } +printk("%s: %d\n", __func__, di); + val = readl(gpr3); + shift = (imx_ldb_ch->chno == 0) ? 6 : 8; + val &= ~(3 << shift); + val |= di << shift; + writel(val, gpr3); + + return 0; +} + +static int imx53_ldb_prepare(struct imx_ldb_channel *imx_ldb_ch, int di) +{ + return -ENOSYS; +} + +static struct imx_ldb_data imx_ldb_data_imx6q = { + .base = (void *)MX6_IOMUXC_BASE_ADDR + 0x8, + .prepare = imx6q_ldb_prepare, + .ipu_mask = 0xf, +}; + +static struct imx_ldb_data imx_ldb_data_imx53 = { + .base = (void *)MX53_IOMUXC_BASE_ADDR + 0x8, + .prepare = imx53_ldb_prepare, + .ipu_mask = 0x3, +}; + +static struct ipu_output_ops imx_ldb_ops = { + .prepare = imx_ldb_prepare, +}; + +static int imx_ldb_probe(struct device_d *dev) +{ + struct device_node *np = dev->device_node; + struct device_node *child; + struct imx_ldb *imx_ldb; + int ret, i; + int dual = 0; + int datawidth; + int mapping; + const struct imx_ldb_data *devtype; + + ret = dev_get_drvdata(dev, (unsigned long *)&devtype); + if (ret) + return ret; + + imx_ldb = xzalloc(sizeof(*imx_ldb)); + imx_ldb->base = devtype->base; + imx_ldb->soc_data = devtype; + + for_each_child_of_node(np, child) { + struct imx_ldb_channel *channel; + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) + return -EINVAL; + + if (dual && i > 0) { + dev_warn(dev, "dual-channel mode, ignoring second output\n"); + continue; + } + + if (!of_device_is_available(child)) + continue; + + channel = &imx_ldb->channel[i]; + channel->ldb = imx_ldb; + channel->chno = i; + + ret = of_property_read_u32(child, "fsl,data-width", &datawidth); + if (ret) + datawidth = 0; + else if (datawidth != 18 && datawidth != 24) + return -EINVAL; + + mapping = of_get_data_mapping(child); + switch (mapping) { + case LVDS_BIT_MAP_SPWG: + if (datawidth == 24) { + if (i == 0 || dual) + imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24; + if (i == 1 || dual) + imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24; + } + break; + case LVDS_BIT_MAP_JEIDA: + if (datawidth == 18) { + dev_err(dev, "JEIDA standard only supported in 24 bit\n"); + return -EINVAL; + } + if (i == 0 || dual) + imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 | LDB_BIT_MAP_CH0_JEIDA; + if (i == 1 || dual) + imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 | LDB_BIT_MAP_CH1_JEIDA; + break; + default: + dev_err(dev, "data mapping not specified or invalid\n"); + return -EINVAL; + } + + channel->output.ops = &imx_ldb_ops; + channel->output.di_clkflags = IPU_DI_CLKMODE_EXT | IPU_DI_CLKMODE_SYNC; + channel->output.out_pixel_fmt = (datawidth == 24) ? + V4L2_PIX_FMT_RGB24 : V4L2_PIX_FMT_BGR666; + channel->output.modes = of_get_display_timings(child); + channel->output.name = asprintf("ldb-%d", i); + channel->output.ipu_mask = devtype->ipu_mask; + + ipu_register_output(&channel->output); + } + + return 0; +} + +static struct of_device_id imx_ldb_dt_ids[] = { + { .compatible = "fsl,imx6q-ldb", (unsigned long)&imx_ldb_data_imx6q}, + { .compatible = "fsl,imx53-ldb", (unsigned long)&imx_ldb_data_imx53}, + { /* sentinel */ } +}; + +static struct driver_d imx_ldb_driver = { + .probe = imx_ldb_probe, + .of_compatible = imx_ldb_dt_ids, + .name = "imx-ldb", +}; +device_platform_driver(imx_ldb_driver); + +MODULE_DESCRIPTION("i.MX LVDS display driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/imx-ipu-v3/ipu-common.c b/drivers/video/imx-ipu-v3/ipu-common.c new file mode 100644 index 0000000000..ffd6ec18a9 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-common.c @@ -0,0 +1,836 @@ +/* + * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <common.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <clock.h> +#include <driver.h> +#include <init.h> +#include <asm/mmu.h> +#include <mach/generic.h> +#include <mach/imx6-regs.h> + +#include "imx-ipu-v3.h" +#include "ipu-prv.h" + +void ipuwritel(const char *unit, uint32_t value, void __iomem *reg) +{ + pr_debug("w: %s 0x%08lx -> 0x%08x\n", unit, (unsigned long)reg & 0xfff, value); + + writel(value, reg); +} + +uint32_t ipureadl(void __iomem *reg) +{ + uint32_t val; + + val = readl(reg); + + pr_debug("r: 0x%p -> 0x%08x\n", reg - 0x02600000, val); + + return val; +} + +static inline u32 ipu_cm_read(struct ipu_soc *ipu, unsigned offset) +{ + return ipureadl(ipu->cm_reg + offset); +} + +static inline void ipu_cm_write(struct ipu_soc *ipu, u32 value, unsigned offset) +{ + ipuwritel("cm", value, ipu->cm_reg + offset); +} + +static inline u32 ipu_idmac_read(struct ipu_soc *ipu, unsigned offset) +{ + return ipureadl(ipu->idmac_reg + offset); +} + +static inline void ipu_idmac_write(struct ipu_soc *ipu, u32 value, + unsigned offset) +{ + ipuwritel("idmac", value, ipu->idmac_reg + offset); +} + +void ipu_srm_dp_sync_update(struct ipu_soc *ipu) +{ + u32 val; + + val = ipu_cm_read(ipu, IPU_SRM_PRI2); + val |= 0x8; + ipu_cm_write(ipu, val, IPU_SRM_PRI2); +} +EXPORT_SYMBOL_GPL(ipu_srm_dp_sync_update); + +struct ipu_ch_param __iomem *ipu_get_cpmem(struct ipuv3_channel *channel) +{ + struct ipu_soc *ipu = channel->ipu; + + return ipu->cpmem_base + channel->num; +} +EXPORT_SYMBOL_GPL(ipu_get_cpmem); + +void ipu_cpmem_set_high_priority(struct ipuv3_channel *channel) +{ + struct ipu_soc *ipu = channel->ipu; + struct ipu_ch_param __iomem *p = ipu_get_cpmem(channel); + u32 val; + + if (ipu->ipu_type == IPUV3EX) + ipu_ch_param_write_field(p, IPU_FIELD_ID, 1); + + val = ipu_idmac_read(ipu, IDMAC_CHA_PRI(channel->num)); + val |= 1 << (channel->num % 32); + ipu_idmac_write(ipu, val, IDMAC_CHA_PRI(channel->num)); +}; +EXPORT_SYMBOL_GPL(ipu_cpmem_set_high_priority); + +void ipu_ch_param_write_field(struct ipu_ch_param __iomem *base, u32 wbs, u32 v) +{ + u32 bit = (wbs >> 8) % 160; + u32 size = wbs & 0xff; + u32 word = (wbs >> 8) / 160; + u32 i = bit / 32; + u32 ofs = bit % 32; + u32 mask = (1 << size) - 1; + u32 val; + + pr_debug("%s %d %d %d 0x%08x\n", __func__, word, bit , size, v); + + val = ipureadl(&base->word[word].data[i]); + val &= ~(mask << ofs); + val |= v << ofs; + ipuwritel("chp", val, &base->word[word].data[i]); + + if ((bit + size - 1) / 32 > i) { + val = ipureadl(&base->word[word].data[i + 1]); + val &= ~(mask >> (ofs ? (32 - ofs) : 0)); + val |= v >> (ofs ? (32 - ofs) : 0); + ipuwritel("chp", val, &base->word[word].data[i + 1]); + } +} +EXPORT_SYMBOL_GPL(ipu_ch_param_write_field); + +u32 ipu_ch_param_read_field(struct ipu_ch_param __iomem *base, u32 wbs) +{ + u32 bit = (wbs >> 8) % 160; + u32 size = wbs & 0xff; + u32 word = (wbs >> 8) / 160; + u32 i = bit / 32; + u32 ofs = bit % 32; + u32 mask = (1 << size) - 1; + u32 val = 0; + + pr_debug("%s %d %d %d\n", __func__, word, bit , size); + + val = (ipureadl(&base->word[word].data[i]) >> ofs) & mask; + + if ((bit + size - 1) / 32 > i) { + u32 tmp; + tmp = ipureadl(&base->word[word].data[i + 1]); + tmp &= mask >> (ofs ? (32 - ofs) : 0); + val |= tmp << (ofs ? (32 - ofs) : 0); + } + + return val; +} +EXPORT_SYMBOL_GPL(ipu_ch_param_read_field); + +int ipu_cpmem_set_format_rgb(struct ipu_ch_param __iomem *p, + const struct ipu_rgb *rgb) +{ + int bpp = 0, npb = 0, ro, go, bo, to; + + ro = rgb->bits_per_pixel - rgb->red.length - rgb->red.offset; + go = rgb->bits_per_pixel - rgb->green.length - rgb->green.offset; + bo = rgb->bits_per_pixel - rgb->blue.length - rgb->blue.offset; + to = rgb->bits_per_pixel - rgb->transp.length - rgb->transp.offset; + + ipu_ch_param_write_field(p, IPU_FIELD_WID0, rgb->red.length - 1); + ipu_ch_param_write_field(p, IPU_FIELD_OFS0, ro); + ipu_ch_param_write_field(p, IPU_FIELD_WID1, rgb->green.length - 1); + ipu_ch_param_write_field(p, IPU_FIELD_OFS1, go); + ipu_ch_param_write_field(p, IPU_FIELD_WID2, rgb->blue.length - 1); + ipu_ch_param_write_field(p, IPU_FIELD_OFS2, bo); + + if (rgb->transp.length) { + ipu_ch_param_write_field(p, IPU_FIELD_WID3, + rgb->transp.length - 1); + ipu_ch_param_write_field(p, IPU_FIELD_OFS3, to); + } else { + ipu_ch_param_write_field(p, IPU_FIELD_WID3, 7); + ipu_ch_param_write_field(p, IPU_FIELD_OFS3, + rgb->bits_per_pixel); + } + + switch (rgb->bits_per_pixel) { + case 32: + bpp = 0; + npb = 15; + break; + case 24: + bpp = 1; + npb = 19; + break; + case 16: + bpp = 3; + npb = 31; + break; + case 8: + bpp = 5; + npb = 63; + break; + default: + return -EINVAL; + } + ipu_ch_param_write_field(p, IPU_FIELD_BPP, bpp); + ipu_ch_param_write_field(p, IPU_FIELD_NPB, npb); + ipu_ch_param_write_field(p, IPU_FIELD_PFS, 7); /* rgb mode */ + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_cpmem_set_format_rgb); + +static struct ipu_rgb def_rgb_32 = { + .red = { .offset = 16, .length = 8, }, + .green = { .offset = 8, .length = 8, }, + .blue = { .offset = 0, .length = 8, }, + .transp = { .offset = 24, .length = 8, }, + .bits_per_pixel = 32, +}; + +static struct ipu_rgb def_bgr_32 = { + .red = { .offset = 0, .length = 8, }, + .green = { .offset = 8, .length = 8, }, + .blue = { .offset = 16, .length = 8, }, + .transp = { .offset = 24, .length = 8, }, + .bits_per_pixel = 32, +}; + +static struct ipu_rgb def_rgb_24 = { + .red = { .offset = 16, .length = 8, }, + .green = { .offset = 8, .length = 8, }, + .blue = { .offset = 0, .length = 8, }, + .transp = { .offset = 0, .length = 0, }, + .bits_per_pixel = 24, +}; + +static struct ipu_rgb def_bgr_24 = { + .red = { .offset = 0, .length = 8, }, + .green = { .offset = 8, .length = 8, }, + .blue = { .offset = 16, .length = 8, }, + .transp = { .offset = 0, .length = 0, }, + .bits_per_pixel = 24, +}; + +static struct ipu_rgb def_rgb_16 = { + .red = { .offset = 11, .length = 5, }, + .green = { .offset = 5, .length = 6, }, + .blue = { .offset = 0, .length = 5, }, + .transp = { .offset = 0, .length = 0, }, + .bits_per_pixel = 16, +}; + +static struct ipu_rgb def_bgr_16 = { + .red = { .offset = 0, .length = 5, }, + .green = { .offset = 5, .length = 6, }, + .blue = { .offset = 11, .length = 5, }, + .transp = { .offset = 0, .length = 0, }, + .bits_per_pixel = 16, +}; + +struct ipu_rgb *drm_fourcc_to_rgb(u32 drm_fourcc) +{ + switch (drm_fourcc) { + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + return &def_bgr_32; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + return &def_rgb_32; + case DRM_FORMAT_BGR888: + return &def_bgr_24; + case DRM_FORMAT_RGB888: + return &def_rgb_24; + case DRM_FORMAT_RGB565: + return &def_rgb_16; + case DRM_FORMAT_BGR565: + return &def_bgr_16; + default: + return NULL; + } +} + +#define Y_OFFSET(pix, x, y) ((x) + pix->width * (y)) +#define U_OFFSET(pix, x, y) ((pix->width * pix->height) + \ + (pix->width * (y) / 4) + (x) / 2) +#define V_OFFSET(pix, x, y) ((pix->width * pix->height) + \ + (pix->width * pix->height / 4) + \ + (pix->width * (y) / 4) + (x) / 2) + +int ipu_cpmem_set_fmt(struct ipu_ch_param __iomem *cpmem, u32 drm_fourcc) +{ + switch (drm_fourcc) { + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + ipu_cpmem_set_format_rgb(cpmem, &def_bgr_32); + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + ipu_cpmem_set_format_rgb(cpmem, &def_rgb_32); + break; + case DRM_FORMAT_BGR888: + ipu_cpmem_set_format_rgb(cpmem, &def_bgr_24); + break; + case DRM_FORMAT_RGB888: + ipu_cpmem_set_format_rgb(cpmem, &def_rgb_24); + break; + case DRM_FORMAT_RGB565: + ipu_cpmem_set_format_rgb(cpmem, &def_rgb_16); + break; + case DRM_FORMAT_BGR565: + ipu_cpmem_set_format_rgb(cpmem, &def_bgr_16); + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_cpmem_set_fmt); + +struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned num) +{ + struct ipuv3_channel *channel; + + dev_dbg(ipu->dev, "%s %d\n", __func__, num); + + if (num > 63) + return ERR_PTR(-ENODEV); + + mutex_lock(&ipu->channel_lock); + + channel = &ipu->channel[num]; + + if (channel->busy) { + channel = ERR_PTR(-EBUSY); + goto out; + } + + channel->busy = true; + channel->num = num; + +out: + mutex_unlock(&ipu->channel_lock); + + return channel; +} +EXPORT_SYMBOL_GPL(ipu_idmac_get); + +void ipu_idmac_put(struct ipuv3_channel *channel) +{ + struct ipu_soc *ipu = channel->ipu; + + dev_dbg(ipu->dev, "%s %d\n", __func__, channel->num); + + mutex_lock(&ipu->channel_lock); + + channel->busy = false; + + mutex_unlock(&ipu->channel_lock); +} +EXPORT_SYMBOL_GPL(ipu_idmac_put); + +#define idma_mask(ch) (1 << (ch & 0x1f)) + +void ipu_idmac_set_double_buffer(struct ipuv3_channel *channel, + bool doublebuffer) +{ + struct ipu_soc *ipu = channel->ipu; + u32 reg; + + reg = ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(channel->num)); + if (doublebuffer) + reg |= idma_mask(channel->num); + else + reg &= ~idma_mask(channel->num); + ipu_cm_write(ipu, reg, IPU_CHA_DB_MODE_SEL(channel->num)); +} +EXPORT_SYMBOL_GPL(ipu_idmac_set_double_buffer); + +int ipu_module_enable(struct ipu_soc *ipu, u32 mask) +{ + u32 val; + + val = ipu_cm_read(ipu, IPU_DISP_GEN); + + if (mask & IPU_CONF_DI0_EN) + val |= IPU_DI0_COUNTER_RELEASE; + if (mask & IPU_CONF_DI1_EN) + val |= IPU_DI1_COUNTER_RELEASE; + + ipu_cm_write(ipu, val, IPU_DISP_GEN); + + val = ipu_cm_read(ipu, IPU_CONF); + val |= mask; + ipu_cm_write(ipu, val, IPU_CONF); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_module_enable); + +int ipu_module_disable(struct ipu_soc *ipu, u32 mask) +{ + u32 val; + + val = ipu_cm_read(ipu, IPU_CONF); + val &= ~mask; + ipu_cm_write(ipu, val, IPU_CONF); + + val = ipu_cm_read(ipu, IPU_DISP_GEN); + + if (mask & IPU_CONF_DI0_EN) + val &= ~IPU_DI0_COUNTER_RELEASE; + if (mask & IPU_CONF_DI1_EN) + val &= ~IPU_DI1_COUNTER_RELEASE; + + ipu_cm_write(ipu, val, IPU_DISP_GEN); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_module_disable); + +void ipu_idmac_select_buffer(struct ipuv3_channel *channel, u32 buf_num) +{ + struct ipu_soc *ipu = channel->ipu; + unsigned int chno = channel->num; + + /* Mark buffer as ready. */ + if (buf_num == 0) + ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF0_RDY(chno)); + else + ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF1_RDY(chno)); +} +EXPORT_SYMBOL_GPL(ipu_idmac_select_buffer); + +int ipu_idmac_enable_channel(struct ipuv3_channel *channel) +{ + struct ipu_soc *ipu = channel->ipu; + u32 val; + + val = ipu_idmac_read(ipu, IDMAC_CHA_EN(channel->num)); + val |= idma_mask(channel->num); + ipu_idmac_write(ipu, val, IDMAC_CHA_EN(channel->num)); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_idmac_enable_channel); + +int ipu_idmac_wait_busy(struct ipuv3_channel *channel, int ms) +{ + struct ipu_soc *ipu = channel->ipu; + uint64_t start; + + start = get_time_ns(); + + while (ipu_idmac_read(ipu, IDMAC_CHA_BUSY(channel->num)) & + idma_mask(channel->num)) { + if (is_timeout(start, ms * MSECOND)) + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_idmac_wait_busy); + +int ipu_idmac_disable_channel(struct ipuv3_channel *channel) +{ + struct ipu_soc *ipu = channel->ipu; + u32 val; + + /* Disable DMA channel(s) */ + val = ipu_idmac_read(ipu, IDMAC_CHA_EN(channel->num)); + val &= ~idma_mask(channel->num); + ipu_idmac_write(ipu, val, IDMAC_CHA_EN(channel->num)); + + /* Set channel buffers NOT to be ready */ + ipu_cm_write(ipu, 0xf0000000, IPU_GPR); /* write one to clear */ + + if (ipu_cm_read(ipu, IPU_CHA_BUF0_RDY(channel->num)) & + idma_mask(channel->num)) { + ipu_cm_write(ipu, idma_mask(channel->num), + IPU_CHA_BUF0_RDY(channel->num)); + } + + if (ipu_cm_read(ipu, IPU_CHA_BUF1_RDY(channel->num)) & + idma_mask(channel->num)) { + ipu_cm_write(ipu, idma_mask(channel->num), + IPU_CHA_BUF1_RDY(channel->num)); + } + + ipu_cm_write(ipu, 0x0, IPU_GPR); /* write one to set */ + + /* Reset the double buffer */ + val = ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(channel->num)); + val &= ~idma_mask(channel->num); + ipu_cm_write(ipu, val, IPU_CHA_DB_MODE_SEL(channel->num)); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_idmac_disable_channel); + +static int ipu_memory_reset(struct ipu_soc *ipu) +{ + uint64_t start; + + ipu_cm_write(ipu, 0x807FFFFF, IPU_MEM_RST); + + start = get_time_ns(); + + while (ipu_cm_read(ipu, IPU_MEM_RST) & 0x80000000) { + if (is_timeout(start, SECOND)) + return -ETIMEDOUT; + } + + return 0; +} + +static int imx6_ipu_reset(struct ipu_soc *ipu) +{ + uint32_t val; + int ret; + void __iomem *reg; + + reg = (void *)MX6_SRC_BASE_ADDR; + val = ipureadl(reg); + if (ipu->base == (void *)MX6_IPU1_BASE_ADDR) + val |= (1 << 3); + else + val |= (1 << 12); + + ipuwritel("reset", val, reg); + + ret = wait_on_timeout(100 * MSECOND, !(readl(reg) & (1 << 3))); + + return ret; + +} + +struct ipu_devtype { + const char *name; + unsigned long cm_ofs; + unsigned long cpmem_ofs; + unsigned long srm_ofs; + unsigned long tpm_ofs; + unsigned long disp0_ofs; + unsigned long disp1_ofs; + unsigned long dc_tmpl_ofs; + unsigned long vdi_ofs; + enum ipuv3_type type; + int (*reset)(struct ipu_soc *ipu); +}; + +static struct ipu_devtype ipu_type_imx51 = { + .name = "IPUv3EX", + .cm_ofs = 0x1e000000, + .cpmem_ofs = 0x1f000000, + .srm_ofs = 0x1f040000, + .tpm_ofs = 0x1f060000, + .disp0_ofs = 0x1e040000, + .disp1_ofs = 0x1e048000, + .dc_tmpl_ofs = 0x1f080000, + .vdi_ofs = 0x1e068000, + .type = IPUV3EX, +}; + +static struct ipu_devtype ipu_type_imx53 = { + .name = "IPUv3M", + .cm_ofs = 0x06000000, + .cpmem_ofs = 0x07000000, + .srm_ofs = 0x07040000, + .tpm_ofs = 0x07060000, + .disp0_ofs = 0x06040000, + .disp1_ofs = 0x06048000, + .dc_tmpl_ofs = 0x07080000, + .vdi_ofs = 0x06068000, + .type = IPUV3M, +}; + +static struct ipu_devtype ipu_type_imx6q = { + .name = "IPUv3H", + .cm_ofs = 0x00200000, + .cpmem_ofs = 0x00300000, + .srm_ofs = 0x00340000, + .tpm_ofs = 0x00360000, + .disp0_ofs = 0x00240000, + .disp1_ofs = 0x00248000, + .dc_tmpl_ofs = 0x00380000, + .vdi_ofs = 0x00268000, + .type = IPUV3H, + .reset = imx6_ipu_reset, +}; + +static struct of_device_id imx_ipu_dt_ids[] = { + { .compatible = "fsl,imx51-ipu", .data = (unsigned long)&ipu_type_imx51, }, + { .compatible = "fsl,imx53-ipu", .data = (unsigned long)&ipu_type_imx53, }, + { .compatible = "fsl,imx6q-ipu", .data = (unsigned long)&ipu_type_imx6q, }, + { /* sentinel */ } +}; + +static int ipu_submodules_init(struct ipu_soc *ipu, + struct device_d *dev, void __iomem *ipu_base, + struct clk *ipu_clk) +{ + char *unit; + int ret; + const struct ipu_devtype *devtype = ipu->devtype; + + ret = ipu_di_init(ipu, dev, 0, ipu_base + devtype->disp0_ofs, + IPU_CONF_DI0_EN, ipu_clk); + if (ret) { + unit = "di0"; + goto err_di_0; + } + + ret = ipu_di_init(ipu, dev, 1, ipu_base + devtype->disp1_ofs, + IPU_CONF_DI1_EN, ipu_clk); + if (ret) { + unit = "di1"; + goto err_di_1; + } + + ret = ipu_dc_init(ipu, dev, ipu_base + devtype->cm_ofs + + IPU_CM_DC_REG_OFS, ipu_base + devtype->dc_tmpl_ofs); + if (ret) { + unit = "dc_template"; + goto err_dc; + } + + ret = ipu_dmfc_init(ipu, dev, ipu_base + + devtype->cm_ofs + IPU_CM_DMFC_REG_OFS, ipu_clk); + if (ret) { + unit = "dmfc"; + goto err_dmfc; + } + + ret = ipu_dp_init(ipu, dev, ipu_base + devtype->srm_ofs); + if (ret) { + unit = "dp"; + goto err_dp; + } + + return 0; + +err_dp: + ipu_dmfc_exit(ipu); +err_dmfc: + ipu_dc_exit(ipu); +err_dc: + ipu_di_exit(ipu, 1); +err_di_1: + ipu_di_exit(ipu, 0); +err_di_0: + dev_err(dev, "init %s failed with %d\n", unit, ret); + return ret; +} + +static void ipu_submodules_exit(struct ipu_soc *ipu) +{ + ipu_dp_exit(ipu); + ipu_dmfc_exit(ipu); + ipu_dc_exit(ipu); + ipu_di_exit(ipu, 1); + ipu_di_exit(ipu, 0); +} + +struct ipu_platform_reg { + struct ipu_client_platformdata pdata; + const char *name; +}; + +static struct ipu_platform_reg client_reg[] = { + { + .pdata = { + .di = 0, + .dc = 5, + .dp = IPU_DP_FLOW_SYNC_BG, + .dma[0] = IPUV3_CHANNEL_MEM_BG_SYNC, + .dma[1] = IPUV3_CHANNEL_MEM_FG_SYNC, + }, + .name = "imx-ipuv3-crtc", + }, { + .pdata = { + .di = 1, + .dc = 1, + .dp = -EINVAL, + .dma[0] = IPUV3_CHANNEL_MEM_DC_SYNC, + .dma[1] = -EINVAL, + }, + .name = "imx-ipuv3-crtc", + }, +}; + +static int ipu_client_id; + +static int ipu_add_subdevice_pdata(struct device_d *ipu_dev, + struct ipu_platform_reg *reg) +{ + struct device_d *dev; + int ret; + + dev = device_alloc(reg->name, ipu_client_id++); + dev->parent = ipu_dev; + device_add_data(dev, ®->pdata, sizeof(reg->pdata)); + ((struct ipu_client_platformdata *)dev->platform_data)->device_node = ipu_dev->device_node; + + ret = platform_device_register(dev); + + return ret; +} + +static int ipu_add_client_devices(struct ipu_soc *ipu) +{ + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(client_reg); i++) { + struct ipu_platform_reg *reg = &client_reg[i]; + ret = ipu_add_subdevice_pdata(ipu->dev, reg); + if (ret) + goto err_register; + } + + return 0; + +err_register: + + return ret; +} + +static int ipu_probe(struct device_d *dev) +{ + struct ipu_soc *ipu; + void __iomem *ipu_base; + int i, ret; + const struct ipu_devtype *devtype; + + ret = dev_get_drvdata(dev, (unsigned long *)&devtype); + if (ret) + return ret; + + ipu_base = dev_request_mem_region(dev, 0); + if (!ipu_base) + return -EBUSY; + + ipu = xzalloc(sizeof(*ipu)); + + ipu->base = ipu_base; + + for (i = 0; i < 64; i++) + ipu->channel[i].ipu = ipu; + ipu->devtype = devtype; + ipu->ipu_type = devtype->type; + + dev_dbg(dev, "cm_reg: 0x%p\n", + ipu_base + devtype->cm_ofs); + dev_dbg(dev, "idmac: 0x%p\n", + ipu_base + devtype->cm_ofs + IPU_CM_IDMAC_REG_OFS); + dev_dbg(dev, "cpmem: 0x%p\n", + ipu_base + devtype->cpmem_ofs); + dev_dbg(dev, "disp0: 0x%p\n", + ipu_base + devtype->disp0_ofs); + dev_dbg(dev, "disp1: 0x%p\n", + ipu_base + devtype->disp1_ofs); + dev_dbg(dev, "srm: 0x%p\n", + ipu_base + devtype->srm_ofs); + dev_dbg(dev, "tpm: 0x%p\n", + ipu_base + devtype->tpm_ofs); + dev_dbg(dev, "dc: 0x%p\n", + ipu_base + devtype->cm_ofs + IPU_CM_DC_REG_OFS); + dev_dbg(dev, "ic: 0x%p\n", + ipu_base + devtype->cm_ofs + IPU_CM_IC_REG_OFS); + dev_dbg(dev, "dmfc: 0x%p\n", + ipu_base + devtype->cm_ofs + IPU_CM_DMFC_REG_OFS); + dev_dbg(dev, "vdi: 0x%p\n", + ipu_base + devtype->vdi_ofs); + + ipu->cm_reg = ipu_base + devtype->cm_ofs, PAGE_SIZE; + ipu->idmac_reg = ipu_base + devtype->cm_ofs + IPU_CM_IDMAC_REG_OFS; + ipu->cpmem_base = ipu_base + devtype->cpmem_ofs; + + ipu->clk = clk_get(dev, "bus"); + if (IS_ERR(ipu->clk)) { + ret = PTR_ERR(ipu->clk); + dev_err(dev, "clk_get failed with %d", ret); + return ret; + } + + dev->priv = ipu; + + ret = clk_enable(ipu->clk); + if (ret) + return ret; + + ipu->dev = dev; + + ret = devtype->reset(ipu); + if (ret) { + dev_err(dev, "failed to reset: %d\n", ret); + goto out_failed_reset; + } + + ret = ipu_memory_reset(ipu); + if (ret) + goto out_failed_reset; + + /* Set MCU_T to divide MCU access window into 2 */ + ipu_cm_write(ipu, 0x00400000L | (IPU_MCU_T_DEFAULT << 18), + IPU_DISP_GEN); + + ret = ipu_submodules_init(ipu, dev, ipu_base, ipu->clk); + if (ret) + goto failed_submodules_init; + + ret = ipu_add_client_devices(ipu); + if (ret) { + dev_err(dev, "adding client devices failed with %d\n", + ret); + goto failed_add_clients; + } + + dev_info(dev, "%s probed\n", devtype->name); + + return 0; + +failed_add_clients: + ipu_submodules_exit(ipu); +failed_submodules_init: +out_failed_reset: + clk_disable(ipu->clk); + return ret; +} + +static struct driver_d imx_ipu_driver = { + .name = "imx-ipuv3", + .of_compatible = imx_ipu_dt_ids, + .probe = ipu_probe, +}; + +device_platform_driver(imx_ipu_driver); + +MODULE_DESCRIPTION("i.MX IPU v3 driver"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/imx-ipu-v3/ipu-dc.c b/drivers/video/imx-ipu-v3/ipu-dc.c new file mode 100644 index 0000000000..2deb2ae048 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-dc.c @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <common.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <malloc.h> + +#include "imx-ipu-v3.h" +#include "ipu-prv.h" + +#define DC_MAP_CONF_PTR(n) (0x108 + ((n) & ~0x1) * 2) +#define DC_MAP_CONF_VAL(n) (0x144 + ((n) & ~0x1) * 2) + +#define DC_EVT_NF 0 +#define DC_EVT_NL 1 +#define DC_EVT_EOF 2 +#define DC_EVT_NFIELD 3 +#define DC_EVT_EOL 4 +#define DC_EVT_EOFIELD 5 +#define DC_EVT_NEW_ADDR 6 +#define DC_EVT_NEW_CHAN 7 +#define DC_EVT_NEW_DATA 8 + +#define DC_EVT_NEW_ADDR_W_0 0 +#define DC_EVT_NEW_ADDR_W_1 1 +#define DC_EVT_NEW_CHAN_W_0 2 +#define DC_EVT_NEW_CHAN_W_1 3 +#define DC_EVT_NEW_DATA_W_0 4 +#define DC_EVT_NEW_DATA_W_1 5 +#define DC_EVT_NEW_ADDR_R_0 6 +#define DC_EVT_NEW_ADDR_R_1 7 +#define DC_EVT_NEW_CHAN_R_0 8 +#define DC_EVT_NEW_CHAN_R_1 9 +#define DC_EVT_NEW_DATA_R_0 10 +#define DC_EVT_NEW_DATA_R_1 11 + +#define DC_WR_CH_CONF 0x0 +#define DC_WR_CH_ADDR 0x4 +#define DC_RL_CH(evt) (8 + ((evt) & ~0x1) * 2) + +#define DC_GEN 0xd4 +#define DC_DISP_CONF1(disp) (0xd8 + (disp) * 4) +#define DC_DISP_CONF2(disp) (0xe8 + (disp) * 4) +#define DC_STAT 0x1c8 + +#define WROD(lf) (0x18 | ((lf) << 1)) +#define WRG 0x01 +#define WCLK 0xc9 + +#define SYNC_WAVE 0 +#define NULL_WAVE (-1) + +#define DC_GEN_SYNC_1_6_SYNC (2 << 1) +#define DC_GEN_SYNC_PRIORITY_1 (1 << 7) + +#define DC_WR_CH_CONF_WORD_SIZE_8 (0 << 0) +#define DC_WR_CH_CONF_WORD_SIZE_16 (1 << 0) +#define DC_WR_CH_CONF_WORD_SIZE_24 (2 << 0) +#define DC_WR_CH_CONF_WORD_SIZE_32 (3 << 0) +#define DC_WR_CH_CONF_DISP_ID_PARALLEL(i) (((i) & 0x1) << 3) +#define DC_WR_CH_CONF_DISP_ID_SERIAL (2 << 3) +#define DC_WR_CH_CONF_DISP_ID_ASYNC (3 << 4) +#define DC_WR_CH_CONF_FIELD_MODE (1 << 9) +#define DC_WR_CH_CONF_PROG_TYPE_NORMAL (4 << 5) +#define DC_WR_CH_CONF_PROG_TYPE_MASK (7 << 5) +#define DC_WR_CH_CONF_PROG_DI_ID (1 << 2) +#define DC_WR_CH_CONF_PROG_DISP_ID(i) (((i) & 0x1) << 3) + +#define IPU_DC_NUM_CHANNELS 10 + +struct ipu_dc_priv; + +enum ipu_dc_map { + IPU_DC_MAP_RGB24, + IPU_DC_MAP_RGB565, + IPU_DC_MAP_GBR24, /* TVEv2 */ + IPU_DC_MAP_BGR666, + IPU_DC_MAP_BGR24, +}; + +struct ipu_dc { + /* The display interface number assigned to this dc channel */ + unsigned int di; + void __iomem *base; + struct ipu_dc_priv *priv; + int chno; + bool in_use; +}; + +struct ipu_dc_priv { + void __iomem *dc_reg; + void __iomem *dc_tmpl_reg; + struct ipu_soc *ipu; + struct device_d *dev; + struct ipu_dc channels[IPU_DC_NUM_CHANNELS]; +}; + +static void dc_link_event(struct ipu_dc *dc, int event, int addr, int priority) +{ + u32 reg; + + reg = ipureadl(dc->base + DC_RL_CH(event)); + reg &= ~(0xffff << (16 * (event & 0x1))); + reg |= ((addr << 8) | priority) << (16 * (event & 0x1)); + ipuwritel("dc", reg, dc->base + DC_RL_CH(event)); +} + +static void dc_write_tmpl(struct ipu_dc *dc, int word, u32 opcode, u32 operand, + int map, int wave, int glue, int sync, int stop) +{ + struct ipu_dc_priv *priv = dc->priv; + u32 reg1, reg2; + + if (opcode == WCLK) { + reg1 = (operand << 20) & 0xfff00000; + reg2 = operand >> 12 | opcode << 1 | stop << 9; + } else if (opcode == WRG) { + reg1 = sync | glue << 4 | ++wave << 11 | ((operand << 15) & 0xffff8000); + reg2 = operand >> 17 | opcode << 7 | stop << 9; + } else { + reg1 = sync | glue << 4 | ++wave << 11 | ++map << 15 | ((operand << 20) & 0xfff00000); + reg2 = operand >> 12 | opcode << 4 | stop << 9; + } + ipuwritel("dc", reg1, priv->dc_tmpl_reg + word * 8); + ipuwritel("dc", reg2, priv->dc_tmpl_reg + word * 8 + 4); +} + +static int ipu_pixfmt_to_map(u32 fmt) +{ + switch (fmt) { + case V4L2_PIX_FMT_RGB24: + return IPU_DC_MAP_RGB24; + case V4L2_PIX_FMT_RGB565: + return IPU_DC_MAP_RGB565; + case IPU_PIX_FMT_GBR24: + return IPU_DC_MAP_GBR24; + case V4L2_PIX_FMT_BGR666: + return IPU_DC_MAP_BGR666; + case V4L2_PIX_FMT_BGR24: + return IPU_DC_MAP_BGR24; + default: + return -EINVAL; + } +} + +int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced, + u32 pixel_fmt, u32 width) +{ + struct ipu_dc_priv *priv = dc->priv; + u32 reg = 0; + int map; + + dc->di = ipu_di_get_num(di); + + map = ipu_pixfmt_to_map(pixel_fmt); + if (map < 0) { + dev_dbg(priv->dev, "IPU_DISP: No MAP\n"); + return map; + } + + if (interlaced) { + dc_link_event(dc, DC_EVT_NL, 0, 3); + dc_link_event(dc, DC_EVT_EOL, 0, 2); + dc_link_event(dc, DC_EVT_NEW_DATA, 0, 1); + + /* Init template microcode */ + dc_write_tmpl(dc, 0, WROD(0), 0, map, SYNC_WAVE, 0, 8, 1); + } else { + if (dc->di) { + dc_link_event(dc, DC_EVT_NL, 2, 3); + dc_link_event(dc, DC_EVT_EOL, 3, 2); + dc_link_event(dc, DC_EVT_NEW_DATA, 1, 1); + /* Init template microcode */ + dc_write_tmpl(dc, 2, WROD(0), 0, map, SYNC_WAVE, 8, 5, 1); + dc_write_tmpl(dc, 3, WROD(0), 0, map, SYNC_WAVE, 4, 5, 0); + dc_write_tmpl(dc, 4, WRG, 0, map, NULL_WAVE, 0, 0, 1); + dc_write_tmpl(dc, 1, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1); + } else { + dc_link_event(dc, DC_EVT_NL, 5, 3); + dc_link_event(dc, DC_EVT_EOL, 6, 2); + dc_link_event(dc, DC_EVT_NEW_DATA, 8, 1); + /* Init template microcode */ + dc_write_tmpl(dc, 5, WROD(0), 0, map, SYNC_WAVE, 8, 5, 1); + dc_write_tmpl(dc, 6, WROD(0), 0, map, SYNC_WAVE, 4, 5, 0); + dc_write_tmpl(dc, 7, WRG, 0, map, NULL_WAVE, 0, 0, 1); + dc_write_tmpl(dc, 8, WROD(0), 0, map, SYNC_WAVE, 0, 5, 1); + } + } + dc_link_event(dc, DC_EVT_NF, 0, 0); + dc_link_event(dc, DC_EVT_NFIELD, 0, 0); + dc_link_event(dc, DC_EVT_EOF, 0, 0); + dc_link_event(dc, DC_EVT_EOFIELD, 0, 0); + dc_link_event(dc, DC_EVT_NEW_CHAN, 0, 0); + dc_link_event(dc, DC_EVT_NEW_ADDR, 0, 0); + + reg = ipureadl(dc->base + DC_WR_CH_CONF); + if (interlaced) + reg |= DC_WR_CH_CONF_FIELD_MODE; + else + reg &= ~DC_WR_CH_CONF_FIELD_MODE; + ipuwritel("dc", reg, dc->base + DC_WR_CH_CONF); + + ipuwritel("dc", 0x0, dc->base + DC_WR_CH_ADDR); + ipuwritel("dc", width, priv->dc_reg + DC_DISP_CONF2(dc->di)); + + ipu_module_enable(priv->ipu, IPU_CONF_DC_EN); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_dc_init_sync); + +void ipu_dc_enable_channel(struct ipu_dc *dc) +{ + int di; + u32 reg; + + di = dc->di; + + reg = ipureadl(dc->base + DC_WR_CH_CONF); + reg |= DC_WR_CH_CONF_PROG_TYPE_NORMAL; + ipuwritel("dc", reg, dc->base + DC_WR_CH_CONF); +} +EXPORT_SYMBOL_GPL(ipu_dc_enable_channel); + +void ipu_dc_disable_channel(struct ipu_dc *dc) +{ + struct ipu_dc_priv *priv = dc->priv; + u32 val; + int irq = 0, timeout = 50; + + if (dc->chno == 1) + irq = IPU_IRQ_DC_FC_1; + else if (dc->chno == 5) + irq = IPU_IRQ_DP_SF_END; + else + return; + + /* should wait for the interrupt here */ + mdelay(50); + + if (dc->di == 0) + val = 0x00000002; + else + val = 0x00000020; + + /* Wait for DC triple buffer to empty */ + while ((ipureadl(priv->dc_reg + DC_STAT) & val) != val) { + mdelay(2); + timeout -= 2; + if (timeout <= 0) + break; + } + + val = ipureadl(dc->base + DC_WR_CH_CONF); + val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; + ipuwritel("dc", val, dc->base + DC_WR_CH_CONF); +} +EXPORT_SYMBOL_GPL(ipu_dc_disable_channel); + +static void ipu_dc_map_config(struct ipu_dc_priv *priv, enum ipu_dc_map map, + int byte_num, int offset, int mask) +{ + int ptr = map * 3 + byte_num; + u32 reg; + + reg = ipureadl(priv->dc_reg + DC_MAP_CONF_VAL(ptr)); + reg &= ~(0xffff << (16 * (ptr & 0x1))); + reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1)); + ipuwritel("dc", reg, priv->dc_reg + DC_MAP_CONF_VAL(ptr)); + + reg = ipureadl(priv->dc_reg + DC_MAP_CONF_PTR(map)); + reg &= ~(0x1f << ((16 * (map & 0x1)) + (5 * byte_num))); + reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num)); + ipuwritel("dc", reg, priv->dc_reg + DC_MAP_CONF_PTR(map)); +} + +static void ipu_dc_map_clear(struct ipu_dc_priv *priv, int map) +{ + u32 reg = ipureadl(priv->dc_reg + DC_MAP_CONF_PTR(map)); + + ipuwritel("dc", reg & ~(0xffff << (16 * (map & 0x1))), + priv->dc_reg + DC_MAP_CONF_PTR(map)); +} + +struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel) +{ + struct ipu_dc_priv *priv = ipu->dc_priv; + struct ipu_dc *dc; + + if (channel >= IPU_DC_NUM_CHANNELS) + return ERR_PTR(-ENODEV); + + dc = &priv->channels[channel]; + + if (dc->in_use) + return ERR_PTR(-EBUSY); + + dc->in_use = true; + + return dc; +} +EXPORT_SYMBOL_GPL(ipu_dc_get); + +void ipu_dc_put(struct ipu_dc *dc) +{ + dc->in_use = false; +} +EXPORT_SYMBOL_GPL(ipu_dc_put); + +int ipu_dc_init(struct ipu_soc *ipu, struct device_d *dev, + void __iomem *base, void __iomem *template_base) +{ + struct ipu_dc_priv *priv; + static int channel_offsets[] = { 0, 0x1c, 0x38, 0x54, 0x58, 0x5c, + 0x78, 0, 0x94, 0xb4}; + int i; + + priv = xzalloc(sizeof(*priv)); + + priv->dev = dev; + priv->ipu = ipu; + priv->dc_reg = base; + priv->dc_tmpl_reg = template_base; + + for (i = 0; i < IPU_DC_NUM_CHANNELS; i++) { + priv->channels[i].chno = i; + priv->channels[i].priv = priv; + priv->channels[i].base = priv->dc_reg + channel_offsets[i]; + } + + ipuwritel("dc", DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(1) | + DC_WR_CH_CONF_PROG_DI_ID, + priv->channels[1].base + DC_WR_CH_CONF); + ipuwritel("dc", DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(0), + priv->channels[5].base + DC_WR_CH_CONF); + + ipuwritel("dc", DC_GEN_SYNC_1_6_SYNC | DC_GEN_SYNC_PRIORITY_1, priv->dc_reg + DC_GEN); + + ipu->dc_priv = priv; + + dev_dbg(dev, "DC base: 0x%p template base: 0x%p\n", + base, template_base); + + /* rgb24 */ + ipu_dc_map_clear(priv, IPU_DC_MAP_RGB24); + ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 0, 7, 0xff); /* blue */ + ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 1, 15, 0xff); /* green */ + ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 2, 23, 0xff); /* red */ + + /* rgb565 */ + ipu_dc_map_clear(priv, IPU_DC_MAP_RGB565); + ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 0, 4, 0xf8); /* blue */ + ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 1, 10, 0xfc); /* green */ + ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 2, 15, 0xf8); /* red */ + + /* gbr24 */ + ipu_dc_map_clear(priv, IPU_DC_MAP_GBR24); + ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 2, 15, 0xff); /* green */ + ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 1, 7, 0xff); /* blue */ + ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 0, 23, 0xff); /* red */ + + /* bgr666 */ + ipu_dc_map_clear(priv, IPU_DC_MAP_BGR666); + ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 0, 5, 0xfc); /* blue */ + ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 1, 11, 0xfc); /* green */ + ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 2, 17, 0xfc); /* red */ + + /* bgr24 */ + ipu_dc_map_clear(priv, IPU_DC_MAP_BGR24); + ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 2, 7, 0xff); /* red */ + ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 1, 15, 0xff); /* green */ + ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 0, 23, 0xff); /* blue */ + + return 0; +} + +void ipu_dc_exit(struct ipu_soc *ipu) +{ +} diff --git a/drivers/video/imx-ipu-v3/ipu-di.c b/drivers/video/imx-ipu-v3/ipu-di.c new file mode 100644 index 0000000000..e3338d09ba --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-di.c @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#include <common.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <asm-generic/div64.h> +#include <malloc.h> + +#include "imx-ipu-v3.h" +#include "ipu-prv.h" + +struct ipu_di { + void __iomem *base; + int id; + u32 module; + struct clk *clk_di; /* display input clock */ + struct clk *clk_ipu; /* IPU bus clock */ + struct clk clk_di_pixel; /* resulting pixel clock */ + char *clk_name; + const char *di_parent_names[2]; + bool inuse; + unsigned long clkflags; + struct ipu_soc *ipu; +}; + +struct di_sync_config { + int run_count; + int run_src; + int offset_count; + int offset_src; + int repeat_count; + int cnt_clr_src; + int cnt_polarity_gen_en; + int cnt_polarity_clr_src; + int cnt_polarity_trigger_src; + int cnt_up; + int cnt_down; +}; + +enum di_pins { + DI_PIN11 = 0, + DI_PIN12 = 1, + DI_PIN13 = 2, + DI_PIN14 = 3, + DI_PIN15 = 4, + DI_PIN16 = 5, + DI_PIN17 = 6, + DI_PIN_CS = 7, + + DI_PIN_SER_CLK = 0, + DI_PIN_SER_RS = 1, +}; + +enum di_sync_wave { + DI_SYNC_NONE = 0, + DI_SYNC_CLK = 1, + DI_SYNC_INT_HSYNC = 2, + DI_SYNC_HSYNC = 3, + DI_SYNC_VSYNC = 4, + DI_SYNC_DE = 6, +}; + +#define SYNC_WAVE 0 + +#define DI_GENERAL 0x0000 +#define DI_BS_CLKGEN0 0x0004 +#define DI_BS_CLKGEN1 0x0008 +#define DI_SW_GEN0(gen) (0x000c + 4 * ((gen) - 1)) +#define DI_SW_GEN1(gen) (0x0030 + 4 * ((gen) - 1)) +#define DI_STP_REP(gen) (0x0148 + 4 * (((gen) - 1)/2)) +#define DI_SYNC_AS_GEN 0x0054 +#define DI_DW_GEN(gen) (0x0058 + 4 * (gen)) +#define DI_DW_SET(gen, set) (0x0088 + 4 * ((gen) + 0xc * (set))) +#define DI_SER_CONF 0x015c +#define DI_SSC 0x0160 +#define DI_POL 0x0164 +#define DI_AW0 0x0168 +#define DI_AW1 0x016c +#define DI_SCR_CONF 0x0170 +#define DI_STAT 0x0174 + +#define DI_SW_GEN0_RUN_COUNT(x) ((x) << 19) +#define DI_SW_GEN0_RUN_SRC(x) ((x) << 16) +#define DI_SW_GEN0_OFFSET_COUNT(x) ((x) << 3) +#define DI_SW_GEN0_OFFSET_SRC(x) ((x) << 0) + +#define DI_SW_GEN1_CNT_POL_GEN_EN(x) ((x) << 29) +#define DI_SW_GEN1_CNT_CLR_SRC(x) ((x) << 25) +#define DI_SW_GEN1_CNT_POL_TRIGGER_SRC(x) ((x) << 12) +#define DI_SW_GEN1_CNT_POL_CLR_SRC(x) ((x) << 9) +#define DI_SW_GEN1_CNT_DOWN(x) ((x) << 16) +#define DI_SW_GEN1_CNT_UP(x) (x) +#define DI_SW_GEN1_AUTO_RELOAD (0x10000000) + +#define DI_DW_GEN_ACCESS_SIZE_OFFSET 24 +#define DI_DW_GEN_COMPONENT_SIZE_OFFSET 16 + +#define DI_GEN_POLARITY_1 (1 << 0) +#define DI_GEN_POLARITY_2 (1 << 1) +#define DI_GEN_POLARITY_3 (1 << 2) +#define DI_GEN_POLARITY_4 (1 << 3) +#define DI_GEN_POLARITY_5 (1 << 4) +#define DI_GEN_POLARITY_6 (1 << 5) +#define DI_GEN_POLARITY_7 (1 << 6) +#define DI_GEN_POLARITY_8 (1 << 7) +#define DI_GEN_POLARITY_DISP_CLK (1 << 17) +#define DI_GEN_DI_CLK_EXT (1 << 20) +#define DI_GEN_DI_VSYNC_EXT (1 << 21) + +#define DI_POL_DRDY_DATA_POLARITY (1 << 7) +#define DI_POL_DRDY_POLARITY_15 (1 << 4) + +#define DI_VSYNC_SEL_OFFSET 13 + +static inline u32 ipu_di_read(struct ipu_di *di, unsigned offset) +{ + return readl(di->base + offset); +} + +static inline void ipu_di_write(struct ipu_di *di, u32 value, unsigned offset) +{ + ipuwritel("di", value, di->base + offset); +} + +static int ipu_di_clk_calc_div(unsigned long inrate, unsigned long outrate) +{ + u64 tmp = inrate; + int div; + + tmp *= 16; + + do_div(tmp, outrate); + + div = tmp; + + if (div < 0x10) + div = 0x10; + +#ifdef WTF_IS_THIS + /* + * Freescale has this in their Kernel. It is neither clear what + * it does nor why it does it + */ + if (div & 0x10) + div &= ~0x7; + else { + /* Round up divider if it gets us closer to desired pix clk */ + if ((div & 0xC) == 0xC) { + div += 0x10; + div &= ~0xF; + } + } +#endif + return div; +} + +static unsigned long clk_di_recalc_rate(struct clk *clk, + unsigned long parent_rate) +{ + struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel); + unsigned long outrate; + u32 div = ipu_di_read(di, DI_BS_CLKGEN0); + + if (div < 0x10) + div = 0x10; + + outrate = (parent_rate / div) * 16; + + return outrate; +} + +static long clk_di_round_rate(struct clk *clk, unsigned long rate, + unsigned long *prate) +{ + struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel); + unsigned long outrate; + int div; + u32 val; + + div = ipu_di_clk_calc_div(*prate, rate); + + outrate = (*prate / div) * 16; + + val = ipu_di_read(di, DI_GENERAL); + + if (!(val & DI_GEN_DI_CLK_EXT) && outrate > *prate / 2) + outrate = *prate / 2; + + dev_dbg(di->ipu->dev, + "%s: inrate: %ld div: 0x%08x outrate: %ld wanted: %ld\n", + __func__, *prate, div, outrate, rate); + + return outrate; +} + +static int clk_di_set_rate(struct clk *clk, unsigned long rate, + unsigned long parent_rate) +{ + struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel); + int div; + u32 clkgen0; + + clkgen0 = ipu_di_read(di, DI_BS_CLKGEN0) & ~0xfff; + + div = ipu_di_clk_calc_div(parent_rate, rate); + + ipu_di_write(di, clkgen0 | div, DI_BS_CLKGEN0); + + dev_dbg(di->ipu->dev, "%s: inrate: %ld desired: %ld div: 0x%08x\n", + __func__, parent_rate, rate, div); + return 0; +} + +static int clk_di_get_parent(struct clk *clk) +{ + struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel); + u32 val; + + val = ipu_di_read(di, DI_GENERAL); + + return val & DI_GEN_DI_CLK_EXT ? 1 : 0; +} + +static int clk_di_set_parent(struct clk *clk, u8 index) +{ + struct ipu_di *di = container_of(clk, struct ipu_di, clk_di_pixel); + u32 val; + + val = ipu_di_read(di, DI_GENERAL); + + if (index) + val |= DI_GEN_DI_CLK_EXT; + else + val &= ~DI_GEN_DI_CLK_EXT; + + ipu_di_write(di, val, DI_GENERAL); + + return 0; +} + +static struct clk_ops clk_di_ops = { + .round_rate = clk_di_round_rate, + .set_rate = clk_di_set_rate, + .recalc_rate = clk_di_recalc_rate, + .set_parent = clk_di_set_parent, + .get_parent = clk_di_get_parent, +}; + +static void ipu_di_data_wave_config(struct ipu_di *di, + int wave_gen, + int access_size, int component_size) +{ + u32 reg; + reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) | + (component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET); + ipu_di_write(di, reg, DI_DW_GEN(wave_gen)); +} + +static void ipu_di_data_pin_config(struct ipu_di *di, int wave_gen, int di_pin, + int set, int up, int down) +{ + u32 reg; + + reg = ipu_di_read(di, DI_DW_GEN(wave_gen)); + reg &= ~(0x3 << (di_pin * 2)); + reg |= set << (di_pin * 2); + ipu_di_write(di, reg, DI_DW_GEN(wave_gen)); + + ipu_di_write(di, (down << 16) | up, DI_DW_SET(wave_gen, set)); +} + +static void ipu_di_sync_config(struct ipu_di *di, struct di_sync_config *config, + int start, int count) +{ + u32 reg; + int i; + + for (i = 0; i < count; i++) { + struct di_sync_config *c = &config[i]; + int wave_gen = start + i + 1; + + if ((c->run_count >= 0x1000) || (c->offset_count >= 0x1000) || + (c->repeat_count >= 0x1000) || + (c->cnt_up >= 0x400) || + (c->cnt_down >= 0x400)) { + dev_err(di->ipu->dev, "DI%d counters out of range.\n", + di->id); + return; + } + + reg = DI_SW_GEN0_RUN_COUNT(c->run_count) | + DI_SW_GEN0_RUN_SRC(c->run_src) | + DI_SW_GEN0_OFFSET_COUNT(c->offset_count) | + DI_SW_GEN0_OFFSET_SRC(c->offset_src); + ipu_di_write(di, reg, DI_SW_GEN0(wave_gen)); + + reg = DI_SW_GEN1_CNT_POL_GEN_EN(c->cnt_polarity_gen_en) | + DI_SW_GEN1_CNT_CLR_SRC(c->cnt_clr_src) | + DI_SW_GEN1_CNT_POL_TRIGGER_SRC( + c->cnt_polarity_trigger_src) | + DI_SW_GEN1_CNT_POL_CLR_SRC(c->cnt_polarity_clr_src) | + DI_SW_GEN1_CNT_DOWN(c->cnt_down) | + DI_SW_GEN1_CNT_UP(c->cnt_up); + + /* Enable auto reload */ + if (c->repeat_count == 0) + reg |= DI_SW_GEN1_AUTO_RELOAD; + + ipu_di_write(di, reg, DI_SW_GEN1(wave_gen)); + + reg = ipu_di_read(di, DI_STP_REP(wave_gen)); + reg &= ~(0xffff << (16 * ((wave_gen - 1) & 0x1))); + reg |= c->repeat_count << (16 * ((wave_gen - 1) & 0x1)); + ipu_di_write(di, reg, DI_STP_REP(wave_gen)); + } +} + +static void ipu_di_sync_config_interlaced(struct ipu_di *di, + struct ipu_di_signal_cfg *sig) +{ + u32 h_total = sig->width + sig->h_sync_width + + sig->h_start_width + sig->h_end_width; + u32 v_total = sig->height + sig->v_sync_width + + sig->v_start_width + sig->v_end_width; + u32 reg; + struct di_sync_config cfg[] = { + { + .run_count = h_total / 2 - 1, + .run_src = DI_SYNC_CLK, + }, { + .run_count = h_total - 11, + .run_src = DI_SYNC_CLK, + .cnt_down = 4, + }, { + .run_count = v_total * 2 - 1, + .run_src = DI_SYNC_INT_HSYNC, + .offset_count = 1, + .offset_src = DI_SYNC_INT_HSYNC, + .cnt_down = 4, + }, { + .run_count = v_total / 2 - 1, + .run_src = DI_SYNC_HSYNC, + .offset_count = sig->v_start_width, + .offset_src = DI_SYNC_HSYNC, + .repeat_count = 2, + .cnt_clr_src = DI_SYNC_VSYNC, + }, { + .run_src = DI_SYNC_HSYNC, + .repeat_count = sig->height / 2, + .cnt_clr_src = 4, + }, { + .run_count = v_total - 1, + .run_src = DI_SYNC_HSYNC, + }, { + .run_count = v_total / 2 - 1, + .run_src = DI_SYNC_HSYNC, + .offset_count = 9, + .offset_src = DI_SYNC_HSYNC, + .repeat_count = 2, + .cnt_clr_src = DI_SYNC_VSYNC, + }, { + .run_src = DI_SYNC_CLK, + .offset_count = sig->h_start_width, + .offset_src = DI_SYNC_CLK, + .repeat_count = sig->width, + .cnt_clr_src = 5, + }, { + .run_count = v_total - 1, + .run_src = DI_SYNC_INT_HSYNC, + .offset_count = v_total / 2, + .offset_src = DI_SYNC_INT_HSYNC, + .cnt_clr_src = DI_SYNC_HSYNC, + .cnt_down = 4, + } + }; + + ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg)); + + /* set gentime select and tag sel */ + reg = ipu_di_read(di, DI_SW_GEN1(9)); + reg &= 0x1FFFFFFF; + reg |= (3 - 1) << 29 | 0x00008000; + ipu_di_write(di, reg, DI_SW_GEN1(9)); + + ipu_di_write(di, v_total / 2 - 1, DI_SCR_CONF); +} + +static void ipu_di_sync_config_noninterlaced(struct ipu_di *di, + struct ipu_di_signal_cfg *sig, int div) +{ + u32 h_total = sig->width + sig->h_sync_width + sig->h_start_width + + sig->h_end_width; + u32 v_total = sig->height + sig->v_sync_width + sig->v_start_width + + sig->v_end_width; + struct di_sync_config cfg[] = { + { + /* 1: INT_HSYNC */ + .run_count = h_total - 1, + .run_src = DI_SYNC_CLK, + } , { + /* PIN2: HSYNC */ + .run_count = h_total - 1, + .run_src = DI_SYNC_CLK, + .offset_count = div * sig->v_to_h_sync, + .offset_src = DI_SYNC_CLK, + .cnt_polarity_gen_en = 1, + .cnt_polarity_trigger_src = DI_SYNC_CLK, + .cnt_down = sig->h_sync_width * 2, + } , { + /* PIN3: VSYNC */ + .run_count = v_total - 1, + .run_src = DI_SYNC_INT_HSYNC, + .cnt_polarity_gen_en = 1, + .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, + .cnt_down = sig->v_sync_width * 2, + } , { + /* 4: Line Active */ + .run_src = DI_SYNC_HSYNC, + .offset_count = sig->v_sync_width + sig->v_start_width, + .offset_src = DI_SYNC_HSYNC, + .repeat_count = sig->height, + .cnt_clr_src = DI_SYNC_VSYNC, + } , { + /* 5: Pixel Active, referenced by DC */ + .run_src = DI_SYNC_CLK, + .offset_count = sig->h_sync_width + sig->h_start_width, + .offset_src = DI_SYNC_CLK, + .repeat_count = sig->width, + .cnt_clr_src = 5, /* Line Active */ + } , { + /* unused */ + } , { + /* unused */ + } , { + /* unused */ + } , { + /* unused */ + }, + }; + /* can't use #7 and #8 for line active and pixel active counters */ + struct di_sync_config cfg_vga[] = { + { + /* 1: INT_HSYNC */ + .run_count = h_total - 1, + .run_src = DI_SYNC_CLK, + } , { + /* 2: VSYNC */ + .run_count = v_total - 1, + .run_src = DI_SYNC_INT_HSYNC, + } , { + /* 3: Line Active */ + .run_src = DI_SYNC_INT_HSYNC, + .offset_count = sig->v_sync_width + sig->v_start_width, + .offset_src = DI_SYNC_INT_HSYNC, + .repeat_count = sig->height, + .cnt_clr_src = 3 /* VSYNC */, + } , { + /* PIN4: HSYNC for VGA via TVEv2 on TQ MBa53 */ + .run_count = h_total - 1, + .run_src = DI_SYNC_CLK, + .offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */ + .offset_src = DI_SYNC_CLK, + .cnt_polarity_gen_en = 1, + .cnt_polarity_trigger_src = DI_SYNC_CLK, + .cnt_down = sig->h_sync_width * 2, + } , { + /* 5: Pixel Active signal to DC */ + .run_src = DI_SYNC_CLK, + .offset_count = sig->h_sync_width + sig->h_start_width, + .offset_src = DI_SYNC_CLK, + .repeat_count = sig->width, + .cnt_clr_src = 4, /* Line Active */ + } , { + /* PIN6: VSYNC for VGA via TVEv2 on TQ MBa53 */ + .run_count = v_total - 1, + .run_src = DI_SYNC_INT_HSYNC, + .offset_count = 1, /* magic value from Freescale TVE driver */ + .offset_src = DI_SYNC_INT_HSYNC, + .cnt_polarity_gen_en = 1, + .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, + .cnt_down = sig->v_sync_width * 2, + } , { + /* PIN4: HSYNC for VGA via TVEv2 on i.MX53-QSB */ + .run_count = h_total - 1, + .run_src = DI_SYNC_CLK, + .offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */ + .offset_src = DI_SYNC_CLK, + .cnt_polarity_gen_en = 1, + .cnt_polarity_trigger_src = DI_SYNC_CLK, + .cnt_down = sig->h_sync_width * 2, + } , { + /* PIN6: VSYNC for VGA via TVEv2 on i.MX53-QSB */ + .run_count = v_total - 1, + .run_src = DI_SYNC_INT_HSYNC, + .offset_count = 1, /* magic value from Freescale TVE driver */ + .offset_src = DI_SYNC_INT_HSYNC, + .cnt_polarity_gen_en = 1, + .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, + .cnt_down = sig->v_sync_width * 2, + } , { + /* unused */ + }, + }; + + ipu_di_write(di, v_total - 1, DI_SCR_CONF); + if (sig->hsync_pin == 2 && sig->vsync_pin == 3) + ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg)); + else + ipu_di_sync_config(di, cfg_vga, 0, ARRAY_SIZE(cfg_vga)); +} + +int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig) +{ + u32 reg; + u32 di_gen, vsync_cnt; + u32 div; + u32 h_total, v_total; + int ret; + unsigned long round; + struct clk *parent; + + dev_dbg(di->ipu->dev, "disp %d: panel size = %d x %d, pixelclock = %ld\n", + di->id, sig->width, sig->height, sig->pixelclock); + + if ((sig->v_sync_width == 0) || (sig->h_sync_width == 0)) + return -EINVAL; + + if (sig->clkflags & IPU_DI_CLKMODE_EXT) + parent = di->clk_di; + else + parent = di->clk_ipu; + + ret = clk_set_parent(&di->clk_di_pixel, parent); + if (ret) { + dev_err(di->ipu->dev, + "setting pixel clock to parent %s failed with %d\n", + parent->name, ret); + return ret; + } + + if (sig->clkflags & IPU_DI_CLKMODE_SYNC) { + ret = clk_set_rate(clk_get_parent(parent), sig->pixelclock); + round = sig->pixelclock; + } else if (sig->clkflags & IPU_DI_CLKMODE_NON_FRACTIONAL) { + unsigned div; + + round = clk_get_rate(parent); + div = DIV_ROUND_CLOSEST(round, sig->pixelclock); + round = round / div; + } else { + round = sig->pixelclock; + } + + ret = clk_set_rate(&di->clk_di_pixel, round); + + h_total = sig->width + sig->h_sync_width + sig->h_start_width + + sig->h_end_width; + v_total = sig->height + sig->v_sync_width + sig->v_start_width + + sig->v_end_width; + + div = ipu_di_read(di, DI_BS_CLKGEN0) & 0xfff; + div = div / 16; /* Now divider is integer portion */ + + /* Setup pixel clock timing */ + /* Down time is half of period */ + ipu_di_write(di, (div << 16), DI_BS_CLKGEN1); + + ipu_di_data_wave_config(di, SYNC_WAVE, div - 1, div - 1); + ipu_di_data_pin_config(di, SYNC_WAVE, DI_PIN15, 3, 0, div * 2); + + di_gen = ipu_di_read(di, DI_GENERAL) & DI_GEN_DI_CLK_EXT; + di_gen |= DI_GEN_DI_VSYNC_EXT; + + if (sig->interlaced) { + ipu_di_sync_config_interlaced(di, sig); + + /* set y_sel = 1 */ + di_gen |= 0x10000000; + di_gen |= DI_GEN_POLARITY_5; + di_gen |= DI_GEN_POLARITY_8; + + vsync_cnt = 7; + + if (sig->Hsync_pol) + di_gen |= DI_GEN_POLARITY_3; + if (sig->Vsync_pol) + di_gen |= DI_GEN_POLARITY_2; + } else { + ipu_di_sync_config_noninterlaced(di, sig, div); + + vsync_cnt = 3; + if (di->id == 1) + /* + * TODO: change only for TVEv2, parallel display + * uses pin 2 / 3 + */ + if (!(sig->hsync_pin == 2 && sig->vsync_pin == 3)) + vsync_cnt = 6; + + if (sig->Hsync_pol) { + if (sig->hsync_pin == 2) + di_gen |= DI_GEN_POLARITY_2; + else if (sig->hsync_pin == 4) + di_gen |= DI_GEN_POLARITY_4; + else if (sig->hsync_pin == 7) + di_gen |= DI_GEN_POLARITY_7; + } + if (sig->Vsync_pol) { + if (sig->vsync_pin == 3) + di_gen |= DI_GEN_POLARITY_3; + else if (sig->vsync_pin == 6) + di_gen |= DI_GEN_POLARITY_6; + else if (sig->vsync_pin == 8) + di_gen |= DI_GEN_POLARITY_8; + } + } + + if (!sig->clk_pol) + di_gen |= DI_GEN_POLARITY_DISP_CLK; + + ipu_di_write(di, di_gen, DI_GENERAL); + + ipu_di_write(di, (--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002, + DI_SYNC_AS_GEN); + + reg = ipu_di_read(di, DI_POL); + reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15); + + if (sig->enable_pol) + reg |= DI_POL_DRDY_POLARITY_15; + if (sig->data_pol) + reg |= DI_POL_DRDY_DATA_POLARITY; + + ipu_di_write(di, reg, DI_POL); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_di_init_sync_panel); + +int ipu_di_enable(struct ipu_di *di) +{ + int ret = clk_enable(&di->clk_di_pixel); + if (ret) + return ret; + + ipu_module_enable(di->ipu, di->module); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_di_enable); + +int ipu_di_disable(struct ipu_di *di) +{ + ipu_module_disable(di->ipu, di->module); + + clk_disable(&di->clk_di_pixel); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_di_disable); + +int ipu_di_get_num(struct ipu_di *di) +{ + return di->id; +} +EXPORT_SYMBOL_GPL(ipu_di_get_num); + +struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp) +{ + struct ipu_di *di; + + if (disp > 1) + return ERR_PTR(-EINVAL); + + di = ipu->di_priv[disp]; + + if (di->inuse) { + di = ERR_PTR(-EBUSY); + goto out; + } + + di->inuse = true; +out: + return di; +} +EXPORT_SYMBOL_GPL(ipu_di_get); + +void ipu_di_put(struct ipu_di *di) +{ + di->inuse = false; +} +EXPORT_SYMBOL_GPL(ipu_di_put); + +int ipu_di_init(struct ipu_soc *ipu, struct device_d *dev, int id, + void __iomem *base, + u32 module, struct clk *clk_ipu) +{ + struct ipu_di *di; + int ret; + + if (id > 1) + return -ENODEV; + + di = xzalloc(sizeof(*di)); + + ipu->di_priv[id] = di; + + di->clk_di = clk_get(dev, id ? "di1" : "di0"); + if (IS_ERR(di->clk_di)) + return PTR_ERR(di->clk_di); + + di->module = module; + di->id = id; + di->clk_ipu = clk_ipu; + di->base = base; + + di->di_parent_names[0] = di->clk_ipu->name; + di->di_parent_names[1] = di->clk_di->name; + + ipu_di_write(di, 0x10, DI_BS_CLKGEN0); + + di->clk_di_pixel.parent_names = di->di_parent_names; + di->clk_name = asprintf("%s_di%d_pixel", + dev_name(dev), id); + if (!di->clk_name) + return -ENOMEM; + + di->clk_di_pixel.ops = &clk_di_ops; + di->clk_di_pixel.num_parents = 2; + di->clk_di_pixel.name = di->clk_name; + ret = clk_register(&di->clk_di_pixel); + if (ret) + goto failed_clk_register; + + dev_dbg(dev, "DI%d base: 0x%p\n", id, base); + di->inuse = false; + di->ipu = ipu; + + return 0; + +failed_clk_register: + + free(di->clk_name); + + return ret; +} + +void ipu_di_exit(struct ipu_soc *ipu, int id) +{ +} diff --git a/drivers/video/imx-ipu-v3/ipu-dmfc.c b/drivers/video/imx-ipu-v3/ipu-dmfc.c new file mode 100644 index 0000000000..7b54e25001 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-dmfc.c @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <common.h> +#include <linux/err.h> +#include <linux/clk.h> + +#include "imx-ipu-v3.h" +#include "ipu-prv.h" + +#define DMFC_RD_CHAN 0x0000 +#define DMFC_WR_CHAN 0x0004 +#define DMFC_WR_CHAN_DEF 0x0008 +#define DMFC_DP_CHAN 0x000c +#define DMFC_DP_CHAN_DEF 0x0010 +#define DMFC_GENERAL1 0x0014 +#define DMFC_GENERAL2 0x0018 +#define DMFC_IC_CTRL 0x001c +#define DMFC_STAT 0x0020 + +#define DMFC_WR_CHAN_1_28 0 +#define DMFC_WR_CHAN_2_41 8 +#define DMFC_WR_CHAN_1C_42 16 +#define DMFC_WR_CHAN_2C_43 24 + +#define DMFC_DP_CHAN_5B_23 0 +#define DMFC_DP_CHAN_5F_27 8 +#define DMFC_DP_CHAN_6B_24 16 +#define DMFC_DP_CHAN_6F_29 24 + +#define DMFC_FIFO_SIZE_64 (3 << 3) +#define DMFC_FIFO_SIZE_128 (2 << 3) +#define DMFC_FIFO_SIZE_256 (1 << 3) +#define DMFC_FIFO_SIZE_512 (0 << 3) + +#define DMFC_SEGMENT(x) ((x & 0x7) << 0) +#define DMFC_BURSTSIZE_128 (0 << 6) +#define DMFC_BURSTSIZE_64 (1 << 6) +#define DMFC_BURSTSIZE_32 (2 << 6) +#define DMFC_BURSTSIZE_16 (3 << 6) + +struct dmfc_channel_data { + int ipu_channel; + unsigned long channel_reg; + unsigned long shift; + unsigned eot_shift; + unsigned max_fifo_lines; +}; + +static const struct dmfc_channel_data dmfcdata[] = { + { + .ipu_channel = IPUV3_CHANNEL_MEM_BG_SYNC, + .channel_reg = DMFC_DP_CHAN, + .shift = DMFC_DP_CHAN_5B_23, + .eot_shift = 20, + .max_fifo_lines = 3, + }, { + .ipu_channel = 24, + .channel_reg = DMFC_DP_CHAN, + .shift = DMFC_DP_CHAN_6B_24, + .eot_shift = 22, + .max_fifo_lines = 1, + }, { + .ipu_channel = IPUV3_CHANNEL_MEM_FG_SYNC, + .channel_reg = DMFC_DP_CHAN, + .shift = DMFC_DP_CHAN_5F_27, + .eot_shift = 21, + .max_fifo_lines = 2, + }, { + .ipu_channel = IPUV3_CHANNEL_MEM_DC_SYNC, + .channel_reg = DMFC_WR_CHAN, + .shift = DMFC_WR_CHAN_1_28, + .eot_shift = 16, + .max_fifo_lines = 2, + }, { + .ipu_channel = 29, + .channel_reg = DMFC_DP_CHAN, + .shift = DMFC_DP_CHAN_6F_29, + .eot_shift = 23, + .max_fifo_lines = 1, + }, +}; + +#define DMFC_NUM_CHANNELS ARRAY_SIZE(dmfcdata) + +struct ipu_dmfc_priv; + +struct dmfc_channel { + unsigned slots; + unsigned slotmask; + unsigned segment; + int burstsize; + struct ipu_soc *ipu; + struct ipu_dmfc_priv *priv; + const struct dmfc_channel_data *data; +}; + +struct ipu_dmfc_priv { + struct ipu_soc *ipu; + struct device_d *dev; + struct dmfc_channel channels[DMFC_NUM_CHANNELS]; + unsigned long bandwidth_per_slot; + void __iomem *base; + int use_count; +}; + +int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc) +{ + struct ipu_dmfc_priv *priv = dmfc->priv; + + if (!priv->use_count) + ipu_module_enable(priv->ipu, IPU_CONF_DMFC_EN); + + priv->use_count++; + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_dmfc_enable_channel); + +void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc) +{ + struct ipu_dmfc_priv *priv = dmfc->priv; + + priv->use_count--; + + if (!priv->use_count) + ipu_module_disable(priv->ipu, IPU_CONF_DMFC_EN); + + if (priv->use_count < 0) + priv->use_count = 0; +} +EXPORT_SYMBOL_GPL(ipu_dmfc_disable_channel); + +static int ipu_dmfc_setup_channel(struct dmfc_channel *dmfc, int slots, + int segment, int burstsize) +{ + struct ipu_dmfc_priv *priv = dmfc->priv; + u32 val, field; + + dev_dbg(priv->dev, + "dmfc: using %d slots starting from segment %d for IPU channel %d\n", + slots, segment, dmfc->data->ipu_channel); + + if (!dmfc) + return -EINVAL; + + switch (slots) { + case 1: + field = DMFC_FIFO_SIZE_64; + break; + case 2: + field = DMFC_FIFO_SIZE_128; + break; + case 4: + field = DMFC_FIFO_SIZE_256; + break; + case 8: + field = DMFC_FIFO_SIZE_512; + break; + default: + return -EINVAL; + } + + switch (burstsize) { + case 16: + field |= DMFC_BURSTSIZE_16; + break; + case 32: + field |= DMFC_BURSTSIZE_32; + break; + case 64: + field |= DMFC_BURSTSIZE_64; + break; + case 128: + field |= DMFC_BURSTSIZE_128; + break; + } + + field |= DMFC_SEGMENT(segment); + + val = ipureadl(priv->base + dmfc->data->channel_reg); + + val &= ~(0xff << dmfc->data->shift); + val |= field << dmfc->data->shift; + + ipuwritel("dmfc", val, priv->base + dmfc->data->channel_reg); + + dmfc->slots = slots; + dmfc->segment = segment; + dmfc->burstsize = burstsize; + dmfc->slotmask = ((1 << slots) - 1) << segment; + + return 0; +} + +static int dmfc_bandwidth_to_slots(struct ipu_dmfc_priv *priv, + unsigned long bandwidth) +{ + int slots = 1; + + while (slots * priv->bandwidth_per_slot < bandwidth) + slots *= 2; + + return slots; +} + +static int dmfc_find_slots(struct ipu_dmfc_priv *priv, int slots) +{ + unsigned slotmask_need, slotmask_used = 0; + int i, segment = 0; + + slotmask_need = (1 << slots) - 1; + + for (i = 0; i < DMFC_NUM_CHANNELS; i++) + slotmask_used |= priv->channels[i].slotmask; + + while (slotmask_need <= 0xff) { + if (!(slotmask_used & slotmask_need)) + return segment; + + slotmask_need <<= 1; + segment++; + } + + return -EBUSY; +} + +void ipu_dmfc_free_bandwidth(struct dmfc_channel *dmfc) +{ + struct ipu_dmfc_priv *priv = dmfc->priv; + int i; + + dev_dbg(priv->dev, "dmfc: freeing %d slots starting from segment %d\n", + dmfc->slots, dmfc->segment); + + if (!dmfc->slots) + return; + + dmfc->slotmask = 0; + dmfc->slots = 0; + dmfc->segment = 0; + + for (i = 0; i < DMFC_NUM_CHANNELS; i++) + priv->channels[i].slotmask = 0; + + for (i = 0; i < DMFC_NUM_CHANNELS; i++) { + if (priv->channels[i].slots > 0) { + priv->channels[i].segment = + dmfc_find_slots(priv, priv->channels[i].slots); + priv->channels[i].slotmask = + ((1 << priv->channels[i].slots) - 1) << + priv->channels[i].segment; + } + } + + for (i = 0; i < DMFC_NUM_CHANNELS; i++) { + if (priv->channels[i].slots > 0) + ipu_dmfc_setup_channel(&priv->channels[i], + priv->channels[i].slots, + priv->channels[i].segment, + priv->channels[i].burstsize); + } +} +EXPORT_SYMBOL_GPL(ipu_dmfc_free_bandwidth); + +int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc, + unsigned long bandwidth_pixel_per_second, int burstsize) +{ + struct ipu_dmfc_priv *priv = dmfc->priv; + int slots = dmfc_bandwidth_to_slots(priv, bandwidth_pixel_per_second); + int segment = -1, ret = 0; + + dev_dbg(priv->dev, "dmfc: trying to allocate %ldMpixel/s for IPU channel %d\n", + bandwidth_pixel_per_second / 1000000, + dmfc->data->ipu_channel); + + ipu_dmfc_free_bandwidth(dmfc); + + if (slots > 8) { + ret = -EBUSY; + goto out; + } + + /* For the MEM_BG channel, first try to allocate twice the slots */ + if (dmfc->data->ipu_channel == IPUV3_CHANNEL_MEM_BG_SYNC) + segment = dmfc_find_slots(priv, slots * 2); + else if (slots < 2) + /* Always allocate at least 128*4 bytes (2 slots) */ + slots = 2; + + if (segment >= 0) + slots *= 2; + else + segment = dmfc_find_slots(priv, slots); + if (segment < 0) { + ret = -EBUSY; + goto out; + } + + ipu_dmfc_setup_channel(dmfc, slots, segment, burstsize); + +out: + return ret; +} +EXPORT_SYMBOL_GPL(ipu_dmfc_alloc_bandwidth); + +int ipu_dmfc_init_channel(struct dmfc_channel *dmfc, int width) +{ + struct ipu_dmfc_priv *priv = dmfc->priv; + u32 dmfc_gen1; + + dmfc_gen1 = ipureadl(priv->base + DMFC_GENERAL1); + + if ((dmfc->slots * 64 * 4) / width > dmfc->data->max_fifo_lines) + dmfc_gen1 |= 1 << dmfc->data->eot_shift; + else + dmfc_gen1 &= ~(1 << dmfc->data->eot_shift); + + ipuwritel("dmfc", dmfc_gen1, priv->base + DMFC_GENERAL1); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_dmfc_init_channel); + +struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipu_channel) +{ + struct ipu_dmfc_priv *priv = ipu->dmfc_priv; + int i; + + for (i = 0; i < DMFC_NUM_CHANNELS; i++) + if (dmfcdata[i].ipu_channel == ipu_channel) + return &priv->channels[i]; + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL_GPL(ipu_dmfc_get); + +void ipu_dmfc_put(struct dmfc_channel *dmfc) +{ + ipu_dmfc_free_bandwidth(dmfc); +} +EXPORT_SYMBOL_GPL(ipu_dmfc_put); + +int ipu_dmfc_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base, + struct clk *ipu_clk) +{ + struct ipu_dmfc_priv *priv; + int i; + + priv = xzalloc(sizeof(*priv)); + + priv->base = base; + + priv->dev = dev; + priv->ipu = ipu; + + ipu->dmfc_priv = priv; + + for (i = 0; i < DMFC_NUM_CHANNELS; i++) { + priv->channels[i].priv = priv; + priv->channels[i].ipu = ipu; + priv->channels[i].data = &dmfcdata[i]; + } + + ipuwritel("dmfc", 0x0, priv->base + DMFC_WR_CHAN); + ipuwritel("dmfc", 0x0, priv->base + DMFC_DP_CHAN); + + /* + * We have a total bandwidth of clkrate * 4pixel divided + * into 8 slots. + */ + priv->bandwidth_per_slot = clk_get_rate(ipu_clk) * 4 / 8; + + dev_dbg(dev, "dmfc: 8 slots with %ldMpixel/s bandwidth each\n", + priv->bandwidth_per_slot / 1000000); + + ipuwritel("dmfc", 0x202020f6, priv->base + DMFC_WR_CHAN_DEF); + ipuwritel("dmfc", 0x2020f6f6, priv->base + DMFC_DP_CHAN_DEF); + ipuwritel("dmfc", 0x00000003, priv->base + DMFC_GENERAL1); + + return 0; +} + +void ipu_dmfc_exit(struct ipu_soc *ipu) +{ +} diff --git a/drivers/video/imx-ipu-v3/ipu-dp.c b/drivers/video/imx-ipu-v3/ipu-dp.c new file mode 100644 index 0000000000..8829954db0 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-dp.c @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#include <common.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <malloc.h> + +#include "imx-ipu-v3.h" +#include "ipu-prv.h" + +#define DP_SYNC 0 +#define DP_ASYNC0 0x60 +#define DP_ASYNC1 0xBC + +#define DP_COM_CONF 0x0 +#define DP_GRAPH_WIND_CTRL 0x0004 +#define DP_FG_POS 0x0008 +#define DP_CSC_A_0 0x0044 +#define DP_CSC_A_1 0x0048 +#define DP_CSC_A_2 0x004C +#define DP_CSC_A_3 0x0050 +#define DP_CSC_0 0x0054 +#define DP_CSC_1 0x0058 + +#define DP_COM_CONF_FG_EN (1 << 0) +#define DP_COM_CONF_GWSEL (1 << 1) +#define DP_COM_CONF_GWAM (1 << 2) +#define DP_COM_CONF_GWCKE (1 << 3) +#define DP_COM_CONF_CSC_DEF_MASK (3 << 8) +#define DP_COM_CONF_CSC_DEF_OFFSET 8 +#define DP_COM_CONF_CSC_DEF_FG (3 << 8) +#define DP_COM_CONF_CSC_DEF_BG (2 << 8) +#define DP_COM_CONF_CSC_DEF_BOTH (1 << 8) + +#define IPUV3_NUM_FLOWS 3 + +struct ipu_dp_priv; + +struct ipu_dp { + u32 flow; + bool in_use; + bool foreground; + enum ipu_color_space in_cs; +}; + +struct ipu_flow { + struct ipu_dp foreground; + struct ipu_dp background; + enum ipu_color_space out_cs; + void __iomem *base; + struct ipu_dp_priv *priv; +}; + +struct ipu_dp_priv { + struct ipu_soc *ipu; + struct device_d *dev; + void __iomem *base; + struct ipu_flow flow[IPUV3_NUM_FLOWS]; + int use_count; +}; + +static u32 ipu_dp_flow_base[] = {DP_SYNC, DP_ASYNC0, DP_ASYNC1}; + +static inline struct ipu_flow *to_flow(struct ipu_dp *dp) +{ + if (dp->foreground) + return container_of(dp, struct ipu_flow, foreground); + else + return container_of(dp, struct ipu_flow, background); +} + +int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, + u8 alpha, bool bg_chan) +{ + struct ipu_flow *flow = to_flow(dp); + struct ipu_dp_priv *priv = flow->priv; + u32 reg; + + reg = ipureadl(flow->base + DP_COM_CONF); + if (bg_chan) + reg &= ~DP_COM_CONF_GWSEL; + else + reg |= DP_COM_CONF_GWSEL; + ipuwritel("dp", reg, flow->base + DP_COM_CONF); + + if (enable) { + reg = ipureadl(flow->base + DP_GRAPH_WIND_CTRL) & 0x00FFFFFFL; + ipuwritel("dp", reg | ((u32) alpha << 24), + flow->base + DP_GRAPH_WIND_CTRL); + + reg = ipureadl(flow->base + DP_COM_CONF); + ipuwritel("dp", reg | DP_COM_CONF_GWAM, flow->base + DP_COM_CONF); + } else { + reg = ipureadl(flow->base + DP_COM_CONF); + ipuwritel("dp", reg & ~DP_COM_CONF_GWAM, flow->base + DP_COM_CONF); + } + + ipu_srm_dp_sync_update(priv->ipu); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_dp_set_global_alpha); + +int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos) +{ + struct ipu_flow *flow = to_flow(dp); + struct ipu_dp_priv *priv = flow->priv; + + ipuwritel("dp", (x_pos << 16) | y_pos, flow->base + DP_FG_POS); + + ipu_srm_dp_sync_update(priv->ipu); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_dp_set_window_pos); + +static void ipu_dp_csc_init(struct ipu_flow *flow, + enum ipu_color_space in, + enum ipu_color_space out, + u32 place) +{ + u32 reg; + + reg = ipureadl(flow->base + DP_COM_CONF); + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + + if (in == out) { + ipuwritel("dp", reg, flow->base + DP_COM_CONF); + return; + } + + if (in == IPUV3_COLORSPACE_RGB && out == IPUV3_COLORSPACE_YUV) { + ipuwritel("dp", 0x099 | (0x12d << 16), flow->base + DP_CSC_A_0); + ipuwritel("dp", 0x03a | (0x3a9 << 16), flow->base + DP_CSC_A_1); + ipuwritel("dp", 0x356 | (0x100 << 16), flow->base + DP_CSC_A_2); + ipuwritel("dp", 0x100 | (0x329 << 16), flow->base + DP_CSC_A_3); + ipuwritel("dp", 0x3d6 | (0x0000 << 16) | (2 << 30), + flow->base + DP_CSC_0); + ipuwritel("dp", 0x200 | (2 << 14) | (0x200 << 16) | (2 << 30), + flow->base + DP_CSC_1); + } else { + ipuwritel("dp", 0x095 | (0x000 << 16), flow->base + DP_CSC_A_0); + ipuwritel("dp", 0x0cc | (0x095 << 16), flow->base + DP_CSC_A_1); + ipuwritel("dp", 0x3ce | (0x398 << 16), flow->base + DP_CSC_A_2); + ipuwritel("dp", 0x095 | (0x0ff << 16), flow->base + DP_CSC_A_3); + ipuwritel("dp", 0x000 | (0x3e42 << 16) | (1 << 30), + flow->base + DP_CSC_0); + ipuwritel("dp", 0x10a | (1 << 14) | (0x3dd6 << 16) | (1 << 30), + flow->base + DP_CSC_1); + } + + reg |= place; + + ipuwritel("dp", reg, flow->base + DP_COM_CONF); +} + +int ipu_dp_setup_channel(struct ipu_dp *dp, + enum ipu_color_space in, + enum ipu_color_space out) +{ + struct ipu_flow *flow = to_flow(dp); + struct ipu_dp_priv *priv = flow->priv; + + dp->in_cs = in; + + if (!dp->foreground) + flow->out_cs = out; + + if (flow->foreground.in_cs == flow->background.in_cs) { + /* + * foreground and background are of same colorspace, put + * colorspace converter after combining unit. + */ + ipu_dp_csc_init(flow, flow->foreground.in_cs, flow->out_cs, + DP_COM_CONF_CSC_DEF_BOTH); + } else { + if (flow->foreground.in_cs == flow->out_cs) + /* + * foreground identical to output, apply color + * conversion on background + */ + ipu_dp_csc_init(flow, flow->background.in_cs, + flow->out_cs, DP_COM_CONF_CSC_DEF_BG); + else + ipu_dp_csc_init(flow, flow->foreground.in_cs, + flow->out_cs, DP_COM_CONF_CSC_DEF_FG); + } + + ipu_srm_dp_sync_update(priv->ipu); + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_dp_setup_channel); + +int ipu_dp_enable_channel(struct ipu_dp *dp) +{ + struct ipu_flow *flow = to_flow(dp); + struct ipu_dp_priv *priv = flow->priv; + + if (!priv->use_count) + ipu_module_enable(priv->ipu, IPU_CONF_DP_EN); + + priv->use_count++; + + if (dp->foreground) { + u32 reg; + + reg = ipureadl(flow->base + DP_COM_CONF); + reg |= DP_COM_CONF_FG_EN; + ipuwritel("dp", reg, flow->base + DP_COM_CONF); + + ipu_srm_dp_sync_update(priv->ipu); + } + + return 0; +} +EXPORT_SYMBOL_GPL(ipu_dp_enable_channel); + +void ipu_dp_disable_channel(struct ipu_dp *dp) +{ + struct ipu_flow *flow = to_flow(dp); + struct ipu_dp_priv *priv = flow->priv; + + priv->use_count--; + + if (dp->foreground) { + u32 reg, csc; + + reg = ipureadl(flow->base + DP_COM_CONF); + csc = reg & DP_COM_CONF_CSC_DEF_MASK; + if (csc == DP_COM_CONF_CSC_DEF_FG) + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + + reg &= ~DP_COM_CONF_FG_EN; + ipuwritel("dp", reg, flow->base + DP_COM_CONF); + + ipuwritel("dp", 0, flow->base + DP_FG_POS); + ipu_srm_dp_sync_update(priv->ipu); + } + + if (!priv->use_count) + ipu_module_disable(priv->ipu, IPU_CONF_DP_EN); + + if (priv->use_count < 0) + priv->use_count = 0; +} +EXPORT_SYMBOL_GPL(ipu_dp_disable_channel); + +struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow) +{ + struct ipu_dp_priv *priv = ipu->dp_priv; + struct ipu_dp *dp; + + if ((flow >> 1) >= IPUV3_NUM_FLOWS) + return ERR_PTR(-EINVAL); + + if (flow & 1) + dp = &priv->flow[flow >> 1].foreground; + else + dp = &priv->flow[flow >> 1].background; + + if (dp->in_use) + return ERR_PTR(-EBUSY); + + dp->in_use = true; + + return dp; +} +EXPORT_SYMBOL_GPL(ipu_dp_get); + +void ipu_dp_put(struct ipu_dp *dp) +{ + dp->in_use = false; +} +EXPORT_SYMBOL_GPL(ipu_dp_put); + +int ipu_dp_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base) +{ + struct ipu_dp_priv *priv; + int i; + + priv = xzalloc(sizeof(*priv)); + priv->dev = dev; + priv->ipu = ipu; + + ipu->dp_priv = priv; + + priv->base = base; + + for (i = 0; i < IPUV3_NUM_FLOWS; i++) { + priv->flow[i].foreground.foreground = true; + priv->flow[i].base = priv->base + ipu_dp_flow_base[i]; + priv->flow[i].priv = priv; + } + + return 0; +} + +void ipu_dp_exit(struct ipu_soc *ipu) +{ +} diff --git a/drivers/video/imx-ipu-v3/ipu-prv.h b/drivers/video/imx-ipu-v3/ipu-prv.h new file mode 100644 index 0000000000..44d7802521 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipu-prv.h @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#ifndef __IPU_PRV_H__ +#define __IPU_PRV_H__ + +struct ipu_soc; + +#include "imx-ipu-v3.h" + +#define IPU_PIX_FMT_GBR24 v4l2_fourcc('G', 'B', 'R', '3') + +#define IPUV3_CHANNEL_CSI0 0 +#define IPUV3_CHANNEL_CSI1 1 +#define IPUV3_CHANNEL_CSI2 2 +#define IPUV3_CHANNEL_CSI3 3 +#define IPUV3_CHANNEL_MEM_BG_SYNC 23 +#define IPUV3_CHANNEL_MEM_FG_SYNC 27 +#define IPUV3_CHANNEL_MEM_DC_SYNC 28 +#define IPUV3_CHANNEL_MEM_FG_SYNC_ALPHA 31 +#define IPUV3_CHANNEL_MEM_DC_ASYNC 41 +#define IPUV3_CHANNEL_ROT_ENC_MEM 45 +#define IPUV3_CHANNEL_ROT_VF_MEM 46 +#define IPUV3_CHANNEL_ROT_PP_MEM 47 +#define IPUV3_CHANNEL_ROT_ENC_MEM_OUT 48 +#define IPUV3_CHANNEL_ROT_VF_MEM_OUT 49 +#define IPUV3_CHANNEL_ROT_PP_MEM_OUT 50 +#define IPUV3_CHANNEL_MEM_BG_SYNC_ALPHA 51 + +#define IPU_MCU_T_DEFAULT 8 +#define IPU_CM_IDMAC_REG_OFS 0x00008000 +#define IPU_CM_IC_REG_OFS 0x00020000 +#define IPU_CM_IRT_REG_OFS 0x00028000 +#define IPU_CM_CSI0_REG_OFS 0x00030000 +#define IPU_CM_CSI1_REG_OFS 0x00038000 +#define IPU_CM_SMFC_REG_OFS 0x00050000 +#define IPU_CM_DC_REG_OFS 0x00058000 +#define IPU_CM_DMFC_REG_OFS 0x00060000 + +/* Register addresses */ +/* IPU Common registers */ +#define IPU_CM_REG(offset) (offset) + +#define IPU_CONF IPU_CM_REG(0) + +#define IPU_SRM_PRI1 IPU_CM_REG(0x00a0) +#define IPU_SRM_PRI2 IPU_CM_REG(0x00a4) +#define IPU_FS_PROC_FLOW1 IPU_CM_REG(0x00a8) +#define IPU_FS_PROC_FLOW2 IPU_CM_REG(0x00ac) +#define IPU_FS_PROC_FLOW3 IPU_CM_REG(0x00b0) +#define IPU_FS_DISP_FLOW1 IPU_CM_REG(0x00b4) +#define IPU_FS_DISP_FLOW2 IPU_CM_REG(0x00b8) +#define IPU_SKIP IPU_CM_REG(0x00bc) +#define IPU_DISP_ALT_CONF IPU_CM_REG(0x00c0) +#define IPU_DISP_GEN IPU_CM_REG(0x00c4) +#define IPU_DISP_ALT1 IPU_CM_REG(0x00c8) +#define IPU_DISP_ALT2 IPU_CM_REG(0x00cc) +#define IPU_DISP_ALT3 IPU_CM_REG(0x00d0) +#define IPU_DISP_ALT4 IPU_CM_REG(0x00d4) +#define IPU_SNOOP IPU_CM_REG(0x00d8) +#define IPU_MEM_RST IPU_CM_REG(0x00dc) +#define IPU_PM IPU_CM_REG(0x00e0) +#define IPU_GPR IPU_CM_REG(0x00e4) +#define IPU_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0150 + 4 * ((ch) / 32)) +#define IPU_ALT_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0168 + 4 * ((ch) / 32)) +#define IPU_CHA_CUR_BUF(ch) IPU_CM_REG(0x023C + 4 * ((ch) / 32)) +#define IPU_ALT_CUR_BUF0 IPU_CM_REG(0x0244) +#define IPU_ALT_CUR_BUF1 IPU_CM_REG(0x0248) +#define IPU_SRM_STAT IPU_CM_REG(0x024C) +#define IPU_PROC_TASK_STAT IPU_CM_REG(0x0250) +#define IPU_DISP_TASK_STAT IPU_CM_REG(0x0254) +#define IPU_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0268 + 4 * ((ch) / 32)) +#define IPU_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0270 + 4 * ((ch) / 32)) +#define IPU_ALT_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0278 + 4 * ((ch) / 32)) +#define IPU_ALT_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0280 + 4 * ((ch) / 32)) + +#define IPU_INT_CTRL(n) IPU_CM_REG(0x003C + 4 * (n)) +#define IPU_INT_STAT(n) IPU_CM_REG(0x0200 + 4 * (n)) + +#define IPU_DI0_COUNTER_RELEASE (1 << 24) +#define IPU_DI1_COUNTER_RELEASE (1 << 25) + +#define IPU_IDMAC_REG(offset) (offset) + +#define IDMAC_CONF IPU_IDMAC_REG(0x0000) +#define IDMAC_CHA_EN(ch) IPU_IDMAC_REG(0x0004 + 4 * ((ch) / 32)) +#define IDMAC_SEP_ALPHA IPU_IDMAC_REG(0x000c) +#define IDMAC_ALT_SEP_ALPHA IPU_IDMAC_REG(0x0010) +#define IDMAC_CHA_PRI(ch) IPU_IDMAC_REG(0x0014 + 4 * ((ch) / 32)) +#define IDMAC_WM_EN(ch) IPU_IDMAC_REG(0x001c + 4 * ((ch) / 32)) +#define IDMAC_CH_LOCK_EN_1 IPU_IDMAC_REG(0x0024) +#define IDMAC_CH_LOCK_EN_2 IPU_IDMAC_REG(0x0028) +#define IDMAC_SUB_ADDR_0 IPU_IDMAC_REG(0x002c) +#define IDMAC_SUB_ADDR_1 IPU_IDMAC_REG(0x0030) +#define IDMAC_SUB_ADDR_2 IPU_IDMAC_REG(0x0034) +#define IDMAC_BAND_EN(ch) IPU_IDMAC_REG(0x0040 + 4 * ((ch) / 32)) +#define IDMAC_CHA_BUSY(ch) IPU_IDMAC_REG(0x0100 + 4 * ((ch) / 32)) + +#define IPU_NUM_IRQS (32 * 15) + +enum ipu_modules { + IPU_CONF_CSI0_EN = (1 << 0), + IPU_CONF_CSI1_EN = (1 << 1), + IPU_CONF_IC_EN = (1 << 2), + IPU_CONF_ROT_EN = (1 << 3), + IPU_CONF_ISP_EN = (1 << 4), + IPU_CONF_DP_EN = (1 << 5), + IPU_CONF_DI0_EN = (1 << 6), + IPU_CONF_DI1_EN = (1 << 7), + IPU_CONF_SMFC_EN = (1 << 8), + IPU_CONF_DC_EN = (1 << 9), + IPU_CONF_DMFC_EN = (1 << 10), + + IPU_CONF_VDI_EN = (1 << 12), + + IPU_CONF_IDMAC_DIS = (1 << 22), + + IPU_CONF_IC_DMFC_SEL = (1 << 25), + IPU_CONF_IC_DMFC_SYNC = (1 << 26), + IPU_CONF_VDI_DMFC_SYNC = (1 << 27), + + IPU_CONF_CSI0_DATA_SOURCE = (1 << 28), + IPU_CONF_CSI1_DATA_SOURCE = (1 << 29), + IPU_CONF_IC_INPUT = (1 << 30), + IPU_CONF_CSI_SEL = (1 << 31), +}; + +struct ipuv3_channel { + unsigned int num; + + bool enabled; + bool busy; + + struct ipu_soc *ipu; +}; + +struct ipu_dc_priv; +struct ipu_dmfc_priv; +struct ipu_di; +struct ipu_devtype; + +struct ipu_soc { + struct device_d *dev; + const struct ipu_devtype *devtype; + enum ipuv3_type ipu_type; + spinlock_t lock; + struct mutex channel_lock; + + void __iomem *base; + void __iomem *cm_reg; + void __iomem *idmac_reg; + struct ipu_ch_param __iomem *cpmem_base; + + int usecount; + + struct clk *clk; + + struct ipuv3_channel channel[64]; + + int irq_sync; + int irq_err; + struct irq_domain *domain; + + struct ipu_dc_priv *dc_priv; + struct ipu_dp_priv *dp_priv; + struct ipu_dmfc_priv *dmfc_priv; + struct ipu_di *di_priv[2]; +}; + +void ipu_srm_dp_sync_update(struct ipu_soc *ipu); + +int ipu_module_enable(struct ipu_soc *ipu, u32 mask); +int ipu_module_disable(struct ipu_soc *ipu, u32 mask); + +int ipu_di_init(struct ipu_soc *ipu, struct device_d *dev, int id, + void __iomem *base, u32 module, struct clk *ipu_clk); +void ipu_di_exit(struct ipu_soc *ipu, int id); + +int ipu_dmfc_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base, + struct clk *ipu_clk); +void ipu_dmfc_exit(struct ipu_soc *ipu); + +int ipu_dp_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base); +void ipu_dp_exit(struct ipu_soc *ipu); + +int ipu_dc_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base, + void __iomem *template_base); +void ipu_dc_exit(struct ipu_soc *ipu); + +int ipu_cpmem_init(struct ipu_soc *ipu, struct device_d *dev, void __iomem *base); +void ipu_cpmem_exit(struct ipu_soc *ipu); + +#endif /* __IPU_PRV_H__ */ diff --git a/drivers/video/imx-ipu-v3/ipufb.c b/drivers/video/imx-ipu-v3/ipufb.c new file mode 100644 index 0000000000..14a099e14a --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipufb.c @@ -0,0 +1,353 @@ +/* + * Freescale i.MX Frame Buffer device driver + * + * Copyright (C) 2004 Sascha Hauer, Pengutronix + * Based on acornfb.c Copyright (C) Russell King. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ +#define pr_fmt(fmt) "IPU: " fmt + +#include <common.h> +#include <fb.h> +#include <io.h> +#include <driver.h> +#include <malloc.h> +#include <errno.h> +#include <init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <asm-generic/div64.h> +#include <asm/mmu.h> + +#include "imx-ipu-v3.h" +#include "ipuv3-plane.h" + +/* + * These are the bitfields for each + * display depth that we support. + */ +struct ipufb_rgb { + struct fb_bitfield red; + struct fb_bitfield green; + struct fb_bitfield blue; + struct fb_bitfield transp; +}; + +struct ipufb_info { + void __iomem *regs; + + struct clk *ahb_clk; + struct clk *ipg_clk; + struct clk *per_clk; + + struct fb_videomode *mode; + + struct fb_info info; + struct device_d *dev; + + /* plane[0] is the full plane, plane[1] is the partial plane */ + struct ipu_plane *plane[2]; + + void (*enable)(int enable); + + unsigned int di_clkflags; + u32 interface_pix_fmt; + struct ipu_dc *dc; + struct ipu_di *di; + + struct list_head list; + char *name; + int id; + + struct ipu_output *output; +}; + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static LIST_HEAD(ipu_outputs); +static LIST_HEAD(ipu_fbs); + +int ipu_register_output(struct ipu_output *ouput) +{ + list_add_tail(&ouput->list, &ipu_outputs); + + return 0; +} + +static int ipu_register_fb(struct ipufb_info *ipufb) +{ + list_add_tail(&ipufb->list, &ipu_fbs); + + return 0; +} + +int ipu_crtc_mode_set(struct ipufb_info *fbi, + struct fb_videomode *mode, + int x, int y) +{ + struct fb_info *info = &fbi->info; + int ret; + struct ipu_di_signal_cfg sig_cfg = {}; + u32 out_pixel_fmt; + + dev_info(fbi->dev, "%s: mode->xres: %d\n", __func__, + mode->xres); + dev_info(fbi->dev, "%s: mode->yres: %d\n", __func__, + mode->yres); + + out_pixel_fmt = fbi->output->out_pixel_fmt; + + if (mode->sync & FB_SYNC_HOR_HIGH_ACT) + sig_cfg.Hsync_pol = 1; + if (mode->sync & FB_SYNC_VERT_HIGH_ACT) + sig_cfg.Vsync_pol = 1; + + sig_cfg.enable_pol = 1; + sig_cfg.clk_pol = 1; + sig_cfg.width = mode->xres; + sig_cfg.height = mode->yres; + sig_cfg.pixel_fmt = out_pixel_fmt; + sig_cfg.h_start_width = mode->left_margin; + sig_cfg.h_sync_width = mode->hsync_len; + sig_cfg.h_end_width = mode->right_margin; + + sig_cfg.v_start_width = mode->upper_margin; + sig_cfg.v_sync_width = mode->vsync_len; + sig_cfg.v_end_width = mode->lower_margin; + sig_cfg.pixelclock = PICOS2KHZ(mode->pixclock) * 1000UL; + sig_cfg.clkflags = fbi->output->di_clkflags; + + sig_cfg.v_to_h_sync = 0; + + sig_cfg.hsync_pin = 2; + sig_cfg.vsync_pin = 3; + + ret = ipu_dc_init_sync(fbi->dc, fbi->di, sig_cfg.interlaced, + out_pixel_fmt, mode->xres); + if (ret) { + dev_err(fbi->dev, + "initializing display controller failed with %d\n", + ret); + return ret; + } + + ret = ipu_di_init_sync_panel(fbi->di, &sig_cfg); + if (ret) { + dev_err(fbi->dev, + "initializing panel failed with %d\n", ret); + return ret; + } + + ret = ipu_plane_mode_set(fbi->plane[0], mode, info, DRM_FORMAT_XRGB8888, + 0, 0, mode->xres, mode->yres, + x, y, mode->xres, mode->yres); + if (ret) { + dev_err(fbi->dev, "initialising plane failed with %s\n", strerror(-ret)); + return ret; + } + + ret = ipu_di_enable(fbi->di); + if (ret) { + dev_err(fbi->dev, "enabling di failed with %s\n", strerror(-ret)); + return ret; + } + + ipu_dc_enable_channel(fbi->dc); + + ipu_plane_enable(fbi->plane[0]); + + return 0; +} + +static void ipufb_enable_controller(struct fb_info *info) +{ + struct ipufb_info *fbi = container_of(info, struct ipufb_info, info); + struct ipu_output *output = fbi->output; + + if (output->ops->prepare) + output->ops->prepare(output, info->mode, fbi->id); + + ipu_crtc_mode_set(fbi, info->mode, 0, 0); + + if (output->ops->enable) + output->ops->enable(output, info->mode, fbi->id); +} + +static void ipufb_disable_controller(struct fb_info *info) +{ + struct ipufb_info *fbi = container_of(info, struct ipufb_info, info); + struct ipu_output *output = fbi->output; + + if (output->ops->disable) + output->ops->disable(output); + + ipu_plane_disable(fbi->plane[0]); + ipu_dc_disable_channel(fbi->dc); + ipu_di_disable(fbi->di); + + if (output->ops->unprepare) + output->ops->unprepare(output); +} + +static int ipufb_activate_var(struct fb_info *info) +{ + struct ipufb_info *fbi = container_of(info, struct ipufb_info, info); + + info->line_length = info->xres * (info->bits_per_pixel >> 3); + fbi->info.screen_base = dma_alloc_coherent(info->line_length * info->yres); + if (!fbi->info.screen_base) + return -ENOMEM; + + memset(fbi->info.screen_base, 0, info->line_length * info->yres); + + return 0; +} + +static struct fb_ops ipufb_ops = { + .fb_enable = ipufb_enable_controller, + .fb_disable = ipufb_disable_controller, + .fb_activate_var = ipufb_activate_var, +}; + +static struct ipufb_info *ipu_output_find_di(struct ipu_output *output) +{ + struct ipufb_info *ipufb; + + list_for_each_entry(ipufb, &ipu_fbs, list) { + if (!(output->ipu_mask & (1 << ipufb->id))) + continue; + if (ipufb->output) + continue; + + return ipufb; + } + + return NULL; +} + +static int ipu_init(void) +{ + struct ipu_output *output; + struct ipufb_info *ipufb; + int ret; + + list_for_each_entry(output, &ipu_outputs, list) { + pr_info("found output: %s\n", output->name); + ipufb = ipu_output_find_di(output); + if (!ipufb) { + pr_info("no di found for output %s\n", output->name); + continue; + } + pr_info("using di %s for output %s\n", ipufb->name, output->name); + + ipufb->output = output; + + ipufb->info.edid_i2c_adapter = output->edid_i2c_adapter; + if (output->modes) { + ipufb->info.modes.modes = output->modes->modes; + ipufb->info.modes.num_modes = output->modes->num_modes; + } + + ret = register_framebuffer(&ipufb->info); + if (ret < 0) { + dev_err(ipufb->dev, "failed to register framebuffer\n"); + return ret; + } + } + + return 0; +} +late_initcall(ipu_init); + +static int ipu_get_resources(struct ipufb_info *fbi, + struct ipu_client_platformdata *pdata) +{ + struct ipu_soc *ipu = fbi->dev->parent->priv; + int ret; + int dp = -EINVAL; + + fbi->dc = ipu_dc_get(ipu, pdata->dc); + if (IS_ERR(fbi->dc)) { + ret = PTR_ERR(fbi->dc); + goto err_out; + } + + fbi->di = ipu_di_get(ipu, pdata->di); + if (IS_ERR(fbi->di)) { + ret = PTR_ERR(fbi->di); + goto err_out; + } + + if (pdata->dp >= 0) + dp = IPU_DP_FLOW_SYNC_BG; + + fbi->plane[0] = ipu_plane_init(ipu, pdata->dma[0], dp); + ret = ipu_plane_get_resources(fbi->plane[0]); + if (ret) { + dev_err(fbi->dev, "getting plane 0 resources failed with %d.\n", + ret); + goto err_out; + } + + return 0; +err_out: + return ret; +} + +static int ipufb_probe(struct device_d *dev) +{ + struct ipufb_info *fbi; + struct fb_info *info; + int ret, ipuid; + struct ipu_client_platformdata *pdata = dev->platform_data; + struct ipu_rgb *ipu_rgb; + + fbi = xzalloc(sizeof(*fbi)); + info = &fbi->info; + + ipuid = of_alias_get_id(dev->parent->device_node, "ipu"); + fbi->name = asprintf("ipu%d-di%d", ipuid + 1, pdata->di); + fbi->id = ipuid * 2 + pdata->di; + + fbi->dev = dev; + info->priv = fbi; + info->fbops = &ipufb_ops; + + ipu_rgb = drm_fourcc_to_rgb(DRM_FORMAT_RGB888); + + info->bits_per_pixel = 32; + info->red = ipu_rgb->red; + info->green = ipu_rgb->green; + info->blue = ipu_rgb->blue; + info->transp = ipu_rgb->transp; + + dev_dbg(dev, "i.MX Framebuffer driver\n"); + + ret = ipu_get_resources(fbi, pdata); + if (ret) + return ret; + + ret = ipu_register_fb(fbi); + + return ret; +} + +static void ipufb_remove(struct device_d *dev) +{ +} + +static struct driver_d ipufb_driver = { + .name = "imx-ipuv3-crtc", + .probe = ipufb_probe, + .remove = ipufb_remove, +}; +device_platform_driver(ipufb_driver); diff --git a/drivers/video/imx-ipu-v3/ipuv3-plane.c b/drivers/video/imx-ipu-v3/ipuv3-plane.c new file mode 100644 index 0000000000..9dffcfc670 --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipuv3-plane.c @@ -0,0 +1,239 @@ +/* + * i.MX IPUv3 DP Overlay Planes + * + * Copyright (C) 2013 Philipp Zabel, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <common.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <malloc.h> +#include <fb.h> + +#include "ipuv3-plane.h" +#include "imx-ipu-v3.h" + +static int calc_vref(struct fb_videomode *mode) +{ + unsigned long htotal, vtotal; + + htotal = mode->left_margin + mode->right_margin + mode->xres + mode->hsync_len; + vtotal = mode->upper_margin + mode->lower_margin + mode->yres + mode->vsync_len; + + return DIV_ROUND_UP(PICOS2KHZ(mode->pixclock) * 1000, vtotal * htotal); +} + +static inline int calc_bandwidth(int width, int height, unsigned int vref) +{ + return width * height * vref; +} + +int ipu_plane_set_base(struct ipu_plane *ipu_plane, struct fb_info *info, + int x, int y) +{ + struct ipu_ch_param __iomem *cpmem; + + pr_debug("phys = 0x%p, x = %d, y = %d, line_length = %d\n", + info->screen_base, x, y, info->line_length); + + cpmem = ipu_get_cpmem(ipu_plane->ipu_ch); + ipu_cpmem_set_stride(cpmem, info->line_length); + ipu_cpmem_set_buffer(cpmem, 0, (unsigned long)info->screen_base + + info->line_length * y + x); + ipu_cpmem_set_buffer(cpmem, 1, (unsigned long)info->screen_base + + info->line_length * y + x); + + return 0; +} + +int ipu_plane_mode_set(struct ipu_plane *ipu_plane, + struct fb_videomode *mode, + struct fb_info *info, u32 pixel_format, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct ipu_ch_param __iomem *cpmem; + struct device_d *dev = &info->dev; + int ret; + + /* no scaling */ + if (src_w != crtc_w || src_h != crtc_h) + return -EINVAL; + + /* clip to crtc bounds */ + if (crtc_x < 0) { + if (-crtc_x > crtc_w) + return -EINVAL; + src_x += -crtc_x; + src_w -= -crtc_x; + crtc_w -= -crtc_x; + crtc_x = 0; + } + if (crtc_y < 0) { + if (-crtc_y > crtc_h) + return -EINVAL; + src_y += -crtc_y; + src_h -= -crtc_y; + crtc_h -= -crtc_y; + crtc_y = 0; + } + if (crtc_x + crtc_w > mode->xres) { + if (crtc_x > mode->xres) + return -EINVAL; + crtc_w = mode->xres - crtc_x; + src_w = crtc_w; + } + if (crtc_y + crtc_h > mode->yres) { + if (crtc_y > mode->yres) + return -EINVAL; + crtc_h = mode->yres - crtc_y; + src_h = crtc_h; + } + /* full plane minimum width is 13 pixels */ + if (crtc_w < 13 && (ipu_plane->dp_flow != IPU_DP_FLOW_SYNC_FG)) + return -EINVAL; + if (crtc_h < 2) + return -EINVAL; + + switch (ipu_plane->dp_flow) { + case IPU_DP_FLOW_SYNC_BG: + ret = ipu_dp_setup_channel(ipu_plane->dp, + IPUV3_COLORSPACE_RGB, + IPUV3_COLORSPACE_RGB); + if (ret) { + dev_err(dev, + "initializing display processor failed with %d\n", + ret); + return ret; + } + ipu_dp_set_global_alpha(ipu_plane->dp, 1, 0, 1); + break; + case IPU_DP_FLOW_SYNC_FG: + ipu_dp_setup_channel(ipu_plane->dp, + IPUV3_COLORSPACE_RGB, + IPUV3_COLORSPACE_UNKNOWN); + ipu_dp_set_window_pos(ipu_plane->dp, crtc_x, crtc_y); + break; + } + + ret = ipu_dmfc_init_channel(ipu_plane->dmfc, crtc_w); + if (ret) { + dev_err(dev, "initializing dmfc channel failed with %d\n", ret); + return ret; + } + + ret = ipu_dmfc_alloc_bandwidth(ipu_plane->dmfc, + calc_bandwidth(crtc_w, crtc_h, + calc_vref(mode)), 64); + if (ret) { + dev_err(dev, "allocating dmfc bandwidth failed with %d\n", ret); + return ret; + } + + cpmem = ipu_get_cpmem(ipu_plane->ipu_ch); + ipu_ch_param_zero(cpmem); + ipu_cpmem_set_resolution(cpmem, src_w, src_h); + ret = ipu_cpmem_set_fmt(cpmem, pixel_format); + if (ret < 0) { + dev_err(dev, "unsupported pixel format 0x%08x\n", + pixel_format); + return ret; + } + ipu_cpmem_set_high_priority(ipu_plane->ipu_ch); + + ret = ipu_plane_set_base(ipu_plane, info, src_x, src_y); + if (ret < 0) + return ret; + + return 0; +} + +void ipu_plane_put_resources(struct ipu_plane *ipu_plane) +{ + if (!IS_ERR_OR_NULL(ipu_plane->dp)) + ipu_dp_put(ipu_plane->dp); + if (!IS_ERR_OR_NULL(ipu_plane->dmfc)) + ipu_dmfc_put(ipu_plane->dmfc); + if (!IS_ERR_OR_NULL(ipu_plane->ipu_ch)) + ipu_idmac_put(ipu_plane->ipu_ch); +} + +int ipu_plane_get_resources(struct ipu_plane *ipu_plane) +{ + int ret; + + ipu_plane->ipu_ch = ipu_idmac_get(ipu_plane->ipu, ipu_plane->dma); + if (IS_ERR(ipu_plane->ipu_ch)) { + ret = PTR_ERR(ipu_plane->ipu_ch); + pr_err("failed to get idmac channel: %d\n", ret); + return ret; + } + + ipu_plane->dmfc = ipu_dmfc_get(ipu_plane->ipu, ipu_plane->dma); + if (IS_ERR(ipu_plane->dmfc)) { + ret = PTR_ERR(ipu_plane->dmfc); + pr_err("failed to get dmfc: ret %d\n", ret); + goto err_out; + } + + if (ipu_plane->dp_flow >= 0) { + ipu_plane->dp = ipu_dp_get(ipu_plane->ipu, ipu_plane->dp_flow); + if (IS_ERR(ipu_plane->dp)) { + ret = PTR_ERR(ipu_plane->dp); + pr_err("failed to get dp flow: %d\n", ret); + goto err_out; + } + } + + return 0; +err_out: + ipu_plane_put_resources(ipu_plane); + + return ret; +} + +void ipu_plane_enable(struct ipu_plane *ipu_plane) +{ + ipu_dmfc_enable_channel(ipu_plane->dmfc); + ipu_idmac_enable_channel(ipu_plane->ipu_ch); + if (ipu_plane->dp) + ipu_dp_enable_channel(ipu_plane->dp); + + ipu_plane->enabled = true; +} + +void ipu_plane_disable(struct ipu_plane *ipu_plane) +{ + ipu_plane->enabled = false; + + ipu_idmac_wait_busy(ipu_plane->ipu_ch, 50); + + if (ipu_plane->dp) + ipu_dp_disable_channel(ipu_plane->dp); + ipu_idmac_disable_channel(ipu_plane->ipu_ch); + ipu_dmfc_disable_channel(ipu_plane->dmfc); +} + +struct ipu_plane *ipu_plane_init(struct ipu_soc *ipu, + int dma, int dp) +{ + struct ipu_plane *ipu_plane; + + ipu_plane = xzalloc(sizeof(*ipu_plane)); + + ipu_plane->ipu = ipu; + ipu_plane->dma = dma; + ipu_plane->dp_flow = dp; + + return ipu_plane; +} diff --git a/drivers/video/imx-ipu-v3/ipuv3-plane.h b/drivers/video/imx-ipu-v3/ipuv3-plane.h new file mode 100644 index 0000000000..3d52f807fe --- /dev/null +++ b/drivers/video/imx-ipu-v3/ipuv3-plane.h @@ -0,0 +1,51 @@ +#ifndef __IPUV3_PLANE_H__ +#define __IPUV3_PLANE_H__ + +struct drm_plane; +struct drm_device; +struct ipu_soc; +struct drm_crtc; +struct drm_framebuffer; + +struct ipuv3_channel; +struct dmfc_channel; +struct ipu_dp; + +struct ipu_plane { + struct ipu_soc *ipu; + struct ipuv3_channel *ipu_ch; + struct dmfc_channel *dmfc; + struct ipu_dp *dp; + + int dma; + int dp_flow; + + int x; + int y; + + bool enabled; +}; + +struct ipu_plane *ipu_plane_init(struct ipu_soc *ipu, + int dma, int dp); + +/* Init IDMAC, DMFC, DP */ +int ipu_plane_mode_set(struct ipu_plane *ipu_plane, + struct fb_videomode *mode, + struct fb_info *info, u32 pixel_format, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h); + +void ipu_plane_enable(struct ipu_plane *plane); +void ipu_plane_disable(struct ipu_plane *plane); +int ipu_plane_set_base(struct ipu_plane *ipu_plane, struct fb_info *info, + int x, int y); + +int ipu_plane_get_resources(struct ipu_plane *plane); +void ipu_plane_put_resources(struct ipu_plane *plane); + +int ipu_plane_irq(struct ipu_plane *plane); + +#endif diff --git a/drivers/video/imx.c b/drivers/video/imx.c index 05c6105c7a..b12c09c8cf 100644 --- a/drivers/video/imx.c +++ b/drivers/video/imx.c @@ -294,7 +294,7 @@ static int imxfb_activate_var(struct fb_info *info) u32 pcr; int i; - for (i = 0; i < info->num_modes; i++) { + for (i = 0; i < info->modes.num_modes; i++) { if (!strcmp(fbi->mode[i].mode.name, mode->name)) { fbi->pcr = fbi->mode[i].pcr; break; @@ -551,8 +551,8 @@ static int imxfb_probe(struct device_d *dev) fbi->enable = pdata->enable; fbi->dev = dev; info->priv = fbi; - info->mode_list = mode_list; - info->num_modes = pdata->num_modes; + info->modes.modes = mode_list; + info->modes.num_modes = pdata->num_modes; info->mode = &pdata->mode->mode; info->xres = pdata->mode->mode.xres; info->yres = pdata->mode->mode.yres; @@ -580,13 +580,8 @@ static int imxfb_probe(struct device_d *dev) return 0; } -static void imxfb_remove(struct device_d *dev) -{ -} - static struct driver_d imxfb_driver = { .name = "imxfb", .probe = imxfb_probe, - .remove = imxfb_remove, }; device_platform_driver(imxfb_driver); diff --git a/drivers/video/of_display_timing.c b/drivers/video/of_display_timing.c new file mode 100644 index 0000000000..5dfd5b3f1c --- /dev/null +++ b/drivers/video/of_display_timing.c @@ -0,0 +1,238 @@ +/* + * OF helpers for parsing display timings + * + * Copyright (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>, Pengutronix + * + * based on of_videomode.c by Sascha Hauer <s.hauer@pengutronix.de> + * + * This file is released under the GPLv2 + */ +#include <common.h> +#include <of.h> +#include <fb.h> +#include <malloc.h> + +void display_timings_release(struct display_timings *disp) +{ + free(disp->modes); + free(disp); +} +EXPORT_SYMBOL_GPL(display_timings_release); + +/** + * parse_timing_property - parse timing_entry from device_node + * @np: device_node with the property + * @name: name of the property + * @result: will be set to the return value + * + * DESCRIPTION: + * Every display_timing can be specified with either just the typical value or + * a range consisting of min/typ/max. This function helps handling this + **/ +static int parse_timing_property(const struct device_node *np, const char *name, + u32 *res) +{ + struct property *prop; + int length, cells, ret; + + prop = of_find_property(np, name, &length); + if (!prop) { + pr_err("%s: could not find property %s\n", + np->full_name, name); + return -EINVAL; + } + + cells = length / sizeof(u32); + if (cells == 1) { + ret = of_property_read_u32(np, name, res); + } else { + pr_err("%s: illegal timing specification in %s\n", + np->full_name, name); + return -EINVAL; + } + + return ret; +} + +/** + * of_parse_display_timing - parse display_timing entry from device_node + * @np: device_node with the properties + **/ +static int of_parse_display_timing(const struct device_node *np, + struct fb_videomode *mode) +{ + u32 val = 0, pixelclock = 0; + int ret = 0; + + memset(mode, 0, sizeof(*mode)); + + ret |= parse_timing_property(np, "hback-porch", &mode->left_margin); + ret |= parse_timing_property(np, "hfront-porch", &mode->right_margin); + ret |= parse_timing_property(np, "hactive", &mode->xres); + ret |= parse_timing_property(np, "hsync-len", &mode->hsync_len); + ret |= parse_timing_property(np, "vback-porch", &mode->upper_margin); + ret |= parse_timing_property(np, "vfront-porch", &mode->lower_margin); + ret |= parse_timing_property(np, "vactive", &mode->yres); + ret |= parse_timing_property(np, "vsync-len", &mode->vsync_len); + ret |= parse_timing_property(np, "clock-frequency", &pixelclock); + + mode->pixclock = pixelclock ? KHZ2PICOS(pixelclock / 1000) : 0; + + if (!of_property_read_u32(np, "vsync-active", &val)) + mode->sync |= val ? FB_SYNC_VERT_HIGH_ACT : 0; + if (!of_property_read_u32(np, "hsync-active", &val)) + mode->sync |= val ? FB_SYNC_HOR_HIGH_ACT : 0; + if (!of_property_read_u32(np, "de-active", &val)) + mode->display_flags |= val ? DISPLAY_FLAGS_DE_HIGH : + DISPLAY_FLAGS_DE_LOW; + if (!of_property_read_u32(np, "pixelclk-active", &val)) + mode->display_flags |= val ? DISPLAY_FLAGS_PIXDATA_POSEDGE : + DISPLAY_FLAGS_PIXDATA_NEGEDGE; + + if (ret) { + pr_err("%s: error reading timing properties\n", + np->full_name); + return -EINVAL; + } + + return 0; +} + +/** + * of_get_display_timing - parse a display_timing entry + * @np: device_node with the timing subnode + * @name: name of the timing node + * @dt: display_timing struct to fill + **/ +int of_get_display_timing(struct device_node *np, const char *name, + struct fb_videomode *mode) +{ + struct device_node *timing_np; + + if (!np) { + pr_err("%s: no devicenode given\n", np->full_name); + return -EINVAL; + } + + timing_np = of_get_child_by_name(np, name); + if (!timing_np) { + pr_err("%s: could not find node '%s'\n", + np->full_name, name); + return -ENOENT; + } + + return of_parse_display_timing(timing_np, mode); +} +EXPORT_SYMBOL_GPL(of_get_display_timing); + +/** + * of_get_display_timings - parse all display_timing entries from a device_node + * @np: device_node with the subnodes + **/ +struct display_timings *of_get_display_timings(struct device_node *np) +{ + struct device_node *timings_np; + struct device_node *entry; + struct device_node *native_mode; + struct display_timings *disp; + + if (!np) { + pr_err("%s: no device node given\n", np->full_name); + return NULL; + } + + timings_np = of_get_child_by_name(np, "display-timings"); + if (!timings_np) { + pr_debug("%s: could not find display-timings node\n", + np->full_name); + return NULL; + } + + disp = xzalloc(sizeof(*disp)); + + entry = of_parse_phandle(timings_np, "native-mode", 0); + /* assume first child as native mode if none provided */ + if (!entry) + entry = of_get_next_available_child(np, NULL); + /* if there is no child, it is useless to go on */ + if (!entry) { + pr_err("%s: no timing specifications given\n", + np->full_name); + goto entryfail; + } + + pr_debug("%s: using %s as default timing\n", + np->full_name, entry->name); + + native_mode = entry; + + disp->num_modes = of_get_child_count(timings_np); + if (disp->num_modes == 0) { + /* should never happen, as entry was already found above */ + pr_err("%s: no timings specified\n", np->full_name); + goto entryfail; + } + + disp->modes = xzalloc(sizeof(struct fb_videomode) * disp->num_modes); + + disp->num_modes = 0; + disp->native_mode = 0; + + for_each_child_of_node(timings_np, entry) { + struct fb_videomode *mode; + int r; + + mode = &disp->modes[disp->num_modes]; + + r = of_parse_display_timing(entry, mode); + if (r) { + /* + * to not encourage wrong devicetrees, fail in case of + * an error + */ + pr_err("%s: error in timing %d\n", + np->full_name, disp->num_modes + 1); + goto timingfail; + } + + mode->name = xstrdup(entry->name); + + if (native_mode == entry) + disp->native_mode = disp->num_modes; + + disp->num_modes++; + } + + pr_debug("%s: got %d timings. Using timing #%d as default\n", + np->full_name, disp->num_modes, + disp->native_mode + 1); + + return disp; + +timingfail: + display_timings_release(disp); +entryfail: + free(disp); + + return NULL; +} +EXPORT_SYMBOL_GPL(of_get_display_timings); + +/** + * of_display_timings_exist - check if a display-timings node is provided + * @np: device_node with the timing + **/ +int of_display_timings_exist(struct device_node *np) +{ + struct device_node *timings_np; + + if (!np) + return -EINVAL; + + timings_np = of_parse_phandle(np, "display-timings", 0); + if (!timings_np) + return -EINVAL; + + return 1; +} +EXPORT_SYMBOL_GPL(of_display_timings_exist); diff --git a/drivers/video/omap.c b/drivers/video/omap.c index 487aca6f26..485cc7538b 100644 --- a/drivers/video/omap.c +++ b/drivers/video/omap.c @@ -467,8 +467,8 @@ static int omapfb_probe(struct device_d *dev) for (i = 0; i < pdata->num_displays; ++i) fbi->video_modes[i] = pdata->displays[i].mode; - info->mode_list = fbi->video_modes; - info->num_modes = pdata->num_displays; + info->modes.modes = fbi->video_modes; + info->modes.num_modes = pdata->num_displays; info->priv = fbi; info->fbops = &omapfb_ops; diff --git a/drivers/video/pxa.c b/drivers/video/pxa.c index 529190baec..b4ce3a1bf8 100644 --- a/drivers/video/pxa.c +++ b/drivers/video/pxa.c @@ -536,13 +536,8 @@ static int pxafb_probe(struct device_d *dev) return 0; } -static void pxafb_remove(struct device_d *dev) -{ -} - static struct driver_d pxafb_driver = { .name = "pxafb", .probe = pxafb_probe, - .remove = pxafb_remove, }; device_platform_driver(pxafb_driver); diff --git a/drivers/video/s3c24xx.c b/drivers/video/s3c24xx.c index c3e05c6cd7..b1883e232c 100644 --- a/drivers/video/s3c24xx.c +++ b/drivers/video/s3c24xx.c @@ -376,9 +376,9 @@ static int s3cfb_probe(struct device_d *hw_dev) hw_dev->priv = &fbi; /* add runtime video info */ - fbi.info.mode_list = pdata->mode_list; - fbi.info.num_modes = pdata->mode_cnt; - fbi.info.mode = &fbi.info.mode_list[1]; + fbi.info.modes.modes = pdata->mode_list; + fbi.info.modes.num_modes = pdata->mode_cnt; + fbi.info.mode = &fbi.info.modes.modes[0]; fbi.info.xres = fbi.info.mode->xres; fbi.info.yres = fbi.info.mode->yres; if (pdata->bits_per_pixel) diff --git a/drivers/video/sdl.c b/drivers/video/sdl.c index 8dec5e5778..a56834004c 100644 --- a/drivers/video/sdl.c +++ b/drivers/video/sdl.c @@ -37,8 +37,8 @@ static int sdlfb_probe(struct device_d *dev) return -EIO; fb = xzalloc(sizeof(*fb)); - fb->mode_list = fb->mode = dev->platform_data; - fb->num_modes = 1; + fb->modes.modes = fb->mode = dev->platform_data; + fb->modes.num_modes = 1; fb->bits_per_pixel = 4 << 3; fb->xres = fb->mode->xres; fb->yres = fb->mode->yres; diff --git a/drivers/video/stm.c b/drivers/video/stm.c index fdeaf9c645..175e4b611d 100644 --- a/drivers/video/stm.c +++ b/drivers/video/stm.c @@ -499,9 +499,9 @@ static int stmfb_probe(struct device_d *hw_dev) clk_enable(fbi.clk); /* add runtime video info */ - fbi.info.mode_list = pdata->mode_list; - fbi.info.num_modes = pdata->mode_cnt; - fbi.info.mode = &fbi.info.mode_list[0]; + fbi.info.modes.modes = pdata->mode_list; + fbi.info.modes.num_modes = pdata->mode_cnt; + fbi.info.mode = &fbi.info.modes.modes[0]; fbi.info.xres = fbi.info.mode->xres; fbi.info.yres = fbi.info.mode->yres; if (pdata->bits_per_pixel) |