linux/arch/x86/platform/ts5500/ts5500.c
<<
>>
Prefs
   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