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))
2018-12-04 22:28:27 +03:00
struct ahci_mvebu_plat_data {
int ( * plat_config ) ( struct ahci_host_priv * hpriv ) ;
2018-12-04 22:28:29 +03:00
unsigned int flags ;
2018-12-04 22:28:27 +03:00
} ;
2014-04-15 19:00:03 +04:00
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-12-04 22:28:27 +03:00
static int ahci_mvebu_armada_380_config ( struct ahci_host_priv * hpriv )
{
const struct mbus_dram_target_info * dram ;
int rc = 0 ;
dram = mv_mbus_dram_info ( ) ;
if ( dram )
ahci_mvebu_mbus_config ( hpriv , dram ) ;
else
rc = - ENODEV ;
ahci_mvebu_regret_option ( hpriv ) ;
return rc ;
}
2018-12-04 22:28:28 +03:00
static int ahci_mvebu_armada_3700_config ( struct ahci_host_priv * hpriv )
{
u32 reg ;
writel ( 0 , hpriv - > mmio + AHCI_VENDOR_SPECIFIC_0_ADDR ) ;
reg = readl ( hpriv - > mmio + AHCI_VENDOR_SPECIFIC_0_DATA ) ;
reg | = BIT ( 6 ) ;
writel ( reg , hpriv - > mmio + AHCI_VENDOR_SPECIFIC_0_DATA ) ;
return 0 ;
}
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 ;
2018-12-04 22:28:27 +03:00
const struct ahci_mvebu_plat_data * pdata = hpriv - > plat_data ;
2015-06-17 15:11:01 +03:00
2018-12-04 22:28:28 +03:00
pdata - > plat_config ( hpriv ) ;
2015-06-17 15:11:01 +03:00
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 )
{
2018-12-04 22:28:27 +03:00
const struct ahci_mvebu_plat_data * pdata ;
2014-04-15 19:00:03 +04:00
struct ahci_host_priv * hpriv ;
int rc ;
2018-12-04 22:28:27 +03:00
pdata = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! pdata )
return - EINVAL ;
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 ) ;
2018-12-04 22:28:29 +03:00
hpriv - > flags | = pdata - > flags ;
2018-12-04 22:28:27 +03:00
hpriv - > plat_data = ( void * ) pdata ;
2014-04-15 19:00:03 +04:00
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 ;
2018-12-04 22:28:28 +03:00
rc = pdata - > plat_config ( hpriv ) ;
if ( rc )
goto disable_resources ;
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 ;
}
2018-12-04 22:28:27 +03:00
static const struct ahci_mvebu_plat_data ahci_mvebu_armada_380_plat_data = {
. plat_config = ahci_mvebu_armada_380_config ,
} ;
static const struct ahci_mvebu_plat_data ahci_mvebu_armada_3700_plat_data = {
2018-12-04 22:28:28 +03:00
. plat_config = ahci_mvebu_armada_3700_config ,
2020-10-09 11:42:44 +03:00
. flags = AHCI_HFLAG_SUSPEND_PHYS | AHCI_HFLAG_IGN_NOTSUPP_POWER_ON ,
2018-12-04 22:28:27 +03:00
} ;
2014-04-15 19:00:03 +04:00
static const struct of_device_id ahci_mvebu_of_match [ ] = {
2018-12-04 22:28:27 +03:00
{
. compatible = " marvell,armada-380-ahci " ,
. data = & ahci_mvebu_armada_380_plat_data ,
} ,
{
. compatible = " marvell,armada-3700-ahci " ,
. data = & ahci_mvebu_armada_3700_plat_data ,
} ,
2014-04-15 19:00:03 +04:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ahci_mvebu_of_match ) ;
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 " ) ;