This driver adds support to perform calibration of the MIPI pads for CSI and DSI. Signed-off-by: Thierry Reding <treding@xxxxxxxxxx> --- Changes in v3: - add device tree binding documentation Changes in v2: - rename fields to mirror the names in the TRM - remove unused fields .../bindings/misc/nvidia,tegra114-mipi.txt | 37 ++++ drivers/gpu/host1x/Makefile | 1 + drivers/gpu/host1x/dev.c | 17 +- drivers/gpu/host1x/dev.h | 2 + drivers/gpu/host1x/mipi.c | 230 +++++++++++++++++++++ include/linux/host1x.h | 2 + 6 files changed, 285 insertions(+), 4 deletions(-) create mode 100644 Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt create mode 100644 drivers/gpu/host1x/mipi.c diff --git a/Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt b/Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt new file mode 100644 index 0000000..642c5d8 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/nvidia,tegra114-mipi.txt @@ -0,0 +1,37 @@ +NVIDIA Tegra MIPI pad calibration controller + +Required properties: +- compatible: "nvidia,tegra<chip>-mipi" +- reg: Physical base address and length of the controller's registers. +- clocks: The clock consumed by the controller. +- #calibrate-cells: Should be 1. The cell is a bitmask of the pads that need + to be calibrated by a given device. + +User nodes need to contain a calibrate property that has a phandle to refer +to the calibration controller node and a bitmask of the pads that need to be +calibrated. + +Example: + + mipi: mipi@700e3000 { + compatible = "nvidia,tegra114-mipi"; + reg = <0x700e3000 0x100>; + clocks = <&tegra_car TEGRA114_CLK_MIPI_CAL>; + #calibrate-cells = <1>; + }; + + ... + + host1x@50000000 { + ... + + dsi@54300000 { + ... + + calibrate = <&mipi 0x060>; + + ... + }; + + ... + }; diff --git a/drivers/gpu/host1x/Makefile b/drivers/gpu/host1x/Makefile index afa1e9e..de305c2 100644 --- a/drivers/gpu/host1x/Makefile +++ b/drivers/gpu/host1x/Makefile @@ -7,6 +7,7 @@ host1x-y = \ channel.o \ job.o \ debug.o \ + mipi.o \ hw/host1x01.o \ hw/host1x02.o diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c index ab402a5..12d6333 100644 --- a/drivers/gpu/host1x/dev.c +++ b/drivers/gpu/host1x/dev.c @@ -208,17 +208,26 @@ static int __init tegra_host1x_init(void) return err; err = platform_driver_register(&tegra_host1x_driver); - if (err < 0) { - host1x_bus_exit(); - return err; - } + if (err < 0) + goto unregister_bus; + + err = platform_driver_register(&tegra_mipi_driver); + if (err < 0) + goto unregister_host1x; return 0; + +unregister_host1x: + platform_driver_unregister(&tegra_host1x_driver); +unregister_bus: + host1x_bus_exit(); + return err; } module_init(tegra_host1x_init); static void __exit tegra_host1x_exit(void) { + platform_driver_unregister(&tegra_mipi_driver); platform_driver_unregister(&tegra_host1x_driver); host1x_bus_exit(); } diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h index 6cf689b..65c80dc 100644 --- a/drivers/gpu/host1x/dev.h +++ b/drivers/gpu/host1x/dev.h @@ -304,4 +304,6 @@ static inline void host1x_hw_show_mlocks(struct host1x *host, struct output *o) host->debug_op->show_mlocks(host, o); } +extern struct platform_driver tegra_mipi_driver; + #endif diff --git a/drivers/gpu/host1x/mipi.c b/drivers/gpu/host1x/mipi.c new file mode 100644 index 0000000..8a54613 --- /dev/null +++ b/drivers/gpu/host1x/mipi.c @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2013 NVIDIA Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define MIPI_CAL_CTRL 0x00 +#define MIPI_CAL_CTRL_START (1 << 0) + +#define MIPI_CAL_AUTOCAL_CTRL 0x01 + +#define MIPI_CAL_STATUS 0x02 +#define MIPI_CAL_STATUS_DONE (1 << 16) +#define MIPI_CAL_STATUS_ACTIVE (1 << 0) + +#define MIPI_CAL_CONFIG_CSIA 0x05 +#define MIPI_CAL_CONFIG_CSIB 0x06 +#define MIPI_CAL_CONFIG_CSIC 0x07 +#define MIPI_CAL_CONFIG_CSID 0x08 +#define MIPI_CAL_CONFIG_CSIE 0x09 +#define MIPI_CAL_CONFIG_DSIA 0x0e +#define MIPI_CAL_CONFIG_DSIB 0x0f +#define MIPI_CAL_CONFIG_DSIC 0x10 +#define MIPI_CAL_CONFIG_DSID 0x11 + +#define MIPI_CAL_CONFIG_SELECT (1 << 21) +#define MIPI_CAL_CONFIG_HSPDOS(x) (((x) & 0x1f) << 16) +#define MIPI_CAL_CONFIG_HSPUOS(x) (((x) & 0x1f) << 8) +#define MIPI_CAL_CONFIG_TERMOS(x) (((x) & 0x1f) << 0) + +#define MIPI_CAL_BIAS_PAD_CFG0 0x16 +#define MIPI_CAL_BIAS_PAD_PDVCLAMP (1 << 1) +#define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF (1 << 0) + +#define MIPI_CAL_BIAS_PAD_CFG1 0x17 + +#define MIPI_CAL_BIAS_PAD_CFG2 0x18 +#define MIPI_CAL_BIAS_PAD_PDVREG (1 << 1) + +static const struct module { + unsigned long reg; +} modules[] = { + { .reg = MIPI_CAL_CONFIG_CSIA }, + { .reg = MIPI_CAL_CONFIG_CSIB }, + { .reg = MIPI_CAL_CONFIG_CSIC }, + { .reg = MIPI_CAL_CONFIG_CSID }, + { .reg = MIPI_CAL_CONFIG_CSIE }, + { .reg = MIPI_CAL_CONFIG_DSIA }, + { .reg = MIPI_CAL_CONFIG_DSIB }, + { .reg = MIPI_CAL_CONFIG_DSIC }, + { .reg = MIPI_CAL_CONFIG_DSID }, +}; + +struct tegra_mipi { + void __iomem *regs; + struct mutex lock; + struct clk *clk; +}; + +static inline unsigned long tegra_mipi_readl(struct tegra_mipi *mipi, + unsigned long reg) +{ + return readl(mipi->regs + (reg << 2)); +} + +static inline void tegra_mipi_writel(struct tegra_mipi *mipi, + unsigned long value, unsigned long reg) +{ + writel(value, mipi->regs + (reg << 2)); +} + +int tegra_mipi_calibrate(struct device *device) +{ + struct platform_device *pdev; + unsigned int timeout = 20, i; + struct of_phandle_args args; + unsigned long value, pads; + struct tegra_mipi *mipi; + int err; + + err = of_parse_phandle_with_args(device->of_node, "calibrate", + "#calibrate-cells", 0, &args); + if (err < 0) + return err; + + pdev = of_find_device_by_node(args.np); + if (!pdev) { + of_node_put(args.np); + return -ENODEV; + } + + of_node_put(args.np); + pads = args.args[0]; + + mipi = platform_get_drvdata(pdev); + if (!mipi) { + err = -ENODEV; + goto out; + } + + err = clk_enable(mipi->clk); + if (err < 0) + goto out; + + mutex_lock(&mipi->lock); + + value = tegra_mipi_readl(mipi, MIPI_CAL_BIAS_PAD_CFG0); + value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP; + value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF; + tegra_mipi_writel(mipi, value, MIPI_CAL_BIAS_PAD_CFG0); + + value = tegra_mipi_readl(mipi, MIPI_CAL_BIAS_PAD_CFG2); + value &= ~MIPI_CAL_BIAS_PAD_PDVREG; + tegra_mipi_writel(mipi, value, MIPI_CAL_BIAS_PAD_CFG2); + + for (i = 0; i < ARRAY_SIZE(modules); i++) { + if (pads & BIT(i)) + value = MIPI_CAL_CONFIG_SELECT | + MIPI_CAL_CONFIG_HSPDOS(0) | + MIPI_CAL_CONFIG_HSPUOS(4) | + MIPI_CAL_CONFIG_TERMOS(5); + else + value = 0; + + tegra_mipi_writel(mipi, value, modules[i].reg); + } + + tegra_mipi_writel(mipi, MIPI_CAL_CTRL_START, MIPI_CAL_CTRL); + + while (timeout) { + value = tegra_mipi_readl(mipi, MIPI_CAL_STATUS); + if ((value & MIPI_CAL_STATUS_ACTIVE) == 0 && + (value & MIPI_CAL_STATUS_DONE) != 0) + break; + + usleep_range(10, 100); + timeout--; + } + + mutex_unlock(&mipi->lock); + clk_disable(mipi->clk); + + if (timeout == 0) + err = -ETIMEDOUT; + else + err = 0; + +out: + platform_device_put(pdev); + return err; +} +EXPORT_SYMBOL_GPL(tegra_mipi_calibrate); + +static int tegra_mipi_probe(struct platform_device *pdev) +{ + struct tegra_mipi *mipi; + struct resource *res; + int err; + + mipi = devm_kzalloc(&pdev->dev, sizeof(*mipi), GFP_KERNEL); + if (!mipi) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mipi->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mipi->regs)) + return PTR_ERR(mipi->regs); + + mutex_init(&mipi->lock); + + mipi->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mipi->clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(mipi->clk); + } + + err = clk_prepare(mipi->clk); + if (err < 0) + return err; + + platform_set_drvdata(pdev, mipi); + + return 0; +} + +static int tegra_mipi_remove(struct platform_device *pdev) +{ + struct tegra_mipi *mipi = platform_get_drvdata(pdev); + + clk_unprepare(mipi->clk); + + return 0; +} + +static struct of_device_id tegra_mipi_of_match[] = { + { .compatible = "nvidia,tegra114-mipi", }, + { }, +}; + +struct platform_driver tegra_mipi_driver = { + .driver = { + .name = "tegra-mipi", + .of_match_table = tegra_mipi_of_match, + }, + .probe = tegra_mipi_probe, + .remove = tegra_mipi_remove, +}; diff --git a/include/linux/host1x.h b/include/linux/host1x.h index e62c61a..da6dd01 100644 --- a/include/linux/host1x.h +++ b/include/linux/host1x.h @@ -273,4 +273,6 @@ int host1x_device_exit(struct host1x_device *device); int host1x_client_register(struct host1x_client *client); int host1x_client_unregister(struct host1x_client *client); +int tegra_mipi_calibrate(struct device *device); + #endif -- 1.8.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html