2014-02-24 21:26:11 +04:00
/*
* TI AEMIF driver
*
* Copyright ( C ) 2010 - 2013 Texas Instruments Incorporated . http : //www.ti.com/
*
* Authors :
* Murali Karicheri < m - karicheri2 @ ti . com >
* Ivan Khoronzhuk < ivan . khoronzhuk @ ti . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
2017-01-13 13:00:25 +03:00
# include <linux/platform_data/ti-aemif.h>
2014-02-24 21:26:11 +04:00
# define TA_SHIFT 2
# define RHOLD_SHIFT 4
# define RSTROBE_SHIFT 7
# define RSETUP_SHIFT 13
# define WHOLD_SHIFT 17
# define WSTROBE_SHIFT 20
# define WSETUP_SHIFT 26
# define EW_SHIFT 30
# define SS_SHIFT 31
# define TA(x) ((x) << TA_SHIFT)
# define RHOLD(x) ((x) << RHOLD_SHIFT)
# define RSTROBE(x) ((x) << RSTROBE_SHIFT)
# define RSETUP(x) ((x) << RSETUP_SHIFT)
# define WHOLD(x) ((x) << WHOLD_SHIFT)
# define WSTROBE(x) ((x) << WSTROBE_SHIFT)
# define WSETUP(x) ((x) << WSETUP_SHIFT)
# define EW(x) ((x) << EW_SHIFT)
# define SS(x) ((x) << SS_SHIFT)
# define ASIZE_MAX 0x1
# define TA_MAX 0x3
# define RHOLD_MAX 0x7
# define RSTROBE_MAX 0x3f
# define RSETUP_MAX 0xf
# define WHOLD_MAX 0x7
# define WSTROBE_MAX 0x3f
# define WSETUP_MAX 0xf
# define EW_MAX 0x1
# define SS_MAX 0x1
# define NUM_CS 4
# define TA_VAL(x) (((x) & TA(TA_MAX)) >> TA_SHIFT)
# define RHOLD_VAL(x) (((x) & RHOLD(RHOLD_MAX)) >> RHOLD_SHIFT)
# define RSTROBE_VAL(x) (((x) & RSTROBE(RSTROBE_MAX)) >> RSTROBE_SHIFT)
# define RSETUP_VAL(x) (((x) & RSETUP(RSETUP_MAX)) >> RSETUP_SHIFT)
# define WHOLD_VAL(x) (((x) & WHOLD(WHOLD_MAX)) >> WHOLD_SHIFT)
# define WSTROBE_VAL(x) (((x) & WSTROBE(WSTROBE_MAX)) >> WSTROBE_SHIFT)
# define WSETUP_VAL(x) (((x) & WSETUP(WSETUP_MAX)) >> WSETUP_SHIFT)
# define EW_VAL(x) (((x) & EW(EW_MAX)) >> EW_SHIFT)
# define SS_VAL(x) (((x) & SS(SS_MAX)) >> SS_SHIFT)
# define NRCSR_OFFSET 0x00
# define AWCCR_OFFSET 0x04
# define A1CR_OFFSET 0x10
# define ACR_ASIZE_MASK 0x3
# define ACR_EW_MASK BIT(30)
# define ACR_SS_MASK BIT(31)
# define ASIZE_16BIT 1
# define CONFIG_MASK (TA(TA_MAX) | \
RHOLD ( RHOLD_MAX ) | \
RSTROBE ( RSTROBE_MAX ) | \
RSETUP ( RSETUP_MAX ) | \
WHOLD ( WHOLD_MAX ) | \
WSTROBE ( WSTROBE_MAX ) | \
WSETUP ( WSETUP_MAX ) | \
EW ( EW_MAX ) | SS ( SS_MAX ) | \
ASIZE_MAX )
/**
* struct aemif_cs_data : structure to hold cs parameters
* @ cs : chip - select number
* @ wstrobe : write strobe width , ns
* @ rstrobe : read strobe width , ns
* @ wsetup : write setup width , ns
* @ whold : write hold width , ns
* @ rsetup : read setup width , ns
* @ rhold : read hold width , ns
* @ ta : minimum turn around time , ns
* @ enable_ss : enable / disable select strobe mode
* @ enable_ew : enable / disable extended wait mode
* @ asize : width of the asynchronous device ' s data bus
*/
struct aemif_cs_data {
u8 cs ;
u16 wstrobe ;
u16 rstrobe ;
u8 wsetup ;
u8 whold ;
u8 rsetup ;
u8 rhold ;
u8 ta ;
u8 enable_ss ;
u8 enable_ew ;
u8 asize ;
} ;
/**
* struct aemif_device : structure to hold device data
* @ base : base address of AEMIF registers
* @ clk : source clock
* @ clk_rate : clock ' s rate in kHz
* @ num_cs : number of assigned chip - selects
* @ cs_offset : start number of cs nodes
* @ cs_data : array of chip - select settings
*/
struct aemif_device {
void __iomem * base ;
struct clk * clk ;
unsigned long clk_rate ;
u8 num_cs ;
int cs_offset ;
struct aemif_cs_data cs_data [ NUM_CS ] ;
} ;
/**
* aemif_calc_rate - calculate timing data .
* @ pdev : platform device to calculate for
* @ wanted : The cycle time needed in nanoseconds .
* @ clk : The input clock rate in kHz .
* @ max : The maximum divider value that can be programmed .
*
* On success , returns the calculated timing value minus 1 for easy
* programming into AEMIF timing registers , else negative errno .
*/
static int aemif_calc_rate ( struct platform_device * pdev , int wanted ,
unsigned long clk , int max )
{
int result ;
result = DIV_ROUND_UP ( ( wanted * clk ) , NSEC_PER_MSEC ) - 1 ;
dev_dbg ( & pdev - > dev , " %s: result %d from %ld, %d \n " , __func__ , result ,
clk , wanted ) ;
/* It is generally OK to have a more relaxed timing than requested... */
if ( result < 0 )
result = 0 ;
/* ... But configuring tighter timings is not an option. */
else if ( result > max )
result = - EINVAL ;
return result ;
}
/**
* aemif_config_abus - configure async bus parameters
* @ pdev : platform device to configure for
* @ csnum : aemif chip select number
*
* This function programs the given timing values ( in real clock ) into the
* AEMIF registers taking the AEMIF clock into account .
*
* This function does not use any locking while programming the AEMIF
* because it is expected that there is only one user of a given
* chip - select .
*
* Returns 0 on success , else negative errno .
*/
static int aemif_config_abus ( struct platform_device * pdev , int csnum )
{
struct aemif_device * aemif = platform_get_drvdata ( pdev ) ;
struct aemif_cs_data * data = & aemif - > cs_data [ csnum ] ;
int ta , rhold , rstrobe , rsetup , whold , wstrobe , wsetup ;
unsigned long clk_rate = aemif - > clk_rate ;
unsigned offset ;
u32 set , val ;
offset = A1CR_OFFSET + ( data - > cs - aemif - > cs_offset ) * 4 ;
ta = aemif_calc_rate ( pdev , data - > ta , clk_rate , TA_MAX ) ;
rhold = aemif_calc_rate ( pdev , data - > rhold , clk_rate , RHOLD_MAX ) ;
rstrobe = aemif_calc_rate ( pdev , data - > rstrobe , clk_rate , RSTROBE_MAX ) ;
rsetup = aemif_calc_rate ( pdev , data - > rsetup , clk_rate , RSETUP_MAX ) ;
whold = aemif_calc_rate ( pdev , data - > whold , clk_rate , WHOLD_MAX ) ;
wstrobe = aemif_calc_rate ( pdev , data - > wstrobe , clk_rate , WSTROBE_MAX ) ;
wsetup = aemif_calc_rate ( pdev , data - > wsetup , clk_rate , WSETUP_MAX ) ;
if ( ta < 0 | | rhold < 0 | | rstrobe < 0 | | rsetup < 0 | |
whold < 0 | | wstrobe < 0 | | wsetup < 0 ) {
dev_err ( & pdev - > dev , " %s: cannot get suitable timings \n " ,
__func__ ) ;
return - EINVAL ;
}
set = TA ( ta ) | RHOLD ( rhold ) | RSTROBE ( rstrobe ) | RSETUP ( rsetup ) |
WHOLD ( whold ) | WSTROBE ( wstrobe ) | WSETUP ( wsetup ) ;
set | = ( data - > asize & ACR_ASIZE_MASK ) ;
if ( data - > enable_ew )
set | = ACR_EW_MASK ;
if ( data - > enable_ss )
set | = ACR_SS_MASK ;
val = readl ( aemif - > base + offset ) ;
val & = ~ CONFIG_MASK ;
val | = set ;
writel ( val , aemif - > base + offset ) ;
return 0 ;
}
static inline int aemif_cycles_to_nsec ( int val , unsigned long clk_rate )
{
return ( ( val + 1 ) * NSEC_PER_MSEC ) / clk_rate ;
}
/**
* aemif_get_hw_params - function to read hw register values
* @ pdev : platform device to read for
* @ csnum : aemif chip select number
*
* This function reads the defaults from the registers and update
* the timing values . Required for get / set commands and also for
* the case when driver needs to use defaults in hardware .
*/
static void aemif_get_hw_params ( struct platform_device * pdev , int csnum )
{
struct aemif_device * aemif = platform_get_drvdata ( pdev ) ;
struct aemif_cs_data * data = & aemif - > cs_data [ csnum ] ;
unsigned long clk_rate = aemif - > clk_rate ;
u32 val , offset ;
offset = A1CR_OFFSET + ( data - > cs - aemif - > cs_offset ) * 4 ;
val = readl ( aemif - > base + offset ) ;
data - > ta = aemif_cycles_to_nsec ( TA_VAL ( val ) , clk_rate ) ;
data - > rhold = aemif_cycles_to_nsec ( RHOLD_VAL ( val ) , clk_rate ) ;
data - > rstrobe = aemif_cycles_to_nsec ( RSTROBE_VAL ( val ) , clk_rate ) ;
data - > rsetup = aemif_cycles_to_nsec ( RSETUP_VAL ( val ) , clk_rate ) ;
data - > whold = aemif_cycles_to_nsec ( WHOLD_VAL ( val ) , clk_rate ) ;
data - > wstrobe = aemif_cycles_to_nsec ( WSTROBE_VAL ( val ) , clk_rate ) ;
data - > wsetup = aemif_cycles_to_nsec ( WSETUP_VAL ( val ) , clk_rate ) ;
data - > enable_ew = EW_VAL ( val ) ;
data - > enable_ss = SS_VAL ( val ) ;
data - > asize = val & ASIZE_MAX ;
}
/**
* of_aemif_parse_abus_config - parse CS configuration from DT
* @ pdev : platform device to parse for
* @ np : device node ptr
*
* This function update the emif async bus configuration based on the values
* configured in a cs device binding node .
*/
static int of_aemif_parse_abus_config ( struct platform_device * pdev ,
struct device_node * np )
{
struct aemif_device * aemif = platform_get_drvdata ( pdev ) ;
struct aemif_cs_data * data ;
u32 cs ;
u32 val ;
if ( of_property_read_u32 ( np , " ti,cs-chipselect " , & cs ) ) {
dev_dbg ( & pdev - > dev , " cs property is required " ) ;
return - EINVAL ;
}
if ( cs - aemif - > cs_offset > = NUM_CS | | cs < aemif - > cs_offset ) {
dev_dbg ( & pdev - > dev , " cs number is incorrect %d " , cs ) ;
return - EINVAL ;
}
if ( aemif - > num_cs > = NUM_CS ) {
dev_dbg ( & pdev - > dev , " cs count is more than %d " , NUM_CS ) ;
return - EINVAL ;
}
data = & aemif - > cs_data [ aemif - > num_cs ] ;
data - > cs = cs ;
/* read the current value in the hw register */
aemif_get_hw_params ( pdev , aemif - > num_cs + + ) ;
/* override the values from device node */
if ( ! of_property_read_u32 ( np , " ti,cs-min-turnaround-ns " , & val ) )
data - > ta = val ;
if ( ! of_property_read_u32 ( np , " ti,cs-read-hold-ns " , & val ) )
data - > rhold = val ;
if ( ! of_property_read_u32 ( np , " ti,cs-read-strobe-ns " , & val ) )
data - > rstrobe = val ;
if ( ! of_property_read_u32 ( np , " ti,cs-read-setup-ns " , & val ) )
data - > rsetup = val ;
if ( ! of_property_read_u32 ( np , " ti,cs-write-hold-ns " , & val ) )
data - > whold = val ;
if ( ! of_property_read_u32 ( np , " ti,cs-write-strobe-ns " , & val ) )
data - > wstrobe = val ;
if ( ! of_property_read_u32 ( np , " ti,cs-write-setup-ns " , & val ) )
data - > wsetup = val ;
if ( ! of_property_read_u32 ( np , " ti,cs-bus-width " , & val ) )
if ( val = = 16 )
data - > asize = 1 ;
data - > enable_ew = of_property_read_bool ( np , " ti,cs-extended-wait-mode " ) ;
data - > enable_ss = of_property_read_bool ( np , " ti,cs-select-strobe-mode " ) ;
return 0 ;
}
static const struct of_device_id aemif_of_match [ ] = {
{ . compatible = " ti,davinci-aemif " , } ,
{ . compatible = " ti,da850-aemif " , } ,
{ } ,
} ;
2015-10-04 19:23:49 +03:00
MODULE_DEVICE_TABLE ( of , aemif_of_match ) ;
2014-02-24 21:26:11 +04:00
static int aemif_probe ( struct platform_device * pdev )
{
int i ;
int ret = - ENODEV ;
struct resource * res ;
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
struct device_node * child_np ;
struct aemif_device * aemif ;
2017-01-13 13:00:25 +03:00
struct aemif_platform_data * pdata ;
struct of_dev_auxdata * dev_lookup ;
2014-02-24 21:26:11 +04:00
if ( np = = NULL )
return 0 ;
aemif = devm_kzalloc ( dev , sizeof ( * aemif ) , GFP_KERNEL ) ;
if ( ! aemif )
return - ENOMEM ;
2017-01-13 13:00:25 +03:00
pdata = dev_get_platdata ( & pdev - > dev ) ;
dev_lookup = pdata ? pdata - > dev_lookup : NULL ;
2014-02-24 21:26:11 +04:00
platform_set_drvdata ( pdev , aemif ) ;
aemif - > clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( aemif - > clk ) ) {
dev_err ( dev , " cannot get clock 'aemif' \n " ) ;
return PTR_ERR ( aemif - > clk ) ;
}
2017-05-31 13:25:54 +03:00
ret = clk_prepare_enable ( aemif - > clk ) ;
if ( ret )
return ret ;
2014-02-24 21:26:11 +04:00
aemif - > clk_rate = clk_get_rate ( aemif - > clk ) / MSEC_PER_SEC ;
if ( of_device_is_compatible ( np , " ti,da850-aemif " ) )
aemif - > cs_offset = 2 ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
aemif - > base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( aemif - > base ) ) {
ret = PTR_ERR ( aemif - > base ) ;
goto error ;
}
/*
* For every controller device node , there is a cs device node that
* describe the bus configuration parameters . This functions iterate
* over these nodes and update the cs data array .
*/
for_each_available_child_of_node ( np , child_np ) {
ret = of_aemif_parse_abus_config ( pdev , child_np ) ;
if ( ret < 0 )
goto error ;
}
for ( i = 0 ; i < aemif - > num_cs ; i + + ) {
ret = aemif_config_abus ( pdev , i ) ;
if ( ret < 0 ) {
dev_err ( dev , " Error configuring chip select %d \n " ,
aemif - > cs_data [ i ] . cs ) ;
goto error ;
}
}
/*
* Create a child devices explicitly from here to
* guarantee that the child will be probed after the AEMIF timing
* parameters are set .
*/
for_each_available_child_of_node ( np , child_np ) {
2017-01-13 13:00:25 +03:00
ret = of_platform_populate ( child_np , NULL , dev_lookup , dev ) ;
2014-02-24 21:26:11 +04:00
if ( ret < 0 )
goto error ;
}
return 0 ;
error :
clk_disable_unprepare ( aemif - > clk ) ;
return ret ;
}
static int aemif_remove ( struct platform_device * pdev )
{
struct aemif_device * aemif = platform_get_drvdata ( pdev ) ;
clk_disable_unprepare ( aemif - > clk ) ;
return 0 ;
}
static struct platform_driver aemif_driver = {
. probe = aemif_probe ,
. remove = aemif_remove ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = of_match_ptr ( aemif_of_match ) ,
} ,
} ;
module_platform_driver ( aemif_driver ) ;
MODULE_AUTHOR ( " Murali Karicheri <m-karicheri2@ti.com> " ) ;
MODULE_AUTHOR ( " Ivan Khoronzhuk <ivan.khoronzhuk@ti.com> " ) ;
MODULE_DESCRIPTION ( " Texas Instruments AEMIF driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " KBUILD_MODNAME ) ;