mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
341 lines
8.8 KiB
341 lines
8.8 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Technologic Systems TS-5500 Single Board Computer support |
|
* |
|
* Copyright (C) 2013-2014 Savoir-faire Linux Inc. |
|
* Vivien Didelot <[email protected]> |
|
* |
|
* 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 may be extended to support similar x86-based platforms. |
|
* Actually, the TS-5500 and TS-5400 are supported. |
|
*/ |
|
|
|
#include <linux/delay.h> |
|
#include <linux/io.h> |
|
#include <linux/kernel.h> |
|
#include <linux/leds.h> |
|
#include <linux/init.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 */ |
|
#define TS5400_PRODUCT_CODE 0x40 /* TS-5400 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 |
|
* @name: Board model name. |
|
* @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 { |
|
const char *name; |
|
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[] __initconst = { |
|
{ "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; |
|
|
|
sbc->id = inb(TS5500_PRODUCT_CODE_ADDR); |
|
if (sbc->id == TS5500_PRODUCT_CODE) { |
|
sbc->name = "TS-5500"; |
|
} else if (sbc->id == TS5400_PRODUCT_CODE) { |
|
sbc->name = "TS-5400"; |
|
} else { |
|
pr_err("ts5500: unknown product code 0x%x\n", sbc->id); |
|
ret = -ENODEV; |
|
goto cleanup; |
|
} |
|
|
|
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 name_show(struct device *dev, struct device_attribute *attr, |
|
char *buf) |
|
{ |
|
struct ts5500_sbc *sbc = dev_get_drvdata(dev); |
|
|
|
return sprintf(buf, "%s\n", sbc->name); |
|
} |
|
static DEVICE_ATTR_RO(name); |
|
|
|
static ssize_t id_show(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 DEVICE_ATTR_RO(id); |
|
|
|
static ssize_t jumpers_show(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); |
|
} |
|
static DEVICE_ATTR_RO(jumpers); |
|
|
|
#define TS5500_ATTR_BOOL(_field) \ |
|
static ssize_t _field##_show(struct device *dev, \ |
|
struct device_attribute *attr, char *buf) \ |
|
{ \ |
|
struct ts5500_sbc *sbc = dev_get_drvdata(dev); \ |
|
\ |
|
return sprintf(buf, "%d\n", sbc->_field); \ |
|
} \ |
|
static DEVICE_ATTR_RO(_field) |
|
|
|
TS5500_ATTR_BOOL(sram); |
|
TS5500_ATTR_BOOL(rs485); |
|
TS5500_ATTR_BOOL(adc); |
|
TS5500_ATTR_BOOL(ereset); |
|
TS5500_ATTR_BOOL(itr); |
|
|
|
static struct attribute *ts5500_attributes[] = { |
|
&dev_attr_id.attr, |
|
&dev_attr_name.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; |
|
|
|
if (sbc->id == TS5500_PRODUCT_CODE) { |
|
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);
|
|
|