1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * CLPS711X SPI bus driver |
4 | * |
5 | * Copyright (C) 2012-2016 Alexander Shiyan <shc_work@mail.ru> |
6 | */ |
7 | |
8 | #include <linux/io.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/gpio/consumer.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regmap.h> |
16 | #include <linux/mfd/syscon.h> |
17 | #include <linux/mfd/syscon/clps711x.h> |
18 | #include <linux/spi/spi.h> |
19 | |
20 | #define DRIVER_NAME "clps711x-spi" |
21 | |
22 | #define SYNCIO_FRMLEN(x) ((x) << 8) |
23 | #define SYNCIO_TXFRMEN (1 << 14) |
24 | |
25 | struct spi_clps711x_data { |
26 | void __iomem *syncio; |
27 | struct regmap *syscon; |
28 | struct clk *spi_clk; |
29 | |
30 | u8 *tx_buf; |
31 | u8 *rx_buf; |
32 | unsigned int bpw; |
33 | int len; |
34 | }; |
35 | |
36 | static int spi_clps711x_prepare_message(struct spi_controller *host, |
37 | struct spi_message *msg) |
38 | { |
39 | struct spi_clps711x_data *hw = spi_controller_get_devdata(ctlr: host); |
40 | struct spi_device *spi = msg->spi; |
41 | |
42 | /* Setup mode for transfer */ |
43 | return regmap_update_bits(map: hw->syscon, SYSCON_OFFSET, SYSCON3_ADCCKNSEN, |
44 | val: (spi->mode & SPI_CPHA) ? |
45 | SYSCON3_ADCCKNSEN : 0); |
46 | } |
47 | |
48 | static int spi_clps711x_transfer_one(struct spi_controller *host, |
49 | struct spi_device *spi, |
50 | struct spi_transfer *xfer) |
51 | { |
52 | struct spi_clps711x_data *hw = spi_controller_get_devdata(ctlr: host); |
53 | u8 data; |
54 | |
55 | clk_set_rate(clk: hw->spi_clk, rate: xfer->speed_hz ? : spi->max_speed_hz); |
56 | |
57 | hw->len = xfer->len; |
58 | hw->bpw = xfer->bits_per_word; |
59 | hw->tx_buf = (u8 *)xfer->tx_buf; |
60 | hw->rx_buf = (u8 *)xfer->rx_buf; |
61 | |
62 | /* Initiate transfer */ |
63 | data = hw->tx_buf ? *hw->tx_buf++ : 0; |
64 | writel(val: data | SYNCIO_FRMLEN(hw->bpw) | SYNCIO_TXFRMEN, addr: hw->syncio); |
65 | |
66 | return 1; |
67 | } |
68 | |
69 | static irqreturn_t spi_clps711x_isr(int irq, void *dev_id) |
70 | { |
71 | struct spi_controller *host = dev_id; |
72 | struct spi_clps711x_data *hw = spi_controller_get_devdata(ctlr: host); |
73 | u8 data; |
74 | |
75 | /* Handle RX */ |
76 | data = readb(addr: hw->syncio); |
77 | if (hw->rx_buf) |
78 | *hw->rx_buf++ = data; |
79 | |
80 | /* Handle TX */ |
81 | if (--hw->len > 0) { |
82 | data = hw->tx_buf ? *hw->tx_buf++ : 0; |
83 | writel(val: data | SYNCIO_FRMLEN(hw->bpw) | SYNCIO_TXFRMEN, |
84 | addr: hw->syncio); |
85 | } else |
86 | spi_finalize_current_transfer(ctlr: host); |
87 | |
88 | return IRQ_HANDLED; |
89 | } |
90 | |
91 | static int spi_clps711x_probe(struct platform_device *pdev) |
92 | { |
93 | struct device_node *np = pdev->dev.of_node; |
94 | struct spi_clps711x_data *hw; |
95 | struct spi_controller *host; |
96 | int irq, ret; |
97 | |
98 | irq = platform_get_irq(pdev, 0); |
99 | if (irq < 0) |
100 | return irq; |
101 | |
102 | host = spi_alloc_host(dev: &pdev->dev, size: sizeof(*hw)); |
103 | if (!host) |
104 | return -ENOMEM; |
105 | |
106 | host->use_gpio_descriptors = true; |
107 | host->bus_num = -1; |
108 | host->mode_bits = SPI_CPHA | SPI_CS_HIGH; |
109 | host->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 8); |
110 | host->dev.of_node = pdev->dev.of_node; |
111 | host->prepare_message = spi_clps711x_prepare_message; |
112 | host->transfer_one = spi_clps711x_transfer_one; |
113 | |
114 | hw = spi_controller_get_devdata(ctlr: host); |
115 | |
116 | hw->spi_clk = devm_clk_get(dev: &pdev->dev, NULL); |
117 | if (IS_ERR(ptr: hw->spi_clk)) { |
118 | ret = PTR_ERR(ptr: hw->spi_clk); |
119 | goto err_out; |
120 | } |
121 | |
122 | hw->syscon = syscon_regmap_lookup_by_phandle(np, property: "syscon" ); |
123 | if (IS_ERR(ptr: hw->syscon)) { |
124 | ret = PTR_ERR(ptr: hw->syscon); |
125 | goto err_out; |
126 | } |
127 | |
128 | hw->syncio = devm_platform_ioremap_resource(pdev, index: 0); |
129 | if (IS_ERR(ptr: hw->syncio)) { |
130 | ret = PTR_ERR(ptr: hw->syncio); |
131 | goto err_out; |
132 | } |
133 | |
134 | /* Disable extended mode due hardware problems */ |
135 | regmap_update_bits(map: hw->syscon, SYSCON_OFFSET, SYSCON3_ADCCON, val: 0); |
136 | |
137 | /* Clear possible pending interrupt */ |
138 | readl(addr: hw->syncio); |
139 | |
140 | ret = devm_request_irq(dev: &pdev->dev, irq, handler: spi_clps711x_isr, irqflags: 0, |
141 | devname: dev_name(dev: &pdev->dev), dev_id: host); |
142 | if (ret) |
143 | goto err_out; |
144 | |
145 | ret = devm_spi_register_controller(dev: &pdev->dev, ctlr: host); |
146 | if (!ret) |
147 | return 0; |
148 | |
149 | err_out: |
150 | spi_controller_put(ctlr: host); |
151 | |
152 | return ret; |
153 | } |
154 | |
155 | static const struct of_device_id clps711x_spi_dt_ids[] = { |
156 | { .compatible = "cirrus,ep7209-spi" , }, |
157 | { } |
158 | }; |
159 | MODULE_DEVICE_TABLE(of, clps711x_spi_dt_ids); |
160 | |
161 | static struct platform_driver clps711x_spi_driver = { |
162 | .driver = { |
163 | .name = DRIVER_NAME, |
164 | .of_match_table = clps711x_spi_dt_ids, |
165 | }, |
166 | .probe = spi_clps711x_probe, |
167 | }; |
168 | module_platform_driver(clps711x_spi_driver); |
169 | |
170 | MODULE_LICENSE("GPL" ); |
171 | MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>" ); |
172 | MODULE_DESCRIPTION("CLPS711X SPI bus driver" ); |
173 | MODULE_ALIAS("platform:" DRIVER_NAME); |
174 | |