2013-01-04 16:18:14 -05:00
/*
* Technologic Systems TS - 5500 Single Board Computer support
*
2014-07-08 18:57:48 -04:00
* Copyright ( C ) 2013 - 2014 Savoir - faire Linux Inc .
2013-01-04 16:18:14 -05:00
* Vivien Didelot < vivien . didelot @ savoirfairelinux . com >
*
* This program is free software ; you can redistribute it and / or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation ; either version 2 of the License , or ( at your option ) any later
* version .
*
*
* 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 .
*
2014-07-08 18:57:49 -04:00
* This code may be extended to support similar x86 - based platforms .
* Actually , the TS - 5500 and TS - 5400 are supported .
2013-01-04 16:18:14 -05:00
*/
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/leds.h>
2016-07-13 20:18:58 -04:00
# include <linux/init.h>
2013-01-04 16:18:14 -05:00
# 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 */
2014-07-08 18:57:49 -04:00
# define TS5400_PRODUCT_CODE 0x40 /* TS-5400 product code */
2013-01-04 16:18:14 -05:00
/* 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
2014-07-08 18:57:48 -04:00
* @ name : Board model name .
2013-01-04 16:18:14 -05:00
* @ 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 {
2014-07-08 18:57:48 -04:00
const char * name ;
2013-01-04 16:18:14 -05:00
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 ;
2014-02-08 08:52:10 +01:00
} ts5500_signatures [ ] __initconst = {
2013-01-04 16:18:14 -05:00
{ " 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 ;
2014-07-08 18:57:48 -04:00
sbc - > id = inb ( TS5500_PRODUCT_CODE_ADDR ) ;
if ( sbc - > id = = TS5500_PRODUCT_CODE ) {
sbc - > name = " TS-5500 " ;
2014-07-08 18:57:49 -04:00
} else if ( sbc - > id = = TS5400_PRODUCT_CODE ) {
sbc - > name = " TS-5400 " ;
2014-07-08 18:57:48 -04:00
} else {
pr_err ( " ts5500: unknown product code 0x%x \n " , sbc - > id ) ;
2013-01-04 16:18:14 -05:00
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 ;
}
2014-07-08 18:57:48 -04:00
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 ) ;
2014-07-08 18:57:47 -04:00
static ssize_t id_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
2013-01-04 16:18:14 -05:00
{
struct ts5500_sbc * sbc = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " 0x%.2x \n " , sbc - > id ) ;
}
2014-07-08 18:57:47 -04:00
static DEVICE_ATTR_RO ( id ) ;
2013-01-04 16:18:14 -05:00
2014-07-08 18:57:47 -04:00
static ssize_t jumpers_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
2013-01-04 16:18:14 -05:00
{
struct ts5500_sbc * sbc = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " 0x%.2x \n " , sbc - > jumpers > > 1 ) ;
}
2014-07-08 18:57:47 -04:00
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 ) ;
2013-01-04 16:18:14 -05:00
static struct attribute * ts5500_attributes [ ] = {
& dev_attr_id . attr ,
2014-07-08 18:57:48 -04:00
& dev_attr_name . attr ,
2013-01-04 16:18:14 -05:00
& 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 ;
2014-07-08 18:57:49 -04:00
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 " ) ;
}
2013-01-04 16:18:14 -05:00
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 ) ;