]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: BSD-3-Clause |
5b47271c MK |
2 | /* |
3 | * Qualcomm SPMI bus driver | |
4 | * | |
5 | * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com> | |
6 | * | |
7 | * Loosely based on Little Kernel driver | |
5b47271c MK |
8 | */ |
9 | ||
d678a59d | 10 | #include <common.h> |
5b47271c MK |
11 | #include <dm.h> |
12 | #include <errno.h> | |
13 | #include <fdtdec.h> | |
401d1c4f | 14 | #include <asm/global_data.h> |
5b47271c | 15 | #include <asm/io.h> |
336d4615 | 16 | #include <dm/device_compat.h> |
5b47271c MK |
17 | #include <spmi/spmi.h> |
18 | ||
19 | DECLARE_GLOBAL_DATA_PTR; | |
20 | ||
210d9592 | 21 | /* PMIC Arbiter configuration registers */ |
f5a2d6b4 DS |
22 | #define PMIC_ARB_VERSION 0x0000 |
23 | #define PMIC_ARB_VERSION_V2_MIN 0x20010000 | |
24 | #define PMIC_ARB_VERSION_V3_MIN 0x30000000 | |
25 | #define PMIC_ARB_VERSION_V5_MIN 0x50000000 | |
ee1d8aa5 | 26 | #define PMIC_ARB_VERSION_V7_MIN 0x70000000 |
f5a2d6b4 DS |
27 | |
28 | #define APID_MAP_OFFSET_V1_V2_V3 (0x800) | |
29 | #define APID_MAP_OFFSET_V5 (0x900) | |
ee1d8aa5 | 30 | #define APID_MAP_OFFSET_V7 (0x2000) |
f5a2d6b4 DS |
31 | #define ARB_CHANNEL_OFFSET(n) (0x4 * (n)) |
32 | #define SPMI_CH_OFFSET(chnl) ((chnl) * 0x8000) | |
33 | #define SPMI_V5_OBS_CH_OFFSET(chnl) ((chnl) * 0x80) | |
ee1d8aa5 | 34 | #define SPMI_V7_OBS_CH_OFFSET(chnl) ((chnl) * 0x20) |
f5a2d6b4 | 35 | #define SPMI_V5_RW_CH_OFFSET(chnl) ((chnl) * 0x10000) |
ee1d8aa5 | 36 | #define SPMI_V7_RW_CH_OFFSET(chnl) ((chnl) * 0x1000) |
f5a2d6b4 | 37 | |
59e0482b NA |
38 | #define SPMI_OWNERSHIP_PERIPH2OWNER(x) ((x) & 0x7) |
39 | ||
f5a2d6b4 DS |
40 | #define SPMI_REG_CMD0 0x0 |
41 | #define SPMI_REG_CONFIG 0x4 | |
42 | #define SPMI_REG_STATUS 0x8 | |
43 | #define SPMI_REG_WDATA 0x10 | |
44 | #define SPMI_REG_RDATA 0x18 | |
45 | ||
46 | #define SPMI_CMD_OPCODE_SHIFT 27 | |
47 | #define SPMI_CMD_SLAVE_ID_SHIFT 20 | |
48 | #define SPMI_CMD_ADDR_SHIFT 12 | |
49 | #define SPMI_CMD_ADDR_OFFSET_SHIFT 4 | |
50 | #define SPMI_CMD_BYTE_CNT_SHIFT 0 | |
51 | ||
52 | #define SPMI_CMD_EXT_REG_WRITE_LONG 0x00 | |
53 | #define SPMI_CMD_EXT_REG_READ_LONG 0x01 | |
54 | ||
55 | #define SPMI_STATUS_DONE 0x1 | |
56 | ||
57 | #define SPMI_MAX_CHANNELS 128 | |
59e0482b | 58 | #define SPMI_MAX_CHANNELS_V5 512 |
ee1d8aa5 | 59 | #define SPMI_MAX_CHANNELS_V7 1024 |
f5a2d6b4 DS |
60 | #define SPMI_MAX_SLAVES 16 |
61 | #define SPMI_MAX_PERIPH 256 | |
62 | ||
59e0482b NA |
63 | #define SPMI_CHANNEL_READ_ONLY BIT(31) |
64 | #define SPMI_CHANNEL_MASK 0xffff | |
65 | ||
f5a2d6b4 DS |
66 | enum arb_ver { |
67 | V1 = 1, | |
68 | V2, | |
69 | V3, | |
ee1d8aa5 NA |
70 | V5 = 5, |
71 | V7 = 7 | |
f5a2d6b4 | 72 | }; |
5b47271c | 73 | |
f5a2d6b4 DS |
74 | /* |
75 | * PMIC arbiter version 5 uses different register offsets for read/write vs | |
76 | * observer channels. | |
77 | */ | |
78 | enum pmic_arb_channel { | |
79 | PMIC_ARB_CHANNEL_RW, | |
80 | PMIC_ARB_CHANNEL_OBS, | |
81 | }; | |
5b47271c MK |
82 | |
83 | struct msm_spmi_priv { | |
f5a2d6b4 | 84 | phys_addr_t arb_chnl; /* ARB channel mapping base */ |
92fe0892 | 85 | phys_addr_t spmi_chnls; /* SPMI channels */ |
f5a2d6b4 | 86 | phys_addr_t spmi_obs; /* SPMI observer */ |
59e0482b NA |
87 | phys_addr_t spmi_cnfg; /* SPMI config */ |
88 | u32 owner; /* Current owner */ | |
89 | unsigned int max_channels; /* Max channels */ | |
5b47271c | 90 | /* SPMI channel map */ |
59e0482b | 91 | uint32_t channel_map[SPMI_MAX_SLAVES][SPMI_MAX_PERIPH]; |
f5a2d6b4 DS |
92 | /* SPMI bus arbiter version */ |
93 | u32 arb_ver; | |
5b47271c MK |
94 | }; |
95 | ||
f0b604d9 NA |
96 | static u32 pmic_arb_fmt_cmd_v1(u8 opc, u8 sid, u8 pid, u8 off) |
97 | { | |
98 | return (opc << 27) | (sid << 20) | (pid << 12) | (off << 4) | 1; | |
99 | } | |
100 | ||
101 | static u32 pmic_arb_fmt_cmd_v2(u8 opc, u8 off) | |
102 | { | |
103 | return (opc << 27) | (off << 4) | 1; | |
104 | } | |
105 | ||
5b47271c MK |
106 | static int msm_spmi_write(struct udevice *dev, int usid, int pid, int off, |
107 | uint8_t val) | |
108 | { | |
109 | struct msm_spmi_priv *priv = dev_get_priv(dev); | |
110 | unsigned channel; | |
f5a2d6b4 | 111 | unsigned int ch_offset; |
5b47271c MK |
112 | uint32_t reg = 0; |
113 | ||
114 | if (usid >= SPMI_MAX_SLAVES) | |
115 | return -EIO; | |
116 | if (pid >= SPMI_MAX_PERIPH) | |
117 | return -EIO; | |
59e0482b NA |
118 | if (priv->channel_map[usid][pid] & SPMI_CHANNEL_READ_ONLY) |
119 | return -EPERM; | |
5b47271c | 120 | |
59e0482b | 121 | channel = priv->channel_map[usid][pid] & SPMI_CHANNEL_MASK; |
5b47271c | 122 | |
f0b604d9 NA |
123 | dev_dbg(dev, "[%d:%d] %s: channel %d\n", usid, pid, __func__, channel); |
124 | ||
125 | switch (priv->arb_ver) { | |
126 | case V1: | |
127 | ch_offset = SPMI_CH_OFFSET(channel); | |
128 | ||
129 | reg = pmic_arb_fmt_cmd_v1(SPMI_CMD_EXT_REG_WRITE_LONG, | |
130 | usid, pid, off); | |
131 | break; | |
132 | ||
133 | case V2: | |
c2de620d NA |
134 | ch_offset = SPMI_CH_OFFSET(channel); |
135 | ||
f0b604d9 NA |
136 | reg = pmic_arb_fmt_cmd_v2(SPMI_CMD_EXT_REG_WRITE_LONG, off); |
137 | break; | |
138 | ||
139 | case V5: | |
140 | ch_offset = SPMI_V5_RW_CH_OFFSET(channel); | |
141 | ||
ee1d8aa5 NA |
142 | reg = pmic_arb_fmt_cmd_v2(SPMI_CMD_EXT_REG_WRITE_LONG, off); |
143 | break; | |
144 | ||
145 | case V7: | |
146 | ch_offset = SPMI_V7_RW_CH_OFFSET(channel); | |
147 | ||
f0b604d9 NA |
148 | reg = pmic_arb_fmt_cmd_v2(SPMI_CMD_EXT_REG_WRITE_LONG, off); |
149 | break; | |
150 | } | |
151 | ||
5b47271c | 152 | /* Disable IRQ mode for the current channel*/ |
c2de620d | 153 | writel(0x0, priv->spmi_chnls + ch_offset + SPMI_REG_CONFIG); |
5b47271c MK |
154 | |
155 | /* Write single byte */ | |
c2de620d | 156 | writel(val, priv->spmi_chnls + ch_offset + SPMI_REG_WDATA); |
5b47271c | 157 | |
5b47271c | 158 | /* Send write command */ |
c2de620d | 159 | writel(reg, priv->spmi_chnls + ch_offset + SPMI_REG_CMD0); |
5b47271c MK |
160 | |
161 | /* Wait till CMD DONE status */ | |
162 | reg = 0; | |
163 | while (!reg) { | |
c2de620d | 164 | reg = readl(priv->spmi_chnls + ch_offset + |
5b47271c MK |
165 | SPMI_REG_STATUS); |
166 | } | |
167 | ||
168 | if (reg ^ SPMI_STATUS_DONE) { | |
169 | printf("SPMI write failure.\n"); | |
170 | return -EIO; | |
171 | } | |
172 | ||
173 | return 0; | |
174 | } | |
175 | ||
176 | static int msm_spmi_read(struct udevice *dev, int usid, int pid, int off) | |
177 | { | |
178 | struct msm_spmi_priv *priv = dev_get_priv(dev); | |
179 | unsigned channel; | |
f5a2d6b4 | 180 | unsigned int ch_offset; |
5b47271c MK |
181 | uint32_t reg = 0; |
182 | ||
183 | if (usid >= SPMI_MAX_SLAVES) | |
184 | return -EIO; | |
185 | if (pid >= SPMI_MAX_PERIPH) | |
186 | return -EIO; | |
187 | ||
59e0482b | 188 | channel = priv->channel_map[usid][pid] & SPMI_CHANNEL_MASK; |
5b47271c | 189 | |
f0b604d9 NA |
190 | dev_dbg(dev, "[%d:%d] %s: channel %d\n", usid, pid, __func__, channel); |
191 | ||
192 | switch (priv->arb_ver) { | |
193 | case V1: | |
194 | ch_offset = SPMI_CH_OFFSET(channel); | |
195 | ||
196 | /* Prepare read command */ | |
197 | reg = pmic_arb_fmt_cmd_v1(SPMI_CMD_EXT_REG_READ_LONG, | |
198 | usid, pid, off); | |
199 | break; | |
200 | ||
201 | case V2: | |
f5a2d6b4 DS |
202 | ch_offset = SPMI_CH_OFFSET(channel); |
203 | ||
f0b604d9 NA |
204 | /* Prepare read command */ |
205 | reg = pmic_arb_fmt_cmd_v2(SPMI_CMD_EXT_REG_READ_LONG, off); | |
206 | break; | |
207 | ||
208 | case V5: | |
209 | ch_offset = SPMI_V5_OBS_CH_OFFSET(channel); | |
210 | ||
ee1d8aa5 NA |
211 | /* Prepare read command */ |
212 | reg = pmic_arb_fmt_cmd_v2(SPMI_CMD_EXT_REG_READ_LONG, off); | |
213 | break; | |
214 | ||
215 | case V7: | |
216 | ch_offset = SPMI_V7_OBS_CH_OFFSET(channel); | |
217 | ||
f0b604d9 NA |
218 | /* Prepare read command */ |
219 | reg = pmic_arb_fmt_cmd_v2(SPMI_CMD_EXT_REG_READ_LONG, off); | |
220 | break; | |
221 | } | |
222 | ||
5b47271c | 223 | /* Disable IRQ mode for the current channel*/ |
f5a2d6b4 | 224 | writel(0x0, priv->spmi_obs + ch_offset + SPMI_REG_CONFIG); |
5b47271c | 225 | |
5b47271c | 226 | /* Request read */ |
f5a2d6b4 | 227 | writel(reg, priv->spmi_obs + ch_offset + SPMI_REG_CMD0); |
5b47271c MK |
228 | |
229 | /* Wait till CMD DONE status */ | |
230 | reg = 0; | |
231 | while (!reg) { | |
f5a2d6b4 | 232 | reg = readl(priv->spmi_obs + ch_offset + SPMI_REG_STATUS); |
5b47271c MK |
233 | } |
234 | ||
235 | if (reg ^ SPMI_STATUS_DONE) { | |
236 | printf("SPMI read failure.\n"); | |
237 | return -EIO; | |
238 | } | |
239 | ||
240 | /* Read the data */ | |
f5a2d6b4 DS |
241 | return readl(priv->spmi_obs + ch_offset + |
242 | SPMI_REG_RDATA) & 0xFF; | |
5b47271c MK |
243 | } |
244 | ||
245 | static struct dm_spmi_ops msm_spmi_ops = { | |
246 | .read = msm_spmi_read, | |
247 | .write = msm_spmi_write, | |
248 | }; | |
249 | ||
250 | static int msm_spmi_probe(struct udevice *dev) | |
251 | { | |
5b47271c | 252 | struct msm_spmi_priv *priv = dev_get_priv(dev); |
92fe0892 | 253 | phys_addr_t core_addr; |
210d9592 | 254 | u32 hw_ver; |
5b47271c | 255 | int i; |
f5a2d6b4 | 256 | |
92fe0892 CC |
257 | core_addr = dev_read_addr_name(dev, "core"); |
258 | priv->spmi_chnls = dev_read_addr_name(dev, "chnls"); | |
259 | priv->spmi_obs = dev_read_addr_name(dev, "obsrvr"); | |
59e0482b | 260 | dev_read_u32(dev, "qcom,ee", &priv->owner); |
f5a2d6b4 | 261 | |
92fe0892 | 262 | hw_ver = readl(core_addr + PMIC_ARB_VERSION); |
f5a2d6b4 DS |
263 | |
264 | if (hw_ver < PMIC_ARB_VERSION_V3_MIN) { | |
265 | priv->arb_ver = V2; | |
92fe0892 | 266 | priv->arb_chnl = core_addr + APID_MAP_OFFSET_V1_V2_V3; |
59e0482b | 267 | priv->max_channels = SPMI_MAX_CHANNELS; |
f5a2d6b4 DS |
268 | } else if (hw_ver < PMIC_ARB_VERSION_V5_MIN) { |
269 | priv->arb_ver = V3; | |
92fe0892 | 270 | priv->arb_chnl = core_addr + APID_MAP_OFFSET_V1_V2_V3; |
59e0482b | 271 | priv->max_channels = SPMI_MAX_CHANNELS; |
ee1d8aa5 | 272 | } else if (hw_ver < PMIC_ARB_VERSION_V7_MIN) { |
f5a2d6b4 | 273 | priv->arb_ver = V5; |
92fe0892 | 274 | priv->arb_chnl = core_addr + APID_MAP_OFFSET_V5; |
ee1d8aa5 NA |
275 | priv->max_channels = SPMI_MAX_CHANNELS; |
276 | priv->spmi_cnfg = dev_read_addr_name(dev, "cnfg"); | |
277 | } else { | |
278 | /* TOFIX: handle second bus */ | |
279 | priv->arb_ver = V7; | |
280 | priv->arb_chnl = core_addr + APID_MAP_OFFSET_V7; | |
281 | priv->max_channels = SPMI_MAX_CHANNELS_V7; | |
59e0482b | 282 | priv->spmi_cnfg = dev_read_addr_name(dev, "cnfg"); |
f5a2d6b4 | 283 | } |
5b47271c | 284 | |
92fe0892 | 285 | dev_dbg(dev, "PMIC Arb Version-%d (%#x)\n", hw_ver >> 28, hw_ver); |
210d9592 | 286 | |
5b47271c | 287 | if (priv->arb_chnl == FDT_ADDR_T_NONE || |
92fe0892 | 288 | priv->spmi_chnls == FDT_ADDR_T_NONE || |
5b47271c MK |
289 | priv->spmi_obs == FDT_ADDR_T_NONE) |
290 | return -EINVAL; | |
291 | ||
92fe0892 CC |
292 | dev_dbg(dev, "priv->arb_chnl address (%#08llx)\n", priv->arb_chnl); |
293 | dev_dbg(dev, "priv->spmi_chnls address (%#08llx)\n", priv->spmi_chnls); | |
294 | dev_dbg(dev, "priv->spmi_obs address (%#08llx)\n", priv->spmi_obs); | |
5b47271c | 295 | /* Scan peripherals connected to each SPMI channel */ |
59e0482b | 296 | for (i = 0; i < priv->max_channels; i++) { |
5b47271c MK |
297 | uint32_t periph = readl(priv->arb_chnl + ARB_CHANNEL_OFFSET(i)); |
298 | uint8_t slave_id = (periph & 0xf0000) >> 16; | |
299 | uint8_t pid = (periph & 0xff00) >> 8; | |
300 | ||
301 | priv->channel_map[slave_id][pid] = i; | |
59e0482b NA |
302 | |
303 | /* Mark channels read-only when from different owner */ | |
ee1d8aa5 | 304 | if (priv->arb_ver == V5 || priv->arb_ver == V7) { |
59e0482b NA |
305 | uint32_t cnfg = readl(priv->spmi_cnfg + ARB_CHANNEL_OFFSET(i)); |
306 | uint8_t owner = SPMI_OWNERSHIP_PERIPH2OWNER(cnfg); | |
307 | ||
308 | if (owner != priv->owner) | |
309 | priv->channel_map[slave_id][pid] |= SPMI_CHANNEL_READ_ONLY; | |
310 | } | |
5b47271c MK |
311 | } |
312 | return 0; | |
313 | } | |
314 | ||
315 | static const struct udevice_id msm_spmi_ids[] = { | |
316 | { .compatible = "qcom,spmi-pmic-arb" }, | |
317 | { } | |
318 | }; | |
319 | ||
320 | U_BOOT_DRIVER(msm_spmi) = { | |
321 | .name = "msm_spmi", | |
322 | .id = UCLASS_SPMI, | |
323 | .of_match = msm_spmi_ids, | |
324 | .ops = &msm_spmi_ops, | |
325 | .probe = msm_spmi_probe, | |
f5a2d6b4 | 326 | .priv_auto = sizeof(struct msm_spmi_priv), |
5b47271c | 327 | }; |