]>
Commit | Line | Data |
---|---|---|
061c97d1 AG |
1 | /* |
2 | * Xilinx Spartan6 Slave Serial SPI Driver | |
3 | * | |
4 | * Copyright (C) 2017 DENX Software Engineering | |
5 | * | |
6 | * Anatolij Gustschin <[email protected]> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms and conditions of the GNU General Public License, | |
10 | * version 2, as published by the Free Software Foundation. | |
11 | * | |
12 | * Manage Xilinx FPGA firmware that is loaded over SPI using | |
13 | * the slave serial configuration interface. | |
14 | */ | |
15 | ||
16 | #include <linux/delay.h> | |
17 | #include <linux/device.h> | |
18 | #include <linux/fpga/fpga-mgr.h> | |
19 | #include <linux/gpio/consumer.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/mod_devicetable.h> | |
22 | #include <linux/of.h> | |
23 | #include <linux/spi/spi.h> | |
24 | #include <linux/sizes.h> | |
25 | ||
26 | struct xilinx_spi_conf { | |
27 | struct spi_device *spi; | |
28 | struct gpio_desc *prog_b; | |
29 | struct gpio_desc *done; | |
30 | }; | |
31 | ||
32 | static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr) | |
33 | { | |
34 | struct xilinx_spi_conf *conf = mgr->priv; | |
35 | ||
36 | if (!gpiod_get_value(conf->done)) | |
37 | return FPGA_MGR_STATE_RESET; | |
38 | ||
39 | return FPGA_MGR_STATE_UNKNOWN; | |
40 | } | |
41 | ||
42 | static int xilinx_spi_write_init(struct fpga_manager *mgr, | |
43 | struct fpga_image_info *info, | |
44 | const char *buf, size_t count) | |
45 | { | |
46 | struct xilinx_spi_conf *conf = mgr->priv; | |
47 | const size_t prog_latency_7500us = 7500; | |
48 | const size_t prog_pulse_1us = 1; | |
49 | ||
50 | if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { | |
51 | dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); | |
52 | return -EINVAL; | |
53 | } | |
54 | ||
55 | gpiod_set_value(conf->prog_b, 1); | |
56 | ||
57 | udelay(prog_pulse_1us); /* min is 500 ns */ | |
58 | ||
59 | gpiod_set_value(conf->prog_b, 0); | |
60 | ||
61 | if (gpiod_get_value(conf->done)) { | |
62 | dev_err(&mgr->dev, "Unexpected DONE pin state...\n"); | |
63 | return -EIO; | |
64 | } | |
65 | ||
66 | /* program latency */ | |
67 | usleep_range(prog_latency_7500us, prog_latency_7500us + 100); | |
68 | return 0; | |
69 | } | |
70 | ||
71 | static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf, | |
72 | size_t count) | |
73 | { | |
74 | struct xilinx_spi_conf *conf = mgr->priv; | |
75 | const char *fw_data = buf; | |
76 | const char *fw_data_end = fw_data + count; | |
77 | ||
78 | while (fw_data < fw_data_end) { | |
79 | size_t remaining, stride; | |
80 | int ret; | |
81 | ||
82 | remaining = fw_data_end - fw_data; | |
83 | stride = min_t(size_t, remaining, SZ_4K); | |
84 | ||
85 | ret = spi_write(conf->spi, fw_data, stride); | |
86 | if (ret) { | |
87 | dev_err(&mgr->dev, "SPI error in firmware write: %d\n", | |
88 | ret); | |
89 | return ret; | |
90 | } | |
91 | fw_data += stride; | |
92 | } | |
93 | ||
94 | return 0; | |
95 | } | |
96 | ||
97 | static int xilinx_spi_apply_cclk_cycles(struct xilinx_spi_conf *conf) | |
98 | { | |
99 | struct spi_device *spi = conf->spi; | |
100 | const u8 din_data[1] = { 0xff }; | |
101 | int ret; | |
102 | ||
103 | ret = spi_write(conf->spi, din_data, sizeof(din_data)); | |
104 | if (ret) | |
105 | dev_err(&spi->dev, "applying CCLK cycles failed: %d\n", ret); | |
106 | ||
107 | return ret; | |
108 | } | |
109 | ||
110 | static int xilinx_spi_write_complete(struct fpga_manager *mgr, | |
111 | struct fpga_image_info *info) | |
112 | { | |
113 | struct xilinx_spi_conf *conf = mgr->priv; | |
114 | unsigned long timeout; | |
115 | int ret; | |
116 | ||
117 | if (gpiod_get_value(conf->done)) | |
118 | return xilinx_spi_apply_cclk_cycles(conf); | |
119 | ||
120 | timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us); | |
121 | ||
122 | while (time_before(jiffies, timeout)) { | |
123 | ||
124 | ret = xilinx_spi_apply_cclk_cycles(conf); | |
125 | if (ret) | |
126 | return ret; | |
127 | ||
128 | if (gpiod_get_value(conf->done)) | |
129 | return xilinx_spi_apply_cclk_cycles(conf); | |
130 | } | |
131 | ||
132 | dev_err(&mgr->dev, "Timeout after config data transfer.\n"); | |
133 | return -ETIMEDOUT; | |
134 | } | |
135 | ||
136 | static const struct fpga_manager_ops xilinx_spi_ops = { | |
137 | .state = xilinx_spi_state, | |
138 | .write_init = xilinx_spi_write_init, | |
139 | .write = xilinx_spi_write, | |
140 | .write_complete = xilinx_spi_write_complete, | |
141 | }; | |
142 | ||
143 | static int xilinx_spi_probe(struct spi_device *spi) | |
144 | { | |
145 | struct xilinx_spi_conf *conf; | |
146 | ||
147 | conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); | |
148 | if (!conf) | |
149 | return -ENOMEM; | |
150 | ||
151 | conf->spi = spi; | |
152 | ||
153 | /* PROGRAM_B is active low */ | |
154 | conf->prog_b = devm_gpiod_get(&spi->dev, "prog_b", GPIOD_OUT_LOW); | |
155 | if (IS_ERR(conf->prog_b)) { | |
156 | dev_err(&spi->dev, "Failed to get PROGRAM_B gpio: %ld\n", | |
157 | PTR_ERR(conf->prog_b)); | |
158 | return PTR_ERR(conf->prog_b); | |
159 | } | |
160 | ||
161 | conf->done = devm_gpiod_get(&spi->dev, "done", GPIOD_IN); | |
162 | if (IS_ERR(conf->done)) { | |
163 | dev_err(&spi->dev, "Failed to get DONE gpio: %ld\n", | |
164 | PTR_ERR(conf->done)); | |
165 | return PTR_ERR(conf->done); | |
166 | } | |
167 | ||
168 | return fpga_mgr_register(&spi->dev, "Xilinx Slave Serial FPGA Manager", | |
169 | &xilinx_spi_ops, conf); | |
170 | } | |
171 | ||
172 | static int xilinx_spi_remove(struct spi_device *spi) | |
173 | { | |
174 | fpga_mgr_unregister(&spi->dev); | |
175 | ||
176 | return 0; | |
177 | } | |
178 | ||
179 | static const struct of_device_id xlnx_spi_of_match[] = { | |
180 | { .compatible = "xlnx,fpga-slave-serial", }, | |
181 | {} | |
182 | }; | |
183 | MODULE_DEVICE_TABLE(of, xlnx_spi_of_match); | |
184 | ||
185 | static struct spi_driver xilinx_slave_spi_driver = { | |
186 | .driver = { | |
187 | .name = "xlnx-slave-spi", | |
188 | .of_match_table = of_match_ptr(xlnx_spi_of_match), | |
189 | }, | |
190 | .probe = xilinx_spi_probe, | |
191 | .remove = xilinx_spi_remove, | |
192 | }; | |
193 | ||
194 | module_spi_driver(xilinx_slave_spi_driver) | |
195 | ||
196 | MODULE_LICENSE("GPL v2"); | |
197 | MODULE_AUTHOR("Anatolij Gustschin <[email protected]>"); | |
198 | MODULE_DESCRIPTION("Load Xilinx FPGA firmware over SPI"); |