/* * 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 #include #include #include #include #include #include #include #include /* 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); /* * To workaround setting clock rate failure issue * when the system resumes back from PM sleep mode, * we need to get the clock rate before setting it's * rate, otherwise, setting the clock rate will fail. */ clk_get_rate(lvds_phy->phy_clk); 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); usleep_range(500, 1000); 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");