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