1 | // SPDX-License-Identifier: BSD-3-Clause |
---|---|
2 | /* |
3 | * Copyright (c) 2020, MIPI Alliance, Inc. |
4 | * |
5 | * Author: Nicolas Pitre <npitre@baylibre.com> |
6 | */ |
7 | |
8 | #include <linux/bitfield.h> |
9 | #include <linux/device.h> |
10 | #include <linux/errno.h> |
11 | #include <linux/i3c/master.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/io.h> |
14 | |
15 | #include "hci.h" |
16 | #include "ext_caps.h" |
17 | #include "xfer_mode_rate.h" |
18 | |
19 | |
20 | /* Extended Capability Header */ |
21 | #define CAP_HEADER_LENGTH GENMASK(23, 8) |
22 | #define CAP_HEADER_ID GENMASK(7, 0) |
23 | |
24 | static int hci_extcap_hardware_id(struct i3c_hci *hci, void __iomem *base) |
25 | { |
26 | hci->vendor_mipi_id = readl(addr: base + 0x04); |
27 | hci->vendor_version_id = readl(addr: base + 0x08); |
28 | hci->vendor_product_id = readl(addr: base + 0x0c); |
29 | |
30 | dev_info(&hci->master.dev, "vendor MIPI ID: %#x\n", hci->vendor_mipi_id); |
31 | dev_info(&hci->master.dev, "vendor version ID: %#x\n", hci->vendor_version_id); |
32 | dev_info(&hci->master.dev, "vendor product ID: %#x\n", hci->vendor_product_id); |
33 | |
34 | /* ought to go in a table if this grows too much */ |
35 | switch (hci->vendor_mipi_id) { |
36 | case MIPI_VENDOR_NXP: |
37 | hci->quirks |= HCI_QUIRK_RAW_CCC; |
38 | DBG("raw CCC quirks set"); |
39 | break; |
40 | } |
41 | |
42 | return 0; |
43 | } |
44 | |
45 | static int hci_extcap_master_config(struct i3c_hci *hci, void __iomem *base) |
46 | { |
47 | u32 master_config = readl(addr: base + 0x04); |
48 | unsigned int operation_mode = FIELD_GET(GENMASK(5, 4), master_config); |
49 | static const char * const functionality[] = { |
50 | "(unknown)", "master only", "target only", |
51 | "primary/secondary master"}; |
52 | dev_info(&hci->master.dev, "operation mode: %s\n", functionality[operation_mode]); |
53 | if (operation_mode & 0x1) |
54 | return 0; |
55 | dev_err(&hci->master.dev, "only master mode is currently supported\n"); |
56 | return -EOPNOTSUPP; |
57 | } |
58 | |
59 | static int hci_extcap_multi_bus(struct i3c_hci *hci, void __iomem *base) |
60 | { |
61 | u32 bus_instance = readl(addr: base + 0x04); |
62 | unsigned int count = FIELD_GET(GENMASK(3, 0), bus_instance); |
63 | |
64 | dev_info(&hci->master.dev, "%d bus instances\n", count); |
65 | return 0; |
66 | } |
67 | |
68 | static int hci_extcap_xfer_modes(struct i3c_hci *hci, void __iomem *base) |
69 | { |
70 | u32 header = readl(addr: base); |
71 | u32 entries = FIELD_GET(CAP_HEADER_LENGTH, header) - 1; |
72 | unsigned int index; |
73 | |
74 | dev_info(&hci->master.dev, "transfer mode table has %d entries\n", |
75 | entries); |
76 | base += 4; /* skip header */ |
77 | for (index = 0; index < entries; index++) { |
78 | u32 mode_entry = readl(addr: base); |
79 | |
80 | DBG("mode %d: 0x%08x", index, mode_entry); |
81 | /* TODO: will be needed when I3C core does more than SDR */ |
82 | base += 4; |
83 | } |
84 | |
85 | return 0; |
86 | } |
87 | |
88 | static int hci_extcap_xfer_rates(struct i3c_hci *hci, void __iomem *base) |
89 | { |
90 | u32 header = readl(addr: base); |
91 | u32 entries = FIELD_GET(CAP_HEADER_LENGTH, header) - 1; |
92 | u32 rate_entry; |
93 | unsigned int index, rate, rate_id, mode_id; |
94 | |
95 | base += 4; /* skip header */ |
96 | |
97 | dev_info(&hci->master.dev, "available data rates:\n"); |
98 | for (index = 0; index < entries; index++) { |
99 | rate_entry = readl(addr: base); |
100 | DBG("entry %d: 0x%08x", index, rate_entry); |
101 | rate = FIELD_GET(XFERRATE_ACTUAL_RATE_KHZ, rate_entry); |
102 | rate_id = FIELD_GET(XFERRATE_RATE_ID, rate_entry); |
103 | mode_id = FIELD_GET(XFERRATE_MODE_ID, rate_entry); |
104 | dev_info(&hci->master.dev, "rate %d for %s = %d kHz\n", |
105 | rate_id, |
106 | mode_id == XFERRATE_MODE_I3C ? "I3C": |
107 | mode_id == XFERRATE_MODE_I2C ? "I2C": |
108 | "unknown mode", |
109 | rate); |
110 | base += 4; |
111 | } |
112 | |
113 | return 0; |
114 | } |
115 | |
116 | static int hci_extcap_auto_command(struct i3c_hci *hci, void __iomem *base) |
117 | { |
118 | u32 autocmd_ext_caps = readl(addr: base + 0x04); |
119 | unsigned int max_count = FIELD_GET(GENMASK(3, 0), autocmd_ext_caps); |
120 | u32 autocmd_ext_config = readl(addr: base + 0x08); |
121 | unsigned int count = FIELD_GET(GENMASK(3, 0), autocmd_ext_config); |
122 | |
123 | dev_info(&hci->master.dev, "%d/%d active auto-command entries\n", |
124 | count, max_count); |
125 | /* remember auto-command register location for later use */ |
126 | hci->AUTOCMD_regs = base; |
127 | return 0; |
128 | } |
129 | |
130 | static int hci_extcap_debug(struct i3c_hci *hci, void __iomem *base) |
131 | { |
132 | dev_info(&hci->master.dev, "debug registers present\n"); |
133 | hci->DEBUG_regs = base; |
134 | return 0; |
135 | } |
136 | |
137 | static int hci_extcap_scheduled_cmd(struct i3c_hci *hci, void __iomem *base) |
138 | { |
139 | dev_info(&hci->master.dev, "scheduled commands available\n"); |
140 | /* hci->schedcmd_regs = base; */ |
141 | return 0; |
142 | } |
143 | |
144 | static int hci_extcap_non_curr_master(struct i3c_hci *hci, void __iomem *base) |
145 | { |
146 | dev_info(&hci->master.dev, "Non-Current Master support available\n"); |
147 | /* hci->NCM_regs = base; */ |
148 | return 0; |
149 | } |
150 | |
151 | static int hci_extcap_ccc_resp_conf(struct i3c_hci *hci, void __iomem *base) |
152 | { |
153 | dev_info(&hci->master.dev, "CCC Response Configuration available\n"); |
154 | return 0; |
155 | } |
156 | |
157 | static int hci_extcap_global_DAT(struct i3c_hci *hci, void __iomem *base) |
158 | { |
159 | dev_info(&hci->master.dev, "Global DAT available\n"); |
160 | return 0; |
161 | } |
162 | |
163 | static int hci_extcap_multilane(struct i3c_hci *hci, void __iomem *base) |
164 | { |
165 | dev_info(&hci->master.dev, "Master Multi-Lane support available\n"); |
166 | return 0; |
167 | } |
168 | |
169 | static int hci_extcap_ncm_multilane(struct i3c_hci *hci, void __iomem *base) |
170 | { |
171 | dev_info(&hci->master.dev, "NCM Multi-Lane support available\n"); |
172 | return 0; |
173 | } |
174 | |
175 | struct hci_ext_caps { |
176 | u8 id; |
177 | u16 min_length; |
178 | int (*parser)(struct i3c_hci *hci, void __iomem *base); |
179 | }; |
180 | |
181 | #define EXT_CAP(_id, _highest_mandatory_reg_offset, _parser) \ |
182 | { .id = (_id), .parser = (_parser), \ |
183 | .min_length = (_highest_mandatory_reg_offset)/4 + 1 } |
184 | |
185 | static const struct hci_ext_caps ext_capabilities[] = { |
186 | EXT_CAP(0x01, 0x0c, hci_extcap_hardware_id), |
187 | EXT_CAP(0x02, 0x04, hci_extcap_master_config), |
188 | EXT_CAP(0x03, 0x04, hci_extcap_multi_bus), |
189 | EXT_CAP(0x04, 0x24, hci_extcap_xfer_modes), |
190 | EXT_CAP(0x05, 0x08, hci_extcap_auto_command), |
191 | EXT_CAP(0x08, 0x40, hci_extcap_xfer_rates), |
192 | EXT_CAP(0x0c, 0x10, hci_extcap_debug), |
193 | EXT_CAP(0x0d, 0x0c, hci_extcap_scheduled_cmd), |
194 | EXT_CAP(0x0e, 0x80, hci_extcap_non_curr_master), /* TODO confirm size */ |
195 | EXT_CAP(0x0f, 0x04, hci_extcap_ccc_resp_conf), |
196 | EXT_CAP(0x10, 0x08, hci_extcap_global_DAT), |
197 | EXT_CAP(0x9d, 0x04, hci_extcap_multilane), |
198 | EXT_CAP(0x9e, 0x04, hci_extcap_ncm_multilane), |
199 | }; |
200 | |
201 | static int hci_extcap_vendor_NXP(struct i3c_hci *hci, void __iomem *base) |
202 | { |
203 | hci->vendor_data = (__force void *)base; |
204 | dev_info(&hci->master.dev, "Build Date Info = %#x\n", readl(base + 1*4)); |
205 | /* reset the FPGA */ |
206 | writel(val: 0xdeadbeef, addr: base + 1*4); |
207 | return 0; |
208 | } |
209 | |
210 | struct hci_ext_cap_vendor_specific { |
211 | u32 vendor; |
212 | u8 cap; |
213 | u16 min_length; |
214 | int (*parser)(struct i3c_hci *hci, void __iomem *base); |
215 | }; |
216 | |
217 | #define EXT_CAP_VENDOR(_vendor, _cap, _highest_mandatory_reg_offset) \ |
218 | { .vendor = (MIPI_VENDOR_##_vendor), .cap = (_cap), \ |
219 | .parser = (hci_extcap_vendor_##_vendor), \ |
220 | .min_length = (_highest_mandatory_reg_offset)/4 + 1 } |
221 | |
222 | static const struct hci_ext_cap_vendor_specific vendor_ext_caps[] = { |
223 | EXT_CAP_VENDOR(NXP, 0xc0, 0x20), |
224 | }; |
225 | |
226 | static int hci_extcap_vendor_specific(struct i3c_hci *hci, void __iomem *base, |
227 | u32 cap_id, u32 cap_length) |
228 | { |
229 | const struct hci_ext_cap_vendor_specific *vendor_cap_entry; |
230 | int i; |
231 | |
232 | vendor_cap_entry = NULL; |
233 | for (i = 0; i < ARRAY_SIZE(vendor_ext_caps); i++) { |
234 | if (vendor_ext_caps[i].vendor == hci->vendor_mipi_id && |
235 | vendor_ext_caps[i].cap == cap_id) { |
236 | vendor_cap_entry = &vendor_ext_caps[i]; |
237 | break; |
238 | } |
239 | } |
240 | |
241 | if (!vendor_cap_entry) { |
242 | dev_notice(&hci->master.dev, |
243 | "unknown ext_cap 0x%02x for vendor 0x%02x\n", |
244 | cap_id, hci->vendor_mipi_id); |
245 | return 0; |
246 | } |
247 | if (cap_length < vendor_cap_entry->min_length) { |
248 | dev_err(&hci->master.dev, |
249 | "ext_cap 0x%02x has size %d (expecting >= %d)\n", |
250 | cap_id, cap_length, vendor_cap_entry->min_length); |
251 | return -EINVAL; |
252 | } |
253 | return vendor_cap_entry->parser(hci, base); |
254 | } |
255 | |
256 | int i3c_hci_parse_ext_caps(struct i3c_hci *hci) |
257 | { |
258 | void __iomem *curr_cap = hci->EXTCAPS_regs; |
259 | void __iomem *end = curr_cap + 0x1000; /* some arbitrary limit */ |
260 | u32 cap_header, cap_id, cap_length; |
261 | const struct hci_ext_caps *cap_entry; |
262 | int i, err = 0; |
263 | |
264 | if (!curr_cap) |
265 | return 0; |
266 | |
267 | for (; !err && curr_cap < end; curr_cap += cap_length * 4) { |
268 | cap_header = readl(addr: curr_cap); |
269 | cap_id = FIELD_GET(CAP_HEADER_ID, cap_header); |
270 | cap_length = FIELD_GET(CAP_HEADER_LENGTH, cap_header); |
271 | DBG("id=0x%02x length=%d", cap_id, cap_length); |
272 | if (!cap_length) |
273 | break; |
274 | if (curr_cap + cap_length * 4 >= end) { |
275 | dev_err(&hci->master.dev, |
276 | "ext_cap 0x%02x has size %d (too big)\n", |
277 | cap_id, cap_length); |
278 | err = -EINVAL; |
279 | break; |
280 | } |
281 | |
282 | if (cap_id >= 0xc0 && cap_id <= 0xcf) { |
283 | err = hci_extcap_vendor_specific(hci, base: curr_cap, |
284 | cap_id, cap_length); |
285 | continue; |
286 | } |
287 | |
288 | cap_entry = NULL; |
289 | for (i = 0; i < ARRAY_SIZE(ext_capabilities); i++) { |
290 | if (ext_capabilities[i].id == cap_id) { |
291 | cap_entry = &ext_capabilities[i]; |
292 | break; |
293 | } |
294 | } |
295 | if (!cap_entry) { |
296 | dev_notice(&hci->master.dev, |
297 | "unknown ext_cap 0x%02x\n", cap_id); |
298 | } else if (cap_length < cap_entry->min_length) { |
299 | dev_err(&hci->master.dev, |
300 | "ext_cap 0x%02x has size %d (expecting >= %d)\n", |
301 | cap_id, cap_length, cap_entry->min_length); |
302 | err = -EINVAL; |
303 | } else { |
304 | err = cap_entry->parser(hci, curr_cap); |
305 | } |
306 | } |
307 | return err; |
308 | } |
309 |
Definitions
- hci_extcap_hardware_id
- hci_extcap_master_config
- hci_extcap_multi_bus
- hci_extcap_xfer_modes
- hci_extcap_xfer_rates
- hci_extcap_auto_command
- hci_extcap_debug
- hci_extcap_scheduled_cmd
- hci_extcap_non_curr_master
- hci_extcap_ccc_resp_conf
- hci_extcap_global_DAT
- hci_extcap_multilane
- hci_extcap_ncm_multilane
- hci_ext_caps
- ext_capabilities
- hci_extcap_vendor_NXP
- hci_ext_cap_vendor_specific
- vendor_ext_caps
- hci_extcap_vendor_specific
Improve your Profiling and Debugging skills
Find out more