// SPDX-License-Identifier: GPL-2.0+
//
// Driver for Amlogic Meson SPI flash controller (SPIFC)
//
// Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com>
//
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.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/regmap.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
/* register map */
#define REG_CMD 0 x00
#define REG_ADDR 0 x04
#define REG_CTRL 0 x08
#define REG_CTRL1 0 x0c
#define REG_STATUS 0 x10
#define REG_CTRL2 0 x14
#define REG_CLOCK 0 x18
#define REG_USER 0 x1c
#define REG_USER1 0 x20
#define REG_USER2 0 x24
#define REG_USER3 0 x28
#define REG_USER4 0 x2c
#define REG_SLAVE 0 x30
#define REG_SLAVE1 0 x34
#define REG_SLAVE2 0 x38
#define REG_SLAVE3 0 x3c
#define REG_C0 0 x40
#define REG_B8 0 x60
#define REG_MAX 0 x7c
/* register fields */
#define CMD_USER BIT(18 )
#define CTRL_ENABLE_AHB BIT(17 )
#define CLOCK_SOURCE BIT(31 )
#define CLOCK_DIV_SHIFT 12
#define CLOCK_DIV_MASK (0 x3f << CLOCK_DIV_SHIFT)
#define CLOCK_CNT_HIGH_SHIFT 6
#define CLOCK_CNT_HIGH_MASK (0 x3f << CLOCK_CNT_HIGH_SHIFT)
#define CLOCK_CNT_LOW_SHIFT 0
#define CLOCK_CNT_LOW_MASK (0 x3f << CLOCK_CNT_LOW_SHIFT)
#define USER_DIN_EN_MS BIT(0 )
#define USER_CMP_MODE BIT(2 )
#define USER_UC_DOUT_SEL BIT(27 )
#define USER_UC_DIN_SEL BIT(28 )
#define USER_UC_MASK ((BIT(5 ) - 1 ) << 27 )
#define USER1_BN_UC_DOUT_SHIFT 17
#define USER1_BN_UC_DOUT_MASK (0 xff << 16 )
#define USER1_BN_UC_DIN_SHIFT 8
#define USER1_BN_UC_DIN_MASK (0 xff << 8 )
#define USER4_CS_ACT BIT(30 )
#define SLAVE_TRST_DONE BIT(4 )
#define SLAVE_OP_MODE BIT(30 )
#define SLAVE_SW_RST BIT(31 )
#define SPIFC_BUFFER_SIZE 64
/**
* struct meson_spifc
* @ host : the SPI host
* @ regmap : regmap for device registers
* @ clk : input clock of the built - in baud rate generator
* @ dev : the device structure
*/
struct meson_spifc {
struct spi_controller *host;
struct regmap *regmap;
struct clk *clk;
struct device *dev;
};
static const struct regmap_config spifc_regmap_config = {
.reg_bits = 32 ,
.val_bits = 32 ,
.reg_stride = 4 ,
.max_register = REG_MAX,
};
/**
* meson_spifc_wait_ready ( ) - wait for the current operation to terminate
* @ spifc : the Meson SPI device
* Return : 0 on success , a negative value on error
*/
static int meson_spifc_wait_ready(struct meson_spifc *spifc)
{
unsigned long deadline = jiffies + msecs_to_jiffies(5 );
u32 data;
do {
regmap_read(spifc->regmap, REG_SLAVE, &data);
if (data & SLAVE_TRST_DONE)
return 0 ;
cond_resched();
} while (!time_after(jiffies, deadline));
return -ETIMEDOUT;
}
/**
* meson_spifc_drain_buffer ( ) - copy data from device buffer to memory
* @ spifc : the Meson SPI device
* @ buf : the destination buffer
* @ len : number of bytes to copy
*/
static void meson_spifc_drain_buffer(struct meson_spifc *spifc, u8 *buf,
int len)
{
u32 data;
int i = 0 ;
while (i < len) {
regmap_read(spifc->regmap, REG_C0 + i, &data);
if (len - i >= 4 ) {
*((u32 *)buf) = data;
buf += 4 ;
} else {
memcpy(buf, &data, len - i);
break ;
}
i += 4 ;
}
}
/**
* meson_spifc_fill_buffer ( ) - copy data from memory to device buffer
* @ spifc : the Meson SPI device
* @ buf : the source buffer
* @ len : number of bytes to copy
*/
static void meson_spifc_fill_buffer(struct meson_spifc *spifc, const u8 *buf,
int len)
{
u32 data;
int i = 0 ;
while (i < len) {
if (len - i >= 4 )
data = *(u32 *)buf;
else
memcpy(&data, buf, len - i);
regmap_write(spifc->regmap, REG_C0 + i, data);
buf += 4 ;
i += 4 ;
}
}
/**
* meson_spifc_setup_speed ( ) - program the clock divider
* @ spifc : the Meson SPI device
* @ speed : desired speed in Hz
*/
static void meson_spifc_setup_speed(struct meson_spifc *spifc, u32 speed)
{
unsigned long parent, value;
int n;
parent = clk_get_rate(spifc->clk);
n = max_t(int , parent / speed - 1 , 1 );
dev_dbg(spifc->dev, "parent %lu, speed %u, n %d\n" , parent,
speed, n);
value = (n << CLOCK_DIV_SHIFT) & CLOCK_DIV_MASK;
value |= (n << CLOCK_CNT_LOW_SHIFT) & CLOCK_CNT_LOW_MASK;
value |= (((n + 1 ) / 2 - 1 ) << CLOCK_CNT_HIGH_SHIFT) &
CLOCK_CNT_HIGH_MASK;
regmap_write(spifc->regmap, REG_CLOCK, value);
}
/**
* meson_spifc_txrx ( ) - transfer a chunk of data
* @ spifc : the Meson SPI device
* @ xfer : the current SPI transfer
* @ offset : offset of the data to transfer
* @ len : length of the data to transfer
* @ last_xfer : whether this is the last transfer of the message
* @ last_chunk : whether this is the last chunk of the transfer
* Return : 0 on success , a negative value on error
*/
static int meson_spifc_txrx(struct meson_spifc *spifc,
struct spi_transfer *xfer,
int offset, int len, bool last_xfer,
bool last_chunk)
{
bool keep_cs = true ;
int ret;
if (xfer->tx_buf)
meson_spifc_fill_buffer(spifc, xfer->tx_buf + offset, len);
/* enable DOUT stage */
regmap_update_bits(spifc->regmap, REG_USER, USER_UC_MASK,
USER_UC_DOUT_SEL);
regmap_write(spifc->regmap, REG_USER1,
(8 * len - 1 ) << USER1_BN_UC_DOUT_SHIFT);
/* enable data input during DOUT */
regmap_update_bits(spifc->regmap, REG_USER, USER_DIN_EN_MS,
USER_DIN_EN_MS);
if (last_chunk) {
if (last_xfer)
keep_cs = xfer->cs_change;
else
keep_cs = !xfer->cs_change;
}
regmap_update_bits(spifc->regmap, REG_USER4, USER4_CS_ACT,
keep_cs ? USER4_CS_ACT : 0 );
/* clear transition done bit */
regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_TRST_DONE, 0 );
/* start transfer */
regmap_update_bits(spifc->regmap, REG_CMD, CMD_USER, CMD_USER);
ret = meson_spifc_wait_ready(spifc);
if (!ret && xfer->rx_buf)
meson_spifc_drain_buffer(spifc, xfer->rx_buf + offset, len);
return ret;
}
/**
* meson_spifc_transfer_one ( ) - perform a single transfer
* @ host : the SPI host
* @ spi : the SPI device
* @ xfer : the current SPI transfer
* Return : 0 on success , a negative value on error
*/
static int meson_spifc_transfer_one(struct spi_controller *host,
struct spi_device *spi,
struct spi_transfer *xfer)
{
struct meson_spifc *spifc = spi_controller_get_devdata(host);
int len, done = 0 , ret = 0 ;
meson_spifc_setup_speed(spifc, xfer->speed_hz);
regmap_update_bits(spifc->regmap, REG_CTRL, CTRL_ENABLE_AHB, 0 );
while (done < xfer->len && !ret) {
len = min_t(int , xfer->len - done, SPIFC_BUFFER_SIZE);
ret = meson_spifc_txrx(spifc, xfer, done, len,
spi_transfer_is_last(host, xfer),
done + len >= xfer->len);
done += len;
}
regmap_update_bits(spifc->regmap, REG_CTRL, CTRL_ENABLE_AHB,
CTRL_ENABLE_AHB);
return ret;
}
/**
* meson_spifc_hw_init ( ) - reset and initialize the SPI controller
* @ spifc : the Meson SPI device
*/
static void meson_spifc_hw_init(struct meson_spifc *spifc)
{
/* reset device */
regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_SW_RST,
SLAVE_SW_RST);
/* disable compatible mode */
regmap_update_bits(spifc->regmap, REG_USER, USER_CMP_MODE, 0 );
/* set master mode */
regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_OP_MODE, 0 );
}
static int meson_spifc_probe(struct platform_device *pdev)
{
struct spi_controller *host;
struct meson_spifc *spifc;
void __iomem *base;
unsigned int rate;
int ret = 0 ;
host = spi_alloc_host(&pdev->dev, sizeof (struct meson_spifc));
if (!host)
return -ENOMEM;
platform_set_drvdata(pdev, host);
spifc = spi_controller_get_devdata(host);
spifc->dev = &pdev->dev;
base = devm_platform_ioremap_resource(pdev, 0 );
if (IS_ERR(base)) {
ret = PTR_ERR(base);
goto out_err;
}
spifc->regmap = devm_regmap_init_mmio(spifc->dev, base,
&spifc_regmap_config);
if (IS_ERR(spifc->regmap)) {
ret = PTR_ERR(spifc->regmap);
goto out_err;
}
spifc->clk = devm_clk_get_enabled(spifc->dev, NULL);
if (IS_ERR(spifc->clk)) {
dev_err(spifc->dev, "missing clock\n" );
ret = PTR_ERR(spifc->clk);
goto out_err;
}
rate = clk_get_rate(spifc->clk);
host->num_chipselect = 1 ;
host->dev.of_node = pdev->dev.of_node;
host->bits_per_word_mask = SPI_BPW_MASK(8 );
host->auto_runtime_pm = true ;
host->transfer_one = meson_spifc_transfer_one;
host->min_speed_hz = rate >> 6 ;
host->max_speed_hz = rate >> 1 ;
meson_spifc_hw_init(spifc);
pm_runtime_set_active(spifc->dev);
pm_runtime_enable(spifc->dev);
ret = devm_spi_register_controller(spifc->dev, host);
if (ret) {
dev_err(spifc->dev, "failed to register spi host\n" );
goto out_pm;
}
return 0 ;
out_pm:
pm_runtime_disable(spifc->dev);
out_err:
spi_controller_put(host);
return ret;
}
static void meson_spifc_remove(struct platform_device *pdev)
{
pm_runtime_get_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
#ifdef CONFIG_PM_SLEEP
static int meson_spifc_suspend(struct device *dev)
{
struct spi_controller *host = dev_get_drvdata(dev);
struct meson_spifc *spifc = spi_controller_get_devdata(host);
int ret;
ret = spi_controller_suspend(host);
if (ret)
return ret;
if (!pm_runtime_suspended(dev))
clk_disable_unprepare(spifc->clk);
return 0 ;
}
static int meson_spifc_resume(struct device *dev)
{
struct spi_controller *host = dev_get_drvdata(dev);
struct meson_spifc *spifc = spi_controller_get_devdata(host);
int ret;
if (!pm_runtime_suspended(dev)) {
ret = clk_prepare_enable(spifc->clk);
if (ret)
return ret;
}
meson_spifc_hw_init(spifc);
ret = spi_controller_resume(host);
if (ret)
clk_disable_unprepare(spifc->clk);
return ret;
}
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM
static int meson_spifc_runtime_suspend(struct device *dev)
{
struct spi_controller *host = dev_get_drvdata(dev);
struct meson_spifc *spifc = spi_controller_get_devdata(host);
clk_disable_unprepare(spifc->clk);
return 0 ;
}
static int meson_spifc_runtime_resume(struct device *dev)
{
struct spi_controller *host = dev_get_drvdata(dev);
struct meson_spifc *spifc = spi_controller_get_devdata(host);
return clk_prepare_enable(spifc->clk);
}
#endif /* CONFIG_PM */
static const struct dev_pm_ops meson_spifc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(meson_spifc_suspend, meson_spifc_resume)
SET_RUNTIME_PM_OPS(meson_spifc_runtime_suspend,
meson_spifc_runtime_resume,
NULL)
};
static const struct of_device_id meson_spifc_dt_match[] = {
{ .compatible = "amlogic,meson6-spifc" , },
{ .compatible = "amlogic,meson-gxbb-spifc" , },
{ },
};
MODULE_DEVICE_TABLE(of, meson_spifc_dt_match);
static struct platform_driver meson_spifc_driver = {
.probe = meson_spifc_probe,
.remove = meson_spifc_remove,
.driver = {
.name = "meson-spifc" ,
.of_match_table = of_match_ptr(meson_spifc_dt_match),
.pm = &meson_spifc_pm_ops,
},
};
module_platform_driver(meson_spifc_driver);
MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>" );
MODULE_DESCRIPTION("Amlogic Meson SPIFC driver" );
MODULE_LICENSE("GPL v2" );
Messung V0.5 in Prozent C=95 H=88 G=91
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland