Commit | Line | Data |
---|---|---|
2f17dd34 AL |
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 | ||
051c69ff MS |
19 | #define TQMX86_IOBASE 0x180 |
20 | #define TQMX86_IOSIZE 0x20 | |
2f17dd34 AL |
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 | ||
051c69ff | 28 | #define TQMX86_REG_BOARD_ID 0x00 |
2f17dd34 AL |
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 | |
f376c479 MS |
33 | #define TQMX86_REG_BOARD_ID_E39MS 5 |
34 | #define TQMX86_REG_BOARD_ID_E39C1 6 | |
35 | #define TQMX86_REG_BOARD_ID_E39C2 7 | |
2f17dd34 AL |
36 | #define TQMX86_REG_BOARD_ID_70EB 8 |
37 | #define TQMX86_REG_BOARD_ID_80UC 9 | |
3da48ccb MS |
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 | |
051c69ff MS |
43 | #define TQMX86_REG_BOARD_REV 0x01 |
44 | #define TQMX86_REG_IO_EXT_INT 0x06 | |
2f17dd34 AL |
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 | |
f376c479 | 51 | #define TQMX86_REG_SAUC 0x17 |
2f17dd34 | 52 | |
1be1b236 | 53 | #define TQMX86_REG_I2C_DETECT 0x1a7 |
2f17dd34 | 54 | #define TQMX86_REG_I2C_DETECT_SOFT 0xa5 |
2f17dd34 AL |
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 | ||
41e9b5e2 | 84 | static struct ocores_i2c_platform_data ocores_platform_data = { |
2f17dd34 AL |
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", | |
41e9b5e2 MS |
92 | .platform_data = &ocores_platform_data, |
93 | .pdata_size = sizeof(ocores_platform_data), | |
2f17dd34 AL |
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 | ||
f376c479 | 114 | static const char *tqmx86_board_id_to_name(u8 board_id, u8 sauc) |
2f17dd34 AL |
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"; | |
f376c479 MS |
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"; | |
2f17dd34 AL |
131 | case TQMX86_REG_BOARD_ID_70EB: |
132 | return "TQMx70EB"; | |
133 | case TQMX86_REG_BOARD_ID_80UC: | |
134 | return "TQMx80UC"; | |
3da48ccb MS |
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"; | |
2f17dd34 AL |
145 | default: |
146 | return "Unknown"; | |
147 | } | |
148 | } | |
149 | ||
9a8c4bac | 150 | static int tqmx86_board_id_to_clk_rate(struct device *dev, u8 board_id) |
2f17dd34 AL |
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: | |
3da48ccb MS |
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: | |
2f17dd34 | 162 | return 24000; |
f376c479 MS |
163 | case TQMX86_REG_BOARD_ID_E39MS: |
164 | case TQMX86_REG_BOARD_ID_E39C1: | |
165 | case TQMX86_REG_BOARD_ID_E39C2: | |
2f17dd34 AL |
166 | return 25000; |
167 | case TQMX86_REG_BOARD_ID_E38M: | |
168 | case TQMX86_REG_BOARD_ID_E38C: | |
169 | return 33000; | |
170 | default: | |
9a8c4bac MS |
171 | dev_warn(dev, "unknown board %d, assuming 24MHz LPC clock\n", |
172 | board_id); | |
173 | return 24000; | |
2f17dd34 AL |
174 | } |
175 | } | |
176 | ||
177 | static int tqmx86_probe(struct platform_device *pdev) | |
178 | { | |
f376c479 | 179 | u8 board_id, sauc, rev, i2c_det, io_ext_int_val; |
2f17dd34 AL |
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); | |
f376c479 MS |
209 | sauc = ioread8(io_base + TQMX86_REG_SAUC); |
210 | board_name = tqmx86_board_id_to_name(board_id, sauc); | |
2f17dd34 AL |
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 | ||
1be1b236 MS |
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); | |
2f17dd34 AL |
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; | |
a946506c MS |
236 | } else { |
237 | tqmx_gpio_resources[0].flags = 0; | |
2f17dd34 AL |
238 | } |
239 | ||
9a8c4bac | 240 | ocores_platform_data.clock_khz = tqmx86_board_id_to_clk_rate(dev, board_id); |
2f17dd34 AL |
241 | |
242 | if (i2c_det == TQMX86_REG_I2C_DETECT_SOFT) { | |
243 | err = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
244 | tqmx86_i2c_soft_dev, | |
245 | ARRAY_SIZE(tqmx86_i2c_soft_dev), | |
246 | NULL, 0, NULL); | |
247 | if (err) | |
248 | return err; | |
249 | } | |
250 | ||
251 | return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
252 | tqmx86_devs, | |
253 | ARRAY_SIZE(tqmx86_devs), | |
254 | NULL, 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("tqmx86", -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 | }, | |
d5949a35 MS |
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 | }, | |
2f17dd34 AL |
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(tqmx86_dmi_table)) | |
304 | return -ENODEV; | |
305 | ||
306 | return platform_driver_register(&tqmx86_driver); | |
307 | } | |
308 | ||
309 | module_init(tqmx86_init); | |
310 | ||
ff8bd0b5 | 311 | MODULE_DESCRIPTION("TQMx86 PLD Core Driver"); |
2f17dd34 AL |
312 | MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); |
313 | MODULE_LICENSE("GPL"); | |
314 | MODULE_ALIAS("platform:tqmx86"); |