xref: /linux/arch/x86/platform/ts5500/ts5500.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
1*2874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
27d029125SVivien Didelot /*
37d029125SVivien Didelot  * Technologic Systems TS-5500 Single Board Computer support
47d029125SVivien Didelot  *
584e288d4SVivien Didelot  * Copyright (C) 2013-2014 Savoir-faire Linux Inc.
67d029125SVivien Didelot  *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
77d029125SVivien Didelot  *
87d029125SVivien Didelot  * This driver registers the Technologic Systems TS-5500 Single Board Computer
97d029125SVivien Didelot  * (SBC) and its devices, and exposes information to userspace such as jumpers'
107d029125SVivien Didelot  * state or available options. For further information about sysfs entries, see
117d029125SVivien Didelot  * Documentation/ABI/testing/sysfs-platform-ts5500.
127d029125SVivien Didelot  *
13832fcc89SVivien Didelot  * This code may be extended to support similar x86-based platforms.
14832fcc89SVivien Didelot  * Actually, the TS-5500 and TS-5400 are supported.
157d029125SVivien Didelot  */
167d029125SVivien Didelot 
177d029125SVivien Didelot #include <linux/delay.h>
187d029125SVivien Didelot #include <linux/io.h>
197d029125SVivien Didelot #include <linux/kernel.h>
207d029125SVivien Didelot #include <linux/leds.h>
21cc3ae7b0SPaul Gortmaker #include <linux/init.h>
227d029125SVivien Didelot #include <linux/platform_data/max197.h>
237d029125SVivien Didelot #include <linux/platform_device.h>
247d029125SVivien Didelot #include <linux/slab.h>
257d029125SVivien Didelot 
267d029125SVivien Didelot /* Product code register */
277d029125SVivien Didelot #define TS5500_PRODUCT_CODE_ADDR	0x74
287d029125SVivien Didelot #define TS5500_PRODUCT_CODE		0x60	/* TS-5500 product code */
29832fcc89SVivien Didelot #define TS5400_PRODUCT_CODE		0x40	/* TS-5400 product code */
307d029125SVivien Didelot 
317d029125SVivien Didelot /* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */
327d029125SVivien Didelot #define TS5500_SRAM_RS485_ADC_ADDR	0x75
337d029125SVivien Didelot #define TS5500_SRAM			BIT(0)	/* SRAM option */
347d029125SVivien Didelot #define TS5500_RS485			BIT(1)	/* RS-485 option */
357d029125SVivien Didelot #define TS5500_ADC			BIT(2)	/* A/D converter option */
367d029125SVivien Didelot #define TS5500_RS485_RTS		BIT(6)	/* RTS for RS-485 */
377d029125SVivien Didelot #define TS5500_RS485_AUTO		BIT(7)	/* Automatic RS-485 */
387d029125SVivien Didelot 
397d029125SVivien Didelot /* External Reset/Industrial Temperature Range options register */
407d029125SVivien Didelot #define TS5500_ERESET_ITR_ADDR		0x76
417d029125SVivien Didelot #define TS5500_ERESET			BIT(0)	/* External Reset option */
427d029125SVivien Didelot #define TS5500_ITR			BIT(1)	/* Indust. Temp. Range option */
437d029125SVivien Didelot 
447d029125SVivien Didelot /* LED/Jumpers register */
457d029125SVivien Didelot #define TS5500_LED_JP_ADDR		0x77
467d029125SVivien Didelot #define TS5500_LED			BIT(0)	/* LED flag */
477d029125SVivien Didelot #define TS5500_JP1			BIT(1)	/* Automatic CMOS */
487d029125SVivien Didelot #define TS5500_JP2			BIT(2)	/* Enable Serial Console */
497d029125SVivien Didelot #define TS5500_JP3			BIT(3)	/* Write Enable Drive A */
507d029125SVivien Didelot #define TS5500_JP4			BIT(4)	/* Fast Console (115K baud) */
517d029125SVivien Didelot #define TS5500_JP5			BIT(5)	/* User Jumper */
527d029125SVivien Didelot #define TS5500_JP6			BIT(6)	/* Console on COM1 (req. JP2) */
537d029125SVivien Didelot #define TS5500_JP7			BIT(7)	/* Undocumented (Unused) */
547d029125SVivien Didelot 
557d029125SVivien Didelot /* A/D Converter registers */
567d029125SVivien Didelot #define TS5500_ADC_CONV_BUSY_ADDR	0x195	/* Conversion state register */
577d029125SVivien Didelot #define TS5500_ADC_CONV_BUSY		BIT(0)
587d029125SVivien Didelot #define TS5500_ADC_CONV_INIT_LSB_ADDR	0x196	/* Start conv. / LSB register */
597d029125SVivien Didelot #define TS5500_ADC_CONV_MSB_ADDR	0x197	/* MSB register */
607d029125SVivien Didelot #define TS5500_ADC_CONV_DELAY		12	/* usec */
617d029125SVivien Didelot 
627d029125SVivien Didelot /**
637d029125SVivien Didelot  * struct ts5500_sbc - TS-5500 board description
6484e288d4SVivien Didelot  * @name:	Board model name.
657d029125SVivien Didelot  * @id:		Board product ID.
667d029125SVivien Didelot  * @sram:	Flag for SRAM option.
677d029125SVivien Didelot  * @rs485:	Flag for RS-485 option.
687d029125SVivien Didelot  * @adc:	Flag for Analog/Digital converter option.
697d029125SVivien Didelot  * @ereset:	Flag for External Reset option.
707d029125SVivien Didelot  * @itr:	Flag for Industrial Temperature Range option.
717d029125SVivien Didelot  * @jumpers:	Bitfield for jumpers' state.
727d029125SVivien Didelot  */
737d029125SVivien Didelot struct ts5500_sbc {
7484e288d4SVivien Didelot 	const char *name;
757d029125SVivien Didelot 	int	id;
767d029125SVivien Didelot 	bool	sram;
777d029125SVivien Didelot 	bool	rs485;
787d029125SVivien Didelot 	bool	adc;
797d029125SVivien Didelot 	bool	ereset;
807d029125SVivien Didelot 	bool	itr;
817d029125SVivien Didelot 	u8	jumpers;
827d029125SVivien Didelot };
837d029125SVivien Didelot 
847d029125SVivien Didelot /* Board signatures in BIOS shadow RAM */
857d029125SVivien Didelot static const struct {
867d029125SVivien Didelot 	const char * const string;
877d029125SVivien Didelot 	const ssize_t offset;
88634676c2SAndi Kleen } ts5500_signatures[] __initconst = {
897d029125SVivien Didelot 	{ "TS-5x00 AMD Elan", 0xb14 },
907d029125SVivien Didelot };
917d029125SVivien Didelot 
ts5500_check_signature(void)927d029125SVivien Didelot static int __init ts5500_check_signature(void)
937d029125SVivien Didelot {
947d029125SVivien Didelot 	void __iomem *bios;
957d029125SVivien Didelot 	int i, ret = -ENODEV;
967d029125SVivien Didelot 
977d029125SVivien Didelot 	bios = ioremap(0xf0000, 0x10000);
987d029125SVivien Didelot 	if (!bios)
997d029125SVivien Didelot 		return -ENOMEM;
1007d029125SVivien Didelot 
1017d029125SVivien Didelot 	for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) {
1027d029125SVivien Didelot 		if (check_signature(bios + ts5500_signatures[i].offset,
1037d029125SVivien Didelot 				    ts5500_signatures[i].string,
1047d029125SVivien Didelot 				    strlen(ts5500_signatures[i].string))) {
1057d029125SVivien Didelot 			ret = 0;
1067d029125SVivien Didelot 			break;
1077d029125SVivien Didelot 		}
1087d029125SVivien Didelot 	}
1097d029125SVivien Didelot 
1107d029125SVivien Didelot 	iounmap(bios);
1117d029125SVivien Didelot 	return ret;
1127d029125SVivien Didelot }
1137d029125SVivien Didelot 
ts5500_detect_config(struct ts5500_sbc * sbc)1147d029125SVivien Didelot static int __init ts5500_detect_config(struct ts5500_sbc *sbc)
1157d029125SVivien Didelot {
1167d029125SVivien Didelot 	u8 tmp;
1177d029125SVivien Didelot 	int ret = 0;
1187d029125SVivien Didelot 
1197d029125SVivien Didelot 	if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500"))
1207d029125SVivien Didelot 		return -EBUSY;
1217d029125SVivien Didelot 
12284e288d4SVivien Didelot 	sbc->id = inb(TS5500_PRODUCT_CODE_ADDR);
12384e288d4SVivien Didelot 	if (sbc->id == TS5500_PRODUCT_CODE) {
12484e288d4SVivien Didelot 		sbc->name = "TS-5500";
125832fcc89SVivien Didelot 	} else if (sbc->id == TS5400_PRODUCT_CODE) {
126832fcc89SVivien Didelot 		sbc->name = "TS-5400";
12784e288d4SVivien Didelot 	} else {
12884e288d4SVivien Didelot 		pr_err("ts5500: unknown product code 0x%x\n", sbc->id);
1297d029125SVivien Didelot 		ret = -ENODEV;
1307d029125SVivien Didelot 		goto cleanup;
1317d029125SVivien Didelot 	}
1327d029125SVivien Didelot 
1337d029125SVivien Didelot 	tmp = inb(TS5500_SRAM_RS485_ADC_ADDR);
1347d029125SVivien Didelot 	sbc->sram = tmp & TS5500_SRAM;
1357d029125SVivien Didelot 	sbc->rs485 = tmp & TS5500_RS485;
1367d029125SVivien Didelot 	sbc->adc = tmp & TS5500_ADC;
1377d029125SVivien Didelot 
1387d029125SVivien Didelot 	tmp = inb(TS5500_ERESET_ITR_ADDR);
1397d029125SVivien Didelot 	sbc->ereset = tmp & TS5500_ERESET;
1407d029125SVivien Didelot 	sbc->itr = tmp & TS5500_ITR;
1417d029125SVivien Didelot 
1427d029125SVivien Didelot 	tmp = inb(TS5500_LED_JP_ADDR);
1437d029125SVivien Didelot 	sbc->jumpers = tmp & ~TS5500_LED;
1447d029125SVivien Didelot 
1457d029125SVivien Didelot cleanup:
1467d029125SVivien Didelot 	release_region(TS5500_PRODUCT_CODE_ADDR, 4);
1477d029125SVivien Didelot 	return ret;
1487d029125SVivien Didelot }
1497d029125SVivien Didelot 
name_show(struct device * dev,struct device_attribute * attr,char * buf)15084e288d4SVivien Didelot static ssize_t name_show(struct device *dev, struct device_attribute *attr,
15184e288d4SVivien Didelot 		char *buf)
15284e288d4SVivien Didelot {
15384e288d4SVivien Didelot 	struct ts5500_sbc *sbc = dev_get_drvdata(dev);
15484e288d4SVivien Didelot 
15584e288d4SVivien Didelot 	return sprintf(buf, "%s\n", sbc->name);
15684e288d4SVivien Didelot }
15784e288d4SVivien Didelot static DEVICE_ATTR_RO(name);
15884e288d4SVivien Didelot 
id_show(struct device * dev,struct device_attribute * attr,char * buf)1591d240875SVivien Didelot static ssize_t id_show(struct device *dev, struct device_attribute *attr,
1601d240875SVivien Didelot 		char *buf)
1617d029125SVivien Didelot {
1627d029125SVivien Didelot 	struct ts5500_sbc *sbc = dev_get_drvdata(dev);
1637d029125SVivien Didelot 
1647d029125SVivien Didelot 	return sprintf(buf, "0x%.2x\n", sbc->id);
1657d029125SVivien Didelot }
1661d240875SVivien Didelot static DEVICE_ATTR_RO(id);
1677d029125SVivien Didelot 
jumpers_show(struct device * dev,struct device_attribute * attr,char * buf)1681d240875SVivien Didelot static ssize_t jumpers_show(struct device *dev, struct device_attribute *attr,
1697d029125SVivien Didelot 		char *buf)
1707d029125SVivien Didelot {
1717d029125SVivien Didelot 	struct ts5500_sbc *sbc = dev_get_drvdata(dev);
1727d029125SVivien Didelot 
1737d029125SVivien Didelot 	return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1);
1747d029125SVivien Didelot }
1751d240875SVivien Didelot static DEVICE_ATTR_RO(jumpers);
1767d029125SVivien Didelot 
1771d240875SVivien Didelot #define TS5500_ATTR_BOOL(_field)					\
1781d240875SVivien Didelot 	static ssize_t _field##_show(struct device *dev,		\
1791d240875SVivien Didelot 			struct device_attribute *attr, char *buf)	\
1807d029125SVivien Didelot 	{								\
1817d029125SVivien Didelot 		struct ts5500_sbc *sbc = dev_get_drvdata(dev);		\
1821d240875SVivien Didelot 									\
1831d240875SVivien Didelot 		return sprintf(buf, "%d\n", sbc->_field);		\
1841d240875SVivien Didelot 	}								\
1851d240875SVivien Didelot 	static DEVICE_ATTR_RO(_field)
1867d029125SVivien Didelot 
1871d240875SVivien Didelot TS5500_ATTR_BOOL(sram);
1881d240875SVivien Didelot TS5500_ATTR_BOOL(rs485);
1891d240875SVivien Didelot TS5500_ATTR_BOOL(adc);
1901d240875SVivien Didelot TS5500_ATTR_BOOL(ereset);
1911d240875SVivien Didelot TS5500_ATTR_BOOL(itr);
1927d029125SVivien Didelot 
1937d029125SVivien Didelot static struct attribute *ts5500_attributes[] = {
1947d029125SVivien Didelot 	&dev_attr_id.attr,
19584e288d4SVivien Didelot 	&dev_attr_name.attr,
1967d029125SVivien Didelot 	&dev_attr_jumpers.attr,
1977d029125SVivien Didelot 	&dev_attr_sram.attr,
1987d029125SVivien Didelot 	&dev_attr_rs485.attr,
1997d029125SVivien Didelot 	&dev_attr_adc.attr,
2007d029125SVivien Didelot 	&dev_attr_ereset.attr,
2017d029125SVivien Didelot 	&dev_attr_itr.attr,
2027d029125SVivien Didelot 	NULL
2037d029125SVivien Didelot };
2047d029125SVivien Didelot 
2057d029125SVivien Didelot static const struct attribute_group ts5500_attr_group = {
2067d029125SVivien Didelot 	.attrs = ts5500_attributes,
2077d029125SVivien Didelot };
2087d029125SVivien Didelot 
2097d029125SVivien Didelot static struct resource ts5500_dio1_resource[] = {
2107d029125SVivien Didelot 	DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"),
2117d029125SVivien Didelot };
2127d029125SVivien Didelot 
2137d029125SVivien Didelot static struct platform_device ts5500_dio1_pdev = {
2147d029125SVivien Didelot 	.name = "ts5500-dio1",
2157d029125SVivien Didelot 	.id = -1,
2167d029125SVivien Didelot 	.resource = ts5500_dio1_resource,
2177d029125SVivien Didelot 	.num_resources = 1,
2187d029125SVivien Didelot };
2197d029125SVivien Didelot 
2207d029125SVivien Didelot static struct resource ts5500_dio2_resource[] = {
2217d029125SVivien Didelot 	DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"),
2227d029125SVivien Didelot };
2237d029125SVivien Didelot 
2247d029125SVivien Didelot static struct platform_device ts5500_dio2_pdev = {
2257d029125SVivien Didelot 	.name = "ts5500-dio2",
2267d029125SVivien Didelot 	.id = -1,
2277d029125SVivien Didelot 	.resource = ts5500_dio2_resource,
2287d029125SVivien Didelot 	.num_resources = 1,
2297d029125SVivien Didelot };
2307d029125SVivien Didelot 
ts5500_led_set(struct led_classdev * led_cdev,enum led_brightness brightness)2317d029125SVivien Didelot static void ts5500_led_set(struct led_classdev *led_cdev,
2327d029125SVivien Didelot 			   enum led_brightness brightness)
2337d029125SVivien Didelot {
2347d029125SVivien Didelot 	outb(!!brightness, TS5500_LED_JP_ADDR);
2357d029125SVivien Didelot }
2367d029125SVivien Didelot 
ts5500_led_get(struct led_classdev * led_cdev)2377d029125SVivien Didelot static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev)
2387d029125SVivien Didelot {
2397d029125SVivien Didelot 	return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF;
2407d029125SVivien Didelot }
2417d029125SVivien Didelot 
2427d029125SVivien Didelot static struct led_classdev ts5500_led_cdev = {
2437d029125SVivien Didelot 	.name = "ts5500:green:",
2447d029125SVivien Didelot 	.brightness_set = ts5500_led_set,
2457d029125SVivien Didelot 	.brightness_get = ts5500_led_get,
2467d029125SVivien Didelot };
2477d029125SVivien Didelot 
ts5500_adc_convert(u8 ctrl)2487d029125SVivien Didelot static int ts5500_adc_convert(u8 ctrl)
2497d029125SVivien Didelot {
2507d029125SVivien Didelot 	u8 lsb, msb;
2517d029125SVivien Didelot 
2527d029125SVivien Didelot 	/* Start conversion (ensure the 3 MSB are set to 0) */
2537d029125SVivien Didelot 	outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR);
2547d029125SVivien Didelot 
2557d029125SVivien Didelot 	/*
2567d029125SVivien Didelot 	 * The platform has CPLD logic driving the A/D converter.
2577d029125SVivien Didelot 	 * The conversion must complete within 11 microseconds,
2587d029125SVivien Didelot 	 * otherwise we have to re-initiate a conversion.
2597d029125SVivien Didelot 	 */
2607d029125SVivien Didelot 	udelay(TS5500_ADC_CONV_DELAY);
2617d029125SVivien Didelot 	if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY)
2627d029125SVivien Didelot 		return -EBUSY;
2637d029125SVivien Didelot 
2647d029125SVivien Didelot 	/* Read the raw data */
2657d029125SVivien Didelot 	lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR);
2667d029125SVivien Didelot 	msb = inb(TS5500_ADC_CONV_MSB_ADDR);
2677d029125SVivien Didelot 
2687d029125SVivien Didelot 	return (msb << 8) | lsb;
2697d029125SVivien Didelot }
2707d029125SVivien Didelot 
2717d029125SVivien Didelot static struct max197_platform_data ts5500_adc_pdata = {
2727d029125SVivien Didelot 	.convert = ts5500_adc_convert,
2737d029125SVivien Didelot };
2747d029125SVivien Didelot 
2757d029125SVivien Didelot static struct platform_device ts5500_adc_pdev = {
2767d029125SVivien Didelot 	.name = "max197",
2777d029125SVivien Didelot 	.id = -1,
2787d029125SVivien Didelot 	.dev = {
2797d029125SVivien Didelot 		.platform_data = &ts5500_adc_pdata,
2807d029125SVivien Didelot 	},
2817d029125SVivien Didelot };
2827d029125SVivien Didelot 
ts5500_init(void)2837d029125SVivien Didelot static int __init ts5500_init(void)
2847d029125SVivien Didelot {
2857d029125SVivien Didelot 	struct platform_device *pdev;
2867d029125SVivien Didelot 	struct ts5500_sbc *sbc;
2877d029125SVivien Didelot 	int err;
2887d029125SVivien Didelot 
2897d029125SVivien Didelot 	/*
2907d029125SVivien Didelot 	 * There is no DMI available or PCI bridge subvendor info,
2917d029125SVivien Didelot 	 * only the BIOS provides a 16-bit identification call.
2927d029125SVivien Didelot 	 * It is safer to find a signature in the BIOS shadow RAM.
2937d029125SVivien Didelot 	 */
2947d029125SVivien Didelot 	err = ts5500_check_signature();
2957d029125SVivien Didelot 	if (err)
2967d029125SVivien Didelot 		return err;
2977d029125SVivien Didelot 
2987d029125SVivien Didelot 	pdev = platform_device_register_simple("ts5500", -1, NULL, 0);
2997d029125SVivien Didelot 	if (IS_ERR(pdev))
3007d029125SVivien Didelot 		return PTR_ERR(pdev);
3017d029125SVivien Didelot 
3027d029125SVivien Didelot 	sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL);
3037d029125SVivien Didelot 	if (!sbc) {
3047d029125SVivien Didelot 		err = -ENOMEM;
3057d029125SVivien Didelot 		goto error;
3067d029125SVivien Didelot 	}
3077d029125SVivien Didelot 
3087d029125SVivien Didelot 	err = ts5500_detect_config(sbc);
3097d029125SVivien Didelot 	if (err)
3107d029125SVivien Didelot 		goto error;
3117d029125SVivien Didelot 
3127d029125SVivien Didelot 	platform_set_drvdata(pdev, sbc);
3137d029125SVivien Didelot 
3147d029125SVivien Didelot 	err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group);
3157d029125SVivien Didelot 	if (err)
3167d029125SVivien Didelot 		goto error;
3177d029125SVivien Didelot 
318832fcc89SVivien Didelot 	if (sbc->id == TS5500_PRODUCT_CODE) {
3197d029125SVivien Didelot 		ts5500_dio1_pdev.dev.parent = &pdev->dev;
3207d029125SVivien Didelot 		if (platform_device_register(&ts5500_dio1_pdev))
3217d029125SVivien Didelot 			dev_warn(&pdev->dev, "DIO1 block registration failed\n");
3227d029125SVivien Didelot 		ts5500_dio2_pdev.dev.parent = &pdev->dev;
3237d029125SVivien Didelot 		if (platform_device_register(&ts5500_dio2_pdev))
3247d029125SVivien Didelot 			dev_warn(&pdev->dev, "DIO2 block registration failed\n");
325832fcc89SVivien Didelot 	}
3267d029125SVivien Didelot 
3277d029125SVivien Didelot 	if (led_classdev_register(&pdev->dev, &ts5500_led_cdev))
3287d029125SVivien Didelot 		dev_warn(&pdev->dev, "LED registration failed\n");
3297d029125SVivien Didelot 
3307d029125SVivien Didelot 	if (sbc->adc) {
3317d029125SVivien Didelot 		ts5500_adc_pdev.dev.parent = &pdev->dev;
3327d029125SVivien Didelot 		if (platform_device_register(&ts5500_adc_pdev))
3337d029125SVivien Didelot 			dev_warn(&pdev->dev, "ADC registration failed\n");
3347d029125SVivien Didelot 	}
3357d029125SVivien Didelot 
3367d029125SVivien Didelot 	return 0;
3377d029125SVivien Didelot error:
3387d029125SVivien Didelot 	platform_device_unregister(pdev);
3397d029125SVivien Didelot 	return err;
3407d029125SVivien Didelot }
3417d029125SVivien Didelot device_initcall(ts5500_init);
342