1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Meson G12A MIPI DSI Analog PHY |
4 | * |
5 | * Copyright (C) 2018 Amlogic, Inc. All rights reserved |
6 | * Copyright (C) 2022 BayLibre, SAS |
7 | * Author: Neil Armstrong <narmstrong@baylibre.com> |
8 | */ |
9 | #include <linux/bitfield.h> |
10 | #include <linux/bitops.h> |
11 | #include <linux/module.h> |
12 | #include <linux/phy/phy.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/mfd/syscon.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <dt-bindings/phy/phy.h> |
19 | |
20 | #define HHI_MIPI_CNTL0 0x00 |
21 | #define HHI_MIPI_CNTL0_DIF_REF_CTL1 GENMASK(31, 16) |
22 | #define HHI_MIPI_CNTL0_DIF_REF_CTL0 GENMASK(15, 0) |
23 | |
24 | #define HHI_MIPI_CNTL1 0x04 |
25 | #define HHI_MIPI_CNTL1_BANDGAP BIT(16) |
26 | #define HHI_MIPI_CNTL2_DIF_REF_CTL2 GENMASK(15, 0) |
27 | |
28 | #define HHI_MIPI_CNTL2 0x08 |
29 | #define HHI_MIPI_CNTL2_DIF_TX_CTL1 GENMASK(31, 16) |
30 | #define HHI_MIPI_CNTL2_CH_EN GENMASK(15, 11) |
31 | #define HHI_MIPI_CNTL2_DIF_TX_CTL0 GENMASK(10, 0) |
32 | |
33 | #define DSI_LANE_0 BIT(4) |
34 | #define DSI_LANE_1 BIT(3) |
35 | #define DSI_LANE_CLK BIT(2) |
36 | #define DSI_LANE_2 BIT(1) |
37 | #define DSI_LANE_3 BIT(0) |
38 | |
39 | struct phy_g12a_mipi_dphy_analog_priv { |
40 | struct phy *phy; |
41 | struct regmap *regmap; |
42 | struct phy_configure_opts_mipi_dphy config; |
43 | }; |
44 | |
45 | static int phy_g12a_mipi_dphy_analog_configure(struct phy *phy, |
46 | union phy_configure_opts *opts) |
47 | { |
48 | struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy); |
49 | int ret; |
50 | |
51 | ret = phy_mipi_dphy_config_validate(cfg: &opts->mipi_dphy); |
52 | if (ret) |
53 | return ret; |
54 | |
55 | memcpy(&priv->config, opts, sizeof(priv->config)); |
56 | |
57 | return 0; |
58 | } |
59 | |
60 | static int phy_g12a_mipi_dphy_analog_power_on(struct phy *phy) |
61 | { |
62 | struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy); |
63 | unsigned int reg; |
64 | |
65 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL0, |
66 | FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL0, 0x8) | |
67 | FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0xa487)); |
68 | |
69 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL1, |
70 | FIELD_PREP(HHI_MIPI_CNTL2_DIF_REF_CTL2, 0x2e) | |
71 | HHI_MIPI_CNTL1_BANDGAP); |
72 | |
73 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL2, |
74 | FIELD_PREP(HHI_MIPI_CNTL2_DIF_TX_CTL0, 0x45a) | |
75 | FIELD_PREP(HHI_MIPI_CNTL2_DIF_TX_CTL1, 0x2680)); |
76 | |
77 | reg = DSI_LANE_CLK; |
78 | switch (priv->config.lanes) { |
79 | case 4: |
80 | reg |= DSI_LANE_3; |
81 | fallthrough; |
82 | case 3: |
83 | reg |= DSI_LANE_2; |
84 | fallthrough; |
85 | case 2: |
86 | reg |= DSI_LANE_1; |
87 | fallthrough; |
88 | case 1: |
89 | reg |= DSI_LANE_0; |
90 | break; |
91 | default: |
92 | reg = 0; |
93 | } |
94 | |
95 | regmap_update_bits(map: priv->regmap, HHI_MIPI_CNTL2, |
96 | HHI_MIPI_CNTL2_CH_EN, |
97 | FIELD_PREP(HHI_MIPI_CNTL2_CH_EN, reg)); |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | static int phy_g12a_mipi_dphy_analog_power_off(struct phy *phy) |
103 | { |
104 | struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy); |
105 | |
106 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL0, val: 0); |
107 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL1, val: 0); |
108 | regmap_write(map: priv->regmap, HHI_MIPI_CNTL2, val: 0); |
109 | |
110 | return 0; |
111 | } |
112 | |
113 | static const struct phy_ops phy_g12a_mipi_dphy_analog_ops = { |
114 | .configure = phy_g12a_mipi_dphy_analog_configure, |
115 | .power_on = phy_g12a_mipi_dphy_analog_power_on, |
116 | .power_off = phy_g12a_mipi_dphy_analog_power_off, |
117 | .owner = THIS_MODULE, |
118 | }; |
119 | |
120 | static int phy_g12a_mipi_dphy_analog_probe(struct platform_device *pdev) |
121 | { |
122 | struct phy_provider *phy; |
123 | struct device *dev = &pdev->dev; |
124 | struct phy_g12a_mipi_dphy_analog_priv *priv; |
125 | struct device_node *np = dev->of_node, *parent_np; |
126 | struct regmap *map; |
127 | |
128 | priv = devm_kmalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
129 | if (!priv) |
130 | return -ENOMEM; |
131 | |
132 | /* Get the hhi system controller node */ |
133 | parent_np = of_get_parent(node: np); |
134 | map = syscon_node_to_regmap(np: parent_np); |
135 | of_node_put(node: parent_np); |
136 | if (IS_ERR(ptr: map)) |
137 | return dev_err_probe(dev, err: PTR_ERR(ptr: map), fmt: "failed to get HHI regmap\n" ); |
138 | |
139 | priv->regmap = map; |
140 | |
141 | priv->phy = devm_phy_create(dev, node: np, ops: &phy_g12a_mipi_dphy_analog_ops); |
142 | if (IS_ERR(ptr: priv->phy)) |
143 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->phy), fmt: "failed to create PHY\n" ); |
144 | |
145 | phy_set_drvdata(phy: priv->phy, data: priv); |
146 | dev_set_drvdata(dev, data: priv); |
147 | |
148 | phy = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
149 | |
150 | return PTR_ERR_OR_ZERO(ptr: phy); |
151 | } |
152 | |
153 | static const struct of_device_id phy_g12a_mipi_dphy_analog_of_match[] = { |
154 | { |
155 | .compatible = "amlogic,g12a-mipi-dphy-analog" , |
156 | }, |
157 | { /* sentinel */ } |
158 | }; |
159 | MODULE_DEVICE_TABLE(of, phy_g12a_mipi_dphy_analog_of_match); |
160 | |
161 | static struct platform_driver phy_g12a_mipi_dphy_analog_driver = { |
162 | .probe = phy_g12a_mipi_dphy_analog_probe, |
163 | .driver = { |
164 | .name = "phy-meson-g12a-mipi-dphy-analog" , |
165 | .of_match_table = phy_g12a_mipi_dphy_analog_of_match, |
166 | }, |
167 | }; |
168 | module_platform_driver(phy_g12a_mipi_dphy_analog_driver); |
169 | |
170 | MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>" ); |
171 | MODULE_DESCRIPTION("Meson G12A MIPI Analog D-PHY driver" ); |
172 | MODULE_LICENSE("GPL v2" ); |
173 | |