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