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