1/* 2 * Technologic Systems TS-5500 Single Board Computer support 3 * 4 * Copyright (C) 2013-2014 Savoir-faire Linux Inc. 5 * Vivien Didelot <vivien.didelot@savoirfairelinux.com> 6 * 7 * This program is free software; you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License as published by the Free Software 9 * Foundation; either version 2 of the License, or (at your option) any later 10 * version. 11 * 12 * 13 * This driver registers the Technologic Systems TS-5500 Single Board Computer 14 * (SBC) and its devices, and exposes information to userspace such as jumpers' 15 * state or available options. For further information about sysfs entries, see 16 * Documentation/ABI/testing/sysfs-platform-ts5500. 17 * 18 * This code may be extended to support similar x86-based platforms. 19 * Actually, the TS-5500 and TS-5400 are supported. 20 */ 21 22#include <linux/delay.h> 23#include <linux/io.h> 24#include <linux/kernel.h> 25#include <linux/leds.h> 26#include <linux/module.h> 27#include <linux/platform_data/gpio-ts5500.h> 28#include <linux/platform_data/max197.h> 29#include <linux/platform_device.h> 30#include <linux/slab.h> 31 32/* Product code register */ 33#define TS5500_PRODUCT_CODE_ADDR 0x74 34#define TS5500_PRODUCT_CODE 0x60 /* TS-5500 product code */ 35#define TS5400_PRODUCT_CODE 0x40 /* TS-5400 product code */ 36 37/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */ 38#define TS5500_SRAM_RS485_ADC_ADDR 0x75 39#define TS5500_SRAM BIT(0) /* SRAM option */ 40#define TS5500_RS485 BIT(1) /* RS-485 option */ 41#define TS5500_ADC BIT(2) /* A/D converter option */ 42#define TS5500_RS485_RTS BIT(6) /* RTS for RS-485 */ 43#define TS5500_RS485_AUTO BIT(7) /* Automatic RS-485 */ 44 45/* External Reset/Industrial Temperature Range options register */ 46#define TS5500_ERESET_ITR_ADDR 0x76 47#define TS5500_ERESET BIT(0) /* External Reset option */ 48#define TS5500_ITR BIT(1) /* Indust. Temp. Range option */ 49 50/* LED/Jumpers register */ 51#define TS5500_LED_JP_ADDR 0x77 52#define TS5500_LED BIT(0) /* LED flag */ 53#define TS5500_JP1 BIT(1) /* Automatic CMOS */ 54#define TS5500_JP2 BIT(2) /* Enable Serial Console */ 55#define TS5500_JP3 BIT(3) /* Write Enable Drive A */ 56#define TS5500_JP4 BIT(4) /* Fast Console (115K baud) */ 57#define TS5500_JP5 BIT(5) /* User Jumper */ 58#define TS5500_JP6 BIT(6) /* Console on COM1 (req. JP2) */ 59#define TS5500_JP7 BIT(7) /* Undocumented (Unused) */ 60 61/* A/D Converter registers */ 62#define TS5500_ADC_CONV_BUSY_ADDR 0x195 /* Conversion state register */ 63#define TS5500_ADC_CONV_BUSY BIT(0) 64#define TS5500_ADC_CONV_INIT_LSB_ADDR 0x196 /* Start conv. / LSB register */ 65#define TS5500_ADC_CONV_MSB_ADDR 0x197 /* MSB register */ 66#define TS5500_ADC_CONV_DELAY 12 /* usec */ 67 68/** 69 * struct ts5500_sbc - TS-5500 board description 70 * @name: Board model name. 71 * @id: Board product ID. 72 * @sram: Flag for SRAM option. 73 * @rs485: Flag for RS-485 option. 74 * @adc: Flag for Analog/Digital converter option. 75 * @ereset: Flag for External Reset option. 76 * @itr: Flag for Industrial Temperature Range option. 77 * @jumpers: Bitfield for jumpers' state. 78 */ 79struct ts5500_sbc { 80 const char *name; 81 int id; 82 bool sram; 83 bool rs485; 84 bool adc; 85 bool ereset; 86 bool itr; 87 u8 jumpers; 88}; 89 90/* Board signatures in BIOS shadow RAM */ 91static const struct { 92 const char * const string; 93 const ssize_t offset; 94} ts5500_signatures[] __initconst = { 95 { "TS-5x00 AMD Elan", 0xb14 }, 96}; 97 98static int __init ts5500_check_signature(void) 99{ 100 void __iomem *bios; 101 int i, ret = -ENODEV; 102 103 bios = ioremap(0xf0000, 0x10000); 104 if (!bios) 105 return -ENOMEM; 106 107 for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) { 108 if (check_signature(bios + ts5500_signatures[i].offset, 109 ts5500_signatures[i].string, 110 strlen(ts5500_signatures[i].string))) { 111 ret = 0; 112 break; 113 } 114 } 115 116 iounmap(bios); 117 return ret; 118} 119 120static int __init ts5500_detect_config(struct ts5500_sbc *sbc) 121{ 122 u8 tmp; 123 int ret = 0; 124 125 if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500")) 126 return -EBUSY; 127 128 sbc->id = inb(TS5500_PRODUCT_CODE_ADDR); 129 if (sbc->id == TS5500_PRODUCT_CODE) { 130 sbc->name = "TS-5500"; 131 } else if (sbc->id == TS5400_PRODUCT_CODE) { 132 sbc->name = "TS-5400"; 133 } else { 134 pr_err("ts5500: unknown product code 0x%x\n", sbc->id); 135 ret = -ENODEV; 136 goto cleanup; 137 } 138 139 tmp = inb(TS5500_SRAM_RS485_ADC_ADDR); 140 sbc->sram = tmp & TS5500_SRAM; 141 sbc->rs485 = tmp & TS5500_RS485; 142 sbc->adc = tmp & TS5500_ADC; 143 144 tmp = inb(TS5500_ERESET_ITR_ADDR); 145 sbc->ereset = tmp & TS5500_ERESET; 146 sbc->itr = tmp & TS5500_ITR; 147 148 tmp = inb(TS5500_LED_JP_ADDR); 149 sbc->jumpers = tmp & ~TS5500_LED; 150 151cleanup: 152 release_region(TS5500_PRODUCT_CODE_ADDR, 4); 153 return ret; 154} 155 156static ssize_t name_show(struct device *dev, struct device_attribute *attr, 157 char *buf) 158{ 159 struct ts5500_sbc *sbc = dev_get_drvdata(dev); 160 161 return sprintf(buf, "%s\n", sbc->name); 162} 163static DEVICE_ATTR_RO(name); 164 165static ssize_t id_show(struct device *dev, struct device_attribute *attr, 166 char *buf) 167{ 168 struct ts5500_sbc *sbc = dev_get_drvdata(dev); 169 170 return sprintf(buf, "0x%.2x\n", sbc->id); 171} 172static DEVICE_ATTR_RO(id); 173 174static ssize_t jumpers_show(struct device *dev, struct device_attribute *attr, 175 char *buf) 176{ 177 struct ts5500_sbc *sbc = dev_get_drvdata(dev); 178 179 return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1); 180} 181static DEVICE_ATTR_RO(jumpers); 182 183#define TS5500_ATTR_BOOL(_field) \ 184 static ssize_t _field##_show(struct device *dev, \ 185 struct device_attribute *attr, char *buf) \ 186 { \ 187 struct ts5500_sbc *sbc = dev_get_drvdata(dev); \ 188 \ 189 return sprintf(buf, "%d\n", sbc->_field); \ 190 } \ 191 static DEVICE_ATTR_RO(_field) 192 193TS5500_ATTR_BOOL(sram); 194TS5500_ATTR_BOOL(rs485); 195TS5500_ATTR_BOOL(adc); 196TS5500_ATTR_BOOL(ereset); 197TS5500_ATTR_BOOL(itr); 198 199static struct attribute *ts5500_attributes[] = { 200 &dev_attr_id.attr, 201 &dev_attr_name.attr, 202 &dev_attr_jumpers.attr, 203 &dev_attr_sram.attr, 204 &dev_attr_rs485.attr, 205 &dev_attr_adc.attr, 206 &dev_attr_ereset.attr, 207 &dev_attr_itr.attr, 208 NULL 209}; 210 211static const struct attribute_group ts5500_attr_group = { 212 .attrs = ts5500_attributes, 213}; 214 215static struct resource ts5500_dio1_resource[] = { 216 DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"), 217}; 218 219static struct platform_device ts5500_dio1_pdev = { 220 .name = "ts5500-dio1", 221 .id = -1, 222 .resource = ts5500_dio1_resource, 223 .num_resources = 1, 224}; 225 226static struct resource ts5500_dio2_resource[] = { 227 DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"), 228}; 229 230static struct platform_device ts5500_dio2_pdev = { 231 .name = "ts5500-dio2", 232 .id = -1, 233 .resource = ts5500_dio2_resource, 234 .num_resources = 1, 235}; 236 237static void ts5500_led_set(struct led_classdev *led_cdev, 238 enum led_brightness brightness) 239{ 240 outb(!!brightness, TS5500_LED_JP_ADDR); 241} 242 243static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev) 244{ 245 return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF; 246} 247 248static struct led_classdev ts5500_led_cdev = { 249 .name = "ts5500:green:", 250 .brightness_set = ts5500_led_set, 251 .brightness_get = ts5500_led_get, 252}; 253 254static int ts5500_adc_convert(u8 ctrl) 255{ 256 u8 lsb, msb; 257 258 /* Start conversion (ensure the 3 MSB are set to 0) */ 259 outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR); 260 261 /* 262 * The platform has CPLD logic driving the A/D converter. 263 * The conversion must complete within 11 microseconds, 264 * otherwise we have to re-initiate a conversion. 265 */ 266 udelay(TS5500_ADC_CONV_DELAY); 267 if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY) 268 return -EBUSY; 269 270 /* Read the raw data */ 271 lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR); 272 msb = inb(TS5500_ADC_CONV_MSB_ADDR); 273 274 return (msb << 8) | lsb; 275} 276 277static struct max197_platform_data ts5500_adc_pdata = { 278 .convert = ts5500_adc_convert, 279}; 280 281static struct platform_device ts5500_adc_pdev = { 282 .name = "max197", 283 .id = -1, 284 .dev = { 285 .platform_data = &ts5500_adc_pdata, 286 }, 287}; 288 289static int __init ts5500_init(void) 290{ 291 struct platform_device *pdev; 292 struct ts5500_sbc *sbc; 293 int err; 294 295 /* 296 * There is no DMI available or PCI bridge subvendor info, 297 * only the BIOS provides a 16-bit identification call. 298 * It is safer to find a signature in the BIOS shadow RAM. 299 */ 300 err = ts5500_check_signature(); 301 if (err) 302 return err; 303 304 pdev = platform_device_register_simple("ts5500", -1, NULL, 0); 305 if (IS_ERR(pdev)) 306 return PTR_ERR(pdev); 307 308 sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL); 309 if (!sbc) { 310 err = -ENOMEM; 311 goto error; 312 } 313 314 err = ts5500_detect_config(sbc); 315 if (err) 316 goto error; 317 318 platform_set_drvdata(pdev, sbc); 319 320 err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group); 321 if (err) 322 goto error; 323 324 if (sbc->id == TS5500_PRODUCT_CODE) { 325 ts5500_dio1_pdev.dev.parent = &pdev->dev; 326 if (platform_device_register(&ts5500_dio1_pdev)) 327 dev_warn(&pdev->dev, "DIO1 block registration failed\n"); 328 ts5500_dio2_pdev.dev.parent = &pdev->dev; 329 if (platform_device_register(&ts5500_dio2_pdev)) 330 dev_warn(&pdev->dev, "DIO2 block registration failed\n"); 331 } 332 333 if (led_classdev_register(&pdev->dev, &ts5500_led_cdev)) 334 dev_warn(&pdev->dev, "LED registration failed\n"); 335 336 if (sbc->adc) { 337 ts5500_adc_pdev.dev.parent = &pdev->dev; 338 if (platform_device_register(&ts5500_adc_pdev)) 339 dev_warn(&pdev->dev, "ADC registration failed\n"); 340 } 341 342 return 0; 343error: 344 platform_device_unregister(pdev); 345 return err; 346} 347device_initcall(ts5500_init); 348 349MODULE_LICENSE("GPL"); 350MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>"); 351MODULE_DESCRIPTION("Technologic Systems TS-5500 platform driver"); 352