1843 lines
50 KiB
C
1843 lines
50 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/amlogic/power_domain.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_domain.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/types.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <linux/amlogic/aml_spi.h>
|
|
#include <linux/pinctrl/devinfo.h>
|
|
#ifdef CONFIG_SPICC_TEST
|
|
#include "spicc_test.h"
|
|
#endif
|
|
|
|
#define spicc_info(fmt, args...) \
|
|
pr_info("[info]%s: " fmt, __func__, ## args)
|
|
|
|
#define spicc_err(fmt, args...) \
|
|
pr_info("[error]%s: " fmt, __func__, ## args)
|
|
|
|
#define spicc_warn(fmt, args...) \
|
|
pr_info("[warn]%s: " fmt, __func__, ## args)
|
|
|
|
//#define SPICC_DEBUG_EN
|
|
#ifdef SPICC_DEBUG_EN
|
|
#define spicc_dbg(fmt, args...) \
|
|
pr_info("[debug]%s: " fmt, __func__, ## args)
|
|
#else
|
|
#define spicc_dbg(fmt, args...)
|
|
#define meson_spicc_dump(fmt, args...)
|
|
#endif
|
|
|
|
/* Register Map */
|
|
#define SPICC_REG_CFG_READY 0x00
|
|
#define SPICC_REG_CFG_SPI 0x04
|
|
#define HW_POS BIT(6)
|
|
#define HW_NEG BIT(7)
|
|
#define SPICC_REG_CFG_START 0x08
|
|
#define SPICC_REG_CFG_BUS 0x0C
|
|
#define SPICC_REG_PIO_TX_DATA_L 0x10
|
|
#define SPICC_REG_PIO_TX_DATA_H 0x14
|
|
#define SPICC_REG_PIO_RX_DATA_L 0x18
|
|
#define SPICC_REG_PIO_RX_DATA_H 0x1C
|
|
#define SPICC_REG_MEM_TX_ADDR_L 0x10
|
|
#define SPICC_REG_MEM_TX_ADDR_H 0x14
|
|
#define SPICC_REG_MEM_RX_ADDR_L 0x18
|
|
#define SPICC_REG_MEM_RX_ADDR_H 0x1C
|
|
#define SPICC_REG_DESC_LIST_L 0x20
|
|
#define SPICC_REG_DESC_LIST_H 0x24
|
|
#define SPICC_DESC_PENDING BIT(31)
|
|
#define SPICC_REG_DESC_CURRENT_L 0x28
|
|
#define SPICC_REG_DESC_CURRENT_H 0x2c
|
|
#define SPICC_REG_IRQ_STS 0x30
|
|
#define SPICC_REG_IRQ_ENABLE 0x34
|
|
#define SPICC_RCH_DESC_EOC BIT(0)
|
|
#define SPICC_RCH_DESC_INVALID BIT(1)
|
|
#define SPICC_RCH_DESC_RESP BIT(2)
|
|
#define SPICC_RCH_DATA_RESP BIT(3)
|
|
#define SPICC_WCH_DESC_EOC BIT(4)
|
|
#define SPICC_WCH_DESC_INVALID BIT(5)
|
|
#define SPICC_WCH_DESC_RESP BIT(6)
|
|
#define SPICC_WCH_DATA_RESP BIT(7)
|
|
#define SPICC_DESC_ERR BIT(8)
|
|
#define SPICC_SPI_READY BIT(9)
|
|
#define SPICC_DESC_DONE BIT(10)
|
|
#define SPICC_DESC_CHAIN_DONE BIT(11)
|
|
#define SPICC_STATUS_ERROR \
|
|
(SPICC_RCH_DESC_INVALID | \
|
|
SPICC_RCH_DESC_RESP | \
|
|
SPICC_RCH_DATA_RESP | \
|
|
SPICC_RCH_DESC_EOC | \
|
|
SPICC_WCH_DESC_EOC | \
|
|
SPICC_WCH_DESC_INVALID | \
|
|
SPICC_WCH_DESC_RESP | \
|
|
SPICC_WCH_DATA_RESP | \
|
|
SPICC_SPI_READY | \
|
|
SPICC_DESC_ERR)
|
|
#define SPICC_INT_ERROR SPICC_STATUS_ERROR
|
|
#define SPICC_INT_ALL \
|
|
(SPICC_INT_ERROR | \
|
|
SPICC_DESC_DONE | \
|
|
SPICC_DESC_CHAIN_DONE)
|
|
|
|
struct spicc_sg_link {
|
|
u32 valid:1;
|
|
u32 eoc:1;
|
|
u32 irq:1;
|
|
u32 act:3;
|
|
u32 ring:1;
|
|
u32 rsv:1;
|
|
u32 len:24;
|
|
#define SPICC_SG_LEN_MAX 0x1000000
|
|
#define SPICC_SG_TX_LEN_MAX SPICC_SG_LEN_MAX
|
|
#define SPICC_SG_RX_LEN_MAX SPICC_SG_LEN_MAX
|
|
u32 addr;
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
u32 addr_h;
|
|
u32 rsv2;
|
|
#endif
|
|
};
|
|
|
|
struct spicc_descriptor_extra {
|
|
struct spicc_sg_link *tx_ccsg;
|
|
struct spicc_sg_link *rx_ccsg;
|
|
int tx_ccsg_len;
|
|
int rx_ccsg_len;
|
|
};
|
|
|
|
#define spicc_writel(_spicc, _val, _offset) \
|
|
writel(_val, (_spicc)->base + (_offset))
|
|
#define spicc_readl(_spicc, _offset) \
|
|
readl((_spicc)->base + (_offset))
|
|
|
|
#ifdef MESON_SPICC_HW_IF
|
|
static void dirspi_start(struct spi_device *spi);
|
|
static void dirspi_stop(struct spi_device *spi);
|
|
static int dirspi_async(struct spi_device *spi,
|
|
dma_addr_t tx_dma,
|
|
dma_addr_t rx_dma,
|
|
int len,
|
|
void (*complete)(void *context),
|
|
void *context);
|
|
static int dirspi_sync(struct spi_device *spi,
|
|
dma_addr_t tx_dma,
|
|
dma_addr_t rx_dma,
|
|
int len);
|
|
static int dirspi_dma_trig(struct spi_device *spi,
|
|
dma_addr_t tx_dma,
|
|
dma_addr_t rx_dma,
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
dma_addr_t tx_dma_pong,
|
|
dma_addr_t rx_dma_pong,
|
|
#endif
|
|
int len,
|
|
u8 src);
|
|
static int dirspi_dma_trig_start(struct spi_device *spi);
|
|
static int dirspi_dma_trig_stop(struct spi_device *spi);
|
|
static int dirspi_dma_trig_release(struct spi_device *spi);
|
|
static bool dmatrig_is_busy(struct spi_device *spi);
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
static u32 dmatrig_ping_or_pong(struct spi_device *spi);
|
|
#endif
|
|
#endif
|
|
|
|
static inline int spicc_sem_down_read(struct spicc_device *spicc)
|
|
{
|
|
int ret;
|
|
|
|
if (spicc->ready)
|
|
return 1;
|
|
|
|
ret = spicc_readl(spicc, SPICC_REG_CFG_READY);
|
|
if (ret) {
|
|
spicc_writel(spicc, 0, SPICC_REG_CFG_READY);
|
|
spicc->ready = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void spicc_sem_up_write(struct spicc_device *spicc)
|
|
{
|
|
if (!spicc->ready || spicc->keep_ready)
|
|
return;
|
|
|
|
spicc_writel(spicc, 1, SPICC_REG_CFG_READY);
|
|
spicc->ready = false;
|
|
}
|
|
|
|
static void spicc_set_cs(struct spi_device *spi, bool enable)
|
|
{
|
|
u8 idx;
|
|
bool activate = enable;
|
|
|
|
if (!spi || !spi_is_csgpiod(spi) || (spi->mode & SPI_NO_CS))
|
|
return;
|
|
|
|
if (!activate)
|
|
spi_delay_exec(&spi->cs_hold, NULL);
|
|
|
|
for (idx = 0; idx < SPI_CS_CNT_MAX; idx++)
|
|
if (spi_get_csgpiod(spi, idx) && (spi->cs_index_mask & BIT(idx)))
|
|
gpiod_set_value(spi_get_csgpiod(spi, idx), activate);
|
|
|
|
if (activate)
|
|
spi_delay_exec(&spi->cs_setup, NULL);
|
|
else
|
|
spi_delay_exec(&spi->cs_inactive, NULL);
|
|
}
|
|
|
|
static int spicc_set_speed(struct spicc_device *spicc, uint speed_hz)
|
|
{
|
|
u32 div;
|
|
|
|
if (!speed_hz || (speed_hz == spicc->speed_hz && spicc->pclk_rate))
|
|
return 0;
|
|
|
|
spicc->speed_hz = speed_hz;
|
|
clk_set_rate(spicc->sclk, speed_hz);
|
|
/* Store the div for the descriptor mode */
|
|
div = FIELD_GET(SPICC_CLK_DIV_MASK,
|
|
spicc_readl(spicc, SPICC_REG_CFG_BUS));
|
|
spicc->cfg_bus.b.clk_div = div;
|
|
spicc->actual_speed_hz = clk_get_rate(spicc->sclk);
|
|
spicc->pclk_rate = clk_get_rate(spicc->spi_clk);
|
|
if (spicc->data->cs_hold_ctrl_conflict_with_keep_ss)
|
|
spicc->cs_hold_min_in_pclk = 0;
|
|
else
|
|
spicc->cs_hold_min_in_pclk = DIV_ROUND_UP(spicc->pclk_rate, 20000000) + 1;
|
|
|
|
spicc_dbg("desired speed %dHz, actual speed %dHz, div=%d, pclk rate %d\n",
|
|
speed_hz, spicc->actual_speed_hz, div, spicc->pclk_rate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline unsigned long spicc_xfer_time_max(struct spicc_device *spicc,
|
|
int len)
|
|
{
|
|
unsigned long ms;
|
|
|
|
if (!spicc->actual_speed_hz)
|
|
return 200;
|
|
|
|
ms = (8 * len) / (spicc->actual_speed_hz / 1000);
|
|
ms += ms + 5; /* some tolerance */
|
|
|
|
return ms;
|
|
}
|
|
|
|
#ifdef SPICC_DEBUG_EN
|
|
static void meson_spicc_dump(struct spicc_device *spicc,
|
|
struct spicc_descriptor *desc,
|
|
struct spicc_descriptor_extra *exdesc,
|
|
int desc_num)
|
|
{
|
|
union spicc_cfg_spi *cfg_spi = &spicc->cfg_spi;
|
|
union spicc_cfg_start *cfg_start;
|
|
union spicc_cfg_bus *cfg_bus;
|
|
struct spicc_sg_link *ccsg;
|
|
int i, j, ccsg_num;
|
|
|
|
pr_info("cfg_spi = 0x%08x, descs num %d\n", cfg_spi->d32, desc_num);
|
|
pr_info("\tbus64_en[0] %d\n", cfg_spi->b.bus64_en);
|
|
pr_info("\tslave_en[1] %d\n", cfg_spi->b.slave_en);
|
|
pr_info("\tss[3:2] %d\n", cfg_spi->b.ss);
|
|
pr_info("\tflash_wp_pin_en[4] %d\n", cfg_spi->b.flash_wp_pin_en);
|
|
pr_info("\tflash_hold_pin_en[5] %d\n", cfg_spi->b.flash_hold_pin_en);
|
|
pr_info("\thw_pos[6] %d\n", cfg_spi->b.hw_pos);
|
|
pr_info("\thw_neg[7] %d\n", cfg_spi->b.hw_neg);
|
|
pr_info("\tword_gap[9:8] %d\n", cfg_spi->b.word_gap);
|
|
pr_info("\tmosi_idle_level[11:10] %d\n", cfg_spi->b.mosi_idle_level);
|
|
pr_info("\tcs_hold[26:12] %d\n", cfg_spi->b.cs_hold);
|
|
pr_info("\tcs_setup_extend[30:27] %d\n", cfg_spi->b.cs_setup_extend);
|
|
|
|
for (i = 0; i < desc_num; i++) {
|
|
cfg_start = &desc[i].cfg_start;
|
|
pr_info("desc[%d]:\n", i);
|
|
pr_info("tx_paddr = 0x%016llx\n", desc[i].tx_paddr);
|
|
pr_info("rx_paddr = 0x%016llx\n", desc[i].rx_paddr);
|
|
pr_info("cfg_start = 0x%08x\n", cfg_start->d32);
|
|
pr_info("\tblock_num[19:0] %d\n", cfg_start->b.block_num);
|
|
pr_info("\tblock_size[22:20] %d\n", cfg_start->b.block_size);
|
|
pr_info("\tdc_level[23] %d\n", cfg_start->b.dc_level);
|
|
pr_info("\top_mode 0:write_cmd, 1:read_sts, 2:write, 3:read\n");
|
|
pr_info("\top_mode[25:24] %d\n", cfg_start->b.op_mode);
|
|
pr_info("\tdata_mode 0:none, 1:pio, 2:mem, 3:sg\n");
|
|
pr_info("\trx_data_mode[27:26] %d\n", cfg_start->b.rx_data_mode);
|
|
pr_info("\ttx_data_mode[29:28] %d\n", cfg_start->b.tx_data_mode);
|
|
pr_info("\teoc[30] %d\n", cfg_start->b.eoc);
|
|
pr_info("\tpending[31] %d\n", cfg_start->b.pending);
|
|
|
|
cfg_bus = &desc[i].cfg_bus;
|
|
pr_info("cfg_bus = 0x%08x\n", cfg_bus->d32);
|
|
pr_info("\tclk_div[7:0] %d\n", cfg_bus->b.clk_div);
|
|
pr_info("\trx_tuning[11:8] %d\n", cfg_bus->b.rx_tuning);
|
|
pr_info("\ttx_tuning[15:12] %d\n", cfg_bus->b.tx_tuning);
|
|
pr_info("\tcs_setup[19:16] %d\n", cfg_bus->b.cs_setup);
|
|
pr_info("\tlane[21:20] %d\n", cfg_bus->b.lane);
|
|
pr_info("\thalf_duplex_en[22] %d\n", cfg_bus->b.half_duplex_en);
|
|
pr_info("\tlittle_endian_en[23] %d\n", cfg_bus->b.little_endian_en);
|
|
pr_info("\tdc_mode[24] %d (0:pin, 1:9bit)\n", cfg_bus->b.dc_mode);
|
|
pr_info("\tnull_ctl[25] %d\n", cfg_bus->b.null_ctl);
|
|
pr_info("\tdummy_ctl[26] %d\n", cfg_bus->b.dummy_ctl);
|
|
pr_info("\tread_turn_around[28:27] %d\n", cfg_bus->b.read_turn_around);
|
|
pr_info("\tkeep_ss[29] %d\n", cfg_bus->b.keep_ss);
|
|
pr_info("\tcpha[30] %d\n", cfg_bus->b.cpha);
|
|
pr_info("\tcpol[31] %d\n", cfg_bus->b.cpol);
|
|
|
|
if (!exdesc)
|
|
continue;
|
|
|
|
ccsg_num = exdesc->tx_ccsg_len / sizeof(struct spicc_sg_link);
|
|
ccsg = exdesc->tx_ccsg;
|
|
for (j = 0; j < ccsg_num; j++) {
|
|
pr_info("\t\ttxsg[%d]:\n", j);
|
|
pr_info("\t\tvalid = %d\n", ccsg->valid);
|
|
pr_info("\t\teoc = %d\n", ccsg->eoc);
|
|
pr_info("\t\tirq = %d\n", ccsg->irq);
|
|
pr_info("\t\tact = %d\n", ccsg->act);
|
|
pr_info("\t\tring = %d\n", ccsg->ring);
|
|
pr_info("\t\tlen = %d\n", ccsg->len);
|
|
pr_info("\t\taddr = 0x%08x\n", ccsg->addr);
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
pr_info("\t\taddr_h = 0x%08x\n", ccsg->addr_h);
|
|
#endif
|
|
ccsg++;
|
|
}
|
|
|
|
ccsg_num = exdesc->rx_ccsg_len / sizeof(struct spicc_sg_link);
|
|
ccsg = exdesc->rx_ccsg;
|
|
for (j = 0; j < ccsg_num; j++) {
|
|
pr_info("\t\trxsg[%d]:\n", j);
|
|
pr_info("\t\tvalid = %d\n", ccsg->valid);
|
|
pr_info("\t\teoc = %d\n", ccsg->eoc);
|
|
pr_info("\t\tirq = %d\n", ccsg->irq);
|
|
pr_info("\t\tact = %d\n", ccsg->act);
|
|
pr_info("\t\tring = %d\n", ccsg->ring);
|
|
pr_info("\t\tlen = %d\n", ccsg->len);
|
|
pr_info("\t\taddr = 0x%08x\n", ccsg->addr);
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
pr_info("\t\taddr_h = 0x%08x\n", ccsg->addr_h);
|
|
#endif
|
|
ccsg++;
|
|
}
|
|
exdesc++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static inline void meson_spicc_config_null_desc(struct spicc_device *spicc,
|
|
struct spicc_descriptor *desc,
|
|
int n_clk)
|
|
{
|
|
desc->cfg_start.b.op_mode = SPICC_OP_MODE_WRITE;
|
|
desc->cfg_start.b.tx_data_mode = SPICC_DATA_MODE_NONE;
|
|
desc->cfg_start.b.rx_data_mode = SPICC_DATA_MODE_NONE;
|
|
desc->cfg_bus.b.null_ctl = 1;
|
|
desc->cfg_start.b.block_size = 1;
|
|
desc->cfg_start.b.block_num = DIV_ROUND_UP(n_clk, 9);
|
|
}
|
|
|
|
static int ns_to_clk(u64 clk_rate, u64 ns)
|
|
{
|
|
u64 abs_ns = abs(ns);
|
|
int n_clk;
|
|
|
|
n_clk = DIV_ROUND_CLOSEST_ULL(clk_rate * abs_ns, 1000000000);
|
|
|
|
return n_clk;
|
|
}
|
|
|
|
static int spi_delay_to_clk(unsigned long clk_rate, struct spi_delay *delay)
|
|
{
|
|
int n_clk;
|
|
|
|
if (!delay->value)
|
|
return 0;
|
|
|
|
switch (delay->unit) {
|
|
case SPI_DELAY_UNIT_NSECS:
|
|
n_clk = ns_to_clk(clk_rate, delay->value);
|
|
break;
|
|
case SPI_DELAY_UNIT_USECS:
|
|
n_clk = ns_to_clk(clk_rate, delay->value * NSEC_PER_USEC);
|
|
break;
|
|
case SPI_DELAY_UNIT_SCK:
|
|
n_clk = delay->value;
|
|
break;
|
|
default:
|
|
n_clk = 0;
|
|
}
|
|
|
|
return n_clk;
|
|
}
|
|
|
|
static void meson_spicc_config_cs_setup(struct spicc_device *spicc,
|
|
struct spicc_descriptor *desc,
|
|
struct spi_delay *cs_setup)
|
|
{
|
|
int n_clk;
|
|
|
|
/* unit SCLK of the last xfer */
|
|
n_clk = spi_delay_to_clk(spicc->actual_speed_hz, cs_setup);
|
|
if (spicc->data->cs_setup_extend_ctrl) {
|
|
n_clk = min_t(int, n_clk, 0xFF);
|
|
desc->cfg_bus.b.cs_setup = n_clk & 0xF;
|
|
spicc->cfg_spi.b.cs_setup_extend = n_clk >> 4;
|
|
} else {
|
|
desc->cfg_bus.b.cs_setup = min_t(int, n_clk, 0xF);
|
|
}
|
|
}
|
|
|
|
static bool meson_spicc_is_desc_cs_hold(struct spicc_device *spicc,
|
|
int xfer_num,
|
|
bool keep_ss)
|
|
{
|
|
// Prevent conflicts of the simultaneous existence of keep_ss and cs_hold
|
|
if (spicc->cs_hold.value && (!spicc->data->cs_hold_ctrl || xfer_num > 1 ||
|
|
(keep_ss && spicc->data->cs_hold_ctrl_conflict_with_keep_ss)))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
static void meson_spicc_config_cs_hold(struct spicc_device *spicc,
|
|
bool is_desc_cs_hold,
|
|
struct spicc_descriptor *desc,
|
|
struct spi_delay *cs_hold)
|
|
{
|
|
struct spicc_descriptor *ancestor;
|
|
int n_clk, n_clk_exist;
|
|
|
|
/* unit SCLK of the last xfer */
|
|
n_clk = spi_delay_to_clk(spicc->actual_speed_hz, cs_hold);
|
|
if (is_desc_cs_hold) {
|
|
ancestor = desc - 1;
|
|
desc->cfg_start.d32 = ancestor->cfg_start.d32;
|
|
desc->cfg_bus.d32 = ancestor->cfg_bus.d32;
|
|
n_clk_exist = spicc->cfg_spi.b.cs_setup_extend << 4;
|
|
n_clk_exist += desc->cfg_bus.b.cs_setup;
|
|
if (n_clk > n_clk_exist)
|
|
n_clk -= n_clk_exist;
|
|
else
|
|
n_clk = 1;
|
|
meson_spicc_config_null_desc(spicc, desc, n_clk);
|
|
desc->cfg_start.b.eoc = 1;
|
|
ancestor->cfg_bus.b.keep_ss = 1;
|
|
ancestor->cfg_start.b.eoc = 0;
|
|
spicc->cfg_spi.b.cs_hold = spicc->cs_hold_min_in_pclk;
|
|
} else {
|
|
/* unit PCLK */
|
|
if (n_clk)
|
|
n_clk *= spicc->cfg_bus.b.clk_div + 1;
|
|
else
|
|
n_clk = spicc->cs_hold_min_in_pclk;
|
|
spicc->cfg_spi.b.cs_hold = min_t(int, n_clk, 0x7FFF);
|
|
}
|
|
}
|
|
|
|
static int meson_spicc_config(struct spicc_device *spicc,
|
|
struct spi_device *spi)
|
|
{
|
|
struct spicc_controller_data *cdata;
|
|
u8 idx;
|
|
|
|
if (!spi->bits_per_word || spi->bits_per_word % 8) {
|
|
spicc_err("invalid wordlen %d\n", spi->bits_per_word);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spicc->bytes_per_word = spi->bits_per_word >> 3;
|
|
spicc->cfg_start.b.block_size = spicc->bytes_per_word & 0x7;
|
|
for (idx = 0; idx < MESON_SPI_CS_CNT_MAX; idx++)
|
|
if (spi->cs_index_mask & BIT(idx))
|
|
spicc->cfg_spi.b.ss = spi_get_chipselect(spi, idx);
|
|
|
|
spicc->cfg_bus.b.cpol = !!(spi->mode & SPI_CPOL);
|
|
spicc->cfg_bus.b.cpha = !!(spi->mode & SPI_CPHA);
|
|
spicc->cfg_bus.b.little_endian_en = !!(spi->mode & SPI_LSB_FIRST);
|
|
spicc->cfg_bus.b.half_duplex_en = !!(spi->mode & SPI_3WIRE);
|
|
|
|
cdata = (struct spicc_controller_data *)spi->controller_data;
|
|
if (cdata && cdata->timing_en) {
|
|
/* 4bit signed, in parent SCLK */
|
|
spicc->cfg_bus.b.tx_tuning = cdata->tx_tuning;
|
|
spicc->cfg_bus.b.rx_tuning = cdata->rx_tuning;
|
|
spicc->cfg_bus.b.dummy_ctl = cdata->dummy_ctl;
|
|
spicc->cfg_spi.b.word_gap = cdata->word_gap;
|
|
spicc->cfg_spi.b.mosi_idle_level = cdata->mosi_idle_level;
|
|
} else {
|
|
spicc->cfg_bus.b.tx_tuning = 0;
|
|
spicc->cfg_bus.b.rx_tuning = 0;
|
|
spicc->cfg_bus.b.dummy_ctl = 0;
|
|
spicc->cfg_spi.b.word_gap = 1;
|
|
spicc->cfg_spi.b.mosi_idle_level = 0;
|
|
}
|
|
|
|
spicc->cs_setup.unit = SPI_DELAY_UNIT_USECS;
|
|
spicc->cs_hold.unit = SPI_DELAY_UNIT_USECS;
|
|
if (cdata && cdata->use_ctrl_cs) {
|
|
spicc->cs_setup.value = cdata->ss_leading_gap;
|
|
spicc->cs_hold.value = cdata->ss_trailing_gap;
|
|
} else if (spi_is_csgpiod(spi)) {
|
|
spicc->cs_setup.value = 0;
|
|
spicc->cs_hold.value = 0;
|
|
} else {
|
|
spicc->cs_setup = spi->cs_setup;
|
|
spicc->cs_hold = spi->cs_hold;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool meson_spicc_can_dma(struct spi_controller *ctlr,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *xfer)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void spicc_sg_xlate(struct sg_table *sgt, struct spicc_sg_link *ccsg)
|
|
{
|
|
struct scatterlist *sg;
|
|
int i;
|
|
|
|
for_each_sg(sgt->sgl, sg, sgt->nents, i) {
|
|
ccsg->valid = 1;
|
|
/* EOC specially for the last sg */
|
|
ccsg->eoc = sg_is_last(sg) ? 1 : 0;
|
|
ccsg->ring = 0;
|
|
ccsg->len = sg_dma_len(sg);
|
|
ccsg->addr = lower_32_bits(sg_dma_address(sg));
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
ccsg->addr_h = upper_32_bits(sg_dma_address(sg));
|
|
#endif
|
|
ccsg++;
|
|
}
|
|
}
|
|
|
|
static int nbits_to_lane[] = {
|
|
SPICC_SINGLE_SPI,
|
|
SPICC_SINGLE_SPI,
|
|
SPICC_DUAL_SPI,
|
|
-EINVAL,
|
|
SPICC_QUAD_SPI
|
|
};
|
|
|
|
static int spicc_config_desc_one_transfer(struct spicc_device *spicc,
|
|
struct spi_transfer *xfer,
|
|
bool is_last_xfer,
|
|
struct spicc_descriptor *desc,
|
|
struct spicc_descriptor_extra *exdesc,
|
|
struct spicc_controller_data *cdata)
|
|
{
|
|
int block_size, blocks;
|
|
struct device *dev = &spicc->pdev->dev;
|
|
struct spicc_sg_link *ccsg;
|
|
int ccsg_len;
|
|
dma_addr_t paddr;
|
|
int ret;
|
|
|
|
memset(desc, 0, sizeof(*desc));
|
|
if (exdesc)
|
|
memset(exdesc, 0, sizeof(*exdesc));
|
|
spicc_set_speed(spicc, xfer->speed_hz);
|
|
desc->cfg_start.d32 = spicc->cfg_start.d32;
|
|
desc->cfg_bus.d32 = spicc->cfg_bus.d32;
|
|
|
|
block_size = xfer->bits_per_word >> 3;
|
|
blocks = xfer->len / block_size;
|
|
|
|
desc->cfg_start.b.tx_data_mode = SPICC_DATA_MODE_NONE;
|
|
desc->cfg_start.b.rx_data_mode = SPICC_DATA_MODE_NONE;
|
|
desc->cfg_start.b.eoc = is_last_xfer;
|
|
desc->cfg_bus.b.keep_ss = is_last_xfer ? xfer->cs_change : !xfer->cs_change;
|
|
if (is_last_xfer)
|
|
spicc->keep_ready = desc->cfg_bus.b.keep_ss;
|
|
|
|
if (cdata && cdata->dc_mode) {
|
|
desc->cfg_start.b.dc_level = cdata->dc_level;
|
|
desc->cfg_bus.b.read_turn_around = min_t(unsigned int,
|
|
cdata->read_turn_around,
|
|
SPICC_READ_TURN_AROUND_MAX);
|
|
desc->cfg_bus.b.dc_mode = cdata->dc_mode - 1;
|
|
}
|
|
|
|
if (xfer->tx_buf || xfer->tx_dma) {
|
|
desc->cfg_bus.b.lane = nbits_to_lane[xfer->tx_nbits];
|
|
desc->cfg_start.b.op_mode = (cdata && cdata->dc_mode) ?
|
|
SPICC_OP_MODE_WRITE_CMD : SPICC_OP_MODE_WRITE;
|
|
}
|
|
if (xfer->rx_buf || xfer->rx_dma) {
|
|
desc->cfg_bus.b.lane = nbits_to_lane[xfer->rx_nbits];
|
|
desc->cfg_start.b.op_mode = (cdata && cdata->dc_mode) ?
|
|
SPICC_OP_MODE_READ_STS : SPICC_OP_MODE_READ;
|
|
}
|
|
|
|
if (desc->cfg_start.b.op_mode == SPICC_OP_MODE_READ_STS) {
|
|
desc->cfg_start.b.block_size = blocks;
|
|
desc->cfg_start.b.block_num = 1;
|
|
} else {
|
|
desc->cfg_start.b.block_size = block_size & 0x7;
|
|
blocks = min_t(int, blocks, SPICC_BLOCK_MAX);
|
|
desc->cfg_start.b.block_num = blocks;
|
|
}
|
|
|
|
if (xfer->tx_sg.nents && xfer->tx_sg.sgl && exdesc) {
|
|
ccsg_len = xfer->tx_sg.nents * sizeof(struct spicc_sg_link);
|
|
ccsg = kzalloc(ccsg_len, GFP_KERNEL | GFP_DMA);
|
|
if (!ccsg) {
|
|
dev_err(dev, "alloc tx_ccsg failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spicc_sg_xlate(&xfer->tx_sg, ccsg);
|
|
paddr = dma_map_single(dev, (void *)ccsg,
|
|
ccsg_len, DMA_TO_DEVICE);
|
|
ret = dma_mapping_error(dev, paddr);
|
|
if (ret) {
|
|
kfree(ccsg);
|
|
dev_err(dev, "tx ccsg map failed\n");
|
|
return ret;
|
|
}
|
|
|
|
desc->tx_paddr = paddr;
|
|
desc->cfg_start.b.tx_data_mode = SPICC_DATA_MODE_SG;
|
|
exdesc->tx_ccsg = ccsg;
|
|
exdesc->tx_ccsg_len = ccsg_len;
|
|
dma_sync_sgtable_for_device(spicc->controller->cur_tx_dma_dev,
|
|
&xfer->tx_sg, DMA_TO_DEVICE);
|
|
} else if (xfer->tx_buf || xfer->tx_dma) {
|
|
paddr = xfer->tx_dma;
|
|
if (!paddr) {
|
|
paddr = dma_map_single(dev, (void *)xfer->tx_buf,
|
|
xfer->len, DMA_TO_DEVICE);
|
|
ret = dma_mapping_error(dev, paddr);
|
|
if (ret) {
|
|
dev_err(dev, "tx buf map failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
desc->tx_paddr = paddr;
|
|
desc->cfg_start.b.tx_data_mode = SPICC_DATA_MODE_MEM;
|
|
}
|
|
|
|
if (xfer->rx_sg.nents && xfer->rx_sg.sgl && exdesc) {
|
|
ccsg_len = xfer->rx_sg.nents * sizeof(struct spicc_sg_link);
|
|
ccsg = kzalloc(ccsg_len, GFP_KERNEL | GFP_DMA);
|
|
if (!ccsg) {
|
|
dev_err(dev, "alloc rx_ccsg failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spicc_sg_xlate(&xfer->rx_sg, ccsg);
|
|
paddr = dma_map_single(dev, (void *)ccsg,
|
|
ccsg_len, DMA_TO_DEVICE);
|
|
ret = dma_mapping_error(dev, paddr);
|
|
if (ret) {
|
|
kfree(ccsg);
|
|
dev_err(dev, "rx ccsg map failed\n");
|
|
return ret;
|
|
}
|
|
|
|
desc->rx_paddr = paddr;
|
|
desc->cfg_start.b.rx_data_mode = SPICC_DATA_MODE_SG;
|
|
exdesc->rx_ccsg = ccsg;
|
|
exdesc->rx_ccsg_len = ccsg_len;
|
|
dma_sync_sgtable_for_device(spicc->controller->cur_rx_dma_dev,
|
|
&xfer->rx_sg, DMA_FROM_DEVICE);
|
|
} else if (xfer->rx_buf || xfer->rx_dma) {
|
|
paddr = xfer->rx_dma;
|
|
if (!paddr) {
|
|
paddr = dma_map_single(dev, xfer->rx_buf,
|
|
xfer->len, DMA_FROM_DEVICE);
|
|
ret = dma_mapping_error(dev, paddr);
|
|
if (ret) {
|
|
dev_err(dev, "rx buf map failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
desc->rx_paddr = paddr;
|
|
desc->cfg_start.b.rx_data_mode = SPICC_DATA_MODE_MEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void spicc_deconfig_desc_one_transfer(struct spicc_device *spicc,
|
|
struct spi_transfer *xfer,
|
|
struct spicc_descriptor *desc,
|
|
struct spicc_descriptor_extra *exdesc)
|
|
{
|
|
struct device *dev = &spicc->pdev->dev;
|
|
|
|
if (desc->tx_paddr) {
|
|
if (desc->cfg_start.b.tx_data_mode == SPICC_DATA_MODE_SG) {
|
|
dma_unmap_single(dev, (dma_addr_t)desc->tx_paddr,
|
|
exdesc->tx_ccsg_len, DMA_TO_DEVICE);
|
|
kfree(exdesc->tx_ccsg);
|
|
dma_sync_sgtable_for_cpu(spicc->controller->cur_tx_dma_dev,
|
|
&xfer->tx_sg, DMA_TO_DEVICE);
|
|
} else if (!xfer->tx_dma) {
|
|
dma_unmap_single(dev, (dma_addr_t)desc->tx_paddr,
|
|
xfer->len, DMA_TO_DEVICE);
|
|
}
|
|
}
|
|
|
|
if (desc->rx_paddr) {
|
|
if (desc->cfg_start.b.rx_data_mode == SPICC_DATA_MODE_SG) {
|
|
dma_unmap_single(dev, (dma_addr_t)desc->rx_paddr,
|
|
exdesc->rx_ccsg_len, DMA_TO_DEVICE);
|
|
kfree(exdesc->rx_ccsg);
|
|
dma_sync_sgtable_for_cpu(spicc->controller->cur_rx_dma_dev,
|
|
&xfer->rx_sg, DMA_FROM_DEVICE);
|
|
} else if (!xfer->rx_dma) {
|
|
dma_unmap_single(dev, (dma_addr_t)desc->rx_paddr,
|
|
xfer->len, DMA_FROM_DEVICE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void spicc_desc_pending(struct spicc_device *spicc,
|
|
dma_addr_t desc_paddr,
|
|
int desc_num,
|
|
bool trig,
|
|
bool irq_en)
|
|
{
|
|
u32 desc_l, desc_h, cfg_spi;
|
|
u32 irq;
|
|
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
desc_l = (u64)desc_paddr & 0xffffffff;
|
|
desc_h = (u64)desc_paddr >> 32;
|
|
#else
|
|
desc_l = desc_paddr & 0xffffffff;
|
|
desc_h = 0;
|
|
#endif
|
|
|
|
cfg_spi = spicc->cfg_spi.d32;
|
|
if (trig)
|
|
cfg_spi |= HW_POS;
|
|
else
|
|
desc_h |= SPICC_DESC_PENDING;
|
|
|
|
if (irq_en) {
|
|
if (spicc->data->clk_gate_ctrl && desc_num > 1)
|
|
irq = SPICC_DESC_DONE | SPICC_DESC_CHAIN_DONE;
|
|
else
|
|
irq = SPICC_DESC_CHAIN_DONE;
|
|
} else {
|
|
irq = 0;
|
|
}
|
|
|
|
spicc_writel(spicc, irq, SPICC_REG_IRQ_ENABLE);
|
|
spicc_writel(spicc, cfg_spi, SPICC_REG_CFG_SPI);
|
|
spicc_writel(spicc, desc_l, SPICC_REG_DESC_LIST_L);
|
|
spicc_writel(spicc, desc_h, SPICC_REG_DESC_LIST_H);
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
static void meson_spicc_ping_pong_switch(struct spicc_device *spicc)
|
|
{
|
|
u32 desc_l, desc_h;
|
|
dma_addr_t desc_paddr;
|
|
|
|
if (atomic_read(&spicc->ping_or_pong)) {
|
|
desc_paddr = spicc->dirspi_desc_paddr_pong;
|
|
atomic_set(&spicc->ping_or_pong, 0);
|
|
} else {
|
|
desc_paddr = spicc->dirspi_desc_paddr;
|
|
atomic_set(&spicc->ping_or_pong, 1);
|
|
}
|
|
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
desc_l = (u64)desc_paddr & 0xffffffff;
|
|
desc_h = (u64)desc_paddr >> 32;
|
|
#else
|
|
desc_l = desc_paddr & 0xffffffff;
|
|
desc_h = 0;
|
|
#endif
|
|
|
|
spicc_writel(spicc, desc_l, SPICC_REG_DESC_LIST_L);
|
|
spicc_writel(spicc, desc_h, SPICC_REG_DESC_LIST_H);
|
|
}
|
|
#endif
|
|
|
|
static irqreturn_t meson_spicc_irq(int irq, void *data)
|
|
{
|
|
struct spicc_device *spicc = (void *)data;
|
|
u32 sts;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&spicc->lock, flags);
|
|
|
|
sts = spicc_readl(spicc, SPICC_REG_IRQ_STS);
|
|
spicc->status = sts & SPICC_STATUS_ERROR;
|
|
if (!spicc->pending) {
|
|
spicc_writel(spicc, sts, SPICC_REG_IRQ_STS);
|
|
} else if (sts & (SPICC_DESC_CHAIN_DONE | SPICC_STATUS_ERROR)) {
|
|
spicc_writel(spicc, sts, SPICC_REG_IRQ_STS);
|
|
spicc->pending = false;
|
|
#ifdef MESON_SPICC_HW_IF
|
|
if (spicc->dirspi_complete) {
|
|
spicc_sem_up_write(spicc);
|
|
spicc->dirspi_complete(spicc->dirspi_context);
|
|
spicc->dirspi_complete = NULL;
|
|
} else {
|
|
complete(&spicc->completion);
|
|
}
|
|
#else
|
|
complete(&spicc->completion);
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
if (spicc->en_ping_pong)
|
|
meson_spicc_ping_pong_switch(spicc);
|
|
#endif
|
|
|
|
spin_unlock_irqrestore(&spicc->lock, flags);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* spi_transfer_one_message - Default implementation of transfer_one_message()
|
|
*
|
|
* This is a standard implementation of transfer_one_message() for
|
|
* drivers which implement a transfer_one() operation. It provides
|
|
* standard handling of delays and chip select management.
|
|
*/
|
|
static int meson_spicc_transfer_one_message(struct spi_controller *ctlr,
|
|
struct spi_message *msg)
|
|
{
|
|
struct spicc_device *spicc = spi_controller_get_devdata(ctlr);
|
|
struct device *dev = &spicc->pdev->dev;
|
|
struct spicc_controller_data *cdata = msg->spi->controller_data;
|
|
unsigned long ms = spicc_xfer_time_max(spicc, msg->frame_length);
|
|
struct spi_transfer *xfer;
|
|
struct spicc_descriptor *descs, *desc;
|
|
struct spicc_descriptor_extra *exdescs, *exdesc;
|
|
dma_addr_t descs_paddr;
|
|
int desc_num = 0, descs_len;
|
|
bool keep_ss = false;
|
|
bool is_desc_cs_hold;
|
|
int ret = -EIO;
|
|
u32 sts;
|
|
unsigned long flags;
|
|
|
|
if (!spicc_sem_down_read(spicc)) {
|
|
spi_finalize_current_message(ctlr);
|
|
dev_err(dev, "controller busy\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
/*calculate the desc num for all xfer */
|
|
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
|
desc_num++;
|
|
if (list_is_last(&xfer->transfer_list, &msg->transfers)) {
|
|
if (xfer->cs_change)
|
|
keep_ss = true;
|
|
} else {
|
|
if (!xfer->cs_change)
|
|
keep_ss = true;
|
|
}
|
|
}
|
|
|
|
/* additional descriptor to achieve the cs_hold */
|
|
is_desc_cs_hold = meson_spicc_is_desc_cs_hold(spicc, desc_num, keep_ss);
|
|
if (is_desc_cs_hold)
|
|
desc_num++;
|
|
|
|
/* alloc descriptor/extra-descriptor table */
|
|
descs = kcalloc(desc_num, sizeof(*desc) + sizeof(*exdesc),
|
|
GFP_KERNEL | GFP_DMA);
|
|
if (!descs) {
|
|
spi_finalize_current_message(ctlr);
|
|
spicc_sem_up_write(spicc);
|
|
return -ENOMEM;
|
|
}
|
|
descs_len = sizeof(*desc) * desc_num;
|
|
exdescs = (struct spicc_descriptor_extra *)(descs + desc_num);
|
|
|
|
/* config descriptor for each xfer */
|
|
desc = descs;
|
|
exdesc = exdescs;
|
|
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
|
ret = spicc_config_desc_one_transfer(spicc, xfer,
|
|
list_is_last(&xfer->transfer_list, &msg->transfers),
|
|
desc++, exdesc++, cdata);
|
|
if (ret) {
|
|
dev_err(dev, "config descriptor failed\n");
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
desc = descs;
|
|
meson_spicc_config_cs_setup(spicc, desc, &spicc->cs_setup);
|
|
desc = descs + desc_num - 1;
|
|
meson_spicc_config_cs_hold(spicc, is_desc_cs_hold, desc, &spicc->cs_hold);
|
|
meson_spicc_dump(spicc, descs, exdescs, desc_num);
|
|
|
|
descs_paddr = dma_map_single(dev, (void *)descs,
|
|
descs_len, DMA_TO_DEVICE);
|
|
ret = dma_mapping_error(dev, descs_paddr);
|
|
if (ret) {
|
|
dev_err(dev, "desc table map failed\n");
|
|
goto end;
|
|
}
|
|
|
|
reinit_completion(&spicc->completion);
|
|
|
|
spin_lock_irqsave(&spicc->lock, flags);
|
|
spicc_set_cs(msg->spi, true);
|
|
spicc->pending = true;
|
|
spicc_desc_pending(spicc, descs_paddr, desc_num, false, true);
|
|
spin_unlock_irqrestore(&spicc->lock, flags);
|
|
|
|
ret = wait_for_completion_timeout(&spicc->completion,
|
|
spi_controller_is_target(spicc->controller) ?
|
|
MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(ms));
|
|
|
|
spin_lock_irqsave(&spicc->lock, flags);
|
|
spicc->pending = false;
|
|
spicc_set_cs(msg->spi, false);
|
|
spin_unlock_irqrestore(&spicc->lock, flags);
|
|
|
|
if (ret) {
|
|
ret = spicc->status ? -EIO : 0;
|
|
} else {
|
|
sts = spicc_readl(spicc, SPICC_REG_IRQ_STS);
|
|
if (sts)
|
|
spicc_writel(spicc, sts, SPICC_REG_IRQ_STS);
|
|
|
|
ret = -ETIMEDOUT;
|
|
if (spicc->data->clk_gate_ctrl) {
|
|
if (sts & SPICC_STATUS_ERROR)
|
|
ret = -EIO;
|
|
else if (sts & SPICC_DESC_CHAIN_DONE)
|
|
ret = 0;
|
|
}
|
|
}
|
|
|
|
dma_unmap_single(dev, descs_paddr, descs_len, DMA_TO_DEVICE);
|
|
end:
|
|
desc = descs;
|
|
exdesc = exdescs;
|
|
list_for_each_entry(xfer, &msg->transfers, transfer_list)
|
|
spicc_deconfig_desc_one_transfer(spicc, xfer, desc++, exdesc++);
|
|
kfree(descs);
|
|
|
|
if (!ret)
|
|
msg->actual_length = msg->frame_length;
|
|
else
|
|
spicc->keep_ready = false;
|
|
msg->status = ret;
|
|
spi_finalize_current_message(ctlr);
|
|
spicc_sem_up_write(spicc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int meson_spicc_prepare_message(struct spi_controller *ctlr,
|
|
struct spi_message *message)
|
|
{
|
|
struct spicc_device *spicc = spi_controller_get_devdata(ctlr);
|
|
|
|
return meson_spicc_config(spicc, message->spi);
|
|
}
|
|
|
|
static int meson_spicc_unprepare_transfer(struct spi_controller *ctlr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int meson_spicc_setup(struct spi_device *spi)
|
|
{
|
|
#ifdef MESON_SPICC_HW_IF
|
|
struct spicc_device *spicc;
|
|
struct spicc_controller_data *cdata;
|
|
int ret = 0;
|
|
|
|
spicc = spi_controller_get_devdata(spi->controller);
|
|
cdata = (struct spicc_controller_data *)spi->controller_data;
|
|
if (cdata) {
|
|
spicc->spi_pinmux = pinctrl_lookup_state(spicc->pinctrl, "spi_pinmux");
|
|
if (IS_ERR_OR_NULL(spicc->spi_pinmux)) {
|
|
dev_warn(&spicc->pdev->dev, "no spi_pinmux pinctrl(Maybe not error)\n");
|
|
} else {
|
|
ret = pinctrl_select_state(spicc->pinctrl, spicc->spi_pinmux);
|
|
if (ret) {
|
|
dev_err(&spicc->pdev->dev, "failed to activate spi_pinmux pinctrl state\n");
|
|
return ret;
|
|
}
|
|
}
|
|
cdata->controller_version = CONTROLLER_SPISG;
|
|
cdata->controller_capabilities = CAP_DMA_TRIG_VSYNC |
|
|
CAP_DMA_TRIG_PWM_VS | CAP_TRIG_DELAY;
|
|
cdata->dirspi_start = dirspi_start;
|
|
cdata->dirspi_stop = dirspi_stop;
|
|
cdata->dirspi_async = dirspi_async;
|
|
cdata->dirspi_sync = dirspi_sync;
|
|
cdata->dirspi_dma_trig = dirspi_dma_trig;
|
|
cdata->dirspi_dma_trig_start = dirspi_dma_trig_start;
|
|
cdata->dirspi_dma_trig_stop = dirspi_dma_trig_stop;
|
|
cdata->dirspi_dma_trig_release = dirspi_dma_trig_release;
|
|
cdata->dmatrig_is_busy = dmatrig_is_busy;
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
cdata->dmatrig_ping_or_pong = dmatrig_ping_or_pong;
|
|
#endif
|
|
if (cdata->use_dirspi)
|
|
spicc_set_speed(spicc, spi->max_speed_hz);
|
|
if (cdata->pclk_rate && cdata->pclk_rate != spicc->pclk_rate) {
|
|
clk_set_rate(spicc->spi_clk, cdata->pclk_rate);
|
|
/* force reset the sclk rate due to the pclk rate changed */
|
|
spicc->pclk_rate = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void meson_spicc_cleanup(struct spi_device *spi)
|
|
{
|
|
spi->controller_state = NULL;
|
|
}
|
|
|
|
static int meson_spicc_slave_abort(struct spi_controller *ctlr)
|
|
{
|
|
struct spicc_device *spicc = spi_controller_get_devdata(ctlr);
|
|
|
|
spicc->status = 0;
|
|
spicc_writel(spicc, 0, SPICC_REG_DESC_LIST_H);
|
|
complete(&spicc->completion);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef MESON_SPICC_HW_IF
|
|
static int spicc_wait_complete(struct spicc_device *spicc, u32 flags,
|
|
unsigned long timeout)
|
|
{
|
|
u32 sts;
|
|
|
|
timeout += jiffies;
|
|
while (time_before(jiffies, timeout)) {
|
|
sts = spicc_readl(spicc, SPICC_REG_IRQ_STS);
|
|
if (sts & (flags | SPICC_STATUS_ERROR)) {
|
|
spicc_writel(spicc, sts, SPICC_REG_IRQ_STS);
|
|
return (sts & SPICC_STATUS_ERROR) ? -EIO : 0;
|
|
}
|
|
|
|
cpu_relax();
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void dirspi_start(struct spi_device *spi)
|
|
{
|
|
if (spi->controller->auto_runtime_pm) {
|
|
if (pm_runtime_get_sync(spi->controller->dev.parent) < 0) {
|
|
dev_err(spi->controller->dev.parent,
|
|
"%s: pm_runtime_get_sync fails\n", __func__);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dirspi_stop(struct spi_device *spi)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* Return
|
|
* <0: error code if the transfer is failed
|
|
* 0: if the transfer is finished.(callback executed)
|
|
* >0: if the transfer is still in progress
|
|
*/
|
|
static int dirspi_async(struct spi_device *spi,
|
|
dma_addr_t tx_dma,
|
|
dma_addr_t rx_dma,
|
|
int len,
|
|
void (*complete)(void *context),
|
|
void *context)
|
|
{
|
|
struct spicc_device *spicc;
|
|
struct device *dev;
|
|
struct spi_transfer xfer;
|
|
struct spicc_descriptor *desc;
|
|
bool is_desc_cs_hold;
|
|
int desc_num = 1;
|
|
int ret;
|
|
unsigned long flags;
|
|
|
|
spicc = spi_controller_get_devdata(spi->controller);
|
|
dev = &spicc->pdev->dev;
|
|
if (!spicc->dirspi_desc) {
|
|
dev_err(dev, "no descriptor for dirspi\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!spicc_sem_down_read(spicc)) {
|
|
dev_err(dev, "controller busy\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
ret = meson_spicc_config(spicc, spi);
|
|
if (ret) {
|
|
spicc_sem_up_write(spicc);
|
|
return ret;
|
|
}
|
|
|
|
memset(&xfer, 0, sizeof(xfer));
|
|
xfer.tx_dma = tx_dma;
|
|
xfer.rx_dma = rx_dma;
|
|
xfer.len = len;
|
|
xfer.bits_per_word = spi->bits_per_word;
|
|
|
|
/* additional descriptor to achieve the cs_hold */
|
|
is_desc_cs_hold = meson_spicc_is_desc_cs_hold(spicc, desc_num, false);
|
|
if (is_desc_cs_hold)
|
|
desc_num++;
|
|
|
|
desc = spicc->dirspi_desc;
|
|
ret = spicc_config_desc_one_transfer(spicc, &xfer, true, desc, NULL, NULL);
|
|
if (ret) {
|
|
spicc_sem_up_write(spicc);
|
|
return ret;
|
|
}
|
|
|
|
desc = spicc->dirspi_desc;
|
|
meson_spicc_config_cs_setup(spicc, desc, &spicc->cs_setup);
|
|
desc = spicc->dirspi_desc + desc_num - 1;
|
|
meson_spicc_config_cs_hold(spicc, is_desc_cs_hold, desc, &spicc->cs_hold);
|
|
meson_spicc_dump(spicc, spicc->dirspi_desc, NULL, desc_num);
|
|
|
|
spicc->dirspi_complete = complete;
|
|
spicc->dirspi_context = context;
|
|
|
|
spin_lock_irqsave(&spicc->lock, flags);
|
|
spicc->pending = true;
|
|
spicc_desc_pending(spicc, spicc->dirspi_desc_paddr, desc_num, false,
|
|
complete ? true : false);
|
|
spin_unlock_irqrestore(&spicc->lock, flags);
|
|
|
|
if (complete)
|
|
return 1;
|
|
|
|
ret = spicc_wait_complete(spicc, SPICC_DESC_CHAIN_DONE,
|
|
msecs_to_jiffies(spicc_xfer_time_max(spicc, len)));
|
|
|
|
spin_lock_irqsave(&spicc->lock, flags);
|
|
spicc->pending = false;
|
|
spin_unlock_irqrestore(&spicc->lock, flags);
|
|
|
|
spicc_sem_up_write(spicc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dirspi_sync(struct spi_device *spi,
|
|
dma_addr_t tx_dma,
|
|
dma_addr_t rx_dma,
|
|
int len)
|
|
{
|
|
return dirspi_async(spi, tx_dma, rx_dma, len, NULL, NULL);
|
|
}
|
|
|
|
/* SYSCTRL_SPI_TRIG */
|
|
#define TRIG_REG_IN_SEL_MASK GENMASK(4, 0)
|
|
#define TRIG_REG_OUT_SEL_MASK GENMASK(6, 5)
|
|
#define TRIG_REG_DELAY_CNT_MASK GENMASK(30, 7)
|
|
#define TRIG_DELAY_MIN 2
|
|
#define TRIG_DELAY_MAX 0xFFFFF
|
|
#define TRIG_REG_MUX_EN BIT(31)
|
|
|
|
static int spicc_dma_trig_start(struct spicc_device *spicc)
|
|
{
|
|
struct device *dev = &spicc->pdev->dev;
|
|
|
|
if (spicc->dirspi_status == DIRSPI_STA_RUNNING) {
|
|
dev_warn(dev, "start trig in running state\n");
|
|
return 0;
|
|
}
|
|
|
|
if (spicc->dirspi_status == DIRSPI_STA_IDLE) {
|
|
dev_err(dev, "start trig in idle state\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (!spicc_sem_down_read(spicc)) {
|
|
dev_err(dev, "controller busy, start trig failed\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (!IS_ERR_OR_NULL(spicc->trig_reg))
|
|
writel(readl(spicc->trig_reg) | TRIG_REG_MUX_EN,
|
|
spicc->trig_reg);
|
|
|
|
spicc->dirspi_status = DIRSPI_STA_RUNNING;
|
|
dev_dbg(dev, "start trig in ready state success\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spicc_dma_trig_stop(struct spicc_device *spicc)
|
|
{
|
|
struct device *dev = &spicc->pdev->dev;
|
|
unsigned long timeout;
|
|
union spicc_cfg_start cfg_start;
|
|
u32 desc_h;
|
|
|
|
if (spicc->dirspi_status == DIRSPI_STA_IDLE) {
|
|
dev_warn(dev, "stop trig in idle state\n");
|
|
return 0;
|
|
}
|
|
|
|
if (spicc->dirspi_status == DIRSPI_STA_READY) {
|
|
dev_warn(dev, "stop trig in ready state\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!IS_ERR_OR_NULL(spicc->trig_reg))
|
|
writel(readl(spicc->trig_reg) & ~TRIG_REG_MUX_EN,
|
|
spicc->trig_reg);
|
|
|
|
timeout = msecs_to_jiffies(100) + jiffies;
|
|
while (time_before(jiffies, timeout)) {
|
|
cfg_start.d32 = spicc_readl(spicc, SPICC_REG_CFG_START);
|
|
desc_h = spicc_readl(spicc, SPICC_REG_DESC_LIST_H);
|
|
if (!cfg_start.b.pending && !(desc_h & SPICC_DESC_PENDING))
|
|
break;
|
|
}
|
|
|
|
if (time_after(jiffies, timeout))
|
|
dev_warn(dev, "dma busy\n");
|
|
|
|
spicc_sem_up_write(spicc);
|
|
spicc->dirspi_status = DIRSPI_STA_READY;
|
|
dev_dbg(dev, "stop trig in running state success\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spicc_dma_trig_release(struct spicc_device *spicc)
|
|
{
|
|
struct device *dev = &spicc->pdev->dev;
|
|
|
|
if (spicc->dirspi_status == DIRSPI_STA_IDLE) {
|
|
dev_warn(dev, "release trig in idle state\n");
|
|
return 0;
|
|
}
|
|
|
|
if (spicc->dirspi_status == DIRSPI_STA_RUNNING)
|
|
spicc_dma_trig_stop(spicc);
|
|
|
|
spicc->dirspi_status = DIRSPI_STA_IDLE;
|
|
dev_dbg(dev, "release trig success\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* @tx_dma: DMA address of tx buf
|
|
* @rx_dma: DMA address of rx buf
|
|
* @src: trigger source, DMA_TRIG_VSYNC or DMA_TRIG_LINE_N
|
|
*/
|
|
static int dirspi_dma_trig(struct spi_device *spi,
|
|
dma_addr_t tx_dma,
|
|
dma_addr_t rx_dma,
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
dma_addr_t tx_dma_pong,
|
|
dma_addr_t rx_dma_pong,
|
|
#endif
|
|
int len,
|
|
u8 src)
|
|
{
|
|
struct spicc_device *spicc;
|
|
struct device *dev;
|
|
struct spicc_controller_data *cdata;
|
|
struct spi_transfer xfer;
|
|
struct spicc_descriptor *desc;
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
struct spicc_descriptor *desc_pong;
|
|
#endif
|
|
bool is_desc_cs_hold;
|
|
u32 in_sel, delay = TRIG_DELAY_MIN;
|
|
u8 idx;
|
|
int desc_num = 1;
|
|
int ret;
|
|
|
|
cdata = (struct spicc_controller_data *)spi->controller_data;
|
|
spicc = spi_controller_get_devdata(spi->controller);
|
|
dev = &spicc->pdev->dev;
|
|
if (!spicc->dirspi_desc) {
|
|
dev_err(dev, "no descriptor for dirspi\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
idx = src >> 4;
|
|
src &= 0xf;
|
|
if (!IS_ERR_OR_NULL(spicc->trig_reg) &&
|
|
(src < DMA_TRIG_PWM || src > DMA_TRIG_GPIO)) {
|
|
dev_err(dev, "error trig source %d:%d\n", src, idx);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spicc_dma_trig_release(spicc);
|
|
|
|
if (!spicc_sem_down_read(spicc)) {
|
|
dev_err(dev, "controller busy\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
ret = meson_spicc_config(spicc, spi);
|
|
if (ret) {
|
|
spicc_sem_up_write(spicc);
|
|
return ret;
|
|
}
|
|
|
|
memset(&xfer, 0, sizeof(xfer));
|
|
xfer.tx_dma = tx_dma;
|
|
xfer.rx_dma = rx_dma;
|
|
xfer.len = len;
|
|
xfer.bits_per_word = spi->bits_per_word;
|
|
|
|
/* additional descriptor to achieve the cs_hold */
|
|
is_desc_cs_hold = meson_spicc_is_desc_cs_hold(spicc, desc_num, false);
|
|
if (is_desc_cs_hold)
|
|
desc_num++;
|
|
|
|
desc = spicc->dirspi_desc;
|
|
ret = spicc_config_desc_one_transfer(spicc, &xfer, true, desc, NULL, NULL);
|
|
if (ret) {
|
|
spicc_sem_up_write(spicc);
|
|
return ret;
|
|
}
|
|
|
|
desc = spicc->dirspi_desc;
|
|
meson_spicc_config_cs_setup(spicc, desc, &spicc->cs_setup);
|
|
desc = spicc->dirspi_desc + desc_num - 1;
|
|
meson_spicc_config_cs_hold(spicc, is_desc_cs_hold, desc, &spicc->cs_hold);
|
|
meson_spicc_dump(spicc, spicc->dirspi_desc, NULL, desc_num);
|
|
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
if (spicc->en_ping_pong) {
|
|
memcpy(spicc->dirspi_desc_pong, spicc->dirspi_desc,
|
|
(desc_num + 1) * sizeof(struct spicc_descriptor));
|
|
desc_pong = spicc->dirspi_desc_pong;
|
|
desc_pong->tx_paddr = tx_dma_pong;
|
|
desc_pong->rx_paddr = rx_dma_pong;
|
|
spicc_desc_pending(spicc, spicc->dirspi_desc_paddr, desc_num, true, true);
|
|
} else {
|
|
spicc_desc_pending(spicc, spicc->dirspi_desc_paddr, desc_num, true, false);
|
|
}
|
|
#else
|
|
spicc_desc_pending(spicc, spicc->dirspi_desc_paddr, desc_num, true, false);
|
|
#endif
|
|
|
|
if (!IS_ERR_OR_NULL(spicc->trig_reg)) {
|
|
in_sel = spicc->trig_in_selects[src - DMA_TRIG_PWM];
|
|
in_sel += idx;
|
|
if (cdata) {
|
|
if (cdata->dma_trig_delay > TRIG_DELAY_MAX)
|
|
delay = TRIG_DELAY_MAX;
|
|
else if (cdata->dma_trig_delay > TRIG_DELAY_MIN)
|
|
delay = cdata->dma_trig_delay;
|
|
}
|
|
|
|
writel(FIELD_PREP(TRIG_REG_IN_SEL_MASK, in_sel)
|
|
| FIELD_PREP(TRIG_REG_DELAY_CNT_MASK, delay),
|
|
spicc->trig_reg);
|
|
}
|
|
|
|
spicc_sem_up_write(spicc);
|
|
spicc->dirspi_status = DIRSPI_STA_READY;
|
|
dev_dbg(dev, "init trig success\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dirspi_dma_trig_start(struct spi_device *spi)
|
|
{
|
|
struct spicc_device *spicc;
|
|
|
|
spicc = spi_controller_get_devdata(spi->controller);
|
|
return spicc_dma_trig_start(spicc);
|
|
}
|
|
|
|
static int dirspi_dma_trig_stop(struct spi_device *spi)
|
|
{
|
|
struct spicc_device *spicc;
|
|
|
|
spicc = spi_controller_get_devdata(spi->controller);
|
|
return spicc_dma_trig_stop(spicc);
|
|
}
|
|
|
|
static int dirspi_dma_trig_release(struct spi_device *spi)
|
|
{
|
|
struct spicc_device *spicc;
|
|
|
|
spicc = spi_controller_get_devdata(spi->controller);
|
|
return spicc_dma_trig_release(spicc);
|
|
}
|
|
|
|
/*
|
|
* dmatrig_is_busy - check SPI xfer states
|
|
*
|
|
* Return TRUE if SPI Ready otherwise return the false.
|
|
*/
|
|
static bool dmatrig_is_busy(struct spi_device *spi)
|
|
{
|
|
struct spicc_device *spicc;
|
|
union spicc_cfg_start cfg_start;
|
|
u32 desc_h;
|
|
|
|
spicc = spi_controller_get_devdata(spi->controller);
|
|
|
|
cfg_start.d32 = spicc_readl(spicc, SPICC_REG_CFG_START);
|
|
desc_h = spicc_readl(spicc, SPICC_REG_DESC_LIST_H);
|
|
if (!cfg_start.b.pending && !(desc_h & SPICC_DESC_PENDING))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
static u32 dmatrig_ping_or_pong(struct spi_device *spi)
|
|
{
|
|
struct spicc_device *spicc;
|
|
|
|
spicc = spi_controller_get_devdata(spi->controller);
|
|
|
|
return atomic_read(&spicc->ping_or_pong);
|
|
}
|
|
#endif
|
|
#endif /* end of MESON_SPICC_HW_IF */
|
|
|
|
#ifdef CONFIG_SPICC_TEST
|
|
static DEVICE_ATTR_WO(test);
|
|
static DEVICE_ATTR_RW(testdev);
|
|
#endif
|
|
|
|
#define DIV_NUM (SPICC_CLK_DIV_MAX - SPICC_CLK_DIV_MIN + 1)
|
|
static struct clk_div_table linear_div_table[DIV_NUM + 1] = {
|
|
[0] = {0, 0 /* init flag */},
|
|
[DIV_NUM] = {0, 0 /* sentinel */}
|
|
};
|
|
|
|
static struct clk *meson_spicc_divider_clk_get(struct spicc_device *spicc)
|
|
{
|
|
struct device *dev = &spicc->pdev->dev;
|
|
struct clk_init_data init;
|
|
struct clk_divider *div;
|
|
const char *parent_names[1];
|
|
char name[32];
|
|
u32 val;
|
|
int i;
|
|
|
|
if (!linear_div_table[0].div)
|
|
for (i = 0; i < DIV_NUM; i++) {
|
|
linear_div_table[i].val = i + SPICC_CLK_DIV_MIN - 1;
|
|
linear_div_table[i].div = i + SPICC_CLK_DIV_MIN;
|
|
}
|
|
|
|
div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
|
|
if (!div)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
div->flags = CLK_DIVIDER_ROUND_CLOSEST;
|
|
div->reg = spicc->base + SPICC_REG_CFG_BUS;
|
|
div->shift = SPICC_CLK_DIV_SHIFT;
|
|
div->width = SPICC_CLK_DIV_WIDTH;
|
|
div->table = linear_div_table;
|
|
|
|
/* Register value should not be outside of the table */
|
|
val = spicc_readl(spicc, SPICC_REG_CFG_BUS);
|
|
val &= ~SPICC_CLK_DIV_MASK;
|
|
val |= FIELD_PREP(SPICC_CLK_DIV_MASK, SPICC_CLK_DIV_MIN - 1);
|
|
spicc_writel(spicc, val, SPICC_REG_CFG_BUS);
|
|
|
|
/* Register clk-divider */
|
|
parent_names[0] = __clk_get_name(spicc->spi_clk);
|
|
snprintf(name, sizeof(name), "%s_div", dev_name(dev));
|
|
init.name = name;
|
|
init.ops = &clk_divider_ops;
|
|
init.flags = CLK_SET_RATE_PARENT;
|
|
if (of_property_read_bool(dev->of_node, "assigned-clock-rates"))
|
|
init.flags = 0;
|
|
init.parent_names = parent_names;
|
|
init.num_parents = 1;
|
|
div->hw.init = &init;
|
|
|
|
return devm_clk_register(dev, &div->hw);
|
|
}
|
|
|
|
static int meson_spicc_probe(struct platform_device *pdev)
|
|
{
|
|
struct spi_controller *ctlr;
|
|
struct spicc_device *spicc;
|
|
struct meson_spicc_v2_data *match;
|
|
unsigned long irq_flags;
|
|
int ret, irq;
|
|
/* default trig reg map for t3x */
|
|
unsigned int trig_in_selects[5] = {0, 10, 13, 16, 19};
|
|
|
|
ctlr = __spi_alloc_controller(&pdev->dev, sizeof(*spicc),
|
|
of_property_read_bool(pdev->dev.of_node, "slave"));
|
|
if (!ctlr) {
|
|
dev_err(&pdev->dev, "controller allocation failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
spicc = spi_controller_get_devdata(ctlr);
|
|
spicc->controller = ctlr;
|
|
|
|
spicc->pdev = pdev;
|
|
platform_set_drvdata(pdev, spicc);
|
|
|
|
match = (struct meson_spicc_v2_data *)of_device_get_match_data(&pdev->dev);
|
|
spicc->data = devm_kzalloc(&pdev->dev, sizeof(*match), GFP_KERNEL);
|
|
spicc->data->idle_ctrl = match->idle_ctrl;
|
|
spicc->data->cs_hold_ctrl = match->cs_hold_ctrl;
|
|
spicc->data->cs_setup_extend_ctrl = match->cs_setup_extend_ctrl;
|
|
spicc->data->clk_gate_ctrl = match->clk_gate_ctrl;
|
|
spicc->data->cs_hold_ctrl_conflict_with_keep_ss =
|
|
match->cs_hold_ctrl_conflict_with_keep_ss;
|
|
|
|
spicc->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR_OR_NULL(spicc->base)) {
|
|
dev_err(&pdev->dev, "io resource mapping failed\n");
|
|
ret = PTR_ERR(spicc->base);
|
|
goto out_controller;
|
|
}
|
|
|
|
spicc->trig_reg = devm_platform_ioremap_resource(pdev, 1);
|
|
if (!IS_ERR_OR_NULL(spicc->trig_reg)) {
|
|
ret = of_property_read_u32_array(pdev->dev.of_node,
|
|
"trig_in_selects",
|
|
spicc->trig_in_selects,
|
|
ARRAY_SIZE(spicc->trig_in_selects));
|
|
if (ret)
|
|
memcpy(spicc->trig_in_selects, trig_in_selects,
|
|
sizeof(spicc->trig_in_selects));
|
|
dev_info(&pdev->dev, "trig resource %ps\n", spicc->trig_reg);
|
|
}
|
|
|
|
spicc->pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
if (IS_ERR_OR_NULL(spicc->pinctrl)) {
|
|
dev_err(&pdev->dev, "get pinctrl fail\n");
|
|
return PTR_ERR(spicc->pinctrl);
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
ret = irq;
|
|
goto out_controller;
|
|
}
|
|
|
|
irq_flags = of_property_read_bool(pdev->dev.of_node, "amlogic,level-high-to-gic")
|
|
? 0 : IRQF_TRIGGER_RISING;
|
|
ret = devm_request_irq(&pdev->dev, irq, meson_spicc_irq,
|
|
irq_flags, NULL, spicc);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "irq request failed\n");
|
|
goto out_controller;
|
|
}
|
|
|
|
if (of_property_read_bool(pdev->dev.of_node, "ping-pong"))
|
|
spicc->en_ping_pong = true;
|
|
|
|
spicc->sys_clk = devm_clk_get(&pdev->dev, "sys");
|
|
if (IS_ERR_OR_NULL(spicc->sys_clk))
|
|
dev_warn(&pdev->dev, "no sys clock\n");
|
|
else
|
|
clk_prepare_enable(spicc->sys_clk);
|
|
|
|
spicc->spi_clk = devm_clk_get(&pdev->dev, "spi");
|
|
if (IS_ERR_OR_NULL(spicc->spi_clk)) {
|
|
dev_err(&pdev->dev, "spi clock request failed\n");
|
|
ret = PTR_ERR(spicc->spi_clk);
|
|
goto out_controller;
|
|
}
|
|
ret = clk_prepare_enable(spicc->spi_clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "spi clock enable failed\n");
|
|
goto out_controller;
|
|
}
|
|
|
|
spicc->sclk = meson_spicc_divider_clk_get(spicc);
|
|
if (IS_ERR_OR_NULL(spicc->sclk)) {
|
|
dev_err(&pdev->dev, "register divider clk failed\n");
|
|
ret = PTR_ERR(spicc->sclk);
|
|
goto out_controller;
|
|
}
|
|
|
|
spin_lock_init(&spicc->lock);
|
|
init_completion(&spicc->completion);
|
|
|
|
spicc->cfg_spi.d32 = 0;
|
|
spicc->cfg_start.d32 = 0;
|
|
spicc->cfg_bus.d32 = 0;
|
|
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
spicc->cfg_spi.b.bus64_en = 1;
|
|
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(36));
|
|
if (!ret)
|
|
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(36));
|
|
dev_warn(&pdev->dev, "set dma mask %s\n", ret ? "failed" : "success");
|
|
if (ret)
|
|
goto out_clk;
|
|
#endif
|
|
|
|
spicc->cfg_spi.b.flash_wp_pin_en = 1;
|
|
spicc->cfg_spi.b.flash_hold_pin_en = 1;
|
|
if (spi_controller_is_target(ctlr))
|
|
spicc->cfg_spi.b.slave_en = true;
|
|
/* default pending */
|
|
spicc->cfg_start.b.pending = 1;
|
|
|
|
device_reset_optional(&pdev->dev);
|
|
ctlr->num_chipselect = MESON_SPI_CS_CNT_MAX;
|
|
ctlr->use_gpio_descriptors = true;
|
|
ctlr->dev.of_node = pdev->dev.of_node;
|
|
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 = meson_spicc_setup;
|
|
ctlr->cleanup = meson_spicc_cleanup;
|
|
ctlr->prepare_message = meson_spicc_prepare_message;
|
|
ctlr->unprepare_transfer_hardware = meson_spicc_unprepare_transfer;
|
|
ctlr->transfer_one_message = meson_spicc_transfer_one_message;
|
|
ctlr->target_abort = meson_spicc_slave_abort;
|
|
ctlr->can_dma = meson_spicc_can_dma;
|
|
ctlr->max_dma_len = SPICC_BLOCK_MAX;
|
|
dma_set_max_seg_size(&pdev->dev, SPICC_BLOCK_MAX);
|
|
ret = devm_spi_register_controller(&pdev->dev, ctlr);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "spi controller registration failed\n");
|
|
goto out_clk;
|
|
}
|
|
|
|
#ifdef MESON_SPICC_HW_IF
|
|
/* additional descriptor to achieve the cs_hold */
|
|
spicc->dirspi_desc_len = sizeof(struct spicc_descriptor) * 2;
|
|
spicc->dirspi_desc = dma_alloc_coherent(&pdev->dev,
|
|
spicc->dirspi_desc_len,
|
|
&spicc->dirspi_desc_paddr,
|
|
GFP_KERNEL | GFP_DMA);
|
|
#ifdef CONFIG_SPI_PING_PONG
|
|
if (spicc->en_ping_pong) {
|
|
atomic_set(&spicc->ping_or_pong, 1);
|
|
spicc->dirspi_desc_pong = dma_alloc_coherent(&pdev->dev,
|
|
spicc->dirspi_desc_len,
|
|
&spicc->dirspi_desc_paddr_pong,
|
|
GFP_KERNEL | GFP_DMA);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#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:
|
|
if (spicc->sys_clk)
|
|
clk_disable_unprepare(spicc->sys_clk);
|
|
clk_disable_unprepare(spicc->spi_clk);
|
|
out_controller:
|
|
spi_controller_put(ctlr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void meson_spicc_remove(struct platform_device *pdev)
|
|
{
|
|
struct spicc_device *spicc = platform_get_drvdata(pdev);
|
|
|
|
if (spicc->sys_clk)
|
|
clk_disable_unprepare(spicc->sys_clk);
|
|
clk_disable_unprepare(spicc->spi_clk);
|
|
|
|
#ifdef MESON_SPICC_HW_IF
|
|
if (spicc->dirspi_desc)
|
|
dma_free_coherent(&pdev->dev,
|
|
spicc->dirspi_desc_len,
|
|
spicc->dirspi_desc,
|
|
spicc->dirspi_desc_paddr);
|
|
#endif
|
|
|
|
#ifdef CONFIG_SPICC_TEST
|
|
device_remove_file(&pdev->dev, &dev_attr_test);
|
|
device_remove_file(&pdev->dev, &dev_attr_testdev);
|
|
#endif
|
|
}
|
|
|
|
static int meson_spicc_off(struct spicc_device *spicc)
|
|
{
|
|
pinctrl_pm_select_sleep_state(&spicc->pdev->dev);
|
|
clk_disable_unprepare(spicc->spi_clk);
|
|
if (spicc->sys_clk)
|
|
clk_disable_unprepare(spicc->sys_clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int meson_spicc_on(struct spicc_device *spicc)
|
|
{
|
|
if (spicc->sys_clk)
|
|
clk_prepare_enable(spicc->sys_clk);
|
|
clk_prepare_enable(spicc->spi_clk);
|
|
pinctrl_pm_select_default_state(&spicc->pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_HIBERNATION
|
|
static int meson_spicc_freeze(struct device *dev)
|
|
{
|
|
struct spicc_device *spicc = dev_get_drvdata(dev);
|
|
|
|
return meson_spicc_off(spicc);
|
|
}
|
|
|
|
static int meson_spicc_thaw(struct device *dev)
|
|
{
|
|
struct spicc_device *spicc = dev_get_drvdata(dev);
|
|
|
|
return meson_spicc_on(spicc);
|
|
}
|
|
|
|
static int meson_spicc_restore(struct device *dev)
|
|
{
|
|
struct spicc_device *spicc = dev_get_drvdata(dev);
|
|
|
|
return meson_spicc_on(spicc);
|
|
}
|
|
#endif
|
|
|
|
static void meson_spicc_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct spicc_device *spicc = platform_get_drvdata(pdev);
|
|
|
|
meson_spicc_off(spicc);
|
|
}
|
|
|
|
static int meson_spicc_suspend(struct device *dev)
|
|
{
|
|
struct spicc_device *spicc = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = spi_controller_suspend(spicc->controller);
|
|
if (!ret)
|
|
ret = meson_spicc_off(spicc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int meson_spicc_resume(struct device *dev)
|
|
{
|
|
struct spicc_device *spicc = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = meson_spicc_on(spicc);
|
|
if (!ret)
|
|
ret = spi_controller_resume(spicc->controller);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct dev_pm_ops meson_spicc_pm_ops = {
|
|
.suspend = meson_spicc_suspend,
|
|
.resume = meson_spicc_resume,
|
|
#ifdef CONFIG_HIBERNATION
|
|
.freeze = meson_spicc_freeze,
|
|
.thaw = meson_spicc_thaw,
|
|
.restore = meson_spicc_restore,
|
|
#endif
|
|
};
|
|
|
|
static struct meson_spicc_v2_data meson_spicc_a4_data __initdata = {
|
|
.idle_ctrl = false,
|
|
.cs_hold_ctrl = false,
|
|
.cs_setup_extend_ctrl = false,
|
|
.clk_gate_ctrl = false,
|
|
.cs_hold_ctrl_conflict_with_keep_ss = true,
|
|
};
|
|
|
|
static struct meson_spicc_v2_data meson_spicc_gxlx4_data __initdata = {
|
|
.idle_ctrl = true,
|
|
.cs_hold_ctrl = true,
|
|
.cs_setup_extend_ctrl = true,
|
|
.clk_gate_ctrl = false,
|
|
.cs_hold_ctrl_conflict_with_keep_ss = true,
|
|
};
|
|
|
|
static struct meson_spicc_v2_data meson_spicc_t6x_data __initdata = {
|
|
.idle_ctrl = true,
|
|
.cs_hold_ctrl = true,
|
|
.cs_setup_extend_ctrl = true,
|
|
.clk_gate_ctrl = false,
|
|
.cs_hold_ctrl_conflict_with_keep_ss = false,
|
|
};
|
|
|
|
static const struct of_device_id meson_spicc_v2_of_match[] = {
|
|
{
|
|
.compatible = "amlogic,meson-a4-spicc-v2",
|
|
.data = &meson_spicc_a4_data,
|
|
},
|
|
{
|
|
.compatible = "amlogic,meson-gxlx4-spicc-v2",
|
|
.data = &meson_spicc_gxlx4_data,
|
|
},
|
|
{
|
|
.compatible = "amlogic,meson-t6x-spicc-v2",
|
|
.data = &meson_spicc_t6x_data,
|
|
},
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, meson_spicc_v2_of_match);
|
|
|
|
struct platform_driver meson_spicc_v2_driver = {
|
|
.probe = meson_spicc_probe,
|
|
.remove = meson_spicc_remove,
|
|
.shutdown = meson_spicc_shutdown,
|
|
.driver = {
|
|
.name = "meson-spicc-v2",
|
|
.of_match_table = of_match_ptr(meson_spicc_v2_of_match),
|
|
.pm = &meson_spicc_pm_ops,
|
|
},
|
|
};
|
|
|
|
#ifndef MODULE
|
|
module_platform_driver(meson_spicc_v2_driver);
|
|
#endif
|
|
|
|
MODULE_DESCRIPTION("Meson SPI Communication Controller(v2) driver");
|
|
MODULE_AUTHOR("Sunny.luo <[email protected]>");
|
|
MODULE_LICENSE("GPL");
|