2005-08-18 13:08:15 +04:00
/*
* linux / drivers / mfd / mcp - sa11x0 . c
*
* Copyright ( C ) 2001 - 2005 Russell King
*
* 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 .
*
* SA11x0 MCP ( Multimedia Communications Port ) driver .
*
* MCP read / write timeouts from Jordi Colomer , rehacked by rmk .
*/
# include <linux/module.h>
# include <linux/init.h>
2012-01-21 03:09:42 +04:00
# include <linux/io.h>
2005-08-18 13:08:15 +04:00
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/delay.h>
# include <linux/spinlock.h>
2005-10-29 22:07:23 +04:00
# include <linux/platform_device.h>
2012-01-21 13:34:30 +04:00
# include <linux/pm.h>
2009-02-10 16:54:57 +03:00
# include <linux/mfd/mcp.h>
2005-08-18 13:08:15 +04:00
2008-08-05 19:14:15 +04:00
# include <mach/hardware.h>
2005-08-18 13:08:15 +04:00
# include <asm/mach-types.h>
2012-08-24 17:17:38 +04:00
# include <linux/platform_data/mfd-mcp-sa11x0.h>
2005-08-18 13:08:15 +04:00
2012-01-21 02:30:15 +04:00
# define DRIVER_NAME "sa11x0-mcp"
2005-08-18 13:08:15 +04:00
struct mcp_sa11x0 {
2012-01-21 03:09:42 +04:00
void __iomem * base0 ;
void __iomem * base1 ;
u32 mccr0 ;
u32 mccr1 ;
2005-08-18 13:08:15 +04:00
} ;
2012-01-21 03:09:42 +04:00
/* Register offsets */
# define MCCR0(m) ((m)->base0 + 0x00)
# define MCDR0(m) ((m)->base0 + 0x08)
# define MCDR1(m) ((m)->base0 + 0x0c)
# define MCDR2(m) ((m)->base0 + 0x10)
# define MCSR(m) ((m)->base0 + 0x18)
# define MCCR1(m) ((m)->base1 + 0x00)
2005-08-18 13:08:15 +04:00
# define priv(mcp) ((struct mcp_sa11x0 *)mcp_priv(mcp))
static void
mcp_sa11x0_set_telecom_divisor ( struct mcp * mcp , unsigned int divisor )
{
2012-01-21 03:09:42 +04:00
struct mcp_sa11x0 * m = priv ( mcp ) ;
2005-08-18 13:08:15 +04:00
divisor / = 32 ;
2012-01-21 03:09:42 +04:00
m - > mccr0 & = ~ 0x00007f00 ;
m - > mccr0 | = divisor < < 8 ;
writel_relaxed ( m - > mccr0 , MCCR0 ( m ) ) ;
2005-08-18 13:08:15 +04:00
}
static void
mcp_sa11x0_set_audio_divisor ( struct mcp * mcp , unsigned int divisor )
{
2012-01-21 03:09:42 +04:00
struct mcp_sa11x0 * m = priv ( mcp ) ;
2005-08-18 13:08:15 +04:00
divisor / = 32 ;
2012-01-21 03:09:42 +04:00
m - > mccr0 & = ~ 0x0000007f ;
m - > mccr0 | = divisor ;
writel_relaxed ( m - > mccr0 , MCCR0 ( m ) ) ;
2005-08-18 13:08:15 +04:00
}
/*
* Write data to the device . The bit should be set after 3 subframe
* times ( each frame is 64 clocks ) . We wait a maximum of 6 subframes .
* We really should try doing something more productive while we
* wait .
*/
static void
mcp_sa11x0_write ( struct mcp * mcp , unsigned int reg , unsigned int val )
{
2012-01-21 03:09:42 +04:00
struct mcp_sa11x0 * m = priv ( mcp ) ;
2005-08-18 13:08:15 +04:00
int ret = - ETIME ;
int i ;
2012-01-21 03:09:42 +04:00
writel_relaxed ( reg < < 17 | MCDR2_Wr | ( val & 0xffff ) , MCDR2 ( m ) ) ;
2005-08-18 13:08:15 +04:00
for ( i = 0 ; i < 2 ; i + + ) {
udelay ( mcp - > rw_timeout ) ;
2012-01-21 03:09:42 +04:00
if ( readl_relaxed ( MCSR ( m ) ) & MCSR_CWC ) {
2005-08-18 13:08:15 +04:00
ret = 0 ;
break ;
}
}
if ( ret < 0 )
printk ( KERN_WARNING " mcp: write timed out \n " ) ;
}
/*
* Read data from the device . The bit should be set after 3 subframe
* times ( each frame is 64 clocks ) . We wait a maximum of 6 subframes .
* We really should try doing something more productive while we
* wait .
*/
static unsigned int
mcp_sa11x0_read ( struct mcp * mcp , unsigned int reg )
{
2012-01-21 03:09:42 +04:00
struct mcp_sa11x0 * m = priv ( mcp ) ;
2005-08-18 13:08:15 +04:00
int ret = - ETIME ;
int i ;
2012-01-21 03:09:42 +04:00
writel_relaxed ( reg < < 17 | MCDR2_Rd , MCDR2 ( m ) ) ;
2005-08-18 13:08:15 +04:00
for ( i = 0 ; i < 2 ; i + + ) {
udelay ( mcp - > rw_timeout ) ;
2012-01-21 03:09:42 +04:00
if ( readl_relaxed ( MCSR ( m ) ) & MCSR_CRC ) {
ret = readl_relaxed ( MCDR2 ( m ) ) & 0xffff ;
2005-08-18 13:08:15 +04:00
break ;
}
}
if ( ret < 0 )
printk ( KERN_WARNING " mcp: read timed out \n " ) ;
return ret ;
}
static void mcp_sa11x0_enable ( struct mcp * mcp )
{
2012-01-21 03:09:42 +04:00
struct mcp_sa11x0 * m = priv ( mcp ) ;
writel ( - 1 , MCSR ( m ) ) ;
m - > mccr0 | = MCCR0_MCE ;
writel_relaxed ( m - > mccr0 , MCCR0 ( m ) ) ;
2005-08-18 13:08:15 +04:00
}
static void mcp_sa11x0_disable ( struct mcp * mcp )
{
2012-01-21 03:09:42 +04:00
struct mcp_sa11x0 * m = priv ( mcp ) ;
m - > mccr0 & = ~ MCCR0_MCE ;
writel_relaxed ( m - > mccr0 , MCCR0 ( m ) ) ;
2005-08-18 13:08:15 +04:00
}
/*
* Our methods .
*/
static struct mcp_ops mcp_sa11x0 = {
. set_telecom_divisor = mcp_sa11x0_set_telecom_divisor ,
. set_audio_divisor = mcp_sa11x0_set_audio_divisor ,
. reg_write = mcp_sa11x0_write ,
. reg_read = mcp_sa11x0_read ,
. enable = mcp_sa11x0_enable ,
. disable = mcp_sa11x0_disable ,
} ;
2012-01-21 03:09:42 +04:00
static int mcp_sa11x0_probe ( struct platform_device * dev )
2005-08-18 13:08:15 +04:00
{
2012-01-21 03:09:42 +04:00
struct mcp_plat_data * data = dev - > dev . platform_data ;
struct resource * mem0 , * mem1 ;
struct mcp_sa11x0 * m ;
2005-08-18 13:08:15 +04:00
struct mcp * mcp ;
int ret ;
2005-08-18 13:10:46 +04:00
if ( ! data )
2005-08-18 13:08:15 +04:00
return - ENODEV ;
2012-01-21 03:09:42 +04:00
mem0 = platform_get_resource ( dev , IORESOURCE_MEM , 0 ) ;
mem1 = platform_get_resource ( dev , IORESOURCE_MEM , 1 ) ;
if ( ! mem0 | | ! mem1 )
return - ENXIO ;
if ( ! request_mem_region ( mem0 - > start , resource_size ( mem0 ) ,
DRIVER_NAME ) ) {
ret = - EBUSY ;
goto err_mem0 ;
}
2005-08-18 13:08:15 +04:00
2012-01-21 03:09:42 +04:00
if ( ! request_mem_region ( mem1 - > start , resource_size ( mem1 ) ,
DRIVER_NAME ) ) {
ret = - EBUSY ;
goto err_mem1 ;
}
mcp = mcp_host_alloc ( & dev - > dev , sizeof ( struct mcp_sa11x0 ) ) ;
2005-08-18 13:08:15 +04:00
if ( ! mcp ) {
ret = - ENOMEM ;
2012-01-21 03:09:42 +04:00
goto err_alloc ;
2005-08-18 13:08:15 +04:00
}
mcp - > owner = THIS_MODULE ;
mcp - > ops = & mcp_sa11x0 ;
2005-08-18 13:10:46 +04:00
mcp - > sclk_rate = data - > sclk_rate ;
2005-08-18 13:08:15 +04:00
2012-01-21 03:09:42 +04:00
m = priv ( mcp ) ;
m - > mccr0 = data - > mccr0 | 0x7f7f ;
m - > mccr1 = data - > mccr1 ;
m - > base0 = ioremap ( mem0 - > start , resource_size ( mem0 ) ) ;
m - > base1 = ioremap ( mem1 - > start , resource_size ( mem1 ) ) ;
if ( ! m - > base0 | | ! m - > base1 ) {
ret = - ENOMEM ;
goto err_ioremap ;
}
platform_set_drvdata ( dev , mcp ) ;
2005-08-18 13:08:15 +04:00
2005-08-18 13:10:46 +04:00
/*
* Initialise device . Note that we initially
* set the sampling rate to minimum .
*/
2012-01-21 03:09:42 +04:00
writel_relaxed ( - 1 , MCSR ( m ) ) ;
writel_relaxed ( m - > mccr1 , MCCR1 ( m ) ) ;
writel_relaxed ( m - > mccr0 , MCCR0 ( m ) ) ;
2005-08-18 13:08:15 +04:00
/*
* Calculate the read / write timeout ( us ) from the bit clock
* rate . This is the period for 3 64 - bit frames . Always
* round this time up .
*/
mcp - > rw_timeout = ( 64 * 3 * 1000000 + mcp - > sclk_rate - 1 ) /
mcp - > sclk_rate ;
2012-01-21 02:13:52 +04:00
ret = mcp_host_add ( mcp , data - > codec_pdata ) ;
2005-08-18 13:08:15 +04:00
if ( ret = = 0 )
2012-01-21 03:09:42 +04:00
return 0 ;
2005-08-18 13:08:15 +04:00
2012-01-21 03:09:42 +04:00
platform_set_drvdata ( dev , NULL ) ;
2005-08-18 13:08:15 +04:00
2012-01-21 03:09:42 +04:00
err_ioremap :
iounmap ( m - > base1 ) ;
iounmap ( m - > base0 ) ;
mcp_host_free ( mcp ) ;
err_alloc :
release_mem_region ( mem1 - > start , resource_size ( mem1 ) ) ;
err_mem1 :
release_mem_region ( mem0 - > start , resource_size ( mem0 ) ) ;
err_mem0 :
2005-08-18 13:08:15 +04:00
return ret ;
}
2012-01-20 21:37:21 +04:00
static int mcp_sa11x0_remove ( struct platform_device * dev )
2005-08-18 13:08:15 +04:00
{
2012-01-20 21:37:21 +04:00
struct mcp * mcp = platform_get_drvdata ( dev ) ;
2012-01-21 03:09:42 +04:00
struct mcp_sa11x0 * m = priv ( mcp ) ;
struct resource * mem0 , * mem1 ;
2012-01-21 22:26:17 +04:00
if ( m - > mccr0 & MCCR0_MCE )
dev_warn ( & dev - > dev ,
" device left active (missing disable call?) \n " ) ;
2012-01-21 03:09:42 +04:00
mem0 = platform_get_resource ( dev , IORESOURCE_MEM , 0 ) ;
mem1 = platform_get_resource ( dev , IORESOURCE_MEM , 1 ) ;
2005-08-18 13:08:15 +04:00
2012-01-20 21:37:21 +04:00
platform_set_drvdata ( dev , NULL ) ;
2012-01-21 02:51:07 +04:00
mcp_host_del ( mcp ) ;
2012-01-21 03:09:42 +04:00
iounmap ( m - > base1 ) ;
iounmap ( m - > base0 ) ;
2012-01-21 02:51:07 +04:00
mcp_host_free ( mcp ) ;
2012-01-21 03:09:42 +04:00
release_mem_region ( mem1 - > start , resource_size ( mem1 ) ) ;
release_mem_region ( mem0 - > start , resource_size ( mem0 ) ) ;
2005-08-18 13:08:15 +04:00
return 0 ;
}
2012-01-21 13:34:30 +04:00
# ifdef CONFIG_PM_SLEEP
static int mcp_sa11x0_suspend ( struct device * dev )
2005-08-18 13:08:15 +04:00
{
2012-01-21 13:34:30 +04:00
struct mcp_sa11x0 * m = priv ( dev_get_drvdata ( dev ) ) ;
2005-08-18 13:08:15 +04:00
2012-01-21 22:26:17 +04:00
if ( m - > mccr0 & MCCR0_MCE )
dev_warn ( dev , " device left active (missing disable call?) \n " ) ;
2012-01-21 03:09:42 +04:00
writel ( m - > mccr0 & ~ MCCR0_MCE , MCCR0 ( m ) ) ;
2005-10-28 20:52:56 +04:00
2005-08-18 13:08:15 +04:00
return 0 ;
}
2012-01-21 13:34:30 +04:00
static int mcp_sa11x0_resume ( struct device * dev )
2005-08-18 13:08:15 +04:00
{
2012-01-21 13:34:30 +04:00
struct mcp_sa11x0 * m = priv ( dev_get_drvdata ( dev ) ) ;
2005-08-18 13:08:15 +04:00
2012-01-21 03:09:42 +04:00
writel_relaxed ( m - > mccr1 , MCCR1 ( m ) ) ;
writel_relaxed ( m - > mccr0 , MCCR0 ( m ) ) ;
2005-10-28 20:52:56 +04:00
2005-08-18 13:08:15 +04:00
return 0 ;
}
2012-01-21 13:34:30 +04:00
# endif
static const struct dev_pm_ops mcp_sa11x0_pm_ops = {
2012-01-21 22:03:00 +04:00
# ifdef CONFIG_PM_SLEEP
. suspend = mcp_sa11x0_suspend ,
. freeze = mcp_sa11x0_suspend ,
. poweroff = mcp_sa11x0_suspend ,
. resume_noirq = mcp_sa11x0_resume ,
. thaw_noirq = mcp_sa11x0_resume ,
. restore_noirq = mcp_sa11x0_resume ,
# endif
2012-01-21 13:34:30 +04:00
} ;
2005-08-18 13:08:15 +04:00
2005-11-10 01:32:44 +03:00
static struct platform_driver mcp_sa11x0_driver = {
2005-08-18 13:08:15 +04:00
. probe = mcp_sa11x0_probe ,
. remove = mcp_sa11x0_remove ,
2005-11-10 01:32:44 +03:00
. driver = {
2012-01-21 02:30:15 +04:00
. name = DRIVER_NAME ,
. owner = THIS_MODULE ,
2012-01-21 13:34:30 +04:00
. pm = & mcp_sa11x0_pm_ops ,
2005-11-10 01:32:44 +03:00
} ,
2005-08-18 13:08:15 +04:00
} ;
/*
* This needs re - working
*/
2011-11-24 02:58:34 +04:00
module_platform_driver ( mcp_sa11x0_driver ) ;
2005-08-18 13:08:15 +04:00
2012-01-21 02:30:15 +04:00
MODULE_ALIAS ( " platform: " DRIVER_NAME ) ;
2005-08-18 13:08:15 +04:00
MODULE_AUTHOR ( " Russell King <rmk@arm.linux.org.uk> " ) ;
MODULE_DESCRIPTION ( " SA11x0 multimedia communications port driver " ) ;
MODULE_LICENSE ( " GPL " ) ;