xref: /openbmc/linux/drivers/soc/samsung/exynos-usi.c (revision a9ca9f9c)
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(usi->sysreg, usi->sw_conf,
114 				 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(usi->data->num_clks, usi->clks);
141 	if (ret)
142 		return ret;
143 
144 	/* Enable USI block */
145 	val = readl(usi->regs + USI_CON);
146 	val &= ~USI_CON_RESET;
147 	writel(val, 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(usi->regs + USI_OPTION);
153 		val &= ~USI_OPTION_CLKSTOP_ON;
154 		val |= USI_OPTION_CLKREQ_ON;
155 		writel(val, usi->regs + USI_OPTION);
156 	}
157 
158 	clk_bulk_disable_unprepare(usi->data->num_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, 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, "samsung,mode", &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, "samsung,sysreg");
190 	if (IS_ERR(usi->sysreg))
191 		return PTR_ERR(usi->sysreg);
192 
193 	ret = of_property_read_u32_index(np, "samsung,sysreg", 1,
194 					 &usi->sw_conf);
195 	if (ret)
196 		return ret;
197 
198 	usi->clkreq_on = of_property_read_bool(np, "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, num, 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, 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, sizeof(*usi), GFP_KERNEL);
230 	if (!usi)
231 		return -ENOMEM;
232 
233 	usi->dev = dev;
234 	platform_set_drvdata(pdev, 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, 0);
250 		if (IS_ERR(usi->regs))
251 			return PTR_ERR(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(np, NULL, NULL, 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