// 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 < vivien . didelot @ savoirfairelinux . com >
*
* 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 0 x74
#define TS5500_PRODUCT_CODE 0 x60 /* TS-5500 product code */
#define TS5400_PRODUCT_CODE 0 x40 /* TS-5400 product code */
/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */
#define TS5500_SRAM_RS485_ADC_ADDR 0 x75
#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 0 x76
#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 0 x77
#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 0 x195 /* Conversion state register */
#define TS5500_ADC_CONV_BUSY BIT(0 )
#define TS5500_ADC_CONV_INIT_LSB_ADDR 0 x196 /* Start conv. / LSB register */
#define TS5500_ADC_CONV_MSB_ADDR 0 x197 /* 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" , 0 xb14 },
};
static int __init ts5500_check_signature(void )
{
void __iomem *bios;
int i, ret = -ENODEV;
bios = ioremap(0 xf0000, 0 x10000);
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 & 0 x1f, 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);
Messung V0.5 in Prozent C=91 H=91 G=90
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland