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