2008-09-10 21:58:42 +04:00
/*
* Core driver for WM8400 .
*
* Copyright 2008 Wolfson Microelectronics PLC .
*
* Author : Mark Brown < broonie @ opensource . wolfsonmicro . 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 .
*
*/
# include <linux/bug.h>
# include <linux/i2c.h>
# include <linux/kernel.h>
2008-12-18 12:54:22 +03:00
# include <linux/mfd/core.h>
2008-09-10 21:58:42 +04:00
# include <linux/mfd/wm8400-private.h>
# include <linux/mfd/wm8400-audio.h>
static struct {
u16 readable ; /* Mask of readable bits */
u16 writable ; /* Mask of writable bits */
u16 vol ; /* Mask of volatile bits */
int is_codec ; /* Register controlled by codec reset */
u16 default_val ; /* Value on reset */
} reg_data [ ] = {
{ 0xFFFF , 0xFFFF , 0x0000 , 0 , 0x6172 } , /* R0 */
{ 0x7000 , 0x0000 , 0x8000 , 0 , 0x0000 } , /* R1 */
{ 0xFF17 , 0xFF17 , 0x0000 , 0 , 0x0000 } , /* R2 */
{ 0xEBF3 , 0xEBF3 , 0x0000 , 1 , 0x6000 } , /* R3 */
{ 0x3CF3 , 0x3CF3 , 0x0000 , 1 , 0x0000 } , /* R4 */
{ 0xF1F8 , 0xF1F8 , 0x0000 , 1 , 0x4050 } , /* R5 */
{ 0xFC1F , 0xFC1F , 0x0000 , 1 , 0x4000 } , /* R6 */
{ 0xDFDE , 0xDFDE , 0x0000 , 1 , 0x01C8 } , /* R7 */
{ 0xFCFC , 0xFCFC , 0x0000 , 1 , 0x0000 } , /* R8 */
{ 0xEFFF , 0xEFFF , 0x0000 , 1 , 0x0040 } , /* R9 */
{ 0xEFFF , 0xEFFF , 0x0000 , 1 , 0x0040 } , /* R10 */
{ 0x27F7 , 0x27F7 , 0x0000 , 1 , 0x0004 } , /* R11 */
{ 0x01FF , 0x01FF , 0x0000 , 1 , 0x00C0 } , /* R12 */
{ 0x01FF , 0x01FF , 0x0000 , 1 , 0x00C0 } , /* R13 */
{ 0x1FEF , 0x1FEF , 0x0000 , 1 , 0x0000 } , /* R14 */
{ 0x0163 , 0x0163 , 0x0000 , 1 , 0x0100 } , /* R15 */
{ 0x01FF , 0x01FF , 0x0000 , 1 , 0x00C0 } , /* R16 */
{ 0x01FF , 0x01FF , 0x0000 , 1 , 0x00C0 } , /* R17 */
{ 0x1FFF , 0x0FFF , 0x0000 , 1 , 0x0000 } , /* R18 */
{ 0xFFFF , 0xFFFF , 0x0000 , 1 , 0x1000 } , /* R19 */
{ 0xFFFF , 0xFFFF , 0x0000 , 1 , 0x1010 } , /* R20 */
{ 0xFFFF , 0xFFFF , 0x0000 , 1 , 0x1010 } , /* R21 */
{ 0x0FDD , 0x0FDD , 0x0000 , 1 , 0x8000 } , /* R22 */
{ 0x1FFF , 0x1FFF , 0x0000 , 1 , 0x0800 } , /* R23 */
{ 0x0000 , 0x01DF , 0x0000 , 1 , 0x008B } , /* R24 */
{ 0x0000 , 0x01DF , 0x0000 , 1 , 0x008B } , /* R25 */
{ 0x0000 , 0x01DF , 0x0000 , 1 , 0x008B } , /* R26 */
{ 0x0000 , 0x01DF , 0x0000 , 1 , 0x008B } , /* R27 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0000 } , /* R28 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0000 } , /* R29 */
{ 0x0000 , 0x0077 , 0x0000 , 1 , 0x0066 } , /* R30 */
{ 0x0000 , 0x0033 , 0x0000 , 1 , 0x0022 } , /* R31 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0079 } , /* R32 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0079 } , /* R33 */
{ 0x0000 , 0x0003 , 0x0000 , 1 , 0x0003 } , /* R34 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0003 } , /* R35 */
{ 0x0000 , 0x0000 , 0x0000 , 0 , 0x0000 } , /* R36 */
{ 0x0000 , 0x003F , 0x0000 , 1 , 0x0100 } , /* R37 */
{ 0x0000 , 0x0000 , 0x0000 , 0 , 0x0000 } , /* R38 */
{ 0x0000 , 0x000F , 0x0000 , 0 , 0x0000 } , /* R39 */
{ 0x0000 , 0x00FF , 0x0000 , 1 , 0x0000 } , /* R40 */
{ 0x0000 , 0x01B7 , 0x0000 , 1 , 0x0000 } , /* R41 */
{ 0x0000 , 0x01B7 , 0x0000 , 1 , 0x0000 } , /* R42 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0000 } , /* R43 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0000 } , /* R44 */
{ 0x0000 , 0x00FD , 0x0000 , 1 , 0x0000 } , /* R45 */
{ 0x0000 , 0x00FD , 0x0000 , 1 , 0x0000 } , /* R46 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0000 } , /* R47 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0000 } , /* R48 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0000 } , /* R49 */
{ 0x0000 , 0x01FF , 0x0000 , 1 , 0x0000 } , /* R50 */
{ 0x0000 , 0x01B3 , 0x0000 , 1 , 0x0180 } , /* R51 */
{ 0x0000 , 0x0077 , 0x0000 , 1 , 0x0000 } , /* R52 */
{ 0x0000 , 0x0077 , 0x0000 , 1 , 0x0000 } , /* R53 */
{ 0x0000 , 0x00FF , 0x0000 , 1 , 0x0000 } , /* R54 */
{ 0x0000 , 0x0001 , 0x0000 , 1 , 0x0000 } , /* R55 */
{ 0x0000 , 0x003F , 0x0000 , 1 , 0x0000 } , /* R56 */
{ 0x0000 , 0x004F , 0x0000 , 1 , 0x0000 } , /* R57 */
{ 0x0000 , 0x00FD , 0x0000 , 1 , 0x0000 } , /* R58 */
{ 0x0000 , 0x0000 , 0x0000 , 0 , 0x0000 } , /* R59 */
{ 0x1FFF , 0x1FFF , 0x0000 , 1 , 0x0000 } , /* R60 */
{ 0xFFFF , 0xFFFF , 0x0000 , 1 , 0x0000 } , /* R61 */
{ 0x03FF , 0x03FF , 0x0000 , 1 , 0x0000 } , /* R62 */
{ 0x007F , 0x007F , 0x0000 , 1 , 0x0000 } , /* R63 */
{ 0x0000 , 0x0000 , 0x0000 , 0 , 0x0000 } , /* R64 */
{ 0xDFFF , 0xDFFF , 0x0000 , 0 , 0x0000 } , /* R65 */
{ 0xDFFF , 0xDFFF , 0x0000 , 0 , 0x0000 } , /* R66 */
{ 0xDFFF , 0xDFFF , 0x0000 , 0 , 0x0000 } , /* R67 */
{ 0xDFFF , 0xDFFF , 0x0000 , 0 , 0x0000 } , /* R68 */
{ 0x0000 , 0x0000 , 0x0000 , 0 , 0x0000 } , /* R69 */
{ 0xFFFF , 0xFFFF , 0x0000 , 0 , 0x4400 } , /* R70 */
{ 0x23FF , 0x23FF , 0x0000 , 0 , 0x0000 } , /* R71 */
{ 0xFFFF , 0xFFFF , 0x0000 , 0 , 0x4400 } , /* R72 */
{ 0x23FF , 0x23FF , 0x0000 , 0 , 0x0000 } , /* R73 */
{ 0x0000 , 0x0000 , 0x0000 , 0 , 0x0000 } , /* R74 */
{ 0x000E , 0x000E , 0x0000 , 0 , 0x0008 } , /* R75 */
{ 0xE00F , 0xE00F , 0x0000 , 0 , 0x0000 } , /* R76 */
{ 0x0000 , 0x0000 , 0x0000 , 0 , 0x0000 } , /* R77 */
{ 0x03C0 , 0x03C0 , 0x0000 , 0 , 0x02C0 } , /* R78 */
{ 0xFFFF , 0x0000 , 0xffff , 0 , 0x0000 } , /* R79 */
{ 0xFFFF , 0xFFFF , 0x0000 , 0 , 0x0000 } , /* R80 */
{ 0xFFFF , 0x0000 , 0xffff , 0 , 0x0000 } , /* R81 */
{ 0x2BFF , 0x0000 , 0xffff , 0 , 0x0000 } , /* R82 */
{ 0x0000 , 0x0000 , 0x0000 , 0 , 0x0000 } , /* R83 */
{ 0x80FF , 0x80FF , 0x0000 , 0 , 0x00ff } , /* R84 */
} ;
static int wm8400_read ( struct wm8400 * wm8400 , u8 reg , int num_regs , u16 * dest )
{
int i , ret = 0 ;
BUG_ON ( reg + num_regs - 1 > ARRAY_SIZE ( wm8400 - > reg_cache ) ) ;
/* If there are any volatile reads then read back the entire block */
for ( i = reg ; i < reg + num_regs ; i + + )
if ( reg_data [ i ] . vol ) {
ret = wm8400 - > read_dev ( wm8400 - > io_data , reg ,
num_regs , dest ) ;
if ( ret ! = 0 )
return ret ;
for ( i = 0 ; i < num_regs ; i + + )
dest [ i ] = be16_to_cpu ( dest [ i ] ) ;
return 0 ;
}
/* Otherwise use the cache */
memcpy ( dest , & wm8400 - > reg_cache [ reg ] , num_regs * sizeof ( u16 ) ) ;
return 0 ;
}
static int wm8400_write ( struct wm8400 * wm8400 , u8 reg , int num_regs ,
u16 * src )
{
int ret , i ;
BUG_ON ( reg + num_regs - 1 > ARRAY_SIZE ( wm8400 - > reg_cache ) ) ;
for ( i = 0 ; i < num_regs ; i + + ) {
BUG_ON ( ! reg_data [ reg + i ] . writable ) ;
wm8400 - > reg_cache [ reg + i ] = src [ i ] ;
src [ i ] = cpu_to_be16 ( src [ i ] ) ;
}
/* Do the actual I/O */
ret = wm8400 - > write_dev ( wm8400 - > io_data , reg , num_regs , src ) ;
if ( ret ! = 0 )
return - EIO ;
return 0 ;
}
/**
* wm8400_reg_read - Single register read
*
* @ wm8400 : Pointer to wm8400 control structure
* @ reg : Register to read
*
* @ return Read value
*/
u16 wm8400_reg_read ( struct wm8400 * wm8400 , u8 reg )
{
u16 val ;
mutex_lock ( & wm8400 - > io_lock ) ;
wm8400_read ( wm8400 , reg , 1 , & val ) ;
mutex_unlock ( & wm8400 - > io_lock ) ;
return val ;
}
EXPORT_SYMBOL_GPL ( wm8400_reg_read ) ;
int wm8400_block_read ( struct wm8400 * wm8400 , u8 reg , int count , u16 * data )
{
int ret ;
mutex_lock ( & wm8400 - > io_lock ) ;
ret = wm8400_read ( wm8400 , reg , count , data ) ;
mutex_unlock ( & wm8400 - > io_lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( wm8400_block_read ) ;
/**
* wm8400_set_bits - Bitmask write
*
* @ wm8400 : Pointer to wm8400 control structure
* @ reg : Register to access
* @ mask : Mask of bits to change
* @ val : Value to set for masked bits
*/
int wm8400_set_bits ( struct wm8400 * wm8400 , u8 reg , u16 mask , u16 val )
{
u16 tmp ;
int ret ;
mutex_lock ( & wm8400 - > io_lock ) ;
ret = wm8400_read ( wm8400 , reg , 1 , & tmp ) ;
tmp = ( tmp & ~ mask ) | val ;
if ( ret = = 0 )
ret = wm8400_write ( wm8400 , reg , 1 , & tmp ) ;
mutex_unlock ( & wm8400 - > io_lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( wm8400_set_bits ) ;
/**
* wm8400_reset_codec_reg_cache - Reset cached codec registers to
* their default values .
*/
void wm8400_reset_codec_reg_cache ( struct wm8400 * wm8400 )
{
int i ;
mutex_lock ( & wm8400 - > io_lock ) ;
/* Reset all codec registers to their initial value */
for ( i = 0 ; i < ARRAY_SIZE ( wm8400 - > reg_cache ) ; i + + )
if ( reg_data [ i ] . is_codec )
wm8400 - > reg_cache [ i ] = reg_data [ i ] . default_val ;
mutex_unlock ( & wm8400 - > io_lock ) ;
}
EXPORT_SYMBOL_GPL ( wm8400_reset_codec_reg_cache ) ;
2008-12-18 12:54:22 +03:00
static int wm8400_register_codec ( struct wm8400 * wm8400 )
{
struct mfd_cell cell = {
. name = " wm8400-codec " ,
. driver_data = wm8400 ,
} ;
return mfd_add_devices ( wm8400 - > dev , - 1 , & cell , 1 , NULL , 0 ) ;
}
2008-09-10 21:58:42 +04:00
/*
* wm8400_init - Generic initialisation
*
* The WM8400 can be configured as either an I2C or SPI device . Probe
* functions for each bus set up the accessors then call into this to
* set up the device itself .
*/
static int wm8400_init ( struct wm8400 * wm8400 ,
struct wm8400_platform_data * pdata )
{
u16 reg ;
int ret , i ;
mutex_init ( & wm8400 - > io_lock ) ;
2009-05-01 01:43:31 +04:00
dev_set_drvdata ( wm8400 - > dev , wm8400 ) ;
2008-09-10 21:58:42 +04:00
/* Check that this is actually a WM8400 */
ret = wm8400 - > read_dev ( wm8400 - > io_data , WM8400_RESET_ID , 1 , & reg ) ;
if ( ret ! = 0 ) {
dev_err ( wm8400 - > dev , " Chip ID register read failed \n " ) ;
return - EIO ;
}
if ( be16_to_cpu ( reg ) ! = reg_data [ WM8400_RESET_ID ] . default_val ) {
dev_err ( wm8400 - > dev , " Device is not a WM8400, ID is %x \n " ,
be16_to_cpu ( reg ) ) ;
return - ENODEV ;
}
/* We don't know what state the hardware is in and since this
* is a PMIC we can ' t reset it safely so initialise the register
* cache from the hardware .
*/
ret = wm8400 - > read_dev ( wm8400 - > io_data , 0 ,
ARRAY_SIZE ( wm8400 - > reg_cache ) ,
wm8400 - > reg_cache ) ;
if ( ret ! = 0 ) {
dev_err ( wm8400 - > dev , " Register cache read failed \n " ) ;
return - EIO ;
}
for ( i = 0 ; i < ARRAY_SIZE ( wm8400 - > reg_cache ) ; i + + )
wm8400 - > reg_cache [ i ] = be16_to_cpu ( wm8400 - > reg_cache [ i ] ) ;
/* If the codec is in reset use hard coded values */
if ( ! ( wm8400 - > reg_cache [ WM8400_POWER_MANAGEMENT_1 ] & WM8400_CODEC_ENA ) )
for ( i = 0 ; i < ARRAY_SIZE ( wm8400 - > reg_cache ) ; i + + )
if ( reg_data [ i ] . is_codec )
wm8400 - > reg_cache [ i ] = reg_data [ i ] . default_val ;
ret = wm8400_read ( wm8400 , WM8400_ID , 1 , & reg ) ;
if ( ret ! = 0 ) {
dev_err ( wm8400 - > dev , " ID register read failed: %d \n " , ret ) ;
return ret ;
}
reg = ( reg & WM8400_CHIP_REV_MASK ) > > WM8400_CHIP_REV_SHIFT ;
dev_info ( wm8400 - > dev , " WM8400 revision %x \n " , reg ) ;
2008-12-18 12:54:22 +03:00
ret = wm8400_register_codec ( wm8400 ) ;
if ( ret ! = 0 ) {
dev_err ( wm8400 - > dev , " Failed to register codec \n " ) ;
goto err_children ;
}
2008-09-10 21:58:42 +04:00
if ( pdata & & pdata - > platform_init ) {
ret = pdata - > platform_init ( wm8400 - > dev ) ;
2008-12-18 12:54:22 +03:00
if ( ret ! = 0 ) {
2008-09-10 21:58:42 +04:00
dev_err ( wm8400 - > dev , " Platform init failed: %d \n " ,
ret ) ;
2008-12-18 12:54:22 +03:00
goto err_children ;
}
2008-09-10 21:58:42 +04:00
} else
dev_warn ( wm8400 - > dev , " No platform initialisation supplied \n " ) ;
2008-12-18 12:54:22 +03:00
return 0 ;
err_children :
mfd_remove_devices ( wm8400 - > dev ) ;
2008-09-10 21:58:42 +04:00
return ret ;
}
static void wm8400_release ( struct wm8400 * wm8400 )
{
2008-12-18 12:54:22 +03:00
mfd_remove_devices ( wm8400 - > dev ) ;
2008-09-10 21:58:42 +04:00
}
# if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
static int wm8400_i2c_read ( void * io_data , char reg , int count , u16 * dest )
{
struct i2c_client * i2c = io_data ;
struct i2c_msg xfer [ 2 ] ;
int ret ;
/* Write register */
xfer [ 0 ] . addr = i2c - > addr ;
xfer [ 0 ] . flags = 0 ;
xfer [ 0 ] . len = 1 ;
xfer [ 0 ] . buf = & reg ;
/* Read data */
xfer [ 1 ] . addr = i2c - > addr ;
xfer [ 1 ] . flags = I2C_M_RD ;
xfer [ 1 ] . len = count * sizeof ( u16 ) ;
xfer [ 1 ] . buf = ( u8 * ) dest ;
ret = i2c_transfer ( i2c - > adapter , xfer , 2 ) ;
if ( ret = = 2 )
ret = 0 ;
else if ( ret > = 0 )
ret = - EIO ;
return ret ;
}
static int wm8400_i2c_write ( void * io_data , char reg , int count , const u16 * src )
{
struct i2c_client * i2c = io_data ;
u8 * msg ;
int ret ;
/* We add 1 byte for device register - ideally I2C would gather. */
msg = kmalloc ( ( count * sizeof ( u16 ) ) + 1 , GFP_KERNEL ) ;
if ( msg = = NULL )
return - ENOMEM ;
msg [ 0 ] = reg ;
memcpy ( & msg [ 1 ] , src , count * sizeof ( u16 ) ) ;
ret = i2c_master_send ( i2c , msg , ( count * sizeof ( u16 ) ) + 1 ) ;
if ( ret = = ( count * 2 ) + 1 )
ret = 0 ;
else if ( ret > = 0 )
ret = - EIO ;
kfree ( msg ) ;
return ret ;
}
static int wm8400_i2c_probe ( struct i2c_client * i2c ,
const struct i2c_device_id * id )
{
struct wm8400 * wm8400 ;
int ret ;
wm8400 = kzalloc ( sizeof ( struct wm8400 ) , GFP_KERNEL ) ;
if ( wm8400 = = NULL ) {
ret = - ENOMEM ;
goto err ;
}
wm8400 - > io_data = i2c ;
wm8400 - > read_dev = wm8400_i2c_read ;
wm8400 - > write_dev = wm8400_i2c_write ;
wm8400 - > dev = & i2c - > dev ;
i2c_set_clientdata ( i2c , wm8400 ) ;
ret = wm8400_init ( wm8400 , i2c - > dev . platform_data ) ;
if ( ret ! = 0 )
goto struct_err ;
return 0 ;
struct_err :
i2c_set_clientdata ( i2c , NULL ) ;
kfree ( wm8400 ) ;
err :
return ret ;
}
static int wm8400_i2c_remove ( struct i2c_client * i2c )
{
struct wm8400 * wm8400 = i2c_get_clientdata ( i2c ) ;
wm8400_release ( wm8400 ) ;
i2c_set_clientdata ( i2c , NULL ) ;
kfree ( wm8400 ) ;
return 0 ;
}
static const struct i2c_device_id wm8400_i2c_id [ ] = {
{ " wm8400 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , wm8400_i2c_id ) ;
static struct i2c_driver wm8400_i2c_driver = {
. driver = {
. name = " WM8400 " ,
. owner = THIS_MODULE ,
} ,
. probe = wm8400_i2c_probe ,
. remove = wm8400_i2c_remove ,
. id_table = wm8400_i2c_id ,
} ;
# endif
static int __init wm8400_module_init ( void )
{
int ret = - ENODEV ;
# if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
ret = i2c_add_driver ( & wm8400_i2c_driver ) ;
if ( ret ! = 0 )
pr_err ( " Failed to register I2C driver: %d \n " , ret ) ;
# endif
return ret ;
}
module_init ( wm8400_module_init ) ;
static void __exit wm8400_module_exit ( void )
{
# if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
i2c_del_driver ( & wm8400_i2c_driver ) ;
# endif
}
module_exit ( wm8400_module_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Mark Brown <broonie@opensource.wolfsonmicro.com> " ) ;