summaryrefslogtreecommitdiff
path: root/drivers/phy
diff options
context:
space:
mode:
authorLiu Ying <victor.liu@nxp.com>2017-05-23 14:55:17 +0800
committerJason Liu <jason.hui.liu@nxp.com>2019-02-12 10:26:54 +0800
commit9a1aaf9daad436ebefc2ca75d8f688a39e6aee3d (patch)
treeee90b1c8f94b804bd15bddd9006450063d5f029e /drivers/phy
parentd8e089c7a45ce66c34db8682435bf31ad7be148a (diff)
MLK-15001-22 phy: Add Mixel LVDS combo PHY support
This patch adds Mixel LVDS combo PHY support(MIPI DSI and LVDS combo). This LVDS PHY supports one LVDS channel in single mode and two channels in dual mode. Signed-off-by: Liu Ying <victor.liu@nxp.com>
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/Kconfig6
-rw-r--r--drivers/phy/Makefile1
-rw-r--r--drivers/phy/phy-mixel-lvds-combo.c275
3 files changed, 282 insertions, 0 deletions
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index bf60a1a54b0a..97723770da9f 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -46,6 +46,12 @@ config PHY_MIXEL_LVDS
select GENERIC_PHY
default ARCH_FSL_IMX8QM
+config PHY_MIXEL_LVDS_COMBO
+ bool
+ depends on OF
+ select GENERIC_PHY
+ default ARCH_FSL_IMX8QXP
+
source "drivers/phy/allwinner/Kconfig"
source "drivers/phy/amlogic/Kconfig"
source "drivers/phy/broadcom/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index e89204e79c9d..057d768e55d5 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
obj-$(CONFIG_PHY_MIXEL_LVDS) += phy-mixel-lvds.o
+obj-$(CONFIG_PHY_MIXEL_LVDS_COMBO) += phy-mixel-lvds-combo.o
obj-$(CONFIG_ARCH_SUNXI) += allwinner/
obj-$(CONFIG_ARCH_MESON) += amlogic/
obj-$(CONFIG_LANTIQ) += lantiq/
diff --git a/drivers/phy/phy-mixel-lvds-combo.c b/drivers/phy/phy-mixel-lvds-combo.c
new file mode 100644
index 000000000000..0005cf2f6a50
--- /dev/null
+++ b/drivers/phy/phy-mixel-lvds-combo.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2017 NXP
+ *
+ * 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 <linux/clk.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mixel-lvds-combo.h>
+#include <linux/platform_device.h>
+
+/* Control and Status Registers(CSR) */
+#define PHY_CTRL 0x00
+#define CCM(n) (((n) & 0x7) << 5)
+#define CCM_MASK 0xe0
+#define CA(n) (((n) & 0x7) << 2)
+#define CA_MASK 0x1c
+#define RFB BIT(1)
+#define LVDS_EN BIT(0)
+
+#define SS 0x20
+#define CH_HSYNC_M(id) BIT(0 + ((id) * 2))
+#define CH_VSYNC_M(id) BIT(1 + ((id) * 2))
+#define CH_PHSYNC(id) BIT(0 + ((id) * 2))
+#define CH_PVSYNC(id) BIT(1 + ((id) * 2))
+
+#define ULPS 0x30
+#define ULPS_MASK 0x1f
+#define LANE(n) BIT(n)
+
+#define DPI 0x40
+#define COLOR_CODE_MASK 0x7
+#define BIT16_CFG1 0x0
+#define BIT16_CFG2 0x1
+#define BIT16_CFG3 0x2
+#define BIT18_CFG1 0x3
+#define BIT18_CFG2 0x4
+#define BIT24 0x5
+
+/* controller registers */
+#define PD_TX 0x300
+#define PD_PLL 0x31c
+
+struct mixel_lvds_phy {
+ struct device *dev;
+ void __iomem *csr_base;
+ void __iomem *ctrl_base;
+ struct mutex lock;
+ struct phy *phy;
+ struct clk *phy_clk;
+};
+
+static inline u32 phy_csr_read(struct phy *phy, unsigned int reg)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+
+ return readl(lvds_phy->csr_base + reg);
+}
+
+static inline void phy_csr_write(struct phy *phy, u32 value, unsigned int reg)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+
+ writel(value, lvds_phy->csr_base + reg);
+}
+
+static inline u32 phy_ctrl_read(struct phy *phy, unsigned int reg)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+
+ return readl(lvds_phy->ctrl_base + reg);
+}
+
+static inline void phy_ctrl_write(struct phy *phy, u32 value, unsigned int reg)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+
+ writel(value, lvds_phy->ctrl_base + reg);
+}
+
+void mixel_phy_combo_lvds_set_phy_speed(struct phy *phy,
+ unsigned long phy_clk_rate)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+
+ clk_set_rate(lvds_phy->phy_clk, phy_clk_rate);
+}
+EXPORT_SYMBOL_GPL(mixel_phy_combo_lvds_set_phy_speed);
+
+void mixel_phy_combo_lvds_set_hsync_pol(struct phy *phy, bool active_high)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+ u32 val;
+
+ clk_prepare_enable(lvds_phy->phy_clk);
+ mutex_lock(&lvds_phy->lock);
+ val = phy_csr_read(phy, SS);
+ val &= ~(CH_HSYNC_M(0) | CH_HSYNC_M(1));
+ if (active_high)
+ val |= (CH_PHSYNC(0) | CH_PHSYNC(1));
+ phy_csr_write(phy, val, SS);
+ mutex_unlock(&lvds_phy->lock);
+ clk_disable_unprepare(lvds_phy->phy_clk);
+}
+EXPORT_SYMBOL_GPL(mixel_phy_combo_lvds_set_hsync_pol);
+
+void mixel_phy_combo_lvds_set_vsync_pol(struct phy *phy, bool active_high)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+ u32 val;
+
+ clk_prepare_enable(lvds_phy->phy_clk);
+ mutex_lock(&lvds_phy->lock);
+ val = phy_csr_read(phy, SS);
+ val &= ~(CH_VSYNC_M(0) | CH_VSYNC_M(1));
+ if (active_high)
+ val |= (CH_PVSYNC(0) | CH_PVSYNC(1));
+ phy_csr_write(phy, val, SS);
+ mutex_unlock(&lvds_phy->lock);
+ clk_disable_unprepare(lvds_phy->phy_clk);
+}
+EXPORT_SYMBOL_GPL(mixel_phy_combo_lvds_set_vsync_pol);
+
+static int mixel_lvds_combo_phy_init(struct phy *phy)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+ u32 val;
+
+ clk_prepare_enable(lvds_phy->phy_clk);
+ mutex_lock(&lvds_phy->lock);
+ val = phy_csr_read(phy, PHY_CTRL);
+ val &= ~(CCM_MASK | CA_MASK);
+ val |= (CCM(0x5) | CA(0x4) | RFB);
+ phy_csr_write(phy, val, PHY_CTRL);
+
+ val = phy_csr_read(phy, DPI);
+ val &= ~COLOR_CODE_MASK;
+ val |= BIT24;
+ phy_csr_write(phy, val, DPI);
+ mutex_unlock(&lvds_phy->lock);
+ clk_disable_unprepare(lvds_phy->phy_clk);
+
+ return 0;
+}
+
+static int mixel_lvds_combo_phy_power_on(struct phy *phy)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+ u32 val;
+
+ clk_prepare_enable(lvds_phy->phy_clk);
+ mutex_lock(&lvds_phy->lock);
+ phy_ctrl_write(phy, 0, PD_PLL);
+ phy_ctrl_write(phy, 0, PD_TX);
+
+ val = phy_csr_read(phy, ULPS);
+ val &= ~ULPS_MASK;
+ phy_csr_write(phy, val, ULPS);
+
+ val = phy_csr_read(phy, PHY_CTRL);
+ val |= LVDS_EN;
+ phy_csr_write(phy, val, PHY_CTRL);
+ mutex_unlock(&lvds_phy->lock);
+
+ return 0;
+}
+
+static int mixel_lvds_combo_phy_power_off(struct phy *phy)
+{
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+ u32 val;
+
+ mutex_lock(&lvds_phy->lock);
+ val = phy_csr_read(phy, PHY_CTRL);
+ val &= ~LVDS_EN;
+ phy_csr_write(phy, val, PHY_CTRL);
+
+ val = phy_csr_read(phy, ULPS);
+ val |= ULPS_MASK;
+ phy_csr_write(phy, val, ULPS);
+
+ phy_ctrl_write(phy, 1, PD_TX);
+ phy_ctrl_write(phy, 1, PD_PLL);
+ mutex_unlock(&lvds_phy->lock);
+ clk_disable_unprepare(lvds_phy->phy_clk);
+
+ return 0;
+}
+
+static const struct phy_ops mixel_lvds_combo_phy_ops = {
+ .init = mixel_lvds_combo_phy_init,
+ .power_on = mixel_lvds_combo_phy_power_on,
+ .power_off = mixel_lvds_combo_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int mixel_lvds_combo_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct phy_provider *phy_provider;
+ struct mixel_lvds_phy *lvds_phy;
+
+ lvds_phy = devm_kzalloc(dev, sizeof(*lvds_phy), GFP_KERNEL);
+ if (!lvds_phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ lvds_phy->csr_base = devm_ioremap(dev, res->start, SZ_256);
+ if (!lvds_phy->csr_base)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res)
+ return -ENODEV;
+
+ lvds_phy->ctrl_base = devm_ioremap(dev, res->start, SZ_4K);
+ if (!lvds_phy->ctrl_base)
+ return -ENOMEM;
+
+ lvds_phy->phy_clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(lvds_phy->phy_clk)) {
+ dev_err(dev, "cannot get phy clock\n");
+ return PTR_ERR(lvds_phy->phy_clk);
+ }
+
+ lvds_phy->dev = dev;
+ mutex_init(&lvds_phy->lock);
+
+ lvds_phy->phy = devm_phy_create(dev, NULL, &mixel_lvds_combo_phy_ops);
+ if (IS_ERR(lvds_phy->phy)) {
+ dev_err(dev, "failed to create phy\n");
+ return PTR_ERR(lvds_phy->phy);
+ }
+
+ phy_set_drvdata(lvds_phy->phy, lvds_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id mixel_lvds_combo_phy_of_match[] = {
+ { .compatible = "mixel,lvds-combo-phy" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mixel_lvds_combo_phy_of_match);
+
+static struct platform_driver mixel_lvds_combo_phy_driver = {
+ .probe = mixel_lvds_combo_phy_probe,
+ .driver = {
+ .name = "mixel-lvds-combo-phy",
+ .of_match_table = mixel_lvds_combo_phy_of_match,
+ }
+};
+module_platform_driver(mixel_lvds_combo_phy_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("Mixel LVDS combo PHY driver");
+MODULE_LICENSE("GPL v2");