diff options
Diffstat (limited to 'arch/x86/platform/ts5500/ts5500.c')
| -rw-r--r-- | arch/x86/platform/ts5500/ts5500.c | 339 | 
1 files changed, 339 insertions, 0 deletions
diff --git a/arch/x86/platform/ts5500/ts5500.c b/arch/x86/platform/ts5500/ts5500.c new file mode 100644 index 00000000000..39febb214e8 --- /dev/null +++ b/arch/x86/platform/ts5500/ts5500.c @@ -0,0 +1,339 @@ +/* + * Technologic Systems TS-5500 Single Board Computer support + * + * Copyright (C) 2013 Savoir-faire Linux Inc. + *	Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * + * This driver registers the Technologic Systems TS-5500 Single Board Computer + * (SBC) and its devices, and exposes information to userspace such as jumpers' + * state or available options. For further information about sysfs entries, see + * Documentation/ABI/testing/sysfs-platform-ts5500. + * + * This code actually supports the TS-5500 platform, but it may be extended to + * support similar Technologic Systems x86-based platforms, such as the TS-5600. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_data/gpio-ts5500.h> +#include <linux/platform_data/max197.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* Product code register */ +#define TS5500_PRODUCT_CODE_ADDR	0x74 +#define TS5500_PRODUCT_CODE		0x60	/* TS-5500 product code */ + +/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */ +#define TS5500_SRAM_RS485_ADC_ADDR	0x75 +#define TS5500_SRAM			BIT(0)	/* SRAM option */ +#define TS5500_RS485			BIT(1)	/* RS-485 option */ +#define TS5500_ADC			BIT(2)	/* A/D converter option */ +#define TS5500_RS485_RTS		BIT(6)	/* RTS for RS-485 */ +#define TS5500_RS485_AUTO		BIT(7)	/* Automatic RS-485 */ + +/* External Reset/Industrial Temperature Range options register */ +#define TS5500_ERESET_ITR_ADDR		0x76 +#define TS5500_ERESET			BIT(0)	/* External Reset option */ +#define TS5500_ITR			BIT(1)	/* Indust. Temp. Range option */ + +/* LED/Jumpers register */ +#define TS5500_LED_JP_ADDR		0x77 +#define TS5500_LED			BIT(0)	/* LED flag */ +#define TS5500_JP1			BIT(1)	/* Automatic CMOS */ +#define TS5500_JP2			BIT(2)	/* Enable Serial Console */ +#define TS5500_JP3			BIT(3)	/* Write Enable Drive A */ +#define TS5500_JP4			BIT(4)	/* Fast Console (115K baud) */ +#define TS5500_JP5			BIT(5)	/* User Jumper */ +#define TS5500_JP6			BIT(6)	/* Console on COM1 (req. JP2) */ +#define TS5500_JP7			BIT(7)	/* Undocumented (Unused) */ + +/* A/D Converter registers */ +#define TS5500_ADC_CONV_BUSY_ADDR	0x195	/* Conversion state register */ +#define TS5500_ADC_CONV_BUSY		BIT(0) +#define TS5500_ADC_CONV_INIT_LSB_ADDR	0x196	/* Start conv. / LSB register */ +#define TS5500_ADC_CONV_MSB_ADDR	0x197	/* MSB register */ +#define TS5500_ADC_CONV_DELAY		12	/* usec */ + +/** + * struct ts5500_sbc - TS-5500 board description + * @id:		Board product ID. + * @sram:	Flag for SRAM option. + * @rs485:	Flag for RS-485 option. + * @adc:	Flag for Analog/Digital converter option. + * @ereset:	Flag for External Reset option. + * @itr:	Flag for Industrial Temperature Range option. + * @jumpers:	Bitfield for jumpers' state. + */ +struct ts5500_sbc { +	int	id; +	bool	sram; +	bool	rs485; +	bool	adc; +	bool	ereset; +	bool	itr; +	u8	jumpers; +}; + +/* Board signatures in BIOS shadow RAM */ +static const struct { +	const char * const string; +	const ssize_t offset; +} ts5500_signatures[] __initdata = { +	{ "TS-5x00 AMD Elan", 0xb14 }, +}; + +static int __init ts5500_check_signature(void) +{ +	void __iomem *bios; +	int i, ret = -ENODEV; + +	bios = ioremap(0xf0000, 0x10000); +	if (!bios) +		return -ENOMEM; + +	for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) { +		if (check_signature(bios + ts5500_signatures[i].offset, +				    ts5500_signatures[i].string, +				    strlen(ts5500_signatures[i].string))) { +			ret = 0; +			break; +		} +	} + +	iounmap(bios); +	return ret; +} + +static int __init ts5500_detect_config(struct ts5500_sbc *sbc) +{ +	u8 tmp; +	int ret = 0; + +	if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500")) +		return -EBUSY; + +	tmp = inb(TS5500_PRODUCT_CODE_ADDR); +	if (tmp != TS5500_PRODUCT_CODE) { +		pr_err("This platform is not a TS-5500 (found ID 0x%x)\n", tmp); +		ret = -ENODEV; +		goto cleanup; +	} +	sbc->id = tmp; + +	tmp = inb(TS5500_SRAM_RS485_ADC_ADDR); +	sbc->sram = tmp & TS5500_SRAM; +	sbc->rs485 = tmp & TS5500_RS485; +	sbc->adc = tmp & TS5500_ADC; + +	tmp = inb(TS5500_ERESET_ITR_ADDR); +	sbc->ereset = tmp & TS5500_ERESET; +	sbc->itr = tmp & TS5500_ITR; + +	tmp = inb(TS5500_LED_JP_ADDR); +	sbc->jumpers = tmp & ~TS5500_LED; + +cleanup: +	release_region(TS5500_PRODUCT_CODE_ADDR, 4); +	return ret; +} + +static ssize_t ts5500_show_id(struct device *dev, +			      struct device_attribute *attr, char *buf) +{ +	struct ts5500_sbc *sbc = dev_get_drvdata(dev); + +	return sprintf(buf, "0x%.2x\n", sbc->id); +} + +static ssize_t ts5500_show_jumpers(struct device *dev, +				   struct device_attribute *attr, +				   char *buf) +{ +	struct ts5500_sbc *sbc = dev_get_drvdata(dev); + +	return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1); +} + +#define TS5500_SHOW(field)					\ +	static ssize_t ts5500_show_##field(struct device *dev,	\ +			struct device_attribute *attr,		\ +			char *buf)				\ +	{							\ +		struct ts5500_sbc *sbc = dev_get_drvdata(dev);	\ +		return sprintf(buf, "%d\n", sbc->field);	\ +	} + +TS5500_SHOW(sram) +TS5500_SHOW(rs485) +TS5500_SHOW(adc) +TS5500_SHOW(ereset) +TS5500_SHOW(itr) + +static DEVICE_ATTR(id, S_IRUGO, ts5500_show_id, NULL); +static DEVICE_ATTR(jumpers, S_IRUGO, ts5500_show_jumpers, NULL); +static DEVICE_ATTR(sram, S_IRUGO, ts5500_show_sram, NULL); +static DEVICE_ATTR(rs485, S_IRUGO, ts5500_show_rs485, NULL); +static DEVICE_ATTR(adc, S_IRUGO, ts5500_show_adc, NULL); +static DEVICE_ATTR(ereset, S_IRUGO, ts5500_show_ereset, NULL); +static DEVICE_ATTR(itr, S_IRUGO, ts5500_show_itr, NULL); + +static struct attribute *ts5500_attributes[] = { +	&dev_attr_id.attr, +	&dev_attr_jumpers.attr, +	&dev_attr_sram.attr, +	&dev_attr_rs485.attr, +	&dev_attr_adc.attr, +	&dev_attr_ereset.attr, +	&dev_attr_itr.attr, +	NULL +}; + +static const struct attribute_group ts5500_attr_group = { +	.attrs = ts5500_attributes, +}; + +static struct resource ts5500_dio1_resource[] = { +	DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"), +}; + +static struct platform_device ts5500_dio1_pdev = { +	.name = "ts5500-dio1", +	.id = -1, +	.resource = ts5500_dio1_resource, +	.num_resources = 1, +}; + +static struct resource ts5500_dio2_resource[] = { +	DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"), +}; + +static struct platform_device ts5500_dio2_pdev = { +	.name = "ts5500-dio2", +	.id = -1, +	.resource = ts5500_dio2_resource, +	.num_resources = 1, +}; + +static void ts5500_led_set(struct led_classdev *led_cdev, +			   enum led_brightness brightness) +{ +	outb(!!brightness, TS5500_LED_JP_ADDR); +} + +static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev) +{ +	return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF; +} + +static struct led_classdev ts5500_led_cdev = { +	.name = "ts5500:green:", +	.brightness_set = ts5500_led_set, +	.brightness_get = ts5500_led_get, +}; + +static int ts5500_adc_convert(u8 ctrl) +{ +	u8 lsb, msb; + +	/* Start conversion (ensure the 3 MSB are set to 0) */ +	outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR); + +	/* +	 * The platform has CPLD logic driving the A/D converter. +	 * The conversion must complete within 11 microseconds, +	 * otherwise we have to re-initiate a conversion. +	 */ +	udelay(TS5500_ADC_CONV_DELAY); +	if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY) +		return -EBUSY; + +	/* Read the raw data */ +	lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR); +	msb = inb(TS5500_ADC_CONV_MSB_ADDR); + +	return (msb << 8) | lsb; +} + +static struct max197_platform_data ts5500_adc_pdata = { +	.convert = ts5500_adc_convert, +}; + +static struct platform_device ts5500_adc_pdev = { +	.name = "max197", +	.id = -1, +	.dev = { +		.platform_data = &ts5500_adc_pdata, +	}, +}; + +static int __init ts5500_init(void) +{ +	struct platform_device *pdev; +	struct ts5500_sbc *sbc; +	int err; + +	/* +	 * There is no DMI available or PCI bridge subvendor info, +	 * only the BIOS provides a 16-bit identification call. +	 * It is safer to find a signature in the BIOS shadow RAM. +	 */ +	err = ts5500_check_signature(); +	if (err) +		return err; + +	pdev = platform_device_register_simple("ts5500", -1, NULL, 0); +	if (IS_ERR(pdev)) +		return PTR_ERR(pdev); + +	sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL); +	if (!sbc) { +		err = -ENOMEM; +		goto error; +	} + +	err = ts5500_detect_config(sbc); +	if (err) +		goto error; + +	platform_set_drvdata(pdev, sbc); + +	err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group); +	if (err) +		goto error; + +	ts5500_dio1_pdev.dev.parent = &pdev->dev; +	if (platform_device_register(&ts5500_dio1_pdev)) +		dev_warn(&pdev->dev, "DIO1 block registration failed\n"); +	ts5500_dio2_pdev.dev.parent = &pdev->dev; +	if (platform_device_register(&ts5500_dio2_pdev)) +		dev_warn(&pdev->dev, "DIO2 block registration failed\n"); + +	if (led_classdev_register(&pdev->dev, &ts5500_led_cdev)) +		dev_warn(&pdev->dev, "LED registration failed\n"); + +	if (sbc->adc) { +		ts5500_adc_pdev.dev.parent = &pdev->dev; +		if (platform_device_register(&ts5500_adc_pdev)) +			dev_warn(&pdev->dev, "ADC registration failed\n"); +	} + +	return 0; +error: +	platform_device_unregister(pdev); +	return err; +} +device_initcall(ts5500_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>"); +MODULE_DESCRIPTION("Technologic Systems TS-5500 platform driver");  |