Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/arch/x86/platform/ts5500/ts5500.c
26583 views
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 <[email protected]>
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(0xf0000, 0x10000);
98
if (!bios)
99
return -ENOMEM;
100
101
for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) {
102
if (check_signature(bios + ts5500_signatures[i].offset,
103
ts5500_signatures[i].string,
104
strlen(ts5500_signatures[i].string))) {
105
ret = 0;
106
break;
107
}
108
}
109
110
iounmap(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, "%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, "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, "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(!!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(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("ts5500", -1, NULL, 0);
299
if (IS_ERR(pdev))
300
return PTR_ERR(pdev);
301
302
sbc = devm_kzalloc(&pdev->dev, 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, sbc);
313
314
err = sysfs_create_group(&pdev->dev.kobj, &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(&pdev->dev, &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
343