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 */
73struct 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 */
85static const struct {
86 const char * const string;
87 const ssize_t offset;
88} ts5500_signatures[] __initconst = {
89 { "TS-5x00 AMD Elan", 0xb14 },
90};
91
92static 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
114static 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
145cleanup:
146 release_region(TS5500_PRODUCT_CODE_ADDR, 4);
147 return ret;
148}
149
150static 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}
157static DEVICE_ATTR_RO(name);
158
159static 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}
166static DEVICE_ATTR_RO(id);
167
168static 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}
175static 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
187TS5500_ATTR_BOOL(sram);
188TS5500_ATTR_BOOL(rs485);
189TS5500_ATTR_BOOL(adc);
190TS5500_ATTR_BOOL(ereset);
191TS5500_ATTR_BOOL(itr);
192
193static 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
205static const struct attribute_group ts5500_attr_group = {
206 .attrs = ts5500_attributes,
207};
208
209static struct resource ts5500_dio1_resource[] = {
210 DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"),
211};
212
213static struct platform_device ts5500_dio1_pdev = {
214 .name = "ts5500-dio1",
215 .id = -1,
216 .resource = ts5500_dio1_resource,
217 .num_resources = 1,
218};
219
220static struct resource ts5500_dio2_resource[] = {
221 DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"),
222};
223
224static struct platform_device ts5500_dio2_pdev = {
225 .name = "ts5500-dio2",
226 .id = -1,
227 .resource = ts5500_dio2_resource,
228 .num_resources = 1,
229};
230
231static 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
237static 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
242static struct led_classdev ts5500_led_cdev = {
243 .name = "ts5500:green:",
244 .brightness_set = ts5500_led_set,
245 .brightness_get = ts5500_led_get,
246};
247
248static 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
271static struct max197_platform_data ts5500_adc_pdata = {
272 .convert = ts5500_adc_convert,
273};
274
275static struct platform_device ts5500_adc_pdev = {
276 .name = "max197",
277 .id = -1,
278 .dev = {
279 .platform_data = &ts5500_adc_pdata,
280 },
281};
282
283static 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;
337error:
338 platform_device_unregister(pdev);
339 return err;
340}
341device_initcall(ts5500_init);
342

source code of linux/arch/x86/platform/ts5500/ts5500.c