2014-04-15 19:00:03 +04:00
/*
* AHCI glue platform driver for Marvell EBU SOCs
*
* Copyright ( C ) 2014 Marvell
*
* Thomas Petazzoni < thomas . petazzoni @ free - electrons . com >
* Marcin Wojtas < mw @ semihalf . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/ahci_platform.h>
# include <linux/kernel.h>
# include <linux/mbus.h>
# include <linux/module.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include "ahci.h"
2015-01-29 02:30:29 +03:00
# define DRV_NAME "ahci-mvebu"
2014-04-15 19:00:03 +04:00
# define AHCI_VENDOR_SPECIFIC_0_ADDR 0xa0
# define AHCI_VENDOR_SPECIFIC_0_DATA 0xa4
# define AHCI_WINDOW_CTRL(win) (0x60 + ((win) << 4))
# define AHCI_WINDOW_BASE(win) (0x64 + ((win) << 4))
# define AHCI_WINDOW_SIZE(win) (0x68 + ((win) << 4))
static void ahci_mvebu_mbus_config ( struct ahci_host_priv * hpriv ,
const struct mbus_dram_target_info * dram )
{
int i ;
for ( i = 0 ; i < 4 ; i + + ) {
writel ( 0 , hpriv - > mmio + AHCI_WINDOW_CTRL ( i ) ) ;
writel ( 0 , hpriv - > mmio + AHCI_WINDOW_BASE ( i ) ) ;
writel ( 0 , hpriv - > mmio + AHCI_WINDOW_SIZE ( i ) ) ;
}
for ( i = 0 ; i < dram - > num_cs ; i + + ) {
const struct mbus_dram_window * cs = dram - > cs + i ;
writel ( ( cs - > mbus_attr < < 8 ) |
( dram - > mbus_dram_target_id < < 4 ) | 1 ,
hpriv - > mmio + AHCI_WINDOW_CTRL ( i ) ) ;
2015-05-26 19:47:23 +03:00
writel ( cs - > base > > 16 , hpriv - > mmio + AHCI_WINDOW_BASE ( i ) ) ;
2014-04-15 19:00:03 +04:00
writel ( ( ( cs - > size - 1 ) & 0xffff0000 ) ,
hpriv - > mmio + AHCI_WINDOW_SIZE ( i ) ) ;
}
}
static void ahci_mvebu_regret_option ( struct ahci_host_priv * hpriv )
{
/*
* Enable the regret bit to allow the SATA unit to regret a
* request that didn ' t receive an acknowlegde and avoid a
* deadlock
*/
writel ( 0x4 , hpriv - > mmio + AHCI_VENDOR_SPECIFIC_0_ADDR ) ;
writel ( 0x80 , hpriv - > mmio + AHCI_VENDOR_SPECIFIC_0_DATA ) ;
}
2018-04-13 07:32:31 +03:00
/**
* ahci_mvebu_stop_engine
*
* @ ap : Target ata port
*
* Errata Ref # 226 - SATA Disk HOT swap issue when connected through
* Port Multiplier in FIS - based Switching mode .
*
* To avoid the issue , according to design , the bits [ 11 : 8 , 0 ] of
* register PxFBS are cleared when Port Command and Status ( 0x18 ) bit [ 0 ]
* changes its value from 1 to 0 , i . e . falling edge of Port
* Command and Status bit [ 0 ] sends PULSE that resets PxFBS
* bits [ 11 : 8 ; 0 ] .
*
* This function is used to override function of " ahci_stop_engine "
* from libahci . c by adding the mvebu work around ( WA ) to save PxFBS
* value before the PxCMD ST write of 0 , then restore PxFBS value .
*
* Return : 0 on success ; Error code otherwise .
*/
2018-06-06 09:56:34 +03:00
static int ahci_mvebu_stop_engine ( struct ata_port * ap )
2018-04-13 07:32:31 +03:00
{
void __iomem * port_mmio = ahci_port_base ( ap ) ;
u32 tmp , port_fbs ;
tmp = readl ( port_mmio + PORT_CMD ) ;
/* check if the HBA is idle */
if ( ( tmp & ( PORT_CMD_START | PORT_CMD_LIST_ON ) ) = = 0 )
return 0 ;
/* save the port PxFBS register for later restore */
port_fbs = readl ( port_mmio + PORT_FBS ) ;
/* setting HBA to idle */
tmp & = ~ PORT_CMD_START ;
writel ( tmp , port_mmio + PORT_CMD ) ;
/*
* bit # 15 PxCMD signal doesn ' t clear PxFBS ,
* restore the PxFBS register right after clearing the PxCMD ST ,
* no need to wait for the PxCMD bit # 15.
*/
writel ( port_fbs , port_mmio + PORT_FBS ) ;
/* wait for engine to stop. This could be as long as 500 msec */
tmp = ata_wait_register ( ap , port_mmio + PORT_CMD ,
PORT_CMD_LIST_ON , PORT_CMD_LIST_ON , 1 , 500 ) ;
if ( tmp & PORT_CMD_LIST_ON )
return - EIO ;
return 0 ;
}
2015-11-19 17:16:30 +03:00
# ifdef CONFIG_PM_SLEEP
2015-06-17 15:11:01 +03:00
static int ahci_mvebu_suspend ( struct platform_device * pdev , pm_message_t state )
{
return ahci_platform_suspend_host ( & pdev - > dev ) ;
}
static int ahci_mvebu_resume ( struct platform_device * pdev )
{
struct ata_host * host = platform_get_drvdata ( pdev ) ;
struct ahci_host_priv * hpriv = host - > private_data ;
const struct mbus_dram_target_info * dram ;
dram = mv_mbus_dram_info ( ) ;
if ( dram )
ahci_mvebu_mbus_config ( hpriv , dram ) ;
ahci_mvebu_regret_option ( hpriv ) ;
return ahci_platform_resume_host ( & pdev - > dev ) ;
}
2015-11-19 17:16:30 +03:00
# else
# define ahci_mvebu_suspend NULL
# define ahci_mvebu_resume NULL
# endif
2015-06-17 15:11:01 +03:00
2014-04-15 19:00:03 +04:00
static const struct ata_port_info ahci_mvebu_port_info = {
. flags = AHCI_FLAG_COMMON ,
. pio_mask = ATA_PIO4 ,
. udma_mask = ATA_UDMA6 ,
. port_ops = & ahci_platform_ops ,
} ;
2015-01-29 02:30:29 +03:00
static struct scsi_host_template ahci_platform_sht = {
AHCI_SHT ( DRV_NAME ) ,
} ;
2014-04-15 19:00:03 +04:00
static int ahci_mvebu_probe ( struct platform_device * pdev )
{
struct ahci_host_priv * hpriv ;
const struct mbus_dram_target_info * dram ;
int rc ;
2018-08-22 15:13:01 +03:00
hpriv = ahci_platform_get_resources ( pdev , 0 ) ;
2014-04-15 19:00:03 +04:00
if ( IS_ERR ( hpriv ) )
return PTR_ERR ( hpriv ) ;
rc = ahci_platform_enable_resources ( hpriv ) ;
if ( rc )
return rc ;
2018-04-13 07:32:31 +03:00
hpriv - > stop_engine = ahci_mvebu_stop_engine ;
2016-02-16 21:14:54 +03:00
if ( of_device_is_compatible ( pdev - > dev . of_node ,
" marvell,armada-380-ahci " ) ) {
dram = mv_mbus_dram_info ( ) ;
if ( ! dram )
return - ENODEV ;
2014-04-15 19:00:03 +04:00
2016-02-16 21:14:54 +03:00
ahci_mvebu_mbus_config ( hpriv , dram ) ;
ahci_mvebu_regret_option ( hpriv ) ;
}
2014-04-15 19:00:03 +04:00
2015-01-29 02:30:29 +03:00
rc = ahci_platform_init_host ( pdev , hpriv , & ahci_mvebu_port_info ,
& ahci_platform_sht ) ;
2014-04-15 19:00:03 +04:00
if ( rc )
goto disable_resources ;
return 0 ;
disable_resources :
ahci_platform_disable_resources ( hpriv ) ;
return rc ;
}
static const struct of_device_id ahci_mvebu_of_match [ ] = {
{ . compatible = " marvell,armada-380-ahci " , } ,
2016-02-16 21:14:54 +03:00
{ . compatible = " marvell,armada-3700-ahci " , } ,
2014-04-15 19:00:03 +04:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ahci_mvebu_of_match ) ;
/*
* We currently don ' t provide power management related operations ,
* since there is no suspend / resume support at the platform level for
* Armada 38 x for the moment .
*/
static struct platform_driver ahci_mvebu_driver = {
. probe = ahci_mvebu_probe ,
. remove = ata_platform_remove_one ,
2015-06-17 15:11:01 +03:00
. suspend = ahci_mvebu_suspend ,
. resume = ahci_mvebu_resume ,
2014-04-15 19:00:03 +04:00
. driver = {
2015-01-29 02:30:29 +03:00
. name = DRV_NAME ,
2014-04-15 19:00:03 +04:00
. of_match_table = ahci_mvebu_of_match ,
} ,
} ;
module_platform_driver ( ahci_mvebu_driver ) ;
MODULE_DESCRIPTION ( " Marvell EBU AHCI SATA driver " ) ;
MODULE_AUTHOR ( " Thomas Petazzoni <thomas.petazzoni@free-electrons.com>, Marcin Wojtas <mw@semihalf.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:ahci_mvebu " ) ;