| @@ -0,0 +1,317 @@ |
| | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
| | 2 | /* |
| | 3 | * LiteX Liteeth Ethernet |
| | 4 | * |
| | 5 | * Copyright 2017 Joel Stanley <joel@jms.id.au> |
| | 6 | * |
| | 7 | */ |
| | 8 | |
| | 9 | #include <linux/etherdevice.h> |
| | 10 | #include <linux/interrupt.h> |
| | 11 | #include <linux/litex.h> |
| | 12 | #include <linux/module.h> |
| | 13 | #include <linux/of_net.h> |
| | 14 | #include <linux/platform_device.h> |
| | 15 | |
| | 16 | #define LITEETH_WRITER_SLOT 0x00 |
| | 17 | #define LITEETH_WRITER_LENGTH 0x04 |
| | 18 | #define LITEETH_WRITER_ERRORS 0x08 |
| | 19 | #define LITEETH_WRITER_EV_STATUS 0x0C |
| | 20 | #define LITEETH_WRITER_EV_PENDING 0x10 |
| | 21 | #define LITEETH_WRITER_EV_ENABLE 0x14 |
| | 22 | #define LITEETH_READER_START 0x18 |
| | 23 | #define LITEETH_READER_READY 0x1C |
| | 24 | #define LITEETH_READER_LEVEL 0x20 |
| | 25 | #define LITEETH_READER_SLOT 0x24 |
| | 26 | #define LITEETH_READER_LENGTH 0x28 |
| | 27 | #define LITEETH_READER_EV_STATUS 0x2C |
| | 28 | #define LITEETH_READER_EV_PENDING 0x30 |
| | 29 | #define LITEETH_READER_EV_ENABLE 0x34 |
| | 30 | #define LITEETH_PREAMBLE_CRC 0x38 |
| | 31 | #define LITEETH_PREAMBLE_ERRORS 0x3C |
| | 32 | #define LITEETH_CRC_ERRORS 0x40 |
| | 33 | |
| | 34 | #define LITEETH_PHY_CRG_RESET 0x00 |
| | 35 | #define LITEETH_MDIO_W 0x04 |
| | 36 | #define LITEETH_MDIO_R 0x0C |
| | 37 | |
| | 38 | #define DRV_NAME "liteeth" |
| | 39 | |
| | 40 | struct liteeth { |
| | 41 | void __iomem *base; |
| | 42 | struct net_device *netdev; |
| | 43 | struct device *dev; |
| | 44 | u32 slot_size; |
| | 45 | |
| | 46 | /* Tx */ |
| | 47 | u32 tx_slot; |
| | 48 | u32 num_tx_slots; |
| | 49 | void __iomem *tx_base; |
| | 50 | |
| | 51 | /* Rx */ |
| | 52 | u32 rx_slot; |
| | 53 | u32 num_rx_slots; |
| | 54 | void __iomem *rx_base; |
| | 55 | }; |
| | 56 | |
| | 57 | static int liteeth_rx(struct net_device *netdev) |
| | 58 | { |
| | 59 | struct liteeth *priv = netdev_priv(netdev); |
| | 60 | struct sk_buff *skb; |
| | 61 | unsigned char *data; |
| | 62 | u8 rx_slot; |
| | 63 | int len; |
| | 64 | |
| | 65 | rx_slot = litex_read8(priv->base + LITEETH_WRITER_SLOT); |
| | 66 | len = litex_read32(priv->base + LITEETH_WRITER_LENGTH); |
| | 67 | |
| | 68 | if (len == 0 || len > 2048) |
| | 69 | goto rx_drop; |
| | 70 | |
| | 71 | skb = netdev_alloc_skb_ip_align(netdev, len); |
| | 72 | if (!skb) { |
| | 73 | netdev_err(netdev, "couldn't get memory\n"); |
| | 74 | goto rx_drop; |
| | 75 | } |
| | 76 | |
| | 77 | data = skb_put(skb, len); |
| | 78 | memcpy_fromio(data, priv->rx_base + rx_slot * priv->slot_size, len); |
| | 79 | skb->protocol = eth_type_trans(skb, netdev); |
| | 80 | |
| | 81 | netdev->stats.rx_packets++; |
| | 82 | netdev->stats.rx_bytes += len; |
| | 83 | |
| | 84 | return netif_rx(skb); |
| | 85 | |
| | 86 | rx_drop: |
| | 87 | netdev->stats.rx_dropped++; |
| | 88 | netdev->stats.rx_errors++; |
| | 89 | |
| | 90 | return NET_RX_DROP; |
| | 91 | } |
| | 92 | |
| | 93 | static irqreturn_t liteeth_interrupt(int irq, void *dev_id) |
| | 94 | { |
| | 95 | struct net_device *netdev = dev_id; |
| | 96 | struct liteeth *priv = netdev_priv(netdev); |
| | 97 | u8 reg; |
| | 98 | |
| | 99 | reg = litex_read8(priv->base + LITEETH_READER_EV_PENDING); |
| | 100 | if (reg) { |
| | 101 | if (netif_queue_stopped(netdev)) |
| | 102 | netif_wake_queue(netdev); |
| | 103 | litex_write8(priv->base + LITEETH_READER_EV_PENDING, reg); |
| | 104 | } |
| | 105 | |
| | 106 | reg = litex_read8(priv->base + LITEETH_WRITER_EV_PENDING); |
| | 107 | if (reg) { |
| | 108 | liteeth_rx(netdev); |
| | 109 | litex_write8(priv->base + LITEETH_WRITER_EV_PENDING, reg); |
| | 110 | } |
| | 111 | |
| | 112 | return IRQ_HANDLED; |
| | 113 | } |
| | 114 | |
| | 115 | static int liteeth_open(struct net_device *netdev) |
| | 116 | { |
| | 117 | struct liteeth *priv = netdev_priv(netdev); |
| | 118 | int err; |
| | 119 | |
| | 120 | /* Clear pending events */ |
| | 121 | litex_write8(priv->base + LITEETH_WRITER_EV_PENDING, 1); |
| | 122 | litex_write8(priv->base + LITEETH_READER_EV_PENDING, 1); |
| | 123 | |
| | 124 | err = request_irq(netdev->irq, liteeth_interrupt, 0, netdev->name, netdev); |
| | 125 | if (err) { |
| | 126 | netdev_err(netdev, "failed to request irq %d\n", netdev->irq); |
| | 127 | return err; |
| | 128 | } |
| | 129 | |
| | 130 | /* Enable IRQs */ |
| | 131 | litex_write8(priv->base + LITEETH_WRITER_EV_ENABLE, 1); |
| | 132 | litex_write8(priv->base + LITEETH_READER_EV_ENABLE, 1); |
| | 133 | |
| | 134 | netif_carrier_on(netdev); |
| | 135 | netif_start_queue(netdev); |
| | 136 | |
| | 137 | return 0; |
| | 138 | } |
| | 139 | |
| | 140 | static int liteeth_stop(struct net_device *netdev) |
| | 141 | { |
| | 142 | struct liteeth *priv = netdev_priv(netdev); |
| | 143 | |
| | 144 | netif_stop_queue(netdev); |
| | 145 | netif_carrier_off(netdev); |
| | 146 | |
| | 147 | litex_write8(priv->base + LITEETH_WRITER_EV_ENABLE, 0); |
| | 148 | litex_write8(priv->base + LITEETH_READER_EV_ENABLE, 0); |
| | 149 | |
| | 150 | free_irq(netdev->irq, netdev); |
| | 151 | |
| | 152 | return 0; |
| | 153 | } |
| | 154 | |
| | 155 | static int liteeth_start_xmit(struct sk_buff *skb, struct net_device *netdev) |
| | 156 | { |
| | 157 | struct liteeth *priv = netdev_priv(netdev); |
| | 158 | void __iomem *txbuffer; |
| | 159 | |
| | 160 | if (!litex_read8(priv->base + LITEETH_READER_READY)) { |
| | 161 | if (net_ratelimit()) |
| | 162 | netdev_err(netdev, "LITEETH_READER_READY not ready\n"); |
| | 163 | |
| | 164 | netif_stop_queue(netdev); |
| | 165 | |
| | 166 | return NETDEV_TX_BUSY; |
| | 167 | } |
| | 168 | |
| | 169 | /* Reject oversize packets */ |
| | 170 | if (unlikely(skb->len > priv->slot_size)) { |
| | 171 | if (net_ratelimit()) |
| | 172 | netdev_err(netdev, "tx packet too big\n"); |
| | 173 | |
| | 174 | dev_kfree_skb_any(skb); |
| | 175 | netdev->stats.tx_dropped++; |
| | 176 | netdev->stats.tx_errors++; |
| | 177 | |
| | 178 | return NETDEV_TX_OK; |
| | 179 | } |
| | 180 | |
| | 181 | txbuffer = priv->tx_base + priv->tx_slot * priv->slot_size; |
| | 182 | memcpy_toio(txbuffer, skb->data, skb->len); |
| | 183 | litex_write8(priv->base + LITEETH_READER_SLOT, priv->tx_slot); |
| | 184 | litex_write16(priv->base + LITEETH_READER_LENGTH, skb->len); |
| | 185 | litex_write8(priv->base + LITEETH_READER_START, 1); |
| | 186 | |
| | 187 | netdev->stats.tx_bytes += skb->len; |
| | 188 | netdev->stats.tx_packets++; |
| | 189 | |
| | 190 | priv->tx_slot = (priv->tx_slot + 1) % priv->num_tx_slots; |
| | 191 | dev_kfree_skb_any(skb); |
| | 192 | |
| | 193 | return NETDEV_TX_OK; |
| | 194 | } |
| | 195 | |
| | 196 | static const struct net_device_ops liteeth_netdev_ops = { |
| | 197 | .ndo_open = liteeth_open, |
| | 198 | .ndo_stop = liteeth_stop, |
| | 199 | .ndo_start_xmit = liteeth_start_xmit, |
| | 200 | }; |
| | 201 | |
| | 202 | static void liteeth_setup_slots(struct liteeth *priv) |
| | 203 | { |
| | 204 | struct device_node *np = priv->dev->of_node; |
| | 205 | int err; |
| | 206 | |
| | 207 | err = of_property_read_u32(np, "litex,rx-slots", &priv->num_rx_slots); |
| | 208 | if (err) { |
| | 209 | dev_dbg(priv->dev, "unable to get litex,rx-slots, using 2\n"); |
| | 210 | priv->num_rx_slots = 2; |
| | 211 | } |
| | 212 | |
| | 213 | err = of_property_read_u32(np, "litex,tx-slots", &priv->num_tx_slots); |
| | 214 | if (err) { |
| | 215 | dev_dbg(priv->dev, "unable to get litex,tx-slots, using 2\n"); |
| | 216 | priv->num_tx_slots = 2; |
| | 217 | } |
| | 218 | |
| | 219 | err = of_property_read_u32(np, "litex,slot-size", &priv->slot_size); |
| | 220 | if (err) { |
| | 221 | dev_dbg(priv->dev, "unable to get litex,slot-size, using 0x800\n"); |
| | 222 | priv->slot_size = 0x800; |
| | 223 | } |
| | 224 | } |
| | 225 | |
| | 226 | static int liteeth_probe(struct platform_device *pdev) |
| | 227 | { |
| | 228 | struct net_device *netdev; |
| | 229 | void __iomem *buf_base; |
| | 230 | struct resource *res; |
| | 231 | struct liteeth *priv; |
| | 232 | int irq, err; |
| | 233 | |
| | 234 | netdev = devm_alloc_etherdev(&pdev->dev, sizeof(*priv)); |
| | 235 | if (!netdev) |
| | 236 | return -ENOMEM; |
| | 237 | |
| | 238 | SET_NETDEV_DEV(netdev, &pdev->dev); |
| | 239 | platform_set_drvdata(pdev, netdev); |
| | 240 | |
| | 241 | priv = netdev_priv(netdev); |
| | 242 | priv->netdev = netdev; |
| | 243 | priv->dev = &pdev->dev; |
| | 244 | |
| | 245 | irq = platform_get_irq(pdev, 0); |
| | 246 | if (irq < 0) { |
| | 247 | dev_err(&pdev->dev, "Failed to get IRQ %d\n", irq); |
| | 248 | return irq; |
| | 249 | } |
| | 250 | netdev->irq = irq; |
| | 251 | |
| | 252 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mac"); |
| | 253 | priv->base = devm_ioremap_resource(&pdev->dev, res); |
| | 254 | if (IS_ERR(priv->base)) |
| | 255 | return PTR_ERR(priv->base); |
| | 256 | |
| | 257 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "buffer"); |
| | 258 | buf_base = devm_ioremap_resource(&pdev->dev, res); |
| | 259 | if (IS_ERR(buf_base)) |
| | 260 | return PTR_ERR(buf_base); |
| | 261 | |
| | 262 | liteeth_setup_slots(priv); |
| | 263 | |
| | 264 | /* Rx slots */ |
| | 265 | priv->rx_base = buf_base; |
| | 266 | priv->rx_slot = 0; |
| | 267 | |
| | 268 | /* Tx slots come after Rx slots */ |
| | 269 | priv->tx_base = buf_base + priv->num_rx_slots * priv->slot_size; |
| | 270 | priv->tx_slot = 0; |
| | 271 | |
| | 272 | err = of_get_mac_address(pdev->dev.of_node, netdev->dev_addr); |
| | 273 | if (err) |
| | 274 | eth_hw_addr_random(netdev); |
| | 275 | |
| | 276 | netdev->netdev_ops = &liteeth_netdev_ops; |
| | 277 | |
| | 278 | err = register_netdev(netdev); |
| | 279 | if (err) { |
| | 280 | dev_err(&pdev->dev, "Failed to register netdev %d\n", err); |
| | 281 | return err; |
| | 282 | } |
| | 283 | |
| | 284 | netdev_info(netdev, "irq %d slots: tx %d rx %d size %d\n", |
| | 285 | netdev->irq, priv->num_tx_slots, priv->num_rx_slots, priv->slot_size); |
| | 286 | |
| | 287 | return 0; |
| | 288 | } |
| | 289 | |
| | 290 | static int liteeth_remove(struct platform_device *pdev) |
| | 291 | { |
| | 292 | struct net_device *netdev = platform_get_drvdata(pdev); |
| | 293 | |
| | 294 | unregister_netdev(netdev); |
| | 295 | free_netdev(netdev); |
| | 296 | |
| | 297 | return 0; |
| | 298 | } |
| | 299 | |
| | 300 | static const struct of_device_id liteeth_of_match[] = { |
| | 301 | { .compatible = "litex,liteeth" }, |
| | 302 | { } |
| | 303 | }; |
| | 304 | MODULE_DEVICE_TABLE(of, liteeth_of_match); |
| | 305 | |
| | 306 | static struct platform_driver liteeth_driver = { |
| | 307 | .probe = liteeth_probe, |
| | 308 | .remove = liteeth_remove, |
| | 309 | .driver = { |
| | 310 | .name = DRV_NAME, |
| | 311 | .of_match_table = liteeth_of_match, |
| | 312 | }, |
| | 313 | }; |
| | 314 | module_platform_driver(liteeth_driver); |
| | 315 | |
| | 316 | MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>"); |
| | 317 | MODULE_LICENSE("GPL"); |