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