2023-04-03 21:32:17 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for Amlogic A1 SPI flash controller ( SPIFC )
*
* Copyright ( c ) 2023 , SberDevices . All Rights Reserved .
*
* Author : Martin Kurbanov < mmkurbanov @ sberdevices . ru >
*/
# include <linux/bitfield.h>
# include <linux/clk.h>
# include <linux/device.h>
# include <linux/io.h>
# include <linux/iopoll.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <linux/spi/spi.h>
# include <linux/spi/spi-mem.h>
# include <linux/types.h>
# define SPIFC_A1_AHB_CTRL_REG 0x0
# define SPIFC_A1_AHB_BUS_EN BIT(31)
# define SPIFC_A1_USER_CTRL0_REG 0x200
# define SPIFC_A1_USER_REQUEST_ENABLE BIT(31)
# define SPIFC_A1_USER_REQUEST_FINISH BIT(30)
# define SPIFC_A1_USER_DATA_UPDATED BIT(0)
# define SPIFC_A1_USER_CTRL1_REG 0x204
# define SPIFC_A1_USER_CMD_ENABLE BIT(30)
# define SPIFC_A1_USER_CMD_MODE GENMASK(29, 28)
# define SPIFC_A1_USER_CMD_CODE GENMASK(27, 20)
# define SPIFC_A1_USER_ADDR_ENABLE BIT(19)
# define SPIFC_A1_USER_ADDR_MODE GENMASK(18, 17)
# define SPIFC_A1_USER_ADDR_BYTES GENMASK(16, 15)
# define SPIFC_A1_USER_DOUT_ENABLE BIT(14)
# define SPIFC_A1_USER_DOUT_MODE GENMASK(11, 10)
# define SPIFC_A1_USER_DOUT_BYTES GENMASK(9, 0)
# define SPIFC_A1_USER_CTRL2_REG 0x208
# define SPIFC_A1_USER_DUMMY_ENABLE BIT(31)
# define SPIFC_A1_USER_DUMMY_MODE GENMASK(30, 29)
# define SPIFC_A1_USER_DUMMY_CLK_SYCLES GENMASK(28, 23)
# define SPIFC_A1_USER_CTRL3_REG 0x20c
# define SPIFC_A1_USER_DIN_ENABLE BIT(31)
# define SPIFC_A1_USER_DIN_MODE GENMASK(28, 27)
# define SPIFC_A1_USER_DIN_BYTES GENMASK(25, 16)
# define SPIFC_A1_USER_ADDR_REG 0x210
# define SPIFC_A1_AHB_REQ_CTRL_REG 0x214
# define SPIFC_A1_AHB_REQ_ENABLE BIT(31)
# define SPIFC_A1_ACTIMING0_REG (0x0088 << 2)
# define SPIFC_A1_TSLCH GENMASK(31, 30)
# define SPIFC_A1_TCLSH GENMASK(29, 28)
# define SPIFC_A1_TSHWL GENMASK(20, 16)
# define SPIFC_A1_TSHSL2 GENMASK(15, 12)
# define SPIFC_A1_TSHSL1 GENMASK(11, 8)
# define SPIFC_A1_TWHSL GENMASK(7, 0)
# define SPIFC_A1_DBUF_CTRL_REG 0x240
# define SPIFC_A1_DBUF_DIR BIT(31)
# define SPIFC_A1_DBUF_AUTO_UPDATE_ADDR BIT(30)
# define SPIFC_A1_DBUF_ADDR GENMASK(7, 0)
# define SPIFC_A1_DBUF_DATA_REG 0x244
# define SPIFC_A1_USER_DBUF_ADDR_REG 0x248
2023-07-06 14:03:30 +03:00
# define SPIFC_A1_BUFFER_SIZE 512U
2023-04-03 21:32:17 +03:00
# define SPIFC_A1_MAX_HZ 200000000
# define SPIFC_A1_MIN_HZ 1000000
# define SPIFC_A1_USER_CMD(op) ( \
SPIFC_A1_USER_CMD_ENABLE | \
FIELD_PREP ( SPIFC_A1_USER_CMD_CODE , ( op ) - > cmd . opcode ) | \
FIELD_PREP ( SPIFC_A1_USER_CMD_MODE , ilog2 ( ( op ) - > cmd . buswidth ) ) )
# define SPIFC_A1_USER_ADDR(op) ( \
SPIFC_A1_USER_ADDR_ENABLE | \
FIELD_PREP ( SPIFC_A1_USER_ADDR_MODE , ilog2 ( ( op ) - > addr . buswidth ) ) | \
FIELD_PREP ( SPIFC_A1_USER_ADDR_BYTES , ( op ) - > addr . nbytes - 1 ) )
# define SPIFC_A1_USER_DUMMY(op) ( \
SPIFC_A1_USER_DUMMY_ENABLE | \
FIELD_PREP ( SPIFC_A1_USER_DUMMY_MODE , ilog2 ( ( op ) - > dummy . buswidth ) ) | \
FIELD_PREP ( SPIFC_A1_USER_DUMMY_CLK_SYCLES , ( op ) - > dummy . nbytes < < 3 ) )
# define SPIFC_A1_TSLCH_VAL FIELD_PREP(SPIFC_A1_TSLCH, 1)
# define SPIFC_A1_TCLSH_VAL FIELD_PREP(SPIFC_A1_TCLSH, 1)
# define SPIFC_A1_TSHWL_VAL FIELD_PREP(SPIFC_A1_TSHWL, 7)
# define SPIFC_A1_TSHSL2_VAL FIELD_PREP(SPIFC_A1_TSHSL2, 7)
# define SPIFC_A1_TSHSL1_VAL FIELD_PREP(SPIFC_A1_TSHSL1, 7)
# define SPIFC_A1_TWHSL_VAL FIELD_PREP(SPIFC_A1_TWHSL, 2)
# define SPIFC_A1_ACTIMING0_VAL (SPIFC_A1_TSLCH_VAL | SPIFC_A1_TCLSH_VAL | \
SPIFC_A1_TSHWL_VAL | SPIFC_A1_TSHSL2_VAL | \
SPIFC_A1_TSHSL1_VAL | SPIFC_A1_TWHSL_VAL )
struct amlogic_spifc_a1 {
struct spi_controller * ctrl ;
struct clk * clk ;
struct device * dev ;
void __iomem * base ;
2023-07-06 14:03:31 +03:00
u32 curr_speed_hz ;
2023-04-03 21:32:17 +03:00
} ;
static int amlogic_spifc_a1_request ( struct amlogic_spifc_a1 * spifc , bool read )
{
u32 mask = SPIFC_A1_USER_REQUEST_FINISH |
( read ? SPIFC_A1_USER_DATA_UPDATED : 0 ) ;
u32 val ;
writel ( SPIFC_A1_USER_REQUEST_ENABLE ,
spifc - > base + SPIFC_A1_USER_CTRL0_REG ) ;
return readl_poll_timeout ( spifc - > base + SPIFC_A1_USER_CTRL0_REG ,
val , ( val & mask ) = = mask , 0 ,
200 * USEC_PER_MSEC ) ;
}
static void amlogic_spifc_a1_drain_buffer ( struct amlogic_spifc_a1 * spifc ,
char * buf , u32 len )
{
u32 data ;
const u32 count = len / sizeof ( data ) ;
const u32 pad = len % sizeof ( data ) ;
writel ( SPIFC_A1_DBUF_AUTO_UPDATE_ADDR ,
spifc - > base + SPIFC_A1_DBUF_CTRL_REG ) ;
ioread32_rep ( spifc - > base + SPIFC_A1_DBUF_DATA_REG , buf , count ) ;
if ( pad ) {
data = readl ( spifc - > base + SPIFC_A1_DBUF_DATA_REG ) ;
memcpy ( buf + len - pad , & data , pad ) ;
}
}
static void amlogic_spifc_a1_fill_buffer ( struct amlogic_spifc_a1 * spifc ,
const char * buf , u32 len )
{
u32 data ;
const u32 count = len / sizeof ( data ) ;
const u32 pad = len % sizeof ( data ) ;
writel ( SPIFC_A1_DBUF_DIR | SPIFC_A1_DBUF_AUTO_UPDATE_ADDR ,
spifc - > base + SPIFC_A1_DBUF_CTRL_REG ) ;
iowrite32_rep ( spifc - > base + SPIFC_A1_DBUF_DATA_REG , buf , count ) ;
if ( pad ) {
memcpy ( & data , buf + len - pad , pad ) ;
writel ( data , spifc - > base + SPIFC_A1_DBUF_DATA_REG ) ;
}
}
static void amlogic_spifc_a1_user_init ( struct amlogic_spifc_a1 * spifc )
{
writel ( 0 , spifc - > base + SPIFC_A1_USER_CTRL0_REG ) ;
writel ( 0 , spifc - > base + SPIFC_A1_USER_CTRL1_REG ) ;
writel ( 0 , spifc - > base + SPIFC_A1_USER_CTRL2_REG ) ;
writel ( 0 , spifc - > base + SPIFC_A1_USER_CTRL3_REG ) ;
}
static void amlogic_spifc_a1_set_cmd ( struct amlogic_spifc_a1 * spifc ,
u32 cmd_cfg )
{
u32 val ;
val = readl ( spifc - > base + SPIFC_A1_USER_CTRL1_REG ) ;
val & = ~ ( SPIFC_A1_USER_CMD_MODE | SPIFC_A1_USER_CMD_CODE ) ;
val | = cmd_cfg ;
writel ( val , spifc - > base + SPIFC_A1_USER_CTRL1_REG ) ;
}
static void amlogic_spifc_a1_set_addr ( struct amlogic_spifc_a1 * spifc , u32 addr ,
u32 addr_cfg )
{
u32 val ;
writel ( addr , spifc - > base + SPIFC_A1_USER_ADDR_REG ) ;
val = readl ( spifc - > base + SPIFC_A1_USER_CTRL1_REG ) ;
val & = ~ ( SPIFC_A1_USER_ADDR_MODE | SPIFC_A1_USER_ADDR_BYTES ) ;
val | = addr_cfg ;
writel ( val , spifc - > base + SPIFC_A1_USER_CTRL1_REG ) ;
}
static void amlogic_spifc_a1_set_dummy ( struct amlogic_spifc_a1 * spifc ,
u32 dummy_cfg )
{
u32 val = readl ( spifc - > base + SPIFC_A1_USER_CTRL2_REG ) ;
val & = ~ ( SPIFC_A1_USER_DUMMY_MODE | SPIFC_A1_USER_DUMMY_CLK_SYCLES ) ;
val | = dummy_cfg ;
writel ( val , spifc - > base + SPIFC_A1_USER_CTRL2_REG ) ;
}
static int amlogic_spifc_a1_read ( struct amlogic_spifc_a1 * spifc , void * buf ,
u32 size , u32 mode )
{
u32 val = readl ( spifc - > base + SPIFC_A1_USER_CTRL3_REG ) ;
int ret ;
val & = ~ ( SPIFC_A1_USER_DIN_MODE | SPIFC_A1_USER_DIN_BYTES ) ;
val | = SPIFC_A1_USER_DIN_ENABLE ;
val | = FIELD_PREP ( SPIFC_A1_USER_DIN_MODE , mode ) ;
val | = FIELD_PREP ( SPIFC_A1_USER_DIN_BYTES , size ) ;
writel ( val , spifc - > base + SPIFC_A1_USER_CTRL3_REG ) ;
ret = amlogic_spifc_a1_request ( spifc , true ) ;
if ( ! ret )
amlogic_spifc_a1_drain_buffer ( spifc , buf , size ) ;
return ret ;
}
static int amlogic_spifc_a1_write ( struct amlogic_spifc_a1 * spifc ,
const void * buf , u32 size , u32 mode )
{
u32 val ;
amlogic_spifc_a1_fill_buffer ( spifc , buf , size ) ;
val = readl ( spifc - > base + SPIFC_A1_USER_CTRL1_REG ) ;
val & = ~ ( SPIFC_A1_USER_DOUT_MODE | SPIFC_A1_USER_DOUT_BYTES ) ;
val | = FIELD_PREP ( SPIFC_A1_USER_DOUT_MODE , mode ) ;
val | = FIELD_PREP ( SPIFC_A1_USER_DOUT_BYTES , size ) ;
val | = SPIFC_A1_USER_DOUT_ENABLE ;
writel ( val , spifc - > base + SPIFC_A1_USER_CTRL1_REG ) ;
return amlogic_spifc_a1_request ( spifc , false ) ;
}
2023-07-06 14:03:31 +03:00
static int amlogic_spifc_a1_set_freq ( struct amlogic_spifc_a1 * spifc , u32 freq )
{
int ret ;
if ( freq = = spifc - > curr_speed_hz )
return 0 ;
ret = clk_set_rate ( spifc - > clk , freq ) ;
if ( ret )
return ret ;
spifc - > curr_speed_hz = freq ;
return 0 ;
}
2023-04-03 21:32:17 +03:00
static int amlogic_spifc_a1_exec_op ( struct spi_mem * mem ,
const struct spi_mem_op * op )
{
struct amlogic_spifc_a1 * spifc =
spi_controller_get_devdata ( mem - > spi - > controller ) ;
2023-07-06 14:03:30 +03:00
size_t data_size = op - > data . nbytes ;
2023-04-03 21:32:17 +03:00
int ret ;
2023-07-06 14:03:31 +03:00
ret = amlogic_spifc_a1_set_freq ( spifc , mem - > spi - > max_speed_hz ) ;
if ( ret )
return ret ;
2023-04-03 21:32:17 +03:00
amlogic_spifc_a1_user_init ( spifc ) ;
2023-07-06 14:03:30 +03:00
amlogic_spifc_a1_set_cmd ( spifc , SPIFC_A1_USER_CMD ( op ) ) ;
2023-04-03 21:32:17 +03:00
2023-07-06 14:03:30 +03:00
if ( op - > addr . nbytes )
amlogic_spifc_a1_set_addr ( spifc , op - > addr . val ,
SPIFC_A1_USER_ADDR ( op ) ) ;
2023-04-03 21:32:17 +03:00
2023-07-06 14:03:30 +03:00
if ( op - > dummy . nbytes )
amlogic_spifc_a1_set_dummy ( spifc , SPIFC_A1_USER_DUMMY ( op ) ) ;
2023-04-03 21:32:17 +03:00
2023-07-06 14:03:30 +03:00
if ( data_size ) {
u32 mode = ilog2 ( op - > data . buswidth ) ;
2023-04-03 21:32:17 +03:00
writel ( 0 , spifc - > base + SPIFC_A1_USER_DBUF_ADDR_REG ) ;
if ( op - > data . dir = = SPI_MEM_DATA_IN )
2023-07-06 14:03:30 +03:00
ret = amlogic_spifc_a1_read ( spifc , op - > data . buf . in ,
data_size , mode ) ;
2023-04-03 21:32:17 +03:00
else
2023-07-06 14:03:30 +03:00
ret = amlogic_spifc_a1_write ( spifc , op - > data . buf . out ,
data_size , mode ) ;
} else {
ret = amlogic_spifc_a1_request ( spifc , false ) ;
}
2023-04-03 21:32:17 +03:00
return ret ;
}
2023-07-06 14:03:30 +03:00
static int amlogic_spifc_a1_adjust_op_size ( struct spi_mem * mem ,
struct spi_mem_op * op )
{
op - > data . nbytes = min ( op - > data . nbytes , SPIFC_A1_BUFFER_SIZE ) ;
return 0 ;
}
2023-04-03 21:32:17 +03:00
static void amlogic_spifc_a1_hw_init ( struct amlogic_spifc_a1 * spifc )
{
u32 regv ;
regv = readl ( spifc - > base + SPIFC_A1_AHB_REQ_CTRL_REG ) ;
regv & = ~ ( SPIFC_A1_AHB_REQ_ENABLE ) ;
writel ( regv , spifc - > base + SPIFC_A1_AHB_REQ_CTRL_REG ) ;
regv = readl ( spifc - > base + SPIFC_A1_AHB_CTRL_REG ) ;
regv & = ~ ( SPIFC_A1_AHB_BUS_EN ) ;
writel ( regv , spifc - > base + SPIFC_A1_AHB_CTRL_REG ) ;
writel ( SPIFC_A1_ACTIMING0_VAL , spifc - > base + SPIFC_A1_ACTIMING0_REG ) ;
writel ( 0 , spifc - > base + SPIFC_A1_USER_DBUF_ADDR_REG ) ;
}
static const struct spi_controller_mem_ops amlogic_spifc_a1_mem_ops = {
. exec_op = amlogic_spifc_a1_exec_op ,
2023-07-06 14:03:30 +03:00
. adjust_op_size = amlogic_spifc_a1_adjust_op_size ,
2023-04-03 21:32:17 +03:00
} ;
static int amlogic_spifc_a1_probe ( struct platform_device * pdev )
{
struct spi_controller * ctrl ;
struct amlogic_spifc_a1 * spifc ;
int ret ;
2023-08-07 20:40:46 +08:00
ctrl = devm_spi_alloc_host ( & pdev - > dev , sizeof ( * spifc ) ) ;
2023-04-03 21:32:17 +03:00
if ( ! ctrl )
return - ENOMEM ;
spifc = spi_controller_get_devdata ( ctrl ) ;
platform_set_drvdata ( pdev , spifc ) ;
spifc - > dev = & pdev - > dev ;
spifc - > ctrl = ctrl ;
spifc - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( spifc - > base ) )
return PTR_ERR ( spifc - > base ) ;
spifc - > clk = devm_clk_get_enabled ( spifc - > dev , NULL ) ;
if ( IS_ERR ( spifc - > clk ) )
return dev_err_probe ( spifc - > dev , PTR_ERR ( spifc - > clk ) ,
" unable to get clock \n " ) ;
amlogic_spifc_a1_hw_init ( spifc ) ;
pm_runtime_set_autosuspend_delay ( spifc - > dev , 500 ) ;
pm_runtime_use_autosuspend ( spifc - > dev ) ;
devm_pm_runtime_enable ( spifc - > dev ) ;
ctrl - > num_chipselect = 1 ;
ctrl - > dev . of_node = pdev - > dev . of_node ;
ctrl - > bits_per_word_mask = SPI_BPW_MASK ( 8 ) ;
ctrl - > auto_runtime_pm = true ;
ctrl - > mem_ops = & amlogic_spifc_a1_mem_ops ;
ctrl - > min_speed_hz = SPIFC_A1_MIN_HZ ;
ctrl - > max_speed_hz = SPIFC_A1_MAX_HZ ;
ctrl - > mode_bits = ( SPI_RX_DUAL | SPI_TX_DUAL |
SPI_RX_QUAD | SPI_TX_QUAD ) ;
ret = devm_spi_register_controller ( spifc - > dev , ctrl ) ;
if ( ret )
return dev_err_probe ( spifc - > dev , ret ,
" failed to register spi controller \n " ) ;
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int amlogic_spifc_a1_suspend ( struct device * dev )
{
struct amlogic_spifc_a1 * spifc = dev_get_drvdata ( dev ) ;
int ret ;
ret = spi_controller_suspend ( spifc - > ctrl ) ;
if ( ret )
return ret ;
if ( ! pm_runtime_suspended ( dev ) )
clk_disable_unprepare ( spifc - > clk ) ;
return 0 ;
}
static int amlogic_spifc_a1_resume ( struct device * dev )
{
struct amlogic_spifc_a1 * spifc = dev_get_drvdata ( dev ) ;
int ret = 0 ;
if ( ! pm_runtime_suspended ( dev ) ) {
ret = clk_prepare_enable ( spifc - > clk ) ;
if ( ret )
return ret ;
}
amlogic_spifc_a1_hw_init ( spifc ) ;
ret = spi_controller_resume ( spifc - > ctrl ) ;
if ( ret )
clk_disable_unprepare ( spifc - > clk ) ;
return ret ;
}
# endif /* CONFIG_PM_SLEEP */
# ifdef CONFIG_PM
static int amlogic_spifc_a1_runtime_suspend ( struct device * dev )
{
struct amlogic_spifc_a1 * spifc = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( spifc - > clk ) ;
return 0 ;
}
static int amlogic_spifc_a1_runtime_resume ( struct device * dev )
{
struct amlogic_spifc_a1 * spifc = dev_get_drvdata ( dev ) ;
int ret ;
ret = clk_prepare_enable ( spifc - > clk ) ;
if ( ! ret )
amlogic_spifc_a1_hw_init ( spifc ) ;
return ret ;
}
# endif /* CONFIG_PM */
static const struct dev_pm_ops amlogic_spifc_a1_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( amlogic_spifc_a1_suspend ,
amlogic_spifc_a1_resume )
SET_RUNTIME_PM_OPS ( amlogic_spifc_a1_runtime_suspend ,
amlogic_spifc_a1_runtime_resume ,
NULL )
} ;
# ifdef CONFIG_OF
static const struct of_device_id amlogic_spifc_a1_dt_match [ ] = {
{ . compatible = " amlogic,a1-spifc " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , amlogic_spifc_a1_dt_match ) ;
# endif /* CONFIG_OF */
static struct platform_driver amlogic_spifc_a1_driver = {
. probe = amlogic_spifc_a1_probe ,
. driver = {
. name = " amlogic-spifc-a1 " ,
. of_match_table = of_match_ptr ( amlogic_spifc_a1_dt_match ) ,
. pm = & amlogic_spifc_a1_pm_ops ,
} ,
} ;
module_platform_driver ( amlogic_spifc_a1_driver ) ;
MODULE_AUTHOR ( " Martin Kurbanov <mmkurbanov@sberdevices.ru> " ) ;
MODULE_DESCRIPTION ( " Amlogic A1 SPIFC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;