1
0
Fork 0

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>
pull/10/head
Liu Ying 2017-05-23 14:55:17 +08:00 committed by Jason Liu
parent 62bf92e109
commit 9477c47fd0
5 changed files with 340 additions and 0 deletions

View File

@ -0,0 +1,20 @@
Mixel LVDS combo PHY
Required properties:
- compatible: must be "mixel,lvds-combo-phy".
- reg: offset and length of the register block.
- #phy-cells: see phy-bindings.txt in the same directory, must be <0>.
- clocks: clock phandle and specifier pair.
- clock-names: string, clock input name, must be "phy".
- power-domains: phandle pointing to power domain.
Example:
ldb1_phy: ldb_phy@56221000 {
compatible = "mixel,lvds-combo-phy";
reg = <0x0 0x56221000 0x0 0x100>, <0x0 0x56228000 0x0 0x1000>;
#phy-cells = <0>;
clocks = <&clk IMX8QXP_MIPI0_LVDS_PHY_CLK>;
clock-names = "phy";
power-domains = <&pd_mipi_dsi0>;
status = "disabled";
};

View File

@ -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"

View File

@ -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/

View File

@ -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");

View File

@ -0,0 +1,38 @@
/*
* 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.
*/
#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
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)
{
}
#endif
#endif /* PHY_MIXEL_LVDS_COMBO_H_ */