From c80c7691570f23cfa36f23b7443ce1eafedde0c2 Mon Sep 17 00:00:00 2001 From: Wyon Bi Date: Mon, 4 Jun 2018 11:20:16 +0800 Subject: [PATCH] phy/rockchip: Add support for INNOSILICON LVDS/TTL PHY Innosilicon LVDS/TTL PHY implements LVDS TIA/EIA protocol. Normally, Innosilicon LVDS/TTL PHY contains four 7-bit parallel-load serial-out shift registers, a 7X clock PLL, and five Low-Voltage Differential Signaling (LVDS) line drivers in a single integrated circuit. These functions allow 28 bits of single-ended LVTTL data to be synchronously transmitted over five balanced-pair conductors for receipt by a compatible receiver. In addition, Innosilicon LVDS/TTL PHY could extend from 4 lanes to N lanes (N is required by the customer). Therefore, the TTL lines extend respectively. Change-Id: Ib48537c49dec919e2ed5bc6347217fe83be07371 Signed-off-by: Wyon Bi --- drivers/phy/rockchip/Kconfig | 8 + drivers/phy/rockchip/Makefile | 1 + .../rockchip/phy-rockchip-inno-video-phy.c | 258 ++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 drivers/phy/rockchip/phy-rockchip-inno-video-phy.c diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index b44d71fb4d8e..328d451b40f9 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -73,6 +73,14 @@ config PHY_ROCKCHIP_INNO_HDMI_PHY help Enable this to support the Rockchip HDMI PHY with Innosilicon IP block. +config PHY_ROCKCHIP_INNO_VIDEO_PHY + tristate "Rockchip INNO LVDS/TTL PHY driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Enable this to support the Rockchip LVDS/TTL PHY with + Innosilicon IP block. + config PHY_ROCKCHIP_INNO_VIDEO_COMBO_PHY tristate "Rockchip INNO MIPI/LVDS/TTL PHY driver" depends on ARCH_ROCKCHIP && OF diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile index d351e3835259..6fff7b5ba39f 100644 --- a/drivers/phy/rockchip/Makefile +++ b/drivers/phy/rockchip/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB3) += phy-rockchip-inno-usb3.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_MIPI_DPHY) += phy-rockchip-inno-mipi-dphy.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI_PHY) += phy-rockchip-inno-hdmi-phy.o +obj-$(CONFIG_PHY_ROCKCHIP_INNO_VIDEO_PHY) += phy-rockchip-inno-video-phy.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_VIDEO_COMBO_PHY) += phy-rockchip-inno-video-combo-phy.o obj-$(CONFIG_PHY_ROCKCHIP_MIPI_RX) += phy-rockchip-mipi-rx.o obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o diff --git a/drivers/phy/rockchip/phy-rockchip-inno-video-phy.c b/drivers/phy/rockchip/phy-rockchip-inno-video-phy.c new file mode 100644 index 000000000000..65b152535870 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-inno-video-phy.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Rockchip Electronics Co. Ltd. + * + * Author: Wyon Bi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 0x0030 */ +#define DISABLE_PLL BIT(3) +/* 0x003c */ +#define PLL_LOCK BIT(1) +/* 0x0084 */ +#define ENABLE_TX BIT(7) + +struct inno_video_phy { + struct device *dev; + struct clk *pclk; + struct regmap *regmap; + struct reset_control *rst; + enum phy_mode mode; +}; + +static const struct reg_sequence ttl_mode[] = { + { 0x0000, 0x7f }, + { 0x0004, 0x3f }, + { 0x0008, 0x80 }, + { 0x0010, 0x3f }, + { 0x0014, 0x3f }, + { 0x0080, 0x44 }, + + { 0x0100, 0x7f }, + { 0x0104, 0x3f }, + { 0x0108, 0x80 }, + { 0x0110, 0x3f }, + { 0x0114, 0x3f }, + { 0x0180, 0x44 }, +}; + +static const struct reg_sequence lvds_mode_single_channel[] = { + { 0x0000, 0xbf }, + { 0x0004, 0x3f }, + { 0x0008, 0xfe }, + { 0x0010, 0x00 }, + { 0x0014, 0x00 }, + { 0x0080, 0x44 }, + + { 0x0100, 0x00 }, + { 0x0104, 0x00 }, + { 0x0108, 0x00 }, + { 0x0110, 0x00 }, + { 0x0114, 0x00 }, + { 0x0180, 0x44 }, +}; + +static const struct reg_sequence lvds_mode_dual_channel[] = { + { 0x0000, 0xbf }, + { 0x0004, 0x3f }, + { 0x0008, 0xfe }, + { 0x0010, 0x00 }, + { 0x0014, 0x00 }, + { 0x0080, 0x44 }, + + { 0x0100, 0xbf }, + { 0x0104, 0x3f }, + { 0x0108, 0xfe }, + { 0x0110, 0x00 }, + { 0x0114, 0x00 }, + { 0x0180, 0x44 }, +}; + +static int inno_video_phy_power_on(struct phy *phy) +{ + struct inno_video_phy *inno = phy_get_drvdata(phy); + const struct reg_sequence *wseq; + bool dual_channel = phy_get_bus_width(phy) == 2 ? true : false; + int nregs; + u32 status; + int ret; + + clk_prepare_enable(inno->pclk); + pm_runtime_get_sync(inno->dev); + + switch (inno->mode) { + case PHY_MODE_VIDEO_LVDS: + if (dual_channel) { + wseq = lvds_mode_dual_channel; + nregs = ARRAY_SIZE(lvds_mode_dual_channel); + } else { + wseq = lvds_mode_single_channel; + nregs = ARRAY_SIZE(lvds_mode_single_channel); + } + break; + case PHY_MODE_VIDEO_TTL: + wseq = ttl_mode; + nregs = ARRAY_SIZE(ttl_mode); + break; + default: + return -EINVAL; + } + + regmap_multi_reg_write(inno->regmap, wseq, nregs); + + regmap_update_bits(inno->regmap, 0x0030, DISABLE_PLL, 0); + ret = regmap_read_poll_timeout(inno->regmap, 0x003c, status, + status & PLL_LOCK, 50, 5000); + if (ret) { + dev_err(inno->dev, "PLL is not lock\n"); + return ret; + } + + regmap_update_bits(inno->regmap, 0x0084, ENABLE_TX, ENABLE_TX); + + return 0; +} + +static int inno_video_phy_power_off(struct phy *phy) +{ + struct inno_video_phy *inno = phy_get_drvdata(phy); + + regmap_update_bits(inno->regmap, 0x0084, ENABLE_TX, 0); + regmap_update_bits(inno->regmap, 0x0030, DISABLE_PLL, DISABLE_PLL); + + pm_runtime_put(inno->dev); + clk_disable_unprepare(inno->pclk); + + return 0; +} + +static int inno_video_phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct inno_video_phy *inno = phy_get_drvdata(phy); + + switch (mode) { + case PHY_MODE_VIDEO_LVDS: + case PHY_MODE_VIDEO_TTL: + inno->mode = mode; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct phy_ops inno_video_phy_ops = { + .set_mode = inno_video_phy_set_mode, + .power_on = inno_video_phy_power_on, + .power_off = inno_video_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct regmap_config inno_video_phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x0180, +}; + +static int inno_video_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct inno_video_phy *inno; + struct phy *phy; + struct phy_provider *phy_provider; + struct resource *res; + void __iomem *regs; + int ret; + + inno = devm_kzalloc(dev, sizeof(*inno), GFP_KERNEL); + if (!inno) + return -ENOMEM; + + inno->dev = dev; + platform_set_drvdata(pdev, inno); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + inno->regmap = devm_regmap_init_mmio(dev, regs, + &inno_video_phy_regmap_config); + if (IS_ERR(inno->regmap)) { + ret = PTR_ERR(inno->regmap); + dev_err(dev, "failed to init regmap: %d\n", ret); + return ret; + } + + inno->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(inno->pclk)) { + dev_err(dev, "failed to get pclk\n"); + return PTR_ERR(inno->pclk); + } + + inno->rst = devm_reset_control_get(dev, "rst"); + if (IS_ERR(inno->rst)) { + dev_err(dev, "failed to get reset control\n"); + return PTR_ERR(inno->rst); + } + + phy = devm_phy_create(dev, NULL, &inno_video_phy_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + dev_err(dev, "failed to create PHY: %d\n", ret); + return ret; + } + + phy_set_drvdata(phy, inno); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + pm_runtime_enable(dev); + + return 0; +} + +static int inno_video_phy_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id inno_video_phy_of_match[] = { + { .compatible = "rockchip,rk3288-video-phy", }, + {} +}; +MODULE_DEVICE_TABLE(of, inno_video_phy_of_match); + +static struct platform_driver inno_video_phy_driver = { + .driver = { + .name = "inno-video-phy", + .of_match_table = of_match_ptr(inno_video_phy_of_match), + }, + .probe = inno_video_phy_probe, + .remove = inno_video_phy_remove, +}; +module_platform_driver(inno_video_phy_driver); + +MODULE_AUTHOR("Wyon Bi "); +MODULE_DESCRIPTION("Innosilicon LVDS/TTL PHY driver"); +MODULE_LICENSE("GPL v2");