2014-03-25 22:51:39 +04:00
/*
* AHCI SATA platform library
*
* 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 .
*/
# include <linux/clk.h>
# include <linux/kernel.h>
# include <linux/gfp.h>
# include <linux/module.h>
# include <linux/pm.h>
# include <linux/interrupt.h>
# include <linux/device.h>
# include <linux/platform_device.h>
# include <linux/libata.h>
# include <linux/ahci_platform.h>
# include <linux/phy/phy.h>
# include <linux/pm_runtime.h>
2015-01-15 17:09:37 +03:00
# include <linux/of_platform.h>
2014-03-25 22:51:39 +04:00
# include "ahci.h"
static void ahci_host_stop ( struct ata_host * host ) ;
struct ata_port_operations ahci_platform_ops = {
. inherits = & ahci_ops ,
. host_stop = ahci_host_stop ,
} ;
EXPORT_SYMBOL_GPL ( ahci_platform_ops ) ;
2014-07-30 22:13:57 +04:00
/**
* ahci_platform_enable_phys - Enable PHYs
* @ hpriv : host private area to store config values
*
* This function enables all the PHYs found in hpriv - > phys , if any .
* If a PHY fails to be enabled , it disables all the PHYs already
* enabled in reverse order and returns an error .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
2014-08-30 23:39:09 +04:00
static int ahci_platform_enable_phys ( struct ahci_host_priv * hpriv )
2014-07-30 22:13:57 +04:00
{
int rc , i ;
for ( i = 0 ; i < hpriv - > nports ; i + + ) {
rc = phy_init ( hpriv - > phys [ i ] ) ;
if ( rc )
goto disable_phys ;
rc = phy_power_on ( hpriv - > phys [ i ] ) ;
if ( rc ) {
phy_exit ( hpriv - > phys [ i ] ) ;
goto disable_phys ;
}
}
return 0 ;
disable_phys :
while ( - - i > = 0 ) {
phy_power_off ( hpriv - > phys [ i ] ) ;
phy_exit ( hpriv - > phys [ i ] ) ;
}
return rc ;
}
/**
* ahci_platform_disable_phys - Disable PHYs
* @ hpriv : host private area to store config values
*
* This function disables all PHYs found in hpriv - > phys .
*/
2014-08-30 23:39:09 +04:00
static void ahci_platform_disable_phys ( struct ahci_host_priv * hpriv )
2014-07-30 22:13:57 +04:00
{
int i ;
for ( i = 0 ; i < hpriv - > nports ; i + + ) {
phy_power_off ( hpriv - > phys [ i ] ) ;
phy_exit ( hpriv - > phys [ i ] ) ;
}
}
2014-03-25 22:51:39 +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 ) ;
2015-01-15 17:09:37 +03:00
/**
* ahci_platform_enable_regulators - Enable regulators
* @ hpriv : host private area to store config values
*
* This function enables all the regulators found in
* hpriv - > target_pwrs , if any . If a regulator fails to be enabled , it
* disables all the regulators already enabled in reverse order and
* returns an error .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
int ahci_platform_enable_regulators ( struct ahci_host_priv * hpriv )
{
int rc , i ;
for ( i = 0 ; i < hpriv - > nports ; i + + ) {
if ( ! hpriv - > target_pwrs [ i ] )
continue ;
rc = regulator_enable ( hpriv - > target_pwrs [ i ] ) ;
if ( rc )
goto disable_target_pwrs ;
}
return 0 ;
disable_target_pwrs :
while ( - - i > = 0 )
if ( hpriv - > target_pwrs [ i ] )
regulator_disable ( hpriv - > target_pwrs [ i ] ) ;
return rc ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_enable_regulators ) ;
/**
* ahci_platform_disable_regulators - Disable regulators
* @ hpriv : host private area to store config values
*
* This function disables all regulators found in hpriv - > target_pwrs .
*/
void ahci_platform_disable_regulators ( struct ahci_host_priv * hpriv )
{
int i ;
for ( i = 0 ; i < hpriv - > nports ; i + + ) {
if ( ! hpriv - > target_pwrs [ i ] )
continue ;
regulator_disable ( hpriv - > target_pwrs [ i ] ) ;
}
}
EXPORT_SYMBOL_GPL ( ahci_platform_disable_regulators ) ;
2014-03-25 22:51:39 +04:00
/**
* ahci_platform_enable_resources - Enable platform resources
* @ hpriv : host private area to store config values
*
* This function enables all ahci_platform managed resources in the
* following order :
* 1 ) Regulator
* 2 ) Clocks ( through ahci_platform_enable_clks )
2014-07-30 22:13:57 +04:00
* 3 ) Phys
2014-03-25 22:51:39 +04:00
*
* If resource enabling fails at any point the previous enabled resources
* are disabled in reverse order .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
int ahci_platform_enable_resources ( struct ahci_host_priv * hpriv )
{
int rc ;
2015-01-15 17:09:37 +03:00
rc = ahci_platform_enable_regulators ( hpriv ) ;
if ( rc )
return rc ;
2014-03-25 22:51:39 +04:00
rc = ahci_platform_enable_clks ( hpriv ) ;
if ( rc )
goto disable_regulator ;
2014-07-30 22:13:57 +04:00
rc = ahci_platform_enable_phys ( hpriv ) ;
if ( rc )
goto disable_clks ;
2014-03-25 22:51:39 +04:00
return 0 ;
disable_clks :
ahci_platform_disable_clks ( hpriv ) ;
disable_regulator :
2015-01-15 17:09:37 +03:00
ahci_platform_disable_regulators ( hpriv ) ;
2014-03-25 22:51:39 +04:00
return rc ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_enable_resources ) ;
/**
* ahci_platform_disable_resources - Disable platform resources
* @ hpriv : host private area to store config values
*
* This function disables all ahci_platform managed resources in the
* following order :
2014-07-30 22:13:57 +04:00
* 1 ) Phys
2014-03-25 22:51:39 +04:00
* 2 ) Clocks ( through ahci_platform_disable_clks )
* 3 ) Regulator
*/
void ahci_platform_disable_resources ( struct ahci_host_priv * hpriv )
{
2014-07-30 22:13:57 +04:00
ahci_platform_disable_phys ( hpriv ) ;
2014-03-25 22:51:39 +04:00
ahci_platform_disable_clks ( hpriv ) ;
2015-01-15 17:09:37 +03:00
ahci_platform_disable_regulators ( hpriv ) ;
2014-03-25 22:51:39 +04:00
}
EXPORT_SYMBOL_GPL ( ahci_platform_disable_resources ) ;
static void ahci_platform_put_resources ( struct device * dev , void * res )
{
struct ahci_host_priv * hpriv = res ;
int c ;
if ( hpriv - > got_runtime_pm ) {
pm_runtime_put_sync ( dev ) ;
pm_runtime_disable ( dev ) ;
}
for ( c = 0 ; c < AHCI_MAX_CLKS & & hpriv - > clks [ c ] ; c + + )
clk_put ( hpriv - > clks [ c ] ) ;
2015-01-15 17:09:37 +03:00
/*
* The regulators are tied to child node device and not to the
* SATA device itself . So we can ' t use devm for automatically
* releasing them . We have to do it manually here .
*/
for ( c = 0 ; c < hpriv - > nports ; c + + )
if ( hpriv - > target_pwrs & & hpriv - > target_pwrs [ c ] )
regulator_put ( hpriv - > target_pwrs [ c ] ) ;
2015-01-21 13:50:52 +03:00
kfree ( hpriv - > target_pwrs ) ;
2015-01-15 17:09:37 +03:00
}
static int ahci_platform_get_phy ( struct ahci_host_priv * hpriv , u32 port ,
struct device * dev , struct device_node * node )
{
int rc ;
hpriv - > phys [ port ] = devm_of_phy_get ( dev , node , NULL ) ;
if ( ! IS_ERR ( hpriv - > phys [ port ] ) )
return 0 ;
rc = PTR_ERR ( hpriv - > phys [ port ] ) ;
switch ( rc ) {
case - ENOSYS :
/* No PHY support. Check if PHY is required. */
if ( of_find_property ( node , " phys " , NULL ) ) {
dev_err ( dev ,
" couldn't get PHY in node %s: ENOSYS \n " ,
node - > name ) ;
break ;
}
2017-10-12 22:19:16 +03:00
/* fall through */
2015-01-15 17:09:37 +03:00
case - ENODEV :
/* continue normally */
hpriv - > phys [ port ] = NULL ;
rc = 0 ;
break ;
default :
dev_err ( dev ,
" couldn't get PHY in node %s: %d \n " ,
node - > name , rc ) ;
break ;
}
return rc ;
}
static int ahci_platform_get_regulator ( struct ahci_host_priv * hpriv , u32 port ,
struct device * dev )
{
struct regulator * target_pwr ;
int rc = 0 ;
target_pwr = regulator_get_optional ( dev , " target " ) ;
if ( ! IS_ERR ( target_pwr ) )
hpriv - > target_pwrs [ port ] = target_pwr ;
else
rc = PTR_ERR ( target_pwr ) ;
return rc ;
2014-03-25 22:51:39 +04:00
}
/**
* ahci_platform_get_resources - Get platform resources
* @ pdev : platform device to get resources for
*
* This function allocates an ahci_host_priv struct , and gets the following
* resources , storing a reference to them inside the returned struct :
*
* 1 ) mmio registers ( IORESOURCE_MEM 0 , mandatory )
* 2 ) regulator for controlling the targets power ( optional )
* 3 ) 0 - AHCI_MAX_CLKS clocks , as specified in the devs devicetree node ,
* or for non devicetree enabled platforms a single clock
2014-07-30 22:13:57 +04:00
* 4 ) phys ( optional )
2014-03-25 22:51:39 +04:00
*
* RETURNS :
* The allocated ahci_host_priv on success , otherwise an ERR_PTR value
*/
struct ahci_host_priv * ahci_platform_get_resources ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct ahci_host_priv * hpriv ;
struct clk * clk ;
2014-07-30 22:13:57 +04:00
struct device_node * child ;
2015-01-15 17:09:37 +03:00
int i , sz , enabled_ports = 0 , rc = - ENOMEM , child_nodes ;
2014-07-30 22:13:57 +04:00
u32 mask_port_map = 0 ;
2014-03-25 22:51:39 +04:00
if ( ! devres_open_group ( dev , NULL , GFP_KERNEL ) )
return ERR_PTR ( - ENOMEM ) ;
hpriv = devres_alloc ( ahci_platform_put_resources , sizeof ( * hpriv ) ,
GFP_KERNEL ) ;
if ( ! hpriv )
goto err_out ;
devres_add ( dev , hpriv ) ;
hpriv - > mmio = devm_ioremap_resource ( dev ,
platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ) ;
if ( IS_ERR ( hpriv - > mmio ) ) {
dev_err ( dev , " no mmio space \n " ) ;
rc = PTR_ERR ( hpriv - > mmio ) ;
goto err_out ;
}
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 err_out ;
break ;
}
hpriv - > clks [ i ] = clk ;
}
2015-01-15 17:09:37 +03:00
hpriv - > nports = child_nodes = of_get_child_count ( dev - > of_node ) ;
2014-07-30 22:13:57 +04:00
2015-01-15 17:09:37 +03:00
/*
* If no sub - node was found , we still need to set nports to
* one in order to be able to use the
* ahci_platform_ [ en | dis ] able_ [ phys | regulators ] functions .
*/
if ( ! child_nodes )
hpriv - > nports = 1 ;
sz = hpriv - > nports * sizeof ( * hpriv - > phys ) ;
hpriv - > phys = devm_kzalloc ( dev , sz , GFP_KERNEL ) ;
if ( ! hpriv - > phys ) {
rc = - ENOMEM ;
goto err_out ;
}
sz = hpriv - > nports * sizeof ( * hpriv - > target_pwrs ) ;
2015-01-21 13:50:52 +03:00
hpriv - > target_pwrs = kzalloc ( sz , GFP_KERNEL ) ;
2015-01-15 17:09:37 +03:00
if ( ! hpriv - > target_pwrs ) {
rc = - ENOMEM ;
goto err_out ;
}
2014-07-30 22:13:57 +04:00
2015-01-15 17:09:37 +03:00
if ( child_nodes ) {
2014-07-30 22:13:57 +04:00
for_each_child_of_node ( dev - > of_node , child ) {
u32 port ;
2015-01-31 21:36:22 +03:00
struct platform_device * port_dev __maybe_unused ;
2014-07-30 22:13:57 +04:00
if ( ! of_device_is_available ( child ) )
continue ;
if ( of_property_read_u32 ( child , " reg " , & port ) ) {
rc = - EINVAL ;
2014-06-17 16:07:55 +04:00
goto err_out ;
}
2014-03-25 22:51:39 +04:00
2014-07-30 22:13:57 +04:00
if ( port > = hpriv - > nports ) {
dev_warn ( dev , " invalid port number %d \n " , port ) ;
continue ;
}
mask_port_map | = BIT ( port ) ;
2015-01-31 21:36:22 +03:00
# ifdef CONFIG_OF_ADDRESS
2015-01-15 17:09:37 +03:00
of_platform_device_create ( child , NULL , NULL ) ;
port_dev = of_find_device_by_node ( child ) ;
if ( port_dev ) {
rc = ahci_platform_get_regulator ( hpriv , port ,
& port_dev - > dev ) ;
if ( rc = = - EPROBE_DEFER )
goto err_out ;
2014-07-30 22:13:57 +04:00
}
2015-01-31 21:36:22 +03:00
# endif
2014-03-25 22:51:39 +04:00
2015-01-15 17:09:37 +03:00
rc = ahci_platform_get_phy ( hpriv , port , dev , child ) ;
if ( rc )
goto err_out ;
2014-07-30 22:13:57 +04:00
enabled_ports + + ;
}
if ( ! enabled_ports ) {
dev_warn ( dev , " No port enabled \n " ) ;
rc = - ENODEV ;
2014-03-25 22:51:39 +04:00
goto err_out ;
}
2014-07-30 22:13:57 +04:00
if ( ! hpriv - > mask_port_map )
hpriv - > mask_port_map = mask_port_map ;
} else {
/*
* If no sub - node was found , keep this for device tree
* compatibility
*/
2015-01-15 17:09:37 +03:00
rc = ahci_platform_get_phy ( hpriv , 0 , dev , dev - > of_node ) ;
if ( rc )
goto err_out ;
2014-07-30 22:13:57 +04:00
2015-01-15 17:09:37 +03:00
rc = ahci_platform_get_regulator ( hpriv , 0 , dev ) ;
if ( rc = = - EPROBE_DEFER )
goto err_out ;
2014-03-25 22:51:39 +04:00
}
pm_runtime_enable ( dev ) ;
pm_runtime_get_sync ( dev ) ;
hpriv - > got_runtime_pm = true ;
devres_remove_group ( dev , NULL ) ;
return hpriv ;
err_out :
devres_release_group ( dev , NULL ) ;
return ERR_PTR ( rc ) ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_get_resources ) ;
/**
* ahci_platform_init_host - Bring up an ahci - platform host
* @ pdev : platform device pointer for the host
* @ hpriv : ahci - host private data for the host
* @ pi_template : template for the ata_port_info to use
2015-01-29 02:30:29 +03:00
* @ sht : scsi_host_template to use when registering
2014-03-25 22:51:39 +04:00
*
* This function does all the usual steps needed to bring up an
2014-07-30 22:13:57 +04:00
* ahci - platform host , note any necessary resources ( ie clks , phys , etc . )
2014-03-25 22:51:39 +04:00
* must be initialized / enabled before calling this .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
int ahci_platform_init_host ( struct platform_device * pdev ,
struct ahci_host_priv * hpriv ,
2015-01-29 02:30:29 +03:00
const struct ata_port_info * pi_template ,
struct scsi_host_template * sht )
2014-03-25 22:51:39 +04:00
{
struct device * dev = & pdev - > dev ;
struct ata_port_info pi = * pi_template ;
const struct ata_port_info * ppi [ ] = { & pi , NULL } ;
struct ata_host * host ;
int i , irq , n_ports , rc ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < = 0 ) {
2017-05-16 15:06:12 +03:00
if ( irq ! = - EPROBE_DEFER )
dev_err ( dev , " no irq \n " ) ;
return irq ;
2014-03-25 22:51:39 +04:00
}
2015-05-31 14:55:18 +03:00
hpriv - > irq = irq ;
2014-03-25 22:51:39 +04:00
/* prepare host */
2014-08-01 18:30:37 +04:00
pi . private_data = ( void * ) ( unsigned long ) hpriv - > flags ;
2014-03-25 22:51:39 +04:00
2014-07-30 22:13:56 +04:00
ahci_save_initial_config ( dev , hpriv ) ;
2014-03-25 22:51:39 +04:00
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 )
return - ENOMEM ;
host - > private_data = hpriv ;
if ( ! ( hpriv - > cap & HOST_CAP_SSS ) | | ahci_ignore_sss )
host - > flags | = ATA_HOST_PARALLEL_SCAN ;
else
dev_info ( dev , " SSS flag set, parallel bus scan disabled \n " ) ;
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 " ,
platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ) ;
ata_port_desc ( ap , " port 0x%x " , 0x100 + ap - > port_no * 0x80 ) ;
/* set enclosure management message type */
if ( ap - > flags & ATA_FLAG_EM )
ap - > em_message_type = hpriv - > em_msg_type ;
/* disabled/not-implemented port */
if ( ! ( hpriv - > port_map & ( 1 < < i ) ) )
ap - > ops = & ata_dummy_port_ops ;
}
2014-06-12 21:40:23 +04:00
if ( hpriv - > cap & HOST_CAP_64 ) {
rc = dma_coerce_mask_and_coherent ( dev , DMA_BIT_MASK ( 64 ) ) ;
if ( rc ) {
rc = dma_coerce_mask_and_coherent ( dev ,
DMA_BIT_MASK ( 32 ) ) ;
if ( rc ) {
dev_err ( dev , " Failed to enable 64-bit DMA. \n " ) ;
return rc ;
}
dev_warn ( dev , " Enable 32-bit DMA instead of 64-bit. \n " ) ;
}
}
2014-03-25 22:51:39 +04:00
rc = ahci_reset_controller ( host ) ;
if ( rc )
return rc ;
ahci_init_controller ( host ) ;
ahci_print_info ( host , " platform " ) ;
2015-05-31 14:55:18 +03:00
return ahci_host_activate ( host , sht ) ;
2014-03-25 22:51:39 +04:00
}
EXPORT_SYMBOL_GPL ( ahci_platform_init_host ) ;
static void ahci_host_stop ( struct ata_host * host )
{
struct ahci_host_priv * hpriv = host - > private_data ;
ahci_platform_disable_resources ( hpriv ) ;
}
2017-07-20 22:26:24 +03:00
/**
* ahci_platform_shutdown - Disable interrupts and stop DMA for host ports
* @ dev : platform device pointer for the host
*
* This function is called during system shutdown and performs the minimal
* deconfiguration required to ensure that an ahci_platform host cannot
* corrupt or otherwise interfere with a new kernel being started with kexec .
*/
void ahci_platform_shutdown ( struct platform_device * pdev )
{
struct ata_host * host = platform_get_drvdata ( pdev ) ;
struct ahci_host_priv * hpriv = host - > private_data ;
void __iomem * mmio = hpriv - > mmio ;
int i ;
for ( i = 0 ; i < host - > n_ports ; i + + ) {
struct ata_port * ap = host - > ports [ i ] ;
/* Disable port interrupts */
if ( ap - > ops - > freeze )
ap - > ops - > freeze ( ap ) ;
/* Stop the port DMA engines */
if ( ap - > ops - > port_stop )
ap - > ops - > port_stop ( ap ) ;
}
/* Disable and clear host interrupts */
writel ( readl ( mmio + HOST_CTL ) & ~ HOST_IRQ_EN , mmio + HOST_CTL ) ;
readl ( mmio + HOST_CTL ) ; /* flush */
writel ( GENMASK ( host - > n_ports , 0 ) , mmio + HOST_IRQ_STAT ) ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_shutdown ) ;
2014-03-25 22:51:39 +04:00
# ifdef CONFIG_PM_SLEEP
/**
* ahci_platform_suspend_host - Suspend an ahci - platform host
* @ dev : device pointer for the host
*
* This function does all the usual steps needed to suspend an
2014-07-30 22:13:57 +04:00
* ahci - platform host , note any necessary resources ( ie clks , phys , etc . )
2014-03-25 22:51:39 +04:00
* must be disabled after calling this .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
int ahci_platform_suspend_host ( struct device * dev )
{
struct ata_host * host = dev_get_drvdata ( dev ) ;
struct ahci_host_priv * hpriv = host - > private_data ;
void __iomem * mmio = hpriv - > mmio ;
u32 ctl ;
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 */
return ata_host_suspend ( host , PMSG_SUSPEND ) ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_suspend_host ) ;
/**
* ahci_platform_resume_host - Resume an ahci - platform host
* @ dev : device pointer for the host
*
* This function does all the usual steps needed to resume an ahci - platform
2014-07-30 22:13:57 +04:00
* host , note any necessary resources ( ie clks , phys , etc . ) must be
2014-03-25 22:51:39 +04:00
* initialized / enabled before calling this .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
int ahci_platform_resume_host ( struct device * dev )
{
struct ata_host * host = dev_get_drvdata ( dev ) ;
int rc ;
if ( dev - > power . power_state . event = = PM_EVENT_SUSPEND ) {
rc = ahci_reset_controller ( host ) ;
if ( rc )
return rc ;
ahci_init_controller ( host ) ;
}
ata_host_resume ( host ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_resume_host ) ;
/**
* ahci_platform_suspend - Suspend an ahci - platform device
* @ dev : the platform device to suspend
*
* This function suspends the host associated with the device , followed by
* disabling all the resources of the device .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
int ahci_platform_suspend ( struct device * dev )
{
struct ata_host * host = dev_get_drvdata ( dev ) ;
struct ahci_host_priv * hpriv = host - > private_data ;
int rc ;
rc = ahci_platform_suspend_host ( dev ) ;
if ( rc )
return rc ;
ahci_platform_disable_resources ( hpriv ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_suspend ) ;
/**
* ahci_platform_resume - Resume an ahci - platform device
* @ dev : the platform device to resume
*
* This function enables all the resources of the device followed by
* resuming the host associated with the device .
*
* RETURNS :
* 0 on success otherwise a negative error code
*/
int ahci_platform_resume ( struct device * dev )
{
struct ata_host * host = dev_get_drvdata ( dev ) ;
struct ahci_host_priv * hpriv = host - > private_data ;
int rc ;
rc = ahci_platform_enable_resources ( hpriv ) ;
if ( rc )
return rc ;
rc = ahci_platform_resume_host ( dev ) ;
if ( rc )
goto disable_resources ;
/* We resumed so update PM runtime state */
pm_runtime_disable ( dev ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
return 0 ;
disable_resources :
ahci_platform_disable_resources ( hpriv ) ;
return rc ;
}
EXPORT_SYMBOL_GPL ( ahci_platform_resume ) ;
# endif
MODULE_DESCRIPTION ( " AHCI SATA platform library " ) ;
MODULE_AUTHOR ( " Anton Vorontsov <avorontsov@ru.mvista.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;