diff options
Diffstat (limited to 'drivers/fpga/ts73xx-fpga.c')
-rw-r--r-- | drivers/fpga/ts73xx-fpga.c | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/drivers/fpga/ts73xx-fpga.c b/drivers/fpga/ts73xx-fpga.c new file mode 100644 index 000000000000..f6a96b42e2ca --- /dev/null +++ b/drivers/fpga/ts73xx-fpga.c | |||
@@ -0,0 +1,156 @@ | |||
1 | /* | ||
2 | * Technologic Systems TS-73xx SBC FPGA loader | ||
3 | * | ||
4 | * Copyright (C) 2016 Florian Fainelli <f.fainelli@gmail.com> | ||
5 | * | ||
6 | * FPGA Manager Driver for the on-board Altera Cyclone II FPGA found on | ||
7 | * TS-7300, heavily based on load_fpga.c in their vendor tree. | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; version 2 of the License. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | */ | ||
18 | |||
19 | #include <linux/delay.h> | ||
20 | #include <linux/io.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/string.h> | ||
24 | #include <linux/iopoll.h> | ||
25 | #include <linux/fpga/fpga-mgr.h> | ||
26 | |||
27 | #define TS73XX_FPGA_DATA_REG 0 | ||
28 | #define TS73XX_FPGA_CONFIG_REG 1 | ||
29 | |||
30 | #define TS73XX_FPGA_WRITE_DONE 0x1 | ||
31 | #define TS73XX_FPGA_WRITE_DONE_TIMEOUT 1000 /* us */ | ||
32 | #define TS73XX_FPGA_RESET 0x2 | ||
33 | #define TS73XX_FPGA_RESET_LOW_DELAY 30 /* us */ | ||
34 | #define TS73XX_FPGA_RESET_HIGH_DELAY 80 /* us */ | ||
35 | #define TS73XX_FPGA_LOAD_OK 0x4 | ||
36 | #define TS73XX_FPGA_CONFIG_LOAD 0x8 | ||
37 | |||
38 | struct ts73xx_fpga_priv { | ||
39 | void __iomem *io_base; | ||
40 | struct device *dev; | ||
41 | }; | ||
42 | |||
43 | static enum fpga_mgr_states ts73xx_fpga_state(struct fpga_manager *mgr) | ||
44 | { | ||
45 | return FPGA_MGR_STATE_UNKNOWN; | ||
46 | } | ||
47 | |||
48 | static int ts73xx_fpga_write_init(struct fpga_manager *mgr, | ||
49 | struct fpga_image_info *info, | ||
50 | const char *buf, size_t count) | ||
51 | { | ||
52 | struct ts73xx_fpga_priv *priv = mgr->priv; | ||
53 | |||
54 | /* Reset the FPGA */ | ||
55 | writeb(0, priv->io_base + TS73XX_FPGA_CONFIG_REG); | ||
56 | udelay(TS73XX_FPGA_RESET_LOW_DELAY); | ||
57 | writeb(TS73XX_FPGA_RESET, priv->io_base + TS73XX_FPGA_CONFIG_REG); | ||
58 | udelay(TS73XX_FPGA_RESET_HIGH_DELAY); | ||
59 | |||
60 | return 0; | ||
61 | } | ||
62 | |||
63 | static int ts73xx_fpga_write(struct fpga_manager *mgr, const char *buf, | ||
64 | size_t count) | ||
65 | { | ||
66 | struct ts73xx_fpga_priv *priv = mgr->priv; | ||
67 | size_t i = 0; | ||
68 | int ret; | ||
69 | u8 reg; | ||
70 | |||
71 | while (count--) { | ||
72 | ret = readb_poll_timeout(priv->io_base + TS73XX_FPGA_CONFIG_REG, | ||
73 | reg, !(reg & TS73XX_FPGA_WRITE_DONE), | ||
74 | 1, TS73XX_FPGA_WRITE_DONE_TIMEOUT); | ||
75 | if (ret < 0) | ||
76 | return ret; | ||
77 | |||
78 | writeb(buf[i], priv->io_base + TS73XX_FPGA_DATA_REG); | ||
79 | i++; | ||
80 | } | ||
81 | |||
82 | return 0; | ||
83 | } | ||
84 | |||
85 | static int ts73xx_fpga_write_complete(struct fpga_manager *mgr, | ||
86 | struct fpga_image_info *info) | ||
87 | { | ||
88 | struct ts73xx_fpga_priv *priv = mgr->priv; | ||
89 | u8 reg; | ||
90 | |||
91 | usleep_range(1000, 2000); | ||
92 | reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); | ||
93 | reg |= TS73XX_FPGA_CONFIG_LOAD; | ||
94 | writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG); | ||
95 | |||
96 | usleep_range(1000, 2000); | ||
97 | reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); | ||
98 | reg &= ~TS73XX_FPGA_CONFIG_LOAD; | ||
99 | writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG); | ||
100 | |||
101 | reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); | ||
102 | if ((reg & TS73XX_FPGA_LOAD_OK) != TS73XX_FPGA_LOAD_OK) | ||
103 | return -ETIMEDOUT; | ||
104 | |||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | static const struct fpga_manager_ops ts73xx_fpga_ops = { | ||
109 | .state = ts73xx_fpga_state, | ||
110 | .write_init = ts73xx_fpga_write_init, | ||
111 | .write = ts73xx_fpga_write, | ||
112 | .write_complete = ts73xx_fpga_write_complete, | ||
113 | }; | ||
114 | |||
115 | static int ts73xx_fpga_probe(struct platform_device *pdev) | ||
116 | { | ||
117 | struct device *kdev = &pdev->dev; | ||
118 | struct ts73xx_fpga_priv *priv; | ||
119 | struct resource *res; | ||
120 | |||
121 | priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL); | ||
122 | if (!priv) | ||
123 | return -ENOMEM; | ||
124 | |||
125 | priv->dev = kdev; | ||
126 | |||
127 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
128 | priv->io_base = devm_ioremap_resource(kdev, res); | ||
129 | if (IS_ERR(priv->io_base)) { | ||
130 | dev_err(kdev, "unable to remap registers\n"); | ||
131 | return PTR_ERR(priv->io_base); | ||
132 | } | ||
133 | |||
134 | return fpga_mgr_register(kdev, "TS-73xx FPGA Manager", | ||
135 | &ts73xx_fpga_ops, priv); | ||
136 | } | ||
137 | |||
138 | static int ts73xx_fpga_remove(struct platform_device *pdev) | ||
139 | { | ||
140 | fpga_mgr_unregister(&pdev->dev); | ||
141 | |||
142 | return 0; | ||
143 | } | ||
144 | |||
145 | static struct platform_driver ts73xx_fpga_driver = { | ||
146 | .driver = { | ||
147 | .name = "ts73xx-fpga-mgr", | ||
148 | }, | ||
149 | .probe = ts73xx_fpga_probe, | ||
150 | .remove = ts73xx_fpga_remove, | ||
151 | }; | ||
152 | module_platform_driver(ts73xx_fpga_driver); | ||
153 | |||
154 | MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>"); | ||
155 | MODULE_DESCRIPTION("TS-73xx FPGA Manager driver"); | ||
156 | MODULE_LICENSE("GPL v2"); | ||