162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Technologic Systems TS-5500 Single Board Computer support
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2013-2014 Savoir-faire Linux Inc.
662306a36Sopenharmony_ci *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * This driver registers the Technologic Systems TS-5500 Single Board Computer
962306a36Sopenharmony_ci * (SBC) and its devices, and exposes information to userspace such as jumpers'
1062306a36Sopenharmony_ci * state or available options. For further information about sysfs entries, see
1162306a36Sopenharmony_ci * Documentation/ABI/testing/sysfs-platform-ts5500.
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * This code may be extended to support similar x86-based platforms.
1462306a36Sopenharmony_ci * Actually, the TS-5500 and TS-5400 are supported.
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/delay.h>
1862306a36Sopenharmony_ci#include <linux/io.h>
1962306a36Sopenharmony_ci#include <linux/kernel.h>
2062306a36Sopenharmony_ci#include <linux/leds.h>
2162306a36Sopenharmony_ci#include <linux/init.h>
2262306a36Sopenharmony_ci#include <linux/platform_data/max197.h>
2362306a36Sopenharmony_ci#include <linux/platform_device.h>
2462306a36Sopenharmony_ci#include <linux/slab.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* Product code register */
2762306a36Sopenharmony_ci#define TS5500_PRODUCT_CODE_ADDR	0x74
2862306a36Sopenharmony_ci#define TS5500_PRODUCT_CODE		0x60	/* TS-5500 product code */
2962306a36Sopenharmony_ci#define TS5400_PRODUCT_CODE		0x40	/* TS-5400 product code */
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */
3262306a36Sopenharmony_ci#define TS5500_SRAM_RS485_ADC_ADDR	0x75
3362306a36Sopenharmony_ci#define TS5500_SRAM			BIT(0)	/* SRAM option */
3462306a36Sopenharmony_ci#define TS5500_RS485			BIT(1)	/* RS-485 option */
3562306a36Sopenharmony_ci#define TS5500_ADC			BIT(2)	/* A/D converter option */
3662306a36Sopenharmony_ci#define TS5500_RS485_RTS		BIT(6)	/* RTS for RS-485 */
3762306a36Sopenharmony_ci#define TS5500_RS485_AUTO		BIT(7)	/* Automatic RS-485 */
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* External Reset/Industrial Temperature Range options register */
4062306a36Sopenharmony_ci#define TS5500_ERESET_ITR_ADDR		0x76
4162306a36Sopenharmony_ci#define TS5500_ERESET			BIT(0)	/* External Reset option */
4262306a36Sopenharmony_ci#define TS5500_ITR			BIT(1)	/* Indust. Temp. Range option */
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/* LED/Jumpers register */
4562306a36Sopenharmony_ci#define TS5500_LED_JP_ADDR		0x77
4662306a36Sopenharmony_ci#define TS5500_LED			BIT(0)	/* LED flag */
4762306a36Sopenharmony_ci#define TS5500_JP1			BIT(1)	/* Automatic CMOS */
4862306a36Sopenharmony_ci#define TS5500_JP2			BIT(2)	/* Enable Serial Console */
4962306a36Sopenharmony_ci#define TS5500_JP3			BIT(3)	/* Write Enable Drive A */
5062306a36Sopenharmony_ci#define TS5500_JP4			BIT(4)	/* Fast Console (115K baud) */
5162306a36Sopenharmony_ci#define TS5500_JP5			BIT(5)	/* User Jumper */
5262306a36Sopenharmony_ci#define TS5500_JP6			BIT(6)	/* Console on COM1 (req. JP2) */
5362306a36Sopenharmony_ci#define TS5500_JP7			BIT(7)	/* Undocumented (Unused) */
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci/* A/D Converter registers */
5662306a36Sopenharmony_ci#define TS5500_ADC_CONV_BUSY_ADDR	0x195	/* Conversion state register */
5762306a36Sopenharmony_ci#define TS5500_ADC_CONV_BUSY		BIT(0)
5862306a36Sopenharmony_ci#define TS5500_ADC_CONV_INIT_LSB_ADDR	0x196	/* Start conv. / LSB register */
5962306a36Sopenharmony_ci#define TS5500_ADC_CONV_MSB_ADDR	0x197	/* MSB register */
6062306a36Sopenharmony_ci#define TS5500_ADC_CONV_DELAY		12	/* usec */
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/**
6362306a36Sopenharmony_ci * struct ts5500_sbc - TS-5500 board description
6462306a36Sopenharmony_ci * @name:	Board model name.
6562306a36Sopenharmony_ci * @id:		Board product ID.
6662306a36Sopenharmony_ci * @sram:	Flag for SRAM option.
6762306a36Sopenharmony_ci * @rs485:	Flag for RS-485 option.
6862306a36Sopenharmony_ci * @adc:	Flag for Analog/Digital converter option.
6962306a36Sopenharmony_ci * @ereset:	Flag for External Reset option.
7062306a36Sopenharmony_ci * @itr:	Flag for Industrial Temperature Range option.
7162306a36Sopenharmony_ci * @jumpers:	Bitfield for jumpers' state.
7262306a36Sopenharmony_ci */
7362306a36Sopenharmony_cistruct ts5500_sbc {
7462306a36Sopenharmony_ci	const char *name;
7562306a36Sopenharmony_ci	int	id;
7662306a36Sopenharmony_ci	bool	sram;
7762306a36Sopenharmony_ci	bool	rs485;
7862306a36Sopenharmony_ci	bool	adc;
7962306a36Sopenharmony_ci	bool	ereset;
8062306a36Sopenharmony_ci	bool	itr;
8162306a36Sopenharmony_ci	u8	jumpers;
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/* Board signatures in BIOS shadow RAM */
8562306a36Sopenharmony_cistatic const struct {
8662306a36Sopenharmony_ci	const char * const string;
8762306a36Sopenharmony_ci	const ssize_t offset;
8862306a36Sopenharmony_ci} ts5500_signatures[] __initconst = {
8962306a36Sopenharmony_ci	{ "TS-5x00 AMD Elan", 0xb14 },
9062306a36Sopenharmony_ci};
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic int __init ts5500_check_signature(void)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	void __iomem *bios;
9562306a36Sopenharmony_ci	int i, ret = -ENODEV;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	bios = ioremap(0xf0000, 0x10000);
9862306a36Sopenharmony_ci	if (!bios)
9962306a36Sopenharmony_ci		return -ENOMEM;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) {
10262306a36Sopenharmony_ci		if (check_signature(bios + ts5500_signatures[i].offset,
10362306a36Sopenharmony_ci				    ts5500_signatures[i].string,
10462306a36Sopenharmony_ci				    strlen(ts5500_signatures[i].string))) {
10562306a36Sopenharmony_ci			ret = 0;
10662306a36Sopenharmony_ci			break;
10762306a36Sopenharmony_ci		}
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	iounmap(bios);
11162306a36Sopenharmony_ci	return ret;
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic int __init ts5500_detect_config(struct ts5500_sbc *sbc)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	u8 tmp;
11762306a36Sopenharmony_ci	int ret = 0;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500"))
12062306a36Sopenharmony_ci		return -EBUSY;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	sbc->id = inb(TS5500_PRODUCT_CODE_ADDR);
12362306a36Sopenharmony_ci	if (sbc->id == TS5500_PRODUCT_CODE) {
12462306a36Sopenharmony_ci		sbc->name = "TS-5500";
12562306a36Sopenharmony_ci	} else if (sbc->id == TS5400_PRODUCT_CODE) {
12662306a36Sopenharmony_ci		sbc->name = "TS-5400";
12762306a36Sopenharmony_ci	} else {
12862306a36Sopenharmony_ci		pr_err("ts5500: unknown product code 0x%x\n", sbc->id);
12962306a36Sopenharmony_ci		ret = -ENODEV;
13062306a36Sopenharmony_ci		goto cleanup;
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	tmp = inb(TS5500_SRAM_RS485_ADC_ADDR);
13462306a36Sopenharmony_ci	sbc->sram = tmp & TS5500_SRAM;
13562306a36Sopenharmony_ci	sbc->rs485 = tmp & TS5500_RS485;
13662306a36Sopenharmony_ci	sbc->adc = tmp & TS5500_ADC;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	tmp = inb(TS5500_ERESET_ITR_ADDR);
13962306a36Sopenharmony_ci	sbc->ereset = tmp & TS5500_ERESET;
14062306a36Sopenharmony_ci	sbc->itr = tmp & TS5500_ITR;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	tmp = inb(TS5500_LED_JP_ADDR);
14362306a36Sopenharmony_ci	sbc->jumpers = tmp & ~TS5500_LED;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cicleanup:
14662306a36Sopenharmony_ci	release_region(TS5500_PRODUCT_CODE_ADDR, 4);
14762306a36Sopenharmony_ci	return ret;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic ssize_t name_show(struct device *dev, struct device_attribute *attr,
15162306a36Sopenharmony_ci		char *buf)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	struct ts5500_sbc *sbc = dev_get_drvdata(dev);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	return sprintf(buf, "%s\n", sbc->name);
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_cistatic DEVICE_ATTR_RO(name);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic ssize_t id_show(struct device *dev, struct device_attribute *attr,
16062306a36Sopenharmony_ci		char *buf)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	struct ts5500_sbc *sbc = dev_get_drvdata(dev);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	return sprintf(buf, "0x%.2x\n", sbc->id);
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_cistatic DEVICE_ATTR_RO(id);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic ssize_t jumpers_show(struct device *dev, struct device_attribute *attr,
16962306a36Sopenharmony_ci		char *buf)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct ts5500_sbc *sbc = dev_get_drvdata(dev);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1);
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_cistatic DEVICE_ATTR_RO(jumpers);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci#define TS5500_ATTR_BOOL(_field)					\
17862306a36Sopenharmony_ci	static ssize_t _field##_show(struct device *dev,		\
17962306a36Sopenharmony_ci			struct device_attribute *attr, char *buf)	\
18062306a36Sopenharmony_ci	{								\
18162306a36Sopenharmony_ci		struct ts5500_sbc *sbc = dev_get_drvdata(dev);		\
18262306a36Sopenharmony_ci									\
18362306a36Sopenharmony_ci		return sprintf(buf, "%d\n", sbc->_field);		\
18462306a36Sopenharmony_ci	}								\
18562306a36Sopenharmony_ci	static DEVICE_ATTR_RO(_field)
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ciTS5500_ATTR_BOOL(sram);
18862306a36Sopenharmony_ciTS5500_ATTR_BOOL(rs485);
18962306a36Sopenharmony_ciTS5500_ATTR_BOOL(adc);
19062306a36Sopenharmony_ciTS5500_ATTR_BOOL(ereset);
19162306a36Sopenharmony_ciTS5500_ATTR_BOOL(itr);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic struct attribute *ts5500_attributes[] = {
19462306a36Sopenharmony_ci	&dev_attr_id.attr,
19562306a36Sopenharmony_ci	&dev_attr_name.attr,
19662306a36Sopenharmony_ci	&dev_attr_jumpers.attr,
19762306a36Sopenharmony_ci	&dev_attr_sram.attr,
19862306a36Sopenharmony_ci	&dev_attr_rs485.attr,
19962306a36Sopenharmony_ci	&dev_attr_adc.attr,
20062306a36Sopenharmony_ci	&dev_attr_ereset.attr,
20162306a36Sopenharmony_ci	&dev_attr_itr.attr,
20262306a36Sopenharmony_ci	NULL
20362306a36Sopenharmony_ci};
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic const struct attribute_group ts5500_attr_group = {
20662306a36Sopenharmony_ci	.attrs = ts5500_attributes,
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic struct resource ts5500_dio1_resource[] = {
21062306a36Sopenharmony_ci	DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"),
21162306a36Sopenharmony_ci};
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic struct platform_device ts5500_dio1_pdev = {
21462306a36Sopenharmony_ci	.name = "ts5500-dio1",
21562306a36Sopenharmony_ci	.id = -1,
21662306a36Sopenharmony_ci	.resource = ts5500_dio1_resource,
21762306a36Sopenharmony_ci	.num_resources = 1,
21862306a36Sopenharmony_ci};
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic struct resource ts5500_dio2_resource[] = {
22162306a36Sopenharmony_ci	DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"),
22262306a36Sopenharmony_ci};
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic struct platform_device ts5500_dio2_pdev = {
22562306a36Sopenharmony_ci	.name = "ts5500-dio2",
22662306a36Sopenharmony_ci	.id = -1,
22762306a36Sopenharmony_ci	.resource = ts5500_dio2_resource,
22862306a36Sopenharmony_ci	.num_resources = 1,
22962306a36Sopenharmony_ci};
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic void ts5500_led_set(struct led_classdev *led_cdev,
23262306a36Sopenharmony_ci			   enum led_brightness brightness)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	outb(!!brightness, TS5500_LED_JP_ADDR);
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic enum led_brightness ts5500_led_get(struct led_classdev *led_cdev)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF;
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic struct led_classdev ts5500_led_cdev = {
24362306a36Sopenharmony_ci	.name = "ts5500:green:",
24462306a36Sopenharmony_ci	.brightness_set = ts5500_led_set,
24562306a36Sopenharmony_ci	.brightness_get = ts5500_led_get,
24662306a36Sopenharmony_ci};
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_cistatic int ts5500_adc_convert(u8 ctrl)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	u8 lsb, msb;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	/* Start conversion (ensure the 3 MSB are set to 0) */
25362306a36Sopenharmony_ci	outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	/*
25662306a36Sopenharmony_ci	 * The platform has CPLD logic driving the A/D converter.
25762306a36Sopenharmony_ci	 * The conversion must complete within 11 microseconds,
25862306a36Sopenharmony_ci	 * otherwise we have to re-initiate a conversion.
25962306a36Sopenharmony_ci	 */
26062306a36Sopenharmony_ci	udelay(TS5500_ADC_CONV_DELAY);
26162306a36Sopenharmony_ci	if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY)
26262306a36Sopenharmony_ci		return -EBUSY;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	/* Read the raw data */
26562306a36Sopenharmony_ci	lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR);
26662306a36Sopenharmony_ci	msb = inb(TS5500_ADC_CONV_MSB_ADDR);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return (msb << 8) | lsb;
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cistatic struct max197_platform_data ts5500_adc_pdata = {
27262306a36Sopenharmony_ci	.convert = ts5500_adc_convert,
27362306a36Sopenharmony_ci};
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cistatic struct platform_device ts5500_adc_pdev = {
27662306a36Sopenharmony_ci	.name = "max197",
27762306a36Sopenharmony_ci	.id = -1,
27862306a36Sopenharmony_ci	.dev = {
27962306a36Sopenharmony_ci		.platform_data = &ts5500_adc_pdata,
28062306a36Sopenharmony_ci	},
28162306a36Sopenharmony_ci};
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic int __init ts5500_init(void)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	struct platform_device *pdev;
28662306a36Sopenharmony_ci	struct ts5500_sbc *sbc;
28762306a36Sopenharmony_ci	int err;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	/*
29062306a36Sopenharmony_ci	 * There is no DMI available or PCI bridge subvendor info,
29162306a36Sopenharmony_ci	 * only the BIOS provides a 16-bit identification call.
29262306a36Sopenharmony_ci	 * It is safer to find a signature in the BIOS shadow RAM.
29362306a36Sopenharmony_ci	 */
29462306a36Sopenharmony_ci	err = ts5500_check_signature();
29562306a36Sopenharmony_ci	if (err)
29662306a36Sopenharmony_ci		return err;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	pdev = platform_device_register_simple("ts5500", -1, NULL, 0);
29962306a36Sopenharmony_ci	if (IS_ERR(pdev))
30062306a36Sopenharmony_ci		return PTR_ERR(pdev);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL);
30362306a36Sopenharmony_ci	if (!sbc) {
30462306a36Sopenharmony_ci		err = -ENOMEM;
30562306a36Sopenharmony_ci		goto error;
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	err = ts5500_detect_config(sbc);
30962306a36Sopenharmony_ci	if (err)
31062306a36Sopenharmony_ci		goto error;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	platform_set_drvdata(pdev, sbc);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group);
31562306a36Sopenharmony_ci	if (err)
31662306a36Sopenharmony_ci		goto error;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	if (sbc->id == TS5500_PRODUCT_CODE) {
31962306a36Sopenharmony_ci		ts5500_dio1_pdev.dev.parent = &pdev->dev;
32062306a36Sopenharmony_ci		if (platform_device_register(&ts5500_dio1_pdev))
32162306a36Sopenharmony_ci			dev_warn(&pdev->dev, "DIO1 block registration failed\n");
32262306a36Sopenharmony_ci		ts5500_dio2_pdev.dev.parent = &pdev->dev;
32362306a36Sopenharmony_ci		if (platform_device_register(&ts5500_dio2_pdev))
32462306a36Sopenharmony_ci			dev_warn(&pdev->dev, "DIO2 block registration failed\n");
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	if (led_classdev_register(&pdev->dev, &ts5500_led_cdev))
32862306a36Sopenharmony_ci		dev_warn(&pdev->dev, "LED registration failed\n");
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	if (sbc->adc) {
33162306a36Sopenharmony_ci		ts5500_adc_pdev.dev.parent = &pdev->dev;
33262306a36Sopenharmony_ci		if (platform_device_register(&ts5500_adc_pdev))
33362306a36Sopenharmony_ci			dev_warn(&pdev->dev, "ADC registration failed\n");
33462306a36Sopenharmony_ci	}
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	return 0;
33762306a36Sopenharmony_cierror:
33862306a36Sopenharmony_ci	platform_device_unregister(pdev);
33962306a36Sopenharmony_ci	return err;
34062306a36Sopenharmony_ci}
34162306a36Sopenharmony_cidevice_initcall(ts5500_init);
342