/* * 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");