1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * TQ-Systems PLD MFD core driver, based on vendor driver by |
4 | * Vadim V.Vlasov <vvlasov@dev.rtsoft.ru> |
5 | * |
6 | * Copyright (c) 2015 TQ-Systems GmbH |
7 | * Copyright (c) 2019 Andrew Lunn <andrew@lunn.ch> |
8 | */ |
9 | |
10 | #include <linux/delay.h> |
11 | #include <linux/dmi.h> |
12 | #include <linux/i2c.h> |
13 | #include <linux/io.h> |
14 | #include <linux/mfd/core.h> |
15 | #include <linux/module.h> |
16 | #include <linux/platform_data/i2c-ocores.h> |
17 | #include <linux/platform_device.h> |
18 | |
19 | #define TQMX86_IOBASE 0x180 |
20 | #define TQMX86_IOSIZE 0x20 |
21 | #define TQMX86_IOBASE_I2C 0x1a0 |
22 | #define TQMX86_IOSIZE_I2C 0xa |
23 | #define TQMX86_IOBASE_WATCHDOG 0x18b |
24 | #define TQMX86_IOSIZE_WATCHDOG 0x2 |
25 | #define TQMX86_IOBASE_GPIO 0x18d |
26 | #define TQMX86_IOSIZE_GPIO 0x4 |
27 | |
28 | #define TQMX86_REG_BOARD_ID 0x00 |
29 | #define TQMX86_REG_BOARD_ID_E38M 1 |
30 | #define TQMX86_REG_BOARD_ID_50UC 2 |
31 | #define TQMX86_REG_BOARD_ID_E38C 3 |
32 | #define TQMX86_REG_BOARD_ID_60EB 4 |
33 | #define TQMX86_REG_BOARD_ID_E39MS 5 |
34 | #define TQMX86_REG_BOARD_ID_E39C1 6 |
35 | #define TQMX86_REG_BOARD_ID_E39C2 7 |
36 | #define TQMX86_REG_BOARD_ID_70EB 8 |
37 | #define TQMX86_REG_BOARD_ID_80UC 9 |
38 | #define TQMX86_REG_BOARD_ID_110EB 11 |
39 | #define TQMX86_REG_BOARD_ID_E40M 12 |
40 | #define TQMX86_REG_BOARD_ID_E40S 13 |
41 | #define TQMX86_REG_BOARD_ID_E40C1 14 |
42 | #define TQMX86_REG_BOARD_ID_E40C2 15 |
43 | #define TQMX86_REG_BOARD_REV 0x01 |
44 | #define TQMX86_REG_IO_EXT_INT 0x06 |
45 | #define TQMX86_REG_IO_EXT_INT_NONE 0 |
46 | #define TQMX86_REG_IO_EXT_INT_7 1 |
47 | #define TQMX86_REG_IO_EXT_INT_9 2 |
48 | #define TQMX86_REG_IO_EXT_INT_12 3 |
49 | #define TQMX86_REG_IO_EXT_INT_MASK 0x3 |
50 | #define TQMX86_REG_IO_EXT_INT_GPIO_SHIFT 4 |
51 | #define TQMX86_REG_SAUC 0x17 |
52 | |
53 | #define TQMX86_REG_I2C_DETECT 0x1a7 |
54 | #define TQMX86_REG_I2C_DETECT_SOFT 0xa5 |
55 | |
56 | static uint gpio_irq; |
57 | module_param(gpio_irq, uint, 0); |
58 | MODULE_PARM_DESC(gpio_irq, "GPIO IRQ number (7, 9, 12)" ); |
59 | |
60 | static const struct resource tqmx_i2c_soft_resources[] = { |
61 | DEFINE_RES_IO(TQMX86_IOBASE_I2C, TQMX86_IOSIZE_I2C), |
62 | }; |
63 | |
64 | static const struct resource tqmx_watchdog_resources[] = { |
65 | DEFINE_RES_IO(TQMX86_IOBASE_WATCHDOG, TQMX86_IOSIZE_WATCHDOG), |
66 | }; |
67 | |
68 | /* |
69 | * The IRQ resource must be first, since it is updated with the |
70 | * configured IRQ in the probe function. |
71 | */ |
72 | static struct resource tqmx_gpio_resources[] = { |
73 | DEFINE_RES_IRQ(0), |
74 | DEFINE_RES_IO(TQMX86_IOBASE_GPIO, TQMX86_IOSIZE_GPIO), |
75 | }; |
76 | |
77 | static struct i2c_board_info tqmx86_i2c_devices[] = { |
78 | { |
79 | /* 4K EEPROM at 0x50 */ |
80 | I2C_BOARD_INFO("24c32" , 0x50), |
81 | }, |
82 | }; |
83 | |
84 | static struct ocores_i2c_platform_data ocores_platform_data = { |
85 | .num_devices = ARRAY_SIZE(tqmx86_i2c_devices), |
86 | .devices = tqmx86_i2c_devices, |
87 | }; |
88 | |
89 | static const struct mfd_cell tqmx86_i2c_soft_dev[] = { |
90 | { |
91 | .name = "ocores-i2c" , |
92 | .platform_data = &ocores_platform_data, |
93 | .pdata_size = sizeof(ocores_platform_data), |
94 | .resources = tqmx_i2c_soft_resources, |
95 | .num_resources = ARRAY_SIZE(tqmx_i2c_soft_resources), |
96 | }, |
97 | }; |
98 | |
99 | static const struct mfd_cell tqmx86_devs[] = { |
100 | { |
101 | .name = "tqmx86-wdt" , |
102 | .resources = tqmx_watchdog_resources, |
103 | .num_resources = ARRAY_SIZE(tqmx_watchdog_resources), |
104 | .ignore_resource_conflicts = true, |
105 | }, |
106 | { |
107 | .name = "tqmx86-gpio" , |
108 | .resources = tqmx_gpio_resources, |
109 | .num_resources = ARRAY_SIZE(tqmx_gpio_resources), |
110 | .ignore_resource_conflicts = true, |
111 | }, |
112 | }; |
113 | |
114 | static const char *tqmx86_board_id_to_name(u8 board_id, u8 sauc) |
115 | { |
116 | switch (board_id) { |
117 | case TQMX86_REG_BOARD_ID_E38M: |
118 | return "TQMxE38M" ; |
119 | case TQMX86_REG_BOARD_ID_50UC: |
120 | return "TQMx50UC" ; |
121 | case TQMX86_REG_BOARD_ID_E38C: |
122 | return "TQMxE38C" ; |
123 | case TQMX86_REG_BOARD_ID_60EB: |
124 | return "TQMx60EB" ; |
125 | case TQMX86_REG_BOARD_ID_E39MS: |
126 | return (sauc == 0xff) ? "TQMxE39M" : "TQMxE39S" ; |
127 | case TQMX86_REG_BOARD_ID_E39C1: |
128 | return "TQMxE39C1" ; |
129 | case TQMX86_REG_BOARD_ID_E39C2: |
130 | return "TQMxE39C2" ; |
131 | case TQMX86_REG_BOARD_ID_70EB: |
132 | return "TQMx70EB" ; |
133 | case TQMX86_REG_BOARD_ID_80UC: |
134 | return "TQMx80UC" ; |
135 | case TQMX86_REG_BOARD_ID_110EB: |
136 | return "TQMx110EB" ; |
137 | case TQMX86_REG_BOARD_ID_E40M: |
138 | return "TQMxE40M" ; |
139 | case TQMX86_REG_BOARD_ID_E40S: |
140 | return "TQMxE40S" ; |
141 | case TQMX86_REG_BOARD_ID_E40C1: |
142 | return "TQMxE40C1" ; |
143 | case TQMX86_REG_BOARD_ID_E40C2: |
144 | return "TQMxE40C2" ; |
145 | default: |
146 | return "Unknown" ; |
147 | } |
148 | } |
149 | |
150 | static int tqmx86_board_id_to_clk_rate(struct device *dev, u8 board_id) |
151 | { |
152 | switch (board_id) { |
153 | case TQMX86_REG_BOARD_ID_50UC: |
154 | case TQMX86_REG_BOARD_ID_60EB: |
155 | case TQMX86_REG_BOARD_ID_70EB: |
156 | case TQMX86_REG_BOARD_ID_80UC: |
157 | case TQMX86_REG_BOARD_ID_110EB: |
158 | case TQMX86_REG_BOARD_ID_E40M: |
159 | case TQMX86_REG_BOARD_ID_E40S: |
160 | case TQMX86_REG_BOARD_ID_E40C1: |
161 | case TQMX86_REG_BOARD_ID_E40C2: |
162 | return 24000; |
163 | case TQMX86_REG_BOARD_ID_E39MS: |
164 | case TQMX86_REG_BOARD_ID_E39C1: |
165 | case TQMX86_REG_BOARD_ID_E39C2: |
166 | return 25000; |
167 | case TQMX86_REG_BOARD_ID_E38M: |
168 | case TQMX86_REG_BOARD_ID_E38C: |
169 | return 33000; |
170 | default: |
171 | dev_warn(dev, "unknown board %d, assuming 24MHz LPC clock\n" , |
172 | board_id); |
173 | return 24000; |
174 | } |
175 | } |
176 | |
177 | static int tqmx86_probe(struct platform_device *pdev) |
178 | { |
179 | u8 board_id, sauc, rev, i2c_det, io_ext_int_val; |
180 | struct device *dev = &pdev->dev; |
181 | u8 gpio_irq_cfg, readback; |
182 | const char *board_name; |
183 | void __iomem *io_base; |
184 | int err; |
185 | |
186 | switch (gpio_irq) { |
187 | case 0: |
188 | gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_NONE; |
189 | break; |
190 | case 7: |
191 | gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_7; |
192 | break; |
193 | case 9: |
194 | gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_9; |
195 | break; |
196 | case 12: |
197 | gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_12; |
198 | break; |
199 | default: |
200 | pr_err("tqmx86: Invalid GPIO IRQ (%d)\n" , gpio_irq); |
201 | return -EINVAL; |
202 | } |
203 | |
204 | io_base = devm_ioport_map(dev, TQMX86_IOBASE, TQMX86_IOSIZE); |
205 | if (!io_base) |
206 | return -ENOMEM; |
207 | |
208 | board_id = ioread8(io_base + TQMX86_REG_BOARD_ID); |
209 | sauc = ioread8(io_base + TQMX86_REG_SAUC); |
210 | board_name = tqmx86_board_id_to_name(board_id, sauc); |
211 | rev = ioread8(io_base + TQMX86_REG_BOARD_REV); |
212 | |
213 | dev_info(dev, |
214 | "Found %s - Board ID %d, PCB Revision %d, PLD Revision %d\n" , |
215 | board_name, board_id, rev >> 4, rev & 0xf); |
216 | |
217 | /* |
218 | * The I2C_DETECT register is in the range assigned to the I2C driver |
219 | * later, so we don't extend TQMX86_IOSIZE. Use inb() for this one-off |
220 | * access instead of ioport_map + unmap. |
221 | */ |
222 | i2c_det = inb(TQMX86_REG_I2C_DETECT); |
223 | |
224 | if (gpio_irq_cfg) { |
225 | io_ext_int_val = |
226 | gpio_irq_cfg << TQMX86_REG_IO_EXT_INT_GPIO_SHIFT; |
227 | iowrite8(io_ext_int_val, io_base + TQMX86_REG_IO_EXT_INT); |
228 | readback = ioread8(io_base + TQMX86_REG_IO_EXT_INT); |
229 | if (readback != io_ext_int_val) { |
230 | dev_warn(dev, "GPIO interrupts not supported.\n" ); |
231 | return -EINVAL; |
232 | } |
233 | |
234 | /* Assumes the IRQ resource is first. */ |
235 | tqmx_gpio_resources[0].start = gpio_irq; |
236 | } else { |
237 | tqmx_gpio_resources[0].flags = 0; |
238 | } |
239 | |
240 | ocores_platform_data.clock_khz = tqmx86_board_id_to_clk_rate(dev, board_id); |
241 | |
242 | if (i2c_det == TQMX86_REG_I2C_DETECT_SOFT) { |
243 | err = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, |
244 | cells: tqmx86_i2c_soft_dev, |
245 | ARRAY_SIZE(tqmx86_i2c_soft_dev), |
246 | NULL, irq_base: 0, NULL); |
247 | if (err) |
248 | return err; |
249 | } |
250 | |
251 | return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, |
252 | cells: tqmx86_devs, |
253 | ARRAY_SIZE(tqmx86_devs), |
254 | NULL, irq_base: 0, NULL); |
255 | } |
256 | |
257 | static int tqmx86_create_platform_device(const struct dmi_system_id *id) |
258 | { |
259 | struct platform_device *pdev; |
260 | int err; |
261 | |
262 | pdev = platform_device_alloc(name: "tqmx86" , id: -1); |
263 | if (!pdev) |
264 | return -ENOMEM; |
265 | |
266 | err = platform_device_add(pdev); |
267 | if (err) |
268 | platform_device_put(pdev); |
269 | |
270 | return err; |
271 | } |
272 | |
273 | static const struct dmi_system_id tqmx86_dmi_table[] __initconst = { |
274 | { |
275 | .ident = "TQMX86" , |
276 | .matches = { |
277 | DMI_MATCH(DMI_SYS_VENDOR, "TQ-Group" ), |
278 | DMI_MATCH(DMI_PRODUCT_NAME, "TQMx" ), |
279 | }, |
280 | .callback = tqmx86_create_platform_device, |
281 | }, |
282 | { |
283 | .ident = "TQMX86" , |
284 | .matches = { |
285 | DMI_MATCH(DMI_SYS_VENDOR, "TQ-Systems" ), |
286 | DMI_MATCH(DMI_PRODUCT_NAME, "TQMx" ), |
287 | }, |
288 | .callback = tqmx86_create_platform_device, |
289 | }, |
290 | {} |
291 | }; |
292 | MODULE_DEVICE_TABLE(dmi, tqmx86_dmi_table); |
293 | |
294 | static struct platform_driver tqmx86_driver = { |
295 | .driver = { |
296 | .name = "tqmx86" , |
297 | }, |
298 | .probe = tqmx86_probe, |
299 | }; |
300 | |
301 | static int __init tqmx86_init(void) |
302 | { |
303 | if (!dmi_check_system(list: tqmx86_dmi_table)) |
304 | return -ENODEV; |
305 | |
306 | return platform_driver_register(&tqmx86_driver); |
307 | } |
308 | |
309 | module_init(tqmx86_init); |
310 | |
311 | MODULE_DESCRIPTION("TQMx86 PLD Core Driver" ); |
312 | MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>" ); |
313 | MODULE_LICENSE("GPL" ); |
314 | MODULE_ALIAS("platform:tqmx86" ); |
315 | |