Commit | Line | Data |
---|---|---|
4348f7e2 FF |
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"); |