]>
Commit | Line | Data |
---|---|---|
7d029125 VD |
1 | /* |
2 | * Technologic Systems TS-5500 Single Board Computer support | |
3 | * | |
84e288d4 | 4 | * Copyright (C) 2013-2014 Savoir-faire Linux Inc. |
7d029125 VD |
5 | * Vivien Didelot <vivien.didelot@savoirfairelinux.com> |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it under | |
8 | * the terms of the GNU General Public License as published by the Free Software | |
9 | * Foundation; either version 2 of the License, or (at your option) any later | |
10 | * version. | |
11 | * | |
12 | * | |
13 | * This driver registers the Technologic Systems TS-5500 Single Board Computer | |
14 | * (SBC) and its devices, and exposes information to userspace such as jumpers' | |
15 | * state or available options. For further information about sysfs entries, see | |
16 | * Documentation/ABI/testing/sysfs-platform-ts5500. | |
17 | * | |
832fcc89 VD |
18 | * This code may be extended to support similar x86-based platforms. |
19 | * Actually, the TS-5500 and TS-5400 are supported. | |
7d029125 VD |
20 | */ |
21 | ||
22 | #include <linux/delay.h> | |
23 | #include <linux/io.h> | |
24 | #include <linux/kernel.h> | |
25 | #include <linux/leds.h> | |
cc3ae7b0 | 26 | #include <linux/init.h> |
7d029125 VD |
27 | #include <linux/platform_data/gpio-ts5500.h> |
28 | #include <linux/platform_data/max197.h> | |
29 | #include <linux/platform_device.h> | |
30 | #include <linux/slab.h> | |
31 | ||
32 | /* Product code register */ | |
33 | #define TS5500_PRODUCT_CODE_ADDR 0x74 | |
34 | #define TS5500_PRODUCT_CODE 0x60 /* TS-5500 product code */ | |
832fcc89 | 35 | #define TS5400_PRODUCT_CODE 0x40 /* TS-5400 product code */ |
7d029125 VD |
36 | |
37 | /* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */ | |
38 | #define TS5500_SRAM_RS485_ADC_ADDR 0x75 | |
39 | #define TS5500_SRAM BIT(0) /* SRAM option */ | |
40 | #define TS5500_RS485 BIT(1) /* RS-485 option */ | |
41 | #define TS5500_ADC BIT(2) /* A/D converter option */ | |
42 | #define TS5500_RS485_RTS BIT(6) /* RTS for RS-485 */ | |
43 | #define TS5500_RS485_AUTO BIT(7) /* Automatic RS-485 */ | |
44 | ||
45 | /* External Reset/Industrial Temperature Range options register */ | |
46 | #define TS5500_ERESET_ITR_ADDR 0x76 | |
47 | #define TS5500_ERESET BIT(0) /* External Reset option */ | |
48 | #define TS5500_ITR BIT(1) /* Indust. Temp. Range option */ | |
49 | ||
50 | /* LED/Jumpers register */ | |
51 | #define TS5500_LED_JP_ADDR 0x77 | |
52 | #define TS5500_LED BIT(0) /* LED flag */ | |
53 | #define TS5500_JP1 BIT(1) /* Automatic CMOS */ | |
54 | #define TS5500_JP2 BIT(2) /* Enable Serial Console */ | |
55 | #define TS5500_JP3 BIT(3) /* Write Enable Drive A */ | |
56 | #define TS5500_JP4 BIT(4) /* Fast Console (115K baud) */ | |
57 | #define TS5500_JP5 BIT(5) /* User Jumper */ | |
58 | #define TS5500_JP6 BIT(6) /* Console on COM1 (req. JP2) */ | |
59 | #define TS5500_JP7 BIT(7) /* Undocumented (Unused) */ | |
60 | ||
61 | /* A/D Converter registers */ | |
62 | #define TS5500_ADC_CONV_BUSY_ADDR 0x195 /* Conversion state register */ | |
63 | #define TS5500_ADC_CONV_BUSY BIT(0) | |
64 | #define TS5500_ADC_CONV_INIT_LSB_ADDR 0x196 /* Start conv. / LSB register */ | |
65 | #define TS5500_ADC_CONV_MSB_ADDR 0x197 /* MSB register */ | |
66 | #define TS5500_ADC_CONV_DELAY 12 /* usec */ | |
67 | ||
68 | /** | |
69 | * struct ts5500_sbc - TS-5500 board description | |
84e288d4 | 70 | * @name: Board model name. |
7d029125 VD |
71 | * @id: Board product ID. |
72 | * @sram: Flag for SRAM option. | |
73 | * @rs485: Flag for RS-485 option. | |
74 | * @adc: Flag for Analog/Digital converter option. | |
75 | * @ereset: Flag for External Reset option. | |
76 | * @itr: Flag for Industrial Temperature Range option. | |
77 | * @jumpers: Bitfield for jumpers' state. | |
78 | */ | |
79 | struct ts5500_sbc { | |
84e288d4 | 80 | const char *name; |
7d029125 VD |
81 | int id; |
82 | bool sram; | |
83 | bool rs485; | |
84 | bool adc; | |
85 | bool ereset; | |
86 | bool itr; | |
87 | u8 jumpers; | |
88 | }; | |
89 | ||
90 | /* Board signatures in BIOS shadow RAM */ | |
91 | static const struct { | |
92 | const char * const string; | |
93 | const ssize_t offset; | |
634676c2 | 94 | } ts5500_signatures[] __initconst = { |
7d029125 VD |
95 | { "TS-5x00 AMD Elan", 0xb14 }, |
96 | }; | |
97 | ||
98 | static int __init ts5500_check_signature(void) | |
99 | { | |
100 | void __iomem *bios; | |
101 | int i, ret = -ENODEV; | |
102 | ||
103 | bios = ioremap(0xf0000, 0x10000); | |
104 | if (!bios) | |
105 | return -ENOMEM; | |
106 | ||
107 | for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) { | |
108 | if (check_signature(bios + ts5500_signatures[i].offset, | |
109 | ts5500_signatures[i].string, | |
110 | strlen(ts5500_signatures[i].string))) { | |
111 | ret = 0; | |
112 | break; | |
113 | } | |
114 | } | |
115 | ||
116 | iounmap(bios); | |
117 | return ret; | |
118 | } | |
119 | ||
120 | static int __init ts5500_detect_config(struct ts5500_sbc *sbc) | |
121 | { | |
122 | u8 tmp; | |
123 | int ret = 0; | |
124 | ||
125 | if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500")) | |
126 | return -EBUSY; | |
127 | ||
84e288d4 VD |
128 | sbc->id = inb(TS5500_PRODUCT_CODE_ADDR); |
129 | if (sbc->id == TS5500_PRODUCT_CODE) { | |
130 | sbc->name = "TS-5500"; | |
832fcc89 VD |
131 | } else if (sbc->id == TS5400_PRODUCT_CODE) { |
132 | sbc->name = "TS-5400"; | |
84e288d4 VD |
133 | } else { |
134 | pr_err("ts5500: unknown product code 0x%x\n", sbc->id); | |
7d029125 VD |
135 | ret = -ENODEV; |
136 | goto cleanup; | |
137 | } | |
7d029125 VD |
138 | |
139 | tmp = inb(TS5500_SRAM_RS485_ADC_ADDR); | |
140 | sbc->sram = tmp & TS5500_SRAM; | |
141 | sbc->rs485 = tmp & TS5500_RS485; | |
142 | sbc->adc = tmp & TS5500_ADC; | |
143 | ||
144 | tmp = inb(TS5500_ERESET_ITR_ADDR); | |
145 | sbc->ereset = tmp & TS5500_ERESET; | |
146 | sbc->itr = tmp & TS5500_ITR; | |
147 | ||
148 | tmp = inb(TS5500_LED_JP_ADDR); | |
149 | sbc->jumpers = tmp & ~TS5500_LED; | |
150 | ||
151 | cleanup: | |
152 | release_region(TS5500_PRODUCT_CODE_ADDR, 4); | |
153 | return ret; | |
154 | } | |
155 | ||
84e288d4 VD |
156 | static ssize_t name_show(struct device *dev, struct device_attribute *attr, |
157 | char *buf) | |
158 | { | |
159 | struct ts5500_sbc *sbc = dev_get_drvdata(dev); | |
160 | ||
161 | return sprintf(buf, "%s\n", sbc->name); | |
162 | } | |
163 | static DEVICE_ATTR_RO(name); | |
164 | ||
1d240875 VD |
165 | static ssize_t id_show(struct device *dev, struct device_attribute *attr, |
166 | char *buf) | |
7d029125 VD |
167 | { |
168 | struct ts5500_sbc *sbc = dev_get_drvdata(dev); | |
169 | ||
170 | return sprintf(buf, "0x%.2x\n", sbc->id); | |
171 | } | |
1d240875 | 172 | static DEVICE_ATTR_RO(id); |
7d029125 | 173 | |
1d240875 VD |
174 | static ssize_t jumpers_show(struct device *dev, struct device_attribute *attr, |
175 | char *buf) | |
7d029125 VD |
176 | { |
177 | struct ts5500_sbc *sbc = dev_get_drvdata(dev); | |
178 | ||
179 | return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1); | |
180 | } | |
1d240875 VD |
181 | static DEVICE_ATTR_RO(jumpers); |
182 | ||
183 | #define TS5500_ATTR_BOOL(_field) \ | |
184 | static ssize_t _field##_show(struct device *dev, \ | |
185 | struct device_attribute *attr, char *buf) \ | |
186 | { \ | |
187 | struct ts5500_sbc *sbc = dev_get_drvdata(dev); \ | |
188 | \ | |
189 | return sprintf(buf, "%d\n", sbc->_field); \ | |
190 | } \ | |
191 | static DEVICE_ATTR_RO(_field) | |
192 | ||
193 | TS5500_ATTR_BOOL(sram); | |
194 | TS5500_ATTR_BOOL(rs485); | |
195 | TS5500_ATTR_BOOL(adc); | |
196 | TS5500_ATTR_BOOL(ereset); | |
197 | TS5500_ATTR_BOOL(itr); | |
7d029125 VD |
198 | |
199 | static struct attribute *ts5500_attributes[] = { | |
200 | &dev_attr_id.attr, | |
84e288d4 | 201 | &dev_attr_name.attr, |
7d029125 VD |
202 | &dev_attr_jumpers.attr, |
203 | &dev_attr_sram.attr, | |
204 | &dev_attr_rs485.attr, | |
205 | &dev_attr_adc.attr, | |
206 | &dev_attr_ereset.attr, | |
207 | &dev_attr_itr.attr, | |
208 | NULL | |
209 | }; | |
210 | ||
211 | static const struct attribute_group ts5500_attr_group = { | |
212 | .attrs = ts5500_attributes, | |
213 | }; | |
214 | ||
215 | static struct resource ts5500_dio1_resource[] = { | |
216 | DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"), | |
217 | }; | |
218 | ||
219 | static struct platform_device ts5500_dio1_pdev = { | |
220 | .name = "ts5500-dio1", | |
221 | .id = -1, | |
222 | .resource = ts5500_dio1_resource, | |
223 | .num_resources = 1, | |
224 | }; | |
225 | ||
226 | static struct resource ts5500_dio2_resource[] = { | |
227 | DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"), | |
228 | }; | |
229 | ||
230 | static struct platform_device ts5500_dio2_pdev = { | |
231 | .name = "ts5500-dio2", | |
232 | .id = -1, | |
233 | .resource = ts5500_dio2_resource, | |
234 | .num_resources = 1, | |
235 | }; | |
236 | ||
237 | static void ts5500_led_set(struct led_classdev *led_cdev, | |
238 | enum led_brightness brightness) | |
239 | { | |
240 | outb(!!brightness, TS5500_LED_JP_ADDR); | |
241 | } | |
242 | ||
243 | static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev) | |
244 | { | |
245 | return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF; | |
246 | } | |
247 | ||
248 | static struct led_classdev ts5500_led_cdev = { | |
249 | .name = "ts5500:green:", | |
250 | .brightness_set = ts5500_led_set, | |
251 | .brightness_get = ts5500_led_get, | |
252 | }; | |
253 | ||
254 | static int ts5500_adc_convert(u8 ctrl) | |
255 | { | |
256 | u8 lsb, msb; | |
257 | ||
258 | /* Start conversion (ensure the 3 MSB are set to 0) */ | |
259 | outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR); | |
260 | ||
261 | /* | |
262 | * The platform has CPLD logic driving the A/D converter. | |
263 | * The conversion must complete within 11 microseconds, | |
264 | * otherwise we have to re-initiate a conversion. | |
265 | */ | |
266 | udelay(TS5500_ADC_CONV_DELAY); | |
267 | if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY) | |
268 | return -EBUSY; | |
269 | ||
270 | /* Read the raw data */ | |
271 | lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR); | |
272 | msb = inb(TS5500_ADC_CONV_MSB_ADDR); | |
273 | ||
274 | return (msb << 8) | lsb; | |
275 | } | |
276 | ||
277 | static struct max197_platform_data ts5500_adc_pdata = { | |
278 | .convert = ts5500_adc_convert, | |
279 | }; | |
280 | ||
281 | static struct platform_device ts5500_adc_pdev = { | |
282 | .name = "max197", | |
283 | .id = -1, | |
284 | .dev = { | |
285 | .platform_data = &ts5500_adc_pdata, | |
286 | }, | |
287 | }; | |
288 | ||
289 | static int __init ts5500_init(void) | |
290 | { | |
291 | struct platform_device *pdev; | |
292 | struct ts5500_sbc *sbc; | |
293 | int err; | |
294 | ||
295 | /* | |
296 | * There is no DMI available or PCI bridge subvendor info, | |
297 | * only the BIOS provides a 16-bit identification call. | |
298 | * It is safer to find a signature in the BIOS shadow RAM. | |
299 | */ | |
300 | err = ts5500_check_signature(); | |
301 | if (err) | |
302 | return err; | |
303 | ||
304 | pdev = platform_device_register_simple("ts5500", -1, NULL, 0); | |
305 | if (IS_ERR(pdev)) | |
306 | return PTR_ERR(pdev); | |
307 | ||
308 | sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL); | |
309 | if (!sbc) { | |
310 | err = -ENOMEM; | |
311 | goto error; | |
312 | } | |
313 | ||
314 | err = ts5500_detect_config(sbc); | |
315 | if (err) | |
316 | goto error; | |
317 | ||
318 | platform_set_drvdata(pdev, sbc); | |
319 | ||
320 | err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group); | |
321 | if (err) | |
322 | goto error; | |
323 | ||
832fcc89 VD |
324 | if (sbc->id == TS5500_PRODUCT_CODE) { |
325 | ts5500_dio1_pdev.dev.parent = &pdev->dev; | |
326 | if (platform_device_register(&ts5500_dio1_pdev)) | |
327 | dev_warn(&pdev->dev, "DIO1 block registration failed\n"); | |
328 | ts5500_dio2_pdev.dev.parent = &pdev->dev; | |
329 | if (platform_device_register(&ts5500_dio2_pdev)) | |
330 | dev_warn(&pdev->dev, "DIO2 block registration failed\n"); | |
331 | } | |
7d029125 VD |
332 | |
333 | if (led_classdev_register(&pdev->dev, &ts5500_led_cdev)) | |
334 | dev_warn(&pdev->dev, "LED registration failed\n"); | |
335 | ||
336 | if (sbc->adc) { | |
337 | ts5500_adc_pdev.dev.parent = &pdev->dev; | |
338 | if (platform_device_register(&ts5500_adc_pdev)) | |
339 | dev_warn(&pdev->dev, "ADC registration failed\n"); | |
340 | } | |
341 | ||
342 | return 0; | |
343 | error: | |
344 | platform_device_unregister(pdev); | |
345 | return err; | |
346 | } | |
347 | device_initcall(ts5500_init); |