1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (C) 2022-2023 Microchip Technology Inc. |
3 | // PCI1xxxx OTP/EEPROM driver |
4 | |
5 | #include <linux/auxiliary_bus.h> |
6 | #include <linux/device.h> |
7 | #include <linux/iopoll.h> |
8 | #include <linux/module.h> |
9 | #include <linux/nvmem-provider.h> |
10 | |
11 | #include "mchp_pci1xxxx_gp.h" |
12 | |
13 | #define AUX_DRIVER_NAME "PCI1xxxxOTPE2P" |
14 | #define EEPROM_NAME "pci1xxxx_eeprom" |
15 | #define OTP_NAME "pci1xxxx_otp" |
16 | |
17 | #define PERI_PF3_SYSTEM_REG_ADDR_BASE 0x2000 |
18 | #define PERI_PF3_SYSTEM_REG_LENGTH 0x4000 |
19 | |
20 | #define EEPROM_SIZE_BYTES 8192 |
21 | #define OTP_SIZE_BYTES 8192 |
22 | |
23 | #define CONFIG_REG_ADDR_BASE 0 |
24 | #define EEPROM_REG_ADDR_BASE 0x0E00 |
25 | #define OTP_REG_ADDR_BASE 0x1000 |
26 | |
27 | #define MMAP_OTP_OFFSET(x) (OTP_REG_ADDR_BASE + (x)) |
28 | #define MMAP_EEPROM_OFFSET(x) (EEPROM_REG_ADDR_BASE + (x)) |
29 | #define MMAP_CFG_OFFSET(x) (CONFIG_REG_ADDR_BASE + (x)) |
30 | |
31 | #define EEPROM_CMD_REG 0x00 |
32 | #define EEPROM_DATA_REG 0x04 |
33 | |
34 | #define EEPROM_CMD_EPC_WRITE (BIT(29) | BIT(28)) |
35 | #define EEPROM_CMD_EPC_TIMEOUT_BIT BIT(17) |
36 | #define EEPROM_CMD_EPC_BUSY_BIT BIT(31) |
37 | |
38 | #define STATUS_READ_DELAY_US 1 |
39 | #define STATUS_READ_TIMEOUT_US 20000 |
40 | |
41 | #define OTP_ADDR_HIGH_OFFSET 0x04 |
42 | #define OTP_ADDR_LOW_OFFSET 0x08 |
43 | #define OTP_PRGM_DATA_OFFSET 0x10 |
44 | #define OTP_PRGM_MODE_OFFSET 0x14 |
45 | #define OTP_RD_DATA_OFFSET 0x18 |
46 | #define OTP_FUNC_CMD_OFFSET 0x20 |
47 | #define OTP_CMD_GO_OFFSET 0x28 |
48 | #define OTP_PASS_FAIL_OFFSET 0x2C |
49 | #define OTP_STATUS_OFFSET 0x30 |
50 | |
51 | #define OTP_FUNC_RD_BIT BIT(0) |
52 | #define OTP_FUNC_PGM_BIT BIT(1) |
53 | #define OTP_CMD_GO_BIT BIT(0) |
54 | #define OTP_STATUS_BUSY_BIT BIT(0) |
55 | #define OTP_PGM_MODE_BYTE_BIT BIT(0) |
56 | #define OTP_FAIL_BIT BIT(0) |
57 | |
58 | #define OTP_PWR_DN_BIT BIT(0) |
59 | #define OTP_PWR_DN_OFFSET 0x00 |
60 | |
61 | #define CFG_SYS_LOCK_OFFSET 0xA0 |
62 | #define CFG_SYS_LOCK_PF3 BIT(5) |
63 | |
64 | #define BYTE_LOW (GENMASK(7, 0)) |
65 | #define BYTE_HIGH (GENMASK(12, 8)) |
66 | |
67 | struct pci1xxxx_otp_eeprom_device { |
68 | struct auxiliary_device *pdev; |
69 | void __iomem *reg_base; |
70 | struct nvmem_config nvmem_config_eeprom; |
71 | struct nvmem_device *nvmem_eeprom; |
72 | struct nvmem_config nvmem_config_otp; |
73 | struct nvmem_device *nvmem_otp; |
74 | }; |
75 | |
76 | static int set_sys_lock(struct pci1xxxx_otp_eeprom_device *priv) |
77 | { |
78 | void __iomem *sys_lock = priv->reg_base + |
79 | MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET); |
80 | u8 data; |
81 | |
82 | writel(CFG_SYS_LOCK_PF3, addr: sys_lock); |
83 | data = readl(addr: sys_lock); |
84 | if (data != CFG_SYS_LOCK_PF3) |
85 | return -EPERM; |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static void release_sys_lock(struct pci1xxxx_otp_eeprom_device *priv) |
91 | { |
92 | void __iomem *sys_lock = priv->reg_base + |
93 | MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET); |
94 | writel(val: 0, addr: sys_lock); |
95 | } |
96 | |
97 | static bool is_eeprom_responsive(struct pci1xxxx_otp_eeprom_device *priv) |
98 | { |
99 | void __iomem *rb = priv->reg_base; |
100 | u32 regval; |
101 | int ret; |
102 | |
103 | writel(EEPROM_CMD_EPC_TIMEOUT_BIT, |
104 | addr: rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG)); |
105 | writel(EEPROM_CMD_EPC_BUSY_BIT, |
106 | addr: rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG)); |
107 | |
108 | /* Wait for the EPC_BUSY bit to get cleared or timeout bit to get set*/ |
109 | ret = read_poll_timeout(readl, regval, !(regval & EEPROM_CMD_EPC_BUSY_BIT), |
110 | STATUS_READ_DELAY_US, STATUS_READ_TIMEOUT_US, |
111 | true, rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG)); |
112 | |
113 | /* Return failure if either of software or hardware timeouts happen */ |
114 | if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT))) |
115 | return false; |
116 | |
117 | return true; |
118 | } |
119 | |
120 | static int pci1xxxx_eeprom_read(void *priv_t, unsigned int off, |
121 | void *buf_t, size_t count) |
122 | { |
123 | struct pci1xxxx_otp_eeprom_device *priv = priv_t; |
124 | void __iomem *rb = priv->reg_base; |
125 | char *buf = buf_t; |
126 | u32 regval; |
127 | u32 byte; |
128 | int ret; |
129 | |
130 | if (off >= priv->nvmem_config_eeprom.size) |
131 | return -EFAULT; |
132 | |
133 | if ((off + count) > priv->nvmem_config_eeprom.size) |
134 | count = priv->nvmem_config_eeprom.size - off; |
135 | |
136 | ret = set_sys_lock(priv); |
137 | if (ret) |
138 | return ret; |
139 | |
140 | for (byte = 0; byte < count; byte++) { |
141 | writel(EEPROM_CMD_EPC_BUSY_BIT | (off + byte), addr: rb + |
142 | MMAP_EEPROM_OFFSET(EEPROM_CMD_REG)); |
143 | |
144 | ret = read_poll_timeout(readl, regval, |
145 | !(regval & EEPROM_CMD_EPC_BUSY_BIT), |
146 | STATUS_READ_DELAY_US, |
147 | STATUS_READ_TIMEOUT_US, true, |
148 | rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG)); |
149 | if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT))) { |
150 | ret = -EIO; |
151 | goto error; |
152 | } |
153 | |
154 | buf[byte] = readl(addr: rb + MMAP_EEPROM_OFFSET(EEPROM_DATA_REG)); |
155 | } |
156 | ret = byte; |
157 | error: |
158 | release_sys_lock(priv); |
159 | return ret; |
160 | } |
161 | |
162 | static int pci1xxxx_eeprom_write(void *priv_t, unsigned int off, |
163 | void *value_t, size_t count) |
164 | { |
165 | struct pci1xxxx_otp_eeprom_device *priv = priv_t; |
166 | void __iomem *rb = priv->reg_base; |
167 | char *value = value_t; |
168 | u32 regval; |
169 | u32 byte; |
170 | int ret; |
171 | |
172 | if (off >= priv->nvmem_config_eeprom.size) |
173 | return -EFAULT; |
174 | |
175 | if ((off + count) > priv->nvmem_config_eeprom.size) |
176 | count = priv->nvmem_config_eeprom.size - off; |
177 | |
178 | ret = set_sys_lock(priv); |
179 | if (ret) |
180 | return ret; |
181 | |
182 | for (byte = 0; byte < count; byte++) { |
183 | writel(val: *(value + byte), addr: rb + MMAP_EEPROM_OFFSET(EEPROM_DATA_REG)); |
184 | regval = EEPROM_CMD_EPC_TIMEOUT_BIT | EEPROM_CMD_EPC_WRITE | |
185 | (off + byte); |
186 | writel(val: regval, addr: rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG)); |
187 | writel(EEPROM_CMD_EPC_BUSY_BIT | regval, |
188 | addr: rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG)); |
189 | |
190 | ret = read_poll_timeout(readl, regval, |
191 | !(regval & EEPROM_CMD_EPC_BUSY_BIT), |
192 | STATUS_READ_DELAY_US, |
193 | STATUS_READ_TIMEOUT_US, true, |
194 | rb + MMAP_EEPROM_OFFSET(EEPROM_CMD_REG)); |
195 | if (ret < 0 || (!ret && (regval & EEPROM_CMD_EPC_TIMEOUT_BIT))) { |
196 | ret = -EIO; |
197 | goto error; |
198 | } |
199 | } |
200 | ret = byte; |
201 | error: |
202 | release_sys_lock(priv); |
203 | return ret; |
204 | } |
205 | |
206 | static void otp_device_set_address(struct pci1xxxx_otp_eeprom_device *priv, |
207 | u16 address) |
208 | { |
209 | u16 lo, hi; |
210 | |
211 | lo = address & BYTE_LOW; |
212 | hi = (address & BYTE_HIGH) >> 8; |
213 | writew(val: lo, addr: priv->reg_base + MMAP_OTP_OFFSET(OTP_ADDR_LOW_OFFSET)); |
214 | writew(val: hi, addr: priv->reg_base + MMAP_OTP_OFFSET(OTP_ADDR_HIGH_OFFSET)); |
215 | } |
216 | |
217 | static int pci1xxxx_otp_read(void *priv_t, unsigned int off, |
218 | void *buf_t, size_t count) |
219 | { |
220 | struct pci1xxxx_otp_eeprom_device *priv = priv_t; |
221 | void __iomem *rb = priv->reg_base; |
222 | char *buf = buf_t; |
223 | u32 regval; |
224 | u32 byte; |
225 | int ret; |
226 | u8 data; |
227 | |
228 | if (off >= priv->nvmem_config_otp.size) |
229 | return -EFAULT; |
230 | |
231 | if ((off + count) > priv->nvmem_config_otp.size) |
232 | count = priv->nvmem_config_otp.size - off; |
233 | |
234 | ret = set_sys_lock(priv); |
235 | if (ret) |
236 | return ret; |
237 | |
238 | for (byte = 0; byte < count; byte++) { |
239 | otp_device_set_address(priv, address: (u16)(off + byte)); |
240 | data = readl(addr: rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET)); |
241 | writel(val: data | OTP_FUNC_RD_BIT, |
242 | addr: rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET)); |
243 | data = readl(addr: rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET)); |
244 | writel(val: data | OTP_CMD_GO_BIT, |
245 | addr: rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET)); |
246 | |
247 | ret = read_poll_timeout(readl, regval, |
248 | !(regval & OTP_STATUS_BUSY_BIT), |
249 | STATUS_READ_DELAY_US, |
250 | STATUS_READ_TIMEOUT_US, true, |
251 | rb + MMAP_OTP_OFFSET(OTP_STATUS_OFFSET)); |
252 | |
253 | data = readl(addr: rb + MMAP_OTP_OFFSET(OTP_PASS_FAIL_OFFSET)); |
254 | if (ret < 0 || data & OTP_FAIL_BIT) { |
255 | ret = -EIO; |
256 | goto error; |
257 | } |
258 | |
259 | buf[byte] = readl(addr: rb + MMAP_OTP_OFFSET(OTP_RD_DATA_OFFSET)); |
260 | } |
261 | ret = byte; |
262 | error: |
263 | release_sys_lock(priv); |
264 | return ret; |
265 | } |
266 | |
267 | static int pci1xxxx_otp_write(void *priv_t, unsigned int off, |
268 | void *value_t, size_t count) |
269 | { |
270 | struct pci1xxxx_otp_eeprom_device *priv = priv_t; |
271 | void __iomem *rb = priv->reg_base; |
272 | char *value = value_t; |
273 | u32 regval; |
274 | u32 byte; |
275 | int ret; |
276 | u8 data; |
277 | |
278 | if (off >= priv->nvmem_config_otp.size) |
279 | return -EFAULT; |
280 | |
281 | if ((off + count) > priv->nvmem_config_otp.size) |
282 | count = priv->nvmem_config_otp.size - off; |
283 | |
284 | ret = set_sys_lock(priv); |
285 | if (ret) |
286 | return ret; |
287 | |
288 | for (byte = 0; byte < count; byte++) { |
289 | otp_device_set_address(priv, address: (u16)(off + byte)); |
290 | |
291 | /* |
292 | * Set OTP_PGM_MODE_BYTE command bit in OTP_PRGM_MODE register |
293 | * to enable Byte programming |
294 | */ |
295 | data = readl(addr: rb + MMAP_OTP_OFFSET(OTP_PRGM_MODE_OFFSET)); |
296 | writel(val: data | OTP_PGM_MODE_BYTE_BIT, |
297 | addr: rb + MMAP_OTP_OFFSET(OTP_PRGM_MODE_OFFSET)); |
298 | writel(val: *(value + byte), addr: rb + MMAP_OTP_OFFSET(OTP_PRGM_DATA_OFFSET)); |
299 | data = readl(addr: rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET)); |
300 | writel(val: data | OTP_FUNC_PGM_BIT, |
301 | addr: rb + MMAP_OTP_OFFSET(OTP_FUNC_CMD_OFFSET)); |
302 | data = readl(addr: rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET)); |
303 | writel(val: data | OTP_CMD_GO_BIT, |
304 | addr: rb + MMAP_OTP_OFFSET(OTP_CMD_GO_OFFSET)); |
305 | |
306 | ret = read_poll_timeout(readl, regval, |
307 | !(regval & OTP_STATUS_BUSY_BIT), |
308 | STATUS_READ_DELAY_US, |
309 | STATUS_READ_TIMEOUT_US, true, |
310 | rb + MMAP_OTP_OFFSET(OTP_STATUS_OFFSET)); |
311 | |
312 | data = readl(addr: rb + MMAP_OTP_OFFSET(OTP_PASS_FAIL_OFFSET)); |
313 | if (ret < 0 || data & OTP_FAIL_BIT) { |
314 | ret = -EIO; |
315 | goto error; |
316 | } |
317 | } |
318 | ret = byte; |
319 | error: |
320 | release_sys_lock(priv); |
321 | return ret; |
322 | } |
323 | |
324 | static int pci1xxxx_otp_eeprom_probe(struct auxiliary_device *aux_dev, |
325 | const struct auxiliary_device_id *id) |
326 | { |
327 | struct auxiliary_device_wrapper *aux_dev_wrapper; |
328 | struct pci1xxxx_otp_eeprom_device *priv; |
329 | struct gp_aux_data_type *pdata; |
330 | int ret; |
331 | u8 data; |
332 | |
333 | aux_dev_wrapper = container_of(aux_dev, struct auxiliary_device_wrapper, |
334 | aux_dev); |
335 | pdata = &aux_dev_wrapper->gp_aux_data; |
336 | if (!pdata) |
337 | return -EINVAL; |
338 | |
339 | priv = devm_kzalloc(dev: &aux_dev->dev, size: sizeof(*priv), GFP_KERNEL); |
340 | if (!priv) |
341 | return -ENOMEM; |
342 | |
343 | priv->pdev = aux_dev; |
344 | |
345 | if (!devm_request_mem_region(&aux_dev->dev, pdata->region_start + |
346 | PERI_PF3_SYSTEM_REG_ADDR_BASE, |
347 | PERI_PF3_SYSTEM_REG_LENGTH, |
348 | aux_dev->name)) |
349 | return -ENOMEM; |
350 | |
351 | priv->reg_base = devm_ioremap(dev: &aux_dev->dev, offset: pdata->region_start + |
352 | PERI_PF3_SYSTEM_REG_ADDR_BASE, |
353 | PERI_PF3_SYSTEM_REG_LENGTH); |
354 | if (!priv->reg_base) |
355 | return -ENOMEM; |
356 | |
357 | ret = set_sys_lock(priv); |
358 | if (ret) |
359 | return ret; |
360 | |
361 | /* Set OTP_PWR_DN to 0 to make OTP Operational */ |
362 | data = readl(addr: priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET)); |
363 | writel(val: data & ~OTP_PWR_DN_BIT, |
364 | addr: priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET)); |
365 | |
366 | dev_set_drvdata(dev: &aux_dev->dev, data: priv); |
367 | |
368 | if (is_eeprom_responsive(priv)) { |
369 | priv->nvmem_config_eeprom.type = NVMEM_TYPE_EEPROM; |
370 | priv->nvmem_config_eeprom.name = EEPROM_NAME; |
371 | priv->nvmem_config_eeprom.dev = &aux_dev->dev; |
372 | priv->nvmem_config_eeprom.owner = THIS_MODULE; |
373 | priv->nvmem_config_eeprom.reg_read = pci1xxxx_eeprom_read; |
374 | priv->nvmem_config_eeprom.reg_write = pci1xxxx_eeprom_write; |
375 | priv->nvmem_config_eeprom.priv = priv; |
376 | priv->nvmem_config_eeprom.stride = 1; |
377 | priv->nvmem_config_eeprom.word_size = 1; |
378 | priv->nvmem_config_eeprom.size = EEPROM_SIZE_BYTES; |
379 | |
380 | priv->nvmem_eeprom = devm_nvmem_register(dev: &aux_dev->dev, |
381 | cfg: &priv->nvmem_config_eeprom); |
382 | if (IS_ERR(ptr: priv->nvmem_eeprom)) |
383 | return PTR_ERR(ptr: priv->nvmem_eeprom); |
384 | } |
385 | |
386 | release_sys_lock(priv); |
387 | |
388 | priv->nvmem_config_otp.type = NVMEM_TYPE_OTP; |
389 | priv->nvmem_config_otp.name = OTP_NAME; |
390 | priv->nvmem_config_otp.dev = &aux_dev->dev; |
391 | priv->nvmem_config_otp.owner = THIS_MODULE; |
392 | priv->nvmem_config_otp.reg_read = pci1xxxx_otp_read; |
393 | priv->nvmem_config_otp.reg_write = pci1xxxx_otp_write; |
394 | priv->nvmem_config_otp.priv = priv; |
395 | priv->nvmem_config_otp.stride = 1; |
396 | priv->nvmem_config_otp.word_size = 1; |
397 | priv->nvmem_config_otp.size = OTP_SIZE_BYTES; |
398 | |
399 | priv->nvmem_otp = devm_nvmem_register(dev: &aux_dev->dev, |
400 | cfg: &priv->nvmem_config_otp); |
401 | if (IS_ERR(ptr: priv->nvmem_otp)) |
402 | return PTR_ERR(ptr: priv->nvmem_otp); |
403 | |
404 | return ret; |
405 | } |
406 | |
407 | static void pci1xxxx_otp_eeprom_remove(struct auxiliary_device *aux_dev) |
408 | { |
409 | struct pci1xxxx_otp_eeprom_device *priv; |
410 | void __iomem *sys_lock; |
411 | |
412 | priv = dev_get_drvdata(dev: &aux_dev->dev); |
413 | sys_lock = priv->reg_base + MMAP_CFG_OFFSET(CFG_SYS_LOCK_OFFSET); |
414 | writel(CFG_SYS_LOCK_PF3, addr: sys_lock); |
415 | |
416 | /* Shut down OTP */ |
417 | writel(OTP_PWR_DN_BIT, |
418 | addr: priv->reg_base + MMAP_OTP_OFFSET(OTP_PWR_DN_OFFSET)); |
419 | |
420 | writel(val: 0, addr: sys_lock); |
421 | } |
422 | |
423 | static const struct auxiliary_device_id pci1xxxx_otp_eeprom_auxiliary_id_table[] = { |
424 | {.name = "mchp_pci1xxxx_gp.gp_otp_e2p" }, |
425 | {}, |
426 | }; |
427 | MODULE_DEVICE_TABLE(auxiliary, pci1xxxx_otp_eeprom_auxiliary_id_table); |
428 | |
429 | static struct auxiliary_driver pci1xxxx_otp_eeprom_driver = { |
430 | .driver = { |
431 | .name = AUX_DRIVER_NAME, |
432 | }, |
433 | .probe = pci1xxxx_otp_eeprom_probe, |
434 | .remove = pci1xxxx_otp_eeprom_remove, |
435 | .id_table = pci1xxxx_otp_eeprom_auxiliary_id_table |
436 | }; |
437 | module_auxiliary_driver(pci1xxxx_otp_eeprom_driver); |
438 | |
439 | MODULE_LICENSE("GPL" ); |
440 | MODULE_AUTHOR("Kumaravel Thiagarajan <kumaravel.thiagarajan@microchip.com>" ); |
441 | MODULE_AUTHOR("Tharun Kumar P <tharunkumar.pasumarthi@microchip.com>" ); |
442 | MODULE_AUTHOR("Vaibhaav Ram T.L <vaibhaavram.tl@microchip.com>" ); |
443 | MODULE_DESCRIPTION("Microchip Technology Inc. PCI1xxxx OTP EEPROM Programmer" ); |
444 | |