| /* |
| * drivers/amlogic/spicc/sspicc.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| */ |
| |
| #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) /* Transfert 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) /* Transfert 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) |
| { |
| u32 reg_val = readl_relaxed(addr); |
| |
| reg_val &= ~mask; |
| reg_val |= val; |
| writel_relaxed(reg_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 interrupt */ |
| 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) |
| { |
| unsigned int byte_shift = (SPICC_BYTES_PER_WORD - 1) * 8; |
| int i; |
| |
| for (i = 0; i < SPICC_BYTES_PER_WORD; i++) { |
| if (spicc->vmem_count < spicc->vmem_len) { |
| *spicc->vmem_buf++ = (data >> byte_shift) & 0xff; |
| byte_shift -= 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, u32 data) |
| { |
| struct vmem_device *vmemdev; |
| int ret = -ENODEV; |
| |
| 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; |
| u32 data32; |
| u64 data64; |
| |
| spin_lock_irqsave(&spicc->lock, flags); |
| |
| data32 = readl_relaxed(spicc->base + SPICC_RXDATA); |
| data64 = (u64)data32 << 32; |
| /* must read more once though in 32bits */ |
| data32 = readl_relaxed(spicc->base + SPICC_RXDATA); |
| data64 |= (u64)data32; |
| if (spicc->state == SPICC_STATE_CMD) { |
| if (!sspicc_vmem_cmd(spicc, data32)) { |
| 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) { |
| 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, data64); |
| 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; |
| class_register(&spicc->cls); |
| #endif |
| |
| return 0; |
| |
| out: |
| vmem_destroy_controller(vmemctlr); |
| dev_err(&pdev->dev, "sspicc registration failed(%d)\n", ret); |
| return ret; |
| } |
| |
| static int 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); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id sspicc_of_match[] = { |
| {.compatible = "amlogic,slave-spicc"}, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, sspicc_of_match); |
| |
| static 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"); |