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>
This commit is contained in:
Liu Ying 2019-02-19 11:25:42 +08:00 committed by Dong Aisheng
parent 72c912225c
commit 2738e0707c
4 changed files with 324 additions and 0 deletions

View File

@ -54,6 +54,11 @@ config PHY_MIXEL_LVDS
depends on OF
select GENERIC_PHY
config PHY_MIXEL_LVDS_COMBO
tristate "MIXEL LVDS PHY(LVDS+MIPI DSI combo PHY) support"
depends on OF
select GENERIC_PHY
source "drivers/phy/allwinner/Kconfig"
source "drivers/phy/amlogic/Kconfig"
source "drivers/phy/broadcom/Kconfig"

View File

@ -9,6 +9,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_ARCH_MEDIATEK) += mediatek/

View File

@ -0,0 +1,278 @@
/*
* Copyright 2017-2019 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/delay.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, unsigned int reg, u32 value)
{
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, unsigned int reg, u32 value)
{
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, SS, val);
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, SS, val);
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, PHY_CTRL, val);
val = phy_csr_read(phy, DPI);
val &= ~COLOR_CODE_MASK;
val |= BIT24;
phy_csr_write(phy, DPI, val);
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, PD_PLL, 0);
phy_ctrl_write(phy, PD_TX, 0);
val = phy_csr_read(phy, ULPS);
val &= ~ULPS_MASK;
phy_csr_write(phy, ULPS, val);
val = phy_csr_read(phy, PHY_CTRL);
val |= LVDS_EN;
phy_csr_write(phy, PHY_CTRL, val);
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, PHY_CTRL, val);
val = phy_csr_read(phy, ULPS);
val |= ULPS_MASK;
phy_csr_write(phy, ULPS, val);
phy_ctrl_write(phy, PD_TX, 1);
phy_ctrl_write(phy, PD_PLL, 1);
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");

View File

@ -0,0 +1,40 @@
/*
* Copyright 2017-2019 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.
*/
#ifndef PHY_MIXEL_LVDS_COMBO_H_
#define PHY_MIXEL_LVDS_COMBO_H_
#include "phy.h"
#if IS_ENABLED(CONFIG_PHY_MIXEL_LVDS_COMBO)
void mixel_phy_combo_lvds_set_phy_speed(struct phy *phy,
unsigned long phy_clk_rate);
void mixel_phy_combo_lvds_set_hsync_pol(struct phy *phy, bool active_high);
void mixel_phy_combo_lvds_set_vsync_pol(struct phy *phy, bool active_high);
#else
static inline void
mixel_phy_combo_lvds_set_phy_speed(struct phy *phy, unsigned long phy_clk_rate)
{
}
static inline void mixel_phy_combo_lvds_set_hsync_pol(struct phy *phy,
bool active_high)
{
}
static inline void mixel_phy_combo_lvds_set_vsync_pol(struct phy *phy,
bool active_high)
{
}
#endif
#endif /* PHY_MIXEL_LVDS_COMBO_H_ */