2008-12-22 14:05:27 +03:00
/*
* dm355evm_msp . c - driver for MSP430 firmware on DM355EVM board
*
* Copyright ( C ) 2008 David Brownell
*
* 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 .
*/
# include <linux/init.h>
# include <linux/mutex.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/gpio.h>
# include <linux/leds.h>
# include <linux/i2c.h>
# include <linux/i2c/dm355evm_msp.h>
/*
* The DM355 is a DaVinci chip with video support but no C64 + DSP . Its
* EVM board has an MSP430 programmed with firmware for various board
* support functions . This driver exposes some of them directly , and
* supports other drivers ( e . g . RTC , input ) for more complex access .
*
* Because this firmware is entirely board - specific , this file embeds
* knowledge that would be passed as platform_data in a generic driver .
*
* This driver was tested with firmware revision A4 .
*/
# if defined(CONFIG_KEYBOARD_DM355EVM) \
| | defined ( CONFIG_KEYBOARD_DM355EVM_MODULE )
# define msp_has_keyboard() true
# else
# define msp_has_keyboard() false
# endif
# if defined(CONFIG_LEDS_GPIO) || defined(CONFIG_LEDS_GPIO_MODULE)
# define msp_has_leds() true
# else
# define msp_has_leds() false
# endif
# if defined(CONFIG_RTC_DRV_DM355EVM) || defined(CONFIG_RTC_DRV_DM355EVM_MODULE)
# define msp_has_rtc() true
# else
# define msp_has_rtc() false
# endif
# if defined(CONFIG_VIDEO_TVP514X) || defined(CONFIG_VIDEO_TVP514X_MODULE)
# define msp_has_tvp() true
# else
# define msp_has_tvp() false
# endif
/*----------------------------------------------------------------------*/
/* REVISIT for paranoia's sake, retry reads/writes on error */
static struct i2c_client * msp430 ;
/**
* dm355evm_msp_write - Writes a register in dm355evm_msp
* @ value : the value to be written
* @ reg : register address
*
* Returns result of operation - 0 is success , else negative errno
*/
int dm355evm_msp_write ( u8 value , u8 reg )
{
return i2c_smbus_write_byte_data ( msp430 , reg , value ) ;
}
EXPORT_SYMBOL ( dm355evm_msp_write ) ;
/**
* dm355evm_msp_read - Reads a register from dm355evm_msp
* @ reg : register address
*
* Returns result of operation - value , or negative errno
*/
int dm355evm_msp_read ( u8 reg )
{
return i2c_smbus_read_byte_data ( msp430 , reg ) ;
}
EXPORT_SYMBOL ( dm355evm_msp_read ) ;
/*----------------------------------------------------------------------*/
/*
* Many of the msp430 pins are just used as fixed - direction GPIOs .
* We could export a few more of them this way , if we wanted .
*/
# define MSP_GPIO(bit,reg) ((DM355EVM_MSP_ ## reg) << 3 | (bit))
static const u8 msp_gpios [ ] = {
/* eight leds */
MSP_GPIO ( 0 , LED ) , MSP_GPIO ( 1 , LED ) ,
MSP_GPIO ( 2 , LED ) , MSP_GPIO ( 3 , LED ) ,
MSP_GPIO ( 4 , LED ) , MSP_GPIO ( 5 , LED ) ,
MSP_GPIO ( 6 , LED ) , MSP_GPIO ( 7 , LED ) ,
/* SW6 and the NTSC/nPAL jumper */
MSP_GPIO ( 0 , SWITCH1 ) , MSP_GPIO ( 1 , SWITCH1 ) ,
MSP_GPIO ( 2 , SWITCH1 ) , MSP_GPIO ( 3 , SWITCH1 ) ,
MSP_GPIO ( 4 , SWITCH1 ) ,
2009-01-09 04:02:42 +03:00
/* switches on MMC/SD sockets */
MSP_GPIO ( 1 , SDMMC ) , MSP_GPIO ( 2 , SDMMC ) , /* mmc0 WP, nCD */
MSP_GPIO ( 3 , SDMMC ) , MSP_GPIO ( 4 , SDMMC ) , /* mmc1 WP, nCD */
2008-12-22 14:05:27 +03:00
} ;
# define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3)
# define MSP_GPIO_MASK(offset) BIT(msp_gpios[(offset)] & 0x07)
static int msp_gpio_in ( struct gpio_chip * chip , unsigned offset )
{
switch ( MSP_GPIO_REG ( offset ) ) {
case DM355EVM_MSP_SWITCH1 :
case DM355EVM_MSP_SWITCH2 :
case DM355EVM_MSP_SDMMC :
return 0 ;
default :
return - EINVAL ;
}
}
static u8 msp_led_cache ;
static int msp_gpio_get ( struct gpio_chip * chip , unsigned offset )
{
int reg , status ;
reg = MSP_GPIO_REG ( offset ) ;
status = dm355evm_msp_read ( reg ) ;
if ( status < 0 )
return status ;
if ( reg = = DM355EVM_MSP_LED )
msp_led_cache = status ;
return status & MSP_GPIO_MASK ( offset ) ;
}
static int msp_gpio_out ( struct gpio_chip * chip , unsigned offset , int value )
{
int mask , bits ;
/* NOTE: there are some other signals that could be
* packaged as output GPIOs , but they aren ' t as useful
* as the LEDs . . . so for now we don ' t .
*/
if ( MSP_GPIO_REG ( offset ) ! = DM355EVM_MSP_LED )
return - EINVAL ;
mask = MSP_GPIO_MASK ( offset ) ;
bits = msp_led_cache ;
bits & = ~ mask ;
if ( value )
bits | = mask ;
msp_led_cache = bits ;
return dm355evm_msp_write ( bits , DM355EVM_MSP_LED ) ;
}
static void msp_gpio_set ( struct gpio_chip * chip , unsigned offset , int value )
{
msp_gpio_out ( chip , offset , value ) ;
}
static struct gpio_chip dm355evm_msp_gpio = {
. label = " dm355evm_msp " ,
. owner = THIS_MODULE ,
. direction_input = msp_gpio_in ,
. get = msp_gpio_get ,
. direction_output = msp_gpio_out ,
. set = msp_gpio_set ,
. base = - EINVAL , /* dynamic assignment */
. ngpio = ARRAY_SIZE ( msp_gpios ) ,
. can_sleep = true ,
} ;
/*----------------------------------------------------------------------*/
static struct device * add_child ( struct i2c_client * client , const char * name ,
void * pdata , unsigned pdata_len ,
bool can_wakeup , int irq )
{
struct platform_device * pdev ;
int status ;
pdev = platform_device_alloc ( name , - 1 ) ;
if ( ! pdev ) {
dev_dbg ( & client - > dev , " can't alloc dev \n " ) ;
status = - ENOMEM ;
goto err ;
}
device_init_wakeup ( & pdev - > dev , can_wakeup ) ;
pdev - > dev . parent = & client - > dev ;
if ( pdata ) {
status = platform_device_add_data ( pdev , pdata , pdata_len ) ;
if ( status < 0 ) {
dev_dbg ( & pdev - > dev , " can't add platform_data \n " ) ;
goto err ;
}
}
if ( irq ) {
struct resource r = {
. start = irq ,
. flags = IORESOURCE_IRQ ,
} ;
status = platform_device_add_resources ( pdev , & r , 1 ) ;
if ( status < 0 ) {
dev_dbg ( & pdev - > dev , " can't add irq \n " ) ;
goto err ;
}
}
status = platform_device_add ( pdev ) ;
err :
if ( status < 0 ) {
platform_device_put ( pdev ) ;
dev_err ( & client - > dev , " can't add %s dev \n " , name ) ;
return ERR_PTR ( status ) ;
}
return & pdev - > dev ;
}
static int add_children ( struct i2c_client * client )
{
static const struct {
int offset ;
char * label ;
} config_inputs [ ] = {
/* 8 == right after the LEDs */
{ 8 + 0 , " sw6_1 " , } ,
{ 8 + 1 , " sw6_2 " , } ,
{ 8 + 2 , " sw6_3 " , } ,
{ 8 + 3 , " sw6_4 " , } ,
{ 8 + 4 , " NTSC/nPAL " , } ,
} ;
struct device * child ;
int status ;
int i ;
/* GPIO-ish stuff */
dm355evm_msp_gpio . dev = & client - > dev ;
status = gpiochip_add ( & dm355evm_msp_gpio ) ;
if ( status < 0 )
return status ;
/* LED output */
if ( msp_has_leds ( ) ) {
# define GPIO_LED(l) .name = l, .active_low = true
static struct gpio_led evm_leds [ ] = {
{ GPIO_LED ( " dm355evm::ds14 " ) ,
. default_trigger = " heartbeat " , } ,
{ GPIO_LED ( " dm355evm::ds15 " ) ,
. default_trigger = " mmc0 " , } ,
{ GPIO_LED ( " dm355evm::ds16 " ) ,
/* could also be a CE-ATA drive */
. default_trigger = " mmc1 " , } ,
{ GPIO_LED ( " dm355evm::ds17 " ) ,
. default_trigger = " nand-disk " , } ,
{ GPIO_LED ( " dm355evm::ds18 " ) , } ,
{ GPIO_LED ( " dm355evm::ds19 " ) , } ,
{ GPIO_LED ( " dm355evm::ds20 " ) , } ,
{ GPIO_LED ( " dm355evm::ds21 " ) , } ,
} ;
# undef GPIO_LED
struct gpio_led_platform_data evm_led_data = {
. num_leds = ARRAY_SIZE ( evm_leds ) ,
. leds = evm_leds ,
} ;
for ( i = 0 ; i < ARRAY_SIZE ( evm_leds ) ; i + + )
evm_leds [ i ] . gpio = i + dm355evm_msp_gpio . base ;
/* NOTE: these are the only fully programmable LEDs
* on the board , since GPIO - 61 / ds22 ( and many signals
* going to DC7 ) must be used for AEMIF address lines
* unless the top 1 GB of NAND is unused . . .
*/
child = add_child ( client , " leds-gpio " ,
& evm_led_data , sizeof ( evm_led_data ) ,
false , 0 ) ;
if ( IS_ERR ( child ) )
return PTR_ERR ( child ) ;
}
/* configuration inputs */
for ( i = 0 ; i < ARRAY_SIZE ( config_inputs ) ; i + + ) {
int gpio = dm355evm_msp_gpio . base + config_inputs [ i ] . offset ;
gpio_request ( gpio , config_inputs [ i ] . label ) ;
gpio_direction_input ( gpio ) ;
/* make it easy for userspace to see these */
gpio_export ( gpio , false ) ;
}
2009-01-09 04:02:42 +03:00
/* MMC/SD inputs -- right after the last config input */
if ( client - > dev . platform_data ) {
void ( * mmcsd_setup ) ( unsigned ) = client - > dev . platform_data ;
mmcsd_setup ( dm355evm_msp_gpio . base + 8 + 5 ) ;
}
2008-12-22 14:05:27 +03:00
/* RTC is a 32 bit counter, no alarm */
if ( msp_has_rtc ( ) ) {
child = add_child ( client , " rtc-dm355evm " ,
NULL , 0 , false , 0 ) ;
if ( IS_ERR ( child ) )
return PTR_ERR ( child ) ;
}
/* input from buttons and IR remote (uses the IRQ) */
if ( msp_has_keyboard ( ) ) {
child = add_child ( client , " dm355evm_keys " ,
NULL , 0 , true , client - > irq ) ;
if ( IS_ERR ( child ) )
return PTR_ERR ( child ) ;
}
return 0 ;
}
/*----------------------------------------------------------------------*/
static void dm355evm_command ( unsigned command )
{
int status ;
status = dm355evm_msp_write ( command , DM355EVM_MSP_COMMAND ) ;
if ( status < 0 )
dev_err ( & msp430 - > dev , " command %d failure %d \n " ,
command , status ) ;
}
static void dm355evm_power_off ( void )
{
dm355evm_command ( MSP_COMMAND_POWEROFF ) ;
}
static int dm355evm_msp_remove ( struct i2c_client * client )
{
pm_power_off = NULL ;
msp430 = NULL ;
return 0 ;
}
static int
dm355evm_msp_probe ( struct i2c_client * client , const struct i2c_device_id * id )
{
int status ;
const char * video = msp_has_tvp ( ) ? " TVP5146 " : " imager " ;
if ( msp430 )
return - EBUSY ;
msp430 = client ;
/* display revision status; doubles as sanity check */
status = dm355evm_msp_read ( DM355EVM_MSP_FIRMREV ) ;
if ( status < 0 )
goto fail ;
dev_info ( & client - > dev , " firmware v.%02X, %s as video-in \n " ,
status , video ) ;
/* mux video input: either tvp5146 or some external imager */
status = dm355evm_msp_write ( msp_has_tvp ( ) ? 0 : MSP_VIDEO_IMAGER ,
DM355EVM_MSP_VIDEO_IN ) ;
if ( status < 0 )
dev_warn ( & client - > dev , " error %d muxing %s as video-in \n " ,
status , video ) ;
/* init LED cache, and turn off the LEDs */
msp_led_cache = 0xff ;
dm355evm_msp_write ( msp_led_cache , DM355EVM_MSP_LED ) ;
/* export capabilities we support */
status = add_children ( client ) ;
if ( status < 0 )
goto fail ;
/* PM hookup */
pm_power_off = dm355evm_power_off ;
return 0 ;
fail :
/* FIXME remove children ... */
dm355evm_msp_remove ( client ) ;
return status ;
}
static const struct i2c_device_id dm355evm_msp_ids [ ] = {
{ " dm355evm_msp " , 0 } ,
{ /* end of list */ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , dm355evm_msp_ids ) ;
static struct i2c_driver dm355evm_msp_driver = {
. driver . name = " dm355evm_msp " ,
. id_table = dm355evm_msp_ids ,
. probe = dm355evm_msp_probe ,
. remove = dm355evm_msp_remove ,
} ;
static int __init dm355evm_msp_init ( void )
{
return i2c_add_driver ( & dm355evm_msp_driver ) ;
}
subsys_initcall ( dm355evm_msp_init ) ;
static void __exit dm355evm_msp_exit ( void )
{
i2c_del_driver ( & dm355evm_msp_driver ) ;
}
module_exit ( dm355evm_msp_exit ) ;
MODULE_DESCRIPTION ( " Interface to MSP430 firmware on DM355EVM " ) ;
MODULE_LICENSE ( " GPL " ) ;