]> Git Repo - linux.git/blob - arch/x86/platform/ts5500/ts5500.c
net: udp: prefer listeners bound to an address
[linux.git] / arch / x86 / platform / ts5500 / ts5500.c
1 /*
2  * Technologic Systems TS-5500 Single Board Computer support
3  *
4  * Copyright (C) 2013-2014 Savoir-faire Linux Inc.
5  *      Vivien Didelot <[email protected]>
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 may be extended to support similar x86-based platforms.
19  * Actually, the TS-5500 and TS-5400 are supported.
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/init.h>
27 #include <linux/platform_data/max197.h>
28 #include <linux/platform_device.h>
29 #include <linux/slab.h>
30
31 /* Product code register */
32 #define TS5500_PRODUCT_CODE_ADDR        0x74
33 #define TS5500_PRODUCT_CODE             0x60    /* TS-5500 product code */
34 #define TS5400_PRODUCT_CODE             0x40    /* TS-5400 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  * @name:       Board model name.
70  * @id:         Board product ID.
71  * @sram:       Flag for SRAM option.
72  * @rs485:      Flag for RS-485 option.
73  * @adc:        Flag for Analog/Digital converter option.
74  * @ereset:     Flag for External Reset option.
75  * @itr:        Flag for Industrial Temperature Range option.
76  * @jumpers:    Bitfield for jumpers' state.
77  */
78 struct ts5500_sbc {
79         const char *name;
80         int     id;
81         bool    sram;
82         bool    rs485;
83         bool    adc;
84         bool    ereset;
85         bool    itr;
86         u8      jumpers;
87 };
88
89 /* Board signatures in BIOS shadow RAM */
90 static const struct {
91         const char * const string;
92         const ssize_t offset;
93 } ts5500_signatures[] __initconst = {
94         { "TS-5x00 AMD Elan", 0xb14 },
95 };
96
97 static int __init ts5500_check_signature(void)
98 {
99         void __iomem *bios;
100         int i, ret = -ENODEV;
101
102         bios = ioremap(0xf0000, 0x10000);
103         if (!bios)
104                 return -ENOMEM;
105
106         for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) {
107                 if (check_signature(bios + ts5500_signatures[i].offset,
108                                     ts5500_signatures[i].string,
109                                     strlen(ts5500_signatures[i].string))) {
110                         ret = 0;
111                         break;
112                 }
113         }
114
115         iounmap(bios);
116         return ret;
117 }
118
119 static int __init ts5500_detect_config(struct ts5500_sbc *sbc)
120 {
121         u8 tmp;
122         int ret = 0;
123
124         if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500"))
125                 return -EBUSY;
126
127         sbc->id = inb(TS5500_PRODUCT_CODE_ADDR);
128         if (sbc->id == TS5500_PRODUCT_CODE) {
129                 sbc->name = "TS-5500";
130         } else if (sbc->id == TS5400_PRODUCT_CODE) {
131                 sbc->name = "TS-5400";
132         } else {
133                 pr_err("ts5500: unknown product code 0x%x\n", sbc->id);
134                 ret = -ENODEV;
135                 goto cleanup;
136         }
137
138         tmp = inb(TS5500_SRAM_RS485_ADC_ADDR);
139         sbc->sram = tmp & TS5500_SRAM;
140         sbc->rs485 = tmp & TS5500_RS485;
141         sbc->adc = tmp & TS5500_ADC;
142
143         tmp = inb(TS5500_ERESET_ITR_ADDR);
144         sbc->ereset = tmp & TS5500_ERESET;
145         sbc->itr = tmp & TS5500_ITR;
146
147         tmp = inb(TS5500_LED_JP_ADDR);
148         sbc->jumpers = tmp & ~TS5500_LED;
149
150 cleanup:
151         release_region(TS5500_PRODUCT_CODE_ADDR, 4);
152         return ret;
153 }
154
155 static ssize_t name_show(struct device *dev, struct device_attribute *attr,
156                 char *buf)
157 {
158         struct ts5500_sbc *sbc = dev_get_drvdata(dev);
159
160         return sprintf(buf, "%s\n", sbc->name);
161 }
162 static DEVICE_ATTR_RO(name);
163
164 static ssize_t id_show(struct device *dev, struct device_attribute *attr,
165                 char *buf)
166 {
167         struct ts5500_sbc *sbc = dev_get_drvdata(dev);
168
169         return sprintf(buf, "0x%.2x\n", sbc->id);
170 }
171 static DEVICE_ATTR_RO(id);
172
173 static ssize_t jumpers_show(struct device *dev, struct device_attribute *attr,
174                 char *buf)
175 {
176         struct ts5500_sbc *sbc = dev_get_drvdata(dev);
177
178         return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1);
179 }
180 static DEVICE_ATTR_RO(jumpers);
181
182 #define TS5500_ATTR_BOOL(_field)                                        \
183         static ssize_t _field##_show(struct device *dev,                \
184                         struct device_attribute *attr, char *buf)       \
185         {                                                               \
186                 struct ts5500_sbc *sbc = dev_get_drvdata(dev);          \
187                                                                         \
188                 return sprintf(buf, "%d\n", sbc->_field);               \
189         }                                                               \
190         static DEVICE_ATTR_RO(_field)
191
192 TS5500_ATTR_BOOL(sram);
193 TS5500_ATTR_BOOL(rs485);
194 TS5500_ATTR_BOOL(adc);
195 TS5500_ATTR_BOOL(ereset);
196 TS5500_ATTR_BOOL(itr);
197
198 static struct attribute *ts5500_attributes[] = {
199         &dev_attr_id.attr,
200         &dev_attr_name.attr,
201         &dev_attr_jumpers.attr,
202         &dev_attr_sram.attr,
203         &dev_attr_rs485.attr,
204         &dev_attr_adc.attr,
205         &dev_attr_ereset.attr,
206         &dev_attr_itr.attr,
207         NULL
208 };
209
210 static const struct attribute_group ts5500_attr_group = {
211         .attrs = ts5500_attributes,
212 };
213
214 static struct resource ts5500_dio1_resource[] = {
215         DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"),
216 };
217
218 static struct platform_device ts5500_dio1_pdev = {
219         .name = "ts5500-dio1",
220         .id = -1,
221         .resource = ts5500_dio1_resource,
222         .num_resources = 1,
223 };
224
225 static struct resource ts5500_dio2_resource[] = {
226         DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"),
227 };
228
229 static struct platform_device ts5500_dio2_pdev = {
230         .name = "ts5500-dio2",
231         .id = -1,
232         .resource = ts5500_dio2_resource,
233         .num_resources = 1,
234 };
235
236 static void ts5500_led_set(struct led_classdev *led_cdev,
237                            enum led_brightness brightness)
238 {
239         outb(!!brightness, TS5500_LED_JP_ADDR);
240 }
241
242 static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev)
243 {
244         return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF;
245 }
246
247 static struct led_classdev ts5500_led_cdev = {
248         .name = "ts5500:green:",
249         .brightness_set = ts5500_led_set,
250         .brightness_get = ts5500_led_get,
251 };
252
253 static int ts5500_adc_convert(u8 ctrl)
254 {
255         u8 lsb, msb;
256
257         /* Start conversion (ensure the 3 MSB are set to 0) */
258         outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR);
259
260         /*
261          * The platform has CPLD logic driving the A/D converter.
262          * The conversion must complete within 11 microseconds,
263          * otherwise we have to re-initiate a conversion.
264          */
265         udelay(TS5500_ADC_CONV_DELAY);
266         if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY)
267                 return -EBUSY;
268
269         /* Read the raw data */
270         lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR);
271         msb = inb(TS5500_ADC_CONV_MSB_ADDR);
272
273         return (msb << 8) | lsb;
274 }
275
276 static struct max197_platform_data ts5500_adc_pdata = {
277         .convert = ts5500_adc_convert,
278 };
279
280 static struct platform_device ts5500_adc_pdev = {
281         .name = "max197",
282         .id = -1,
283         .dev = {
284                 .platform_data = &ts5500_adc_pdata,
285         },
286 };
287
288 static int __init ts5500_init(void)
289 {
290         struct platform_device *pdev;
291         struct ts5500_sbc *sbc;
292         int err;
293
294         /*
295          * There is no DMI available or PCI bridge subvendor info,
296          * only the BIOS provides a 16-bit identification call.
297          * It is safer to find a signature in the BIOS shadow RAM.
298          */
299         err = ts5500_check_signature();
300         if (err)
301                 return err;
302
303         pdev = platform_device_register_simple("ts5500", -1, NULL, 0);
304         if (IS_ERR(pdev))
305                 return PTR_ERR(pdev);
306
307         sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL);
308         if (!sbc) {
309                 err = -ENOMEM;
310                 goto error;
311         }
312
313         err = ts5500_detect_config(sbc);
314         if (err)
315                 goto error;
316
317         platform_set_drvdata(pdev, sbc);
318
319         err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group);
320         if (err)
321                 goto error;
322
323         if (sbc->id == TS5500_PRODUCT_CODE) {
324                 ts5500_dio1_pdev.dev.parent = &pdev->dev;
325                 if (platform_device_register(&ts5500_dio1_pdev))
326                         dev_warn(&pdev->dev, "DIO1 block registration failed\n");
327                 ts5500_dio2_pdev.dev.parent = &pdev->dev;
328                 if (platform_device_register(&ts5500_dio2_pdev))
329                         dev_warn(&pdev->dev, "DIO2 block registration failed\n");
330         }
331
332         if (led_classdev_register(&pdev->dev, &ts5500_led_cdev))
333                 dev_warn(&pdev->dev, "LED registration failed\n");
334
335         if (sbc->adc) {
336                 ts5500_adc_pdev.dev.parent = &pdev->dev;
337                 if (platform_device_register(&ts5500_adc_pdev))
338                         dev_warn(&pdev->dev, "ADC registration failed\n");
339         }
340
341         return 0;
342 error:
343         platform_device_unregister(pdev);
344         return err;
345 }
346 device_initcall(ts5500_init);
This page took 0.052308 seconds and 4 git commands to generate.