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