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