-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
phy: mtk-mipi-csi: add driver for CSI phy
This is a new driver that supports the MIPI CSI CD-PHY version 0.5 The number of PHYs depend on the SoC. Each PHY can support D-PHY only or CD-PHY configuration. The driver supports only D-PHY mode, so CD-PHY compatible PHY are configured in D-PHY mode. [Julien Stephan: simplify driver model: one instance per phy vs one instance for all phys] Signed-off-by: Louis Kuo <louis.kuo@mediatek.com> Signed-off-by: Phi-bang Nguyen <pnguyen@baylibre.com> [Julien Stephan: refactor code] Co-developed-by: Julien Stephan <jstephan@baylibre.com> Signed-off-by: Julien Stephan <jstephan@baylibre.com> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> Link: https://lore.kernel.org/r/20240111101738.468916-1-jstephan@baylibre.com Signed-off-by: Vinod Koul <vkoul@kernel.org>
- Loading branch information
Phi-bang Nguyen
authored and
Vinod Koul
committed
Feb 7, 2024
1 parent
a41baa4
commit 442f34e
Showing
5 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* SPDX-License-Identifier: GPL-2.0 */ | ||
/* | ||
* Copyright (c) 2023, MediaTek Inc. | ||
* Copyright (c) 2023, BayLibre Inc. | ||
*/ | ||
|
||
#ifndef __PHY_MTK_MIPI_CSI_V_0_5_RX_REG_H__ | ||
#define __PHY_MTK_MIPI_CSI_V_0_5_RX_REG_H__ | ||
|
||
/* | ||
* CSI1 and CSI2 are identical, and similar to CSI0. All CSIX macros are | ||
* applicable to the three PHYs. Where differences exist, they are denoted by | ||
* macro names using CSI0 and CSI1, the latter being applicable to CSI1 and | ||
* CSI2 alike. | ||
*/ | ||
|
||
#define MIPI_RX_ANA00_CSIXA 0x0000 | ||
#define RG_CSI0A_CPHY_EN BIT(0) | ||
#define RG_CSIXA_EQ_PROTECT_EN BIT(1) | ||
#define RG_CSIXA_BG_LPF_EN BIT(2) | ||
#define RG_CSIXA_BG_CORE_EN BIT(3) | ||
#define RG_CSIXA_DPHY_L0_CKMODE_EN BIT(5) | ||
#define RG_CSIXA_DPHY_L0_CKSEL BIT(6) | ||
#define RG_CSIXA_DPHY_L1_CKMODE_EN BIT(8) | ||
#define RG_CSIXA_DPHY_L1_CKSEL BIT(9) | ||
#define RG_CSIXA_DPHY_L2_CKMODE_EN BIT(11) | ||
#define RG_CSIXA_DPHY_L2_CKSEL BIT(12) | ||
|
||
#define MIPI_RX_ANA18_CSIXA 0x0018 | ||
#define RG_CSI0A_L0_T0AB_EQ_IS GENMASK(5, 4) | ||
#define RG_CSI0A_L0_T0AB_EQ_BW GENMASK(7, 6) | ||
#define RG_CSI0A_L1_T1AB_EQ_IS GENMASK(21, 20) | ||
#define RG_CSI0A_L1_T1AB_EQ_BW GENMASK(23, 22) | ||
#define RG_CSI0A_L2_T1BC_EQ_IS GENMASK(21, 20) | ||
#define RG_CSI0A_L2_T1BC_EQ_BW GENMASK(23, 22) | ||
#define RG_CSI1A_L0_EQ_IS GENMASK(5, 4) | ||
#define RG_CSI1A_L0_EQ_BW GENMASK(7, 6) | ||
#define RG_CSI1A_L1_EQ_IS GENMASK(21, 20) | ||
#define RG_CSI1A_L1_EQ_BW GENMASK(23, 22) | ||
#define RG_CSI1A_L2_EQ_IS GENMASK(5, 4) | ||
#define RG_CSI1A_L2_EQ_BW GENMASK(7, 6) | ||
|
||
#define MIPI_RX_ANA1C_CSIXA 0x001c | ||
#define MIPI_RX_ANA20_CSI0A 0x0020 | ||
|
||
#define MIPI_RX_ANA24_CSIXA 0x0024 | ||
#define RG_CSIXA_RESERVE GENMASK(31, 24) | ||
|
||
#define MIPI_RX_ANA40_CSIXA 0x0040 | ||
#define RG_CSIXA_CPHY_FMCK_SEL GENMASK(1, 0) | ||
#define RG_CSIXA_ASYNC_OPTION GENMASK(7, 4) | ||
#define RG_CSIXA_CPHY_SPARE GENMASK(31, 16) | ||
|
||
#define MIPI_RX_WRAPPER80_CSIXA 0x0080 | ||
#define CSR_CSI_RST_MODE GENMASK(17, 16) | ||
|
||
#define MIPI_RX_ANAA8_CSIXA 0x00a8 | ||
#define RG_CSIXA_CDPHY_L0_T0_BYTECK_INVERT BIT(0) | ||
#define RG_CSIXA_DPHY_L1_BYTECK_INVERT BIT(1) | ||
#define RG_CSIXA_CDPHY_L2_T1_BYTECK_INVERT BIT(2) | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,294 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* MediaTek MIPI CSI v0.5 driver | ||
* | ||
* Copyright (c) 2023, MediaTek Inc. | ||
* Copyright (c) 2023, BayLibre Inc. | ||
*/ | ||
|
||
#include <dt-bindings/phy/phy.h> | ||
#include <linux/bitfield.h> | ||
#include <linux/delay.h> | ||
#include <linux/io.h> | ||
#include <linux/module.h> | ||
#include <linux/mutex.h> | ||
#include <linux/phy/phy.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/slab.h> | ||
|
||
#include "phy-mtk-io.h" | ||
#include "phy-mtk-mipi-csi-0-5-rx-reg.h" | ||
|
||
#define CSIXB_OFFSET 0x1000 | ||
|
||
struct mtk_mipi_cdphy_port { | ||
struct device *dev; | ||
void __iomem *base; | ||
struct phy *phy; | ||
u32 type; | ||
u32 mode; | ||
u32 num_lanes; | ||
}; | ||
|
||
enum PHY_TYPE { | ||
DPHY = 0, | ||
CPHY, | ||
CDPHY, | ||
}; | ||
|
||
static void mtk_phy_csi_cdphy_ana_eq_tune(void __iomem *base) | ||
{ | ||
mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI0A_L0_T0AB_EQ_IS, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI0A_L0_T0AB_EQ_BW, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA1C_CSIXA, RG_CSI0A_L1_T1AB_EQ_IS, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA1C_CSIXA, RG_CSI0A_L1_T1AB_EQ_BW, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA20_CSI0A, RG_CSI0A_L2_T1BC_EQ_IS, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA20_CSI0A, RG_CSI0A_L2_T1BC_EQ_BW, 1); | ||
|
||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI0A_L0_T0AB_EQ_IS, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI0A_L0_T0AB_EQ_BW, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA1C_CSIXA, RG_CSI0A_L1_T1AB_EQ_IS, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA1C_CSIXA, RG_CSI0A_L1_T1AB_EQ_BW, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA20_CSI0A, RG_CSI0A_L2_T1BC_EQ_IS, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA20_CSI0A, RG_CSI0A_L2_T1BC_EQ_BW, 1); | ||
} | ||
|
||
static void mtk_phy_csi_dphy_ana_eq_tune(void __iomem *base) | ||
{ | ||
mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L0_EQ_IS, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L0_EQ_BW, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L1_EQ_IS, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L1_EQ_BW, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA1C_CSIXA, RG_CSI1A_L2_EQ_IS, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA1C_CSIXA, RG_CSI1A_L2_EQ_BW, 1); | ||
|
||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L0_EQ_IS, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L0_EQ_BW, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L1_EQ_IS, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA18_CSIXA, RG_CSI1A_L1_EQ_BW, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA1C_CSIXA, RG_CSI1A_L2_EQ_IS, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA1C_CSIXA, RG_CSI1A_L2_EQ_BW, 1); | ||
} | ||
|
||
static int mtk_mipi_phy_power_on(struct phy *phy) | ||
{ | ||
struct mtk_mipi_cdphy_port *port = phy_get_drvdata(phy); | ||
void __iomem *base = port->base; | ||
|
||
/* | ||
* The driver currently supports DPHY and CD-PHY phys, | ||
* but the only mode supported is DPHY, | ||
* so CD-PHY capable phys must be configured in DPHY mode | ||
*/ | ||
if (port->type == CDPHY) { | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSI0A_CPHY_EN, 0); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, | ||
RG_CSI0A_CPHY_EN, 0); | ||
} | ||
|
||
/* | ||
* Lane configuration: | ||
* | ||
* Only 4 data + 1 clock is supported for now with the following mapping: | ||
* | ||
* CSIXA_LNR0 --> D2 | ||
* CSIXA_LNR1 --> D0 | ||
* CSIXA_LNR2 --> C | ||
* CSIXB_LNR0 --> D1 | ||
* CSIXB_LNR1 --> D3 | ||
*/ | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L0_CKMODE_EN, 0); | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L0_CKSEL, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L1_CKMODE_EN, 0); | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L1_CKSEL, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L2_CKMODE_EN, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L2_CKSEL, 1); | ||
|
||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, | ||
RG_CSIXA_DPHY_L0_CKMODE_EN, 0); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L0_CKSEL, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, | ||
RG_CSIXA_DPHY_L1_CKMODE_EN, 0); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L1_CKSEL, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, | ||
RG_CSIXA_DPHY_L2_CKMODE_EN, 0); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_DPHY_L2_CKSEL, 1); | ||
|
||
/* Byte clock invert */ | ||
mtk_phy_update_field(base + MIPI_RX_ANAA8_CSIXA, RG_CSIXA_CDPHY_L0_T0_BYTECK_INVERT, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANAA8_CSIXA, RG_CSIXA_DPHY_L1_BYTECK_INVERT, 1); | ||
mtk_phy_update_field(base + MIPI_RX_ANAA8_CSIXA, RG_CSIXA_CDPHY_L2_T1_BYTECK_INVERT, 1); | ||
|
||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANAA8_CSIXA, | ||
RG_CSIXA_CDPHY_L0_T0_BYTECK_INVERT, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANAA8_CSIXA, | ||
RG_CSIXA_DPHY_L1_BYTECK_INVERT, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANAA8_CSIXA, | ||
RG_CSIXA_CDPHY_L2_T1_BYTECK_INVERT, 1); | ||
|
||
/* Start ANA EQ tuning */ | ||
if (port->type == CDPHY) | ||
mtk_phy_csi_cdphy_ana_eq_tune(base); | ||
else | ||
mtk_phy_csi_dphy_ana_eq_tune(base); | ||
|
||
/* End ANA EQ tuning */ | ||
mtk_phy_set_bits(base + MIPI_RX_ANA40_CSIXA, 0x90); | ||
|
||
mtk_phy_update_field(base + MIPI_RX_ANA24_CSIXA, RG_CSIXA_RESERVE, 0x40); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA24_CSIXA, RG_CSIXA_RESERVE, 0x40); | ||
mtk_phy_update_field(base + MIPI_RX_WRAPPER80_CSIXA, CSR_CSI_RST_MODE, 0); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_WRAPPER80_CSIXA, CSR_CSI_RST_MODE, 0); | ||
/* ANA power on */ | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_CORE_EN, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_CORE_EN, 1); | ||
usleep_range(20, 40); | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_LPF_EN, 1); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_LPF_EN, 1); | ||
|
||
return 0; | ||
} | ||
|
||
static int mtk_mipi_phy_power_off(struct phy *phy) | ||
{ | ||
struct mtk_mipi_cdphy_port *port = phy_get_drvdata(phy); | ||
void __iomem *base = port->base; | ||
|
||
/* Disable MIPI BG. */ | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_CORE_EN, 0); | ||
mtk_phy_update_field(base + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_LPF_EN, 0); | ||
|
||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_CORE_EN, 0); | ||
mtk_phy_update_field(base + CSIXB_OFFSET + MIPI_RX_ANA00_CSIXA, RG_CSIXA_BG_LPF_EN, 0); | ||
|
||
return 0; | ||
} | ||
|
||
static struct phy *mtk_mipi_cdphy_xlate(struct device *dev, | ||
struct of_phandle_args *args) | ||
{ | ||
struct mtk_mipi_cdphy_port *priv = dev_get_drvdata(dev); | ||
|
||
/* | ||
* If PHY is CD-PHY then we need to get the operating mode | ||
* For now only D-PHY mode is supported | ||
*/ | ||
if (priv->type == CDPHY) { | ||
if (args->args_count != 1) { | ||
dev_err(dev, "invalid number of arguments\n"); | ||
return ERR_PTR(-EINVAL); | ||
} | ||
switch (args->args[0]) { | ||
case PHY_TYPE_DPHY: | ||
priv->mode = DPHY; | ||
if (priv->num_lanes != 4) { | ||
dev_err(dev, "Only 4D1C mode is supported for now!\n"); | ||
return ERR_PTR(-EINVAL); | ||
} | ||
break; | ||
default: | ||
dev_err(dev, "Unsupported PHY type: %i\n", args->args[0]); | ||
return ERR_PTR(-EINVAL); | ||
} | ||
} else { | ||
if (args->args_count) { | ||
dev_err(dev, "invalid number of arguments\n"); | ||
return ERR_PTR(-EINVAL); | ||
} | ||
priv->mode = DPHY; | ||
} | ||
|
||
return priv->phy; | ||
} | ||
|
||
static const struct phy_ops mtk_cdphy_ops = { | ||
.power_on = mtk_mipi_phy_power_on, | ||
.power_off = mtk_mipi_phy_power_off, | ||
.owner = THIS_MODULE, | ||
}; | ||
|
||
static int mtk_mipi_cdphy_probe(struct platform_device *pdev) | ||
{ | ||
struct device *dev = &pdev->dev; | ||
struct phy_provider *phy_provider; | ||
struct mtk_mipi_cdphy_port *port; | ||
struct phy *phy; | ||
int ret; | ||
u32 phy_type; | ||
|
||
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); | ||
if (!port) | ||
return -ENOMEM; | ||
|
||
dev_set_drvdata(dev, port); | ||
|
||
port->dev = dev; | ||
|
||
port->base = devm_platform_ioremap_resource(pdev, 0); | ||
if (IS_ERR(port->base)) | ||
return PTR_ERR(port->base); | ||
|
||
ret = of_property_read_u32(dev->of_node, "num-lanes", &port->num_lanes); | ||
if (ret) { | ||
dev_err(dev, "Failed to read num-lanes property: %i\n", ret); | ||
return ret; | ||
} | ||
|
||
/* | ||
* phy-type is optional, if not present, PHY is considered to be CD-PHY | ||
*/ | ||
if (device_property_present(dev, "phy-type")) { | ||
ret = of_property_read_u32(dev->of_node, "phy-type", &phy_type); | ||
if (ret) { | ||
dev_err(dev, "Failed to read phy-type property: %i\n", ret); | ||
return ret; | ||
} | ||
switch (phy_type) { | ||
case PHY_TYPE_DPHY: | ||
port->type = DPHY; | ||
break; | ||
default: | ||
dev_err(dev, "Unsupported PHY type: %i\n", phy_type); | ||
return -EINVAL; | ||
} | ||
} else { | ||
port->type = CDPHY; | ||
} | ||
|
||
phy = devm_phy_create(dev, NULL, &mtk_cdphy_ops); | ||
if (IS_ERR(phy)) { | ||
dev_err(dev, "Failed to create PHY: %ld\n", PTR_ERR(phy)); | ||
return PTR_ERR(phy); | ||
} | ||
|
||
port->phy = phy; | ||
phy_set_drvdata(phy, port); | ||
|
||
phy_provider = devm_of_phy_provider_register(dev, mtk_mipi_cdphy_xlate); | ||
if (IS_ERR(phy_provider)) { | ||
dev_err(dev, "Failed to register PHY provider: %ld\n", | ||
PTR_ERR(phy_provider)); | ||
return PTR_ERR(phy_provider); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static const struct of_device_id mtk_mipi_cdphy_of_match[] = { | ||
{ .compatible = "mediatek,mt8365-csi-rx" }, | ||
{ /* sentinel */}, | ||
}; | ||
MODULE_DEVICE_TABLE(of, mtk_mipi_cdphy_of_match); | ||
|
||
static struct platform_driver mipi_cdphy_pdrv = { | ||
.probe = mtk_mipi_cdphy_probe, | ||
.driver = { | ||
.name = "mtk-mipi-csi-0-5", | ||
.of_match_table = mtk_mipi_cdphy_of_match, | ||
}, | ||
}; | ||
module_platform_driver(mipi_cdphy_pdrv); | ||
|
||
MODULE_DESCRIPTION("MediaTek MIPI CSI CD-PHY v0.5 Driver"); | ||
MODULE_AUTHOR("Louis Kuo <louis.kuo@mediatek.com>"); | ||
MODULE_LICENSE("GPL"); |