1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2021 Linaro Ltd. |
4 | * Author: Sam Protsenko <semen.protsenko@linaro.org> |
5 | * |
6 | * Samsung Exynos USI driver (Universal Serial Interface). |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/mfd/syscon.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_platform.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regmap.h> |
16 | |
17 | #include <dt-bindings/soc/samsung,exynos-usi.h> |
18 | |
19 | /* USIv2: System Register: SW_CONF register bits */ |
20 | #define USI_V2_SW_CONF_NONE 0x0 |
21 | #define USI_V2_SW_CONF_UART BIT(0) |
22 | #define USI_V2_SW_CONF_SPI BIT(1) |
23 | #define USI_V2_SW_CONF_I2C BIT(2) |
24 | #define USI_V2_SW_CONF_MASK (USI_V2_SW_CONF_UART | USI_V2_SW_CONF_SPI | \ |
25 | USI_V2_SW_CONF_I2C) |
26 | |
27 | /* USIv2: USI register offsets */ |
28 | #define USI_CON 0x04 |
29 | #define USI_OPTION 0x08 |
30 | |
31 | /* USIv2: USI register bits */ |
32 | #define USI_CON_RESET BIT(0) |
33 | #define USI_OPTION_CLKREQ_ON BIT(1) |
34 | #define USI_OPTION_CLKSTOP_ON BIT(2) |
35 | |
36 | enum exynos_usi_ver { |
37 | USI_VER2 = 2, |
38 | }; |
39 | |
40 | struct exynos_usi_variant { |
41 | enum exynos_usi_ver ver; /* USI IP-core version */ |
42 | unsigned int sw_conf_mask; /* SW_CONF mask for all protocols */ |
43 | size_t min_mode; /* first index in exynos_usi_modes[] */ |
44 | size_t max_mode; /* last index in exynos_usi_modes[] */ |
45 | size_t num_clks; /* number of clocks to assert */ |
46 | const char * const *clk_names; /* clock names to assert */ |
47 | }; |
48 | |
49 | struct exynos_usi { |
50 | struct device *dev; |
51 | void __iomem *regs; /* USI register map */ |
52 | struct clk_bulk_data *clks; /* USI clocks */ |
53 | |
54 | size_t mode; /* current USI SW_CONF mode index */ |
55 | bool clkreq_on; /* always provide clock to IP */ |
56 | |
57 | /* System Register */ |
58 | struct regmap *sysreg; /* System Register map */ |
59 | unsigned int sw_conf; /* SW_CONF register offset in sysreg */ |
60 | |
61 | const struct exynos_usi_variant *data; |
62 | }; |
63 | |
64 | struct exynos_usi_mode { |
65 | const char *name; /* mode name */ |
66 | unsigned int val; /* mode register value */ |
67 | }; |
68 | |
69 | static const struct exynos_usi_mode exynos_usi_modes[] = { |
70 | [USI_V2_NONE] = { .name = "none" , .val = USI_V2_SW_CONF_NONE }, |
71 | [USI_V2_UART] = { .name = "uart" , .val = USI_V2_SW_CONF_UART }, |
72 | [USI_V2_SPI] = { .name = "spi" , .val = USI_V2_SW_CONF_SPI }, |
73 | [USI_V2_I2C] = { .name = "i2c" , .val = USI_V2_SW_CONF_I2C }, |
74 | }; |
75 | |
76 | static const char * const exynos850_usi_clk_names[] = { "pclk" , "ipclk" }; |
77 | static const struct exynos_usi_variant exynos850_usi_data = { |
78 | .ver = USI_VER2, |
79 | .sw_conf_mask = USI_V2_SW_CONF_MASK, |
80 | .min_mode = USI_V2_NONE, |
81 | .max_mode = USI_V2_I2C, |
82 | .num_clks = ARRAY_SIZE(exynos850_usi_clk_names), |
83 | .clk_names = exynos850_usi_clk_names, |
84 | }; |
85 | |
86 | static const struct of_device_id exynos_usi_dt_match[] = { |
87 | { |
88 | .compatible = "samsung,exynos850-usi" , |
89 | .data = &exynos850_usi_data, |
90 | }, |
91 | { } /* sentinel */ |
92 | }; |
93 | MODULE_DEVICE_TABLE(of, exynos_usi_dt_match); |
94 | |
95 | /** |
96 | * exynos_usi_set_sw_conf - Set USI block configuration mode |
97 | * @usi: USI driver object |
98 | * @mode: Mode index |
99 | * |
100 | * Select underlying serial protocol (UART/SPI/I2C) in USI IP-core. |
101 | * |
102 | * Return: 0 on success, or negative error code on failure. |
103 | */ |
104 | static int exynos_usi_set_sw_conf(struct exynos_usi *usi, size_t mode) |
105 | { |
106 | unsigned int val; |
107 | int ret; |
108 | |
109 | if (mode < usi->data->min_mode || mode > usi->data->max_mode) |
110 | return -EINVAL; |
111 | |
112 | val = exynos_usi_modes[mode].val; |
113 | ret = regmap_update_bits(map: usi->sysreg, reg: usi->sw_conf, |
114 | mask: usi->data->sw_conf_mask, val); |
115 | if (ret) |
116 | return ret; |
117 | |
118 | usi->mode = mode; |
119 | dev_dbg(usi->dev, "protocol: %s\n" , exynos_usi_modes[usi->mode].name); |
120 | |
121 | return 0; |
122 | } |
123 | |
124 | /** |
125 | * exynos_usi_enable - Initialize USI block |
126 | * @usi: USI driver object |
127 | * |
128 | * USI IP-core start state is "reset" (on startup and after CPU resume). This |
129 | * routine enables the USI block by clearing the reset flag. It also configures |
130 | * HWACG behavior (needed e.g. for UART Rx). It should be performed before |
131 | * underlying protocol becomes functional. |
132 | * |
133 | * Return: 0 on success, or negative error code on failure. |
134 | */ |
135 | static int exynos_usi_enable(const struct exynos_usi *usi) |
136 | { |
137 | u32 val; |
138 | int ret; |
139 | |
140 | ret = clk_bulk_prepare_enable(num_clks: usi->data->num_clks, clks: usi->clks); |
141 | if (ret) |
142 | return ret; |
143 | |
144 | /* Enable USI block */ |
145 | val = readl(addr: usi->regs + USI_CON); |
146 | val &= ~USI_CON_RESET; |
147 | writel(val, addr: usi->regs + USI_CON); |
148 | udelay(1); |
149 | |
150 | /* Continuously provide the clock to USI IP w/o gating */ |
151 | if (usi->clkreq_on) { |
152 | val = readl(addr: usi->regs + USI_OPTION); |
153 | val &= ~USI_OPTION_CLKSTOP_ON; |
154 | val |= USI_OPTION_CLKREQ_ON; |
155 | writel(val, addr: usi->regs + USI_OPTION); |
156 | } |
157 | |
158 | clk_bulk_disable_unprepare(num_clks: usi->data->num_clks, clks: usi->clks); |
159 | |
160 | return ret; |
161 | } |
162 | |
163 | static int exynos_usi_configure(struct exynos_usi *usi) |
164 | { |
165 | int ret; |
166 | |
167 | ret = exynos_usi_set_sw_conf(usi, mode: usi->mode); |
168 | if (ret) |
169 | return ret; |
170 | |
171 | if (usi->data->ver == USI_VER2) |
172 | return exynos_usi_enable(usi); |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | static int exynos_usi_parse_dt(struct device_node *np, struct exynos_usi *usi) |
178 | { |
179 | int ret; |
180 | u32 mode; |
181 | |
182 | ret = of_property_read_u32(np, propname: "samsung,mode" , out_value: &mode); |
183 | if (ret) |
184 | return ret; |
185 | if (mode < usi->data->min_mode || mode > usi->data->max_mode) |
186 | return -EINVAL; |
187 | usi->mode = mode; |
188 | |
189 | usi->sysreg = syscon_regmap_lookup_by_phandle(np, property: "samsung,sysreg" ); |
190 | if (IS_ERR(ptr: usi->sysreg)) |
191 | return PTR_ERR(ptr: usi->sysreg); |
192 | |
193 | ret = of_property_read_u32_index(np, propname: "samsung,sysreg" , index: 1, |
194 | out_value: &usi->sw_conf); |
195 | if (ret) |
196 | return ret; |
197 | |
198 | usi->clkreq_on = of_property_read_bool(np, propname: "samsung,clkreq-on" ); |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | static int exynos_usi_get_clocks(struct exynos_usi *usi) |
204 | { |
205 | const size_t num = usi->data->num_clks; |
206 | struct device *dev = usi->dev; |
207 | size_t i; |
208 | |
209 | if (num == 0) |
210 | return 0; |
211 | |
212 | usi->clks = devm_kcalloc(dev, n: num, size: sizeof(*usi->clks), GFP_KERNEL); |
213 | if (!usi->clks) |
214 | return -ENOMEM; |
215 | |
216 | for (i = 0; i < num; ++i) |
217 | usi->clks[i].id = usi->data->clk_names[i]; |
218 | |
219 | return devm_clk_bulk_get(dev, num_clks: num, clks: usi->clks); |
220 | } |
221 | |
222 | static int exynos_usi_probe(struct platform_device *pdev) |
223 | { |
224 | struct device *dev = &pdev->dev; |
225 | struct device_node *np = dev->of_node; |
226 | struct exynos_usi *usi; |
227 | int ret; |
228 | |
229 | usi = devm_kzalloc(dev, size: sizeof(*usi), GFP_KERNEL); |
230 | if (!usi) |
231 | return -ENOMEM; |
232 | |
233 | usi->dev = dev; |
234 | platform_set_drvdata(pdev, data: usi); |
235 | |
236 | usi->data = of_device_get_match_data(dev); |
237 | if (!usi->data) |
238 | return -EINVAL; |
239 | |
240 | ret = exynos_usi_parse_dt(np, usi); |
241 | if (ret) |
242 | return ret; |
243 | |
244 | ret = exynos_usi_get_clocks(usi); |
245 | if (ret) |
246 | return ret; |
247 | |
248 | if (usi->data->ver == USI_VER2) { |
249 | usi->regs = devm_platform_ioremap_resource(pdev, index: 0); |
250 | if (IS_ERR(ptr: usi->regs)) |
251 | return PTR_ERR(ptr: usi->regs); |
252 | } |
253 | |
254 | ret = exynos_usi_configure(usi); |
255 | if (ret) |
256 | return ret; |
257 | |
258 | /* Make it possible to embed protocol nodes into USI np */ |
259 | return of_platform_populate(root: np, NULL, NULL, parent: dev); |
260 | } |
261 | |
262 | static int __maybe_unused exynos_usi_resume_noirq(struct device *dev) |
263 | { |
264 | struct exynos_usi *usi = dev_get_drvdata(dev); |
265 | |
266 | return exynos_usi_configure(usi); |
267 | } |
268 | |
269 | static const struct dev_pm_ops exynos_usi_pm = { |
270 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, exynos_usi_resume_noirq) |
271 | }; |
272 | |
273 | static struct platform_driver exynos_usi_driver = { |
274 | .driver = { |
275 | .name = "exynos-usi" , |
276 | .pm = &exynos_usi_pm, |
277 | .of_match_table = exynos_usi_dt_match, |
278 | }, |
279 | .probe = exynos_usi_probe, |
280 | }; |
281 | module_platform_driver(exynos_usi_driver); |
282 | |
283 | MODULE_DESCRIPTION("Samsung USI driver" ); |
284 | MODULE_AUTHOR("Sam Protsenko <semen.protsenko@linaro.org>" ); |
285 | MODULE_LICENSE("GPL" ); |
286 | |