| 1 | |
| 2 |
|
| 3 | #include <linux/module.h>
|
| 4 | #include <linux/platform_device.h>
|
| 5 | #include <linux/spi/spi.h>
|
| 6 |
|
| 7 | struct rtspi {
|
| 8 | void __iomem *base;
|
| 9 | };
|
| 10 |
|
| 11 |
|
| 12 | #define RTL_SPI_SFCR 0x00
|
| 13 | #define RTL_SPI_SFCR_RBO BIT(28)
|
| 14 | #define RTL_SPI_SFCR_WBO BIT(27)
|
| 15 |
|
| 16 |
|
| 17 | #define RTL_SPI_SFCSR 0x08
|
| 18 | #define RTL_SPI_SFCSR_CSB0 BIT(31)
|
| 19 | #define RTL_SPI_SFCSR_CSB1 BIT(30)
|
| 20 | #define RTL_SPI_SFCSR_RDY BIT(27)
|
| 21 | #define RTL_SPI_SFCSR_CS BIT(24)
|
| 22 | #define RTL_SPI_SFCSR_LEN_MASK ~(0x03 << 28)
|
| 23 | #define RTL_SPI_SFCSR_LEN1 (0x00 << 28)
|
| 24 | #define RTL_SPI_SFCSR_LEN4 (0x03 << 28)
|
| 25 |
|
| 26 |
|
| 27 | #define RTL_SPI_SFDR 0x0c
|
| 28 |
|
| 29 | #define REG(x) (rtspi->base + x)
|
| 30 |
|
| 31 |
|
| 32 | static void rt_set_cs(struct spi_device *spi, bool active)
|
| 33 | {
|
| 34 | struct rtspi *rtspi = spi_controller_get_devdata(spi->controller);
|
| 35 | u32 value;
|
| 36 |
|
| 37 |
|
| 38 | value = __raw_readl(REG(RTL_SPI_SFCSR));
|
| 39 | if (active)
|
| 40 | value |= RTL_SPI_SFCSR_CSB0;
|
| 41 | else
|
| 42 | value &= ~RTL_SPI_SFCSR_CSB0;
|
| 43 | __raw_writel(value, REG(RTL_SPI_SFCSR));
|
| 44 | }
|
| 45 |
|
| 46 | static void set_size(struct rtspi *rtspi, int size)
|
| 47 | {
|
| 48 | u32 value;
|
| 49 |
|
| 50 | value = __raw_readl(REG(RTL_SPI_SFCSR));
|
| 51 | value &= RTL_SPI_SFCSR_LEN_MASK;
|
| 52 | if (size == 4)
|
| 53 | value |= RTL_SPI_SFCSR_LEN4;
|
| 54 | else if (size == 1)
|
| 55 | value |= RTL_SPI_SFCSR_LEN1;
|
| 56 | __raw_writel(value, REG(RTL_SPI_SFCSR));
|
| 57 | }
|
| 58 |
|
| 59 | static inline void wait_ready(struct rtspi *rtspi)
|
| 60 | {
|
| 61 | while (!(__raw_readl(REG(RTL_SPI_SFCSR)) & RTL_SPI_SFCSR_RDY))
|
| 62 | cpu_relax();
|
| 63 | }
|
| 64 | static void send4(struct rtspi *rtspi, const u32 *buf)
|
| 65 | {
|
| 66 | wait_ready(rtspi);
|
| 67 | set_size(rtspi, 4);
|
| 68 | __raw_writel(*buf, REG(RTL_SPI_SFDR));
|
| 69 | }
|
| 70 |
|
| 71 | static void send1(struct rtspi *rtspi, const u8 *buf)
|
| 72 | {
|
| 73 | wait_ready(rtspi);
|
| 74 | set_size(rtspi, 1);
|
| 75 | __raw_writel(buf[0] << 24, REG(RTL_SPI_SFDR));
|
| 76 | }
|
| 77 |
|
| 78 | static void rcv4(struct rtspi *rtspi, u32 *buf)
|
| 79 | {
|
| 80 | wait_ready(rtspi);
|
| 81 | set_size(rtspi, 4);
|
| 82 | *buf = __raw_readl(REG(RTL_SPI_SFDR));
|
| 83 | }
|
| 84 |
|
| 85 | static void rcv1(struct rtspi *rtspi, u8 *buf)
|
| 86 | {
|
| 87 | wait_ready(rtspi);
|
| 88 | set_size(rtspi, 1);
|
| 89 | *buf = __raw_readl(REG(RTL_SPI_SFDR)) >> 24;
|
| 90 | }
|
| 91 |
|
| 92 | static int transfer_one(struct spi_controller *ctrl, struct spi_device *spi,
|
| 93 | struct spi_transfer *xfer)
|
| 94 | {
|
| 95 | struct rtspi *rtspi = spi_controller_get_devdata(ctrl);
|
| 96 | void *rx_buf;
|
| 97 | const void *tx_buf;
|
| 98 | int cnt;
|
| 99 |
|
| 100 | tx_buf = xfer->tx_buf;
|
| 101 | rx_buf = xfer->rx_buf;
|
| 102 | cnt = xfer->len;
|
| 103 | if (tx_buf) {
|
| 104 | while (cnt >= 4) {
|
| 105 | send4(rtspi, tx_buf);
|
| 106 | tx_buf += 4;
|
| 107 | cnt -= 4;
|
| 108 | }
|
| 109 | while (cnt) {
|
| 110 | send1(rtspi, tx_buf);
|
| 111 | tx_buf++;
|
| 112 | cnt--;
|
| 113 | }
|
| 114 | } else if (rx_buf) {
|
| 115 | while (cnt >= 4) {
|
| 116 | rcv4(rtspi, rx_buf);
|
| 117 | rx_buf += 4;
|
| 118 | cnt -= 4;
|
| 119 | }
|
| 120 | while (cnt) {
|
| 121 | rcv1(rtspi, rx_buf);
|
| 122 | rx_buf++;
|
| 123 | cnt--;
|
| 124 | }
|
| 125 | }
|
| 126 |
|
| 127 | spi_finalize_current_transfer(ctrl);
|
| 128 |
|
| 129 | return 0;
|
| 130 | }
|
| 131 |
|
| 132 | static void init_hw(struct rtspi *rtspi)
|
| 133 | {
|
| 134 | u32 value;
|
| 135 |
|
| 136 |
|
| 137 | value = __raw_readl(REG(RTL_SPI_SFCR));
|
| 138 | value |= RTL_SPI_SFCR_RBO | RTL_SPI_SFCR_WBO;
|
| 139 | __raw_writel(value, REG(RTL_SPI_SFCR));
|
| 140 |
|
| 141 | value = __raw_readl(REG(RTL_SPI_SFCSR));
|
| 142 |
|
| 143 | value |= RTL_SPI_SFCSR_CSB1;
|
| 144 |
|
| 145 | value &= RTL_SPI_SFCSR_CS;
|
| 146 | __raw_writel(value, REG(RTL_SPI_SFCSR));
|
| 147 | }
|
| 148 |
|
| 149 | static int realtek_rtl_spi_probe(struct platform_device *pdev)
|
| 150 | {
|
| 151 | struct spi_controller *ctrl;
|
| 152 | struct rtspi *rtspi;
|
| 153 | int err;
|
| 154 |
|
| 155 | ctrl = devm_spi_alloc_host(&pdev->dev, sizeof(*rtspi));
|
| 156 | if (!ctrl) {
|
| 157 | dev_err(&pdev->dev, "Error allocating SPI controller\n");
|
| 158 | return -ENOMEM;
|
| 159 | }
|
| 160 | platform_set_drvdata(pdev, ctrl);
|
| 161 | rtspi = spi_controller_get_devdata(ctrl);
|
| 162 |
|
| 163 | rtspi->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
|
| 164 | if (IS_ERR(rtspi->base)) {
|
| 165 | dev_err(&pdev->dev, "Could not map SPI register address");
|
| 166 | return -ENOMEM;
|
| 167 | }
|
| 168 |
|
| 169 | init_hw(rtspi);
|
| 170 |
|
| 171 | ctrl->flags = SPI_CONTROLLER_HALF_DUPLEX;
|
| 172 | ctrl->set_cs = rt_set_cs;
|
| 173 | ctrl->transfer_one = transfer_one;
|
| 174 |
|
| 175 | err = devm_spi_register_controller(&pdev->dev, ctrl);
|
| 176 | if (err) {
|
| 177 | dev_err(&pdev->dev, "Could not register SPI controller\n");
|
| 178 | return -ENODEV;
|
| 179 | }
|
| 180 |
|
| 181 | return 0;
|
| 182 | }
|
| 183 |
|
| 184 |
|
| 185 | static const struct of_device_id realtek_rtl_spi_of_ids[] = {
|
| 186 | { .compatible = "realtek,rtl8380-spi" },
|
| 187 | { .compatible = "realtek,rtl8382-spi" },
|
| 188 | { .compatible = "realtek,rtl8391-spi" },
|
| 189 | { .compatible = "realtek,rtl8392-spi" },
|
| 190 | { .compatible = "realtek,rtl8393-spi" },
|
| 191 | { }
|
| 192 | };
|
| 193 | MODULE_DEVICE_TABLE(of, realtek_rtl_spi_of_ids);
|
| 194 |
|
| 195 | static struct platform_driver realtek_rtl_spi_driver = {
|
| 196 | .probe = realtek_rtl_spi_probe,
|
| 197 | .driver = {
|
| 198 | .name = "realtek-rtl-spi",
|
| 199 | .of_match_table = realtek_rtl_spi_of_ids,
|
| 200 | },
|
| 201 | };
|
| 202 |
|
| 203 | module_platform_driver(realtek_rtl_spi_driver);
|
| 204 |
|
| 205 | MODULE_LICENSE("GPL v2");
|
| 206 | MODULE_AUTHOR("Bert Vermeulen <[email protected]>");
|
| 207 | MODULE_DESCRIPTION("Realtek RTL SPI driver");
|