blob: b746a7838e1f8a9c37f44326a0158f77b9e1bfc7 [file] [log] [blame]
Sam Protsenko619e5e42024-01-10 21:09:00 -06001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2023 Linaro Ltd.
4 * Author: Sam Protsenko <semen.protsenko@linaro.org>
5 *
6 * Samsung Exynos USI driver (Universal Serial Interface).
7 */
8
9#include <dm.h>
10#include <dm/device_compat.h>
11#include <errno.h>
12#include <regmap.h>
13#include <syscon.h>
14#include <asm/io.h>
15#include <linux/bitops.h>
16#include <linux/delay.h>
17#include <linux/err.h>
18
19#include <dt-bindings/soc/samsung,exynos-usi.h>
20
21/* USIv2: System Register: SW_CONF register bits */
22#define USI_V2_SW_CONF_NONE 0x0
23#define USI_V2_SW_CONF_UART BIT(0)
24#define USI_V2_SW_CONF_SPI BIT(1)
25#define USI_V2_SW_CONF_I2C BIT(2)
26#define USI_V2_SW_CONF_MASK (USI_V2_SW_CONF_UART | USI_V2_SW_CONF_SPI | \
27 USI_V2_SW_CONF_I2C)
28
29/* USIv2: USI register offsets */
30#define USI_CON 0x04
31#define USI_OPTION 0x08
32
33/* USIv2: USI register bits */
34#define USI_CON_RESET BIT(0)
35#define USI_OPTION_CLKREQ_ON BIT(1)
36#define USI_OPTION_CLKSTOP_ON BIT(2)
37
38enum exynos_usi_ver {
39 USI_VER2 = 2,
40};
41
42struct exynos_usi_variant {
43 enum exynos_usi_ver ver; /* USI IP-core version */
44 unsigned int sw_conf_mask; /* SW_CONF mask for all protocols */
45 size_t min_mode; /* first index in exynos_usi_modes[] */
46 size_t max_mode; /* last index in exynos_usi_modes[] */
47};
48
49struct exynos_usi {
50 void __iomem *regs; /* USI register map */
51
52 size_t mode; /* current USI SW_CONF mode index */
53 bool clkreq_on; /* always provide clock to IP */
54
55 /* System Register */
56 struct regmap *sysreg; /* System Register map */
57 unsigned int sw_conf; /* SW_CONF register offset in sysreg */
58
59 const struct exynos_usi_variant *data;
60};
61
62struct exynos_usi_mode {
63 const char *name; /* mode name */
64 unsigned int val; /* mode register value */
65};
66
67static const struct exynos_usi_mode exynos_usi_modes[] = {
68 [USI_V2_NONE] = { .name = "none", .val = USI_V2_SW_CONF_NONE },
69 [USI_V2_UART] = { .name = "uart", .val = USI_V2_SW_CONF_UART },
70 [USI_V2_SPI] = { .name = "spi", .val = USI_V2_SW_CONF_SPI },
71 [USI_V2_I2C] = { .name = "i2c", .val = USI_V2_SW_CONF_I2C },
72};
73
74static const struct exynos_usi_variant exynos850_usi_data = {
75 .ver = USI_VER2,
76 .sw_conf_mask = USI_V2_SW_CONF_MASK,
77 .min_mode = USI_V2_NONE,
78 .max_mode = USI_V2_I2C,
79};
80
81static const struct udevice_id exynos_usi_ids[] = {
82 {
83 .compatible = "samsung,exynos850-usi",
84 .data = (ulong)&exynos850_usi_data,
85 },
86 { } /* sentinel */
87};
88
89/**
90 * exynos_usi_set_sw_conf - Set USI block configuration mode
91 * @dev: Driver object
92 *
93 * Select underlying serial protocol (UART/SPI/I2C) in USI IP-core as specified
94 * in @usi.mode.
95 *
96 * Return: 0 on success, or negative error code on failure.
97 */
98static int exynos_usi_set_sw_conf(struct udevice *dev)
99{
100 struct exynos_usi *usi = dev_get_priv(dev);
101 size_t mode = usi->mode;
102 unsigned int val;
103 int ret;
104
105 if (mode < usi->data->min_mode || mode > usi->data->max_mode)
106 return -EINVAL;
107
108 val = exynos_usi_modes[mode].val;
109 ret = regmap_update_bits(usi->sysreg, usi->sw_conf,
110 usi->data->sw_conf_mask, val);
111 if (ret)
112 return ret;
113
114 dev_dbg(dev, "protocol: %s\n", exynos_usi_modes[mode].name);
115
116 return 0;
117}
118
119/**
120 * exynos_usi_enable - Initialize USI block
121 * @usi: USI driver object
122 *
123 * USI IP-core start state is "reset" (on startup and after CPU resume). This
124 * routine enables the USI block by clearing the reset flag. It also configures
125 * HWACG behavior (needed e.g. for UART Rx). It should be performed before
126 * underlying protocol becomes functional.
127 */
128static void exynos_usi_enable(const struct exynos_usi *usi)
129{
130 u32 val;
131
132 /* Enable USI block */
133 val = readl(usi->regs + USI_CON);
134 val &= ~USI_CON_RESET;
135 writel(val, usi->regs + USI_CON);
136 udelay(1);
137
138 /* Continuously provide the clock to USI IP w/o gating */
139 if (usi->clkreq_on) {
140 val = readl(usi->regs + USI_OPTION);
141 val &= ~USI_OPTION_CLKSTOP_ON;
142 val |= USI_OPTION_CLKREQ_ON;
143 writel(val, usi->regs + USI_OPTION);
144 }
145}
146
147static int exynos_usi_configure(struct udevice *dev)
148{
149 struct exynos_usi *usi = dev_get_priv(dev);
150 int ret;
151
152 ret = exynos_usi_set_sw_conf(dev);
153 if (ret)
154 return ret;
155
156 if (usi->data->ver == USI_VER2)
157 exynos_usi_enable(usi);
158
159 return 0;
160}
161
162static int exynos_usi_of_to_plat(struct udevice *dev)
163{
164 struct exynos_usi *usi = dev_get_priv(dev);
165 ofnode node = dev_ofnode(dev);
166 int ret;
167 u32 mode;
168
169 usi->data = (struct exynos_usi_variant *)dev_get_driver_data(dev);
170 if (usi->data->ver == USI_VER2) {
171 usi->regs = dev_read_addr_ptr(dev);
172 if (!usi->regs)
173 return -ENODEV;
174 }
175
176 ret = ofnode_read_u32(node, "samsung,mode", &mode);
177 if (ret)
178 return ret;
179 if (mode < usi->data->min_mode || mode > usi->data->max_mode)
180 return -EINVAL;
181 usi->mode = mode;
182
183 usi->sysreg = syscon_regmap_lookup_by_phandle(dev, "samsung,sysreg");
184 if (IS_ERR(usi->sysreg))
185 return PTR_ERR(usi->sysreg);
186
187 ret = ofnode_read_u32_index(node, "samsung,sysreg", 1, &usi->sw_conf);
188 if (ret)
189 return ret;
190
191 usi->clkreq_on = ofnode_read_bool(node, "samsung,clkreq-on");
192
193 return 0;
194}
195
196static int exynos_usi_probe(struct udevice *dev)
197{
198 return exynos_usi_configure(dev);
199}
200
201U_BOOT_DRIVER(exynos_usi) = {
202 .name = "exynos-usi",
203 .id = UCLASS_MISC,
204 .of_match = exynos_usi_ids,
205 .of_to_plat = exynos_usi_of_to_plat,
206 .probe = exynos_usi_probe,
207 .priv_auto = sizeof(struct exynos_usi),
208};