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 | ||
19 | #define TQMX86_IOBASE 0x160 | |
20 | #define TQMX86_IOSIZE 0x3f | |
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 0x20 | |
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_E39M 5 | |
34 | #define TQMX86_REG_BOARD_ID_E39C 6 | |
35 | #define TQMX86_REG_BOARD_ID_E39x 7 | |
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 | |
2f17dd34 AL |
43 | #define TQMX86_REG_BOARD_REV 0x21 |
44 | #define TQMX86_REG_IO_EXT_INT 0x26 | |
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 | ||
52 | #define TQMX86_REG_I2C_DETECT 0x47 | |
53 | #define TQMX86_REG_I2C_DETECT_SOFT 0xa5 | |
54 | #define TQMX86_REG_I2C_INT_EN 0x49 | |
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 | ||
114 | static const char *tqmx86_board_id_to_name(u8 board_id) | |
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_E39M: | |
126 | return "TQMxE39M"; | |
127 | case TQMX86_REG_BOARD_ID_E39C: | |
128 | return "TQMxE39C"; | |
129 | case TQMX86_REG_BOARD_ID_E39x: | |
130 | return "TQMxE39x"; | |
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 AL |
162 | return 24000; |
163 | case TQMX86_REG_BOARD_ID_E39M: | |
164 | case TQMX86_REG_BOARD_ID_E39C: | |
165 | case TQMX86_REG_BOARD_ID_E39x: | |
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 | { | |
7ad2915f | 179 | u8 board_id, 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); | |
209 | board_name = tqmx86_board_id_to_name(board_id); | |
210 | rev = ioread8(io_base + TQMX86_REG_BOARD_REV); | |
211 | ||
212 | dev_info(dev, | |
213 | "Found %s - Board ID %d, PCB Revision %d, PLD Revision %d\n", | |
214 | board_name, board_id, rev >> 4, rev & 0xf); | |
215 | ||
216 | i2c_det = ioread8(io_base + TQMX86_REG_I2C_DETECT); | |
2f17dd34 AL |
217 | |
218 | if (gpio_irq_cfg) { | |
219 | io_ext_int_val = | |
220 | gpio_irq_cfg << TQMX86_REG_IO_EXT_INT_GPIO_SHIFT; | |
221 | iowrite8(io_ext_int_val, io_base + TQMX86_REG_IO_EXT_INT); | |
222 | readback = ioread8(io_base + TQMX86_REG_IO_EXT_INT); | |
223 | if (readback != io_ext_int_val) { | |
224 | dev_warn(dev, "GPIO interrupts not supported.\n"); | |
225 | return -EINVAL; | |
226 | } | |
227 | ||
228 | /* Assumes the IRQ resource is first. */ | |
229 | tqmx_gpio_resources[0].start = gpio_irq; | |
a946506c MS |
230 | } else { |
231 | tqmx_gpio_resources[0].flags = 0; | |
2f17dd34 AL |
232 | } |
233 | ||
9a8c4bac | 234 | ocores_platform_data.clock_khz = tqmx86_board_id_to_clk_rate(dev, board_id); |
2f17dd34 AL |
235 | |
236 | if (i2c_det == TQMX86_REG_I2C_DETECT_SOFT) { | |
237 | err = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
238 | tqmx86_i2c_soft_dev, | |
239 | ARRAY_SIZE(tqmx86_i2c_soft_dev), | |
240 | NULL, 0, NULL); | |
241 | if (err) | |
242 | return err; | |
243 | } | |
244 | ||
245 | return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
246 | tqmx86_devs, | |
247 | ARRAY_SIZE(tqmx86_devs), | |
248 | NULL, 0, NULL); | |
249 | } | |
250 | ||
251 | static int tqmx86_create_platform_device(const struct dmi_system_id *id) | |
252 | { | |
253 | struct platform_device *pdev; | |
254 | int err; | |
255 | ||
256 | pdev = platform_device_alloc("tqmx86", -1); | |
257 | if (!pdev) | |
258 | return -ENOMEM; | |
259 | ||
260 | err = platform_device_add(pdev); | |
261 | if (err) | |
262 | platform_device_put(pdev); | |
263 | ||
264 | return err; | |
265 | } | |
266 | ||
267 | static const struct dmi_system_id tqmx86_dmi_table[] __initconst = { | |
268 | { | |
269 | .ident = "TQMX86", | |
270 | .matches = { | |
271 | DMI_MATCH(DMI_SYS_VENDOR, "TQ-Group"), | |
272 | DMI_MATCH(DMI_PRODUCT_NAME, "TQMx"), | |
273 | }, | |
274 | .callback = tqmx86_create_platform_device, | |
275 | }, | |
d5949a35 MS |
276 | { |
277 | .ident = "TQMX86", | |
278 | .matches = { | |
279 | DMI_MATCH(DMI_SYS_VENDOR, "TQ-Systems"), | |
280 | DMI_MATCH(DMI_PRODUCT_NAME, "TQMx"), | |
281 | }, | |
282 | .callback = tqmx86_create_platform_device, | |
283 | }, | |
2f17dd34 AL |
284 | {} |
285 | }; | |
286 | MODULE_DEVICE_TABLE(dmi, tqmx86_dmi_table); | |
287 | ||
288 | static struct platform_driver tqmx86_driver = { | |
289 | .driver = { | |
290 | .name = "tqmx86", | |
291 | }, | |
292 | .probe = tqmx86_probe, | |
293 | }; | |
294 | ||
295 | static int __init tqmx86_init(void) | |
296 | { | |
297 | if (!dmi_check_system(tqmx86_dmi_table)) | |
298 | return -ENODEV; | |
299 | ||
300 | return platform_driver_register(&tqmx86_driver); | |
301 | } | |
302 | ||
303 | module_init(tqmx86_init); | |
304 | ||
ff8bd0b5 | 305 | MODULE_DESCRIPTION("TQMx86 PLD Core Driver"); |
2f17dd34 AL |
306 | MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); |
307 | MODULE_LICENSE("GPL"); | |
308 | MODULE_ALIAS("platform:tqmx86"); |