From eec286fc91f8742003d23b4348942764027eba84 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Tue, 17 Jul 2012 11:33:41 -0400 Subject: [PATCH 004/123] openwrt ar71xx driver files Import of the openwrt ar71xx files from trunk, as of this commit: ---------- commit c1d79f64eed0a7ac36b5b9bca52275b397bec424 Author: nbd Date: Mon Jul 16 16:26:51 2012 +0000 uboot-ar71xx: fix compile on recent mac os x versions git-svn-id: svn://svn.openwrt.org/openwrt/trunk@32750 3c298f89-4303-0410-b956-a3cf2f4a3e73 ---------- Due to the nature of how the openwrt source is managed, new files are found separate from the commits which actually make use of them, so that unused stuff could not easily be left behind, but it is what it is. Path to files in the repo is: target/linux/generic/files Repo is: git://nbd.name/openwrt.git Signed-off-by: Paul Gortmaker --- drivers/gpio/gpio-nxp-74hc153.c | 247 ++++ drivers/leds/leds-rb750.c | 144 +++ drivers/leds/leds-wndr3700-usb.c | 76 ++ drivers/mtd/nand/rb4xx_nand.c | 309 +++++ drivers/mtd/nand/rb750_nand.c | 367 ++++++ drivers/mtd/tplinkpart.c | 197 +++ drivers/mtd/wrt160nl_part.c | 205 ++++ drivers/net/dsa/mv88e6063.c | 294 +++++ drivers/net/ethernet/atheros/ag71xx/Kconfig | 33 + drivers/net/ethernet/atheros/ag71xx/Makefile | 14 + drivers/net/ethernet/atheros/ag71xx/ag71xx.h | 477 ++++++++ .../net/ethernet/atheros/ag71xx/ag71xx_ar7240.c | 1193 +++++++++++++++++++ .../net/ethernet/atheros/ag71xx/ag71xx_ar8216.c | 44 + .../net/ethernet/atheros/ag71xx/ag71xx_debugfs.c | 280 +++++ .../net/ethernet/atheros/ag71xx/ag71xx_ethtool.c | 124 ++ drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c | 1258 ++++++++++++++++++++ drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c | 259 ++++ drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c | 235 ++++ drivers/spi/spi-ap83.c | 283 +++++ drivers/spi/spi-rb4xx-cpld.c | 441 +++++++ drivers/spi/spi-rb4xx.c | 507 ++++++++ drivers/spi/spi-vsc7385.c | 620 ++++++++++ net/dsa/mv88e6063.c | 294 +++++ 23 files changed, 7901 insertions(+) create mode 100644 drivers/gpio/gpio-nxp-74hc153.c create mode 100644 drivers/leds/leds-rb750.c create mode 100644 drivers/leds/leds-wndr3700-usb.c create mode 100644 drivers/mtd/nand/rb4xx_nand.c create mode 100644 drivers/mtd/nand/rb750_nand.c create mode 100644 drivers/mtd/tplinkpart.c create mode 100644 drivers/mtd/wrt160nl_part.c create mode 100644 drivers/net/dsa/mv88e6063.c create mode 100644 drivers/net/ethernet/atheros/ag71xx/Kconfig create mode 100644 drivers/net/ethernet/atheros/ag71xx/Makefile create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx.h create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_ar8216.c create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_debugfs.c create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_ethtool.c create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c create mode 100644 drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c create mode 100644 drivers/spi/spi-ap83.c create mode 100644 drivers/spi/spi-rb4xx-cpld.c create mode 100644 drivers/spi/spi-rb4xx.c create mode 100644 drivers/spi/spi-vsc7385.c create mode 100644 net/dsa/mv88e6063.c diff --git a/drivers/gpio/gpio-nxp-74hc153.c b/drivers/gpio/gpio-nxp-74hc153.c new file mode 100644 index 0000000..a5fec5c --- /dev/null +++ b/drivers/gpio/gpio-nxp-74hc153.c @@ -0,0 +1,247 @@ +/* + * NXP 74HC153 - Dual 4-input multiplexer GPIO driver + * + * Copyright (C) 2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#define NXP_74HC153_NUM_GPIOS 8 +#define NXP_74HC153_S0_MASK 0x1 +#define NXP_74HC153_S1_MASK 0x2 +#define NXP_74HC153_BANK_MASK 0x4 + +struct nxp_74hc153_chip { + struct device *parent; + struct gpio_chip gpio_chip; + struct mutex lock; +}; + +static struct nxp_74hc153_chip *gpio_to_nxp(struct gpio_chip *gc) +{ + return container_of(gc, struct nxp_74hc153_chip, gpio_chip); +} + +static int nxp_74hc153_direction_input(struct gpio_chip *gc, unsigned offset) +{ + return 0; +} + +static int nxp_74hc153_direction_output(struct gpio_chip *gc, + unsigned offset, int val) +{ + return -EINVAL; +} + +static int nxp_74hc153_get_value(struct gpio_chip *gc, unsigned offset) +{ + struct nxp_74hc153_chip *nxp; + struct nxp_74hc153_platform_data *pdata; + unsigned s0; + unsigned s1; + unsigned pin; + int ret; + + nxp = gpio_to_nxp(gc); + pdata = nxp->parent->platform_data; + + s0 = !!(offset & NXP_74HC153_S0_MASK); + s1 = !!(offset & NXP_74HC153_S1_MASK); + pin = (offset & NXP_74HC153_BANK_MASK) ? pdata->gpio_pin_2y + : pdata->gpio_pin_1y; + + mutex_lock(&nxp->lock); + gpio_set_value(pdata->gpio_pin_s0, s0); + gpio_set_value(pdata->gpio_pin_s1, s1); + ret = gpio_get_value(pin); + mutex_unlock(&nxp->lock); + + return ret; +} + +static void nxp_74hc153_set_value(struct gpio_chip *gc, + unsigned offset, int val) +{ + /* not supported */ +} + +static int __devinit nxp_74hc153_probe(struct platform_device *pdev) +{ + struct nxp_74hc153_platform_data *pdata; + struct nxp_74hc153_chip *nxp; + struct gpio_chip *gc; + int err; + + pdata = pdev->dev.platform_data; + if (pdata == NULL) { + dev_dbg(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + + nxp = kzalloc(sizeof(struct nxp_74hc153_chip), GFP_KERNEL); + if (nxp == NULL) { + dev_err(&pdev->dev, "no memory for private data\n"); + return -ENOMEM; + } + + err = gpio_request(pdata->gpio_pin_s0, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "unable to claim gpio %u, err=%d\n", + pdata->gpio_pin_s0, err); + goto err_free_nxp; + } + + err = gpio_request(pdata->gpio_pin_s1, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "unable to claim gpio %u, err=%d\n", + pdata->gpio_pin_s1, err); + goto err_free_s0; + } + + err = gpio_request(pdata->gpio_pin_1y, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "unable to claim gpio %u, err=%d\n", + pdata->gpio_pin_1y, err); + goto err_free_s1; + } + + err = gpio_request(pdata->gpio_pin_2y, dev_name(&pdev->dev)); + if (err) { + dev_err(&pdev->dev, "unable to claim gpio %u, err=%d\n", + pdata->gpio_pin_2y, err); + goto err_free_1y; + } + + err = gpio_direction_output(pdata->gpio_pin_s0, 0); + if (err) { + dev_err(&pdev->dev, + "unable to set direction of gpio %u, err=%d\n", + pdata->gpio_pin_s0, err); + goto err_free_2y; + } + + err = gpio_direction_output(pdata->gpio_pin_s1, 0); + if (err) { + dev_err(&pdev->dev, + "unable to set direction of gpio %u, err=%d\n", + pdata->gpio_pin_s1, err); + goto err_free_2y; + } + + err = gpio_direction_input(pdata->gpio_pin_1y); + if (err) { + dev_err(&pdev->dev, + "unable to set direction of gpio %u, err=%d\n", + pdata->gpio_pin_1y, err); + goto err_free_2y; + } + + err = gpio_direction_input(pdata->gpio_pin_2y); + if (err) { + dev_err(&pdev->dev, + "unable to set direction of gpio %u, err=%d\n", + pdata->gpio_pin_2y, err); + goto err_free_2y; + } + + nxp->parent = &pdev->dev; + mutex_init(&nxp->lock); + + gc = &nxp->gpio_chip; + + gc->direction_input = nxp_74hc153_direction_input; + gc->direction_output = nxp_74hc153_direction_output; + gc->get = nxp_74hc153_get_value; + gc->set = nxp_74hc153_set_value; + gc->can_sleep = 1; + + gc->base = pdata->gpio_base; + gc->ngpio = NXP_74HC153_NUM_GPIOS; + gc->label = dev_name(nxp->parent); + gc->dev = nxp->parent; + gc->owner = THIS_MODULE; + + err = gpiochip_add(&nxp->gpio_chip); + if (err) { + dev_err(&pdev->dev, "unable to add gpio chip, err=%d\n", err); + goto err_free_2y; + } + + platform_set_drvdata(pdev, nxp); + return 0; + +err_free_2y: + gpio_free(pdata->gpio_pin_2y); +err_free_1y: + gpio_free(pdata->gpio_pin_1y); +err_free_s1: + gpio_free(pdata->gpio_pin_s1); +err_free_s0: + gpio_free(pdata->gpio_pin_s0); +err_free_nxp: + kfree(nxp); + return err; +} + +static int nxp_74hc153_remove(struct platform_device *pdev) +{ + struct nxp_74hc153_chip *nxp = platform_get_drvdata(pdev); + struct nxp_74hc153_platform_data *pdata = pdev->dev.platform_data; + + if (nxp) { + int err; + + err = gpiochip_remove(&nxp->gpio_chip); + if (err) { + dev_err(&pdev->dev, + "unable to remove gpio chip, err=%d\n", + err); + return err; + } + + gpio_free(pdata->gpio_pin_2y); + gpio_free(pdata->gpio_pin_1y); + gpio_free(pdata->gpio_pin_s1); + gpio_free(pdata->gpio_pin_s0); + + kfree(nxp); + platform_set_drvdata(pdev, NULL); + } + + return 0; +} + +static struct platform_driver nxp_74hc153_driver = { + .probe = nxp_74hc153_probe, + .remove = __devexit_p(nxp_74hc153_remove), + .driver = { + .name = NXP_74HC153_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init nxp_74hc153_init(void) +{ + return platform_driver_register(&nxp_74hc153_driver); +} +subsys_initcall(nxp_74hc153_init); + +static void __exit nxp_74hc153_exit(void) +{ + platform_driver_unregister(&nxp_74hc153_driver); +} +module_exit(nxp_74hc153_exit); + +MODULE_AUTHOR("Gabor Juhos "); +MODULE_DESCRIPTION("GPIO expander driver for NXP 74HC153"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" NXP_74HC153_DRIVER_NAME); diff --git a/drivers/leds/leds-rb750.c b/drivers/leds/leds-rb750.c new file mode 100644 index 0000000..483e4fc --- /dev/null +++ b/drivers/leds/leds-rb750.c @@ -0,0 +1,144 @@ +/* + * LED driver for the RouterBOARD 750 + * + * Copyright (C) 2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include + +#include + +#define DRV_NAME "leds-rb750" + +struct rb750_led_dev { + struct led_classdev cdev; + u32 mask; + int active_low; + void (*latch_change)(u32 clear, u32 set); +}; + +struct rb750_led_drvdata { + struct rb750_led_dev *led_devs; + int num_leds; +}; + +static inline struct rb750_led_dev *to_rbled(struct led_classdev *led_cdev) +{ + return (struct rb750_led_dev *)container_of(led_cdev, + struct rb750_led_dev, cdev); +} + +static void rb750_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct rb750_led_dev *rbled = to_rbled(led_cdev); + int level; + + level = (value == LED_OFF) ? 0 : 1; + level ^= rbled->active_low; + + if (level) + rbled->latch_change(0, rbled->mask); + else + rbled->latch_change(rbled->mask, 0); +} + +static int __devinit rb750_led_probe(struct platform_device *pdev) +{ + struct rb750_led_platform_data *pdata; + struct rb750_led_drvdata *drvdata; + int ret = 0; + int i; + + pdata = pdev->dev.platform_data; + if (!pdata) + return -EINVAL; + + drvdata = kzalloc(sizeof(struct rb750_led_drvdata) + + sizeof(struct rb750_led_dev) * pdata->num_leds, + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->num_leds = pdata->num_leds; + drvdata->led_devs = (struct rb750_led_dev *) &drvdata[1]; + + for (i = 0; i < drvdata->num_leds; i++) { + struct rb750_led_dev *rbled = &drvdata->led_devs[i]; + struct rb750_led_data *led_data = &pdata->leds[i]; + + rbled->cdev.name = led_data->name; + rbled->cdev.default_trigger = led_data->default_trigger; + rbled->cdev.brightness_set = rb750_led_brightness_set; + rbled->cdev.brightness = LED_OFF; + + rbled->mask = led_data->mask; + rbled->active_low = !!led_data->active_low; + rbled->latch_change = pdata->latch_change; + + ret = led_classdev_register(&pdev->dev, &rbled->cdev); + if (ret) + goto err; + } + + platform_set_drvdata(pdev, drvdata); + return 0; + +err: + for (i = i - 1; i >= 0; i--) + led_classdev_unregister(&drvdata->led_devs[i].cdev); + + kfree(drvdata); + return ret; +} + +static int __devexit rb750_led_remove(struct platform_device *pdev) +{ + struct rb750_led_drvdata *drvdata; + int i; + + drvdata = platform_get_drvdata(pdev); + for (i = 0; i < drvdata->num_leds; i++) + led_classdev_unregister(&drvdata->led_devs[i].cdev); + + kfree(drvdata); + return 0; +} + +static struct platform_driver rb750_led_driver = { + .probe = rb750_led_probe, + .remove = __devexit_p(rb750_led_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +MODULE_ALIAS("platform:leds-rb750"); + +static int __init rb750_led_init(void) +{ + return platform_driver_register(&rb750_led_driver); +} + +static void __exit rb750_led_exit(void) +{ + platform_driver_unregister(&rb750_led_driver); +} + +module_init(rb750_led_init); +module_exit(rb750_led_exit); + +MODULE_DESCRIPTION(DRV_NAME); +MODULE_DESCRIPTION("LED driver for the RouterBOARD 750"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-wndr3700-usb.c b/drivers/leds/leds-wndr3700-usb.c new file mode 100644 index 0000000..42f5b70 --- /dev/null +++ b/drivers/leds/leds-wndr3700-usb.c @@ -0,0 +1,76 @@ +/* + * USB LED driver for the NETGEAR WNDR3700 + * + * Copyright (C) 2009 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include + +#include +#include + +#define DRIVER_NAME "wndr3700-led-usb" + +static void wndr3700_usb_led_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + if (brightness) + ath79_device_reset_clear(AR71XX_RESET_GE1_PHY); + else + ath79_device_reset_set(AR71XX_RESET_GE1_PHY); +} + +static enum led_brightness wndr3700_usb_led_get(struct led_classdev *cdev) +{ + return ath79_device_reset_get(AR71XX_RESET_GE1_PHY) ? LED_OFF : LED_FULL; +} + +static struct led_classdev wndr3700_usb_led = { + .name = "wndr3700:green:usb", + .brightness_set = wndr3700_usb_led_set, + .brightness_get = wndr3700_usb_led_get, +}; + +static int __devinit wndr3700_usb_led_probe(struct platform_device *pdev) +{ + return led_classdev_register(&pdev->dev, &wndr3700_usb_led); +} + +static int __devexit wndr3700_usb_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&wndr3700_usb_led); + return 0; +} + +static struct platform_driver wndr3700_usb_led_driver = { + .probe = wndr3700_usb_led_probe, + .remove = __devexit_p(wndr3700_usb_led_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init wndr3700_usb_led_init(void) +{ + return platform_driver_register(&wndr3700_usb_led_driver); +} + +static void __exit wndr3700_usb_led_exit(void) +{ + platform_driver_unregister(&wndr3700_usb_led_driver); +} + +module_init(wndr3700_usb_led_init); +module_exit(wndr3700_usb_led_exit); + +MODULE_DESCRIPTION("USB LED driver for the NETGEAR WNDR3700"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/mtd/nand/rb4xx_nand.c b/drivers/mtd/nand/rb4xx_nand.c new file mode 100644 index 0000000..1cb8f82 --- /dev/null +++ b/drivers/mtd/nand/rb4xx_nand.c @@ -0,0 +1,309 @@ +/* + * NAND flash driver for the MikroTik RouterBoard 4xx series + * + * Copyright (C) 2008-2011 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * This file was based on the driver for Linux 2.6.22 published by + * MikroTik for their RouterBoard 4xx series devices. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DRV_NAME "rb4xx-nand" +#define DRV_VERSION "0.2.0" +#define DRV_DESC "NAND flash driver for RouterBoard 4xx series" + +#define RB4XX_NAND_GPIO_READY 5 +#define RB4XX_NAND_GPIO_ALE 37 +#define RB4XX_NAND_GPIO_CLE 38 +#define RB4XX_NAND_GPIO_NCE 39 + +struct rb4xx_nand_info { + struct nand_chip chip; + struct mtd_info mtd; +}; + +/* + * We need to use the OLD Yaffs-1 OOB layout, otherwise the RB bootloader + * will not be able to find the kernel that we load. + */ +static struct nand_ecclayout rb4xx_nand_ecclayout = { + .eccbytes = 6, + .eccpos = { 8, 9, 10, 13, 14, 15 }, + .oobavail = 9, + .oobfree = { { 0, 4 }, { 6, 2 }, { 11, 2 }, { 4, 1 } } +}; + +static struct mtd_partition rb4xx_nand_partitions[] = { + { + .name = "booter", + .offset = 0, + .size = (256 * 1024), + .mask_flags = MTD_WRITEABLE, + }, + { + .name = "kernel", + .offset = (256 * 1024), + .size = (6 * 1024 * 1024) - (256 * 1024), + }, + { + .name = "rootfs", + .offset = MTDPART_OFS_NXTBLK, + .size = MTDPART_SIZ_FULL, + }, +}; + +static int rb4xx_nand_dev_ready(struct mtd_info *mtd) +{ + return gpio_get_value_cansleep(RB4XX_NAND_GPIO_READY); +} + +static void rb4xx_nand_write_cmd(unsigned char cmd) +{ + unsigned char data = cmd; + int err; + + err = rb4xx_cpld_write(&data, 1); + if (err) + pr_err("rb4xx_nand: write cmd failed, err=%d\n", err); +} + +static void rb4xx_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + if (ctrl & NAND_CTRL_CHANGE) { + gpio_set_value_cansleep(RB4XX_NAND_GPIO_CLE, + (ctrl & NAND_CLE) ? 1 : 0); + gpio_set_value_cansleep(RB4XX_NAND_GPIO_ALE, + (ctrl & NAND_ALE) ? 1 : 0); + gpio_set_value_cansleep(RB4XX_NAND_GPIO_NCE, + (ctrl & NAND_NCE) ? 0 : 1); + } + + if (cmd != NAND_CMD_NONE) + rb4xx_nand_write_cmd(cmd); +} + +static unsigned char rb4xx_nand_read_byte(struct mtd_info *mtd) +{ + unsigned char data = 0; + int err; + + err = rb4xx_cpld_read(&data, NULL, 1); + if (err) { + pr_err("rb4xx_nand: read data failed, err=%d\n", err); + data = 0xff; + } + + return data; +} + +static void rb4xx_nand_write_buf(struct mtd_info *mtd, const unsigned char *buf, + int len) +{ + int err; + + err = rb4xx_cpld_write(buf, len); + if (err) + pr_err("rb4xx_nand: write buf failed, err=%d\n", err); +} + +static void rb4xx_nand_read_buf(struct mtd_info *mtd, unsigned char *buf, + int len) +{ + int err; + + err = rb4xx_cpld_read(buf, NULL, len); + if (err) + pr_err("rb4xx_nand: read buf failed, err=%d\n", err); +} + +static int __devinit rb4xx_nand_probe(struct platform_device *pdev) +{ + struct rb4xx_nand_info *info; + int ret; + + printk(KERN_INFO DRV_DESC " version " DRV_VERSION "\n"); + + ret = gpio_request(RB4XX_NAND_GPIO_READY, "NAND RDY"); + if (ret) { + dev_err(&pdev->dev, "unable to request gpio %d\n", + RB4XX_NAND_GPIO_READY); + goto err; + } + + ret = gpio_direction_input(RB4XX_NAND_GPIO_READY); + if (ret) { + dev_err(&pdev->dev, "unable to set input mode on gpio %d\n", + RB4XX_NAND_GPIO_READY); + goto err_free_gpio_ready; + } + + ret = gpio_request(RB4XX_NAND_GPIO_ALE, "NAND ALE"); + if (ret) { + dev_err(&pdev->dev, "unable to request gpio %d\n", + RB4XX_NAND_GPIO_ALE); + goto err_free_gpio_ready; + } + + ret = gpio_direction_output(RB4XX_NAND_GPIO_ALE, 0); + if (ret) { + dev_err(&pdev->dev, "unable to set output mode on gpio %d\n", + RB4XX_NAND_GPIO_ALE); + goto err_free_gpio_ale; + } + + ret = gpio_request(RB4XX_NAND_GPIO_CLE, "NAND CLE"); + if (ret) { + dev_err(&pdev->dev, "unable to request gpio %d\n", + RB4XX_NAND_GPIO_CLE); + goto err_free_gpio_ale; + } + + ret = gpio_direction_output(RB4XX_NAND_GPIO_CLE, 0); + if (ret) { + dev_err(&pdev->dev, "unable to set output mode on gpio %d\n", + RB4XX_NAND_GPIO_CLE); + goto err_free_gpio_cle; + } + + ret = gpio_request(RB4XX_NAND_GPIO_NCE, "NAND NCE"); + if (ret) { + dev_err(&pdev->dev, "unable to request gpio %d\n", + RB4XX_NAND_GPIO_NCE); + goto err_free_gpio_cle; + } + + ret = gpio_direction_output(RB4XX_NAND_GPIO_NCE, 1); + if (ret) { + dev_err(&pdev->dev, "unable to set output mode on gpio %d\n", + RB4XX_NAND_GPIO_ALE); + goto err_free_gpio_nce; + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "rb4xx-nand: no memory for private data\n"); + ret = -ENOMEM; + goto err_free_gpio_nce; + } + + info->chip.priv = &info; + info->mtd.priv = &info->chip; + info->mtd.owner = THIS_MODULE; + + info->chip.cmd_ctrl = rb4xx_nand_cmd_ctrl; + info->chip.dev_ready = rb4xx_nand_dev_ready; + info->chip.read_byte = rb4xx_nand_read_byte; + info->chip.write_buf = rb4xx_nand_write_buf; + info->chip.read_buf = rb4xx_nand_read_buf; +#if 0 + info->chip.verify_buf = rb4xx_nand_verify_buf; +#endif + + info->chip.chip_delay = 25; + info->chip.ecc.mode = NAND_ECC_SOFT; + info->chip.options |= NAND_NO_AUTOINCR; + + platform_set_drvdata(pdev, info); + + ret = nand_scan_ident(&info->mtd, 1, NULL); + if (ret) { + ret = -ENXIO; + goto err_free_info; + } + + if (info->mtd.writesize == 512) + info->chip.ecc.layout = &rb4xx_nand_ecclayout; + + ret = nand_scan_tail(&info->mtd); + if (ret) { + return -ENXIO; + goto err_set_drvdata; + } + + mtd_device_register(&info->mtd, rb4xx_nand_partitions, + ARRAY_SIZE(rb4xx_nand_partitions)); + if (ret) + goto err_release_nand; + + return 0; + +err_release_nand: + nand_release(&info->mtd); +err_set_drvdata: + platform_set_drvdata(pdev, NULL); +err_free_info: + kfree(info); +err_free_gpio_nce: + gpio_free(RB4XX_NAND_GPIO_NCE); +err_free_gpio_cle: + gpio_free(RB4XX_NAND_GPIO_CLE); +err_free_gpio_ale: + gpio_free(RB4XX_NAND_GPIO_ALE); +err_free_gpio_ready: + gpio_free(RB4XX_NAND_GPIO_READY); +err: + return ret; +} + +static int __devexit rb4xx_nand_remove(struct platform_device *pdev) +{ + struct rb4xx_nand_info *info = platform_get_drvdata(pdev); + + nand_release(&info->mtd); + platform_set_drvdata(pdev, NULL); + kfree(info); + gpio_free(RB4XX_NAND_GPIO_NCE); + gpio_free(RB4XX_NAND_GPIO_CLE); + gpio_free(RB4XX_NAND_GPIO_ALE); + gpio_free(RB4XX_NAND_GPIO_READY); + + return 0; +} + +static struct platform_driver rb4xx_nand_driver = { + .probe = rb4xx_nand_probe, + .remove = __devexit_p(rb4xx_nand_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init rb4xx_nand_init(void) +{ + return platform_driver_register(&rb4xx_nand_driver); +} + +static void __exit rb4xx_nand_exit(void) +{ + platform_driver_unregister(&rb4xx_nand_driver); +} + +module_init(rb4xx_nand_init); +module_exit(rb4xx_nand_exit); + +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Imre Kaloz "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/rb750_nand.c b/drivers/mtd/nand/rb750_nand.c new file mode 100644 index 0000000..251e182 --- /dev/null +++ b/drivers/mtd/nand/rb750_nand.c @@ -0,0 +1,367 @@ +/* + * NAND flash driver for the MikroTik RouterBOARD 750 + * + * Copyright (C) 2010-2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DRV_NAME "rb750-nand" +#define DRV_VERSION "0.1.0" +#define DRV_DESC "NAND flash driver for the RouterBOARD 750" + +#define RB750_NAND_IO0 BIT(RB750_GPIO_NAND_IO0) +#define RB750_NAND_ALE BIT(RB750_GPIO_NAND_ALE) +#define RB750_NAND_CLE BIT(RB750_GPIO_NAND_CLE) +#define RB750_NAND_NRE BIT(RB750_GPIO_NAND_NRE) +#define RB750_NAND_NWE BIT(RB750_GPIO_NAND_NWE) +#define RB750_NAND_RDY BIT(RB750_GPIO_NAND_RDY) + +#define RB750_NAND_DATA_SHIFT 1 +#define RB750_NAND_DATA_BITS (0xff << RB750_NAND_DATA_SHIFT) +#define RB750_NAND_INPUT_BITS (RB750_NAND_DATA_BITS | RB750_NAND_RDY) +#define RB750_NAND_OUTPUT_BITS (RB750_NAND_ALE | RB750_NAND_CLE | \ + RB750_NAND_NRE | RB750_NAND_NWE) + +struct rb750_nand_info { + struct nand_chip chip; + struct mtd_info mtd; + struct rb7xx_nand_platform_data *pdata; +}; + +static inline struct rb750_nand_info *mtd_to_rbinfo(struct mtd_info *mtd) +{ + return container_of(mtd, struct rb750_nand_info, mtd); +} + +/* + * We need to use the OLD Yaffs-1 OOB layout, otherwise the RB bootloader + * will not be able to find the kernel that we load. + */ +static struct nand_ecclayout rb750_nand_ecclayout = { + .eccbytes = 6, + .eccpos = { 8, 9, 10, 13, 14, 15 }, + .oobavail = 9, + .oobfree = { { 0, 4 }, { 6, 2 }, { 11, 2 }, { 4, 1 } } +}; + +static struct mtd_partition rb750_nand_partitions[] = { + { + .name = "booter", + .offset = 0, + .size = (256 * 1024), + .mask_flags = MTD_WRITEABLE, + }, { + .name = "kernel", + .offset = (256 * 1024), + .size = (4 * 1024 * 1024) - (256 * 1024), + }, { + .name = "rootfs", + .offset = MTDPART_OFS_NXTBLK, + .size = MTDPART_SIZ_FULL, + }, +}; + +static void rb750_nand_write(const u8 *buf, unsigned len) +{ + void __iomem *base = ath79_gpio_base; + u32 out; + u32 t; + unsigned i; + + /* set data lines to output mode */ + t = __raw_readl(base + AR71XX_GPIO_REG_OE); + __raw_writel(t | RB750_NAND_DATA_BITS, base + AR71XX_GPIO_REG_OE); + + out = __raw_readl(base + AR71XX_GPIO_REG_OUT); + out &= ~(RB750_NAND_DATA_BITS | RB750_NAND_NWE); + for (i = 0; i != len; i++) { + u32 data; + + data = buf[i]; + data <<= RB750_NAND_DATA_SHIFT; + data |= out; + __raw_writel(data, base + AR71XX_GPIO_REG_OUT); + + __raw_writel(data | RB750_NAND_NWE, base + AR71XX_GPIO_REG_OUT); + /* flush write */ + __raw_readl(base + AR71XX_GPIO_REG_OUT); + } + + /* set data lines to input mode */ + t = __raw_readl(base + AR71XX_GPIO_REG_OE); + __raw_writel(t & ~RB750_NAND_DATA_BITS, base + AR71XX_GPIO_REG_OE); + /* flush write */ + __raw_readl(base + AR71XX_GPIO_REG_OE); +} + +static int rb750_nand_read_verify(u8 *read_buf, unsigned len, + const u8 *verify_buf) +{ + void __iomem *base = ath79_gpio_base; + unsigned i; + + for (i = 0; i < len; i++) { + u8 data; + + /* activate RE line */ + __raw_writel(RB750_NAND_NRE, base + AR71XX_GPIO_REG_CLEAR); + /* flush write */ + __raw_readl(base + AR71XX_GPIO_REG_CLEAR); + + /* read input lines */ + data = __raw_readl(base + AR71XX_GPIO_REG_IN) >> + RB750_NAND_DATA_SHIFT; + + /* deactivate RE line */ + __raw_writel(RB750_NAND_NRE, base + AR71XX_GPIO_REG_SET); + + if (read_buf) + read_buf[i] = data; + else if (verify_buf && verify_buf[i] != data) + return -EFAULT; + } + + return 0; +} + +static void rb750_nand_select_chip(struct mtd_info *mtd, int chip) +{ + struct rb750_nand_info *rbinfo = mtd_to_rbinfo(mtd); + void __iomem *base = ath79_gpio_base; + u32 t; + + if (chip >= 0) { + rbinfo->pdata->enable_pins(); + + /* set input mode for data lines */ + t = __raw_readl(base + AR71XX_GPIO_REG_OE); + __raw_writel(t & ~RB750_NAND_INPUT_BITS, + base + AR71XX_GPIO_REG_OE); + + /* deactivate RE and WE lines */ + __raw_writel(RB750_NAND_NRE | RB750_NAND_NWE, + base + AR71XX_GPIO_REG_SET); + /* flush write */ + (void) __raw_readl(base + AR71XX_GPIO_REG_SET); + + /* activate CE line */ + __raw_writel(rbinfo->pdata->nce_line, + base + AR71XX_GPIO_REG_CLEAR); + } else { + /* deactivate CE line */ + __raw_writel(rbinfo->pdata->nce_line, + base + AR71XX_GPIO_REG_SET); + /* flush write */ + (void) __raw_readl(base + AR71XX_GPIO_REG_SET); + + t = __raw_readl(base + AR71XX_GPIO_REG_OE); + __raw_writel(t | RB750_NAND_IO0 | RB750_NAND_RDY, + base + AR71XX_GPIO_REG_OE); + + rbinfo->pdata->disable_pins(); + } +} + +static int rb750_nand_dev_ready(struct mtd_info *mtd) +{ + void __iomem *base = ath79_gpio_base; + + return !!(__raw_readl(base + AR71XX_GPIO_REG_IN) & RB750_NAND_RDY); +} + +static void rb750_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + if (ctrl & NAND_CTRL_CHANGE) { + void __iomem *base = ath79_gpio_base; + u32 t; + + t = __raw_readl(base + AR71XX_GPIO_REG_OUT); + + t &= ~(RB750_NAND_CLE | RB750_NAND_ALE); + t |= (ctrl & NAND_CLE) ? RB750_NAND_CLE : 0; + t |= (ctrl & NAND_ALE) ? RB750_NAND_ALE : 0; + + __raw_writel(t, base + AR71XX_GPIO_REG_OUT); + /* flush write */ + __raw_readl(base + AR71XX_GPIO_REG_OUT); + } + + if (cmd != NAND_CMD_NONE) { + u8 t = cmd; + rb750_nand_write(&t, 1); + } +} + +static u8 rb750_nand_read_byte(struct mtd_info *mtd) +{ + u8 data = 0; + rb750_nand_read_verify(&data, 1, NULL); + return data; +} + +static void rb750_nand_read_buf(struct mtd_info *mtd, u8 *buf, int len) +{ + rb750_nand_read_verify(buf, len, NULL); +} + +static void rb750_nand_write_buf(struct mtd_info *mtd, const u8 *buf, int len) +{ + rb750_nand_write(buf, len); +} + +static int rb750_nand_verify_buf(struct mtd_info *mtd, const u8 *buf, int len) +{ + return rb750_nand_read_verify(NULL, len, buf); +} + +static void __init rb750_nand_gpio_init(struct rb750_nand_info *info) +{ + void __iomem *base = ath79_gpio_base; + u32 out; + u32 t; + + out = __raw_readl(base + AR71XX_GPIO_REG_OUT); + + /* setup output levels */ + __raw_writel(RB750_NAND_NCE | RB750_NAND_NRE | RB750_NAND_NWE, + base + AR71XX_GPIO_REG_SET); + + __raw_writel(RB750_NAND_ALE | RB750_NAND_CLE, + base + AR71XX_GPIO_REG_CLEAR); + + /* setup input lines */ + t = __raw_readl(base + AR71XX_GPIO_REG_OE); + __raw_writel(t & ~(RB750_NAND_INPUT_BITS), base + AR71XX_GPIO_REG_OE); + + /* setup output lines */ + t = __raw_readl(base + AR71XX_GPIO_REG_OE); + t |= RB750_NAND_OUTPUT_BITS; + t |= info->pdata->nce_line; + __raw_writel(t, base + AR71XX_GPIO_REG_OE); + + info->pdata->latch_change(~out & RB750_NAND_IO0, out & RB750_NAND_IO0); +} + +static int __devinit rb750_nand_probe(struct platform_device *pdev) +{ + struct rb750_nand_info *info; + struct rb7xx_nand_platform_data *pdata; + int ret; + + printk(KERN_INFO DRV_DESC " version " DRV_VERSION "\n"); + + pdata = pdev->dev.platform_data; + if (!pdata) + return -EINVAL; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->chip.priv = &info; + info->mtd.priv = &info->chip; + info->mtd.owner = THIS_MODULE; + + info->chip.select_chip = rb750_nand_select_chip; + info->chip.cmd_ctrl = rb750_nand_cmd_ctrl; + info->chip.dev_ready = rb750_nand_dev_ready; + info->chip.read_byte = rb750_nand_read_byte; + info->chip.write_buf = rb750_nand_write_buf; + info->chip.read_buf = rb750_nand_read_buf; + info->chip.verify_buf = rb750_nand_verify_buf; + + info->chip.chip_delay = 25; + info->chip.ecc.mode = NAND_ECC_SOFT; + info->chip.options |= NAND_NO_AUTOINCR; + + info->pdata = pdata; + + platform_set_drvdata(pdev, info); + + rb750_nand_gpio_init(info); + + ret = nand_scan_ident(&info->mtd, 1, NULL); + if (ret) { + ret = -ENXIO; + goto err_free_info; + } + + if (info->mtd.writesize == 512) + info->chip.ecc.layout = &rb750_nand_ecclayout; + + ret = nand_scan_tail(&info->mtd); + if (ret) { + return -ENXIO; + goto err_set_drvdata; + } + + ret = mtd_device_register(&info->mtd, rb750_nand_partitions, + ARRAY_SIZE(rb750_nand_partitions)); + if (ret) + goto err_release_nand; + + return 0; + +err_release_nand: + nand_release(&info->mtd); +err_set_drvdata: + platform_set_drvdata(pdev, NULL); +err_free_info: + kfree(info); + return ret; +} + +static int __devexit rb750_nand_remove(struct platform_device *pdev) +{ + struct rb750_nand_info *info = platform_get_drvdata(pdev); + + nand_release(&info->mtd); + platform_set_drvdata(pdev, NULL); + kfree(info); + + return 0; +} + +static struct platform_driver rb750_nand_driver = { + .probe = rb750_nand_probe, + .remove = __devexit_p(rb750_nand_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init rb750_nand_init(void) +{ + return platform_driver_register(&rb750_nand_driver); +} + +static void __exit rb750_nand_exit(void) +{ + platform_driver_unregister(&rb750_nand_driver); +} + +module_init(rb750_nand_init); +module_exit(rb750_nand_exit); + +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/tplinkpart.c b/drivers/mtd/tplinkpart.c new file mode 100644 index 0000000..674df66 --- /dev/null +++ b/drivers/mtd/tplinkpart.c @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define TPLINK_NUM_PARTS 5 +#define TPLINK_HEADER_V1 0x01000000 +#define MD5SUM_LEN 16 + +#define TPLINK_ART_LEN 0x10000 +#define TPLINK_KERNEL_OFFS 0x20000 + +struct tplink_fw_header { + uint32_t version; /* header version */ + char vendor_name[24]; + char fw_version[36]; + uint32_t hw_id; /* hardware id */ + uint32_t hw_rev; /* hardware revision */ + uint32_t unk1; + uint8_t md5sum1[MD5SUM_LEN]; + uint32_t unk2; + uint8_t md5sum2[MD5SUM_LEN]; + uint32_t unk3; + uint32_t kernel_la; /* kernel load address */ + uint32_t kernel_ep; /* kernel entry point */ + uint32_t fw_length; /* total length of the firmware */ + uint32_t kernel_ofs; /* kernel data offset */ + uint32_t kernel_len; /* kernel data length */ + uint32_t rootfs_ofs; /* rootfs data offset */ + uint32_t rootfs_len; /* rootfs data length */ + uint32_t boot_ofs; /* bootloader data offset */ + uint32_t boot_len; /* bootloader data length */ + uint8_t pad[360]; +} __attribute__ ((packed)); + +static struct tplink_fw_header * +tplink_read_header(struct mtd_info *mtd, size_t offset) +{ + struct tplink_fw_header *header; + size_t header_len; + size_t retlen; + int ret; + u32 t; + + header = vmalloc(sizeof(*header)); + if (!header) + goto err; + + header_len = sizeof(struct tplink_fw_header); + ret = mtd->read(mtd, offset, header_len, &retlen, + (unsigned char *) header); + if (ret) + goto err_free_header; + + if (retlen != header_len) + goto err_free_header; + + /* sanity checks */ + t = be32_to_cpu(header->version); + if (t != TPLINK_HEADER_V1) + goto err_free_header; + + t = be32_to_cpu(header->kernel_ofs); + if (t != header_len) + goto err_free_header; + + return header; + +err_free_header: + vfree(header); +err: + return NULL; +} + +static int tplink_check_rootfs_magic(struct mtd_info *mtd, size_t offset) +{ + u32 magic; + size_t retlen; + int ret; + + ret = mtd->read(mtd, offset, sizeof(magic), &retlen, + (unsigned char *) &magic); + if (ret) + return ret; + + if (retlen != sizeof(magic)) + return -EIO; + + if (le32_to_cpu(magic) != SQUASHFS_MAGIC && + magic != 0x19852003) + return -EINVAL; + + return 0; +} + +static int tplink_parse_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct tplink_fw_header *header; + int nr_parts; + size_t offset; + size_t art_offset; + size_t rootfs_offset; + size_t squashfs_offset; + int ret; + + nr_parts = TPLINK_NUM_PARTS; + parts = kzalloc(nr_parts * sizeof(struct mtd_partition), GFP_KERNEL); + if (!parts) { + ret = -ENOMEM; + goto err; + } + + offset = TPLINK_KERNEL_OFFS; + + header = tplink_read_header(master, offset); + if (!header) { + pr_notice("%s: no TP-Link header found\n", master->name); + ret = -ENODEV; + goto err_free_parts; + } + + squashfs_offset = offset + sizeof(struct tplink_fw_header) + + be32_to_cpu(header->kernel_len); + + ret = tplink_check_rootfs_magic(master, squashfs_offset); + if (ret == 0) + rootfs_offset = squashfs_offset; + else + rootfs_offset = offset + be32_to_cpu(header->rootfs_ofs); + + art_offset = master->size - TPLINK_ART_LEN; + + parts[0].name = "u-boot"; + parts[0].offset = 0; + parts[0].size = offset; + parts[0].mask_flags = MTD_WRITEABLE; + + parts[1].name = "kernel"; + parts[1].offset = offset; + parts[1].size = rootfs_offset - offset; + + parts[2].name = "rootfs"; + parts[2].offset = rootfs_offset; + parts[2].size = art_offset - rootfs_offset; + + parts[3].name = "art"; + parts[3].offset = art_offset; + parts[3].size = TPLINK_ART_LEN; + parts[3].mask_flags = MTD_WRITEABLE; + + parts[4].name = "firmware"; + parts[4].offset = offset; + parts[4].size = art_offset - offset; + + vfree(header); + + *pparts = parts; + return nr_parts; + +err_free_parts: + kfree(parts); +err: + *pparts = NULL; + return ret; +} + +static struct mtd_part_parser tplink_parser = { + .owner = THIS_MODULE, + .parse_fn = tplink_parse_partitions, + .name = "tp-link", +}; + +static int __init tplink_parser_init(void) +{ + return register_mtd_parser(&tplink_parser); +} + +module_init(tplink_parser_init); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Gabor Juhos "); diff --git a/drivers/mtd/wrt160nl_part.c b/drivers/mtd/wrt160nl_part.c new file mode 100644 index 0000000..72927f6 --- /dev/null +++ b/drivers/mtd/wrt160nl_part.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2009 Christian Daniel + * Copyright (C) 2009 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * TRX flash partition table. + * Based on ar7 map by Felix Fietkau + * + */ + +#include +#include +#include +#include + +#include +#include + +struct cybertan_header { + char magic[4]; + u8 res1[4]; + char fw_date[3]; + char fw_ver[3]; + char id[4]; + char hw_ver; + char unused; + u8 flags[2]; + u8 res2[10]; +}; + +#define TRX_PARTS 6 +#define TRX_MAGIC 0x30524448 +#define TRX_MAX_OFFSET 3 + +struct trx_header { + uint32_t magic; /* "HDR0" */ + uint32_t len; /* Length of file including header */ + uint32_t crc32; /* 32-bit CRC from flag_version to end of file */ + uint32_t flag_version; /* 0:15 flags, 16:31 version */ + uint32_t offsets[TRX_MAX_OFFSET]; /* Offsets of partitions from start of header */ +}; + +#define IH_MAGIC 0x27051956 /* Image Magic Number */ +#define IH_NMLEN 32 /* Image Name Length */ + +struct uimage_header { + uint32_t ih_magic; /* Image Header Magic Number */ + uint32_t ih_hcrc; /* Image Header CRC Checksum */ + uint32_t ih_time; /* Image Creation Timestamp */ + uint32_t ih_size; /* Image Data Size */ + uint32_t ih_load; /* Data» Load Address */ + uint32_t ih_ep; /* Entry Point Address */ + uint32_t ih_dcrc; /* Image Data CRC Checksum */ + uint8_t ih_os; /* Operating System */ + uint8_t ih_arch; /* CPU architecture */ + uint8_t ih_type; /* Image Type */ + uint8_t ih_comp; /* Compression Type */ + uint8_t ih_name[IH_NMLEN]; /* Image Name */ +}; + +struct wrt160nl_header { + struct cybertan_header cybertan; + struct trx_header trx; + struct uimage_header uimage; +} __attribute__ ((packed)); + +#define WRT160NL_UBOOT_LEN 0x40000 +#define WRT160NL_ART_LEN 0x10000 +#define WRT160NL_NVRAM_LEN 0x10000 + +static int wrt160nl_parse_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct wrt160nl_header *header; + struct trx_header *theader; + struct uimage_header *uheader; + struct mtd_partition *trx_parts; + size_t retlen; + unsigned int kernel_len; + unsigned int uboot_len; + unsigned int nvram_len; + unsigned int art_len; + int ret; + + uboot_len = max_t(unsigned int, master->erasesize, WRT160NL_UBOOT_LEN); + nvram_len = max_t(unsigned int, master->erasesize, WRT160NL_NVRAM_LEN); + art_len = max_t(unsigned int, master->erasesize, WRT160NL_ART_LEN); + + trx_parts = kzalloc(TRX_PARTS * sizeof(struct mtd_partition), + GFP_KERNEL); + if (!trx_parts) { + ret = -ENOMEM; + goto out; + } + + header = vmalloc(sizeof(*header)); + if (!header) { + return -ENOMEM; + goto free_parts; + } + + ret = master->read(master, uboot_len, sizeof(*header), + &retlen, (void *) header); + if (ret) + goto free_hdr; + + if (retlen != sizeof(*header)) { + ret = -EIO; + goto free_hdr; + } + + if (strncmp(header->cybertan.magic, "NL16", 4) != 0) { + printk(KERN_NOTICE "%s: no WRT160NL signature found\n", + master->name); + goto free_hdr; + } + + theader = &header->trx; + if (le32_to_cpu(theader->magic) != TRX_MAGIC) { + printk(KERN_NOTICE "%s: no TRX header found\n", master->name); + goto free_hdr; + } + + uheader = &header->uimage; + if (uheader->ih_magic != IH_MAGIC) { + printk(KERN_NOTICE "%s: no uImage found\n", master->name); + goto free_hdr; + } + + kernel_len = le32_to_cpu(theader->offsets[1]) + + sizeof(struct cybertan_header); + + trx_parts[0].name = "u-boot"; + trx_parts[0].offset = 0; + trx_parts[0].size = uboot_len; + trx_parts[0].mask_flags = MTD_WRITEABLE; + + trx_parts[1].name = "kernel"; + trx_parts[1].offset = trx_parts[0].offset + trx_parts[0].size; + trx_parts[1].size = kernel_len; + trx_parts[1].mask_flags = 0; + + trx_parts[2].name = "rootfs"; + trx_parts[2].offset = trx_parts[1].offset + trx_parts[1].size; + trx_parts[2].size = master->size - uboot_len - nvram_len - art_len - + trx_parts[1].size; + trx_parts[2].mask_flags = 0; + + trx_parts[3].name = "nvram"; + trx_parts[3].offset = master->size - nvram_len - art_len; + trx_parts[3].size = nvram_len; + trx_parts[3].mask_flags = MTD_WRITEABLE; + + trx_parts[4].name = "art"; + trx_parts[4].offset = master->size - art_len; + trx_parts[4].size = art_len; + trx_parts[4].mask_flags = MTD_WRITEABLE; + + trx_parts[5].name = "firmware"; + trx_parts[5].offset = uboot_len; + trx_parts[5].size = master->size - uboot_len - nvram_len - art_len; + trx_parts[5].mask_flags = 0; + + vfree(header); + + *pparts = trx_parts; + return TRX_PARTS; + +free_hdr: + vfree(header); +free_parts: + kfree(trx_parts); +out: + return ret; +} + +static struct mtd_part_parser wrt160nl_parser = { + .owner = THIS_MODULE, + .parse_fn = wrt160nl_parse_partitions, + .name = "wrt160nl", +}; + +static int __init wrt160nl_parser_init(void) +{ + return register_mtd_parser(&wrt160nl_parser); +} + +module_init(wrt160nl_parser_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Christian Daniel "); diff --git a/drivers/net/dsa/mv88e6063.c b/drivers/net/dsa/mv88e6063.c new file mode 100644 index 0000000..b41eb54 --- /dev/null +++ b/drivers/net/dsa/mv88e6063.c @@ -0,0 +1,294 @@ +/* + * net/dsa/mv88e6063.c - Driver for Marvell 88e6063 switch chips + * Copyright (c) 2009 Gabor Juhos + * + * This driver was base on: net/dsa/mv88e6060.c + * net/dsa/mv88e6063.c - Driver for Marvell 88e6060 switch chips + * Copyright (c) 2008-2009 Marvell Semiconductor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include + +#define REG_BASE 0x10 +#define REG_PHY(p) (REG_BASE + (p)) +#define REG_PORT(p) (REG_BASE + 8 + (p)) +#define REG_GLOBAL (REG_BASE + 0x0f) +#define NUM_PORTS 7 + +static int reg_read(struct dsa_switch *ds, int addr, int reg) +{ + return mdiobus_read(ds->master_mii_bus, addr, reg); +} + +#define REG_READ(addr, reg) \ + ({ \ + int __ret; \ + \ + __ret = reg_read(ds, addr, reg); \ + if (__ret < 0) \ + return __ret; \ + __ret; \ + }) + + +static int reg_write(struct dsa_switch *ds, int addr, int reg, u16 val) +{ + return mdiobus_write(ds->master_mii_bus, addr, reg, val); +} + +#define REG_WRITE(addr, reg, val) \ + ({ \ + int __ret; \ + \ + __ret = reg_write(ds, addr, reg, val); \ + if (__ret < 0) \ + return __ret; \ + }) + +static char *mv88e6063_probe(struct mii_bus *bus, int sw_addr) +{ + int ret; + + ret = mdiobus_read(bus, REG_PORT(0), 0x03); + if (ret >= 0) { + ret &= 0xfff0; + if (ret == 0x1530) + return "Marvell 88E6063"; + } + + return NULL; +} + +static int mv88e6063_switch_reset(struct dsa_switch *ds) +{ + int i; + int ret; + + /* + * Set all ports to the disabled state. + */ + for (i = 0; i < NUM_PORTS; i++) { + ret = REG_READ(REG_PORT(i), 0x04); + REG_WRITE(REG_PORT(i), 0x04, ret & 0xfffc); + } + + /* + * Wait for transmit queues to drain. + */ + msleep(2); + + /* + * Reset the switch. + */ + REG_WRITE(REG_GLOBAL, 0x0a, 0xa130); + + /* + * Wait up to one second for reset to complete. + */ + for (i = 0; i < 1000; i++) { + ret = REG_READ(REG_GLOBAL, 0x00); + if ((ret & 0x8000) == 0x0000) + break; + + msleep(1); + } + if (i == 1000) + return -ETIMEDOUT; + + return 0; +} + +static int mv88e6063_setup_global(struct dsa_switch *ds) +{ + /* + * Disable discarding of frames with excessive collisions, + * set the maximum frame size to 1536 bytes, and mask all + * interrupt sources. + */ + REG_WRITE(REG_GLOBAL, 0x04, 0x0800); + + /* + * Enable automatic address learning, set the address + * database size to 1024 entries, and set the default aging + * time to 5 minutes. + */ + REG_WRITE(REG_GLOBAL, 0x0a, 0x2130); + + return 0; +} + +static int mv88e6063_setup_port(struct dsa_switch *ds, int p) +{ + int addr = REG_PORT(p); + + /* + * Do not force flow control, disable Ingress and Egress + * Header tagging, disable VLAN tunneling, and set the port + * state to Forwarding. Additionally, if this is the CPU + * port, enable Ingress and Egress Trailer tagging mode. + */ + REG_WRITE(addr, 0x04, dsa_is_cpu_port(ds, p) ? 0x4103 : 0x0003); + + /* + * Port based VLAN map: give each port its own address + * database, allow the CPU port to talk to each of the 'real' + * ports, and allow each of the 'real' ports to only talk to + * the CPU port. + */ + REG_WRITE(addr, 0x06, + ((p & 0xf) << 12) | + (dsa_is_cpu_port(ds, p) ? + ds->phys_port_mask : + (1 << ds->dst->cpu_port))); + + /* + * Port Association Vector: when learning source addresses + * of packets, add the address to the address database using + * a port bitmap that has only the bit for this port set and + * the other bits clear. + */ + REG_WRITE(addr, 0x0b, 1 << p); + + return 0; +} + +static int mv88e6063_setup(struct dsa_switch *ds) +{ + int i; + int ret; + + ret = mv88e6063_switch_reset(ds); + if (ret < 0) + return ret; + + /* @@@ initialise atu */ + + ret = mv88e6063_setup_global(ds); + if (ret < 0) + return ret; + + for (i = 0; i < NUM_PORTS; i++) { + ret = mv88e6063_setup_port(ds, i); + if (ret < 0) + return ret; + } + + return 0; +} + +static int mv88e6063_set_addr(struct dsa_switch *ds, u8 *addr) +{ + REG_WRITE(REG_GLOBAL, 0x01, (addr[0] << 8) | addr[1]); + REG_WRITE(REG_GLOBAL, 0x02, (addr[2] << 8) | addr[3]); + REG_WRITE(REG_GLOBAL, 0x03, (addr[4] << 8) | addr[5]); + + return 0; +} + +static int mv88e6063_port_to_phy_addr(int port) +{ + if (port >= 0 && port <= NUM_PORTS) + return REG_PHY(port); + return -1; +} + +static int mv88e6063_phy_read(struct dsa_switch *ds, int port, int regnum) +{ + int addr; + + addr = mv88e6063_port_to_phy_addr(port); + if (addr == -1) + return 0xffff; + + return reg_read(ds, addr, regnum); +} + +static int +mv88e6063_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val) +{ + int addr; + + addr = mv88e6063_port_to_phy_addr(port); + if (addr == -1) + return 0xffff; + + return reg_write(ds, addr, regnum, val); +} + +static void mv88e6063_poll_link(struct dsa_switch *ds) +{ + int i; + + for (i = 0; i < DSA_MAX_PORTS; i++) { + struct net_device *dev; + int uninitialized_var(port_status); + int link; + int speed; + int duplex; + int fc; + + dev = ds->ports[i]; + if (dev == NULL) + continue; + + link = 0; + if (dev->flags & IFF_UP) { + port_status = reg_read(ds, REG_PORT(i), 0x00); + if (port_status < 0) + continue; + + link = !!(port_status & 0x1000); + } + + if (!link) { + if (netif_carrier_ok(dev)) { + printk(KERN_INFO "%s: link down\n", dev->name); + netif_carrier_off(dev); + } + continue; + } + + speed = (port_status & 0x0100) ? 100 : 10; + duplex = (port_status & 0x0200) ? 1 : 0; + fc = ((port_status & 0xc000) == 0xc000) ? 1 : 0; + + if (!netif_carrier_ok(dev)) { + printk(KERN_INFO "%s: link up, %d Mb/s, %s duplex, " + "flow control %sabled\n", dev->name, + speed, duplex ? "full" : "half", + fc ? "en" : "dis"); + netif_carrier_on(dev); + } + } +} + +static struct dsa_switch_driver mv88e6063_switch_driver = { + .tag_protocol = htons(ETH_P_TRAILER), + .probe = mv88e6063_probe, + .setup = mv88e6063_setup, + .set_addr = mv88e6063_set_addr, + .phy_read = mv88e6063_phy_read, + .phy_write = mv88e6063_phy_write, + .poll_link = mv88e6063_poll_link, +}; + +static int __init mv88e6063_init(void) +{ + register_switch_driver(&mv88e6063_switch_driver); + return 0; +} +module_init(mv88e6063_init); + +static void __exit mv88e6063_cleanup(void) +{ + unregister_switch_driver(&mv88e6063_switch_driver); +} +module_exit(mv88e6063_cleanup); diff --git a/drivers/net/ethernet/atheros/ag71xx/Kconfig b/drivers/net/ethernet/atheros/ag71xx/Kconfig new file mode 100644 index 0000000..42d544f --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/Kconfig @@ -0,0 +1,33 @@ +config AG71XX + tristate "Atheros AR7XXX/AR9XXX built-in ethernet mac support" + depends on ATH79 + select PHYLIB + help + If you wish to compile a kernel for AR7XXX/91XXX and enable + ethernet support, then you should always answer Y to this. + +if AG71XX + +config AG71XX_DEBUG + bool "Atheros AR71xx built-in ethernet driver debugging" + default n + help + Atheros AR71xx built-in ethernet driver debugging messages. + +config AG71XX_DEBUG_FS + bool "Atheros AR71xx built-in ethernet driver debugfs support" + depends on DEBUG_FS + default n + help + Say Y, if you need access to various statistics provided by + the ag71xx driver. + +config AG71XX_AR8216_SUPPORT + bool "special support for the Atheros AR8216 switch" + default n + default y if ATH79_MACH_WNR2000 || ATH79_MACH_MZK_W04NU + help + Say 'y' here if you want to enable special support for the + Atheros AR8216 switch found on some boards. + +endif diff --git a/drivers/net/ethernet/atheros/ag71xx/Makefile b/drivers/net/ethernet/atheros/ag71xx/Makefile new file mode 100644 index 0000000..f86e01a --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for the Atheros AR71xx built-in ethernet macs +# + +ag71xx-y += ag71xx_main.o +ag71xx-y += ag71xx_ethtool.o +ag71xx-y += ag71xx_phy.o +ag71xx-y += ag71xx_mdio.o +ag71xx-y += ag71xx_ar7240.o + +ag71xx-$(CONFIG_AG71XX_DEBUG_FS) += ag71xx_debugfs.o +ag71xx-$(CONFIG_AG71XX_AR8216_SUPPORT) += ag71xx_ar8216.o + +obj-$(CONFIG_AG71XX) += ag71xx.o diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx.h b/drivers/net/ethernet/atheros/ag71xx/ag71xx.h new file mode 100644 index 0000000..b9d95ad --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx.h @@ -0,0 +1,477 @@ +/* + * Atheros AR71xx built-in ethernet mac driver + * + * Copyright (C) 2008-2010 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * Based on Atheros' AG7100 driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef __AG71XX_H +#define __AG71XX_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define AG71XX_DRV_NAME "ag71xx" +#define AG71XX_DRV_VERSION "0.5.35" + +#define AG71XX_NAPI_WEIGHT 64 +#define AG71XX_OOM_REFILL (1 + HZ/10) + +#define AG71XX_INT_ERR (AG71XX_INT_RX_BE | AG71XX_INT_TX_BE) +#define AG71XX_INT_TX (AG71XX_INT_TX_PS) +#define AG71XX_INT_RX (AG71XX_INT_RX_PR | AG71XX_INT_RX_OF) + +#define AG71XX_INT_POLL (AG71XX_INT_RX | AG71XX_INT_TX) +#define AG71XX_INT_INIT (AG71XX_INT_ERR | AG71XX_INT_POLL) + +#define AG71XX_TX_MTU_LEN 1540 +#define AG71XX_RX_PKT_SIZE \ + (ETH_FRAME_LEN + ETH_FCS_LEN + VLAN_HLEN) +#define AG71XX_RX_BUF_SIZE (AG71XX_RX_PKT_SIZE + NET_SKB_PAD + NET_IP_ALIGN) + +#define AG71XX_TX_RING_SIZE_DEFAULT 64 +#define AG71XX_RX_RING_SIZE_DEFAULT 128 + +#define AG71XX_TX_RING_SIZE_MAX 256 +#define AG71XX_RX_RING_SIZE_MAX 256 + +#ifdef CONFIG_AG71XX_DEBUG +#define DBG(fmt, args...) pr_debug(fmt, ## args) +#else +#define DBG(fmt, args...) do {} while (0) +#endif + +#define ag71xx_assert(_cond) \ +do { \ + if (_cond) \ + break; \ + printk("%s,%d: assertion failed\n", __FILE__, __LINE__); \ + BUG(); \ +} while (0) + +struct ag71xx_desc { + u32 data; + u32 ctrl; +#define DESC_EMPTY BIT(31) +#define DESC_MORE BIT(24) +#define DESC_PKTLEN_M 0xfff + u32 next; + u32 pad; +} __attribute__((aligned(4))); + +struct ag71xx_buf { + union { + struct sk_buff *skb; + void *rx_buf; + }; + struct ag71xx_desc *desc; + dma_addr_t dma_addr; + unsigned long timestamp; +}; + +struct ag71xx_ring { + struct ag71xx_buf *buf; + u8 *descs_cpu; + dma_addr_t descs_dma; + unsigned int desc_size; + unsigned int curr; + unsigned int dirty; + unsigned int size; +}; + +struct ag71xx_mdio { + struct mii_bus *mii_bus; + int mii_irq[PHY_MAX_ADDR]; + void __iomem *mdio_base; + struct ag71xx_mdio_platform_data *pdata; +}; + +struct ag71xx_int_stats { + unsigned long rx_pr; + unsigned long rx_be; + unsigned long rx_of; + unsigned long tx_ps; + unsigned long tx_be; + unsigned long tx_ur; + unsigned long total; +}; + +struct ag71xx_napi_stats { + unsigned long napi_calls; + unsigned long rx_count; + unsigned long rx_packets; + unsigned long rx_packets_max; + unsigned long tx_count; + unsigned long tx_packets; + unsigned long tx_packets_max; + + unsigned long rx[AG71XX_NAPI_WEIGHT + 1]; + unsigned long tx[AG71XX_NAPI_WEIGHT + 1]; +}; + +struct ag71xx_debug { + struct dentry *debugfs_dir; + + struct ag71xx_int_stats int_stats; + struct ag71xx_napi_stats napi_stats; +}; + +struct ag71xx { + void __iomem *mac_base; + + spinlock_t lock; + struct platform_device *pdev; + struct net_device *dev; + struct napi_struct napi; + u32 msg_enable; + + struct ag71xx_desc *stop_desc; + dma_addr_t stop_desc_dma; + + struct ag71xx_ring rx_ring; + struct ag71xx_ring tx_ring; + + struct mii_bus *mii_bus; + struct phy_device *phy_dev; + void *phy_priv; + + unsigned int link; + unsigned int speed; + int duplex; + + struct work_struct restart_work; + struct delayed_work link_work; + struct timer_list oom_timer; + +#ifdef CONFIG_AG71XX_DEBUG_FS + struct ag71xx_debug debug; +#endif +}; + +extern struct ethtool_ops ag71xx_ethtool_ops; +void ag71xx_link_adjust(struct ag71xx *ag); + +int ag71xx_mdio_driver_init(void) __init; +void ag71xx_mdio_driver_exit(void); + +int ag71xx_phy_connect(struct ag71xx *ag); +void ag71xx_phy_disconnect(struct ag71xx *ag); +void ag71xx_phy_start(struct ag71xx *ag); +void ag71xx_phy_stop(struct ag71xx *ag); + +static inline struct ag71xx_platform_data *ag71xx_get_pdata(struct ag71xx *ag) +{ + return ag->pdev->dev.platform_data; +} + +static inline int ag71xx_desc_empty(struct ag71xx_desc *desc) +{ + return (desc->ctrl & DESC_EMPTY) != 0; +} + +static inline int ag71xx_desc_pktlen(struct ag71xx_desc *desc) +{ + return desc->ctrl & DESC_PKTLEN_M; +} + +/* Register offsets */ +#define AG71XX_REG_MAC_CFG1 0x0000 +#define AG71XX_REG_MAC_CFG2 0x0004 +#define AG71XX_REG_MAC_IPG 0x0008 +#define AG71XX_REG_MAC_HDX 0x000c +#define AG71XX_REG_MAC_MFL 0x0010 +#define AG71XX_REG_MII_CFG 0x0020 +#define AG71XX_REG_MII_CMD 0x0024 +#define AG71XX_REG_MII_ADDR 0x0028 +#define AG71XX_REG_MII_CTRL 0x002c +#define AG71XX_REG_MII_STATUS 0x0030 +#define AG71XX_REG_MII_IND 0x0034 +#define AG71XX_REG_MAC_IFCTL 0x0038 +#define AG71XX_REG_MAC_ADDR1 0x0040 +#define AG71XX_REG_MAC_ADDR2 0x0044 +#define AG71XX_REG_FIFO_CFG0 0x0048 +#define AG71XX_REG_FIFO_CFG1 0x004c +#define AG71XX_REG_FIFO_CFG2 0x0050 +#define AG71XX_REG_FIFO_CFG3 0x0054 +#define AG71XX_REG_FIFO_CFG4 0x0058 +#define AG71XX_REG_FIFO_CFG5 0x005c +#define AG71XX_REG_FIFO_RAM0 0x0060 +#define AG71XX_REG_FIFO_RAM1 0x0064 +#define AG71XX_REG_FIFO_RAM2 0x0068 +#define AG71XX_REG_FIFO_RAM3 0x006c +#define AG71XX_REG_FIFO_RAM4 0x0070 +#define AG71XX_REG_FIFO_RAM5 0x0074 +#define AG71XX_REG_FIFO_RAM6 0x0078 +#define AG71XX_REG_FIFO_RAM7 0x007c + +#define AG71XX_REG_TX_CTRL 0x0180 +#define AG71XX_REG_TX_DESC 0x0184 +#define AG71XX_REG_TX_STATUS 0x0188 +#define AG71XX_REG_RX_CTRL 0x018c +#define AG71XX_REG_RX_DESC 0x0190 +#define AG71XX_REG_RX_STATUS 0x0194 +#define AG71XX_REG_INT_ENABLE 0x0198 +#define AG71XX_REG_INT_STATUS 0x019c + +#define AG71XX_REG_FIFO_DEPTH 0x01a8 +#define AG71XX_REG_RX_SM 0x01b0 +#define AG71XX_REG_TX_SM 0x01b4 + +#define MAC_CFG1_TXE BIT(0) /* Tx Enable */ +#define MAC_CFG1_STX BIT(1) /* Synchronize Tx Enable */ +#define MAC_CFG1_RXE BIT(2) /* Rx Enable */ +#define MAC_CFG1_SRX BIT(3) /* Synchronize Rx Enable */ +#define MAC_CFG1_TFC BIT(4) /* Tx Flow Control Enable */ +#define MAC_CFG1_RFC BIT(5) /* Rx Flow Control Enable */ +#define MAC_CFG1_LB BIT(8) /* Loopback mode */ +#define MAC_CFG1_SR BIT(31) /* Soft Reset */ + +#define MAC_CFG2_FDX BIT(0) +#define MAC_CFG2_CRC_EN BIT(1) +#define MAC_CFG2_PAD_CRC_EN BIT(2) +#define MAC_CFG2_LEN_CHECK BIT(4) +#define MAC_CFG2_HUGE_FRAME_EN BIT(5) +#define MAC_CFG2_IF_1000 BIT(9) +#define MAC_CFG2_IF_10_100 BIT(8) + +#define FIFO_CFG0_WTM BIT(0) /* Watermark Module */ +#define FIFO_CFG0_RXS BIT(1) /* Rx System Module */ +#define FIFO_CFG0_RXF BIT(2) /* Rx Fabric Module */ +#define FIFO_CFG0_TXS BIT(3) /* Tx System Module */ +#define FIFO_CFG0_TXF BIT(4) /* Tx Fabric Module */ +#define FIFO_CFG0_ALL (FIFO_CFG0_WTM | FIFO_CFG0_RXS | FIFO_CFG0_RXF \ + | FIFO_CFG0_TXS | FIFO_CFG0_TXF) + +#define FIFO_CFG0_ENABLE_SHIFT 8 + +#define FIFO_CFG4_DE BIT(0) /* Drop Event */ +#define FIFO_CFG4_DV BIT(1) /* RX_DV Event */ +#define FIFO_CFG4_FC BIT(2) /* False Carrier */ +#define FIFO_CFG4_CE BIT(3) /* Code Error */ +#define FIFO_CFG4_CR BIT(4) /* CRC error */ +#define FIFO_CFG4_LM BIT(5) /* Length Mismatch */ +#define FIFO_CFG4_LO BIT(6) /* Length out of range */ +#define FIFO_CFG4_OK BIT(7) /* Packet is OK */ +#define FIFO_CFG4_MC BIT(8) /* Multicast Packet */ +#define FIFO_CFG4_BC BIT(9) /* Broadcast Packet */ +#define FIFO_CFG4_DR BIT(10) /* Dribble */ +#define FIFO_CFG4_LE BIT(11) /* Long Event */ +#define FIFO_CFG4_CF BIT(12) /* Control Frame */ +#define FIFO_CFG4_PF BIT(13) /* Pause Frame */ +#define FIFO_CFG4_UO BIT(14) /* Unsupported Opcode */ +#define FIFO_CFG4_VT BIT(15) /* VLAN tag detected */ +#define FIFO_CFG4_FT BIT(16) /* Frame Truncated */ +#define FIFO_CFG4_UC BIT(17) /* Unicast Packet */ + +#define FIFO_CFG5_DE BIT(0) /* Drop Event */ +#define FIFO_CFG5_DV BIT(1) /* RX_DV Event */ +#define FIFO_CFG5_FC BIT(2) /* False Carrier */ +#define FIFO_CFG5_CE BIT(3) /* Code Error */ +#define FIFO_CFG5_LM BIT(4) /* Length Mismatch */ +#define FIFO_CFG5_LO BIT(5) /* Length Out of Range */ +#define FIFO_CFG5_OK BIT(6) /* Packet is OK */ +#define FIFO_CFG5_MC BIT(7) /* Multicast Packet */ +#define FIFO_CFG5_BC BIT(8) /* Broadcast Packet */ +#define FIFO_CFG5_DR BIT(9) /* Dribble */ +#define FIFO_CFG5_CF BIT(10) /* Control Frame */ +#define FIFO_CFG5_PF BIT(11) /* Pause Frame */ +#define FIFO_CFG5_UO BIT(12) /* Unsupported Opcode */ +#define FIFO_CFG5_VT BIT(13) /* VLAN tag detected */ +#define FIFO_CFG5_LE BIT(14) /* Long Event */ +#define FIFO_CFG5_FT BIT(15) /* Frame Truncated */ +#define FIFO_CFG5_16 BIT(16) /* unknown */ +#define FIFO_CFG5_17 BIT(17) /* unknown */ +#define FIFO_CFG5_SF BIT(18) /* Short Frame */ +#define FIFO_CFG5_BM BIT(19) /* Byte Mode */ + +#define AG71XX_INT_TX_PS BIT(0) +#define AG71XX_INT_TX_UR BIT(1) +#define AG71XX_INT_TX_BE BIT(3) +#define AG71XX_INT_RX_PR BIT(4) +#define AG71XX_INT_RX_OF BIT(6) +#define AG71XX_INT_RX_BE BIT(7) + +#define MAC_IFCTL_SPEED BIT(16) + +#define MII_CFG_CLK_DIV_4 0 +#define MII_CFG_CLK_DIV_6 2 +#define MII_CFG_CLK_DIV_8 3 +#define MII_CFG_CLK_DIV_10 4 +#define MII_CFG_CLK_DIV_14 5 +#define MII_CFG_CLK_DIV_20 6 +#define MII_CFG_CLK_DIV_28 7 +#define MII_CFG_CLK_DIV_34 8 +#define MII_CFG_CLK_DIV_42 9 +#define MII_CFG_CLK_DIV_50 10 +#define MII_CFG_CLK_DIV_58 11 +#define MII_CFG_CLK_DIV_66 12 +#define MII_CFG_CLK_DIV_74 13 +#define MII_CFG_CLK_DIV_82 14 +#define MII_CFG_CLK_DIV_98 15 +#define MII_CFG_RESET BIT(31) + +#define MII_CMD_WRITE 0x0 +#define MII_CMD_READ 0x1 +#define MII_ADDR_SHIFT 8 +#define MII_IND_BUSY BIT(0) +#define MII_IND_INVALID BIT(2) + +#define TX_CTRL_TXE BIT(0) /* Tx Enable */ + +#define TX_STATUS_PS BIT(0) /* Packet Sent */ +#define TX_STATUS_UR BIT(1) /* Tx Underrun */ +#define TX_STATUS_BE BIT(3) /* Bus Error */ + +#define RX_CTRL_RXE BIT(0) /* Rx Enable */ + +#define RX_STATUS_PR BIT(0) /* Packet Received */ +#define RX_STATUS_OF BIT(2) /* Rx Overflow */ +#define RX_STATUS_BE BIT(3) /* Bus Error */ + +static inline void ag71xx_check_reg_offset(struct ag71xx *ag, unsigned reg) +{ + switch (reg) { + case AG71XX_REG_MAC_CFG1 ... AG71XX_REG_MAC_MFL: + case AG71XX_REG_MAC_IFCTL ... AG71XX_REG_TX_SM: + case AG71XX_REG_MII_CFG: + break; + + default: + BUG(); + } +} + +static inline void ag71xx_wr(struct ag71xx *ag, unsigned reg, u32 value) +{ + ag71xx_check_reg_offset(ag, reg); + + __raw_writel(value, ag->mac_base + reg); + /* flush write */ + (void) __raw_readl(ag->mac_base + reg); +} + +static inline u32 ag71xx_rr(struct ag71xx *ag, unsigned reg) +{ + ag71xx_check_reg_offset(ag, reg); + + return __raw_readl(ag->mac_base + reg); +} + +static inline void ag71xx_sb(struct ag71xx *ag, unsigned reg, u32 mask) +{ + void __iomem *r; + + ag71xx_check_reg_offset(ag, reg); + + r = ag->mac_base + reg; + __raw_writel(__raw_readl(r) | mask, r); + /* flush write */ + (void)__raw_readl(r); +} + +static inline void ag71xx_cb(struct ag71xx *ag, unsigned reg, u32 mask) +{ + void __iomem *r; + + ag71xx_check_reg_offset(ag, reg); + + r = ag->mac_base + reg; + __raw_writel(__raw_readl(r) & ~mask, r); + /* flush write */ + (void) __raw_readl(r); +} + +static inline void ag71xx_int_enable(struct ag71xx *ag, u32 ints) +{ + ag71xx_sb(ag, AG71XX_REG_INT_ENABLE, ints); +} + +static inline void ag71xx_int_disable(struct ag71xx *ag, u32 ints) +{ + ag71xx_cb(ag, AG71XX_REG_INT_ENABLE, ints); +} + +#ifdef CONFIG_AG71XX_AR8216_SUPPORT +void ag71xx_add_ar8216_header(struct ag71xx *ag, struct sk_buff *skb); +int ag71xx_remove_ar8216_header(struct ag71xx *ag, struct sk_buff *skb, + int pktlen); +static inline int ag71xx_has_ar8216(struct ag71xx *ag) +{ + return ag71xx_get_pdata(ag)->has_ar8216; +} +#else +static inline void ag71xx_add_ar8216_header(struct ag71xx *ag, + struct sk_buff *skb) +{ +} + +static inline int ag71xx_remove_ar8216_header(struct ag71xx *ag, + struct sk_buff *skb, + int pktlen) +{ + return 0; +} +static inline int ag71xx_has_ar8216(struct ag71xx *ag) +{ + return 0; +} +#endif + +#ifdef CONFIG_AG71XX_DEBUG_FS +int ag71xx_debugfs_root_init(void); +void ag71xx_debugfs_root_exit(void); +int ag71xx_debugfs_init(struct ag71xx *ag); +void ag71xx_debugfs_exit(struct ag71xx *ag); +void ag71xx_debugfs_update_int_stats(struct ag71xx *ag, u32 status); +void ag71xx_debugfs_update_napi_stats(struct ag71xx *ag, int rx, int tx); +#else +static inline int ag71xx_debugfs_root_init(void) { return 0; } +static inline void ag71xx_debugfs_root_exit(void) {} +static inline int ag71xx_debugfs_init(struct ag71xx *ag) { return 0; } +static inline void ag71xx_debugfs_exit(struct ag71xx *ag) {} +static inline void ag71xx_debugfs_update_int_stats(struct ag71xx *ag, + u32 status) {} +static inline void ag71xx_debugfs_update_napi_stats(struct ag71xx *ag, + int rx, int tx) {} +#endif /* CONFIG_AG71XX_DEBUG_FS */ + +void ag71xx_ar7240_start(struct ag71xx *ag); +void ag71xx_ar7240_stop(struct ag71xx *ag); +int ag71xx_ar7240_init(struct ag71xx *ag); +void ag71xx_ar7240_cleanup(struct ag71xx *ag); + +int ag71xx_mdio_mii_read(struct ag71xx_mdio *am, int addr, int reg); +void ag71xx_mdio_mii_write(struct ag71xx_mdio *am, int addr, int reg, u16 val); + +u16 ar7240sw_phy_read(struct mii_bus *mii, unsigned phy_addr, + unsigned reg_addr); +int ar7240sw_phy_write(struct mii_bus *mii, unsigned phy_addr, + unsigned reg_addr, u16 reg_val); + +#endif /* _AG71XX_H */ diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c new file mode 100644 index 0000000..49f0775 --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c @@ -0,0 +1,1193 @@ +/* + * Driver for the built-in ethernet switch of the Atheros AR7240 SoC + * Copyright (c) 2010 Gabor Juhos + * Copyright (c) 2010 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "ag71xx.h" + +#define BITM(_count) (BIT(_count) - 1) +#define BITS(_shift, _count) (BITM(_count) << _shift) + +#define AR7240_REG_MASK_CTRL 0x00 +#define AR7240_MASK_CTRL_REVISION_M BITM(8) +#define AR7240_MASK_CTRL_VERSION_M BITM(8) +#define AR7240_MASK_CTRL_VERSION_S 8 +#define AR7240_MASK_CTRL_VERSION_AR7240 0x01 +#define AR7240_MASK_CTRL_VERSION_AR934X 0x02 +#define AR7240_MASK_CTRL_SOFT_RESET BIT(31) + +#define AR7240_REG_MAC_ADDR0 0x20 +#define AR7240_REG_MAC_ADDR1 0x24 + +#define AR7240_REG_FLOOD_MASK 0x2c +#define AR7240_FLOOD_MASK_BROAD_TO_CPU BIT(26) + +#define AR7240_REG_GLOBAL_CTRL 0x30 +#define AR7240_GLOBAL_CTRL_MTU_M BITM(12) + +#define AR7240_REG_VTU 0x0040 +#define AR7240_VTU_OP BITM(3) +#define AR7240_VTU_OP_NOOP 0x0 +#define AR7240_VTU_OP_FLUSH 0x1 +#define AR7240_VTU_OP_LOAD 0x2 +#define AR7240_VTU_OP_PURGE 0x3 +#define AR7240_VTU_OP_REMOVE_PORT 0x4 +#define AR7240_VTU_ACTIVE BIT(3) +#define AR7240_VTU_FULL BIT(4) +#define AR7240_VTU_PORT BITS(8, 4) +#define AR7240_VTU_PORT_S 8 +#define AR7240_VTU_VID BITS(16, 12) +#define AR7240_VTU_VID_S 16 +#define AR7240_VTU_PRIO BITS(28, 3) +#define AR7240_VTU_PRIO_S 28 +#define AR7240_VTU_PRIO_EN BIT(31) + +#define AR7240_REG_VTU_DATA 0x0044 +#define AR7240_VTUDATA_MEMBER BITS(0, 10) +#define AR7240_VTUDATA_VALID BIT(11) + +#define AR7240_REG_ATU 0x50 +#define AR7240_ATU_FLUSH_ALL 0x1 + +#define AR7240_REG_AT_CTRL 0x5c +#define AR7240_AT_CTRL_AGE_TIME BITS(0, 15) +#define AR7240_AT_CTRL_AGE_EN BIT(17) +#define AR7240_AT_CTRL_LEARN_CHANGE BIT(18) +#define AR7240_AT_CTRL_RESERVED BIT(19) +#define AR7240_AT_CTRL_ARP_EN BIT(20) + +#define AR7240_REG_TAG_PRIORITY 0x70 + +#define AR7240_REG_SERVICE_TAG 0x74 +#define AR7240_SERVICE_TAG_M BITM(16) + +#define AR7240_REG_CPU_PORT 0x78 +#define AR7240_MIRROR_PORT_S 4 +#define AR7240_CPU_PORT_EN BIT(8) + +#define AR7240_REG_MIB_FUNCTION0 0x80 +#define AR7240_MIB_TIMER_M BITM(16) +#define AR7240_MIB_AT_HALF_EN BIT(16) +#define AR7240_MIB_BUSY BIT(17) +#define AR7240_MIB_FUNC_S 24 +#define AR7240_MIB_FUNC_M BITM(3) +#define AR7240_MIB_FUNC_NO_OP 0x0 +#define AR7240_MIB_FUNC_FLUSH 0x1 +#define AR7240_MIB_FUNC_CAPTURE 0x3 + +#define AR7240_REG_MDIO_CTRL 0x98 +#define AR7240_MDIO_CTRL_DATA_M BITM(16) +#define AR7240_MDIO_CTRL_REG_ADDR_S 16 +#define AR7240_MDIO_CTRL_PHY_ADDR_S 21 +#define AR7240_MDIO_CTRL_CMD_WRITE 0 +#define AR7240_MDIO_CTRL_CMD_READ BIT(27) +#define AR7240_MDIO_CTRL_MASTER_EN BIT(30) +#define AR7240_MDIO_CTRL_BUSY BIT(31) + +#define AR7240_REG_PORT_BASE(_port) (0x100 + (_port) * 0x100) + +#define AR7240_REG_PORT_STATUS(_port) (AR7240_REG_PORT_BASE((_port)) + 0x00) +#define AR7240_PORT_STATUS_SPEED_S 0 +#define AR7240_PORT_STATUS_SPEED_M BITM(2) +#define AR7240_PORT_STATUS_SPEED_10 0 +#define AR7240_PORT_STATUS_SPEED_100 1 +#define AR7240_PORT_STATUS_SPEED_1000 2 +#define AR7240_PORT_STATUS_TXMAC BIT(2) +#define AR7240_PORT_STATUS_RXMAC BIT(3) +#define AR7240_PORT_STATUS_TXFLOW BIT(4) +#define AR7240_PORT_STATUS_RXFLOW BIT(5) +#define AR7240_PORT_STATUS_DUPLEX BIT(6) +#define AR7240_PORT_STATUS_LINK_UP BIT(8) +#define AR7240_PORT_STATUS_LINK_AUTO BIT(9) +#define AR7240_PORT_STATUS_LINK_PAUSE BIT(10) + +#define AR7240_REG_PORT_CTRL(_port) (AR7240_REG_PORT_BASE((_port)) + 0x04) +#define AR7240_PORT_CTRL_STATE_M BITM(3) +#define AR7240_PORT_CTRL_STATE_DISABLED 0 +#define AR7240_PORT_CTRL_STATE_BLOCK 1 +#define AR7240_PORT_CTRL_STATE_LISTEN 2 +#define AR7240_PORT_CTRL_STATE_LEARN 3 +#define AR7240_PORT_CTRL_STATE_FORWARD 4 +#define AR7240_PORT_CTRL_LEARN_LOCK BIT(7) +#define AR7240_PORT_CTRL_VLAN_MODE_S 8 +#define AR7240_PORT_CTRL_VLAN_MODE_KEEP 0 +#define AR7240_PORT_CTRL_VLAN_MODE_STRIP 1 +#define AR7240_PORT_CTRL_VLAN_MODE_ADD 2 +#define AR7240_PORT_CTRL_VLAN_MODE_DOUBLE_TAG 3 +#define AR7240_PORT_CTRL_IGMP_SNOOP BIT(10) +#define AR7240_PORT_CTRL_HEADER BIT(11) +#define AR7240_PORT_CTRL_MAC_LOOP BIT(12) +#define AR7240_PORT_CTRL_SINGLE_VLAN BIT(13) +#define AR7240_PORT_CTRL_LEARN BIT(14) +#define AR7240_PORT_CTRL_DOUBLE_TAG BIT(15) +#define AR7240_PORT_CTRL_MIRROR_TX BIT(16) +#define AR7240_PORT_CTRL_MIRROR_RX BIT(17) + +#define AR7240_REG_PORT_VLAN(_port) (AR7240_REG_PORT_BASE((_port)) + 0x08) + +#define AR7240_PORT_VLAN_DEFAULT_ID_S 0 +#define AR7240_PORT_VLAN_DEST_PORTS_S 16 +#define AR7240_PORT_VLAN_MODE_S 30 +#define AR7240_PORT_VLAN_MODE_PORT_ONLY 0 +#define AR7240_PORT_VLAN_MODE_PORT_FALLBACK 1 +#define AR7240_PORT_VLAN_MODE_VLAN_ONLY 2 +#define AR7240_PORT_VLAN_MODE_SECURE 3 + + +#define AR7240_REG_STATS_BASE(_port) (0x20000 + (_port) * 0x100) + +#define AR7240_STATS_RXBROAD 0x00 +#define AR7240_STATS_RXPAUSE 0x04 +#define AR7240_STATS_RXMULTI 0x08 +#define AR7240_STATS_RXFCSERR 0x0c +#define AR7240_STATS_RXALIGNERR 0x10 +#define AR7240_STATS_RXRUNT 0x14 +#define AR7240_STATS_RXFRAGMENT 0x18 +#define AR7240_STATS_RX64BYTE 0x1c +#define AR7240_STATS_RX128BYTE 0x20 +#define AR7240_STATS_RX256BYTE 0x24 +#define AR7240_STATS_RX512BYTE 0x28 +#define AR7240_STATS_RX1024BYTE 0x2c +#define AR7240_STATS_RX1518BYTE 0x30 +#define AR7240_STATS_RXMAXBYTE 0x34 +#define AR7240_STATS_RXTOOLONG 0x38 +#define AR7240_STATS_RXGOODBYTE 0x3c +#define AR7240_STATS_RXBADBYTE 0x44 +#define AR7240_STATS_RXOVERFLOW 0x4c +#define AR7240_STATS_FILTERED 0x50 +#define AR7240_STATS_TXBROAD 0x54 +#define AR7240_STATS_TXPAUSE 0x58 +#define AR7240_STATS_TXMULTI 0x5c +#define AR7240_STATS_TXUNDERRUN 0x60 +#define AR7240_STATS_TX64BYTE 0x64 +#define AR7240_STATS_TX128BYTE 0x68 +#define AR7240_STATS_TX256BYTE 0x6c +#define AR7240_STATS_TX512BYTE 0x70 +#define AR7240_STATS_TX1024BYTE 0x74 +#define AR7240_STATS_TX1518BYTE 0x78 +#define AR7240_STATS_TXMAXBYTE 0x7c +#define AR7240_STATS_TXOVERSIZE 0x80 +#define AR7240_STATS_TXBYTE 0x84 +#define AR7240_STATS_TXCOLLISION 0x8c +#define AR7240_STATS_TXABORTCOL 0x90 +#define AR7240_STATS_TXMULTICOL 0x94 +#define AR7240_STATS_TXSINGLECOL 0x98 +#define AR7240_STATS_TXEXCDEFER 0x9c +#define AR7240_STATS_TXDEFER 0xa0 +#define AR7240_STATS_TXLATECOL 0xa4 + +#define AR7240_PORT_CPU 0 +#define AR7240_NUM_PORTS 6 +#define AR7240_NUM_PHYS 5 + +#define AR7240_PHY_ID1 0x004d +#define AR7240_PHY_ID2 0xd041 + +#define AR934X_PHY_ID1 0x004d +#define AR934X_PHY_ID2 0xd042 + +#define AR7240_MAX_VLANS 16 + +#define AR934X_REG_OPER_MODE0 0x04 +#define AR934X_OPER_MODE0_MAC_GMII_EN BIT(6) +#define AR934X_OPER_MODE0_PHY_MII_EN BIT(10) + +#define AR934X_REG_OPER_MODE1 0x08 +#define AR934X_REG_OPER_MODE1_PHY4_MII_EN BIT(28) + +#define AR934X_REG_FLOOD_MASK 0x2c +#define AR934X_FLOOD_MASK_BC_DP(_p) BIT(25 + (_p)) + +#define AR934X_REG_QM_CTRL 0x3c +#define AR934X_QM_CTRL_ARP_EN BIT(15) + +#define AR934X_REG_AT_CTRL 0x5c +#define AR934X_AT_CTRL_AGE_TIME BITS(0, 15) +#define AR934X_AT_CTRL_AGE_EN BIT(17) +#define AR934X_AT_CTRL_LEARN_CHANGE BIT(18) + +#define AR934X_MIB_ENABLE BIT(30) + +#define AR934X_REG_PORT_BASE(_port) (0x100 + (_port) * 0x100) + +#define AR934X_REG_PORT_VLAN1(_port) (AR934X_REG_PORT_BASE((_port)) + 0x08) +#define AR934X_PORT_VLAN1_DEFAULT_SVID_S 0 +#define AR934X_PORT_VLAN1_FORCE_DEFAULT_VID_EN BIT(12) +#define AR934X_PORT_VLAN1_PORT_TLS_MODE BIT(13) +#define AR934X_PORT_VLAN1_PORT_VLAN_PROP_EN BIT(14) +#define AR934X_PORT_VLAN1_PORT_CLONE_EN BIT(15) +#define AR934X_PORT_VLAN1_DEFAULT_CVID_S 16 +#define AR934X_PORT_VLAN1_FORCE_PORT_VLAN_EN BIT(28) +#define AR934X_PORT_VLAN1_ING_PORT_PRI_S 29 + +#define AR934X_REG_PORT_VLAN2(_port) (AR934X_REG_PORT_BASE((_port)) + 0x0c) +#define AR934X_PORT_VLAN2_PORT_VID_MEM_S 16 +#define AR934X_PORT_VLAN2_8021Q_MODE_S 30 +#define AR934X_PORT_VLAN2_8021Q_MODE_PORT_ONLY 0 +#define AR934X_PORT_VLAN2_8021Q_MODE_PORT_FALLBACK 1 +#define AR934X_PORT_VLAN2_8021Q_MODE_VLAN_ONLY 2 +#define AR934X_PORT_VLAN2_8021Q_MODE_SECURE 3 + +#define sw_to_ar7240(_dev) container_of(_dev, struct ar7240sw, swdev) + +struct ar7240sw_port_stat { + unsigned long rx_broadcast; + unsigned long rx_pause; + unsigned long rx_multicast; + unsigned long rx_fcs_error; + unsigned long rx_align_error; + unsigned long rx_runt; + unsigned long rx_fragments; + unsigned long rx_64byte; + unsigned long rx_128byte; + unsigned long rx_256byte; + unsigned long rx_512byte; + unsigned long rx_1024byte; + unsigned long rx_1518byte; + unsigned long rx_maxbyte; + unsigned long rx_toolong; + unsigned long rx_good_byte; + unsigned long rx_bad_byte; + unsigned long rx_overflow; + unsigned long filtered; + + unsigned long tx_broadcast; + unsigned long tx_pause; + unsigned long tx_multicast; + unsigned long tx_underrun; + unsigned long tx_64byte; + unsigned long tx_128byte; + unsigned long tx_256byte; + unsigned long tx_512byte; + unsigned long tx_1024byte; + unsigned long tx_1518byte; + unsigned long tx_maxbyte; + unsigned long tx_oversize; + unsigned long tx_byte; + unsigned long tx_collision; + unsigned long tx_abortcol; + unsigned long tx_multicol; + unsigned long tx_singlecol; + unsigned long tx_excdefer; + unsigned long tx_defer; + unsigned long tx_xlatecol; +}; + +struct ar7240sw { + struct mii_bus *mii_bus; + struct ag71xx_switch_platform_data *swdata; + struct switch_dev swdev; + int num_ports; + u8 ver; + bool vlan; + u16 vlan_id[AR7240_MAX_VLANS]; + u8 vlan_table[AR7240_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[AR7240_NUM_PORTS]; + char buf[80]; + + rwlock_t stats_lock; + struct ar7240sw_port_stat port_stats[AR7240_NUM_PORTS]; +}; + +struct ar7240sw_hw_stat { + char string[ETH_GSTRING_LEN]; + int sizeof_stat; + int reg; +}; + +static DEFINE_MUTEX(reg_mutex); + +static inline int sw_is_ar7240(struct ar7240sw *as) +{ + return as->ver == AR7240_MASK_CTRL_VERSION_AR7240; +} + +static inline int sw_is_ar934x(struct ar7240sw *as) +{ + return as->ver == AR7240_MASK_CTRL_VERSION_AR934X; +} + +static inline u32 ar7240sw_port_mask(struct ar7240sw *as, int port) +{ + return BIT(port); +} + +static inline u32 ar7240sw_port_mask_all(struct ar7240sw *as) +{ + return BIT(as->swdev.ports) - 1; +} + +static inline u32 ar7240sw_port_mask_but(struct ar7240sw *as, int port) +{ + return ar7240sw_port_mask_all(as) & ~BIT(port); +} + +static inline u16 mk_phy_addr(u32 reg) +{ + return 0x17 & ((reg >> 4) | 0x10); +} + +static inline u16 mk_phy_reg(u32 reg) +{ + return (reg << 1) & 0x1e; +} + +static inline u16 mk_high_addr(u32 reg) +{ + return (reg >> 7) & 0x1ff; +} + +static u32 __ar7240sw_reg_read(struct mii_bus *mii, u32 reg) +{ + unsigned long flags; + u16 phy_addr; + u16 phy_reg; + u32 hi, lo; + + reg = (reg & 0xfffffffc) >> 2; + phy_addr = mk_phy_addr(reg); + phy_reg = mk_phy_reg(reg); + + local_irq_save(flags); + ag71xx_mdio_mii_write(mii->priv, 0x1f, 0x10, mk_high_addr(reg)); + lo = (u32) ag71xx_mdio_mii_read(mii->priv, phy_addr, phy_reg); + hi = (u32) ag71xx_mdio_mii_read(mii->priv, phy_addr, phy_reg + 1); + local_irq_restore(flags); + + return (hi << 16) | lo; +} + +static void __ar7240sw_reg_write(struct mii_bus *mii, u32 reg, u32 val) +{ + unsigned long flags; + u16 phy_addr; + u16 phy_reg; + + reg = (reg & 0xfffffffc) >> 2; + phy_addr = mk_phy_addr(reg); + phy_reg = mk_phy_reg(reg); + + local_irq_save(flags); + ag71xx_mdio_mii_write(mii->priv, 0x1f, 0x10, mk_high_addr(reg)); + ag71xx_mdio_mii_write(mii->priv, phy_addr, phy_reg + 1, (val >> 16)); + ag71xx_mdio_mii_write(mii->priv, phy_addr, phy_reg, (val & 0xffff)); + local_irq_restore(flags); +} + +static u32 ar7240sw_reg_read(struct mii_bus *mii, u32 reg_addr) +{ + u32 ret; + + mutex_lock(®_mutex); + ret = __ar7240sw_reg_read(mii, reg_addr); + mutex_unlock(®_mutex); + + return ret; +} + +static void ar7240sw_reg_write(struct mii_bus *mii, u32 reg_addr, u32 reg_val) +{ + mutex_lock(®_mutex); + __ar7240sw_reg_write(mii, reg_addr, reg_val); + mutex_unlock(®_mutex); +} + +static u32 ar7240sw_reg_rmw(struct mii_bus *mii, u32 reg, u32 mask, u32 val) +{ + u32 t; + + mutex_lock(®_mutex); + t = __ar7240sw_reg_read(mii, reg); + t &= ~mask; + t |= val; + __ar7240sw_reg_write(mii, reg, t); + mutex_unlock(®_mutex); + + return t; +} + +static void ar7240sw_reg_set(struct mii_bus *mii, u32 reg, u32 val) +{ + u32 t; + + mutex_lock(®_mutex); + t = __ar7240sw_reg_read(mii, reg); + t |= val; + __ar7240sw_reg_write(mii, reg, t); + mutex_unlock(®_mutex); +} + +static int __ar7240sw_reg_wait(struct mii_bus *mii, u32 reg, u32 mask, u32 val, + unsigned timeout) +{ + int i; + + for (i = 0; i < timeout; i++) { + u32 t; + + t = __ar7240sw_reg_read(mii, reg); + if ((t & mask) == val) + return 0; + + msleep(1); + } + + return -ETIMEDOUT; +} + +static int ar7240sw_reg_wait(struct mii_bus *mii, u32 reg, u32 mask, u32 val, + unsigned timeout) +{ + int ret; + + mutex_lock(®_mutex); + ret = __ar7240sw_reg_wait(mii, reg, mask, val, timeout); + mutex_unlock(®_mutex); + return ret; +} + +u16 ar7240sw_phy_read(struct mii_bus *mii, unsigned phy_addr, + unsigned reg_addr) +{ + u32 t, val = 0xffff; + int err; + + if (phy_addr >= AR7240_NUM_PHYS) + return 0xffff; + + mutex_lock(®_mutex); + t = (reg_addr << AR7240_MDIO_CTRL_REG_ADDR_S) | + (phy_addr << AR7240_MDIO_CTRL_PHY_ADDR_S) | + AR7240_MDIO_CTRL_MASTER_EN | + AR7240_MDIO_CTRL_BUSY | + AR7240_MDIO_CTRL_CMD_READ; + + __ar7240sw_reg_write(mii, AR7240_REG_MDIO_CTRL, t); + err = __ar7240sw_reg_wait(mii, AR7240_REG_MDIO_CTRL, + AR7240_MDIO_CTRL_BUSY, 0, 5); + if (!err) + val = __ar7240sw_reg_read(mii, AR7240_REG_MDIO_CTRL); + mutex_unlock(®_mutex); + + return val & AR7240_MDIO_CTRL_DATA_M; +} + +int ar7240sw_phy_write(struct mii_bus *mii, unsigned phy_addr, + unsigned reg_addr, u16 reg_val) +{ + u32 t; + int ret; + + if (phy_addr >= AR7240_NUM_PHYS) + return -EINVAL; + + mutex_lock(®_mutex); + t = (phy_addr << AR7240_MDIO_CTRL_PHY_ADDR_S) | + (reg_addr << AR7240_MDIO_CTRL_REG_ADDR_S) | + AR7240_MDIO_CTRL_MASTER_EN | + AR7240_MDIO_CTRL_BUSY | + AR7240_MDIO_CTRL_CMD_WRITE | + reg_val; + + __ar7240sw_reg_write(mii, AR7240_REG_MDIO_CTRL, t); + ret = __ar7240sw_reg_wait(mii, AR7240_REG_MDIO_CTRL, + AR7240_MDIO_CTRL_BUSY, 0, 5); + mutex_unlock(®_mutex); + + return ret; +} + +static int ar7240sw_capture_stats(struct ar7240sw *as) +{ + struct mii_bus *mii = as->mii_bus; + int port; + int ret; + + write_lock(&as->stats_lock); + + /* Capture the hardware statistics for all ports */ + ar7240sw_reg_rmw(mii, AR7240_REG_MIB_FUNCTION0, + (AR7240_MIB_FUNC_M << AR7240_MIB_FUNC_S), + (AR7240_MIB_FUNC_CAPTURE << AR7240_MIB_FUNC_S)); + + /* Wait for the capturing to complete. */ + ret = ar7240sw_reg_wait(mii, AR7240_REG_MIB_FUNCTION0, + AR7240_MIB_BUSY, 0, 10); + + if (ret) + goto unlock; + + for (port = 0; port < AR7240_NUM_PORTS; port++) { + unsigned int base; + struct ar7240sw_port_stat *stats; + + base = AR7240_REG_STATS_BASE(port); + stats = &as->port_stats[port]; + +#define READ_STAT(_r) ar7240sw_reg_read(mii, base + AR7240_STATS_ ## _r) + + stats->rx_good_byte += READ_STAT(RXGOODBYTE); + stats->tx_byte += READ_STAT(TXBYTE); + +#undef READ_STAT + } + + ret = 0; + +unlock: + write_unlock(&as->stats_lock); + return ret; +} + +static void ar7240sw_disable_port(struct ar7240sw *as, unsigned port) +{ + ar7240sw_reg_write(as->mii_bus, AR7240_REG_PORT_CTRL(port), + AR7240_PORT_CTRL_STATE_DISABLED); +} + +static void ar7240sw_setup(struct ar7240sw *as) +{ + struct mii_bus *mii = as->mii_bus; + + /* Enable CPU port, and disable mirror port */ + ar7240sw_reg_write(mii, AR7240_REG_CPU_PORT, + AR7240_CPU_PORT_EN | + (15 << AR7240_MIRROR_PORT_S)); + + /* Setup TAG priority mapping */ + ar7240sw_reg_write(mii, AR7240_REG_TAG_PRIORITY, 0xfa50); + + if (sw_is_ar934x(as)) { + /* Enable aging, MAC replacing */ + ar7240sw_reg_write(mii, AR934X_REG_AT_CTRL, + 0x2b /* 5 min age time */ | + AR934X_AT_CTRL_AGE_EN | + AR934X_AT_CTRL_LEARN_CHANGE); + /* Enable ARP frame acknowledge */ + ar7240sw_reg_set(mii, AR934X_REG_QM_CTRL, + AR934X_QM_CTRL_ARP_EN); + /* Enable Broadcast frames transmitted to the CPU */ + ar7240sw_reg_set(mii, AR934X_REG_FLOOD_MASK, + AR934X_FLOOD_MASK_BC_DP(0)); + + /* Enable MIB counters */ + ar7240sw_reg_set(mii, AR7240_REG_MIB_FUNCTION0, + AR934X_MIB_ENABLE); + + } else { + /* Enable ARP frame acknowledge, aging, MAC replacing */ + ar7240sw_reg_write(mii, AR7240_REG_AT_CTRL, + AR7240_AT_CTRL_RESERVED | + 0x2b /* 5 min age time */ | + AR7240_AT_CTRL_AGE_EN | + AR7240_AT_CTRL_ARP_EN | + AR7240_AT_CTRL_LEARN_CHANGE); + /* Enable Broadcast frames transmitted to the CPU */ + ar7240sw_reg_set(mii, AR7240_REG_FLOOD_MASK, + AR7240_FLOOD_MASK_BROAD_TO_CPU); + } + + /* setup MTU */ + ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL, AR7240_GLOBAL_CTRL_MTU_M, + 1536); + + /* setup Service TAG */ + ar7240sw_reg_rmw(mii, AR7240_REG_SERVICE_TAG, AR7240_SERVICE_TAG_M, 0); +} + +static int ar7240sw_reset(struct ar7240sw *as) +{ + struct mii_bus *mii = as->mii_bus; + int ret; + int i; + + /* Set all ports to disabled state. */ + for (i = 0; i < AR7240_NUM_PORTS; i++) + ar7240sw_disable_port(as, i); + + /* Wait for transmit queues to drain. */ + msleep(2); + + /* Reset the switch. */ + ar7240sw_reg_write(mii, AR7240_REG_MASK_CTRL, + AR7240_MASK_CTRL_SOFT_RESET); + + ret = ar7240sw_reg_wait(mii, AR7240_REG_MASK_CTRL, + AR7240_MASK_CTRL_SOFT_RESET, 0, 1000); + + /* setup PHYs */ + for (i = 0; i < AR7240_NUM_PHYS; i++) { + ar7240sw_phy_write(mii, i, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | + ADVERTISE_PAUSE_ASYM); + ar7240sw_phy_write(mii, i, MII_BMCR, + BMCR_RESET | BMCR_ANENABLE); + } + msleep(1000); + + ar7240sw_setup(as); + return ret; +} + +static void ar7240sw_setup_port(struct ar7240sw *as, unsigned port, u8 portmask) +{ + struct mii_bus *mii = as->mii_bus; + u32 ctrl; + u32 vid, mode; + + ctrl = AR7240_PORT_CTRL_STATE_FORWARD | AR7240_PORT_CTRL_LEARN | + AR7240_PORT_CTRL_SINGLE_VLAN; + + if (port == AR7240_PORT_CPU) { + ar7240sw_reg_write(mii, AR7240_REG_PORT_STATUS(port), + AR7240_PORT_STATUS_SPEED_1000 | + AR7240_PORT_STATUS_TXFLOW | + AR7240_PORT_STATUS_RXFLOW | + AR7240_PORT_STATUS_TXMAC | + AR7240_PORT_STATUS_RXMAC | + AR7240_PORT_STATUS_DUPLEX); + } else { + ar7240sw_reg_write(mii, AR7240_REG_PORT_STATUS(port), + AR7240_PORT_STATUS_LINK_AUTO); + } + + /* Set the default VID for this port */ + if (as->vlan) { + vid = as->vlan_id[as->pvid[port]]; + mode = AR7240_PORT_VLAN_MODE_SECURE; + } else { + vid = port; + mode = AR7240_PORT_VLAN_MODE_PORT_ONLY; + } + + if (as->vlan) { + if (as->vlan_tagged & BIT(port)) + ctrl |= AR7240_PORT_CTRL_VLAN_MODE_ADD << + AR7240_PORT_CTRL_VLAN_MODE_S; + else + ctrl |= AR7240_PORT_CTRL_VLAN_MODE_STRIP << + AR7240_PORT_CTRL_VLAN_MODE_S; + } else { + ctrl |= AR7240_PORT_CTRL_VLAN_MODE_KEEP << + AR7240_PORT_CTRL_VLAN_MODE_S; + } + + if (!portmask) { + if (port == AR7240_PORT_CPU) + portmask = ar7240sw_port_mask_but(as, AR7240_PORT_CPU); + else + portmask = ar7240sw_port_mask(as, AR7240_PORT_CPU); + } + + /* allow the port to talk to all other ports, but exclude its + * own ID to prevent frames from being reflected back to the + * port that they came from */ + portmask &= ar7240sw_port_mask_but(as, port); + + ar7240sw_reg_write(mii, AR7240_REG_PORT_CTRL(port), ctrl); + if (sw_is_ar934x(as)) { + u32 vlan1, vlan2; + + vlan1 = (vid << AR934X_PORT_VLAN1_DEFAULT_CVID_S); + vlan2 = (portmask << AR934X_PORT_VLAN2_PORT_VID_MEM_S) | + (mode << AR934X_PORT_VLAN2_8021Q_MODE_S); + ar7240sw_reg_write(mii, AR934X_REG_PORT_VLAN1(port), vlan1); + ar7240sw_reg_write(mii, AR934X_REG_PORT_VLAN2(port), vlan2); + } else { + u32 vlan; + + vlan = vid | (mode << AR7240_PORT_VLAN_MODE_S) | + (portmask << AR7240_PORT_VLAN_DEST_PORTS_S); + + ar7240sw_reg_write(mii, AR7240_REG_PORT_VLAN(port), vlan); + } +} + +static int ar7240_set_addr(struct ar7240sw *as, u8 *addr) +{ + struct mii_bus *mii = as->mii_bus; + u32 t; + + t = (addr[4] << 8) | addr[5]; + ar7240sw_reg_write(mii, AR7240_REG_MAC_ADDR0, t); + + t = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | addr[3]; + ar7240sw_reg_write(mii, AR7240_REG_MAC_ADDR1, t); + + return 0; +} + +static int +ar7240_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + as->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +ar7240_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + val->value.i = as->vlan_id[val->port_vlan]; + return 0; +} + +static int +ar7240_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + + /* make sure no invalid PVIDs get set */ + + if (vlan >= dev->vlans) + return -EINVAL; + + as->pvid[port] = vlan; + return 0; +} + +static int +ar7240_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + *vlan = as->pvid[port]; + return 0; +} + +static int +ar7240_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + u8 ports = as->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < as->swdev.ports; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (as->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar7240_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + u8 *vt = &as->vlan_table[val->port_vlan]; + int i, j; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + as->vlan_tagged |= (1 << p->id); + else { + as->vlan_tagged &= ~(1 << p->id); + as->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < AR7240_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + as->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static int +ar7240_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + as->vlan = !!val->value.i; + return 0; +} + +static int +ar7240_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + val->value.i = as->vlan; + return 0; +} + +static void +ar7240_vtu_op(struct ar7240sw *as, u32 op, u32 val) +{ + struct mii_bus *mii = as->mii_bus; + + if (ar7240sw_reg_wait(mii, AR7240_REG_VTU, AR7240_VTU_ACTIVE, 0, 5)) + return; + + if ((op & AR7240_VTU_OP) == AR7240_VTU_OP_LOAD) { + val &= AR7240_VTUDATA_MEMBER; + val |= AR7240_VTUDATA_VALID; + ar7240sw_reg_write(mii, AR7240_REG_VTU_DATA, val); + } + op |= AR7240_VTU_ACTIVE; + ar7240sw_reg_write(mii, AR7240_REG_VTU, op); +} + +static int +ar7240_hw_apply(struct switch_dev *dev) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + u8 portmask[AR7240_NUM_PORTS]; + int i, j; + + /* flush all vlan translation unit entries */ + ar7240_vtu_op(as, AR7240_VTU_OP_FLUSH, 0); + + memset(portmask, 0, sizeof(portmask)); + if (as->vlan) { + /* calculate the port destination masks and load vlans + * into the vlan translation unit */ + for (j = 0; j < AR7240_MAX_VLANS; j++) { + u8 vp = as->vlan_table[j]; + + if (!vp) + continue; + + for (i = 0; i < as->swdev.ports; i++) { + u8 mask = (1 << i); + if (vp & mask) + portmask[i] |= vp & ~mask; + } + + ar7240_vtu_op(as, + AR7240_VTU_OP_LOAD | + (as->vlan_id[j] << AR7240_VTU_VID_S), + as->vlan_table[j]); + } + } else { + /* vlan disabled: + * isolate all ports, but connect them to the cpu port */ + for (i = 0; i < as->swdev.ports; i++) { + if (i == AR7240_PORT_CPU) + continue; + + portmask[i] = 1 << AR7240_PORT_CPU; + portmask[AR7240_PORT_CPU] |= (1 << i); + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < as->swdev.ports; i++) + ar7240sw_setup_port(as, i, portmask[i]); + + return 0; +} + +static int +ar7240_reset_switch(struct switch_dev *dev) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + ar7240sw_reset(as); + return 0; +} + +static int +ar7240_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + struct mii_bus *mii = as->mii_bus; + u32 status; + + if (port > AR7240_NUM_PORTS) + return -EINVAL; + + status = ar7240sw_reg_read(mii, AR7240_REG_PORT_STATUS(port)); + link->aneg = !!(status & AR7240_PORT_STATUS_LINK_AUTO); + if (link->aneg) { + link->link = !!(status & AR7240_PORT_STATUS_LINK_UP); + if (!link->link) + return 0; + } else { + link->link = true; + } + + link->duplex = !!(status & AR7240_PORT_STATUS_DUPLEX); + link->tx_flow = !!(status & AR7240_PORT_STATUS_TXFLOW); + link->rx_flow = !!(status & AR7240_PORT_STATUS_RXFLOW); + switch (status & AR7240_PORT_STATUS_SPEED_M) { + case AR7240_PORT_STATUS_SPEED_10: + link->speed = SWITCH_PORT_SPEED_10; + break; + case AR7240_PORT_STATUS_SPEED_100: + link->speed = SWITCH_PORT_SPEED_100; + break; + case AR7240_PORT_STATUS_SPEED_1000: + link->speed = SWITCH_PORT_SPEED_1000; + break; + } + + return 0; +} + +static int +ar7240_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + struct ar7240sw *as = sw_to_ar7240(dev); + + if (port > AR7240_NUM_PORTS) + return -EINVAL; + + ar7240sw_capture_stats(as); + + read_lock(&as->stats_lock); + stats->rx_bytes = as->port_stats[port].rx_good_byte; + stats->tx_bytes = as->port_stats[port].tx_byte; + read_unlock(&as->stats_lock); + + return 0; +} + +static struct switch_attr ar7240_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar7240_set_vlan, + .get = ar7240_get_vlan, + .max = 1 + }, +}; + +static struct switch_attr ar7240_port[] = { +}; + +static struct switch_attr ar7240_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID", + .set = ar7240_set_vid, + .get = ar7240_get_vid, + .max = 4094, + }, +}; + +static const struct switch_dev_ops ar7240_ops = { + .attr_global = { + .attr = ar7240_globals, + .n_attr = ARRAY_SIZE(ar7240_globals), + }, + .attr_port = { + .attr = ar7240_port, + .n_attr = ARRAY_SIZE(ar7240_port), + }, + .attr_vlan = { + .attr = ar7240_vlan, + .n_attr = ARRAY_SIZE(ar7240_vlan), + }, + .get_port_pvid = ar7240_get_pvid, + .set_port_pvid = ar7240_set_pvid, + .get_vlan_ports = ar7240_get_ports, + .set_vlan_ports = ar7240_set_ports, + .apply_config = ar7240_hw_apply, + .reset_switch = ar7240_reset_switch, + .get_port_link = ar7240_get_port_link, + .get_port_stats = ar7240_get_port_stats, +}; + +static struct ar7240sw *ar7240_probe(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + struct mii_bus *mii = ag->mii_bus; + struct ar7240sw *as; + struct switch_dev *swdev; + u32 ctrl; + u16 phy_id1; + u16 phy_id2; + int i; + + phy_id1 = ar7240sw_phy_read(mii, 0, MII_PHYSID1); + phy_id2 = ar7240sw_phy_read(mii, 0, MII_PHYSID2); + if ((phy_id1 != AR7240_PHY_ID1 || phy_id2 != AR7240_PHY_ID2) && + (phy_id1 != AR934X_PHY_ID1 || phy_id2 != AR934X_PHY_ID2)) { + pr_err("%s: unknown phy id '%04x:%04x'\n", + ag->dev->name, phy_id1, phy_id2); + return NULL; + } + + as = kzalloc(sizeof(*as), GFP_KERNEL); + if (!as) + return NULL; + + as->mii_bus = mii; + as->swdata = pdata->switch_data; + + swdev = &as->swdev; + + ctrl = ar7240sw_reg_read(mii, AR7240_REG_MASK_CTRL); + as->ver = (ctrl >> AR7240_MASK_CTRL_VERSION_S) & + AR7240_MASK_CTRL_VERSION_M; + + if (sw_is_ar7240(as)) { + swdev->name = "AR7240/AR9330 built-in switch"; + swdev->ports = AR7240_NUM_PORTS - 1; + } else if (sw_is_ar934x(as)) { + swdev->name = "AR934X built-in switch"; + + if (pdata->phy_if_mode == PHY_INTERFACE_MODE_GMII) { + ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE0, + AR934X_OPER_MODE0_MAC_GMII_EN); + } else if (pdata->phy_if_mode == PHY_INTERFACE_MODE_MII) { + ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE0, + AR934X_OPER_MODE0_PHY_MII_EN); + } else { + pr_err("%s: invalid PHY interface mode\n", + ag->dev->name); + goto err_free; + } + + if (as->swdata->phy4_mii_en) { + ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE1, + AR934X_REG_OPER_MODE1_PHY4_MII_EN); + swdev->ports = AR7240_NUM_PORTS - 1; + } else { + swdev->ports = AR7240_NUM_PORTS; + } + } else { + pr_err("%s: unsupported chip, ctrl=%08x\n", + ag->dev->name, ctrl); + goto err_free; + } + + swdev->cpu_port = AR7240_PORT_CPU; + swdev->vlans = AR7240_MAX_VLANS; + swdev->ops = &ar7240_ops; + + if (register_switch(&as->swdev, ag->dev) < 0) + goto err_free; + + pr_info("%s: Found an %s\n", ag->dev->name, swdev->name); + + /* initialize defaults */ + for (i = 0; i < AR7240_MAX_VLANS; i++) + as->vlan_id[i] = i; + + as->vlan_table[0] = ar7240sw_port_mask_all(as); + + return as; + +err_free: + kfree(as); + return NULL; +} + +static void link_function(struct work_struct *work) { + struct ag71xx *ag = container_of(work, struct ag71xx, link_work.work); + struct ar7240sw *as = ag->phy_priv; + unsigned long flags; + u8 mask; + int i; + int status = 0; + + mask = ~as->swdata->phy_poll_mask; + for (i = 0; i < AR7240_NUM_PHYS; i++) { + int link; + + if (!(mask & BIT(i))) + continue; + + link = ar7240sw_phy_read(ag->mii_bus, i, MII_BMSR); + if (link & BMSR_LSTATUS) { + status = 1; + break; + } + } + + spin_lock_irqsave(&ag->lock, flags); + if (status != ag->link) { + ag->link = status; + ag71xx_link_adjust(ag); + } + spin_unlock_irqrestore(&ag->lock, flags); + + schedule_delayed_work(&ag->link_work, HZ / 2); +} + +void ag71xx_ar7240_start(struct ag71xx *ag) +{ + struct ar7240sw *as = ag->phy_priv; + + ar7240sw_reset(as); + + ag->speed = SPEED_1000; + ag->duplex = 1; + + ar7240_set_addr(as, ag->dev->dev_addr); + ar7240_hw_apply(&as->swdev); + + schedule_delayed_work(&ag->link_work, HZ / 10); +} + +void ag71xx_ar7240_stop(struct ag71xx *ag) +{ + cancel_delayed_work_sync(&ag->link_work); +} + +int __devinit ag71xx_ar7240_init(struct ag71xx *ag) +{ + struct ar7240sw *as; + + as = ar7240_probe(ag); + if (!as) + return -ENODEV; + + ag->phy_priv = as; + ar7240sw_reset(as); + + rwlock_init(&as->stats_lock); + INIT_DELAYED_WORK(&ag->link_work, link_function); + + return 0; +} + +void ag71xx_ar7240_cleanup(struct ag71xx *ag) +{ + struct ar7240sw *as = ag->phy_priv; + + if (!as) + return; + + unregister_switch(&as->swdev); + kfree(as); + ag->phy_priv = NULL; +} diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar8216.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar8216.c new file mode 100644 index 0000000..7ec43b7 --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar8216.c @@ -0,0 +1,44 @@ +/* + * Atheros AR71xx built-in ethernet mac driver + * Special support for the Atheros ar8216 switch chip + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * Based on Atheros' AG7100 driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "ag71xx.h" + +#define AR8216_PACKET_TYPE_MASK 0xf +#define AR8216_PACKET_TYPE_NORMAL 0 + +#define AR8216_HEADER_LEN 2 + +void ag71xx_add_ar8216_header(struct ag71xx *ag, struct sk_buff *skb) +{ + skb_push(skb, AR8216_HEADER_LEN); + skb->data[0] = 0x10; + skb->data[1] = 0x80; +} + +int ag71xx_remove_ar8216_header(struct ag71xx *ag, struct sk_buff *skb, + int pktlen) +{ + u8 type; + + type = skb->data[1] & AR8216_PACKET_TYPE_MASK; + switch (type) { + case AR8216_PACKET_TYPE_NORMAL: + break; + + default: + return -EINVAL; + } + + skb_pull(skb, AR8216_HEADER_LEN); + return 0; +} diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_debugfs.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_debugfs.c new file mode 100644 index 0000000..65f2be1 --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_debugfs.c @@ -0,0 +1,280 @@ +/* + * Atheros AR71xx built-in ethernet mac driver + * + * Copyright (C) 2008-2010 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * Based on Atheros' AG7100 driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include + +#include "ag71xx.h" + +static struct dentry *ag71xx_debugfs_root; + +static int ag71xx_debugfs_generic_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +void ag71xx_debugfs_update_int_stats(struct ag71xx *ag, u32 status) +{ + if (status) + ag->debug.int_stats.total++; + if (status & AG71XX_INT_TX_PS) + ag->debug.int_stats.tx_ps++; + if (status & AG71XX_INT_TX_UR) + ag->debug.int_stats.tx_ur++; + if (status & AG71XX_INT_TX_BE) + ag->debug.int_stats.tx_be++; + if (status & AG71XX_INT_RX_PR) + ag->debug.int_stats.rx_pr++; + if (status & AG71XX_INT_RX_OF) + ag->debug.int_stats.rx_of++; + if (status & AG71XX_INT_RX_BE) + ag->debug.int_stats.rx_be++; +} + +static ssize_t read_file_int_stats(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ +#define PR_INT_STAT(_label, _field) \ + len += snprintf(buf + len, sizeof(buf) - len, \ + "%20s: %10lu\n", _label, ag->debug.int_stats._field); + + struct ag71xx *ag = file->private_data; + char buf[256]; + unsigned int len = 0; + + PR_INT_STAT("TX Packet Sent", tx_ps); + PR_INT_STAT("TX Underrun", tx_ur); + PR_INT_STAT("TX Bus Error", tx_be); + PR_INT_STAT("RX Packet Received", rx_pr); + PR_INT_STAT("RX Overflow", rx_of); + PR_INT_STAT("RX Bus Error", rx_be); + len += snprintf(buf + len, sizeof(buf) - len, "\n"); + PR_INT_STAT("Total", total); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +#undef PR_INT_STAT +} + +static const struct file_operations ag71xx_fops_int_stats = { + .open = ag71xx_debugfs_generic_open, + .read = read_file_int_stats, + .owner = THIS_MODULE +}; + +void ag71xx_debugfs_update_napi_stats(struct ag71xx *ag, int rx, int tx) +{ + struct ag71xx_napi_stats *stats = &ag->debug.napi_stats; + + if (rx) { + stats->rx_count++; + stats->rx_packets += rx; + if (rx <= AG71XX_NAPI_WEIGHT) + stats->rx[rx]++; + if (rx > stats->rx_packets_max) + stats->rx_packets_max = rx; + } + + if (tx) { + stats->tx_count++; + stats->tx_packets += tx; + if (tx <= AG71XX_NAPI_WEIGHT) + stats->tx[tx]++; + if (tx > stats->tx_packets_max) + stats->tx_packets_max = tx; + } +} + +static ssize_t read_file_napi_stats(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ag71xx *ag = file->private_data; + struct ag71xx_napi_stats *stats = &ag->debug.napi_stats; + char *buf; + unsigned int buflen; + unsigned int len = 0; + unsigned long rx_avg = 0; + unsigned long tx_avg = 0; + int ret; + int i; + + buflen = 2048; + buf = kmalloc(buflen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (stats->rx_count) + rx_avg = stats->rx_packets / stats->rx_count; + + if (stats->tx_count) + tx_avg = stats->tx_packets / stats->tx_count; + + len += snprintf(buf + len, buflen - len, "%3s %10s %10s\n", + "len", "rx", "tx"); + + for (i = 1; i <= AG71XX_NAPI_WEIGHT; i++) + len += snprintf(buf + len, buflen - len, + "%3d: %10lu %10lu\n", + i, stats->rx[i], stats->tx[i]); + + len += snprintf(buf + len, buflen - len, "\n"); + + len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", + "sum", stats->rx_count, stats->tx_count); + len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", + "avg", rx_avg, tx_avg); + len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", + "max", stats->rx_packets_max, stats->tx_packets_max); + len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", + "pkt", stats->rx_packets, stats->tx_packets); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return ret; +} + +static const struct file_operations ag71xx_fops_napi_stats = { + .open = ag71xx_debugfs_generic_open, + .read = read_file_napi_stats, + .owner = THIS_MODULE +}; + +#define DESC_PRINT_LEN 64 + +static ssize_t read_file_ring(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos, + struct ag71xx *ag, + struct ag71xx_ring *ring, + unsigned desc_reg) +{ + char *buf; + unsigned int buflen; + unsigned int len = 0; + unsigned long flags; + ssize_t ret; + int curr; + int dirty; + u32 desc_hw; + int i; + + buflen = (ring->size * DESC_PRINT_LEN); + buf = kmalloc(buflen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + len += snprintf(buf + len, buflen - len, + "Idx ... %-8s %-8s %-8s %-8s . %-10s\n", + "desc", "next", "data", "ctrl", "timestamp"); + + spin_lock_irqsave(&ag->lock, flags); + + curr = (ring->curr % ring->size); + dirty = (ring->dirty % ring->size); + desc_hw = ag71xx_rr(ag, desc_reg); + for (i = 0; i < ring->size; i++) { + struct ag71xx_buf *ab = &ring->buf[i]; + u32 desc_dma = ((u32) ring->descs_dma) + i * ring->desc_size; + + len += snprintf(buf + len, buflen - len, + "%3d %c%c%c %08x %08x %08x %08x %c %10lu\n", + i, + (i == curr) ? 'C' : ' ', + (i == dirty) ? 'D' : ' ', + (desc_hw == desc_dma) ? 'H' : ' ', + desc_dma, + ab->desc->next, + ab->desc->data, + ab->desc->ctrl, + (ab->desc->ctrl & DESC_EMPTY) ? 'E' : '*', + ab->timestamp); + } + + spin_unlock_irqrestore(&ag->lock, flags); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return ret; +} + +static ssize_t read_file_tx_ring(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ag71xx *ag = file->private_data; + + return read_file_ring(file, user_buf, count, ppos, ag, &ag->tx_ring, + AG71XX_REG_TX_DESC); +} + +static const struct file_operations ag71xx_fops_tx_ring = { + .open = ag71xx_debugfs_generic_open, + .read = read_file_tx_ring, + .owner = THIS_MODULE +}; + +static ssize_t read_file_rx_ring(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ag71xx *ag = file->private_data; + + return read_file_ring(file, user_buf, count, ppos, ag, &ag->rx_ring, + AG71XX_REG_RX_DESC); +} + +static const struct file_operations ag71xx_fops_rx_ring = { + .open = ag71xx_debugfs_generic_open, + .read = read_file_rx_ring, + .owner = THIS_MODULE +}; + +void ag71xx_debugfs_exit(struct ag71xx *ag) +{ + debugfs_remove_recursive(ag->debug.debugfs_dir); +} + +int ag71xx_debugfs_init(struct ag71xx *ag) +{ + ag->debug.debugfs_dir = debugfs_create_dir(ag->dev->name, + ag71xx_debugfs_root); + if (!ag->debug.debugfs_dir) + return -ENOMEM; + + debugfs_create_file("int_stats", S_IRUGO, ag->debug.debugfs_dir, + ag, &ag71xx_fops_int_stats); + debugfs_create_file("napi_stats", S_IRUGO, ag->debug.debugfs_dir, + ag, &ag71xx_fops_napi_stats); + debugfs_create_file("tx_ring", S_IRUGO, ag->debug.debugfs_dir, + ag, &ag71xx_fops_tx_ring); + debugfs_create_file("rx_ring", S_IRUGO, ag->debug.debugfs_dir, + ag, &ag71xx_fops_rx_ring); + + return 0; +} + +int ag71xx_debugfs_root_init(void) +{ + if (ag71xx_debugfs_root) + return -EBUSY; + + ag71xx_debugfs_root = debugfs_create_dir(KBUILD_MODNAME, NULL); + if (!ag71xx_debugfs_root) + return -ENOENT; + + return 0; +} + +void ag71xx_debugfs_root_exit(void) +{ + debugfs_remove(ag71xx_debugfs_root); + ag71xx_debugfs_root = NULL; +} diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_ethtool.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ethtool.c new file mode 100644 index 0000000..498fbed --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_ethtool.c @@ -0,0 +1,124 @@ +/* + * Atheros AR71xx built-in ethernet mac driver + * + * Copyright (C) 2008-2010 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * Based on Atheros' AG7100 driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "ag71xx.h" + +static int ag71xx_ethtool_get_settings(struct net_device *dev, + struct ethtool_cmd *cmd) +{ + struct ag71xx *ag = netdev_priv(dev); + struct phy_device *phydev = ag->phy_dev; + + if (!phydev) + return -ENODEV; + + return phy_ethtool_gset(phydev, cmd); +} + +static int ag71xx_ethtool_set_settings(struct net_device *dev, + struct ethtool_cmd *cmd) +{ + struct ag71xx *ag = netdev_priv(dev); + struct phy_device *phydev = ag->phy_dev; + + if (!phydev) + return -ENODEV; + + return phy_ethtool_sset(phydev, cmd); +} + +static void ag71xx_ethtool_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + struct ag71xx *ag = netdev_priv(dev); + + strcpy(info->driver, ag->pdev->dev.driver->name); + strcpy(info->version, AG71XX_DRV_VERSION); + strcpy(info->bus_info, dev_name(&ag->pdev->dev)); +} + +static u32 ag71xx_ethtool_get_msglevel(struct net_device *dev) +{ + struct ag71xx *ag = netdev_priv(dev); + + return ag->msg_enable; +} + +static void ag71xx_ethtool_set_msglevel(struct net_device *dev, u32 msg_level) +{ + struct ag71xx *ag = netdev_priv(dev); + + ag->msg_enable = msg_level; +} + +static void ag71xx_ethtool_get_ringparam(struct net_device *dev, + struct ethtool_ringparam *er) +{ + struct ag71xx *ag = netdev_priv(dev); + + er->tx_max_pending = AG71XX_TX_RING_SIZE_MAX; + er->rx_max_pending = AG71XX_RX_RING_SIZE_MAX; + er->rx_mini_max_pending = 0; + er->rx_jumbo_max_pending = 0; + + er->tx_pending = ag->tx_ring.size; + er->rx_pending = ag->rx_ring.size; + er->rx_mini_pending = 0; + er->rx_jumbo_pending = 0; +} + +static int ag71xx_ethtool_set_ringparam(struct net_device *dev, + struct ethtool_ringparam *er) +{ + struct ag71xx *ag = netdev_priv(dev); + unsigned tx_size; + unsigned rx_size; + int err; + + if (er->rx_mini_pending != 0|| + er->rx_jumbo_pending != 0 || + er->rx_pending == 0 || + er->tx_pending == 0) + return -EINVAL; + + tx_size = er->tx_pending < AG71XX_TX_RING_SIZE_MAX ? + er->tx_pending : AG71XX_TX_RING_SIZE_MAX; + + rx_size = er->rx_pending < AG71XX_RX_RING_SIZE_MAX ? + er->rx_pending : AG71XX_RX_RING_SIZE_MAX; + + if (netif_running(dev)) { + err = dev->netdev_ops->ndo_stop(dev); + if (err) + return err; + } + + ag->tx_ring.size = tx_size; + ag->rx_ring.size = rx_size; + + if (netif_running(dev)) + err = dev->netdev_ops->ndo_open(dev); + + return err; +} + +struct ethtool_ops ag71xx_ethtool_ops = { + .set_settings = ag71xx_ethtool_set_settings, + .get_settings = ag71xx_ethtool_get_settings, + .get_drvinfo = ag71xx_ethtool_get_drvinfo, + .get_msglevel = ag71xx_ethtool_get_msglevel, + .set_msglevel = ag71xx_ethtool_set_msglevel, + .get_ringparam = ag71xx_ethtool_get_ringparam, + .set_ringparam = ag71xx_ethtool_set_ringparam, + .get_link = ethtool_op_get_link, +}; diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c new file mode 100644 index 0000000..fb99d27 --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c @@ -0,0 +1,1258 @@ +/* + * Atheros AR71xx built-in ethernet mac driver + * + * Copyright (C) 2008-2010 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * Based on Atheros' AG7100 driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "ag71xx.h" + +#define AG71XX_DEFAULT_MSG_ENABLE \ + (NETIF_MSG_DRV \ + | NETIF_MSG_PROBE \ + | NETIF_MSG_LINK \ + | NETIF_MSG_TIMER \ + | NETIF_MSG_IFDOWN \ + | NETIF_MSG_IFUP \ + | NETIF_MSG_RX_ERR \ + | NETIF_MSG_TX_ERR) + +static int ag71xx_msg_level = -1; + +module_param_named(msg_level, ag71xx_msg_level, int, 0); +MODULE_PARM_DESC(msg_level, "Message level (-1=defaults,0=none,...,16=all)"); + +static void ag71xx_dump_dma_regs(struct ag71xx *ag) +{ + DBG("%s: dma_tx_ctrl=%08x, dma_tx_desc=%08x, dma_tx_status=%08x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_TX_CTRL), + ag71xx_rr(ag, AG71XX_REG_TX_DESC), + ag71xx_rr(ag, AG71XX_REG_TX_STATUS)); + + DBG("%s: dma_rx_ctrl=%08x, dma_rx_desc=%08x, dma_rx_status=%08x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_RX_CTRL), + ag71xx_rr(ag, AG71XX_REG_RX_DESC), + ag71xx_rr(ag, AG71XX_REG_RX_STATUS)); +} + +static void ag71xx_dump_regs(struct ag71xx *ag) +{ + DBG("%s: mac_cfg1=%08x, mac_cfg2=%08x, ipg=%08x, hdx=%08x, mfl=%08x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_MAC_CFG1), + ag71xx_rr(ag, AG71XX_REG_MAC_CFG2), + ag71xx_rr(ag, AG71XX_REG_MAC_IPG), + ag71xx_rr(ag, AG71XX_REG_MAC_HDX), + ag71xx_rr(ag, AG71XX_REG_MAC_MFL)); + DBG("%s: mac_ifctl=%08x, mac_addr1=%08x, mac_addr2=%08x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_MAC_IFCTL), + ag71xx_rr(ag, AG71XX_REG_MAC_ADDR1), + ag71xx_rr(ag, AG71XX_REG_MAC_ADDR2)); + DBG("%s: fifo_cfg0=%08x, fifo_cfg1=%08x, fifo_cfg2=%08x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG0), + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG1), + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG2)); + DBG("%s: fifo_cfg3=%08x, fifo_cfg4=%08x, fifo_cfg5=%08x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG3), + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG4), + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG5)); +} + +static inline void ag71xx_dump_intr(struct ag71xx *ag, char *label, u32 intr) +{ + DBG("%s: %s intr=%08x %s%s%s%s%s%s\n", + ag->dev->name, label, intr, + (intr & AG71XX_INT_TX_PS) ? "TXPS " : "", + (intr & AG71XX_INT_TX_UR) ? "TXUR " : "", + (intr & AG71XX_INT_TX_BE) ? "TXBE " : "", + (intr & AG71XX_INT_RX_PR) ? "RXPR " : "", + (intr & AG71XX_INT_RX_OF) ? "RXOF " : "", + (intr & AG71XX_INT_RX_BE) ? "RXBE " : ""); +} + +static void ag71xx_ring_free(struct ag71xx_ring *ring) +{ + kfree(ring->buf); + + if (ring->descs_cpu) + dma_free_coherent(NULL, ring->size * ring->desc_size, + ring->descs_cpu, ring->descs_dma); +} + +static int ag71xx_ring_alloc(struct ag71xx_ring *ring) +{ + int err; + int i; + + ring->desc_size = sizeof(struct ag71xx_desc); + if (ring->desc_size % cache_line_size()) { + DBG("ag71xx: ring %p, desc size %u rounded to %u\n", + ring, ring->desc_size, + roundup(ring->desc_size, cache_line_size())); + ring->desc_size = roundup(ring->desc_size, cache_line_size()); + } + + ring->descs_cpu = dma_alloc_coherent(NULL, ring->size * ring->desc_size, + &ring->descs_dma, GFP_ATOMIC); + if (!ring->descs_cpu) { + err = -ENOMEM; + goto err; + } + + + ring->buf = kzalloc(ring->size * sizeof(*ring->buf), GFP_KERNEL); + if (!ring->buf) { + err = -ENOMEM; + goto err; + } + + for (i = 0; i < ring->size; i++) { + int idx = i * ring->desc_size; + ring->buf[i].desc = (struct ag71xx_desc *)&ring->descs_cpu[idx]; + DBG("ag71xx: ring %p, desc %d at %p\n", + ring, i, ring->buf[i].desc); + } + + return 0; + +err: + return err; +} + +static void ag71xx_ring_tx_clean(struct ag71xx *ag) +{ + struct ag71xx_ring *ring = &ag->tx_ring; + struct net_device *dev = ag->dev; + u32 bytes_compl = 0, pkts_compl = 0; + + while (ring->curr != ring->dirty) { + u32 i = ring->dirty % ring->size; + + if (!ag71xx_desc_empty(ring->buf[i].desc)) { + ring->buf[i].desc->ctrl = 0; + dev->stats.tx_errors++; + } + + if (ring->buf[i].skb) { + bytes_compl += ring->buf[i].skb->len; + pkts_compl++; + dev_kfree_skb_any(ring->buf[i].skb); + } + ring->buf[i].skb = NULL; + ring->dirty++; + } + + /* flush descriptors */ + wmb(); + + netdev_completed_queue(dev, pkts_compl, bytes_compl); +} + +static void ag71xx_ring_tx_init(struct ag71xx *ag) +{ + struct ag71xx_ring *ring = &ag->tx_ring; + int i; + + for (i = 0; i < ring->size; i++) { + ring->buf[i].desc->next = (u32) (ring->descs_dma + + ring->desc_size * ((i + 1) % ring->size)); + + ring->buf[i].desc->ctrl = DESC_EMPTY; + ring->buf[i].skb = NULL; + } + + /* flush descriptors */ + wmb(); + + ring->curr = 0; + ring->dirty = 0; + netdev_reset_queue(ag->dev); +} + +static void ag71xx_ring_rx_clean(struct ag71xx *ag) +{ + struct ag71xx_ring *ring = &ag->rx_ring; + int i; + + if (!ring->buf) + return; + + for (i = 0; i < ring->size; i++) + if (ring->buf[i].rx_buf) { + dma_unmap_single(&ag->dev->dev, ring->buf[i].dma_addr, + AG71XX_RX_BUF_SIZE, DMA_FROM_DEVICE); + kfree(ring->buf[i].rx_buf); + } +} + +static int ag71xx_buffer_offset(struct ag71xx *ag) +{ + int offset = NET_SKB_PAD; + + /* + * On AR71xx/AR91xx packets must be 4-byte aligned. + * + * When using builtin AR8216 support, hardware adds a 2-byte header, + * so we don't need any extra alignment in that case. + */ + if (!ag71xx_get_pdata(ag)->is_ar724x || ag71xx_has_ar8216(ag)) + return offset; + + return offset + NET_IP_ALIGN; +} + +static bool ag71xx_fill_rx_buf(struct ag71xx *ag, struct ag71xx_buf *buf, + int offset) +{ + void *data; + + data = kmalloc(AG71XX_RX_BUF_SIZE + + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)), + GFP_ATOMIC); + if (!data) + return false; + + buf->rx_buf = data; + buf->dma_addr = dma_map_single(&ag->dev->dev, data, + AG71XX_RX_BUF_SIZE, DMA_FROM_DEVICE); + buf->desc->data = (u32) buf->dma_addr + offset; + return true; +} + +static int ag71xx_ring_rx_init(struct ag71xx *ag) +{ + struct ag71xx_ring *ring = &ag->rx_ring; + unsigned int i; + int ret; + int offset = ag71xx_buffer_offset(ag); + + ret = 0; + for (i = 0; i < ring->size; i++) { + ring->buf[i].desc->next = (u32) (ring->descs_dma + + ring->desc_size * ((i + 1) % ring->size)); + + DBG("ag71xx: RX desc at %p, next is %08x\n", + ring->buf[i].desc, + ring->buf[i].desc->next); + } + + for (i = 0; i < ring->size; i++) { + if (!ag71xx_fill_rx_buf(ag, &ring->buf[i], offset)) { + ret = -ENOMEM; + break; + } + + ring->buf[i].desc->ctrl = DESC_EMPTY; + } + + /* flush descriptors */ + wmb(); + + ring->curr = 0; + ring->dirty = 0; + + return ret; +} + +static int ag71xx_ring_rx_refill(struct ag71xx *ag) +{ + struct ag71xx_ring *ring = &ag->rx_ring; + unsigned int count; + int offset = ag71xx_buffer_offset(ag); + + count = 0; + for (; ring->curr - ring->dirty > 0; ring->dirty++) { + unsigned int i; + + i = ring->dirty % ring->size; + + if (!ring->buf[i].rx_buf && + !ag71xx_fill_rx_buf(ag, &ring->buf[i], offset)) + break; + + ring->buf[i].desc->ctrl = DESC_EMPTY; + count++; + } + + /* flush descriptors */ + wmb(); + + DBG("%s: %u rx descriptors refilled\n", ag->dev->name, count); + + return count; +} + +static int ag71xx_rings_init(struct ag71xx *ag) +{ + int ret; + + ret = ag71xx_ring_alloc(&ag->tx_ring); + if (ret) + return ret; + + ag71xx_ring_tx_init(ag); + + ret = ag71xx_ring_alloc(&ag->rx_ring); + if (ret) + return ret; + + ret = ag71xx_ring_rx_init(ag); + return ret; +} + +static void ag71xx_rings_cleanup(struct ag71xx *ag) +{ + ag71xx_ring_rx_clean(ag); + ag71xx_ring_free(&ag->rx_ring); + + ag71xx_ring_tx_clean(ag); + netdev_reset_queue(ag->dev); + ag71xx_ring_free(&ag->tx_ring); +} + +static unsigned char *ag71xx_speed_str(struct ag71xx *ag) +{ + switch (ag->speed) { + case SPEED_1000: + return "1000"; + case SPEED_100: + return "100"; + case SPEED_10: + return "10"; + } + + return "?"; +} + +static void ag71xx_hw_set_macaddr(struct ag71xx *ag, unsigned char *mac) +{ + u32 t; + + t = (((u32) mac[5]) << 24) | (((u32) mac[4]) << 16) + | (((u32) mac[3]) << 8) | ((u32) mac[2]); + + ag71xx_wr(ag, AG71XX_REG_MAC_ADDR1, t); + + t = (((u32) mac[1]) << 24) | (((u32) mac[0]) << 16); + ag71xx_wr(ag, AG71XX_REG_MAC_ADDR2, t); +} + +static void ag71xx_dma_reset(struct ag71xx *ag) +{ + u32 val; + int i; + + ag71xx_dump_dma_regs(ag); + + /* stop RX and TX */ + ag71xx_wr(ag, AG71XX_REG_RX_CTRL, 0); + ag71xx_wr(ag, AG71XX_REG_TX_CTRL, 0); + + /* + * give the hardware some time to really stop all rx/tx activity + * clearing the descriptors too early causes random memory corruption + */ + mdelay(1); + + /* clear descriptor addresses */ + ag71xx_wr(ag, AG71XX_REG_TX_DESC, ag->stop_desc_dma); + ag71xx_wr(ag, AG71XX_REG_RX_DESC, ag->stop_desc_dma); + + /* clear pending RX/TX interrupts */ + for (i = 0; i < 256; i++) { + ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_PR); + ag71xx_wr(ag, AG71XX_REG_TX_STATUS, TX_STATUS_PS); + } + + /* clear pending errors */ + ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_BE | RX_STATUS_OF); + ag71xx_wr(ag, AG71XX_REG_TX_STATUS, TX_STATUS_BE | TX_STATUS_UR); + + val = ag71xx_rr(ag, AG71XX_REG_RX_STATUS); + if (val) + pr_alert("%s: unable to clear DMA Rx status: %08x\n", + ag->dev->name, val); + + val = ag71xx_rr(ag, AG71XX_REG_TX_STATUS); + + /* mask out reserved bits */ + val &= ~0xff000000; + + if (val) + pr_alert("%s: unable to clear DMA Tx status: %08x\n", + ag->dev->name, val); + + ag71xx_dump_dma_regs(ag); +} + +#define MAC_CFG1_INIT (MAC_CFG1_RXE | MAC_CFG1_TXE | \ + MAC_CFG1_SRX | MAC_CFG1_STX) + +#define FIFO_CFG0_INIT (FIFO_CFG0_ALL << FIFO_CFG0_ENABLE_SHIFT) + +#define FIFO_CFG4_INIT (FIFO_CFG4_DE | FIFO_CFG4_DV | FIFO_CFG4_FC | \ + FIFO_CFG4_CE | FIFO_CFG4_CR | FIFO_CFG4_LM | \ + FIFO_CFG4_LO | FIFO_CFG4_OK | FIFO_CFG4_MC | \ + FIFO_CFG4_BC | FIFO_CFG4_DR | FIFO_CFG4_LE | \ + FIFO_CFG4_CF | FIFO_CFG4_PF | FIFO_CFG4_UO | \ + FIFO_CFG4_VT) + +#define FIFO_CFG5_INIT (FIFO_CFG5_DE | FIFO_CFG5_DV | FIFO_CFG5_FC | \ + FIFO_CFG5_CE | FIFO_CFG5_LO | FIFO_CFG5_OK | \ + FIFO_CFG5_MC | FIFO_CFG5_BC | FIFO_CFG5_DR | \ + FIFO_CFG5_CF | FIFO_CFG5_PF | FIFO_CFG5_VT | \ + FIFO_CFG5_LE | FIFO_CFG5_FT | FIFO_CFG5_16 | \ + FIFO_CFG5_17 | FIFO_CFG5_SF) + +static void ag71xx_hw_stop(struct ag71xx *ag) +{ + /* disable all interrupts and stop the rx/tx engine */ + ag71xx_wr(ag, AG71XX_REG_INT_ENABLE, 0); + ag71xx_wr(ag, AG71XX_REG_RX_CTRL, 0); + ag71xx_wr(ag, AG71XX_REG_TX_CTRL, 0); +} + +static void ag71xx_hw_setup(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + + /* setup MAC configuration registers */ + ag71xx_wr(ag, AG71XX_REG_MAC_CFG1, MAC_CFG1_INIT); + + ag71xx_sb(ag, AG71XX_REG_MAC_CFG2, + MAC_CFG2_PAD_CRC_EN | MAC_CFG2_LEN_CHECK); + + /* setup max frame length */ + ag71xx_wr(ag, AG71XX_REG_MAC_MFL, AG71XX_TX_MTU_LEN); + + /* setup FIFO configuration registers */ + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG0, FIFO_CFG0_INIT); + if (pdata->is_ar724x) { + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG1, pdata->fifo_cfg1); + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG2, pdata->fifo_cfg2); + } else { + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG1, 0x0fff0000); + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG2, 0x00001fff); + } + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG4, FIFO_CFG4_INIT); + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG5, FIFO_CFG5_INIT); +} + +static void ag71xx_hw_init(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + u32 reset_mask = pdata->reset_bit; + + ag71xx_hw_stop(ag); + + if (pdata->is_ar724x) { + u32 reset_phy = reset_mask; + + reset_phy &= AR71XX_RESET_GE0_PHY | AR71XX_RESET_GE1_PHY; + reset_mask &= ~(AR71XX_RESET_GE0_PHY | AR71XX_RESET_GE1_PHY); + + ath79_device_reset_set(reset_phy); + mdelay(50); + ath79_device_reset_clear(reset_phy); + mdelay(200); + } + + ag71xx_sb(ag, AG71XX_REG_MAC_CFG1, MAC_CFG1_SR); + udelay(20); + + ath79_device_reset_set(reset_mask); + mdelay(100); + ath79_device_reset_clear(reset_mask); + mdelay(200); + + ag71xx_hw_setup(ag); + + ag71xx_dma_reset(ag); +} + +static void ag71xx_fast_reset(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + struct net_device *dev = ag->dev; + u32 reset_mask = pdata->reset_bit; + u32 rx_ds, tx_ds; + u32 mii_reg; + + reset_mask &= AR71XX_RESET_GE0_MAC | AR71XX_RESET_GE1_MAC; + + mii_reg = ag71xx_rr(ag, AG71XX_REG_MII_CFG); + rx_ds = ag71xx_rr(ag, AG71XX_REG_RX_DESC); + tx_ds = ag71xx_rr(ag, AG71XX_REG_TX_DESC); + + ath79_device_reset_set(reset_mask); + udelay(10); + ath79_device_reset_clear(reset_mask); + udelay(10); + + ag71xx_dma_reset(ag); + ag71xx_hw_setup(ag); + + ag71xx_wr(ag, AG71XX_REG_RX_DESC, rx_ds); + ag71xx_wr(ag, AG71XX_REG_TX_DESC, tx_ds); + ag71xx_wr(ag, AG71XX_REG_MII_CFG, mii_reg); + + ag71xx_hw_set_macaddr(ag, dev->dev_addr); +} + +static void ag71xx_hw_start(struct ag71xx *ag) +{ + /* start RX engine */ + ag71xx_wr(ag, AG71XX_REG_RX_CTRL, RX_CTRL_RXE); + + /* enable interrupts */ + ag71xx_wr(ag, AG71XX_REG_INT_ENABLE, AG71XX_INT_INIT); +} + +void ag71xx_link_adjust(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + u32 cfg2; + u32 ifctl; + u32 fifo5; + + if (!ag->link) { + ag71xx_hw_stop(ag); + netif_carrier_off(ag->dev); + if (netif_msg_link(ag)) + pr_info("%s: link down\n", ag->dev->name); + return; + } + + if (pdata->is_ar724x) + ag71xx_fast_reset(ag); + + cfg2 = ag71xx_rr(ag, AG71XX_REG_MAC_CFG2); + cfg2 &= ~(MAC_CFG2_IF_1000 | MAC_CFG2_IF_10_100 | MAC_CFG2_FDX); + cfg2 |= (ag->duplex) ? MAC_CFG2_FDX : 0; + + ifctl = ag71xx_rr(ag, AG71XX_REG_MAC_IFCTL); + ifctl &= ~(MAC_IFCTL_SPEED); + + fifo5 = ag71xx_rr(ag, AG71XX_REG_FIFO_CFG5); + fifo5 &= ~FIFO_CFG5_BM; + + switch (ag->speed) { + case SPEED_1000: + cfg2 |= MAC_CFG2_IF_1000; + fifo5 |= FIFO_CFG5_BM; + break; + case SPEED_100: + cfg2 |= MAC_CFG2_IF_10_100; + ifctl |= MAC_IFCTL_SPEED; + break; + case SPEED_10: + cfg2 |= MAC_CFG2_IF_10_100; + break; + default: + BUG(); + return; + } + + if (pdata->is_ar91xx) + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG3, 0x00780fff); + else if (pdata->is_ar724x) + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG3, pdata->fifo_cfg3); + else + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG3, 0x008001ff); + + if (pdata->set_speed) + pdata->set_speed(ag->speed); + + ag71xx_wr(ag, AG71XX_REG_MAC_CFG2, cfg2); + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG5, fifo5); + ag71xx_wr(ag, AG71XX_REG_MAC_IFCTL, ifctl); + ag71xx_hw_start(ag); + + netif_carrier_on(ag->dev); + if (netif_msg_link(ag)) + pr_info("%s: link up (%sMbps/%s duplex)\n", + ag->dev->name, + ag71xx_speed_str(ag), + (DUPLEX_FULL == ag->duplex) ? "Full" : "Half"); + + DBG("%s: fifo_cfg0=%#x, fifo_cfg1=%#x, fifo_cfg2=%#x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG0), + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG1), + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG2)); + + DBG("%s: fifo_cfg3=%#x, fifo_cfg4=%#x, fifo_cfg5=%#x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG3), + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG4), + ag71xx_rr(ag, AG71XX_REG_FIFO_CFG5)); + + DBG("%s: mac_cfg2=%#x, mac_ifctl=%#x\n", + ag->dev->name, + ag71xx_rr(ag, AG71XX_REG_MAC_CFG2), + ag71xx_rr(ag, AG71XX_REG_MAC_IFCTL)); +} + +static int ag71xx_open(struct net_device *dev) +{ + struct ag71xx *ag = netdev_priv(dev); + int ret; + + ret = ag71xx_rings_init(ag); + if (ret) + goto err; + + napi_enable(&ag->napi); + + netif_carrier_off(dev); + ag71xx_phy_start(ag); + + ag71xx_wr(ag, AG71XX_REG_TX_DESC, ag->tx_ring.descs_dma); + ag71xx_wr(ag, AG71XX_REG_RX_DESC, ag->rx_ring.descs_dma); + + ag71xx_hw_set_macaddr(ag, dev->dev_addr); + + netif_start_queue(dev); + + return 0; + +err: + ag71xx_rings_cleanup(ag); + return ret; +} + +static int ag71xx_stop(struct net_device *dev) +{ + struct ag71xx *ag = netdev_priv(dev); + unsigned long flags; + + netif_carrier_off(dev); + ag71xx_phy_stop(ag); + + spin_lock_irqsave(&ag->lock, flags); + + netif_stop_queue(dev); + + ag71xx_hw_stop(ag); + ag71xx_dma_reset(ag); + + napi_disable(&ag->napi); + del_timer_sync(&ag->oom_timer); + + spin_unlock_irqrestore(&ag->lock, flags); + + ag71xx_rings_cleanup(ag); + + return 0; +} + +static netdev_tx_t ag71xx_hard_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct ag71xx *ag = netdev_priv(dev); + struct ag71xx_ring *ring = &ag->tx_ring; + struct ag71xx_desc *desc; + dma_addr_t dma_addr; + int i; + + i = ring->curr % ring->size; + desc = ring->buf[i].desc; + + if (!ag71xx_desc_empty(desc)) + goto err_drop; + + if (ag71xx_has_ar8216(ag)) + ag71xx_add_ar8216_header(ag, skb); + + if (skb->len <= 0) { + DBG("%s: packet len is too small\n", ag->dev->name); + goto err_drop; + } + + dma_addr = dma_map_single(&dev->dev, skb->data, skb->len, + DMA_TO_DEVICE); + + netdev_sent_queue(dev, skb->len); + ring->buf[i].skb = skb; + ring->buf[i].timestamp = jiffies; + + /* setup descriptor fields */ + desc->data = (u32) dma_addr; + desc->ctrl = (skb->len & DESC_PKTLEN_M); + + /* flush descriptor */ + wmb(); + + ring->curr++; + if (ring->curr == (ring->dirty + ring->size)) { + DBG("%s: tx queue full\n", ag->dev->name); + netif_stop_queue(dev); + } + + DBG("%s: packet injected into TX queue\n", ag->dev->name); + + /* enable TX engine */ + ag71xx_wr(ag, AG71XX_REG_TX_CTRL, TX_CTRL_TXE); + + return NETDEV_TX_OK; + +err_drop: + dev->stats.tx_dropped++; + + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +static int ag71xx_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct ag71xx *ag = netdev_priv(dev); + int ret; + + switch (cmd) { + case SIOCETHTOOL: + if (ag->phy_dev == NULL) + break; + + spin_lock_irq(&ag->lock); + ret = phy_ethtool_ioctl(ag->phy_dev, (void *) ifr->ifr_data); + spin_unlock_irq(&ag->lock); + return ret; + + case SIOCSIFHWADDR: + if (copy_from_user + (dev->dev_addr, ifr->ifr_data, sizeof(dev->dev_addr))) + return -EFAULT; + return 0; + + case SIOCGIFHWADDR: + if (copy_to_user + (ifr->ifr_data, dev->dev_addr, sizeof(dev->dev_addr))) + return -EFAULT; + return 0; + + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + if (ag->phy_dev == NULL) + break; + + return phy_mii_ioctl(ag->phy_dev, ifr, cmd); + + default: + break; + } + + return -EOPNOTSUPP; +} + +static void ag71xx_oom_timer_handler(unsigned long data) +{ + struct net_device *dev = (struct net_device *) data; + struct ag71xx *ag = netdev_priv(dev); + + napi_schedule(&ag->napi); +} + +static void ag71xx_tx_timeout(struct net_device *dev) +{ + struct ag71xx *ag = netdev_priv(dev); + + if (netif_msg_tx_err(ag)) + pr_info("%s: tx timeout\n", ag->dev->name); + + schedule_work(&ag->restart_work); +} + +static void ag71xx_restart_work_func(struct work_struct *work) +{ + struct ag71xx *ag = container_of(work, struct ag71xx, restart_work); + + if (ag71xx_get_pdata(ag)->is_ar724x) { + ag->link = 0; + ag71xx_link_adjust(ag); + return; + } + + ag71xx_stop(ag->dev); + ag71xx_open(ag->dev); +} + +static bool ag71xx_check_dma_stuck(struct ag71xx *ag, unsigned long timestamp) +{ + u32 rx_sm, tx_sm, rx_fd; + + if (likely(time_before(jiffies, timestamp + HZ/10))) + return false; + + if (!netif_carrier_ok(ag->dev)) + return false; + + rx_sm = ag71xx_rr(ag, AG71XX_REG_RX_SM); + if ((rx_sm & 0x7) == 0x3 && ((rx_sm >> 4) & 0x7) == 0x6) + return true; + + tx_sm = ag71xx_rr(ag, AG71XX_REG_TX_SM); + rx_fd = ag71xx_rr(ag, AG71XX_REG_FIFO_DEPTH); + if (((tx_sm >> 4) & 0x7) == 0 && ((rx_sm & 0x7) == 0) && + ((rx_sm >> 4) & 0x7) == 0 && rx_fd == 0) + return true; + + return false; +} + +static int ag71xx_tx_packets(struct ag71xx *ag) +{ + struct ag71xx_ring *ring = &ag->tx_ring; + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + int sent = 0; + int bytes_compl = 0; + + DBG("%s: processing TX ring\n", ag->dev->name); + + while (ring->dirty != ring->curr) { + unsigned int i = ring->dirty % ring->size; + struct ag71xx_desc *desc = ring->buf[i].desc; + struct sk_buff *skb = ring->buf[i].skb; + + if (!ag71xx_desc_empty(desc)) { + if (pdata->is_ar7240 && + ag71xx_check_dma_stuck(ag, ring->buf[i].timestamp)) + schedule_work(&ag->restart_work); + break; + } + + ag71xx_wr(ag, AG71XX_REG_TX_STATUS, TX_STATUS_PS); + + bytes_compl += skb->len; + ag->dev->stats.tx_bytes += skb->len; + ag->dev->stats.tx_packets++; + + dev_kfree_skb_any(skb); + ring->buf[i].skb = NULL; + + ring->dirty++; + sent++; + } + + DBG("%s: %d packets sent out\n", ag->dev->name, sent); + + netdev_completed_queue(ag->dev, sent, bytes_compl); + if ((ring->curr - ring->dirty) < (ring->size * 3) / 4) + netif_wake_queue(ag->dev); + + return sent; +} + +static int ag71xx_rx_packets(struct ag71xx *ag, int limit) +{ + struct net_device *dev = ag->dev; + struct ag71xx_ring *ring = &ag->rx_ring; + int offset = ag71xx_buffer_offset(ag); + int done = 0; + + DBG("%s: rx packets, limit=%d, curr=%u, dirty=%u\n", + dev->name, limit, ring->curr, ring->dirty); + + while (done < limit) { + unsigned int i = ring->curr % ring->size; + struct ag71xx_desc *desc = ring->buf[i].desc; + struct sk_buff *skb; + int pktlen; + int err = 0; + + if (ag71xx_desc_empty(desc)) + break; + + if ((ring->dirty + ring->size) == ring->curr) { + ag71xx_assert(0); + break; + } + + ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_PR); + + pktlen = ag71xx_desc_pktlen(desc); + pktlen -= ETH_FCS_LEN; + + dma_unmap_single(&dev->dev, ring->buf[i].dma_addr, + AG71XX_RX_BUF_SIZE, DMA_FROM_DEVICE); + + dev->last_rx = jiffies; + dev->stats.rx_packets++; + dev->stats.rx_bytes += pktlen; + + skb = build_skb(ring->buf[i].rx_buf); + if (!skb) { + kfree(ring->buf[i].rx_buf); + goto next; + } + + skb_reserve(skb, offset); + skb_put(skb, pktlen); + + if (ag71xx_has_ar8216(ag)) + err = ag71xx_remove_ar8216_header(ag, skb, pktlen); + + if (err) { + dev->stats.rx_dropped++; + kfree_skb(skb); + } else { + skb->dev = dev; + skb->ip_summed = CHECKSUM_NONE; + skb->protocol = eth_type_trans(skb, dev); + netif_receive_skb(skb); + } + +next: + ring->buf[i].rx_buf = NULL; + done++; + + ring->curr++; + } + + ag71xx_ring_rx_refill(ag); + + DBG("%s: rx finish, curr=%u, dirty=%u, done=%d\n", + dev->name, ring->curr, ring->dirty, done); + + return done; +} + +static int ag71xx_poll(struct napi_struct *napi, int limit) +{ + struct ag71xx *ag = container_of(napi, struct ag71xx, napi); + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + struct net_device *dev = ag->dev; + struct ag71xx_ring *rx_ring; + unsigned long flags; + u32 status; + int tx_done; + int rx_done; + + pdata->ddr_flush(); + tx_done = ag71xx_tx_packets(ag); + + DBG("%s: processing RX ring\n", dev->name); + rx_done = ag71xx_rx_packets(ag, limit); + + ag71xx_debugfs_update_napi_stats(ag, rx_done, tx_done); + + rx_ring = &ag->rx_ring; + if (rx_ring->buf[rx_ring->dirty % rx_ring->size].rx_buf == NULL) + goto oom; + + status = ag71xx_rr(ag, AG71XX_REG_RX_STATUS); + if (unlikely(status & RX_STATUS_OF)) { + ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_OF); + dev->stats.rx_fifo_errors++; + + /* restart RX */ + ag71xx_wr(ag, AG71XX_REG_RX_CTRL, RX_CTRL_RXE); + } + + if (rx_done < limit) { + if (status & RX_STATUS_PR) + goto more; + + status = ag71xx_rr(ag, AG71XX_REG_TX_STATUS); + if (status & TX_STATUS_PS) + goto more; + + DBG("%s: disable polling mode, rx=%d, tx=%d,limit=%d\n", + dev->name, rx_done, tx_done, limit); + + napi_complete(napi); + + /* enable interrupts */ + spin_lock_irqsave(&ag->lock, flags); + ag71xx_int_enable(ag, AG71XX_INT_POLL); + spin_unlock_irqrestore(&ag->lock, flags); + return rx_done; + } + +more: + DBG("%s: stay in polling mode, rx=%d, tx=%d, limit=%d\n", + dev->name, rx_done, tx_done, limit); + return rx_done; + +oom: + if (netif_msg_rx_err(ag)) + pr_info("%s: out of memory\n", dev->name); + + mod_timer(&ag->oom_timer, jiffies + AG71XX_OOM_REFILL); + napi_complete(napi); + return 0; +} + +static irqreturn_t ag71xx_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct ag71xx *ag = netdev_priv(dev); + u32 status; + + status = ag71xx_rr(ag, AG71XX_REG_INT_STATUS); + ag71xx_dump_intr(ag, "raw", status); + + if (unlikely(!status)) + return IRQ_NONE; + + if (unlikely(status & AG71XX_INT_ERR)) { + if (status & AG71XX_INT_TX_BE) { + ag71xx_wr(ag, AG71XX_REG_TX_STATUS, TX_STATUS_BE); + dev_err(&dev->dev, "TX BUS error\n"); + } + if (status & AG71XX_INT_RX_BE) { + ag71xx_wr(ag, AG71XX_REG_RX_STATUS, RX_STATUS_BE); + dev_err(&dev->dev, "RX BUS error\n"); + } + } + + if (likely(status & AG71XX_INT_POLL)) { + ag71xx_int_disable(ag, AG71XX_INT_POLL); + DBG("%s: enable polling mode\n", dev->name); + napi_schedule(&ag->napi); + } + + ag71xx_debugfs_update_int_stats(ag, status); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +/* + * Polling 'interrupt' - used by things like netconsole to send skbs + * without having to re-enable interrupts. It's not called while + * the interrupt routine is executing. + */ +static void ag71xx_netpoll(struct net_device *dev) +{ + disable_irq(dev->irq); + ag71xx_interrupt(dev->irq, dev); + enable_irq(dev->irq); +} +#endif + +static const struct net_device_ops ag71xx_netdev_ops = { + .ndo_open = ag71xx_open, + .ndo_stop = ag71xx_stop, + .ndo_start_xmit = ag71xx_hard_start_xmit, + .ndo_do_ioctl = ag71xx_do_ioctl, + .ndo_tx_timeout = ag71xx_tx_timeout, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = ag71xx_netpoll, +#endif +}; + +static int __devinit ag71xx_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct resource *res; + struct ag71xx *ag; + struct ag71xx_platform_data *pdata; + int err; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + err = -ENXIO; + goto err_out; + } + + if (pdata->mii_bus_dev == NULL) { + dev_err(&pdev->dev, "no MII bus device specified\n"); + err = -EINVAL; + goto err_out; + } + + dev = alloc_etherdev(sizeof(*ag)); + if (!dev) { + dev_err(&pdev->dev, "alloc_etherdev failed\n"); + err = -ENOMEM; + goto err_out; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + ag = netdev_priv(dev); + ag->pdev = pdev; + ag->dev = dev; + ag->msg_enable = netif_msg_init(ag71xx_msg_level, + AG71XX_DEFAULT_MSG_ENABLE); + spin_lock_init(&ag->lock); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mac_base"); + if (!res) { + dev_err(&pdev->dev, "no mac_base resource found\n"); + err = -ENXIO; + goto err_out; + } + + ag->mac_base = ioremap_nocache(res->start, res->end - res->start + 1); + if (!ag->mac_base) { + dev_err(&pdev->dev, "unable to ioremap mac_base\n"); + err = -ENOMEM; + goto err_free_dev; + } + + dev->irq = platform_get_irq(pdev, 0); + err = request_irq(dev->irq, ag71xx_interrupt, + IRQF_DISABLED, + dev->name, dev); + if (err) { + dev_err(&pdev->dev, "unable to request IRQ %d\n", dev->irq); + goto err_unmap_base; + } + + dev->base_addr = (unsigned long)ag->mac_base; + dev->netdev_ops = &ag71xx_netdev_ops; + dev->ethtool_ops = &ag71xx_ethtool_ops; + + INIT_WORK(&ag->restart_work, ag71xx_restart_work_func); + + init_timer(&ag->oom_timer); + ag->oom_timer.data = (unsigned long) dev; + ag->oom_timer.function = ag71xx_oom_timer_handler; + + ag->tx_ring.size = AG71XX_TX_RING_SIZE_DEFAULT; + ag->rx_ring.size = AG71XX_RX_RING_SIZE_DEFAULT; + + ag->stop_desc = dma_alloc_coherent(NULL, + sizeof(struct ag71xx_desc), &ag->stop_desc_dma, GFP_KERNEL); + + if (!ag->stop_desc) + goto err_free_irq; + + ag->stop_desc->data = 0; + ag->stop_desc->ctrl = 0; + ag->stop_desc->next = (u32) ag->stop_desc_dma; + + memcpy(dev->dev_addr, pdata->mac_addr, ETH_ALEN); + + netif_napi_add(dev, &ag->napi, ag71xx_poll, AG71XX_NAPI_WEIGHT); + + err = register_netdev(dev); + if (err) { + dev_err(&pdev->dev, "unable to register net device\n"); + goto err_free_desc; + } + + pr_info("%s: Atheros AG71xx at 0x%08lx, irq %d\n", + dev->name, dev->base_addr, dev->irq); + + ag71xx_dump_regs(ag); + + ag71xx_hw_init(ag); + + ag71xx_dump_regs(ag); + + err = ag71xx_phy_connect(ag); + if (err) + goto err_unregister_netdev; + + err = ag71xx_debugfs_init(ag); + if (err) + goto err_phy_disconnect; + + platform_set_drvdata(pdev, dev); + + return 0; + +err_phy_disconnect: + ag71xx_phy_disconnect(ag); +err_unregister_netdev: + unregister_netdev(dev); +err_free_desc: + dma_free_coherent(NULL, sizeof(struct ag71xx_desc), ag->stop_desc, + ag->stop_desc_dma); +err_free_irq: + free_irq(dev->irq, dev); +err_unmap_base: + iounmap(ag->mac_base); +err_free_dev: + kfree(dev); +err_out: + platform_set_drvdata(pdev, NULL); + return err; +} + +static int __devexit ag71xx_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + + if (dev) { + struct ag71xx *ag = netdev_priv(dev); + + ag71xx_debugfs_exit(ag); + ag71xx_phy_disconnect(ag); + unregister_netdev(dev); + free_irq(dev->irq, dev); + iounmap(ag->mac_base); + kfree(dev); + platform_set_drvdata(pdev, NULL); + } + + return 0; +} + +static struct platform_driver ag71xx_driver = { + .probe = ag71xx_probe, + .remove = __exit_p(ag71xx_remove), + .driver = { + .name = AG71XX_DRV_NAME, + } +}; + +static int __init ag71xx_module_init(void) +{ + int ret; + + ret = ag71xx_debugfs_root_init(); + if (ret) + goto err_out; + + ret = ag71xx_mdio_driver_init(); + if (ret) + goto err_debugfs_exit; + + ret = platform_driver_register(&ag71xx_driver); + if (ret) + goto err_mdio_exit; + + return 0; + +err_mdio_exit: + ag71xx_mdio_driver_exit(); +err_debugfs_exit: + ag71xx_debugfs_root_exit(); +err_out: + return ret; +} + +static void __exit ag71xx_module_exit(void) +{ + platform_driver_unregister(&ag71xx_driver); + ag71xx_mdio_driver_exit(); + ag71xx_debugfs_root_exit(); +} + +module_init(ag71xx_module_init); +module_exit(ag71xx_module_exit); + +MODULE_VERSION(AG71XX_DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Imre Kaloz "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" AG71XX_DRV_NAME); diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c new file mode 100644 index 0000000..eaaf121 --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_mdio.c @@ -0,0 +1,259 @@ +/* + * Atheros AR71xx built-in ethernet mac driver + * + * Copyright (C) 2008-2010 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * Based on Atheros' AG7100 driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "ag71xx.h" + +#define AG71XX_MDIO_RETRY 1000 +#define AG71XX_MDIO_DELAY 5 + +static inline void ag71xx_mdio_wr(struct ag71xx_mdio *am, unsigned reg, + u32 value) +{ + void __iomem *r; + + r = am->mdio_base + reg; + __raw_writel(value, r); + + /* flush write */ + (void) __raw_readl(r); +} + +static inline u32 ag71xx_mdio_rr(struct ag71xx_mdio *am, unsigned reg) +{ + return __raw_readl(am->mdio_base + reg); +} + +static void ag71xx_mdio_dump_regs(struct ag71xx_mdio *am) +{ + DBG("%s: mii_cfg=%08x, mii_cmd=%08x, mii_addr=%08x\n", + am->mii_bus->name, + ag71xx_mdio_rr(am, AG71XX_REG_MII_CFG), + ag71xx_mdio_rr(am, AG71XX_REG_MII_CMD), + ag71xx_mdio_rr(am, AG71XX_REG_MII_ADDR)); + DBG("%s: mii_ctrl=%08x, mii_status=%08x, mii_ind=%08x\n", + am->mii_bus->name, + ag71xx_mdio_rr(am, AG71XX_REG_MII_CTRL), + ag71xx_mdio_rr(am, AG71XX_REG_MII_STATUS), + ag71xx_mdio_rr(am, AG71XX_REG_MII_IND)); +} + +static int ag71xx_mdio_wait_busy(struct ag71xx_mdio *am) +{ + int i; + + for (i = 0; i < AG71XX_MDIO_RETRY; i++) { + u32 busy; + + udelay(AG71XX_MDIO_DELAY); + + busy = ag71xx_mdio_rr(am, AG71XX_REG_MII_IND); + if (!busy) + return 0; + + udelay(AG71XX_MDIO_DELAY); + } + + pr_err("%s: MDIO operation timed out\n", am->mii_bus->name); + + return -ETIMEDOUT; +} + +int ag71xx_mdio_mii_read(struct ag71xx_mdio *am, int addr, int reg) +{ + int err; + int ret; + + err = ag71xx_mdio_wait_busy(am); + if (err) + return 0xffff; + + ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_WRITE); + ag71xx_mdio_wr(am, AG71XX_REG_MII_ADDR, + ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff)); + ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_READ); + + err = ag71xx_mdio_wait_busy(am); + if (err) + return 0xffff; + + ret = ag71xx_mdio_rr(am, AG71XX_REG_MII_STATUS) & 0xffff; + ag71xx_mdio_wr(am, AG71XX_REG_MII_CMD, MII_CMD_WRITE); + + DBG("mii_read: addr=%04x, reg=%04x, value=%04x\n", addr, reg, ret); + + return ret; +} + +void ag71xx_mdio_mii_write(struct ag71xx_mdio *am, int addr, int reg, u16 val) +{ + DBG("mii_write: addr=%04x, reg=%04x, value=%04x\n", addr, reg, val); + + ag71xx_mdio_wr(am, AG71XX_REG_MII_ADDR, + ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff)); + ag71xx_mdio_wr(am, AG71XX_REG_MII_CTRL, val); + + ag71xx_mdio_wait_busy(am); +} + +static int ag71xx_mdio_reset(struct mii_bus *bus) +{ + struct ag71xx_mdio *am = bus->priv; + u32 t; + + if (am->pdata->is_ar7240) + t = MII_CFG_CLK_DIV_6; + else if (am->pdata->builtin_switch && !am->pdata->is_ar934x) + t = MII_CFG_CLK_DIV_10; + else if (!am->pdata->builtin_switch && am->pdata->is_ar934x) + t = MII_CFG_CLK_DIV_58; + else + t = MII_CFG_CLK_DIV_28; + + ag71xx_mdio_wr(am, AG71XX_REG_MII_CFG, t | MII_CFG_RESET); + udelay(100); + + ag71xx_mdio_wr(am, AG71XX_REG_MII_CFG, t); + udelay(100); + + return 0; +} + +static int ag71xx_mdio_read(struct mii_bus *bus, int addr, int reg) +{ + struct ag71xx_mdio *am = bus->priv; + + if (am->pdata->builtin_switch) + return ar7240sw_phy_read(bus, addr, reg); + else + return ag71xx_mdio_mii_read(am, addr, reg); +} + +static int ag71xx_mdio_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct ag71xx_mdio *am = bus->priv; + + if (am->pdata->builtin_switch) + ar7240sw_phy_write(bus, addr, reg, val); + else + ag71xx_mdio_mii_write(am, addr, reg, val); + return 0; +} + +static int __devinit ag71xx_mdio_probe(struct platform_device *pdev) +{ + struct ag71xx_mdio_platform_data *pdata; + struct ag71xx_mdio *am; + struct resource *res; + int i; + int err; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + + am = kzalloc(sizeof(*am), GFP_KERNEL); + if (!am) { + err = -ENOMEM; + goto err_out; + } + + am->pdata = pdata; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no iomem resource found\n"); + err = -ENXIO; + goto err_out; + } + + am->mdio_base = ioremap_nocache(res->start, res->end - res->start + 1); + if (!am->mdio_base) { + dev_err(&pdev->dev, "unable to ioremap registers\n"); + err = -ENOMEM; + goto err_free_mdio; + } + + am->mii_bus = mdiobus_alloc(); + if (am->mii_bus == NULL) { + err = -ENOMEM; + goto err_iounmap; + } + + am->mii_bus->name = "ag71xx_mdio"; + am->mii_bus->read = ag71xx_mdio_read; + am->mii_bus->write = ag71xx_mdio_write; + am->mii_bus->reset = ag71xx_mdio_reset; + am->mii_bus->irq = am->mii_irq; + am->mii_bus->priv = am; + am->mii_bus->parent = &pdev->dev; + snprintf(am->mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(&pdev->dev)); + am->mii_bus->phy_mask = pdata->phy_mask; + + for (i = 0; i < PHY_MAX_ADDR; i++) + am->mii_irq[i] = PHY_POLL; + + ag71xx_mdio_wr(am, AG71XX_REG_MAC_CFG1, 0); + + err = mdiobus_register(am->mii_bus); + if (err) + goto err_free_bus; + + ag71xx_mdio_dump_regs(am); + + platform_set_drvdata(pdev, am); + return 0; + +err_free_bus: + mdiobus_free(am->mii_bus); +err_iounmap: + iounmap(am->mdio_base); +err_free_mdio: + kfree(am); +err_out: + return err; +} + +static int __devexit ag71xx_mdio_remove(struct platform_device *pdev) +{ + struct ag71xx_mdio *am = platform_get_drvdata(pdev); + + if (am) { + mdiobus_unregister(am->mii_bus); + mdiobus_free(am->mii_bus); + iounmap(am->mdio_base); + kfree(am); + platform_set_drvdata(pdev, NULL); + } + + return 0; +} + +static struct platform_driver ag71xx_mdio_driver = { + .probe = ag71xx_mdio_probe, + .remove = __exit_p(ag71xx_mdio_remove), + .driver = { + .name = "ag71xx-mdio", + } +}; + +int __init ag71xx_mdio_driver_init(void) +{ + return platform_driver_register(&ag71xx_mdio_driver); +} + +void ag71xx_mdio_driver_exit(void) +{ + platform_driver_unregister(&ag71xx_mdio_driver); +} diff --git a/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c b/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c new file mode 100644 index 0000000..ebdbc5b --- /dev/null +++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_phy.c @@ -0,0 +1,235 @@ +/* + * Atheros AR71xx built-in ethernet mac driver + * + * Copyright (C) 2008-2010 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * Based on Atheros' AG7100 driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "ag71xx.h" + +static void ag71xx_phy_link_adjust(struct net_device *dev) +{ + struct ag71xx *ag = netdev_priv(dev); + struct phy_device *phydev = ag->phy_dev; + unsigned long flags; + int status_change = 0; + + spin_lock_irqsave(&ag->lock, flags); + + if (phydev->link) { + if (ag->duplex != phydev->duplex + || ag->speed != phydev->speed) { + status_change = 1; + } + } + + if (phydev->link != ag->link) + status_change = 1; + + ag->link = phydev->link; + ag->duplex = phydev->duplex; + ag->speed = phydev->speed; + + if (status_change) + ag71xx_link_adjust(ag); + + spin_unlock_irqrestore(&ag->lock, flags); +} + +void ag71xx_phy_start(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + + if (ag->phy_dev) { + phy_start(ag->phy_dev); + } else if (pdata->switch_data) { + ag71xx_ar7240_start(ag); + } else { + ag->link = 1; + ag71xx_link_adjust(ag); + } +} + +void ag71xx_phy_stop(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + unsigned long flags; + + if (ag->phy_dev) + phy_stop(ag->phy_dev); + else if (pdata->switch_data) + ag71xx_ar7240_stop(ag); + + spin_lock_irqsave(&ag->lock, flags); + if (ag->link) { + ag->link = 0; + ag71xx_link_adjust(ag); + } + spin_unlock_irqrestore(&ag->lock, flags); +} + +static int ag71xx_phy_connect_fixed(struct ag71xx *ag) +{ + struct net_device *dev = ag->dev; + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + int ret = 0; + + /* use fixed settings */ + switch (pdata->speed) { + case SPEED_10: + case SPEED_100: + case SPEED_1000: + break; + default: + netdev_err(dev, "invalid speed specified\n"); + ret = -EINVAL; + break; + } + + netdev_dbg(dev, "using fixed link parameters\n"); + + ag->duplex = pdata->duplex; + ag->speed = pdata->speed; + + return ret; +} + +static int ag71xx_phy_connect_multi(struct ag71xx *ag) +{ + struct net_device *dev = ag->dev; + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + struct phy_device *phydev = NULL; + int phy_addr; + int ret = 0; + + for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) { + if (!(pdata->phy_mask & (1 << phy_addr))) + continue; + + if (ag->mii_bus->phy_map[phy_addr] == NULL) + continue; + + DBG("%s: PHY found at %s, uid=%08x\n", + dev->name, + dev_name(&ag->mii_bus->phy_map[phy_addr]->dev), + ag->mii_bus->phy_map[phy_addr]->phy_id); + + if (phydev == NULL) + phydev = ag->mii_bus->phy_map[phy_addr]; + } + + if (!phydev) { + netdev_err(dev, "no PHY found with phy_mask=%08x\n", + pdata->phy_mask); + return -ENODEV; + } + + ag->phy_dev = phy_connect(dev, dev_name(&phydev->dev), + &ag71xx_phy_link_adjust, 0, + pdata->phy_if_mode); + + if (IS_ERR(ag->phy_dev)) { + netdev_err(dev, "could not connect to PHY at %s\n", + dev_name(&phydev->dev)); + return PTR_ERR(ag->phy_dev); + } + + /* mask with MAC supported features */ + if (pdata->has_gbit) + phydev->supported &= PHY_GBIT_FEATURES; + else + phydev->supported &= PHY_BASIC_FEATURES; + + phydev->advertising = phydev->supported; + + netdev_info(dev, "connected to PHY at %s [uid=%08x, driver=%s]\n", + dev_name(&phydev->dev), phydev->phy_id, phydev->drv->name); + + ag->link = 0; + ag->speed = 0; + ag->duplex = -1; + + return ret; +} + +static int dev_is_class(struct device *dev, void *class) +{ + if (dev->class != NULL && !strcmp(dev->class->name, class)) + return 1; + + return 0; +} + +static struct device *dev_find_class(struct device *parent, char *class) +{ + if (dev_is_class(parent, class)) { + get_device(parent); + return parent; + } + + return device_find_child(parent, class, dev_is_class); +} + +static struct mii_bus *dev_to_mii_bus(struct device *dev) +{ + struct device *d; + + d = dev_find_class(dev, "mdio_bus"); + if (d != NULL) { + struct mii_bus *bus; + + bus = to_mii_bus(d); + put_device(d); + + return bus; + } + + return NULL; +} + +int __devinit ag71xx_phy_connect(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + + if (pdata->mii_bus_dev == NULL || + pdata->mii_bus_dev->bus == NULL ) + return ag71xx_phy_connect_fixed(ag); + + ag->mii_bus = dev_to_mii_bus(pdata->mii_bus_dev); + if (ag->mii_bus == NULL) { + netdev_err(ag->dev, "unable to find MII bus on device '%s'\n", + dev_name(pdata->mii_bus_dev)); + return -ENODEV; + } + + /* Reset the mdio bus explicitly */ + if (ag->mii_bus->reset) { + mutex_lock(&ag->mii_bus->mdio_lock); + ag->mii_bus->reset(ag->mii_bus); + mutex_unlock(&ag->mii_bus->mdio_lock); + } + + if (pdata->switch_data) + return ag71xx_ar7240_init(ag); + + if (pdata->phy_mask) + return ag71xx_phy_connect_multi(ag); + + return ag71xx_phy_connect_fixed(ag); +} + +void ag71xx_phy_disconnect(struct ag71xx *ag) +{ + struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); + + if (pdata->switch_data) + ag71xx_ar7240_cleanup(ag); + else if (ag->phy_dev) + phy_disconnect(ag->phy_dev); +} diff --git a/drivers/spi/spi-ap83.c b/drivers/spi/spi-ap83.c new file mode 100644 index 0000000..33843a6 --- /dev/null +++ b/drivers/spi/spi-ap83.c @@ -0,0 +1,283 @@ +/* + * Atheros AP83 board specific SPI Controller driver + * + * Copyright (C) 2009 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRV_DESC "Atheros AP83 board SPI Controller driver" +#define DRV_VERSION "0.1.0" +#define DRV_NAME "ap83-spi" + +#define AP83_SPI_CLK_HIGH (1 << 23) +#define AP83_SPI_CLK_LOW 0 +#define AP83_SPI_MOSI_HIGH (1 << 22) +#define AP83_SPI_MOSI_LOW 0 + +#define AP83_SPI_GPIO_CS 1 +#define AP83_SPI_GPIO_MISO 3 + +struct ap83_spi { + struct spi_bitbang bitbang; + void __iomem *base; + u32 addr; + + struct platform_device *pdev; +}; + +static inline u32 ap83_spi_rr(struct ap83_spi *sp, u32 reg) +{ + return __raw_readl(sp->base + reg); +} + +static inline struct ap83_spi *spidev_to_sp(struct spi_device *spi) +{ + return spi_master_get_devdata(spi->master); +} + +static inline void setsck(struct spi_device *spi, int val) +{ + struct ap83_spi *sp = spidev_to_sp(spi); + + if (val) + sp->addr |= AP83_SPI_CLK_HIGH; + else + sp->addr &= ~AP83_SPI_CLK_HIGH; + + dev_dbg(&spi->dev, "addr=%08x, SCK set to %s\n", + sp->addr, (val) ? "HIGH" : "LOW"); + + ap83_spi_rr(sp, sp->addr); +} + +static inline void setmosi(struct spi_device *spi, int val) +{ + struct ap83_spi *sp = spidev_to_sp(spi); + + if (val) + sp->addr |= AP83_SPI_MOSI_HIGH; + else + sp->addr &= ~AP83_SPI_MOSI_HIGH; + + dev_dbg(&spi->dev, "addr=%08x, MOSI set to %s\n", + sp->addr, (val) ? "HIGH" : "LOW"); + + ap83_spi_rr(sp, sp->addr); +} + +static inline u32 getmiso(struct spi_device *spi) +{ + u32 ret; + + ret = gpio_get_value(AP83_SPI_GPIO_MISO) ? 1 : 0; + dev_dbg(&spi->dev, "get MISO: %d\n", ret); + + return ret; +} + +static inline void do_spidelay(struct spi_device *spi, unsigned nsecs) +{ + ndelay(nsecs); +} + +static void ap83_spi_chipselect(struct spi_device *spi, int on) +{ + struct ap83_spi *sp = spidev_to_sp(spi); + + dev_dbg(&spi->dev, "set CS to %d\n", (on) ? 0 : 1); + + if (on) { + ath79_flash_acquire(); + + sp->addr = 0; + ap83_spi_rr(sp, sp->addr); + + gpio_set_value(AP83_SPI_GPIO_CS, 0); + } else { + gpio_set_value(AP83_SPI_GPIO_CS, 1); + ath79_flash_release(); + } +} + +#define spidelay(nsecs) \ + do { \ + /* Steal the spi_device pointer from our caller. \ + * The bitbang-API should probably get fixed here... */ \ + do_spidelay(spi, nsecs); \ + } while (0) + +#define EXPAND_BITBANG_TXRX +#include +#include "spi-bitbang-txrx.h" + +static u32 ap83_spi_txrx_mode0(struct spi_device *spi, + unsigned nsecs, u32 word, u8 bits) +{ + dev_dbg(&spi->dev, "TXRX0 word=%08x, bits=%u\n", word, bits); + return bitbang_txrx_be_cpha0(spi, nsecs, 0, 0, word, bits); +} + +static u32 ap83_spi_txrx_mode1(struct spi_device *spi, + unsigned nsecs, u32 word, u8 bits) +{ + dev_dbg(&spi->dev, "TXRX1 word=%08x, bits=%u\n", word, bits); + return bitbang_txrx_be_cpha1(spi, nsecs, 0, 0, word, bits); +} + +static u32 ap83_spi_txrx_mode2(struct spi_device *spi, + unsigned nsecs, u32 word, u8 bits) +{ + dev_dbg(&spi->dev, "TXRX2 word=%08x, bits=%u\n", word, bits); + return bitbang_txrx_be_cpha0(spi, nsecs, 1, 0, word, bits); +} + +static u32 ap83_spi_txrx_mode3(struct spi_device *spi, + unsigned nsecs, u32 word, u8 bits) +{ + dev_dbg(&spi->dev, "TXRX3 word=%08x, bits=%u\n", word, bits); + return bitbang_txrx_be_cpha1(spi, nsecs, 1, 0, word, bits); +} + +static int ap83_spi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct ap83_spi *sp; + struct ap83_spi_platform_data *pdata; + struct resource *r; + int ret; + + ret = gpio_request(AP83_SPI_GPIO_MISO, "spi-miso"); + if (ret) { + dev_err(&pdev->dev, "gpio request failed for MISO\n"); + return ret; + } + + ret = gpio_request(AP83_SPI_GPIO_CS, "spi-cs"); + if (ret) { + dev_err(&pdev->dev, "gpio request failed for CS\n"); + goto err_free_miso; + } + + ret = gpio_direction_input(AP83_SPI_GPIO_MISO); + if (ret) { + dev_err(&pdev->dev, "unable to set direction of MISO\n"); + goto err_free_cs; + } + + ret = gpio_direction_output(AP83_SPI_GPIO_CS, 0); + if (ret) { + dev_err(&pdev->dev, "unable to set direction of CS\n"); + goto err_free_cs; + } + + master = spi_alloc_master(&pdev->dev, sizeof(*sp)); + if (master == NULL) { + dev_err(&pdev->dev, "failed to allocate spi master\n"); + return -ENOMEM; + } + + sp = spi_master_get_devdata(master); + platform_set_drvdata(pdev, sp); + + pdata = pdev->dev.platform_data; + + sp->bitbang.master = spi_master_get(master); + sp->bitbang.chipselect = ap83_spi_chipselect; + sp->bitbang.txrx_word[SPI_MODE_0] = ap83_spi_txrx_mode0; + sp->bitbang.txrx_word[SPI_MODE_1] = ap83_spi_txrx_mode1; + sp->bitbang.txrx_word[SPI_MODE_2] = ap83_spi_txrx_mode2; + sp->bitbang.txrx_word[SPI_MODE_3] = ap83_spi_txrx_mode3; + + sp->bitbang.master->bus_num = pdev->id; + sp->bitbang.master->num_chipselect = 1; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + ret = -ENOENT; + goto err_spi_put; + } + + sp->base = ioremap_nocache(r->start, r->end - r->start + 1); + if (!sp->base) { + ret = -ENXIO; + goto err_spi_put; + } + + ret = spi_bitbang_start(&sp->bitbang); + if (!ret) + goto err_unmap; + + dev_info(&pdev->dev, "AP83 SPI adapter at %08x\n", r->start); + + return 0; + +err_unmap: + iounmap(sp->base); +err_spi_put: + platform_set_drvdata(pdev, NULL); + spi_master_put(sp->bitbang.master); + +err_free_cs: + gpio_free(AP83_SPI_GPIO_CS); +err_free_miso: + gpio_free(AP83_SPI_GPIO_MISO); + return ret; +} + +static int ap83_spi_remove(struct platform_device *pdev) +{ + struct ap83_spi *sp = platform_get_drvdata(pdev); + + spi_bitbang_stop(&sp->bitbang); + iounmap(sp->base); + platform_set_drvdata(pdev, NULL); + spi_master_put(sp->bitbang.master); + + return 0; +} + +static struct platform_driver ap83_spi_drv = { + .probe = ap83_spi_probe, + .remove = ap83_spi_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ap83_spi_init(void) +{ + return platform_driver_register(&ap83_spi_drv); +} +module_init(ap83_spi_init); + +static void __exit ap83_spi_exit(void) +{ + platform_driver_unregister(&ap83_spi_drv); +} +module_exit(ap83_spi_exit); + +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-rb4xx-cpld.c b/drivers/spi/spi-rb4xx-cpld.c new file mode 100644 index 0000000..4e8ce31 --- /dev/null +++ b/drivers/spi/spi-rb4xx-cpld.c @@ -0,0 +1,441 @@ +/* + * SPI driver for the CPLD chip on the Mikrotik RB4xx boards + * + * Copyright (C) 2010 Gabor Juhos + * + * This file was based on the patches for Linux 2.6.27.39 published by + * MikroTik for their RouterBoard 4xx series devices. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRV_NAME "spi-rb4xx-cpld" +#define DRV_DESC "RB4xx CPLD driver" +#define DRV_VERSION "0.1.0" + +#define CPLD_CMD_WRITE_NAND 0x08 /* send cmd, n x send data, send indle */ +#define CPLD_CMD_WRITE_CFG 0x09 /* send cmd, n x send cfg */ +#define CPLD_CMD_READ_NAND 0x0a /* send cmd, send idle, n x read data */ +#define CPLD_CMD_READ_FAST 0x0b /* send cmd, 4 x idle, n x read data */ +#define CPLD_CMD_LED5_ON 0x0c /* send cmd */ +#define CPLD_CMD_LED5_OFF 0x0d /* send cmd */ + +struct rb4xx_cpld { + struct spi_device *spi; + struct mutex lock; + struct gpio_chip chip; + unsigned int config; +}; + +static struct rb4xx_cpld *rb4xx_cpld; + +static inline struct rb4xx_cpld *gpio_to_cpld(struct gpio_chip *chip) +{ + return container_of(chip, struct rb4xx_cpld, chip); +} + +static int rb4xx_cpld_write_cmd(struct rb4xx_cpld *cpld, unsigned char cmd) +{ + struct spi_transfer t[1]; + struct spi_message m; + unsigned char tx_buf[1]; + int err; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = tx_buf; + t[0].len = sizeof(tx_buf); + spi_message_add_tail(&t[0], &m); + + tx_buf[0] = cmd; + + err = spi_sync(cpld->spi, &m); + return err; +} + +static int rb4xx_cpld_write_cfg(struct rb4xx_cpld *cpld, unsigned char config) +{ + struct spi_transfer t[1]; + struct spi_message m; + unsigned char cmd[2]; + int err; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + cmd[0] = CPLD_CMD_WRITE_CFG; + cmd[1] = config; + + err = spi_sync(cpld->spi, &m); + return err; +} + +static int __rb4xx_cpld_change_cfg(struct rb4xx_cpld *cpld, unsigned mask, + unsigned value) +{ + unsigned int config; + int err; + + config = cpld->config & ~mask; + config |= value; + + if ((cpld->config ^ config) & 0xff) { + err = rb4xx_cpld_write_cfg(cpld, config); + if (err) + return err; + } + + if ((cpld->config ^ config) & CPLD_CFG_nLED5) { + err = rb4xx_cpld_write_cmd(cpld, (value) ? CPLD_CMD_LED5_ON : + CPLD_CMD_LED5_OFF); + if (err) + return err; + } + + cpld->config = config; + return 0; +} + +int rb4xx_cpld_change_cfg(unsigned mask, unsigned value) +{ + int ret; + + if (rb4xx_cpld == NULL) + return -ENODEV; + + mutex_lock(&rb4xx_cpld->lock); + ret = __rb4xx_cpld_change_cfg(rb4xx_cpld, mask, value); + mutex_unlock(&rb4xx_cpld->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(rb4xx_cpld_change_cfg); + +int rb4xx_cpld_read_from(unsigned addr, unsigned char *rx_buf, + const unsigned char *verify_buf, unsigned count) +{ + const unsigned char cmd[5] = { + CPLD_CMD_READ_FAST, + (addr >> 16) & 0xff, + (addr >> 8) & 0xff, + addr & 0xff, + 0 + }; + struct spi_transfer t[2] = { + { + .tx_buf = &cmd, + .len = 5, + }, + { + .tx_buf = verify_buf, + .rx_buf = rx_buf, + .len = count, + .verify = (verify_buf != NULL), + }, + }; + struct spi_message m; + + if (rb4xx_cpld == NULL) + return -ENODEV; + + spi_message_init(&m); + m.fast_read = 1; + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + return spi_sync(rb4xx_cpld->spi, &m); +} +EXPORT_SYMBOL_GPL(rb4xx_cpld_read_from); + +#if 0 +int rb4xx_cpld_read(unsigned char *buf, unsigned char *verify_buf, + unsigned count) +{ + struct spi_transfer t[2]; + struct spi_message m; + unsigned char cmd[2]; + + if (rb4xx_cpld == NULL) + return -ENODEV; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); + + /* send command */ + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + cmd[0] = CPLD_CMD_READ_NAND; + cmd[1] = 0; + + /* read data */ + t[1].rx_buf = buf; + t[1].len = count; + spi_message_add_tail(&t[1], &m); + + return spi_sync(rb4xx_cpld->spi, &m); +} +#else +int rb4xx_cpld_read(unsigned char *rx_buf, const unsigned char *verify_buf, + unsigned count) +{ + static const unsigned char cmd[2] = { CPLD_CMD_READ_NAND, 0 }; + struct spi_transfer t[2] = { + { + .tx_buf = &cmd, + .len = 2, + }, { + .tx_buf = verify_buf, + .rx_buf = rx_buf, + .len = count, + .verify = (verify_buf != NULL), + }, + }; + struct spi_message m; + + if (rb4xx_cpld == NULL) + return -ENODEV; + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + return spi_sync(rb4xx_cpld->spi, &m); +} +#endif +EXPORT_SYMBOL_GPL(rb4xx_cpld_read); + +int rb4xx_cpld_write(const unsigned char *buf, unsigned count) +{ +#if 0 + struct spi_transfer t[3]; + struct spi_message m; + unsigned char cmd[1]; + + if (rb4xx_cpld == NULL) + return -ENODEV; + + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + + /* send command */ + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + cmd[0] = CPLD_CMD_WRITE_NAND; + + /* write data */ + t[1].tx_buf = buf; + t[1].len = count; + spi_message_add_tail(&t[1], &m); + + /* send idle */ + t[2].len = 1; + spi_message_add_tail(&t[2], &m); + + return spi_sync(rb4xx_cpld->spi, &m); +#else + static const unsigned char cmd = CPLD_CMD_WRITE_NAND; + struct spi_transfer t[3] = { + { + .tx_buf = &cmd, + .len = 1, + }, { + .tx_buf = buf, + .len = count, + .fast_write = 1, + }, { + .len = 1, + .fast_write = 1, + }, + }; + struct spi_message m; + + if (rb4xx_cpld == NULL) + return -ENODEV; + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + spi_message_add_tail(&t[2], &m); + return spi_sync(rb4xx_cpld->spi, &m); +#endif +} +EXPORT_SYMBOL_GPL(rb4xx_cpld_write); + +static int rb4xx_cpld_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct rb4xx_cpld *cpld = gpio_to_cpld(chip); + int ret; + + mutex_lock(&cpld->lock); + ret = (cpld->config >> offset) & 1; + mutex_unlock(&cpld->lock); + + return ret; +} + +static void rb4xx_cpld_gpio_set(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct rb4xx_cpld *cpld = gpio_to_cpld(chip); + + mutex_lock(&cpld->lock); + __rb4xx_cpld_change_cfg(cpld, (1 << offset), !!value << offset); + mutex_unlock(&cpld->lock); +} + +static int rb4xx_cpld_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + return -EOPNOTSUPP; +} + +static int rb4xx_cpld_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, + int value) +{ + struct rb4xx_cpld *cpld = gpio_to_cpld(chip); + int ret; + + mutex_lock(&cpld->lock); + ret = __rb4xx_cpld_change_cfg(cpld, (1 << offset), !!value << offset); + mutex_unlock(&cpld->lock); + + return ret; +} + +static int rb4xx_cpld_gpio_init(struct rb4xx_cpld *cpld, unsigned int base) +{ + int err; + + /* init config */ + cpld->config = CPLD_CFG_nLED1 | CPLD_CFG_nLED2 | CPLD_CFG_nLED3 | + CPLD_CFG_nLED4 | CPLD_CFG_nCE; + rb4xx_cpld_write_cfg(cpld, cpld->config); + + /* setup GPIO chip */ + cpld->chip.label = DRV_NAME; + + cpld->chip.get = rb4xx_cpld_gpio_get; + cpld->chip.set = rb4xx_cpld_gpio_set; + cpld->chip.direction_input = rb4xx_cpld_gpio_direction_input; + cpld->chip.direction_output = rb4xx_cpld_gpio_direction_output; + + cpld->chip.base = base; + cpld->chip.ngpio = CPLD_NUM_GPIOS; + cpld->chip.can_sleep = 1; + cpld->chip.dev = &cpld->spi->dev; + cpld->chip.owner = THIS_MODULE; + + err = gpiochip_add(&cpld->chip); + if (err) + dev_err(&cpld->spi->dev, "adding GPIO chip failed, err=%d\n", + err); + + return err; +} + +static int __devinit rb4xx_cpld_probe(struct spi_device *spi) +{ + struct rb4xx_cpld *cpld; + struct rb4xx_cpld_platform_data *pdata; + int err; + + pdata = spi->dev.platform_data; + if (!pdata) { + dev_dbg(&spi->dev, "no platform data\n"); + return -EINVAL; + } + + cpld = kzalloc(sizeof(*cpld), GFP_KERNEL); + if (!cpld) { + dev_err(&spi->dev, "no memory for private data\n"); + return -ENOMEM; + } + + mutex_init(&cpld->lock); + cpld->spi = spi_dev_get(spi); + dev_set_drvdata(&spi->dev, cpld); + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "spi_setup failed, err=%d\n", err); + goto err_drvdata; + } + + err = rb4xx_cpld_gpio_init(cpld, pdata->gpio_base); + if (err) + goto err_drvdata; + + rb4xx_cpld = cpld; + + return 0; + +err_drvdata: + dev_set_drvdata(&spi->dev, NULL); + kfree(cpld); + + return err; +} + +static int __devexit rb4xx_cpld_remove(struct spi_device *spi) +{ + struct rb4xx_cpld *cpld; + + rb4xx_cpld = NULL; + cpld = dev_get_drvdata(&spi->dev); + dev_set_drvdata(&spi->dev, NULL); + kfree(cpld); + + return 0; +} + +static struct spi_driver rb4xx_cpld_driver = { + .driver = { + .name = DRV_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = rb4xx_cpld_probe, + .remove = __devexit_p(rb4xx_cpld_remove), +}; + +static int __init rb4xx_cpld_init(void) +{ + return spi_register_driver(&rb4xx_cpld_driver); +} +module_init(rb4xx_cpld_init); + +static void __exit rb4xx_cpld_exit(void) +{ + spi_unregister_driver(&rb4xx_cpld_driver); +} +module_exit(rb4xx_cpld_exit); + +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-rb4xx.c b/drivers/spi/spi-rb4xx.c new file mode 100644 index 0000000..56260ff --- /dev/null +++ b/drivers/spi/spi-rb4xx.c @@ -0,0 +1,507 @@ +/* + * SPI controller driver for the Mikrotik RB4xx boards + * + * Copyright (C) 2010 Gabor Juhos + * + * This file was based on the patches for Linux 2.6.27.39 published by + * MikroTik for their RouterBoard 4xx series devices. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DRV_NAME "rb4xx-spi" +#define DRV_DESC "Mikrotik RB4xx SPI controller driver" +#define DRV_VERSION "0.1.0" + +#define SPI_CTRL_FASTEST 0x40 +#define SPI_FLASH_HZ 33333334 +#define SPI_CPLD_HZ 33333334 + +#define CPLD_CMD_READ_FAST 0x0b + +#undef RB4XX_SPI_DEBUG + +struct rb4xx_spi { + void __iomem *base; + struct spi_master *master; + + unsigned spi_ctrl_flash; + unsigned spi_ctrl_fread; + + struct clk *ahb_clk; + unsigned long ahb_freq; + + spinlock_t lock; + struct list_head queue; + int busy:1; + int cs_wait; +}; + +static unsigned spi_clk_low = AR71XX_SPI_IOC_CS1; + +#ifdef RB4XX_SPI_DEBUG +static inline void do_spi_delay(void) +{ + ndelay(20000); +} +#else +static inline void do_spi_delay(void) { } +#endif + +static inline void do_spi_init(struct spi_device *spi) +{ + unsigned cs = AR71XX_SPI_IOC_CS0 | AR71XX_SPI_IOC_CS1; + + if (!(spi->mode & SPI_CS_HIGH)) + cs ^= (spi->chip_select == 2) ? AR71XX_SPI_IOC_CS1 : + AR71XX_SPI_IOC_CS0; + + spi_clk_low = cs; +} + +static inline void do_spi_finish(void __iomem *base) +{ + do_spi_delay(); + __raw_writel(AR71XX_SPI_IOC_CS0 | AR71XX_SPI_IOC_CS1, + base + AR71XX_SPI_REG_IOC); +} + +static inline void do_spi_clk(void __iomem *base, int bit) +{ + unsigned bval = spi_clk_low | ((bit & 1) ? AR71XX_SPI_IOC_DO : 0); + + do_spi_delay(); + __raw_writel(bval, base + AR71XX_SPI_REG_IOC); + do_spi_delay(); + __raw_writel(bval | AR71XX_SPI_IOC_CLK, base + AR71XX_SPI_REG_IOC); +} + +static void do_spi_byte(void __iomem *base, unsigned char byte) +{ + do_spi_clk(base, byte >> 7); + do_spi_clk(base, byte >> 6); + do_spi_clk(base, byte >> 5); + do_spi_clk(base, byte >> 4); + do_spi_clk(base, byte >> 3); + do_spi_clk(base, byte >> 2); + do_spi_clk(base, byte >> 1); + do_spi_clk(base, byte); + + pr_debug("spi_byte sent 0x%02x got 0x%02x\n", + (unsigned)byte, + (unsigned char)__raw_readl(base + AR71XX_SPI_REG_RDS)); +} + +static inline void do_spi_clk_fast(void __iomem *base, unsigned bit1, + unsigned bit2) +{ + unsigned bval = (spi_clk_low | + ((bit1 & 1) ? AR71XX_SPI_IOC_DO : 0) | + ((bit2 & 1) ? AR71XX_SPI_IOC_CS2 : 0)); + do_spi_delay(); + __raw_writel(bval, base + AR71XX_SPI_REG_IOC); + do_spi_delay(); + __raw_writel(bval | AR71XX_SPI_IOC_CLK, base + AR71XX_SPI_REG_IOC); +} + +static void do_spi_byte_fast(void __iomem *base, unsigned char byte) +{ + do_spi_clk_fast(base, byte >> 7, byte >> 6); + do_spi_clk_fast(base, byte >> 5, byte >> 4); + do_spi_clk_fast(base, byte >> 3, byte >> 2); + do_spi_clk_fast(base, byte >> 1, byte >> 0); + + pr_debug("spi_byte_fast sent 0x%02x got 0x%02x\n", + (unsigned)byte, + (unsigned char) __raw_readl(base + AR71XX_SPI_REG_RDS)); +} + +static int rb4xx_spi_txrx(void __iomem *base, struct spi_transfer *t) +{ + const unsigned char *rxv_ptr = NULL; + const unsigned char *tx_ptr = t->tx_buf; + unsigned char *rx_ptr = t->rx_buf; + unsigned i; + + pr_debug("spi_txrx len %u tx %u rx %u\n", + t->len, + (t->tx_buf ? 1 : 0), + (t->rx_buf ? 1 : 0)); + + if (t->verify) { + rxv_ptr = tx_ptr; + tx_ptr = NULL; + } + + for (i = 0; i < t->len; ++i) { + unsigned char sdata = tx_ptr ? tx_ptr[i] : 0; + + if (t->fast_write) + do_spi_byte_fast(base, sdata); + else + do_spi_byte(base, sdata); + + if (rx_ptr) { + rx_ptr[i] = __raw_readl(base + AR71XX_SPI_REG_RDS) & 0xff; + } else if (rxv_ptr) { + unsigned char c = __raw_readl(base + AR71XX_SPI_REG_RDS); + if (rxv_ptr[i] != c) + return i; + } + } + + return i; +} + +static int rb4xx_spi_read_fast(struct rb4xx_spi *rbspi, + struct spi_message *m) +{ + struct spi_transfer *t; + const unsigned char *tx_ptr; + unsigned addr; + void __iomem *base = rbspi->base; + + /* check for exactly two transfers */ + if (list_empty(&m->transfers) || + list_is_last(m->transfers.next, &m->transfers) || + !list_is_last(m->transfers.next->next, &m->transfers)) { + return -1; + } + + /* first transfer contains command and address */ + t = list_entry(m->transfers.next, + struct spi_transfer, transfer_list); + + if (t->len != 5 || t->tx_buf == NULL) + return -1; + + tx_ptr = t->tx_buf; + if (tx_ptr[0] != CPLD_CMD_READ_FAST) + return -1; + + addr = tx_ptr[1]; + addr = tx_ptr[2] | (addr << 8); + addr = tx_ptr[3] | (addr << 8); + addr += (unsigned) base; + + m->actual_length += t->len; + + /* second transfer contains data itself */ + t = list_entry(m->transfers.next->next, + struct spi_transfer, transfer_list); + + if (t->tx_buf && !t->verify) + return -1; + + __raw_writel(AR71XX_SPI_FS_GPIO, base + AR71XX_SPI_REG_FS); + __raw_writel(rbspi->spi_ctrl_fread, base + AR71XX_SPI_REG_CTRL); + __raw_writel(0, base + AR71XX_SPI_REG_FS); + + if (t->rx_buf) { + memcpy(t->rx_buf, (const void *)addr, t->len); + } else if (t->tx_buf) { + unsigned char buf[t->len]; + memcpy(buf, (const void *)addr, t->len); + if (memcmp(t->tx_buf, buf, t->len) != 0) + m->status = -EMSGSIZE; + } + m->actual_length += t->len; + + if (rbspi->spi_ctrl_flash != rbspi->spi_ctrl_fread) { + __raw_writel(AR71XX_SPI_FS_GPIO, base + AR71XX_SPI_REG_FS); + __raw_writel(rbspi->spi_ctrl_flash, base + AR71XX_SPI_REG_CTRL); + __raw_writel(0, base + AR71XX_SPI_REG_FS); + } + + return 0; +} + +static int rb4xx_spi_msg(struct rb4xx_spi *rbspi, struct spi_message *m) +{ + struct spi_transfer *t = NULL; + void __iomem *base = rbspi->base; + + m->status = 0; + if (list_empty(&m->transfers)) + return -1; + + if (m->fast_read) + if (rb4xx_spi_read_fast(rbspi, m) == 0) + return -1; + + __raw_writel(AR71XX_SPI_FS_GPIO, base + AR71XX_SPI_REG_FS); + __raw_writel(SPI_CTRL_FASTEST, base + AR71XX_SPI_REG_CTRL); + do_spi_init(m->spi); + + list_for_each_entry(t, &m->transfers, transfer_list) { + int len; + + len = rb4xx_spi_txrx(base, t); + if (len != t->len) { + m->status = -EMSGSIZE; + break; + } + m->actual_length += len; + + if (t->cs_change) { + if (list_is_last(&t->transfer_list, &m->transfers)) { + /* wait for continuation */ + return m->spi->chip_select; + } + do_spi_finish(base); + ndelay(100); + } + } + + do_spi_finish(base); + __raw_writel(rbspi->spi_ctrl_flash, base + AR71XX_SPI_REG_CTRL); + __raw_writel(0, base + AR71XX_SPI_REG_FS); + return -1; +} + +static void rb4xx_spi_process_queue_locked(struct rb4xx_spi *rbspi, + unsigned long *flags) +{ + int cs = rbspi->cs_wait; + + rbspi->busy = 1; + while (!list_empty(&rbspi->queue)) { + struct spi_message *m; + + list_for_each_entry(m, &rbspi->queue, queue) + if (cs < 0 || cs == m->spi->chip_select) + break; + + if (&m->queue == &rbspi->queue) + break; + + list_del_init(&m->queue); + spin_unlock_irqrestore(&rbspi->lock, *flags); + + cs = rb4xx_spi_msg(rbspi, m); + m->complete(m->context); + + spin_lock_irqsave(&rbspi->lock, *flags); + } + + rbspi->cs_wait = cs; + rbspi->busy = 0; + + if (cs >= 0) { + /* TODO: add timer to unlock cs after 1s inactivity */ + } +} + +static int rb4xx_spi_transfer(struct spi_device *spi, + struct spi_message *m) +{ + struct rb4xx_spi *rbspi = spi_master_get_devdata(spi->master); + unsigned long flags; + + m->actual_length = 0; + m->status = -EINPROGRESS; + + spin_lock_irqsave(&rbspi->lock, flags); + list_add_tail(&m->queue, &rbspi->queue); + if (rbspi->busy || + (rbspi->cs_wait >= 0 && rbspi->cs_wait != m->spi->chip_select)) { + /* job will be done later */ + spin_unlock_irqrestore(&rbspi->lock, flags); + return 0; + } + + /* process job in current context */ + rb4xx_spi_process_queue_locked(rbspi, &flags); + spin_unlock_irqrestore(&rbspi->lock, flags); + + return 0; +} + +static int rb4xx_spi_setup(struct spi_device *spi) +{ + struct rb4xx_spi *rbspi = spi_master_get_devdata(spi->master); + unsigned long flags; + + if (spi->mode & ~(SPI_CS_HIGH)) { + dev_err(&spi->dev, "mode %x not supported\n", + (unsigned) spi->mode); + return -EINVAL; + } + + if (spi->bits_per_word != 8 && spi->bits_per_word != 0) { + dev_err(&spi->dev, "bits_per_word %u not supported\n", + (unsigned) spi->bits_per_word); + return -EINVAL; + } + + spin_lock_irqsave(&rbspi->lock, flags); + if (rbspi->cs_wait == spi->chip_select && !rbspi->busy) { + rbspi->cs_wait = -1; + rb4xx_spi_process_queue_locked(rbspi, &flags); + } + spin_unlock_irqrestore(&rbspi->lock, flags); + + return 0; +} + +static unsigned get_spi_ctrl(struct rb4xx_spi *rbspi, unsigned hz_max, + const char *name) +{ + unsigned div; + + div = (rbspi->ahb_freq - 1) / (2 * hz_max); + + /* + * CPU has a bug at (div == 0) - first bit read is random + */ + if (div == 0) + ++div; + + if (name) { + unsigned ahb_khz = (rbspi->ahb_freq + 500) / 1000; + unsigned div_real = 2 * (div + 1); + pr_debug("rb4xx: %s SPI clock %u kHz (AHB %u kHz / %u)\n", + name, + ahb_khz / div_real, + ahb_khz, div_real); + } + + return SPI_CTRL_FASTEST + div; +} + +static int rb4xx_spi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct rb4xx_spi *rbspi; + struct resource *r; + int err = 0; + + master = spi_alloc_master(&pdev->dev, sizeof(*rbspi)); + if (master == NULL) { + dev_err(&pdev->dev, "no memory for spi_master\n"); + err = -ENOMEM; + goto err_out; + } + + master->bus_num = 0; + master->num_chipselect = 3; + master->setup = rb4xx_spi_setup; + master->transfer = rb4xx_spi_transfer; + + rbspi = spi_master_get_devdata(master); + + rbspi->ahb_clk = clk_get(&pdev->dev, "ahb"); + if (IS_ERR(rbspi->ahb_clk)) { + err = PTR_ERR(rbspi->ahb_clk); + goto err_put_master; + } + + err = clk_enable(rbspi->ahb_clk); + if (err) + goto err_clk_put; + + rbspi->ahb_freq = clk_get_rate(rbspi->ahb_clk); + if (!rbspi->ahb_freq) { + err = -EINVAL; + goto err_clk_disable; + } + + platform_set_drvdata(pdev, rbspi); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + err = -ENOENT; + goto err_clk_disable; + } + + rbspi->base = ioremap(r->start, r->end - r->start + 1); + if (!rbspi->base) { + err = -ENXIO; + goto err_clk_disable; + } + + rbspi->master = master; + rbspi->spi_ctrl_flash = get_spi_ctrl(rbspi, SPI_FLASH_HZ, "FLASH"); + rbspi->spi_ctrl_fread = get_spi_ctrl(rbspi, SPI_CPLD_HZ, "CPLD"); + rbspi->cs_wait = -1; + + spin_lock_init(&rbspi->lock); + INIT_LIST_HEAD(&rbspi->queue); + + err = spi_register_master(master); + if (err) { + dev_err(&pdev->dev, "failed to register SPI master\n"); + goto err_iounmap; + } + + return 0; + +err_iounmap: + iounmap(rbspi->base); +err_clk_disable: + clk_disable(rbspi->ahb_clk); +err_clk_put: + clk_put(rbspi->ahb_clk); +err_put_master: + platform_set_drvdata(pdev, NULL); + spi_master_put(master); +err_out: + return err; +} + +static int rb4xx_spi_remove(struct platform_device *pdev) +{ + struct rb4xx_spi *rbspi = platform_get_drvdata(pdev); + + iounmap(rbspi->base); + clk_disable(rbspi->ahb_clk); + clk_put(rbspi->ahb_clk); + platform_set_drvdata(pdev, NULL); + spi_master_put(rbspi->master); + + return 0; +} + +static struct platform_driver rb4xx_spi_drv = { + .probe = rb4xx_spi_probe, + .remove = rb4xx_spi_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init rb4xx_spi_init(void) +{ + return platform_driver_register(&rb4xx_spi_drv); +} +subsys_initcall(rb4xx_spi_init); + +static void __exit rb4xx_spi_exit(void) +{ + platform_driver_unregister(&rb4xx_spi_drv); +} + +module_exit(rb4xx_spi_exit); + +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-vsc7385.c b/drivers/spi/spi-vsc7385.c new file mode 100644 index 0000000..9b3b60d5 --- /dev/null +++ b/drivers/spi/spi-vsc7385.c @@ -0,0 +1,620 @@ +/* + * SPI driver for the Vitesse VSC7385 ethernet switch + * + * Copyright (C) 2009 Gabor Juhos + * + * Parts of this file are based on Atheros' 2.6.15 BSP + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "spi-vsc7385" +#define DRV_DESC "Vitesse VSC7385 Gbit ethernet switch driver" +#define DRV_VERSION "0.1.0" + +#define VSC73XX_BLOCK_MAC 0x1 +#define VSC73XX_BLOCK_2 0x2 +#define VSC73XX_BLOCK_MII 0x3 +#define VSC73XX_BLOCK_4 0x4 +#define VSC73XX_BLOCK_5 0x5 +#define VSC73XX_BLOCK_SYSTEM 0x7 + +#define VSC73XX_SUBBLOCK_PORT_0 0 +#define VSC73XX_SUBBLOCK_PORT_1 1 +#define VSC73XX_SUBBLOCK_PORT_2 2 +#define VSC73XX_SUBBLOCK_PORT_3 3 +#define VSC73XX_SUBBLOCK_PORT_4 4 +#define VSC73XX_SUBBLOCK_PORT_MAC 6 + +/* MAC Block registers */ +#define VSC73XX_MAC_CFG 0x0 +#define VSC73XX_ADVPORTM 0x19 +#define VSC73XX_RXOCT 0x50 +#define VSC73XX_TXOCT 0x51 +#define VSC73XX_C_RX0 0x52 +#define VSC73XX_C_RX1 0x53 +#define VSC73XX_C_RX2 0x54 +#define VSC73XX_C_TX0 0x55 +#define VSC73XX_C_TX1 0x56 +#define VSC73XX_C_TX2 0x57 +#define VSC73XX_C_CFG 0x58 + +/* MAC_CFG register bits */ +#define VSC73XX_MAC_CFG_WEXC_DIS (1 << 31) +#define VSC73XX_MAC_CFG_PORT_RST (1 << 29) +#define VSC73XX_MAC_CFG_TX_EN (1 << 28) +#define VSC73XX_MAC_CFG_SEED_LOAD (1 << 27) +#define VSC73XX_MAC_CFG_FDX (1 << 18) +#define VSC73XX_MAC_CFG_GIGE (1 << 17) +#define VSC73XX_MAC_CFG_RX_EN (1 << 16) +#define VSC73XX_MAC_CFG_VLAN_DBLAWR (1 << 15) +#define VSC73XX_MAC_CFG_VLAN_AWR (1 << 14) +#define VSC73XX_MAC_CFG_100_BASE_T (1 << 13) +#define VSC73XX_MAC_CFG_TX_IPG(x) (((x) & 0x1f) << 6) +#define VSC73XX_MAC_CFG_MAC_RX_RST (1 << 5) +#define VSC73XX_MAC_CFG_MAC_TX_RST (1 << 4) +#define VSC73XX_MAC_CFG_BIT2 (1 << 2) +#define VSC73XX_MAC_CFG_CLK_SEL(x) ((x) & 0x3) + +/* ADVPORTM register bits */ +#define VSC73XX_ADVPORTM_IFG_PPM (1 << 7) +#define VSC73XX_ADVPORTM_EXC_COL_CONT (1 << 6) +#define VSC73XX_ADVPORTM_EXT_PORT (1 << 5) +#define VSC73XX_ADVPORTM_INV_GTX (1 << 4) +#define VSC73XX_ADVPORTM_ENA_GTX (1 << 3) +#define VSC73XX_ADVPORTM_DDR_MODE (1 << 2) +#define VSC73XX_ADVPORTM_IO_LOOPBACK (1 << 1) +#define VSC73XX_ADVPORTM_HOST_LOOPBACK (1 << 0) + +/* MII Block registers */ +#define VSC73XX_MII_STAT 0x0 +#define VSC73XX_MII_CMD 0x1 +#define VSC73XX_MII_DATA 0x2 + +/* System Block registers */ +#define VSC73XX_ICPU_SIPAD 0x01 +#define VSC73XX_ICPU_CLOCK_DELAY 0x05 +#define VSC73XX_ICPU_CTRL 0x10 +#define VSC73XX_ICPU_ADDR 0x11 +#define VSC73XX_ICPU_SRAM 0x12 +#define VSC73XX_ICPU_MBOX_VAL 0x15 +#define VSC73XX_ICPU_MBOX_SET 0x16 +#define VSC73XX_ICPU_MBOX_CLR 0x17 +#define VSC73XX_ICPU_CHIPID 0x18 +#define VSC73XX_ICPU_GPIO 0x34 + +#define VSC73XX_ICPU_CTRL_CLK_DIV (1 << 8) +#define VSC73XX_ICPU_CTRL_SRST_HOLD (1 << 7) +#define VSC73XX_ICPU_CTRL_BOOT_EN (1 << 3) +#define VSC73XX_ICPU_CTRL_EXT_ACC_EN (1 << 2) +#define VSC73XX_ICPU_CTRL_CLK_EN (1 << 1) +#define VSC73XX_ICPU_CTRL_SRST (1 << 0) + +#define VSC73XX_ICPU_CHIPID_ID_SHIFT 12 +#define VSC73XX_ICPU_CHIPID_ID_MASK 0xffff +#define VSC73XX_ICPU_CHIPID_REV_SHIFT 28 +#define VSC73XX_ICPU_CHIPID_REV_MASK 0xf +#define VSC73XX_ICPU_CHIPID_ID_7385 0x7385 +#define VSC73XX_ICPU_CHIPID_ID_7395 0x7395 + +#define VSC73XX_CMD_MODE_READ 0 +#define VSC73XX_CMD_MODE_WRITE 1 +#define VSC73XX_CMD_MODE_SHIFT 4 +#define VSC73XX_CMD_BLOCK_SHIFT 5 +#define VSC73XX_CMD_BLOCK_MASK 0x7 +#define VSC73XX_CMD_SUBBLOCK_MASK 0xf + +#define VSC7385_CLOCK_DELAY ((3 << 4) | 3) +#define VSC7385_CLOCK_DELAY_MASK ((3 << 4) | 3) + +#define VSC73XX_ICPU_CTRL_STOP (VSC73XX_ICPU_CTRL_SRST_HOLD | \ + VSC73XX_ICPU_CTRL_BOOT_EN | \ + VSC73XX_ICPU_CTRL_EXT_ACC_EN) + +#define VSC73XX_ICPU_CTRL_START (VSC73XX_ICPU_CTRL_CLK_DIV | \ + VSC73XX_ICPU_CTRL_BOOT_EN | \ + VSC73XX_ICPU_CTRL_CLK_EN | \ + VSC73XX_ICPU_CTRL_SRST) + +#define VSC7385_ADVPORTM_MASK (VSC73XX_ADVPORTM_IFG_PPM | \ + VSC73XX_ADVPORTM_EXC_COL_CONT | \ + VSC73XX_ADVPORTM_EXT_PORT | \ + VSC73XX_ADVPORTM_INV_GTX | \ + VSC73XX_ADVPORTM_ENA_GTX | \ + VSC73XX_ADVPORTM_DDR_MODE | \ + VSC73XX_ADVPORTM_IO_LOOPBACK | \ + VSC73XX_ADVPORTM_HOST_LOOPBACK) + +#define VSC7385_ADVPORTM_INIT (VSC73XX_ADVPORTM_EXT_PORT | \ + VSC73XX_ADVPORTM_ENA_GTX | \ + VSC73XX_ADVPORTM_DDR_MODE) + +#define VSC7385_MAC_CFG_RESET (VSC73XX_MAC_CFG_PORT_RST | \ + VSC73XX_MAC_CFG_MAC_RX_RST | \ + VSC73XX_MAC_CFG_MAC_TX_RST) + +#define VSC73XX_MAC_CFG_INIT (VSC73XX_MAC_CFG_TX_EN | \ + VSC73XX_MAC_CFG_FDX | \ + VSC73XX_MAC_CFG_GIGE | \ + VSC73XX_MAC_CFG_RX_EN) + +#define VSC73XX_RESET_DELAY 100 + +struct vsc7385 { + struct spi_device *spi; + struct mutex lock; + struct vsc7385_platform_data *pdata; +}; + +static int vsc7385_is_addr_valid(u8 block, u8 subblock) +{ + switch (block) { + case VSC73XX_BLOCK_MAC: + switch (subblock) { + case 0 ... 4: + case 6: + return 1; + } + break; + + case VSC73XX_BLOCK_2: + case VSC73XX_BLOCK_SYSTEM: + switch (subblock) { + case 0: + return 1; + } + break; + + case VSC73XX_BLOCK_MII: + case VSC73XX_BLOCK_4: + case VSC73XX_BLOCK_5: + switch (subblock) { + case 0 ... 1: + return 1; + } + break; + } + + return 0; +} + +static inline u8 vsc7385_make_addr(u8 mode, u8 block, u8 subblock) +{ + u8 ret; + + ret = (block & VSC73XX_CMD_BLOCK_MASK) << VSC73XX_CMD_BLOCK_SHIFT; + ret |= (mode & 1) << VSC73XX_CMD_MODE_SHIFT; + ret |= subblock & VSC73XX_CMD_SUBBLOCK_MASK; + + return ret; +} + +static int vsc7385_read(struct vsc7385 *vsc, u8 block, u8 subblock, u8 reg, + u32 *value) +{ + u8 cmd[4]; + u8 buf[4]; + struct spi_transfer t[2]; + struct spi_message m; + int err; + + if (!vsc7385_is_addr_valid(block, subblock)) + return -EINVAL; + + spi_message_init(&m); + + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = sizeof(buf); + spi_message_add_tail(&t[1], &m); + + cmd[0] = vsc7385_make_addr(VSC73XX_CMD_MODE_READ, block, subblock); + cmd[1] = reg; + cmd[2] = 0; + cmd[3] = 0; + + mutex_lock(&vsc->lock); + err = spi_sync(vsc->spi, &m); + mutex_unlock(&vsc->lock); + + if (err) + return err; + + *value = (((u32) buf[0]) << 24) | (((u32) buf[1]) << 16) | + (((u32) buf[2]) << 8) | ((u32) buf[3]); + + return 0; +} + + +static int vsc7385_write(struct vsc7385 *vsc, u8 block, u8 subblock, u8 reg, + u32 value) +{ + u8 cmd[2]; + u8 buf[4]; + struct spi_transfer t[2]; + struct spi_message m; + int err; + + if (!vsc7385_is_addr_valid(block, subblock)) + return -EINVAL; + + spi_message_init(&m); + + memset(&t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(cmd); + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = buf; + t[1].len = sizeof(buf); + spi_message_add_tail(&t[1], &m); + + cmd[0] = vsc7385_make_addr(VSC73XX_CMD_MODE_WRITE, block, subblock); + cmd[1] = reg; + + buf[0] = (value >> 24) & 0xff; + buf[1] = (value >> 16) & 0xff; + buf[2] = (value >> 8) & 0xff; + buf[3] = value & 0xff; + + mutex_lock(&vsc->lock); + err = spi_sync(vsc->spi, &m); + mutex_unlock(&vsc->lock); + + return err; +} + +static inline int vsc7385_write_verify(struct vsc7385 *vsc, u8 block, + u8 subblock, u8 reg, u32 value, + u32 read_mask, u32 read_val) +{ + struct spi_device *spi = vsc->spi; + u32 t; + int err; + + err = vsc7385_write(vsc, block, subblock, reg, value); + if (err) + return err; + + err = vsc7385_read(vsc, block, subblock, reg, &t); + if (err) + return err; + + if ((t & read_mask) != read_val) { + dev_err(&spi->dev, "register write error\n"); + return -EIO; + } + + return 0; +} + +static inline int vsc7385_set_clock_delay(struct vsc7385 *vsc, u32 val) +{ + return vsc7385_write(vsc, VSC73XX_BLOCK_SYSTEM, 0, + VSC73XX_ICPU_CLOCK_DELAY, val); +} + +static inline int vsc7385_get_clock_delay(struct vsc7385 *vsc, u32 *val) +{ + return vsc7385_read(vsc, VSC73XX_BLOCK_SYSTEM, 0, + VSC73XX_ICPU_CLOCK_DELAY, val); +} + +static inline int vsc7385_icpu_stop(struct vsc7385 *vsc) +{ + return vsc7385_write(vsc, VSC73XX_BLOCK_SYSTEM, 0, VSC73XX_ICPU_CTRL, + VSC73XX_ICPU_CTRL_STOP); +} + +static inline int vsc7385_icpu_start(struct vsc7385 *vsc) +{ + return vsc7385_write(vsc, VSC73XX_BLOCK_SYSTEM, 0, VSC73XX_ICPU_CTRL, + VSC73XX_ICPU_CTRL_START); +} + +static inline int vsc7385_icpu_reset(struct vsc7385 *vsc) +{ + int rc; + + rc = vsc7385_write(vsc, VSC73XX_BLOCK_SYSTEM, 0, VSC73XX_ICPU_ADDR, + 0x0000); + if (rc) + dev_err(&vsc->spi->dev, + "could not reset microcode, err=%d\n", rc); + + return rc; +} + +static int vsc7385_upload_ucode(struct vsc7385 *vsc) +{ + struct spi_device *spi = vsc->spi; + const struct firmware *firmware; + char *ucode_name; + unsigned char *dp; + unsigned int curVal; + int i; + int diffs; + int rc; + + ucode_name = (vsc->pdata->ucode_name) ? vsc->pdata->ucode_name + : "vsc7385_ucode.bin"; + rc = request_firmware(&firmware, ucode_name, &spi->dev); + if (rc) { + dev_err(&spi->dev, "request_firmware failed, err=%d\n", + rc); + return rc; + } + + rc = vsc7385_icpu_stop(vsc); + if (rc) + goto out; + + rc = vsc7385_icpu_reset(vsc); + if (rc) + goto out; + + dev_info(&spi->dev, "uploading microcode...\n"); + + dp = (unsigned char *) firmware->data; + for (i = 0; i < firmware->size; i++) { + rc = vsc7385_write(vsc, VSC73XX_BLOCK_SYSTEM, 0, + VSC73XX_ICPU_SRAM, *dp++); + if (rc) { + dev_err(&spi->dev, "could not load microcode, err=%d\n", + rc); + goto out; + } + } + + rc = vsc7385_icpu_reset(vsc); + if (rc) + goto out; + + dev_info(&spi->dev, "verifying microcode...\n"); + + dp = (unsigned char *) firmware->data; + diffs = 0; + for (i = 0; i < firmware->size; i++) { + rc = vsc7385_read(vsc, VSC73XX_BLOCK_SYSTEM, 0, + VSC73XX_ICPU_SRAM, &curVal); + if (rc) { + dev_err(&spi->dev, "could not read microcode %d\n", + rc); + goto out; + } + + if (curVal > 0xff) { + dev_err(&spi->dev, "bad val read: %04x : %02x %02x\n", + i, *dp, curVal); + rc = -EIO; + goto out; + } + + if ((curVal & 0xff) != *dp) { + diffs++; + dev_err(&spi->dev, "verify error: %04x : %02x %02x\n", + i, *dp, curVal); + + if (diffs > 4) + break; + } + dp++; + } + + if (diffs) { + dev_err(&spi->dev, "microcode verification failed\n"); + rc = -EIO; + goto out; + } + + dev_info(&spi->dev, "microcode uploaded\n"); + + rc = vsc7385_icpu_start(vsc); + +out: + release_firmware(firmware); + return rc; +} + +static int vsc7385_setup(struct vsc7385 *vsc) +{ + struct vsc7385_platform_data *pdata = vsc->pdata; + u32 t; + int err; + + err = vsc7385_write_verify(vsc, VSC73XX_BLOCK_SYSTEM, 0, + VSC73XX_ICPU_CLOCK_DELAY, + VSC7385_CLOCK_DELAY, + VSC7385_CLOCK_DELAY_MASK, + VSC7385_CLOCK_DELAY); + if (err) + goto err; + + err = vsc7385_write_verify(vsc, VSC73XX_BLOCK_MAC, + VSC73XX_SUBBLOCK_PORT_MAC, VSC73XX_ADVPORTM, + VSC7385_ADVPORTM_INIT, + VSC7385_ADVPORTM_MASK, + VSC7385_ADVPORTM_INIT); + if (err) + goto err; + + err = vsc7385_write(vsc, VSC73XX_BLOCK_MAC, VSC73XX_SUBBLOCK_PORT_MAC, + VSC73XX_MAC_CFG, VSC7385_MAC_CFG_RESET); + if (err) + goto err; + + t = VSC73XX_MAC_CFG_INIT; + t |= VSC73XX_MAC_CFG_TX_IPG(pdata->mac_cfg.tx_ipg); + t |= VSC73XX_MAC_CFG_CLK_SEL(pdata->mac_cfg.clk_sel); + if (pdata->mac_cfg.bit2) + t |= VSC73XX_MAC_CFG_BIT2; + + err = vsc7385_write(vsc, VSC73XX_BLOCK_MAC, VSC73XX_SUBBLOCK_PORT_MAC, + VSC73XX_MAC_CFG, t); + if (err) + goto err; + + return 0; + +err: + return err; +} + +static int vsc7385_detect(struct vsc7385 *vsc) +{ + struct spi_device *spi = vsc->spi; + u32 t; + u32 id; + u32 rev; + int err; + + err = vsc7385_read(vsc, VSC73XX_BLOCK_SYSTEM, 0, + VSC73XX_ICPU_MBOX_VAL, &t); + if (err) { + dev_err(&spi->dev, "unable to read mailbox, err=%d\n", err); + return err; + } + + if (t == 0xffffffff) { + dev_dbg(&spi->dev, "assert chip reset\n"); + if (vsc->pdata->reset) + vsc->pdata->reset(); + + } + + err = vsc7385_read(vsc, VSC73XX_BLOCK_SYSTEM, 0, + VSC73XX_ICPU_CHIPID, &t); + if (err) { + dev_err(&spi->dev, "unable to read chip id, err=%d\n", err); + return err; + } + + id = (t >> VSC73XX_ICPU_CHIPID_ID_SHIFT) & VSC73XX_ICPU_CHIPID_ID_MASK; + switch (id) { + case VSC73XX_ICPU_CHIPID_ID_7385: + case VSC73XX_ICPU_CHIPID_ID_7395: + break; + default: + dev_err(&spi->dev, "unsupported chip, id=%04x\n", id); + return -ENODEV; + } + + rev = (t >> VSC73XX_ICPU_CHIPID_REV_SHIFT) & + VSC73XX_ICPU_CHIPID_REV_MASK; + dev_info(&spi->dev, "VSC%04X (rev. %d) switch found\n", id, rev); + + return 0; +} + +static int __devinit vsc7385_probe(struct spi_device *spi) +{ + struct vsc7385 *vsc; + struct vsc7385_platform_data *pdata; + int err; + + printk(KERN_INFO DRV_DESC " version " DRV_VERSION"\n"); + + pdata = spi->dev.platform_data; + if (!pdata) { + dev_err(&spi->dev, "no platform data specified\n"); + return -ENODEV; + } + + vsc = kzalloc(sizeof(*vsc), GFP_KERNEL); + if (!vsc) { + dev_err(&spi->dev, "no memory for private data\n"); + return -ENOMEM; + } + + mutex_init(&vsc->lock); + vsc->pdata = pdata; + vsc->spi = spi_dev_get(spi); + dev_set_drvdata(&spi->dev, vsc); + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + err = spi_setup(spi); + if (err) { + dev_err(&spi->dev, "spi_setup failed, err=%d\n", err); + goto err_drvdata; + } + + err = vsc7385_detect(vsc); + if (err) { + dev_err(&spi->dev, "no chip found, err=%d\n", err); + goto err_drvdata; + } + + err = vsc7385_upload_ucode(vsc); + if (err) + goto err_drvdata; + + err = vsc7385_setup(vsc); + if (err) + goto err_drvdata; + + return 0; + +err_drvdata: + dev_set_drvdata(&spi->dev, NULL); + kfree(vsc); + return err; +} + +static int __devexit vsc7385_remove(struct spi_device *spi) +{ + struct vsc7385_data *vsc; + + vsc = dev_get_drvdata(&spi->dev); + dev_set_drvdata(&spi->dev, NULL); + kfree(vsc); + + return 0; +} + +static struct spi_driver vsc7385_driver = { + .driver = { + .name = DRV_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = vsc7385_probe, + .remove = __devexit_p(vsc7385_remove), +}; + +static int __init vsc7385_init(void) +{ + return spi_register_driver(&vsc7385_driver); +} +module_init(vsc7385_init); + +static void __exit vsc7385_exit(void) +{ + spi_unregister_driver(&vsc7385_driver); +} +module_exit(vsc7385_exit); + +MODULE_DESCRIPTION(DRV_DESC); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/net/dsa/mv88e6063.c b/net/dsa/mv88e6063.c new file mode 100644 index 0000000..5638a9f --- /dev/null +++ b/net/dsa/mv88e6063.c @@ -0,0 +1,294 @@ +/* + * net/dsa/mv88e6063.c - Driver for Marvell 88e6063 switch chips + * Copyright (c) 2009 Gabor Juhos + * + * This driver was base on: net/dsa/mv88e6060.c + * net/dsa/mv88e6063.c - Driver for Marvell 88e6060 switch chips + * Copyright (c) 2008-2009 Marvell Semiconductor + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include "dsa_priv.h" + +#define REG_BASE 0x10 +#define REG_PHY(p) (REG_BASE + (p)) +#define REG_PORT(p) (REG_BASE + 8 + (p)) +#define REG_GLOBAL (REG_BASE + 0x0f) +#define NUM_PORTS 7 + +static int reg_read(struct dsa_switch *ds, int addr, int reg) +{ + return mdiobus_read(ds->master_mii_bus, addr, reg); +} + +#define REG_READ(addr, reg) \ + ({ \ + int __ret; \ + \ + __ret = reg_read(ds, addr, reg); \ + if (__ret < 0) \ + return __ret; \ + __ret; \ + }) + + +static int reg_write(struct dsa_switch *ds, int addr, int reg, u16 val) +{ + return mdiobus_write(ds->master_mii_bus, addr, reg, val); +} + +#define REG_WRITE(addr, reg, val) \ + ({ \ + int __ret; \ + \ + __ret = reg_write(ds, addr, reg, val); \ + if (__ret < 0) \ + return __ret; \ + }) + +static char *mv88e6063_probe(struct mii_bus *bus, int sw_addr) +{ + int ret; + + ret = mdiobus_read(bus, REG_PORT(0), 0x03); + if (ret >= 0) { + ret &= 0xfff0; + if (ret == 0x1530) + return "Marvell 88E6063"; + } + + return NULL; +} + +static int mv88e6063_switch_reset(struct dsa_switch *ds) +{ + int i; + int ret; + + /* + * Set all ports to the disabled state. + */ + for (i = 0; i < NUM_PORTS; i++) { + ret = REG_READ(REG_PORT(i), 0x04); + REG_WRITE(REG_PORT(i), 0x04, ret & 0xfffc); + } + + /* + * Wait for transmit queues to drain. + */ + msleep(2); + + /* + * Reset the switch. + */ + REG_WRITE(REG_GLOBAL, 0x0a, 0xa130); + + /* + * Wait up to one second for reset to complete. + */ + for (i = 0; i < 1000; i++) { + ret = REG_READ(REG_GLOBAL, 0x00); + if ((ret & 0x8000) == 0x0000) + break; + + msleep(1); + } + if (i == 1000) + return -ETIMEDOUT; + + return 0; +} + +static int mv88e6063_setup_global(struct dsa_switch *ds) +{ + /* + * Disable discarding of frames with excessive collisions, + * set the maximum frame size to 1536 bytes, and mask all + * interrupt sources. + */ + REG_WRITE(REG_GLOBAL, 0x04, 0x0800); + + /* + * Enable automatic address learning, set the address + * database size to 1024 entries, and set the default aging + * time to 5 minutes. + */ + REG_WRITE(REG_GLOBAL, 0x0a, 0x2130); + + return 0; +} + +static int mv88e6063_setup_port(struct dsa_switch *ds, int p) +{ + int addr = REG_PORT(p); + + /* + * Do not force flow control, disable Ingress and Egress + * Header tagging, disable VLAN tunneling, and set the port + * state to Forwarding. Additionally, if this is the CPU + * port, enable Ingress and Egress Trailer tagging mode. + */ + REG_WRITE(addr, 0x04, dsa_is_cpu_port(ds, p) ? 0x4103 : 0x0003); + + /* + * Port based VLAN map: give each port its own address + * database, allow the CPU port to talk to each of the 'real' + * ports, and allow each of the 'real' ports to only talk to + * the CPU port. + */ + REG_WRITE(addr, 0x06, + ((p & 0xf) << 12) | + (dsa_is_cpu_port(ds, p) ? + ds->phys_port_mask : + (1 << ds->dst->cpu_port))); + + /* + * Port Association Vector: when learning source addresses + * of packets, add the address to the address database using + * a port bitmap that has only the bit for this port set and + * the other bits clear. + */ + REG_WRITE(addr, 0x0b, 1 << p); + + return 0; +} + +static int mv88e6063_setup(struct dsa_switch *ds) +{ + int i; + int ret; + + ret = mv88e6063_switch_reset(ds); + if (ret < 0) + return ret; + + /* @@@ initialise atu */ + + ret = mv88e6063_setup_global(ds); + if (ret < 0) + return ret; + + for (i = 0; i < NUM_PORTS; i++) { + ret = mv88e6063_setup_port(ds, i); + if (ret < 0) + return ret; + } + + return 0; +} + +static int mv88e6063_set_addr(struct dsa_switch *ds, u8 *addr) +{ + REG_WRITE(REG_GLOBAL, 0x01, (addr[0] << 8) | addr[1]); + REG_WRITE(REG_GLOBAL, 0x02, (addr[2] << 8) | addr[3]); + REG_WRITE(REG_GLOBAL, 0x03, (addr[4] << 8) | addr[5]); + + return 0; +} + +static int mv88e6063_port_to_phy_addr(int port) +{ + if (port >= 0 && port <= NUM_PORTS) + return REG_PHY(port); + return -1; +} + +static int mv88e6063_phy_read(struct dsa_switch *ds, int port, int regnum) +{ + int addr; + + addr = mv88e6063_port_to_phy_addr(port); + if (addr == -1) + return 0xffff; + + return reg_read(ds, addr, regnum); +} + +static int +mv88e6063_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val) +{ + int addr; + + addr = mv88e6063_port_to_phy_addr(port); + if (addr == -1) + return 0xffff; + + return reg_write(ds, addr, regnum, val); +} + +static void mv88e6063_poll_link(struct dsa_switch *ds) +{ + int i; + + for (i = 0; i < DSA_MAX_PORTS; i++) { + struct net_device *dev; + int uninitialized_var(port_status); + int link; + int speed; + int duplex; + int fc; + + dev = ds->ports[i]; + if (dev == NULL) + continue; + + link = 0; + if (dev->flags & IFF_UP) { + port_status = reg_read(ds, REG_PORT(i), 0x00); + if (port_status < 0) + continue; + + link = !!(port_status & 0x1000); + } + + if (!link) { + if (netif_carrier_ok(dev)) { + printk(KERN_INFO "%s: link down\n", dev->name); + netif_carrier_off(dev); + } + continue; + } + + speed = (port_status & 0x0100) ? 100 : 10; + duplex = (port_status & 0x0200) ? 1 : 0; + fc = ((port_status & 0xc000) == 0xc000) ? 1 : 0; + + if (!netif_carrier_ok(dev)) { + printk(KERN_INFO "%s: link up, %d Mb/s, %s duplex, " + "flow control %sabled\n", dev->name, + speed, duplex ? "full" : "half", + fc ? "en" : "dis"); + netif_carrier_on(dev); + } + } +} + +static struct dsa_switch_driver mv88e6063_switch_driver = { + .tag_protocol = htons(ETH_P_TRAILER), + .probe = mv88e6063_probe, + .setup = mv88e6063_setup, + .set_addr = mv88e6063_set_addr, + .phy_read = mv88e6063_phy_read, + .phy_write = mv88e6063_phy_write, + .poll_link = mv88e6063_poll_link, +}; + +static int __init mv88e6063_init(void) +{ + register_switch_driver(&mv88e6063_switch_driver); + return 0; +} +module_init(mv88e6063_init); + +static void __exit mv88e6063_cleanup(void) +{ + unregister_switch_driver(&mv88e6063_switch_driver); +} +module_exit(mv88e6063_cleanup); -- 1.7.9.7