2010-03-04 20:06:06 +03:00
/*
* AHCI SATA platform driver
*
* Copyright 2004 - 2005 Red Hat , Inc .
* Jeff Garzik < jgarzik @ pobox . com >
* Copyright 2010 MontaVista Software , LLC .
* Anton Vorontsov < avorontsov @ ru . mvista . 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 , or ( at your option )
* any later version .
*/
2012-08-27 09:07:19 +04:00
# include <linux/clk.h>
2010-03-04 20:06:06 +03:00
# include <linux/kernel.h>
2010-03-29 21:52:43 +04:00
# include <linux/gfp.h>
2010-03-04 20:06:06 +03:00
# include <linux/module.h>
2012-03-16 14:19:55 +04:00
# include <linux/pm.h>
2010-03-04 20:06:06 +03:00
# include <linux/interrupt.h>
# include <linux/device.h>
# include <linux/platform_device.h>
# include <linux/libata.h>
# include <linux/ahci_platform.h>
# include "ahci.h"
2012-11-02 11:46:17 +04:00
static void ahci_host_stop ( struct ata_host * host ) ;
2011-09-28 11:41:54 +04:00
enum ahci_type {
AHCI , /* standard platform ahci */
IMX53_AHCI , /* ahci on i.mx53 */
2012-02-21 22:38:44 +04:00
STRICT_AHCI , /* delayed DMA engine start */
2011-09-28 11:41:54 +04:00
} ;
static struct platform_device_id ahci_devtype [ ] = {
{
. name = " ahci " ,
. driver_data = AHCI ,
} , {
. name = " imx53-ahci " ,
. driver_data = IMX53_AHCI ,
2012-02-21 22:38:44 +04:00
} , {
. name = " strict-ahci " ,
. driver_data = STRICT_AHCI ,
2011-09-28 11:41:54 +04:00
} , {
/* sentinel */
}
} ;
MODULE_DEVICE_TABLE ( platform , ahci_devtype ) ;
2013-10-15 06:44:54 +04:00
struct ata_port_operations ahci_platform_ops = {
2012-11-02 11:46:17 +04:00
. inherits = & ahci_ops ,
. host_stop = ahci_host_stop ,
} ;
2013-10-15 06:44:54 +04:00
EXPORT_SYMBOL_GPL ( ahci_platform_ops ) ;
2012-11-02 11:46:17 +04:00
2012-12-06 11:44:20 +04:00
static struct ata_port_operations ahci_platform_retry_srst_ops = {
2012-11-02 11:46:17 +04:00
. inherits = & ahci_pmp_retry_srst_ops ,
. host_stop = ahci_host_stop ,
} ;
2011-09-28 11:41:54 +04:00
static const struct ata_port_info ahci_port_info [ ] = {
/* by features */
[ AHCI ] = {
. flags = AHCI_FLAG_COMMON ,
. pio_mask = ATA_PIO4 ,
. udma_mask = ATA_UDMA6 ,
2012-11-02 11:46:17 +04:00
. port_ops = & ahci_platform_ops ,
2011-09-28 11:41:54 +04:00
} ,
[ IMX53_AHCI ] = {
. flags = AHCI_FLAG_COMMON ,
. pio_mask = ATA_PIO4 ,
. udma_mask = ATA_UDMA6 ,
2012-11-02 11:46:17 +04:00
. port_ops = & ahci_platform_retry_srst_ops ,
2011-09-28 11:41:54 +04:00
} ,
2012-02-21 22:38:44 +04:00
[ STRICT_AHCI ] = {
AHCI_HFLAGS ( AHCI_HFLAG_DELAY_ENGINE ) ,
. flags = AHCI_FLAG_COMMON ,
. pio_mask = ATA_PIO4 ,
. udma_mask = ATA_UDMA6 ,
2012-11-02 11:46:17 +04:00
. port_ops = & ahci_platform_ops ,
2012-02-21 22:38:44 +04:00
} ,
2011-09-28 11:41:54 +04:00
} ;
2010-09-21 11:25:48 +04:00
static struct scsi_host_template ahci_platform_sht = {
AHCI_SHT ( " ahci_platform " ) ,
} ;
2014-02-22 19:53:31 +04:00
/**
* ahci_platform_enable_clks - Enable platform clocks
* @ hpriv : host private area to store config values
*
* This function enables all the clks found in hpriv - > clks , starting at
* index 0. If any clk fails to enable it disables all the clks already
* enabled in reverse order , and then returns an error .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
int ahci_platform_enable_clks ( struct ahci_host_priv * hpriv )
{
int c , rc ;
for ( c = 0 ; c < AHCI_MAX_CLKS & & hpriv - > clks [ c ] ; c + + ) {
rc = clk_prepare_enable ( hpriv - > clks [ c ] ) ;
if ( rc )
goto disable_unprepare_clk ;
}
return 0 ;
disable_unprepare_clk :
while ( - - c > = 0 )
clk_disable_unprepare ( hpriv - > clks [ c ] ) ;
return rc ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_enable_clks ) ;
/**
* ahci_platform_disable_clks - Disable platform clocks
* @ hpriv : host private area to store config values
*
* This function disables all the clks found in hpriv - > clks , in reverse
* order of ahci_platform_enable_clks ( starting at the end of the array ) .
*/
void ahci_platform_disable_clks ( struct ahci_host_priv * hpriv )
{
int c ;
for ( c = AHCI_MAX_CLKS - 1 ; c > = 0 ; c - - )
if ( hpriv - > clks [ c ] )
clk_disable_unprepare ( hpriv - > clks [ c ] ) ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_disable_clks ) ;
static void ahci_put_clks ( struct ahci_host_priv * hpriv )
{
int c ;
for ( c = 0 ; c < AHCI_MAX_CLKS & & hpriv - > clks [ c ] ; c + + )
clk_put ( hpriv - > clks [ c ] ) ;
}
2012-12-22 01:19:58 +04:00
static int ahci_probe ( struct platform_device * pdev )
2010-03-04 20:06:06 +03:00
{
struct device * dev = & pdev - > dev ;
2011-10-31 17:20:10 +04:00
struct ahci_platform_data * pdata = dev_get_platdata ( dev ) ;
2011-09-28 11:41:54 +04:00
const struct platform_device_id * id = platform_get_device_id ( pdev ) ;
2011-11-16 07:00:56 +04:00
struct ata_port_info pi = ahci_port_info [ id ? id - > driver_data : 0 ] ;
2010-03-04 20:06:06 +03:00
const struct ata_port_info * ppi [ ] = { & pi , NULL } ;
struct ahci_host_priv * hpriv ;
struct ata_host * host ;
struct resource * mem ;
2014-02-22 19:53:31 +04:00
struct clk * clk ;
2010-03-04 20:06:06 +03:00
int irq ;
int n_ports ;
int i ;
int rc ;
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! mem ) {
dev_err ( dev , " no mmio space \n " ) ;
return - EINVAL ;
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < = 0 ) {
dev_err ( dev , " no irq \n " ) ;
return - EINVAL ;
}
if ( pdata & & pdata - > ata_port_info )
pi = * pdata - > ata_port_info ;
hpriv = devm_kzalloc ( dev , sizeof ( * hpriv ) , GFP_KERNEL ) ;
if ( ! hpriv ) {
2010-06-25 13:21:19 +04:00
dev_err ( dev , " can't alloc ahci_host_priv \n " ) ;
return - ENOMEM ;
2010-03-04 20:06:06 +03:00
}
hpriv - > flags | = ( unsigned long ) pi . private_data ;
hpriv - > mmio = devm_ioremap ( dev , mem - > start , resource_size ( mem ) ) ;
if ( ! hpriv - > mmio ) {
dev_err ( dev , " can't map %pR \n " , mem ) ;
2010-06-25 13:21:19 +04:00
return - ENOMEM ;
}
2014-02-22 19:53:32 +04:00
hpriv - > target_pwr = devm_regulator_get_optional ( dev , " target " ) ;
if ( IS_ERR ( hpriv - > target_pwr ) ) {
rc = PTR_ERR ( hpriv - > target_pwr ) ;
if ( rc = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
hpriv - > target_pwr = NULL ;
}
2014-02-22 19:53:31 +04:00
for ( i = 0 ; i < AHCI_MAX_CLKS ; i + + ) {
/*
* For now we must use clk_get ( dev , NULL ) for the first clock ,
* because some platforms ( da850 , spear13xx ) are not yet
* converted to use devicetree for clocks . For new platforms
* this is equivalent to of_clk_get ( dev - > of_node , 0 ) .
*/
if ( i = = 0 )
clk = clk_get ( dev , NULL ) ;
else
clk = of_clk_get ( dev - > of_node , i ) ;
if ( IS_ERR ( clk ) ) {
rc = PTR_ERR ( clk ) ;
if ( rc = = - EPROBE_DEFER )
goto free_clk ;
break ;
2012-08-27 09:07:19 +04:00
}
2014-02-22 19:53:31 +04:00
hpriv - > clks [ i ] = clk ;
2012-08-27 09:07:19 +04:00
}
2014-02-22 19:53:32 +04:00
if ( hpriv - > target_pwr ) {
rc = regulator_enable ( hpriv - > target_pwr ) ;
if ( rc )
goto free_clk ;
}
2014-02-22 19:53:31 +04:00
rc = ahci_enable_clks ( dev , hpriv ) ;
if ( rc )
2014-02-22 19:53:32 +04:00
goto disable_regulator ;
2014-02-22 19:53:31 +04:00
2010-06-25 13:21:19 +04:00
/*
* Some platforms might need to prepare for mmio region access ,
* which could be done in the following init call . So , the mmio
* region shouldn ' t be accessed before init ( if provided ) has
* returned successfully .
*/
if ( pdata & & pdata - > init ) {
rc = pdata - > init ( dev , hpriv - > mmio ) ;
if ( rc )
2012-08-27 09:07:19 +04:00
goto disable_unprepare_clk ;
2010-03-04 20:06:06 +03:00
}
ahci_save_initial_config ( dev , hpriv ,
pdata ? pdata - > force_port_map : 0 ,
pdata ? pdata - > mask_port_map : 0 ) ;
/* prepare host */
if ( hpriv - > cap & HOST_CAP_NCQ )
pi . flags | = ATA_FLAG_NCQ ;
if ( hpriv - > cap & HOST_CAP_PMP )
pi . flags | = ATA_FLAG_PMP ;
ahci_set_em_messages ( hpriv , & pi ) ;
/* CAP.NP sometimes indicate the index of the last enabled
* port , at other times , that of the last possible port , so
* determining the maximum port number requires looking at
* both CAP . NP and port_map .
*/
n_ports = max ( ahci_nr_ports ( hpriv - > cap ) , fls ( hpriv - > port_map ) ) ;
host = ata_host_alloc_pinfo ( dev , ppi , n_ports ) ;
if ( ! host ) {
rc = - ENOMEM ;
2012-08-27 09:07:19 +04:00
goto pdata_exit ;
2010-03-04 20:06:06 +03:00
}
host - > private_data = hpriv ;
if ( ! ( hpriv - > cap & HOST_CAP_SSS ) | | ahci_ignore_sss )
host - > flags | = ATA_HOST_PARALLEL_SCAN ;
else
2013-10-05 04:15:59 +04:00
dev_info ( dev , " SSS flag set, parallel bus scan disabled \n " ) ;
2010-03-04 20:06:06 +03:00
if ( pi . flags & ATA_FLAG_EM )
ahci_reset_em ( host ) ;
for ( i = 0 ; i < host - > n_ports ; i + + ) {
struct ata_port * ap = host - > ports [ i ] ;
ata_port_desc ( ap , " mmio %pR " , mem ) ;
ata_port_desc ( ap , " port 0x%x " , 0x100 + ap - > port_no * 0x80 ) ;
/* set enclosure management message type */
if ( ap - > flags & ATA_FLAG_EM )
2010-05-15 01:23:37 +04:00
ap - > em_message_type = hpriv - > em_msg_type ;
2010-03-04 20:06:06 +03:00
/* disabled/not-implemented port */
if ( ! ( hpriv - > port_map & ( 1 < < i ) ) )
ap - > ops = & ata_dummy_port_ops ;
}
rc = ahci_reset_controller ( host ) ;
if ( rc )
2012-08-27 09:07:19 +04:00
goto pdata_exit ;
2010-03-04 20:06:06 +03:00
ahci_init_controller ( host ) ;
ahci_print_info ( host , " platform " ) ;
rc = ata_host_activate ( host , irq , ahci_interrupt , IRQF_SHARED ,
2010-09-21 11:25:48 +04:00
& ahci_platform_sht ) ;
2010-03-04 20:06:06 +03:00
if ( rc )
2012-08-27 09:07:19 +04:00
goto pdata_exit ;
2010-03-04 20:06:06 +03:00
return 0 ;
2012-08-27 09:07:19 +04:00
pdata_exit :
2010-03-04 20:06:06 +03:00
if ( pdata & & pdata - > exit )
pdata - > exit ( dev ) ;
2012-08-27 09:07:19 +04:00
disable_unprepare_clk :
2014-02-22 19:53:31 +04:00
ahci_disable_clks ( hpriv ) ;
2014-02-22 19:53:32 +04:00
disable_regulator :
if ( hpriv - > target_pwr )
regulator_disable ( hpriv - > target_pwr ) ;
2012-08-27 09:07:19 +04:00
free_clk :
2014-02-22 19:53:31 +04:00
ahci_put_clks ( hpriv ) ;
2010-03-04 20:06:06 +03:00
return rc ;
}
2012-11-02 11:46:17 +04:00
static void ahci_host_stop ( struct ata_host * host )
{
struct device * dev = host - > dev ;
struct ahci_platform_data * pdata = dev_get_platdata ( dev ) ;
struct ahci_host_priv * hpriv = host - > private_data ;
2010-03-04 20:06:06 +03:00
if ( pdata & & pdata - > exit )
pdata - > exit ( dev ) ;
2014-02-22 19:53:31 +04:00
ahci_disable_clks ( hpriv ) ;
ahci_put_clks ( hpriv ) ;
2014-02-22 19:53:32 +04:00
if ( hpriv - > target_pwr )
regulator_disable ( hpriv - > target_pwr ) ;
2010-03-04 20:06:06 +03:00
}
2012-10-16 18:59:01 +04:00
# ifdef CONFIG_PM_SLEEP
2011-11-18 23:10:10 +04:00
static int ahci_suspend ( struct device * dev )
{
struct ahci_platform_data * pdata = dev_get_platdata ( dev ) ;
struct ata_host * host = dev_get_drvdata ( dev ) ;
struct ahci_host_priv * hpriv = host - > private_data ;
void __iomem * mmio = hpriv - > mmio ;
u32 ctl ;
int rc ;
if ( hpriv - > flags & AHCI_HFLAG_NO_SUSPEND ) {
dev_err ( dev , " firmware update required for suspend/resume \n " ) ;
return - EIO ;
}
/*
* AHCI spec rev1 .1 section 8.3 .3 :
* Software must disable interrupts prior to requesting a
* transition of the HBA to D3 state .
*/
ctl = readl ( mmio + HOST_CTL ) ;
ctl & = ~ HOST_IRQ_EN ;
writel ( ctl , mmio + HOST_CTL ) ;
readl ( mmio + HOST_CTL ) ; /* flush */
rc = ata_host_suspend ( host , PMSG_SUSPEND ) ;
if ( rc )
return rc ;
if ( pdata & & pdata - > suspend )
return pdata - > suspend ( dev ) ;
2012-08-27 09:07:19 +04:00
2014-02-22 19:53:31 +04:00
ahci_disable_clks ( hpriv ) ;
2012-08-27 09:07:19 +04:00
2014-02-22 19:53:32 +04:00
if ( hpriv - > target_pwr )
regulator_disable ( hpriv - > target_pwr ) ;
2011-11-18 23:10:10 +04:00
return 0 ;
}
static int ahci_resume ( struct device * dev )
{
struct ahci_platform_data * pdata = dev_get_platdata ( dev ) ;
struct ata_host * host = dev_get_drvdata ( dev ) ;
2012-08-27 09:07:19 +04:00
struct ahci_host_priv * hpriv = host - > private_data ;
2011-11-18 23:10:10 +04:00
int rc ;
2014-02-22 19:53:32 +04:00
if ( hpriv - > target_pwr ) {
rc = regulator_enable ( hpriv - > target_pwr ) ;
if ( rc )
return rc ;
}
2014-02-22 19:53:31 +04:00
rc = ahci_enable_clks ( dev , hpriv ) ;
if ( rc )
2014-02-22 19:53:32 +04:00
goto disable_regulator ;
2012-08-27 09:07:19 +04:00
2011-11-18 23:10:10 +04:00
if ( pdata & & pdata - > resume ) {
rc = pdata - > resume ( dev ) ;
if ( rc )
2012-08-27 09:07:19 +04:00
goto disable_unprepare_clk ;
2011-11-18 23:10:10 +04:00
}
if ( dev - > power . power_state . event = = PM_EVENT_SUSPEND ) {
rc = ahci_reset_controller ( host ) ;
if ( rc )
2012-08-27 09:07:19 +04:00
goto disable_unprepare_clk ;
2011-11-18 23:10:10 +04:00
ahci_init_controller ( host ) ;
}
ata_host_resume ( host ) ;
return 0 ;
2012-08-27 09:07:19 +04:00
disable_unprepare_clk :
2014-02-22 19:53:31 +04:00
ahci_disable_clks ( hpriv ) ;
2014-02-22 19:53:32 +04:00
disable_regulator :
if ( hpriv - > target_pwr )
regulator_disable ( hpriv - > target_pwr ) ;
2012-08-27 09:07:19 +04:00
return rc ;
2011-11-18 23:10:10 +04:00
}
# endif
2012-12-06 11:44:20 +04:00
static SIMPLE_DEV_PM_OPS ( ahci_pm_ops , ahci_suspend , ahci_resume ) ;
2012-03-16 14:19:55 +04:00
2010-11-04 05:04:59 +03:00
static const struct of_device_id ahci_of_match [ ] = {
2012-04-21 16:10:12 +04:00
{ . compatible = " snps,spear-ahci " , } ,
2013-04-16 13:28:02 +04:00
{ . compatible = " snps,exynos5440-ahci " , } ,
2013-11-22 06:08:29 +04:00
{ . compatible = " ibm,476gtr-ahci " , } ,
2010-11-04 05:04:59 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ahci_of_match ) ;
2010-03-04 20:06:06 +03:00
static struct platform_driver ahci_driver = {
2012-11-02 11:46:15 +04:00
. probe = ahci_probe ,
2012-11-02 11:46:19 +04:00
. remove = ata_platform_remove_one ,
2010-03-04 20:06:06 +03:00
. driver = {
. name = " ahci " ,
. owner = THIS_MODULE ,
2010-11-04 05:04:59 +03:00
. of_match_table = ahci_of_match ,
2011-11-18 23:10:10 +04:00
. pm = & ahci_pm_ops ,
2010-03-04 20:06:06 +03:00
} ,
2011-09-28 11:41:54 +04:00
. id_table = ahci_devtype ,
2010-03-04 20:06:06 +03:00
} ;
2012-11-02 11:46:16 +04:00
module_platform_driver ( ahci_driver ) ;
2010-03-04 20:06:06 +03:00
MODULE_DESCRIPTION ( " AHCI SATA platform driver " ) ;
MODULE_AUTHOR ( " Anton Vorontsov <avorontsov@ru.mvista.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:ahci " ) ;