blob: 0f9cc53800b13b37832bf88393f59b5641c043b2 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#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>
#ifdef CONFIG_MTD_SPI_NOR
#include <linux/mtd/spi-nor.h>
#endif
#include <linux/atomic.h>
#define CONFIG_SPIFC_HWCAPS_DUAL_QUAD
//#define CONFIG_SPIFC_DEBUG
/* register map */
#define REG_MAX 0x400
//AHB domain register.
#define SPIFC_AHB_CTRL (0x0000 << 2)
#define AHB_BUS_EN BIT(31)
#define DECERR_EN BIT(30)
#define FORCE_INCR BIT(29)
#define CWF_EN BIT(19)
#define RDBUF_SIZE GENMASK(18, 17)
#define AHB_MASTER_EN BIT(16)
#define CLR_HRDATA2 BIT(14)
#define CLR_HRDATA1 BIT(13)
#define CLR_HRDATA0 BIT(12)
#define SPIFC_CLK_CTRL (0x0001 << 2)
#define ASYNC_BUFFER_AHB_CLK_DISABLE BIT(14) // 1-disable
#define AHB2SPI_AHB_CLK_DISABLE BIT(13) // 1-disable
#define AHB_ARB_AHB_CLK_DISABLE BIT(12) // 1-disable
#define ASYNC_BUFFER_AHB_CLK_AUTO_GATING_ENABLE BIT(10) // 1-enable
#define AHB2SPI_AHB_CLK_AUTO_GATING_ENABLE BIT(9) // 1-enable
#define AHB_ARB_AHB_CLK_AUTO_GATING_ENABLE BIT(8) // 1-enable
#define ASYNC_BUFFER_AHB_CLK_DOMAIN_RESET BIT(2) // 1-reset
#define AHB2SPI_AHB_CLK_DOMAIN_RESET BIT(1) // 1-reset
#define SPIFC_SEC_CTRL (0x0002 << 2)
#define ADDRESS_SEC_RANGE_ENABLE BIT(31)
#define RANGE6_ENABLE BIT(14)
#define RANGE5_ENABLE BIT(13)
#define RANGE4_ENABLE BIT(12)
#define RANGE3_ENABLE BIT(11)
#define RANGE2_ENABLE BIT(10)
#define RANGE1_ENABLE BIT(9)
#define RANGE0_ENABLE BIT(8)
#define SPI_CLK_REG_SEC_CTRL GENMASK(7, 4)
#define AHB_CLK_DOMAIN_REG_SEC_CTRL GENMASK(3, 0)
#define SPIFC_RANGE0_STA (0x0010 << 2)
#define SPIFC_RANGE0_EDA (0x0011 << 2)
#define SPIFC_RANGE0_CTRL (0x0012 << 2)
#define SPIFC_RANGE1_STA (0x0013 << 2)
#define SPIFC_RANGE1_EDA (0x0014 << 2)
#define SPIFC_RANGE1_CTRL (0x0015 << 2)
#define SPIFC_RANGE2_STA (0x0016 << 2)
#define SPIFC_RANGE2_EDA (0x0017 << 2)
#define SPIFC_RANGE2_CTRL (0x0018 << 2)
#define SPIFC_RANGE3_STA (0x0019 << 2)
#define SPIFC_RANGE3_EDA (0x001a << 2)
#define SPIFC_RANGE3_CTRL (0x001b << 2)
#define SPIFC_RANGE4_STA (0x001c << 2)
#define SPIFC_RANGE4_EDA (0x001d << 2)
#define SPIFC_RANGE4_CTRL (0x001e << 2)
#define SPIFC_RANGE5_STA (0x001f << 2)
#define SPIFC_RANGE5_EDA (0x0021 << 2)
#define SPIFC_RANGE5_CTRL (0x0020 << 2)
#define SPIFC_RANGE6_STA (0x0022 << 2)
#define SPIFC_RANGE6_EDA (0x0023 << 2)
#define SPIFC_RANGE6_CTRL (0x0024 << 2)
#define SPIFC_RANGE7_CTRL (0x0025 << 2)
#define SPIFC_AHB_WTCH_CTRL (0x0026 << 2)
#define AHB_BUS_WDT GENMASK(15, 0)
#define SPIFC_SEC_VIO0 (0x0027 << 2)
#define SPIFC_SEC_VIO1 (0x0028 << 2)
#define AHB_VIO_STATUS BIT(31)
#define AHB_VIO_HMASTER GENMASK(6, 5)
#define AHB_VIO_HPROT GENMASK(4, 2)
#define AHB_VIO_HNONSEC BIT(1)
#define AHB_VIO_HWRITE BIT(0)
#define SPIFC_AHB_STS (0x0029 << 2)
#define AHB_STATUS BIT(31)
#define AHB_DATA_CYCLE_STATUS BIT(30)
#define AHB_BUS_SPI_STATUS BIT(29)
//SPI cts_spi_clk domain register.
#define SPIFC_USER_CTRL0 (0x0080 << 2)
#define USER_REQUEST_ENABLE BIT(31) //W1R0
#define USER_REQUEST_FINISH BIT(30) //W0R1
#define USER_DATA_UPDATED BIT(0) //W0R1
#define SPIFC_USER_CTRL1 (0x0081 << 2)
#define USER_CMD_ENABLE BIT(30)
#define USER_CMD_MODE GENMASK(29, 28)
#define USER_CMD_CODE GENMASK(27, 20)
#define USER_ADDR_ENABLE BIT(19)
#define USER_ADDR_MODE GENMASK(18, 17)
#define USER_ADDR_BYTES GENMASK(16, 15)
#define USER_DOUT_ENABLE BIT(14)
#define USER_DOUT_AES_ENABLE BIT(13)
#define USER_DOUT_SRC BIT(12)
#define USER_DOUT_MODE GENMASK(11, 10)
#define USER_DOUT_BYTES GENMASK(9, 0)
#define USER_CMD_MODE_SHIFT 28
#define USER_CMD_CODE_SHIFT 20
#define USER_ADDR_MODE_SHIFT 17
#define USER_ADDR_BYTES_SHIFT 15
#define USER_DOUT_MODE_SHIFT 10
#define USER_DOUT_BYTES_SHIFT 0
#define SPIFC_USER_CTRL2 (0x0082 << 2)
#define USER_DUMMY_ENABLE BIT(31)
#define USER_DUMMY_MODE GENMASK(30, 29)
#define USER_DUMMY_CLK_SYCLES GENMASK(28, 23)
#define USER_DUMMY_MODE_SHIFT 29
#define USER_DUMMY_CLK_SYCLES_SHIFT 23
#define SPIFC_USER_CTRL3 (0x0083 << 2)
#define USER_DIN_ENABLE BIT(31)
#define USER_DIN_DEST BIT(30)
#define USER_DIN_AES_ENABLE BIT(29)
#define USER_DIN_MODE GENMASK(28, 27)
#define USER_DIN_BYTES GENMASK(25, 16)
#define USER_DIN_MODE_SHIFT 27
#define USER_DIN_BYTES_SHIFT 16
#define SPIFC_USER_ADDR (0x0084 << 2)
#define SPIFC_AHB_REQ_CTRL (0x0085 << 2)
#define AHB_REQ_ENABLE BIT(31)
#define AHB_CMD_ENABLE BIT(30)
#define AHB_CMD_MODE GENMASK(29, 28)
#define AHB_CMD_CODE GENMASK(27, 20)
#define AHB_ADDR_ENABLE BIT(19)
#define AHB_ADDR_MODE GENMASK(18, 17)
#define AHB_ADDR_WIDTH GENMASK(16, 15)
#define AHB_DIN_MODE GENMASK(9, 8)
#define AHB_DIN_AES_ENABLE BIT(7)
#define AHB_DIN_BYTES GENMASK(1, 0)
#define SPIFC_AHB_REQ_CTRL1 (0x0086 << 2)
#define AHB_DUMMY_ENABLE BIT(31)
#define AHB_DUMMY_MODE GENMASK(30, 29)
#define AHB_DUMMY_CLK_SYCLES GENMASK(28, 23)
#define AHB_DUMMY_OUTPUT_DATA GENMASK(15, 0)
#define SPIFC_AHB_REQ_CTRL2 (0x0087 << 2)
#define SPIFC_ACTIMING0 (0x0088 << 2)
#define tSLCH GENMASK(31, 30)
#define tCLSH GENMASK(29, 28)
#define tSHWL GENMASK(20, 16)
#define tSHSL2 GENMASK(15, 12)
#define tSHSL1 GENMASK(11, 8)
#define tWHSL GENMASK(7, 0)
#define SPIFC_ACTIMING1 (0x0089 << 2)
#define D2_WPn_ENABLE BIT(31)
#define D3_HOLDn_ENABLE BIT(29)
#define DTR_MODE BIT(8)
#define CLOCK_TURN_AROUND_DELAY GENMASK(6, 4)
#define SPIFC_ACTIMING2 (0x008a << 2)
#define SP_CLK_INPUT_PIN_ENABLE BIT(31)
#define SP_CLK_INPUT_PIN_DELAY GENMASK(3, 0)
#define SPIFC_DBUF_CTRL (0x0090 << 2)
#define DBUF_DIR BIT(31)
#define DBUF_AUTO_UPDATE_ADDR BIT(30)
#define DBUF_ADDR GENMASK(7, 0)
#define SPIFC_DBUF_DATA (0x0091 << 2)
#define SPIFC_USER_DBUF_ADDR (0x0092 << 2)
#define SPIFC_FLASH_STATUS (0x00a0 << 2)
#define SPIFC_STATUS (0x00a1 << 2)
#define AES_KEY_VALID BIT(1)
#define SPI_CTRL_STATE BIT(0)
#define SPIFC_CTRL (0x00a2 << 2)
#define DBUF_MEMPD GENMASK(15, 14)
#define ASYNC_BUF_CLK_DISABLE BIT(13)
#define SPI_CLK_DISABLE BIT(12)
#define ASYNC_BUF_CLK_AUTO_GATE_ENABLE BIT(9)
#define SPI_CLK_AUTO_GATE_ENABLE BIT(8)
#define SPI_INTERFACE_RESET BIT(2)
#define ASYNC_BUF_RESET BIT(1)
#define SPI_RESET BIT(0)
#define SPIFC_BUFFER_SIZE 512
#define convert_nbits(n) (fls(n) - 1)
/**
* struct meson_spifc
* @master: the SPI master
* @regmap: regmap for device registers
* @clk: input clock of the built-in baud rate generator
* @device: the device structure
*/
struct meson_spifc {
#ifdef CONFIG_MTD_SPI_NOR
struct spi_nor *nor;
/* used by nor core */
struct mutex lock;
u32 clk_rate;
#endif
struct spi_master *master;
struct regmap *regmap;
struct clk *clk;
struct device *dev;
u8 cmd;
u16 cmd_nbits :2;
u32 addr;
u16 addr_nbits :2;
u8 addr_len; // in bytes
u16 dummy_nbits :2;
u8 dummy_clk_sycles; // in bits
u16 dout_nbits :2;
u16 din_nbits :2;
};
static const struct regmap_config spifc_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = REG_MAX,
};
#ifdef CONFIG_SPIFC_DEBUG
#define spifc_dbg(fmt, args...) {pr_info("spifc: " fmt, ## args); }
static void meson_spifc_dump(struct meson_spifc *spifc)
{
u32 buf[7];
regmap_read(spifc->regmap, SPIFC_USER_CTRL0, &buf[0]);
regmap_read(spifc->regmap, SPIFC_USER_CTRL1, &buf[1]);
regmap_read(spifc->regmap, SPIFC_USER_CTRL2, &buf[2]);
regmap_read(spifc->regmap, SPIFC_USER_CTRL3, &buf[3]);
regmap_read(spifc->regmap, SPIFC_USER_ADDR, &buf[4]);
regmap_read(spifc->regmap, SPIFC_DBUF_CTRL, &buf[5]);
regmap_read(spifc->regmap, SPIFC_USER_DBUF_ADDR, &buf[6]);
pr_info("0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]);
}
#else
#define spifc_dbg(fmt, args...)
#define meson_spifc_dump(spifc)
#endif
/**
* 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_start_then_wait_ready(struct meson_spifc *spifc,
bool read)
{
unsigned long deadline = jiffies + msecs_to_jiffies(200);
u32 data;
meson_spifc_dump(spifc);
/* clear transition done bit, and start transfer */
regmap_write(spifc->regmap, SPIFC_USER_CTRL0, USER_REQUEST_ENABLE);
do {
regmap_read(spifc->regmap, SPIFC_USER_CTRL0, &data);
if ((data & USER_REQUEST_FINISH) &&
(!read || (data & USER_DATA_UPDATED))) {
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;
regmap_write(spifc->regmap, SPIFC_DBUF_CTRL, DBUF_AUTO_UPDATE_ADDR);
while (i < len) {
regmap_read(spifc->regmap, SPIFC_DBUF_DATA, &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;
regmap_write(spifc->regmap, SPIFC_DBUF_CTRL,
DBUF_DIR | DBUF_AUTO_UPDATE_ADDR);
while (i < len) {
if (len - i >= 4)
data = *(u32 *)buf;
else
memcpy(&data, buf, len - i);
regmap_write(spifc->regmap, SPIFC_DBUF_DATA, 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)
{
clk_set_rate(spifc->clk, speed);
}
static void meson_spifc_user_init(struct meson_spifc *spifc)
{
spifc->cmd_nbits = 0;
spifc->addr_len = 0;
spifc->addr_nbits = 0;
spifc->dummy_clk_sycles = 0;
spifc->dummy_nbits = 0;
spifc->din_nbits = 0;
spifc->dout_nbits = 0;
regmap_write(spifc->regmap, SPIFC_USER_CTRL0, 0);
regmap_write(spifc->regmap, SPIFC_USER_CTRL1, 0);
regmap_write(spifc->regmap, SPIFC_USER_CTRL2, 0);
regmap_write(spifc->regmap, SPIFC_USER_CTRL3, 0);
}
static void meson_spifc_set_cmd(struct meson_spifc *spifc)
{
u32 regv;
regmap_read(spifc->regmap, SPIFC_USER_CTRL1, &regv);
regv &= ~(USER_CMD_MODE | USER_CMD_CODE);
regv |= (spifc->cmd_nbits << USER_CMD_MODE_SHIFT)
| (spifc->cmd << USER_CMD_CODE_SHIFT)
| USER_CMD_ENABLE;
regmap_write(spifc->regmap, SPIFC_USER_CTRL1, regv);
}
static void meson_spifc_set_addr(struct meson_spifc *spifc)
{
u32 regv;
/* set address */
regmap_write(spifc->regmap, SPIFC_USER_ADDR, spifc->addr);
/* set nbits and address len, enable address-stage */
regmap_read(spifc->regmap, SPIFC_USER_CTRL1, &regv);
regv &= ~(USER_ADDR_MODE | USER_ADDR_BYTES);
regv |= (spifc->addr_nbits << USER_ADDR_MODE_SHIFT) & USER_ADDR_MODE;
regv |= ((spifc->addr_len - 1) << USER_ADDR_BYTES_SHIFT)
& USER_ADDR_BYTES;
regv |= USER_ADDR_ENABLE;
regmap_write(spifc->regmap, SPIFC_USER_CTRL1, regv);
}
static void meson_spifc_set_dummy(struct meson_spifc *spifc)
{
u32 regv;
regmap_read(spifc->regmap, SPIFC_USER_CTRL2, &regv);
regv &= ~(USER_DUMMY_MODE | USER_DUMMY_CLK_SYCLES);
regv |= (spifc->dummy_nbits << USER_DUMMY_MODE_SHIFT)
| (spifc->dummy_clk_sycles << USER_DUMMY_CLK_SYCLES_SHIFT)
| USER_DUMMY_ENABLE;
regmap_write(spifc->regmap, SPIFC_USER_CTRL2, regv);
}
/**
* meson_spifc_tx() - 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
* Return: 0 on success, a negative value on error
*/
static int meson_spifc_dout(struct meson_spifc *spifc,
u8 *buf, int offset, int len)
{
u32 regv;
/* write data to DBUF */
meson_spifc_fill_buffer(spifc, buf + offset, len);
/* set DOUT address 0*/
regmap_write(spifc->regmap, SPIFC_USER_DBUF_ADDR, 0);
/* set DOUT len and mode */
regmap_read(spifc->regmap, SPIFC_USER_CTRL1, &regv);
regv &= ~(USER_DOUT_MODE | USER_DOUT_BYTES);
regv |= (len ? USER_DOUT_ENABLE : 0)
| (spifc->dout_nbits << USER_DOUT_MODE_SHIFT)
| (len << USER_DOUT_BYTES_SHIFT);
regmap_write(spifc->regmap, SPIFC_USER_CTRL1, regv);
/* start DOUT */
return meson_spifc_start_then_wait_ready(spifc, 0);
}
/**
* 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
* Return: 0 on success, a negative value on error
*/
static int meson_spifc_din(struct meson_spifc *spifc,
u8 *buf, int offset, int len)
{
int ret;
/* set DIN address 0*/
regmap_write(spifc->regmap, SPIFC_USER_DBUF_ADDR, 0);
/* set DIN len and mode */
regmap_write(spifc->regmap, SPIFC_USER_CTRL3,
(len ? USER_DIN_ENABLE : 0)
| (spifc->din_nbits << USER_DIN_MODE_SHIFT)
| (len << USER_DIN_BYTES_SHIFT));
/* start DIN */
ret = meson_spifc_start_then_wait_ready(spifc, 1);
if (!ret)
/* read data from DBUF */
meson_spifc_drain_buffer(spifc, buf + offset, len);
return ret;
}
/**
* meson_spifc_transfer_one() - perform a single transfer
* @master: the SPI master
* @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_master *master,
struct spi_device *spi,
struct spi_transfer *xfer)
{
struct meson_spifc *spifc = spi_master_get_devdata(master);
int i, len, done = 0, ret = 0;
u8 *p = (u8 *)xfer->tx_buf;
bool last_xfer = spi_transfer_is_last(master, xfer);
u8 stage;
meson_spifc_setup_speed(spifc, xfer->speed_hz);
stage = (xfer->tx_buf && !xfer->rx_buf) ? xfer->rx_nbits : 0;
if (stage == 1) {
meson_spifc_user_init(spifc);
spifc->cmd = *p;
spifc->cmd_nbits = convert_nbits(xfer->tx_nbits);
spifc_dbg("cmd=0x%x\n", spifc->cmd);
meson_spifc_set_cmd(spifc);
if (last_xfer)
ret = meson_spifc_start_then_wait_ready(spifc, 0);
} else if (stage == 2) {
spifc->addr = 0;
for (i = 0; i < xfer->len; i++) {
spifc->addr <<= 8;
spifc->addr |= p[i];
}
spifc->addr_nbits = convert_nbits(xfer->tx_nbits);
spifc->addr_len = xfer->len;
spifc_dbg("addr=0x%x, len=%d\n", spifc->addr, spifc->addr_len);
meson_spifc_set_addr(spifc);
if (last_xfer)
ret = meson_spifc_start_then_wait_ready(spifc, 0);
} else if (stage == 3) {
spifc->dummy_nbits = convert_nbits(xfer->tx_nbits);
spifc->dummy_clk_sycles = xfer->len << 3;
spifc_dbg("dummy: clk_sycles=%d\n", spifc->dummy_clk_sycles);
meson_spifc_set_dummy(spifc);
if (last_xfer)
ret = meson_spifc_start_then_wait_ready(spifc, 0);
} else if (xfer->tx_buf) {
spifc->dout_nbits = convert_nbits(xfer->tx_nbits);
spifc_dbg("dout: len=%d\n", xfer->len);
while (done < xfer->len && !ret) {
len = min_t(int, xfer->len - done, SPIFC_BUFFER_SIZE);
meson_spifc_set_cmd(spifc);
if (spifc->addr_len)
meson_spifc_set_addr(spifc);
if (spifc->dummy_clk_sycles)
meson_spifc_set_dummy(spifc);
ret = meson_spifc_dout(spifc, (u8 *)xfer->tx_buf,
done, len);
spifc_dbg(" cmd=0x%x, len=%d, addr=0x%x\n",
spifc->cmd, len, spifc->addr);
done += len;
spifc->addr += len;
if (spifc->cmd == 0x02)
spifc->cmd = 0x84;
}
} else if (xfer->rx_buf) {
spifc->din_nbits = convert_nbits(xfer->rx_nbits);
spifc_dbg("din: len=%d\n", xfer->len);
while (done < xfer->len && !ret) {
len = min_t(int, xfer->len - done, SPIFC_BUFFER_SIZE);
meson_spifc_set_cmd(spifc);
if (spifc->addr_len)
meson_spifc_set_addr(spifc);
if (spifc->dummy_clk_sycles)
meson_spifc_set_dummy(spifc);
ret = meson_spifc_din(spifc, (u8 *)xfer->rx_buf,
done, len);
spifc_dbg(" cmd=0x%x, len=%d, addr=0x%x\n",
spifc->cmd, len, spifc->addr);
done += len;
spifc->addr += len;
}
}
return ret;
}
#ifdef CONFIG_MTD_SPI_NOR
static int meson_snor_prep(struct spi_nor *nor, enum spi_nor_ops ops)
{
struct meson_spifc *spifc = nor->priv;
mutex_lock(&spifc->lock);
return 0;
}
static void meson_snor_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
{
struct meson_spifc *spifc = nor->priv;
mutex_unlock(&spifc->lock);
}
static int meson_snor_read_reg(struct spi_nor *nor, u8 opcode,
u8 *buf, int len)
{
struct meson_spifc *spifc = nor->priv;
spifc_dbg("read_reg: cmd=0x%x, len=%d\n", opcode, len);
if (len > SPIFC_BUFFER_SIZE)
return -ENOBUFS;
meson_spifc_user_init(spifc);
spifc->cmd = opcode;
meson_spifc_set_cmd(spifc);
return meson_spifc_din(spifc, buf, 0, len);
}
ssize_t meson_snor_read(struct spi_nor *nor, loff_t from,
size_t len, u_char *read_buf)
{
struct meson_spifc *spifc = nor->priv;
int lening, done = 0, ret = 0;
spifc_dbg("read: cmd=0x%x, len=%d, from=0x%x\n",
nor->read_opcode, (u32)len, (u32)from);
meson_spifc_user_init(spifc);
spifc->cmd = nor->read_opcode;
spifc->addr = from;
spifc->addr_len = nor->addr_width; /* 3 */
spifc->dummy_clk_sycles = nor->read_dummy;
spifc->din_nbits = convert_nbits(SNOR_PROTO_DATA(nor->read_proto));
while (done < len && !ret) {
lening = min_t(int, len - done, SPIFC_BUFFER_SIZE);
meson_spifc_set_cmd(spifc);
meson_spifc_set_addr(spifc);
if (spifc->dummy_clk_sycles)
meson_spifc_set_dummy(spifc);
ret = meson_spifc_din(spifc, read_buf, done, lening);
if (ret)
break;
done += lening;
spifc->addr += lening;
}
return ret ? 0 : len;
}
static int meson_snor_write_reg(struct spi_nor *nor, u8 opcode,
u8 *buf, int len)
{
struct meson_spifc *spifc = nor->priv;
spifc_dbg("write_reg: cmd=0x%x, len=%d\n", opcode, len);
if (len > SPIFC_BUFFER_SIZE)
return -ENOBUFS;
meson_spifc_user_init(spifc);
spifc->cmd = opcode;
meson_spifc_set_cmd(spifc);
return meson_spifc_dout(spifc, buf, 0, len);
}
static ssize_t meson_snor_write(struct spi_nor *nor, loff_t to,
size_t len, const u_char *write_buf)
{
struct meson_spifc *spifc = nor->priv;
int lening, done = 0, ret = 0;
spifc_dbg("write: cmd=0x%x, len=%d, to=0x%x\n",
nor->program_opcode, (u32)len, (u32)to);
meson_spifc_user_init(spifc);
spifc->cmd = nor->program_opcode;
spifc->addr = to;
spifc->addr_len = nor->addr_width; /* 3 */
spifc->dout_nbits = convert_nbits(SNOR_PROTO_DATA(nor->write_proto));
while (done < len && !ret) {
lening = min_t(int, len - done, SPIFC_BUFFER_SIZE);
meson_spifc_set_cmd(spifc);
meson_spifc_set_addr(spifc);
ret = meson_spifc_dout(spifc, (u8 *)write_buf, done, lening);
if (ret)
break;
done += lening;
spifc->addr += lening;
}
return ret ? 0 : len;
}
static struct spi_nor *meson_snor_init(struct meson_spifc *spifc,
struct device_node *np)
{
struct device *dev = spifc->dev;
struct spi_nor *nor;
struct mtd_info *mtd;
const struct spi_nor_hwcaps hwcaps = {
#ifdef CONFIG_SPIFC_HWCAPS_DUAL_QUAD
.mask = SNOR_HWCAPS_READ |
SNOR_HWCAPS_READ_FAST |
SNOR_HWCAPS_READ_1_1_2 |
SNOR_HWCAPS_READ_1_1_4 |
SNOR_HWCAPS_PP |
SNOR_HWCAPS_PP_1_1_4,
#else
.mask = SNOR_HWCAPS_READ |
SNOR_HWCAPS_PP,
#endif
};
nor = devm_kzalloc(dev, sizeof(*nor), GFP_KERNEL);
if (!nor)
return 0;
spifc->clk_rate = 24000000;
of_property_read_u32(np, "spi-max-frequency", &spifc->clk_rate);
clk_set_rate(spifc->clk, spifc->clk_rate);
dev_info(dev, "clk_rate = %d\n", spifc->clk_rate);
nor->dev = dev;
spi_nor_set_flash_node(nor, np);
nor->priv = spifc;
nor->prepare = meson_snor_prep;
nor->unprepare = meson_snor_unprep;
nor->read_reg = meson_snor_read_reg;
nor->write_reg = meson_snor_write_reg;
nor->read = meson_snor_read;
nor->write = meson_snor_write;
nor->erase = NULL;
if (!spi_nor_scan(nor, NULL, &hwcaps)) {
dev_info(dev, "read_proto = 0x%x\n", nor->read_proto);
dev_info(dev, "write_proto = 0x%x\n", nor->write_proto);
dev_info(dev, "addr_width = %d\n", nor->addr_width);
dev_info(dev, "read_dummy = %d\n", nor->read_dummy);
mtd = &nor->mtd;
mtd->name = (np->name) ? np->name : "meson_snor";
if (!mtd_device_register(mtd, NULL, 0))
return nor;
}
devm_kfree(dev, nor);
return 0;
}
#endif /* end CONFIG_MTD_SPI_NOR */
/**
* 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)
{
u32 regv;
regmap_read(spifc->regmap, SPIFC_AHB_REQ_CTRL, &regv);
regv &= ~(1 << 31);
regmap_write(spifc->regmap, SPIFC_AHB_REQ_CTRL, regv);
regmap_read(spifc->regmap, SPIFC_AHB_CTRL, &regv);
regv &= ~(1 << 31);
regmap_write(spifc->regmap, SPIFC_AHB_CTRL, regv);
regmap_write(spifc->regmap, SPIFC_ACTIMING0,
(1 << 30) | (1 << 28) | (4 << 16) |
(4 << 12) | (4 << 8) | (1));
regmap_write(spifc->regmap, SPIFC_USER_DBUF_ADDR, 0);
}
static int meson_spifc_probe(struct platform_device *pdev)
{
#ifdef CONFIG_MTD_SPI_NOR
struct device_node *np;
#endif
struct spi_master *master = 0;
struct meson_spifc *spifc;
struct resource *res;
void __iomem *base;
int ret = 0;
spifc = devm_kzalloc(&pdev->dev, sizeof(*spifc), GFP_KERNEL);
if (!spifc)
return -ENOMEM;
spifc->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(spifc->dev, res);
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(spifc->dev, NULL);
if (IS_ERR(spifc->clk)) {
dev_err(spifc->dev, "missing clock\n");
ret = PTR_ERR(spifc->clk);
goto out_err;
}
ret = clk_prepare_enable(spifc->clk);
if (ret) {
dev_err(spifc->dev, "can't prepare clock\n");
goto out_err;
}
platform_set_drvdata(pdev, spifc);
meson_spifc_hw_init(spifc);
#ifdef CONFIG_MTD_SPI_NOR
np = of_get_next_available_child(spifc->dev->of_node, NULL);
if (np && of_device_is_compatible(np, "jedec,spi-nor")) {
mutex_init(&spifc->lock);
spifc->nor = meson_snor_init(spifc, np);
}
if (spifc->nor) {
return 0;
} else {
mutex_destroy(&spifc->lock);
dev_warn(spifc->dev, "no snor on spifc bus\n");
}
#endif
pm_runtime_set_autosuspend_delay(spifc->dev, 500);
pm_runtime_use_autosuspend(spifc->dev);
pm_runtime_enable(spifc->dev);
master = spi_alloc_master(&pdev->dev, 0);
if (!master) {
ret = -ENOMEM;
goto out_clk;
}
spifc->master = master;
spi_master_set_devdata(master, spifc);
master->num_chipselect = 1;
master->dev.of_node = pdev->dev.of_node;
master->bits_per_word_mask = SPI_BPW_MASK(8);
master->auto_runtime_pm = true;
master->transfer_one = meson_spifc_transfer_one;
master->min_speed_hz = 1000000;
master->max_speed_hz = 200000000;
ret = devm_spi_register_master(spifc->dev, master);
if (ret) {
dev_err(spifc->dev, "failed to register spi master\n");
goto out_clk;
}
return 0;
out_clk:
clk_disable_unprepare(spifc->clk);
out_err:
if (master)
spi_master_put(master);
devm_kfree(&pdev->dev, spifc);
return ret;
}
static int meson_spifc_remove(struct platform_device *pdev)
{
struct meson_spifc *spifc = platform_get_drvdata(pdev);
pm_runtime_get_sync(&pdev->dev);
clk_disable_unprepare(spifc->clk);
#ifdef CONFIG_MTD_SPI_NOR
mutex_destroy(&spifc->lock);
#endif
pm_runtime_disable(&pdev->dev);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int meson_spifc_suspend(struct device *dev)
{
struct meson_spifc *spifc = dev_get_drvdata(dev);
int ret = 0;
if (spifc->master) {
ret = spi_master_suspend(spifc->master);
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 meson_spifc *spifc = dev_get_drvdata(dev);
int ret = 0;
if (!pm_runtime_suspended(dev)) {
ret = clk_prepare_enable(spifc->clk);
if (ret)
return ret;
}
meson_spifc_hw_init(spifc);
if (spifc->master) {
ret = spi_master_resume(spifc->master);
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 meson_spifc *spifc = dev_get_drvdata(dev);
clk_disable_unprepare(spifc->clk);
return 0;
}
static int meson_spifc_runtime_resume(struct device *dev)
{
struct meson_spifc *spifc = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(spifc->clk);
meson_spifc_hw_init(spifc);
return ret;
}
#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,meson-a1-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-v2",
.of_match_table = of_match_ptr(meson_spifc_dt_match),
.pm = &meson_spifc_pm_ops,
},
};
module_platform_driver(meson_spifc_driver);
MODULE_AUTHOR("Amlogic R&D");
MODULE_DESCRIPTION("Amlogic Meson SPIFC V2 driver");
MODULE_LICENSE("GPL v2");