507 lines
14 KiB
C
507 lines
14 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/clk-provider.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/platform_device.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/types.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/amlogic/vmem.h>
|
|
|
|
/* Register Map */
|
|
#define SPICC_RXDATA 0x00
|
|
|
|
#define SPICC_TXDATA 0x04
|
|
|
|
#define SPICC_CONREG 0x08
|
|
#define SPICC_ENABLE BIT(0)
|
|
#define SPICC_MODE_MASTER BIT(1)
|
|
#define SPICC_XCH BIT(2)
|
|
#define SPICC_SMC BIT(3)
|
|
#define SPICC_POL BIT(4)
|
|
#define SPICC_PHA BIT(5)
|
|
#define SPICC_SSCTL BIT(6)
|
|
#define SPICC_SSPOL BIT(7)
|
|
#define SPICC_DRCTL_MASK GENMASK(9, 8)
|
|
#define SPICC_DRCTL_IGNORE 0
|
|
#define SPICC_DRCTL_FALLING 1
|
|
#define SPICC_DRCTL_LOWLEVEL 2
|
|
#define SPICC_CS_MASK GENMASK(13, 12)
|
|
#define SPICC_DATARATE_MASK GENMASK(18, 16)
|
|
#define SPICC_FIX_FACTOR_MULT 1
|
|
#define SPICC_FIX_FACTOR_DIV 4
|
|
#define SPICC_BITLENGTH_MASK GENMASK(24, 19)
|
|
#define SPICC_BURSTLENGTH_MASK GENMASK(31, 25)
|
|
|
|
#define SPICC_INTREG 0x0c
|
|
#define SPICC_TE_EN BIT(0) /* TX FIFO Empty Interrupt */
|
|
#define SPICC_TH_EN BIT(1) /* TX FIFO Half-Full Interrupt */
|
|
#define SPICC_TF_EN BIT(2) /* TX FIFO Full Interrupt */
|
|
#define SPICC_RR_EN BIT(3) /* RX FIFO Ready Interrupt */
|
|
#define SPICC_RH_EN BIT(4) /* RX FIFO Half-Full Interrupt */
|
|
#define SPICC_RF_EN BIT(5) /* RX FIFO Full Interrupt */
|
|
#define SPICC_RO_EN BIT(6) /* RX FIFO Overflow Interrupt */
|
|
#define SPICC_TC_EN BIT(7) /* Transfer Complete Interrupt */
|
|
|
|
#define SPICC_DMAREG 0x10
|
|
#define SPICC_DMA_ENABLE BIT(0)
|
|
/* When txfifo_count<threshold, request a read(dma->txfifo) burst */
|
|
#define SPICC_TXFIFO_THRESHOLD_MASK GENMASK(5, 1)
|
|
#define SPICC_TXFIFO_THRESHOLD_DEFAULT 10
|
|
/* When rxfifo count>threshold, request a write(rxfifo->dma) burst */
|
|
#define SPICC_RXFIFO_THRESHOLD_MASK GENMASK(10, 6)
|
|
#define SPICC_READ_BURST_MASK GENMASK(14, 11)
|
|
#define SPICC_WRITE_BURST_MASK GENMASK(18, 15)
|
|
#define SPICC_DMA_URGENT BIT(19)
|
|
#define SPICC_DMA_THREADID_MASK GENMASK(25, 20)
|
|
#define SPICC_DMA_BURSTNUM_MASK GENMASK(31, 26)
|
|
|
|
#define SPICC_STATREG 0x14
|
|
#define SPICC_TE BIT(0) /* TX FIFO Empty Interrupt */
|
|
#define SPICC_TH BIT(1) /* TX FIFO Half-Full Interrupt */
|
|
#define SPICC_TF BIT(2) /* TX FIFO Full Interrupt */
|
|
#define SPICC_RR BIT(3) /* RX FIFO Ready Interrupt */
|
|
#define SPICC_RH BIT(4) /* RX FIFO Half-Full Interrupt */
|
|
#define SPICC_RF BIT(5) /* RX FIFO Full Interrupt */
|
|
#define SPICC_RO BIT(6) /* RX FIFO Overflow Interrupt */
|
|
#define SPICC_TC BIT(7) /* Transfer Complete Interrupt */
|
|
|
|
#define SPICC_PERIODREG 0x18
|
|
#define SPICC_PERIOD GENMASK(14, 0) /* Wait cycles */
|
|
|
|
#define SPICC_TESTREG 0x1c
|
|
#define SPICC_TXCNT_MASK GENMASK(4, 0) /* TX FIFO Counter */
|
|
#define SPICC_RXCNT_MASK GENMASK(9, 5) /* RX FIFO Counter */
|
|
#define SPICC_SMSTATUS_MASK GENMASK(12, 10) /* State Machine Status */
|
|
#define SPICC_LBC BIT(14) /* Loop Back Control */
|
|
#define SPICC_SWAP BIT(15) /* RX FIFO Data Swap */
|
|
#define SPICC_MO_DELAY_MASK GENMASK(17, 16) /* Master Output Delay */
|
|
#define SPICC_MO_NO_DELAY 0
|
|
#define SPICC_MO_DELAY_1_CYCLE 1
|
|
#define SPICC_MO_DELAY_2_CYCLE 2
|
|
#define SPICC_MO_DELAY_3_CYCLE 3
|
|
#define SPICC_MI_DELAY_MASK GENMASK(19, 18) /* Master Input Delay */
|
|
#define SPICC_MI_NO_DELAY 0
|
|
#define SPICC_MI_DELAY_1_CYCLE 1
|
|
#define SPICC_MI_DELAY_2_CYCLE 2
|
|
#define SPICC_MI_DELAY_3_CYCLE 3
|
|
#define SPICC_MI_CAP_DELAY_MASK GENMASK(21, 20) /* Master Capture Delay */
|
|
#define SPICC_CAP_AHEAD_2_CYCLE 0
|
|
#define SPICC_CAP_AHEAD_1_CYCLE 1
|
|
#define SPICC_CAP_NO_DELAY 2
|
|
#define SPICC_CAP_DELAY_1_CYCLE 3
|
|
#define SPICC_FIFORST_MASK GENMASK(23, 22) /* FIFO Softreset */
|
|
|
|
#define SPICC_DRADDR 0x20 /* Read Address of DMA */
|
|
|
|
#define SPICC_DWADDR 0x24 /* Write Address of DMA */
|
|
|
|
#define SPICC_LD_CNTL0 0x28
|
|
#define SPICC_LD_CNTL1 0x2c
|
|
|
|
#define SPICC_ENH_CTL0 0x38 /* Enhanced Feature 0 */
|
|
#define SPICC_ENH_CS_PRE_DELAY_MASK GENMASK(15, 0)
|
|
#define SPICC_ENH_DATARATE_MASK GENMASK(23, 16)
|
|
#define SPICC_ENH_FIX_FACTOR_MULT 1
|
|
#define SPICC_ENH_FIX_FACTOR_DIV 2
|
|
#define SPICC_ENH_DATARATE_EN BIT(24)
|
|
#define SPICC_ENH_MOSI_OEN BIT(25)
|
|
#define SPICC_ENH_CLK_OEN BIT(26)
|
|
#define SPICC_ENH_CS_OEN BIT(27)
|
|
#define SPICC_ENH_OEN GENMASK(27, 25)
|
|
#define SPICC_ENH_CS_PRE_DELAY_EN BIT(28)
|
|
#define SPICC_ENH_MAIN_CLK_AO BIT(29)
|
|
|
|
#define SPICC_ENH_CTL1 0x3c /* Enhanced Feature 1 */
|
|
#define SPICC_ENH_MI_CAP_DELAY_EN BIT(0)
|
|
#define SPICC_ENH_MI_CAP_DELAY_MASK GENMASK(9, 1)
|
|
#define SPICC_ENH_SI_CAP_DELAY_EN BIT(14) /* slave mode */
|
|
#define SPICC_ENH_DELAY_EN BIT(15)
|
|
#define SPICC_ENH_SI_DELAY_EN BIT(16) /* slave mode */
|
|
#define SPICC_ENH_SI_DELAY_MASK GENMASK(19, 17) /* slave mode */
|
|
#define SPICC_ENH_MI_DELAY_EN BIT(20)
|
|
#define SPICC_ENH_MI_DELAY_MASK GENMASK(23, 21)
|
|
#define SPICC_ENH_MO_DELAY_EN BIT(24)
|
|
#define SPICC_ENH_MO_DELAY_MASK GENMASK(27, 25)
|
|
#define SPICC_ENH_MO_OEN_DELAY_EN BIT(28)
|
|
#define SPICC_ENH_MO_OEN_DELAY_MASK GENMASK(31, 29)
|
|
|
|
#define SPICC_ENH_CTL2 0x40 /* Enhanced Feature */
|
|
#define SPICC_ENH_TI_DELAY_MASK GENMASK(14, 0)
|
|
#define SPICC_ENH_TI_DELAY_EN BIT(15)
|
|
#define SPICC_ENH_TT_DELAY_MASK GENMASK(30, 16)
|
|
#define SPICC_ENH_TT_DELAY_EN BIT(31)
|
|
|
|
static inline void writel_bits_relaxed(u32 mask, u32 val, void __iomem *addr)
|
|
{
|
|
writel_relaxed((readl_relaxed(addr) & ~(mask)) | (val), addr);
|
|
}
|
|
|
|
#define SPICC_BYTES_PER_WORD 8
|
|
|
|
#define SPICC_STATE_IDLE 0
|
|
#define SPICC_STATE_CMD 1
|
|
#define SPICC_STATE_DATA 2
|
|
|
|
#define VMEM_CMD_WRITE 1
|
|
#define VMEM_CMD_READ 2
|
|
|
|
#define VMEM_DEV_OF(d32) (((d32) >> 0) & 0x3)
|
|
#define VMEM_CMD_OF(d32) (((d32) >> 2) & 0xF)
|
|
#define VMEM_LEN_OF(d32) (((d32) >> 6) & 0x3FF)
|
|
#define VMEM_OFFSET_OF(d32) (((d32) >> 16) & 0xFFFF)
|
|
|
|
struct slave_spicc {
|
|
void __iomem *base;
|
|
spinlock_t lock; /*for spicc interrupt handle */
|
|
struct vmem_controller *vmemctlr;
|
|
struct vmem_device *vmemdev;
|
|
struct workqueue_struct *xfer_wq;
|
|
struct work_struct xfer_work;
|
|
struct completion xfer_completion;
|
|
u16 mode;
|
|
u8 cs_num;
|
|
|
|
u8 state;
|
|
u8 vmem_cmd_val;
|
|
u16 vmem_offset;
|
|
u16 vmem_len;
|
|
u8 *vmem_buf;
|
|
u16 vmem_count;
|
|
#ifdef SSPICC_TEST_ENTRY
|
|
struct class cls;
|
|
#endif
|
|
};
|
|
|
|
static void sspicc_hw_init(struct slave_spicc *spicc)
|
|
{
|
|
u32 conf;
|
|
|
|
/* Set slave mode and enable controller */
|
|
conf = SPICC_ENABLE;
|
|
/* Setup transfer mode */
|
|
if (spicc->mode & SPI_CPOL)
|
|
conf |= SPICC_POL;
|
|
if (spicc->mode & SPI_CPHA)
|
|
conf |= SPICC_PHA;
|
|
if (spicc->mode & SPI_CS_HIGH)
|
|
conf |= SPICC_SSPOL;
|
|
if (spicc->mode & SPI_READY)
|
|
conf |= FIELD_PREP(SPICC_DRCTL_MASK, SPICC_DRCTL_LOWLEVEL);
|
|
/* Select CS 0 */
|
|
conf |= FIELD_PREP(SPICC_CS_MASK, spicc->cs_num);
|
|
/* Setup word width */
|
|
conf |= FIELD_PREP(SPICC_BITLENGTH_MASK, 64 - 1);
|
|
writel_relaxed(conf, spicc->base + SPICC_CONREG);
|
|
|
|
/* Disable all IRQs */
|
|
writel_relaxed(SPICC_RR_EN, spicc->base + SPICC_INTREG);
|
|
|
|
/* Disable DMA */
|
|
writel_relaxed(0, spicc->base + SPICC_DMAREG);
|
|
|
|
/* Setup no wait cycles by default */
|
|
writel_relaxed(0, spicc->base + SPICC_PERIODREG);
|
|
|
|
/* Set sampling time point at middle */
|
|
writel_relaxed(SPICC_ENH_DELAY_EN | SPICC_ENH_SI_CAP_DELAY_EN,
|
|
spicc->base + SPICC_ENH_CTL1);
|
|
|
|
//writel_relaxed(SPICC_ENH_OEN, spicc->base + SPICC_ENH_CTL0);
|
|
}
|
|
|
|
static inline bool sspicc_txfull(struct slave_spicc *spicc)
|
|
{
|
|
return !!FIELD_GET(SPICC_TF,
|
|
readl_relaxed(spicc->base + SPICC_STATREG));
|
|
}
|
|
|
|
static inline bool sspicc_rxready(struct slave_spicc *spicc)
|
|
{
|
|
return FIELD_GET(SPICC_RH | SPICC_RR | SPICC_RF,
|
|
readl_relaxed(spicc->base + SPICC_STATREG));
|
|
}
|
|
|
|
static u32 sspicc_reset_fifo(struct slave_spicc *spicc)
|
|
{
|
|
u32 data = 0;
|
|
|
|
writel_bits_relaxed(SPICC_ENH_MAIN_CLK_AO,
|
|
SPICC_ENH_MAIN_CLK_AO,
|
|
spicc->base + SPICC_ENH_CTL0);
|
|
|
|
writel_bits_relaxed(SPICC_FIFORST_MASK,
|
|
FIELD_PREP(SPICC_FIFORST_MASK, 3),
|
|
spicc->base + SPICC_TESTREG);
|
|
|
|
while (sspicc_rxready(spicc))
|
|
data += readl_relaxed(spicc->base + SPICC_RXDATA);
|
|
data += readl_relaxed(spicc->base + SPICC_RXDATA);
|
|
data += readl_relaxed(spicc->base + SPICC_RXDATA);
|
|
|
|
writel_bits_relaxed(SPICC_ENH_MAIN_CLK_AO, 0,
|
|
spicc->base + SPICC_ENH_CTL0);
|
|
|
|
return data;
|
|
}
|
|
|
|
static void sspicc_vmem_data_out(struct slave_spicc *spicc)
|
|
{
|
|
u64 data = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < SPICC_BYTES_PER_WORD; i++) {
|
|
data <<= 8;
|
|
if (spicc->vmem_count < spicc->vmem_len) {
|
|
data |= *spicc->vmem_buf++;
|
|
spicc->vmem_count++;
|
|
}
|
|
}
|
|
|
|
writel_relaxed(data >> 32, spicc->base + SPICC_TXDATA);
|
|
writel_relaxed(data, spicc->base + SPICC_TXDATA);
|
|
}
|
|
|
|
static void sspicc_vmem_data_in(struct slave_spicc *spicc, u64 data)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SPICC_BYTES_PER_WORD; i++) {
|
|
if (spicc->vmem_count < spicc->vmem_len) {
|
|
*spicc->vmem_buf++ = data;
|
|
data >>= 8;
|
|
spicc->vmem_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sspicc_vmem_ready(struct slave_spicc *spicc)
|
|
{
|
|
sspicc_reset_fifo(spicc);
|
|
writel_bits_relaxed(SPICC_BITLENGTH_MASK,
|
|
FIELD_PREP(SPICC_BITLENGTH_MASK, 31),
|
|
spicc->base + SPICC_CONREG);
|
|
|
|
spicc->state = SPICC_STATE_CMD;
|
|
}
|
|
|
|
static int sspicc_vmem_cmd(struct slave_spicc *spicc)
|
|
{
|
|
struct vmem_device *vmemdev;
|
|
u32 data;
|
|
int ret = -ENODEV;
|
|
|
|
data = readl_relaxed(spicc->base + SPICC_RXDATA);
|
|
vmemdev = vmem_get_device(spicc->vmemctlr, VMEM_DEV_OF(data));
|
|
if (vmemdev) {
|
|
spicc->vmem_cmd_val = VMEM_CMD_OF(data);
|
|
spicc->vmem_offset = VMEM_OFFSET_OF(data);
|
|
spicc->vmem_len = VMEM_LEN_OF(data);
|
|
spicc->vmem_buf = vmemdev->mem + spicc->vmem_offset;
|
|
spicc->vmem_count = 0;
|
|
spicc->vmemdev = vmemdev;
|
|
if (vmemdev->cmd_notify)
|
|
vmemdev->cmd_notify(spicc->vmem_cmd_val,
|
|
spicc->vmem_offset,
|
|
spicc->vmem_len);
|
|
spicc->state = SPICC_STATE_DATA;
|
|
|
|
sspicc_reset_fifo(spicc);
|
|
writel_bits_relaxed(SPICC_BITLENGTH_MASK,
|
|
FIELD_PREP(SPICC_BITLENGTH_MASK, 63),
|
|
spicc->base + SPICC_CONREG);
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sspicc_xfer_work(struct work_struct *work)
|
|
{
|
|
struct slave_spicc *spicc =
|
|
container_of(work, struct slave_spicc, xfer_work);
|
|
|
|
while (1) {
|
|
if (!wait_for_completion_timeout(&spicc->xfer_completion,
|
|
msecs_to_jiffies(500)))
|
|
break;
|
|
|
|
if (spicc->state == SPICC_STATE_IDLE)
|
|
break;
|
|
}
|
|
|
|
if (spicc->vmemdev->data_notify)
|
|
spicc->vmemdev->data_notify(spicc->vmem_cmd_val,
|
|
spicc->vmem_offset,
|
|
spicc->vmem_count);
|
|
|
|
sspicc_vmem_ready(spicc);
|
|
}
|
|
|
|
static irqreturn_t sspicc_irq(int irq, void *context)
|
|
{
|
|
struct slave_spicc *spicc = (void *)context;
|
|
unsigned long flags;
|
|
u64 data;
|
|
|
|
spin_lock_irqsave(&spicc->lock, flags);
|
|
|
|
if (spicc->state == SPICC_STATE_CMD) {
|
|
if (!sspicc_vmem_cmd(spicc)) {
|
|
if (spicc->vmem_cmd_val == VMEM_CMD_READ)
|
|
sspicc_vmem_data_out(spicc);
|
|
queue_work(spicc->xfer_wq, &spicc->xfer_work);
|
|
}
|
|
} else if (spicc->state == SPICC_STATE_DATA) {
|
|
data = readl_relaxed(spicc->base + SPICC_RXDATA);
|
|
data |= (u64)readl_relaxed(spicc->base + SPICC_RXDATA) << 32;
|
|
if (spicc->vmem_cmd_val == VMEM_CMD_READ) {
|
|
if (spicc->vmem_count >= spicc->vmem_len)
|
|
spicc->state = SPICC_STATE_IDLE;
|
|
else
|
|
sspicc_vmem_data_out(spicc);
|
|
}
|
|
|
|
else if (spicc->vmem_cmd_val == VMEM_CMD_WRITE) {
|
|
sspicc_vmem_data_in(spicc, data);
|
|
if (spicc->vmem_count >= spicc->vmem_len)
|
|
spicc->state = SPICC_STATE_IDLE;
|
|
}
|
|
|
|
complete(&spicc->xfer_completion);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&spicc->lock, flags);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int sspicc_probe(struct platform_device *pdev)
|
|
{
|
|
struct vmem_controller *vmemctlr;
|
|
struct slave_spicc *spicc;
|
|
struct resource *res;
|
|
struct clk *clk;
|
|
int ret, irq;
|
|
|
|
vmemctlr = vmem_create_controller(&pdev->dev, sizeof(*spicc));
|
|
if (!vmemctlr) {
|
|
dev_err(&pdev->dev, "vmem controller alloc failed\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
spicc = dev_get_drvdata(&vmemctlr->dev);
|
|
spicc->vmemctlr = vmemctlr;
|
|
platform_set_drvdata(pdev, spicc);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
spicc->base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(spicc->base)) {
|
|
ret = PTR_ERR(spicc->base);
|
|
dev_err(&pdev->dev, "io resource mapping failed\n");
|
|
goto out;
|
|
}
|
|
|
|
/* Get core clk */
|
|
clk = devm_clk_get(&pdev->dev, "core");
|
|
if (WARN_ON(IS_ERR(clk))) {
|
|
ret = PTR_ERR(clk);
|
|
dev_err(&pdev->dev, "get core clk failed\n");
|
|
goto out;
|
|
}
|
|
clk_prepare_enable(clk);
|
|
|
|
/* Get composite clk */
|
|
clk = devm_clk_get(&pdev->dev, "comp");
|
|
if (WARN_ON(IS_ERR(clk))) {
|
|
ret = PTR_ERR(clk);
|
|
dev_err(&pdev->dev, "get comp clk failed\n");
|
|
goto out;
|
|
}
|
|
clk_prepare_enable(clk);
|
|
clk_set_rate(clk, 200000000);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
ret = devm_request_irq(&pdev->dev, irq, sspicc_irq, 0,
|
|
dev_name(&pdev->dev), spicc);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "irq request failed\n");
|
|
goto out;
|
|
}
|
|
|
|
spin_lock_init(&spicc->lock);
|
|
spicc->mode = 0;
|
|
spicc->cs_num = 0;
|
|
sspicc_hw_init(spicc);
|
|
sspicc_vmem_ready(spicc);
|
|
|
|
spicc->xfer_wq = create_singlethread_workqueue(dev_name(&pdev->dev));
|
|
if (!spicc->xfer_wq) {
|
|
dev_err(&pdev->dev, "create workqueue failed\n");
|
|
ret = PTR_ERR(spicc->xfer_wq);
|
|
goto out;
|
|
}
|
|
INIT_WORK(&spicc->xfer_work, sspicc_xfer_work);
|
|
init_completion(&spicc->xfer_completion);
|
|
dev_info(&pdev->dev, "sspicc registration success\n");
|
|
|
|
#ifdef SSPICC_TEST_ENTRY
|
|
spicc->cls.name = dev_name(&pdev->dev);
|
|
spicc->cls.class_attrs = spicc_class_attrs;
|
|
if (class_register(&spicc->cls) < 0)
|
|
goto out;
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
out:
|
|
vmem_destroy_controller(vmemctlr);
|
|
dev_err(&pdev->dev, "sspicc registration failed(%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static void sspicc_remove(struct platform_device *pdev)
|
|
{
|
|
struct slave_spicc *spicc = platform_get_drvdata(pdev);
|
|
|
|
/* Disable SPI */
|
|
writel(0, spicc->base + SPICC_CONREG);
|
|
destroy_workqueue(spicc->xfer_wq);
|
|
|
|
vmem_destroy_controller(spicc->vmemctlr);
|
|
}
|
|
|
|
static const struct of_device_id sspicc_of_match[] = {
|
|
{.compatible = "amlogic,slave-spicc"},
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sspicc_of_match);
|
|
|
|
struct platform_driver sspicc_driver = {
|
|
.probe = sspicc_probe,
|
|
.remove = sspicc_remove,
|
|
.driver = {
|
|
.name = "slave-spicc",
|
|
.of_match_table = of_match_ptr(sspicc_of_match),
|
|
},
|
|
};
|
|
|
|
//module_platform_driver(sspicc_driver);
|
|
|
|
MODULE_DESCRIPTION("Meson Slave SPICC driver");
|
|
MODULE_AUTHOR("Amlogic");
|
|
MODULE_LICENSE("GPL");
|