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 | |
38 | #define TQMX86_REG_BOARD_ID_90UC 10 | |
39 | #define TQMX86_REG_BOARD_REV 0x21 | |
40 | #define TQMX86_REG_IO_EXT_INT 0x26 | |
41 | #define TQMX86_REG_IO_EXT_INT_NONE 0 | |
42 | #define TQMX86_REG_IO_EXT_INT_7 1 | |
43 | #define TQMX86_REG_IO_EXT_INT_9 2 | |
44 | #define TQMX86_REG_IO_EXT_INT_12 3 | |
45 | #define TQMX86_REG_IO_EXT_INT_MASK 0x3 | |
46 | #define TQMX86_REG_IO_EXT_INT_GPIO_SHIFT 4 | |
47 | ||
48 | #define TQMX86_REG_I2C_DETECT 0x47 | |
49 | #define TQMX86_REG_I2C_DETECT_SOFT 0xa5 | |
50 | #define TQMX86_REG_I2C_INT_EN 0x49 | |
51 | ||
52 | static uint gpio_irq; | |
53 | module_param(gpio_irq, uint, 0); | |
54 | MODULE_PARM_DESC(gpio_irq, "GPIO IRQ number (7, 9, 12)"); | |
55 | ||
56 | static const struct resource tqmx_i2c_soft_resources[] = { | |
57 | DEFINE_RES_IO(TQMX86_IOBASE_I2C, TQMX86_IOSIZE_I2C), | |
58 | }; | |
59 | ||
60 | static const struct resource tqmx_watchdog_resources[] = { | |
61 | DEFINE_RES_IO(TQMX86_IOBASE_WATCHDOG, TQMX86_IOSIZE_WATCHDOG), | |
62 | }; | |
63 | ||
64 | /* | |
65 | * The IRQ resource must be first, since it is updated with the | |
66 | * configured IRQ in the probe function. | |
67 | */ | |
68 | static struct resource tqmx_gpio_resources[] = { | |
69 | DEFINE_RES_IRQ(0), | |
70 | DEFINE_RES_IO(TQMX86_IOBASE_GPIO, TQMX86_IOSIZE_GPIO), | |
71 | }; | |
72 | ||
73 | static struct i2c_board_info tqmx86_i2c_devices[] = { | |
74 | { | |
75 | /* 4K EEPROM at 0x50 */ | |
76 | I2C_BOARD_INFO("24c32", 0x50), | |
77 | }, | |
78 | }; | |
79 | ||
80 | static struct ocores_i2c_platform_data ocores_platfom_data = { | |
81 | .num_devices = ARRAY_SIZE(tqmx86_i2c_devices), | |
82 | .devices = tqmx86_i2c_devices, | |
83 | }; | |
84 | ||
85 | static const struct mfd_cell tqmx86_i2c_soft_dev[] = { | |
86 | { | |
87 | .name = "ocores-i2c", | |
88 | .platform_data = &ocores_platfom_data, | |
89 | .pdata_size = sizeof(ocores_platfom_data), | |
90 | .resources = tqmx_i2c_soft_resources, | |
91 | .num_resources = ARRAY_SIZE(tqmx_i2c_soft_resources), | |
92 | }, | |
93 | }; | |
94 | ||
95 | static const struct mfd_cell tqmx86_devs[] = { | |
96 | { | |
97 | .name = "tqmx86-wdt", | |
98 | .resources = tqmx_watchdog_resources, | |
99 | .num_resources = ARRAY_SIZE(tqmx_watchdog_resources), | |
100 | .ignore_resource_conflicts = true, | |
101 | }, | |
102 | { | |
103 | .name = "tqmx86-gpio", | |
104 | .resources = tqmx_gpio_resources, | |
105 | .num_resources = ARRAY_SIZE(tqmx_gpio_resources), | |
106 | .ignore_resource_conflicts = true, | |
107 | }, | |
108 | }; | |
109 | ||
110 | static const char *tqmx86_board_id_to_name(u8 board_id) | |
111 | { | |
112 | switch (board_id) { | |
113 | case TQMX86_REG_BOARD_ID_E38M: | |
114 | return "TQMxE38M"; | |
115 | case TQMX86_REG_BOARD_ID_50UC: | |
116 | return "TQMx50UC"; | |
117 | case TQMX86_REG_BOARD_ID_E38C: | |
118 | return "TQMxE38C"; | |
119 | case TQMX86_REG_BOARD_ID_60EB: | |
120 | return "TQMx60EB"; | |
121 | case TQMX86_REG_BOARD_ID_E39M: | |
122 | return "TQMxE39M"; | |
123 | case TQMX86_REG_BOARD_ID_E39C: | |
124 | return "TQMxE39C"; | |
125 | case TQMX86_REG_BOARD_ID_E39x: | |
126 | return "TQMxE39x"; | |
127 | case TQMX86_REG_BOARD_ID_70EB: | |
128 | return "TQMx70EB"; | |
129 | case TQMX86_REG_BOARD_ID_80UC: | |
130 | return "TQMx80UC"; | |
131 | case TQMX86_REG_BOARD_ID_90UC: | |
132 | return "TQMx90UC"; | |
133 | default: | |
134 | return "Unknown"; | |
135 | } | |
136 | } | |
137 | ||
138 | static int tqmx86_board_id_to_clk_rate(u8 board_id) | |
139 | { | |
140 | switch (board_id) { | |
141 | case TQMX86_REG_BOARD_ID_50UC: | |
142 | case TQMX86_REG_BOARD_ID_60EB: | |
143 | case TQMX86_REG_BOARD_ID_70EB: | |
144 | case TQMX86_REG_BOARD_ID_80UC: | |
145 | case TQMX86_REG_BOARD_ID_90UC: | |
146 | return 24000; | |
147 | case TQMX86_REG_BOARD_ID_E39M: | |
148 | case TQMX86_REG_BOARD_ID_E39C: | |
149 | case TQMX86_REG_BOARD_ID_E39x: | |
150 | return 25000; | |
151 | case TQMX86_REG_BOARD_ID_E38M: | |
152 | case TQMX86_REG_BOARD_ID_E38C: | |
153 | return 33000; | |
154 | default: | |
155 | return 0; | |
156 | } | |
157 | } | |
158 | ||
159 | static int tqmx86_probe(struct platform_device *pdev) | |
160 | { | |
161 | u8 board_id, rev, i2c_det, i2c_ien, io_ext_int_val; | |
162 | struct device *dev = &pdev->dev; | |
163 | u8 gpio_irq_cfg, readback; | |
164 | const char *board_name; | |
165 | void __iomem *io_base; | |
166 | int err; | |
167 | ||
168 | switch (gpio_irq) { | |
169 | case 0: | |
170 | gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_NONE; | |
171 | break; | |
172 | case 7: | |
173 | gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_7; | |
174 | break; | |
175 | case 9: | |
176 | gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_9; | |
177 | break; | |
178 | case 12: | |
179 | gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_12; | |
180 | break; | |
181 | default: | |
182 | pr_err("tqmx86: Invalid GPIO IRQ (%d)\n", gpio_irq); | |
183 | return -EINVAL; | |
184 | } | |
185 | ||
186 | io_base = devm_ioport_map(dev, TQMX86_IOBASE, TQMX86_IOSIZE); | |
187 | if (!io_base) | |
188 | return -ENOMEM; | |
189 | ||
190 | board_id = ioread8(io_base + TQMX86_REG_BOARD_ID); | |
191 | board_name = tqmx86_board_id_to_name(board_id); | |
192 | rev = ioread8(io_base + TQMX86_REG_BOARD_REV); | |
193 | ||
194 | dev_info(dev, | |
195 | "Found %s - Board ID %d, PCB Revision %d, PLD Revision %d\n", | |
196 | board_name, board_id, rev >> 4, rev & 0xf); | |
197 | ||
198 | i2c_det = ioread8(io_base + TQMX86_REG_I2C_DETECT); | |
199 | i2c_ien = ioread8(io_base + TQMX86_REG_I2C_INT_EN); | |
200 | ||
201 | if (gpio_irq_cfg) { | |
202 | io_ext_int_val = | |
203 | gpio_irq_cfg << TQMX86_REG_IO_EXT_INT_GPIO_SHIFT; | |
204 | iowrite8(io_ext_int_val, io_base + TQMX86_REG_IO_EXT_INT); | |
205 | readback = ioread8(io_base + TQMX86_REG_IO_EXT_INT); | |
206 | if (readback != io_ext_int_val) { | |
207 | dev_warn(dev, "GPIO interrupts not supported.\n"); | |
208 | return -EINVAL; | |
209 | } | |
210 | ||
211 | /* Assumes the IRQ resource is first. */ | |
212 | tqmx_gpio_resources[0].start = gpio_irq; | |
213 | } | |
214 | ||
215 | ocores_platfom_data.clock_khz = tqmx86_board_id_to_clk_rate(board_id); | |
216 | ||
217 | if (i2c_det == TQMX86_REG_I2C_DETECT_SOFT) { | |
218 | err = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
219 | tqmx86_i2c_soft_dev, | |
220 | ARRAY_SIZE(tqmx86_i2c_soft_dev), | |
221 | NULL, 0, NULL); | |
222 | if (err) | |
223 | return err; | |
224 | } | |
225 | ||
226 | return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
227 | tqmx86_devs, | |
228 | ARRAY_SIZE(tqmx86_devs), | |
229 | NULL, 0, NULL); | |
230 | } | |
231 | ||
232 | static int tqmx86_create_platform_device(const struct dmi_system_id *id) | |
233 | { | |
234 | struct platform_device *pdev; | |
235 | int err; | |
236 | ||
237 | pdev = platform_device_alloc("tqmx86", -1); | |
238 | if (!pdev) | |
239 | return -ENOMEM; | |
240 | ||
241 | err = platform_device_add(pdev); | |
242 | if (err) | |
243 | platform_device_put(pdev); | |
244 | ||
245 | return err; | |
246 | } | |
247 | ||
248 | static const struct dmi_system_id tqmx86_dmi_table[] __initconst = { | |
249 | { | |
250 | .ident = "TQMX86", | |
251 | .matches = { | |
252 | DMI_MATCH(DMI_SYS_VENDOR, "TQ-Group"), | |
253 | DMI_MATCH(DMI_PRODUCT_NAME, "TQMx"), | |
254 | }, | |
255 | .callback = tqmx86_create_platform_device, | |
256 | }, | |
257 | {} | |
258 | }; | |
259 | MODULE_DEVICE_TABLE(dmi, tqmx86_dmi_table); | |
260 | ||
261 | static struct platform_driver tqmx86_driver = { | |
262 | .driver = { | |
263 | .name = "tqmx86", | |
264 | }, | |
265 | .probe = tqmx86_probe, | |
266 | }; | |
267 | ||
268 | static int __init tqmx86_init(void) | |
269 | { | |
270 | if (!dmi_check_system(tqmx86_dmi_table)) | |
271 | return -ENODEV; | |
272 | ||
273 | return platform_driver_register(&tqmx86_driver); | |
274 | } | |
275 | ||
276 | module_init(tqmx86_init); | |
277 | ||
278 | MODULE_DESCRIPTION("TQx86 PLD Core Driver"); | |
279 | MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); | |
280 | MODULE_LICENSE("GPL"); | |
281 | MODULE_ALIAS("platform:tqmx86"); |