845 lines
24 KiB
C
845 lines
24 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2025 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/types.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/regmap.h>
|
|
#ifdef CONFIG_SPICC_TEST
|
|
#include <linux/amlogic/aml_spi.h>
|
|
#include "spicc_test.h"
|
|
#endif
|
|
|
|
/* Register Map */
|
|
#define SPISG_REG_CFG_READY 0x00
|
|
#define CFG_SPI_READY BIT(0)
|
|
#define CFG_TEE_OPEN BIT(1)
|
|
#define CFG_JOIN_RX BIT(2)
|
|
#define CFG_JOIN_TX BIT(3)
|
|
#define CFG_THRESH_RX GENMASK(12, 4)
|
|
#define CFG_THRESH_TX GENMASK(21, 13)
|
|
#define CFG_DMA BIT(22)
|
|
|
|
#define SPISG_REG_CFG_SPI 0x04
|
|
#define CFG_BUS64_EN BIT(0)
|
|
#define CFG_SLAVE_EN BIT(1)
|
|
#define CFG_SLAVE_SELECT GENMASK(3, 2)
|
|
#define CFG_SFLASH_WP BIT(4)
|
|
#define CFG_SFLASH_HD BIT(5)
|
|
/* start on vsync rising */
|
|
#define CFG_HW_POS BIT(6)
|
|
/* start on vsync falling */
|
|
#define CFG_HW_NEG BIT(7)
|
|
#define CFG_WORD_GAP GENMASK(13, 8)
|
|
#define CFG_MO_IDLE_OUTPUT GENMASK(15, 14)
|
|
/* cs hold time in sclk */
|
|
#define CFG_CS_HOLD GENMASK(23, 16)
|
|
#define CFG_CS_SETUP GENMASK(31, 24)
|
|
|
|
#define SPISG_REG_CFG_START 0x08
|
|
#define CFG_BLOCK_NUM GENMASK(22, 0)
|
|
#define CFG_DATA_COMMAND BIT(23)
|
|
#define CFG_OP_MODE GENMASK(25, 24)
|
|
#define CFG_RXD_MODE GENMASK(27, 26)
|
|
#define CFG_TXD_MODE GENMASK(29, 28)
|
|
#define CFG_EOC BIT(30)
|
|
#define CFG_PEND BIT(31)
|
|
|
|
#define SPISG_REG_CFG_BUS 0x0C
|
|
#define CFG_CLK_DIV GENMASK(11, 0)
|
|
#define CLK_DIV_WIDTH 12
|
|
#define CFG_RX_TUNING GENMASK(15, 12)
|
|
#define CFG_TX_TUNING GENMASK(19, 16)
|
|
#define CFG_LANE GENMASK(21, 20)
|
|
#define CFG_HALF_DUPLEX BIT(22)
|
|
#define CFG_DC_MODE BIT(23)
|
|
#define CFG_NULL_CTL BIT(24)
|
|
#define CFG_DUMMY_CTL BIT(25)
|
|
#define CFG_READ_TURN GENMASK(28, 26)
|
|
#define CFG_KEEP_SS BIT(29)
|
|
#define CFG_CPHA BIT(30)
|
|
#define CFG_CPOL BIT(31)
|
|
|
|
#define SPISG_REG_TX_RD_ADDR 0x10
|
|
#define SPISG_REG_TX_WR_ADDR 0x14
|
|
#define SPISG_REG_RX_RD_ADDR 0x18
|
|
#define SPISG_REG_RX_WR_ADDR 0x1C
|
|
|
|
#define SPISG_REG_TX_FIFO 0x20
|
|
#define TX_FIFO_DATA GENMASK(7, 0)
|
|
#define TX_FIFO_FULL BIT(8)
|
|
#define TX_FIFO_EMPTY BIT(9)
|
|
#define TX_FIFO_APB_ERR BIT(10)
|
|
#define TX_FIFO_COUNT GENMASK(19, 11)
|
|
|
|
#define SPISG_REG_RX_FIFO 0x24
|
|
#define RX_FIFO_DATA GENMASK(7, 0)
|
|
#define RX_FIFO_FULL BIT(8)
|
|
#define RX_FIFO_EMPTY BIT(9)
|
|
#define RX_FIFO_APB_ERR BIT(10)
|
|
#define RX_FIFO_COUNT GENMASK(19, 11)
|
|
|
|
#define SPISG_REG_SHAKE 0x38
|
|
#define SPISG_REG_IRQ_STS 0x30
|
|
#define SPISG_REG_IRQ_ENABLE 0x34
|
|
|
|
#define IRQ_SPI_READY BIT(0)
|
|
#define IRQ_APB_RX_EMPTY BIT(1)
|
|
#define IRQ_APB_RX_FULL BIT(2)
|
|
#define IRQ_APB_TX_EMPTY BIT(3)
|
|
#define IRQ_APB_TX_FULL BIT(4)
|
|
#define IRQ_RX_FIFO_IRQ BIT(5)
|
|
#define IRQ_TX_FIFO_IRQ BIT(6)
|
|
#define IRQ_PHY_DONE BIT(7)
|
|
#define IRQ_TASK_DONE BIT(8)
|
|
#define IRQ_ALL_DONE BIT(9)
|
|
|
|
#define SPISG_MAX_REG 0x40
|
|
|
|
#define SPISG_BLOCK_MAX 0x800000
|
|
#define SPISG_FIFO_SIZE 128
|
|
|
|
#define SPISG_OP_MODE_WRITE_CMD 0
|
|
#define SPISG_OP_MODE_READ_STS 1
|
|
#define SPISG_OP_MODE_WRITE 2
|
|
#define SPISG_OP_MODE_READ 3
|
|
|
|
#define SPISG_DATA_MODE_NONE 0
|
|
#define SPISG_DATA_MODE_PIO 1
|
|
#define SPISG_DATA_MODE_MEM 2
|
|
#define SPISG_DATA_MODE_SG 3
|
|
|
|
#define SPISG_CLK_DIV_MAX 4096
|
|
/* recommended by specification */
|
|
#define SPISG_CLK_DIV_MIN 5
|
|
#define DIV_NUM (SPISG_CLK_DIV_MAX - SPISG_CLK_DIV_MIN + 1)
|
|
|
|
#define SPISG_PCLK_RATE_MIN 24000000
|
|
|
|
#define SPISG_SINGLE_SPI 0
|
|
#define SPISG_DUAL_SPI 1
|
|
#define SPISG_QUAD_SPI 2
|
|
|
|
struct spisg_device {
|
|
struct spi_controller *controller;
|
|
struct platform_device *pdev;
|
|
struct regmap *map;
|
|
struct clk *pclk;
|
|
struct clk *sclk;
|
|
struct clk_div_table *tbl;
|
|
u32 status;
|
|
u32 speed_hz;
|
|
u32 effective_speed_hz;
|
|
u32 cfg_spi;
|
|
u32 cfg_start;
|
|
u32 cfg_bus;
|
|
struct spi_device *spi;
|
|
struct spi_message *msg;
|
|
u8 *tx_buf;
|
|
u8 *rx_buf;
|
|
int tx_remain;
|
|
int rx_remain;
|
|
bool keep_ss;
|
|
int cs_hold_in_sclk;
|
|
int fifo_size;
|
|
};
|
|
|
|
#ifdef CONFIG_SPICC_TEST
|
|
static unsigned int aml_spisg_flags;
|
|
module_param(aml_spisg_flags, uint, 0644);
|
|
MODULE_PARM_DESC(aml_spisg_flags, "aml_spisg_flags");
|
|
#define dump_en() (aml_spisg_flags & BIT(0))
|
|
#endif
|
|
|
|
#ifdef CONFIG_SPICC_TEST
|
|
static void aml_spisg_dump(struct spisg_device *spisg)
|
|
{
|
|
unsigned char *op_mode_name[] = {"write_cmd", "read_sts", "write", "read"};
|
|
unsigned char *data_mode_name[] = {"none", "pio", "mem", "sg"};
|
|
u32 val;
|
|
|
|
if (!dump_en())
|
|
return;
|
|
|
|
regmap_read(spisg->map, SPISG_REG_CFG_READY, &val);
|
|
pr_info("cfg_ready = 0x%08x\n", val);
|
|
pr_info("\tspi_ready[0] %d\n", (u32)FIELD_GET(CFG_SPI_READY, val));
|
|
pr_info("\ttee_open[1] %d\n", (u32)FIELD_GET(CFG_TEE_OPEN, val));
|
|
pr_info("\tjoin_rx[2] %d\n", (u32)FIELD_GET(CFG_JOIN_RX, val));
|
|
pr_info("\tjoin_tx[3] %d\n", (u32)FIELD_GET(CFG_JOIN_TX, val));
|
|
pr_info("\tthres_rx[12:4] %d\n", (u32)FIELD_GET(CFG_THRESH_RX, val));
|
|
pr_info("\tthres_tx[21:13] %d\n", (u32)FIELD_GET(CFG_THRESH_TX, val));
|
|
pr_info("\tdma[22] %d\n", (u32)FIELD_GET(CFG_DMA, val));
|
|
|
|
val = spisg->cfg_spi;
|
|
pr_info("cfg_spi = 0x%08x\n", val);
|
|
pr_info("\tbus64_en[0] %d\n", (u32)FIELD_GET(CFG_BUS64_EN, val));
|
|
pr_info("\tslave_en[1] %d\n", (u32)FIELD_GET(CFG_SLAVE_EN, val));
|
|
pr_info("\tss[3:2] %d\n", (u32)FIELD_GET(CFG_SLAVE_SELECT, val));
|
|
pr_info("\tflash_wp_pin_en[4] %d\n", (u32)FIELD_GET(CFG_SFLASH_WP, val));
|
|
pr_info("\tflash_hold_pin_en[5] %d\n", (u32)FIELD_GET(CFG_SFLASH_HD, val));
|
|
pr_info("\thw_pos[6] %d\n", (u32)FIELD_GET(CFG_HW_POS, val));
|
|
pr_info("\thw_neg[7] %d\n", (u32)FIELD_GET(CFG_HW_NEG, val));
|
|
pr_info("\tword_gap[13:8] %d\n", (u32)FIELD_GET(CFG_WORD_GAP, val));
|
|
pr_info("\tmoso_idle_level[15:14] %d\n", (u32)FIELD_GET(CFG_MO_IDLE_OUTPUT, val));
|
|
pr_info("\tcs_hold[23:16] %d\n", (u32)FIELD_GET(CFG_CS_HOLD, val));
|
|
pr_info("\tcs_setup_extend[31:24] %d\n", (u32)FIELD_GET(CFG_CS_SETUP, val));
|
|
|
|
val = spisg->cfg_start;
|
|
pr_info("cfg_start = 0x%08x\n", val);
|
|
pr_info("\tblock_num[22:0] %d\n", (u32)FIELD_GET(CFG_BLOCK_NUM, val));
|
|
pr_info("\tdc_level[23] %d\n", (u32)FIELD_GET(CFG_DATA_COMMAND, val));
|
|
pr_info("\top_mode[25:24] %s\n", op_mode_name[FIELD_GET(CFG_OP_MODE, val)]);
|
|
pr_info("\trx_data_mode[27:26] %s\n", data_mode_name[FIELD_GET(CFG_RXD_MODE, val)]);
|
|
pr_info("\ttx_data_mode[29:28] %s\n", data_mode_name[FIELD_GET(CFG_TXD_MODE, val)]);
|
|
pr_info("\teoc[30] %d\n", (u32)FIELD_GET(CFG_EOC, val));
|
|
pr_info("\tpending[31] %d\n", (u32)FIELD_GET(CFG_PEND, val));
|
|
|
|
val = spisg->cfg_bus;
|
|
pr_info("cfg_bus = 0x%08x\n", val);
|
|
pr_info("\tclk_div[11:0] %d\n", (u32)FIELD_GET(CFG_CLK_DIV, val));
|
|
pr_info("\trx_tuning[15:12] %d\n", (u32)FIELD_GET(CFG_RX_TUNING, val));
|
|
pr_info("\ttx_tuning[19:16] %d\n", (u32)FIELD_GET(CFG_TX_TUNING, val));
|
|
pr_info("\tlane[21:20] %d\n", (u32)FIELD_GET(CFG_LANE, val));
|
|
pr_info("\thalf_duplex_en[22] %d\n", (u32)FIELD_GET(CFG_HALF_DUPLEX, val));
|
|
pr_info("\tdc_mode[23] %d\n", (u32)FIELD_GET(CFG_DC_MODE, val));
|
|
pr_info("\tnull_ctl[24] %d\n", (u32)FIELD_GET(CFG_NULL_CTL, val));
|
|
pr_info("\tdummy_ctl[25] %d\n", (u32)FIELD_GET(CFG_DUMMY_CTL, val));
|
|
pr_info("\tread_turn_around[28:26] %d\n", (u32)FIELD_GET(CFG_READ_TURN, val));
|
|
pr_info("\tkeep_ss[29] %d\n", (u32)FIELD_GET(CFG_KEEP_SS, val));
|
|
pr_info("\tcpha[30] %d\n", (u32)FIELD_GET(CFG_CPHA, val));
|
|
pr_info("\tcpol[31] %d\n", (u32)FIELD_GET(CFG_CPOL, val));
|
|
}
|
|
#endif
|
|
|
|
static int spi_delay_to_sclk(u32 slck_speed_hz, struct spi_delay *delay)
|
|
{
|
|
s32 ns;
|
|
|
|
if (!delay)
|
|
return 0;
|
|
|
|
if (delay->unit == SPI_DELAY_UNIT_SCK)
|
|
return delay->value;
|
|
|
|
ns = spi_delay_to_ns(delay, NULL);
|
|
if (ns < 0)
|
|
return 0;
|
|
|
|
return DIV_ROUND_UP_ULL((u64)slck_speed_hz * ns, NSEC_PER_SEC);
|
|
}
|
|
|
|
static inline u32 aml_spisg_sem_down_read(struct spisg_device *spisg)
|
|
{
|
|
u32 ret;
|
|
|
|
regmap_read(spisg->map, SPISG_REG_CFG_READY, &ret);
|
|
if (ret & CFG_SPI_READY)
|
|
regmap_write(spisg->map, SPISG_REG_CFG_READY, ret & ~CFG_SPI_READY);
|
|
|
|
return (ret & CFG_SPI_READY) || spisg->keep_ss;
|
|
}
|
|
|
|
static inline void aml_spisg_sem_up_write(struct spisg_device *spisg)
|
|
{
|
|
regmap_update_bits(spisg->map, SPISG_REG_CFG_READY, CFG_SPI_READY, CFG_SPI_READY);
|
|
}
|
|
|
|
static int aml_spisg_set_speed(struct spisg_device *spisg, uint speed_hz)
|
|
{
|
|
u32 cfg_bus;
|
|
|
|
if (!speed_hz || speed_hz == spisg->speed_hz)
|
|
return 0;
|
|
|
|
spisg->speed_hz = speed_hz;
|
|
clk_set_rate(spisg->sclk, speed_hz);
|
|
/* Store the div for the descriptor mode */
|
|
regmap_read(spisg->map, SPISG_REG_CFG_BUS, &cfg_bus);
|
|
spisg->cfg_bus &= ~CFG_CLK_DIV;
|
|
spisg->cfg_bus |= cfg_bus & CFG_CLK_DIV;
|
|
spisg->effective_speed_hz = clk_get_rate(spisg->sclk);
|
|
dev_dbg(&spisg->pdev->dev,
|
|
"desired speed %dHz, effective speed %dHz\n",
|
|
speed_hz, spisg->effective_speed_hz);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool aml_spisg_can_dma(struct spi_controller *ctlr,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *xfer)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static int nbits_to_lane[] = {
|
|
SPISG_SINGLE_SPI,
|
|
SPISG_SINGLE_SPI,
|
|
SPISG_DUAL_SPI,
|
|
-EINVAL,
|
|
SPISG_QUAD_SPI
|
|
};
|
|
|
|
static int aml_spisg_setup_transfer(struct spisg_device *spisg,
|
|
struct spi_transfer *xfer)
|
|
{
|
|
#ifdef CONFIG_SPICC_TEST
|
|
struct spicc_controller_data *cdata = spisg->spi->controller_data;
|
|
#endif
|
|
u32 val;
|
|
|
|
/* Setup transfer parameters */
|
|
spisg->tx_buf = (u8 *)xfer->tx_buf;
|
|
spisg->rx_buf = (u8 *)xfer->rx_buf;
|
|
spisg->tx_remain = xfer->len;
|
|
spisg->rx_remain = xfer->len;
|
|
|
|
regmap_read(spisg->map, SPISG_REG_CFG_READY, &val);
|
|
val &= ~(CFG_JOIN_TX | CFG_JOIN_RX);
|
|
spisg->fifo_size = SPISG_FIFO_SIZE;
|
|
if (xfer->len > SPISG_FIFO_SIZE) {
|
|
if (!xfer->tx_buf) {
|
|
val |= CFG_JOIN_RX;
|
|
spisg->fifo_size += SPISG_FIFO_SIZE;
|
|
}
|
|
if (!xfer->rx_buf) {
|
|
val |= CFG_JOIN_TX;
|
|
spisg->fifo_size += SPISG_FIFO_SIZE;
|
|
}
|
|
}
|
|
regmap_write(spisg->map, SPISG_REG_CFG_READY, val);
|
|
|
|
regmap_write(spisg->map, SPISG_REG_TX_RD_ADDR, 0);
|
|
regmap_write(spisg->map, SPISG_REG_TX_WR_ADDR, 0);
|
|
regmap_write(spisg->map, SPISG_REG_RX_RD_ADDR, 0);
|
|
regmap_write(spisg->map, SPISG_REG_RX_WR_ADDR, 0);
|
|
|
|
aml_spisg_set_speed(spisg, xfer->speed_hz);
|
|
xfer->effective_speed_hz = spisg->effective_speed_hz;
|
|
|
|
/* Setup cs_setup for the first burst of the first xfer */
|
|
spisg->cfg_spi &= ~CFG_CS_SETUP;
|
|
if (list_is_first(&xfer->transfer_list, &spisg->msg->transfers))
|
|
spisg->cfg_spi |= FIELD_PREP(CFG_CS_SETUP, min_t(int, FIELD_MAX(CFG_CS_SETUP),
|
|
spi_delay_to_sclk(xfer->effective_speed_hz, &spisg->spi->cs_setup)));
|
|
|
|
/* Keep ss except for the last burst */
|
|
spisg->cfg_bus |= CFG_KEEP_SS;
|
|
/* No cs_hold except for the last burst */
|
|
spisg->cfg_spi &= ~CFG_CS_HOLD;
|
|
|
|
/* Save the keep_ss and cs_hold for the last burst */
|
|
if (list_is_last(&xfer->transfer_list, &spisg->msg->transfers)) {
|
|
spisg->keep_ss = xfer->cs_change;
|
|
spisg->cs_hold_in_sclk = spi_delay_to_sclk(xfer->effective_speed_hz,
|
|
&spisg->spi->cs_hold);
|
|
} else {
|
|
spisg->keep_ss = !xfer->cs_change;
|
|
spisg->cs_hold_in_sclk = 0;
|
|
}
|
|
|
|
spisg->cfg_bus &= ~(CFG_LANE | CFG_DC_MODE);
|
|
spisg->cfg_start &= ~(CFG_OP_MODE | CFG_TXD_MODE | CFG_RXD_MODE | CFG_DATA_COMMAND);
|
|
if (xfer->tx_buf) {
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_LANE, nbits_to_lane[xfer->tx_nbits]);
|
|
spisg->cfg_start |= FIELD_PREP(CFG_OP_MODE, SPISG_OP_MODE_WRITE);
|
|
spisg->cfg_start |= FIELD_PREP(CFG_TXD_MODE, SPISG_DATA_MODE_PIO);
|
|
}
|
|
if (xfer->rx_buf) {
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_LANE, nbits_to_lane[xfer->rx_nbits]);
|
|
spisg->cfg_start |= FIELD_PREP(CFG_OP_MODE, SPISG_OP_MODE_READ);
|
|
spisg->cfg_start |= FIELD_PREP(CFG_RXD_MODE, SPISG_DATA_MODE_PIO);
|
|
}
|
|
|
|
#ifdef CONFIG_SPICC_TEST
|
|
if (cdata->dc_mode) {
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_DC_MODE, cdata->dc_mode - 1);
|
|
spisg->cfg_start |= FIELD_PREP(CFG_DATA_COMMAND, cdata->dc_level);
|
|
spisg->cfg_start &= ~CFG_OP_MODE;
|
|
if (xfer->tx_buf)
|
|
spisg->cfg_start |= FIELD_PREP(CFG_OP_MODE, SPISG_OP_MODE_WRITE_CMD);
|
|
if (xfer->rx_buf)
|
|
spisg->cfg_start |= FIELD_PREP(CFG_OP_MODE, SPISG_OP_MODE_READ_STS);
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_spisg_fifo_tx(struct spisg_device *spisg)
|
|
{
|
|
u32 fifo;
|
|
int tx_cnt;
|
|
|
|
if (!spisg->tx_buf) {
|
|
tx_cnt = min_t(int, spisg->tx_remain, spisg->fifo_size);
|
|
spisg->tx_remain -= tx_cnt;
|
|
} else {
|
|
tx_cnt = 0;
|
|
while (spisg->tx_remain) {
|
|
regmap_read(spisg->map, SPISG_REG_TX_FIFO, &fifo);
|
|
if (fifo & TX_FIFO_FULL)
|
|
break;
|
|
fifo &= ~TX_FIFO_DATA;
|
|
fifo |= *spisg->tx_buf++;
|
|
regmap_write(spisg->map, SPISG_REG_TX_FIFO, fifo);
|
|
spisg->tx_remain--;
|
|
tx_cnt++;
|
|
}
|
|
}
|
|
|
|
spisg->cfg_start &= ~CFG_BLOCK_NUM;
|
|
spisg->cfg_start |= FIELD_PREP(CFG_BLOCK_NUM, tx_cnt);
|
|
|
|
return tx_cnt;
|
|
}
|
|
|
|
static int aml_spisg_fifo_rx(struct spisg_device *spisg)
|
|
{
|
|
u32 fifo;
|
|
int rx_cnt;
|
|
|
|
if (!spisg->rx_buf) {
|
|
rx_cnt = min_t(int, spisg->rx_remain, spisg->fifo_size);
|
|
spisg->rx_remain -= rx_cnt;
|
|
} else {
|
|
rx_cnt = 0;
|
|
while (spisg->rx_remain) {
|
|
regmap_read(spisg->map, SPISG_REG_RX_FIFO, &fifo);
|
|
if (fifo & RX_FIFO_EMPTY)
|
|
break;
|
|
*spisg->rx_buf++ = FIELD_GET(RX_FIFO_DATA, fifo);
|
|
spisg->rx_remain--;
|
|
rx_cnt++;
|
|
}
|
|
}
|
|
|
|
return rx_cnt;
|
|
}
|
|
|
|
static void aml_spisg_pending(struct spisg_device *spisg,
|
|
bool irq_en)
|
|
{
|
|
/* Setup keep_ss and cs_hold for the last burst */
|
|
if (!spisg->tx_remain) {
|
|
if (spisg->keep_ss)
|
|
spisg->cfg_bus |= CFG_KEEP_SS;
|
|
else
|
|
spisg->cfg_bus &= ~CFG_KEEP_SS;
|
|
spisg->cfg_spi &= ~CFG_CS_HOLD;
|
|
spisg->cfg_spi |= FIELD_PREP(CFG_CS_HOLD, min_t(int,
|
|
FIELD_MAX(CFG_CS_HOLD), spisg->cs_hold_in_sclk));
|
|
}
|
|
|
|
#ifdef CONFIG_SPICC_TEST
|
|
aml_spisg_dump(spisg);
|
|
#endif
|
|
|
|
regmap_write(spisg->map, SPISG_REG_CFG_SPI, spisg->cfg_spi);
|
|
regmap_write(spisg->map, SPISG_REG_CFG_BUS, spisg->cfg_bus);
|
|
regmap_write(spisg->map, SPISG_REG_IRQ_ENABLE, irq_en ? IRQ_PHY_DONE : 0);
|
|
regmap_write(spisg->map, SPISG_REG_CFG_START, spisg->cfg_start | CFG_PEND);
|
|
}
|
|
|
|
static irqreturn_t aml_spisg_irq(int irq, void *data)
|
|
{
|
|
struct spisg_device *spisg = (void *)data;
|
|
u32 sts;
|
|
|
|
regmap_read(spisg->map, SPISG_REG_IRQ_STS, &sts);
|
|
regmap_write(spisg->map, SPISG_REG_IRQ_STS, sts);
|
|
if (!(sts & IRQ_PHY_DONE))
|
|
return IRQ_NONE;
|
|
|
|
aml_spisg_fifo_rx(spisg);
|
|
if (spisg->tx_remain) {
|
|
/* clear cs_setup for the next burst */
|
|
spisg->cfg_spi &= ~CFG_CS_SETUP;
|
|
aml_spisg_fifo_tx(spisg);
|
|
aml_spisg_pending(spisg, true);
|
|
} else if (!spisg->rx_remain) {
|
|
if (!spisg->keep_ss)
|
|
aml_spisg_sem_up_write(spisg);
|
|
spi_finalize_current_transfer(spisg->controller);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int aml_spisg_transfer_one(struct spi_controller *ctlr,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *xfer)
|
|
{
|
|
struct spisg_device *spisg = spi_controller_get_devdata(ctlr);
|
|
struct device *dev = &spisg->pdev->dev;
|
|
|
|
if (!aml_spisg_sem_down_read(spisg)) {
|
|
dev_err(dev, "controller busy\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
spisg->spi = spi;
|
|
aml_spisg_setup_transfer(spisg, xfer);
|
|
aml_spisg_fifo_tx(spisg);
|
|
aml_spisg_pending(spisg, true);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int aml_spisg_prepare_message(struct spi_controller *ctlr,
|
|
struct spi_message *message)
|
|
{
|
|
struct spisg_device *spisg = spi_controller_get_devdata(ctlr);
|
|
struct spi_device *spi = message->spi;
|
|
#ifdef CONFIG_SPICC_TEST
|
|
struct spicc_controller_data *cdata = spi->controller_data;
|
|
#endif
|
|
|
|
if (spi->bits_per_word != 8) {
|
|
dev_err(&spisg->pdev->dev, "invalid wordlen %d\n", spi->bits_per_word);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spisg->msg = message;
|
|
spisg->cfg_spi &= ~CFG_SLAVE_SELECT;
|
|
spisg->cfg_spi |= FIELD_PREP(CFG_SLAVE_SELECT, spi_get_chipselect(spi, 0));
|
|
|
|
spisg->cfg_bus &= ~(CFG_CPOL | CFG_CPHA | CFG_HALF_DUPLEX);
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_CPOL, !!(spi->mode & SPI_CPOL)) |
|
|
FIELD_PREP(CFG_CPHA, !!(spi->mode & SPI_CPHA)) |
|
|
FIELD_PREP(CFG_HALF_DUPLEX, !!(spi->mode & SPI_3WIRE));
|
|
|
|
#ifdef CONFIG_SPICC_TEST
|
|
if (cdata && cdata->timing_en) {
|
|
spisg->cfg_bus &= ~(CFG_TX_TUNING | CFG_RX_TUNING | CFG_DUMMY_CTL | CFG_READ_TURN);
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_TX_TUNING, cdata->tx_tuning);
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_RX_TUNING, cdata->rx_tuning);
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_DUMMY_CTL, cdata->dummy_ctl);
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_READ_TURN, cdata->read_turn_around);
|
|
spisg->cfg_spi &= ~(CFG_WORD_GAP | CFG_MO_IDLE_OUTPUT);
|
|
spisg->cfg_spi |= FIELD_PREP(CFG_WORD_GAP, cdata->word_gap);
|
|
spisg->cfg_spi |= FIELD_PREP(CFG_MO_IDLE_OUTPUT, cdata->mosi_idle_level);
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_spisg_setup(struct spi_device *spi)
|
|
{
|
|
if (!spi->controller_state)
|
|
spi->controller_state = spi_controller_get_devdata(spi->controller);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aml_spisg_cleanup(struct spi_device *spi)
|
|
{
|
|
spi->controller_state = NULL;
|
|
}
|
|
|
|
static int aml_spisg_target_abort(struct spi_controller *ctlr)
|
|
{
|
|
struct spisg_device *spisg = spi_controller_get_devdata(ctlr);
|
|
|
|
regmap_write(spisg->map, SPISG_REG_IRQ_ENABLE, 0);
|
|
regmap_write(spisg->map, SPISG_REG_CFG_START, spisg->cfg_start & ~CFG_PEND);
|
|
aml_spisg_sem_up_write(spisg);
|
|
spisg->keep_ss = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aml_spisg_handle_err(struct spi_controller *ctlr,
|
|
struct spi_message *message)
|
|
{
|
|
aml_spisg_target_abort(ctlr);
|
|
}
|
|
|
|
static int aml_spisg_clk_init(struct spisg_device *spisg, void __iomem *base)
|
|
{
|
|
struct device *dev = &spisg->pdev->dev;
|
|
struct clk_init_data init;
|
|
struct clk_divider *div;
|
|
struct clk_div_table *tbl;
|
|
char name[32];
|
|
int ret, i;
|
|
|
|
spisg->pclk = devm_clk_get_enabled(dev, "pclk");
|
|
if (IS_ERR(spisg->pclk)) {
|
|
dev_err(dev, "pclk clock request failed\n");
|
|
return PTR_ERR(spisg->pclk);
|
|
}
|
|
|
|
clk_set_min_rate(spisg->pclk, SPISG_PCLK_RATE_MIN);
|
|
|
|
clk_disable_unprepare(spisg->pclk);
|
|
|
|
tbl = devm_kcalloc(dev, (DIV_NUM + 1), sizeof(*tbl), GFP_KERNEL);
|
|
if (!tbl)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < DIV_NUM; i++) {
|
|
tbl[i].val = i + SPISG_CLK_DIV_MIN - 1;
|
|
tbl[i].div = i + SPISG_CLK_DIV_MIN;
|
|
}
|
|
spisg->tbl = tbl;
|
|
|
|
div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
|
|
if (!div)
|
|
return -ENOMEM;
|
|
|
|
div->flags = CLK_DIVIDER_ROUND_CLOSEST;
|
|
div->reg = base + SPISG_REG_CFG_BUS;
|
|
div->shift = __bf_shf(CFG_CLK_DIV);
|
|
div->width = CLK_DIV_WIDTH;
|
|
div->table = tbl;
|
|
|
|
/* Register value should not be outside of the table */
|
|
regmap_update_bits(spisg->map, SPISG_REG_CFG_BUS, CFG_CLK_DIV,
|
|
FIELD_PREP(CFG_CLK_DIV, SPISG_CLK_DIV_MIN - 1));
|
|
|
|
/* Register clk-divider */
|
|
snprintf(name, sizeof(name), "%s_div", dev_name(dev));
|
|
init.name = name;
|
|
init.ops = &clk_divider_ops;
|
|
init.flags = 0;
|
|
init.parent_data = &(const struct clk_parent_data) {
|
|
.fw_name = "pclk",
|
|
};
|
|
init.num_parents = 1;
|
|
div->hw.init = &init;
|
|
ret = devm_clk_hw_register(dev, &div->hw);
|
|
if (ret) {
|
|
dev_err(dev, "clock registration failed\n");
|
|
return ret;
|
|
}
|
|
|
|
spisg->sclk = devm_clk_hw_get_clk(dev, &div->hw, NULL);
|
|
if (IS_ERR(spisg->sclk)) {
|
|
dev_err(dev, "get clock failed\n");
|
|
return PTR_ERR(spisg->sclk);
|
|
}
|
|
|
|
clk_prepare_enable(spisg->sclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_SPICC_TEST
|
|
static DEVICE_ATTR_WO(test);
|
|
static DEVICE_ATTR_RW(testdev);
|
|
#endif
|
|
|
|
static int aml_spisg_probe(struct platform_device *pdev)
|
|
{
|
|
struct spi_controller *ctlr;
|
|
struct spisg_device *spisg;
|
|
struct device *dev = &pdev->dev;
|
|
void __iomem *base;
|
|
u8 val;
|
|
int ret, irq;
|
|
|
|
const struct regmap_config aml_regmap_config = {
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.max_register = SPISG_MAX_REG,
|
|
};
|
|
|
|
if (of_property_read_bool(dev->of_node, "spi-slave"))
|
|
ctlr = devm_spi_alloc_target(dev, sizeof(*spisg));
|
|
else
|
|
ctlr = devm_spi_alloc_host(dev, sizeof(*spisg));
|
|
if (!ctlr)
|
|
return -ENOMEM;
|
|
|
|
spisg = spi_controller_get_devdata(ctlr);
|
|
spisg->controller = ctlr;
|
|
|
|
spisg->pdev = pdev;
|
|
platform_set_drvdata(pdev, spisg);
|
|
|
|
base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(base))
|
|
return dev_err_probe(dev, PTR_ERR(base), "resource ioremap failed\n");
|
|
|
|
spisg->map = devm_regmap_init_mmio(dev, base, &aml_regmap_config);
|
|
if (IS_ERR(spisg->map))
|
|
return dev_err_probe(dev, PTR_ERR(spisg->map), "regmap init failed\n");
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
ret = device_reset_optional(dev);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "reset dev failed\n");
|
|
|
|
ret = aml_spisg_clk_init(spisg, base);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "clock init failed\n");
|
|
|
|
spisg->cfg_spi = 0;
|
|
spisg->cfg_start = 0;
|
|
spisg->cfg_bus = 0;
|
|
|
|
spisg->cfg_spi = FIELD_PREP(CFG_SFLASH_WP, 1) |
|
|
FIELD_PREP(CFG_SFLASH_HD, 1);
|
|
if (spi_controller_is_target(ctlr)) {
|
|
spisg->cfg_spi |= FIELD_PREP(CFG_SLAVE_EN, 1);
|
|
spisg->cfg_bus = FIELD_PREP(CFG_TX_TUNING, 0xf);
|
|
}
|
|
/* default pending */
|
|
spisg->cfg_start = FIELD_PREP(CFG_PEND, 1);
|
|
|
|
pm_runtime_set_active(&spisg->pdev->dev);
|
|
pm_runtime_enable(&spisg->pdev->dev);
|
|
pm_runtime_resume_and_get(&spisg->pdev->dev);
|
|
|
|
if (!of_property_read_u8(dev->of_node, "tx_tuning", &val)) {
|
|
spisg->cfg_bus &= ~CFG_TX_TUNING;
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_TX_TUNING, val);
|
|
}
|
|
|
|
if (!of_property_read_u8(dev->of_node, "rx_tuning", &val))
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_RX_TUNING, val);
|
|
|
|
if (!of_property_read_u8(dev->of_node, "dummy_ctl", &val))
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_DUMMY_CTL, val);
|
|
|
|
if (!of_property_read_u8(dev->of_node, "read_turn", &val))
|
|
spisg->cfg_bus |= FIELD_PREP(CFG_READ_TURN, val);
|
|
|
|
val = 1;
|
|
of_property_read_u8(dev->of_node, "word_gap", &val);
|
|
spisg->cfg_spi |= FIELD_PREP(CFG_WORD_GAP, val);
|
|
|
|
if (!of_property_read_u8(dev->of_node, "mo_idle_output", &val))
|
|
spisg->cfg_spi |= FIELD_PREP(CFG_MO_IDLE_OUTPUT, val);
|
|
|
|
ctlr->dev.of_node = pdev->dev.of_node;
|
|
ctlr->num_chipselect = 4;
|
|
ctlr->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST |
|
|
SPI_3WIRE | SPI_TX_QUAD | SPI_RX_QUAD;
|
|
ctlr->max_speed_hz = 1000 * 1000 * 100;
|
|
ctlr->min_speed_hz = 1000 * 10;
|
|
ctlr->setup = aml_spisg_setup;
|
|
ctlr->cleanup = aml_spisg_cleanup;
|
|
ctlr->prepare_message = aml_spisg_prepare_message;
|
|
ctlr->transfer_one = aml_spisg_transfer_one;
|
|
ctlr->target_abort = aml_spisg_target_abort;
|
|
ctlr->handle_err = aml_spisg_handle_err;
|
|
ctlr->can_dma = aml_spisg_can_dma;
|
|
ctlr->max_dma_len = SPISG_BLOCK_MAX;
|
|
ctlr->auto_runtime_pm = true;
|
|
|
|
dma_set_max_seg_size(&pdev->dev, SPISG_BLOCK_MAX);
|
|
|
|
ret = devm_request_irq(&pdev->dev, irq, aml_spisg_irq, 0, NULL, spisg);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "irq request failed\n");
|
|
goto out_clk;
|
|
}
|
|
|
|
ret = spi_register_controller(ctlr);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "spi controller registration failed\n");
|
|
goto out_clk;
|
|
}
|
|
|
|
pm_runtime_put(&spisg->pdev->dev);
|
|
#ifdef CONFIG_SPICC_TEST
|
|
device_create_file(&ctlr->dev, &dev_attr_test);
|
|
device_create_file(&ctlr->dev, &dev_attr_testdev);
|
|
#endif
|
|
|
|
return 0;
|
|
out_clk:
|
|
clk_disable_unprepare(spisg->pclk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void aml_spisg_remove(struct platform_device *pdev)
|
|
{
|
|
struct spisg_device *spisg = platform_get_drvdata(pdev);
|
|
|
|
spi_unregister_controller(spisg->controller);
|
|
|
|
if (!pm_runtime_suspended(&pdev->dev)) {
|
|
pinctrl_pm_select_sleep_state(&spisg->pdev->dev);
|
|
clk_disable_unprepare(spisg->pclk);
|
|
}
|
|
#ifdef CONFIG_SPICC_TEST
|
|
device_remove_file(&spisg->controller->dev, &dev_attr_test);
|
|
device_remove_file(&spisg->controller->dev, &dev_attr_testdev);
|
|
#endif
|
|
}
|
|
|
|
static int spisg_suspend_runtime(struct device *dev)
|
|
{
|
|
struct spisg_device *spisg = dev_get_drvdata(dev);
|
|
|
|
pinctrl_pm_select_sleep_state(&spisg->pdev->dev);
|
|
clk_disable_unprepare(spisg->sclk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spisg_resume_runtime(struct device *dev)
|
|
{
|
|
struct spisg_device *spisg = dev_get_drvdata(dev);
|
|
|
|
clk_prepare_enable(spisg->sclk);
|
|
pinctrl_pm_select_default_state(&spisg->pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops amlogic_spiao_pm_ops = {
|
|
.runtime_suspend = spisg_suspend_runtime,
|
|
.runtime_resume = spisg_resume_runtime,
|
|
};
|
|
|
|
static const struct of_device_id amlogic_spiao_of_match[] = {
|
|
{
|
|
.compatible = "amlogic,a9-spiao",
|
|
},
|
|
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, amlogic_spiao_of_match);
|
|
|
|
struct platform_driver amlogic_spiao_driver = {
|
|
.probe = aml_spisg_probe,
|
|
.remove = aml_spisg_remove,
|
|
.driver = {
|
|
.name = "amlogic-spiao",
|
|
.of_match_table = amlogic_spiao_of_match,
|
|
.pm = &amlogic_spiao_pm_ops,
|
|
},
|
|
};
|
|
|
|
#ifndef MODULE
|
|
module_platform_driver(amlogic_spiao_driver);
|
|
#endif
|
|
|
|
MODULE_DESCRIPTION("Amlogic SPI AO Controller driver");
|
|
MODULE_AUTHOR("Sunny Luo <[email protected]>");
|
|
MODULE_LICENSE("GPL");
|