diff options
author | blogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-05 10:12:53 +0000 |
---|---|---|
committer | blogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-05 10:12:53 +0000 |
commit | 5c105d9f3fd086aff195d3849dcf847d6b0bd927 (patch) | |
tree | 1229a11f725bfa58aa7c57a76898553bb5f6654a /target/linux/generic/files/drivers | |
download | openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.tar.gz openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.zip |
branch Attitude Adjustment
git-svn-id: svn://svn.openwrt.org/openwrt/branches/attitude_adjustment@33625 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/generic/files/drivers')
27 files changed, 16177 insertions, 0 deletions
diff --git a/target/linux/generic/files/drivers/char/gpio_dev.c b/target/linux/generic/files/drivers/char/gpio_dev.c new file mode 100644 index 000000000..c74157302 --- /dev/null +++ b/target/linux/generic/files/drivers/char/gpio_dev.c @@ -0,0 +1,181 @@ +/* + * character device wrapper for generic gpio layer + * + * 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., 59 Temple Place, Suite 330, Boston, MA02111-1307USA + * + * Feedback, Bugs... blogic@openwrt.org + * + * dpg 20100106 + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/gpio.h> +#include <asm/atomic.h> +#include <linux/init.h> +#include <linux/genhd.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/gpio_dev.h> +#include <linux/fs.h> + +#define DRVNAME "gpiodev" +#define DEVNAME "gpio" + +static int dev_major; +static struct class *gpiodev_class; + + +/* third argument of user space ioctl ('arg' here) contains the <pin> */ +static int +gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + + switch (cmd) + { + case GPIO_GET: + retval = gpio_get_value(arg); + break; + case GPIO_SET: + gpio_set_value(arg, 1); + break; + case GPIO_CLEAR: + gpio_set_value(arg, 0); + break; + case GPIO_DIR_IN: + retval = gpio_direction_input(arg); + break; + case GPIO_DIR_OUT: + retval = gpio_direction_output(arg, 0); + break; + case GPIO_DIR_HIGH: + retval = gpio_direction_output(arg, 1); + break; + case GPIO_REQUEST: + /* should be first ioctl operation on <pin> */ + retval = gpio_request(arg, DRVNAME); + break; + case GPIO_FREE: + /* should be last ioctl operation on <pin> */ + /* may be needed first if previous user missed this ioctl */ + gpio_free(arg); + break; + case GPIO_CAN_SLEEP: + retval = gpio_cansleep(arg); + break; + default: + retval = -EINVAL; + /* = -ENOTTY; // correct return but ... */ + break; + } + return retval; +} + +/* Allow co-incident opens */ +static int +gpio_open(struct inode *inode, struct file *file) +{ + int result = 0; + unsigned int dev_minor = MINOR(inode->i_rdev); + + if (dev_minor != 0) + { + printk(KERN_ERR DRVNAME ": trying to access unknown minor device -> %d\n", dev_minor); + result = -ENODEV; + goto out; + } +out: + return result; +} + +static int +gpio_close(struct inode * inode, struct file * file) +{ + /* could track all <pin>s requested by this fd and gpio_free() + * them here + */ + return 0; +} + +struct file_operations gpio_fops = { + unlocked_ioctl: gpio_ioctl, + open: gpio_open, + release: gpio_close +}; + +static int +gpio_probe(struct platform_device *dev) +{ + int result = 0; + + dev_major = register_chrdev(0, DEVNAME, &gpio_fops); + if (!dev_major) + { + printk(KERN_ERR DRVNAME ": Error whilst opening %s \n", DEVNAME); + result = -ENODEV; + goto out; + } + gpiodev_class = class_create(THIS_MODULE, DRVNAME); + device_create(gpiodev_class, NULL, MKDEV(dev_major, 0), dev, DEVNAME); + printk(KERN_INFO DRVNAME ": gpio device registered with major %d\n", dev_major); +out: + return result; +} + +static int +gpio_remove(struct platform_device *dev) +{ + device_destroy(gpiodev_class, MKDEV(dev_major, 0)); + class_destroy(gpiodev_class); + unregister_chrdev(dev_major, DEVNAME); + return 0; +} + +static struct +platform_driver gpio_driver = { + .probe = gpio_probe, + .remove = gpio_remove, + .driver = { + .name = "GPIODEV", + .owner = THIS_MODULE, + }, +}; + +static int __init +gpio_mod_init(void) +{ + int ret = platform_driver_register(&gpio_driver); + if (ret) + printk(KERN_INFO DRVNAME ": Error registering platfom driver!\n"); + + return ret; +} + +static void __exit +gpio_mod_exit(void) +{ + platform_driver_unregister(&gpio_driver); +} + +module_init (gpio_mod_init); +module_exit (gpio_mod_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("John Crispin / OpenWrt +"); +MODULE_DESCRIPTION("Character device for for generic gpio api"); diff --git a/target/linux/generic/files/drivers/input/misc/gpio_buttons.c b/target/linux/generic/files/drivers/input/misc/gpio_buttons.c new file mode 100644 index 000000000..51288a3f9 --- /dev/null +++ b/target/linux/generic/files/drivers/input/misc/gpio_buttons.c @@ -0,0 +1,232 @@ +/* + * Driver for buttons on GPIO lines not capable of generating interrupts + * + * Copyright (C) 2007-2010 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2010 Nuno Goncalves <nunojpg@gmail.com> + * + * This file was based on: /drivers/input/misc/cobalt_btns.c + * Copyright (C) 2007 Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp> + * + * also was based on: /drivers/input/keyboard/gpio_keys.c + * Copyright 2005 Phil Blundell + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/gpio_buttons.h> + +#define DRV_NAME "gpio-buttons" + +struct gpio_button_data { + int last_state; + int count; + int can_sleep; +}; + +struct gpio_buttons_dev { + struct input_polled_dev *poll_dev; + struct gpio_buttons_platform_data *pdata; + struct gpio_button_data *data; +}; + +static void gpio_buttons_check_state(struct input_dev *input, + struct gpio_button *button, + struct gpio_button_data *bdata) +{ + int state; + + if (bdata->can_sleep) + state = !!gpio_get_value_cansleep(button->gpio); + else + state = !!gpio_get_value(button->gpio); + + if (state != bdata->last_state) { + unsigned int type = button->type ?: EV_KEY; + + input_event(input, type, button->code, + !!(state ^ button->active_low)); + input_sync(input); + bdata->count = 0; + bdata->last_state = state; + } +} + +static void gpio_buttons_poll(struct input_polled_dev *dev) +{ + struct gpio_buttons_dev *bdev = dev->private; + struct gpio_buttons_platform_data *pdata = bdev->pdata; + struct input_dev *input = dev->input; + int i; + + for (i = 0; i < bdev->pdata->nbuttons; i++) { + struct gpio_button *button = &pdata->buttons[i]; + struct gpio_button_data *bdata = &bdev->data[i]; + + if (bdata->count < button->threshold) + bdata->count++; + else + gpio_buttons_check_state(input, button, bdata); + + } +} + +static int __devinit gpio_buttons_probe(struct platform_device *pdev) +{ + struct gpio_buttons_platform_data *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct gpio_buttons_dev *bdev; + struct input_polled_dev *poll_dev; + struct input_dev *input; + int error; + int i; + + if (!pdata) + return -ENXIO; + + bdev = kzalloc(sizeof(struct gpio_buttons_dev) + + pdata->nbuttons * sizeof(struct gpio_button_data), + GFP_KERNEL); + if (!bdev) { + dev_err(dev, "no memory for private data\n"); + return -ENOMEM; + } + + bdev->data = (struct gpio_button_data *) &bdev[1]; + + poll_dev = input_allocate_polled_device(); + if (!poll_dev) { + dev_err(dev, "no memory for polled device\n"); + error = -ENOMEM; + goto err_free_bdev; + } + + poll_dev->private = bdev; + poll_dev->poll = gpio_buttons_poll; + poll_dev->poll_interval = pdata->poll_interval; + + input = poll_dev->input; + + input->evbit[0] = BIT(EV_KEY); + input->name = pdev->name; + input->phys = "gpio-buttons/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_button *button = &pdata->buttons[i]; + unsigned int gpio = button->gpio; + unsigned int type = button->type ?: EV_KEY; + + error = gpio_request(gpio, + button->desc ? button->desc : DRV_NAME); + if (error) { + dev_err(dev, "unable to claim gpio %u, err=%d\n", + gpio, error); + goto err_free_gpio; + } + + error = gpio_direction_input(gpio); + if (error) { + dev_err(dev, + "unable to set direction on gpio %u, err=%d\n", + gpio, error); + goto err_free_gpio; + } + + bdev->data[i].can_sleep = gpio_cansleep(gpio); + bdev->data[i].last_state = -1; + + input_set_capability(input, type, button->code); + } + + bdev->poll_dev = poll_dev; + bdev->pdata = pdata; + platform_set_drvdata(pdev, bdev); + + error = input_register_polled_device(poll_dev); + if (error) { + dev_err(dev, "unable to register polled device, err=%d\n", + error); + goto err_free_gpio; + } + + /* report initial state of the buttons */ + for (i = 0; i < pdata->nbuttons; i++) + gpio_buttons_check_state(input, &pdata->buttons[i], + &bdev->data[i]); + + return 0; + +err_free_gpio: + for (i = i - 1; i >= 0; i--) + gpio_free(pdata->buttons[i].gpio); + + input_free_polled_device(poll_dev); + +err_free_bdev: + kfree(bdev); + + platform_set_drvdata(pdev, NULL); + return error; +} + +static int __devexit gpio_buttons_remove(struct platform_device *pdev) +{ + struct gpio_buttons_dev *bdev = platform_get_drvdata(pdev); + struct gpio_buttons_platform_data *pdata = bdev->pdata; + int i; + + input_unregister_polled_device(bdev->poll_dev); + + for (i = 0; i < pdata->nbuttons; i++) + gpio_free(pdata->buttons[i].gpio); + + input_free_polled_device(bdev->poll_dev); + + kfree(bdev); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver gpio_buttons_driver = { + .probe = gpio_buttons_probe, + .remove = __devexit_p(gpio_buttons_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init gpio_buttons_init(void) +{ + return platform_driver_register(&gpio_buttons_driver); +} + +static void __exit gpio_buttons_exit(void) +{ + platform_driver_unregister(&gpio_buttons_driver); +} + +module_init(gpio_buttons_init); +module_exit(gpio_buttons_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_DESCRIPTION("Polled GPIO Buttons driver"); diff --git a/target/linux/generic/files/drivers/leds/ledtrig-morse.c b/target/linux/generic/files/drivers/leds/ledtrig-morse.c new file mode 100644 index 000000000..bc58afe4c --- /dev/null +++ b/target/linux/generic/files/drivers/leds/ledtrig-morse.c @@ -0,0 +1,366 @@ +/* + * LED Morse Trigger + * + * Copyright (C) 2007 Gabor Juhos <juhosg at openwrt.org> + * + * This file was based on: drivers/led/ledtrig-timer.c + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * also based on the patch '[PATCH] 2.5.59 morse code panics' posted + * in the LKML by Tomas Szepe at Thu, 30 Jan 2003 + * Copyright (C) 2002 Andrew Rodland <arodland@noln.com> + * Copyright (C) 2003 Tomas Szepe <szepe@pinerecords.com> + * + * 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 <linux/kernel.h> +#include <linux/version.h> +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/timer.h> +#include <linux/ctype.h> +#include <linux/leds.h> +#include <linux/slab.h> + +#include "leds.h" + +#define MORSE_DELAY_BASE (HZ/2) + +#define MORSE_STATE_BLINK_START 0 +#define MORSE_STATE_BLINK_STOP 1 + +#define MORSE_DIT_LEN 1 +#define MORSE_DAH_LEN 3 +#define MORSE_SPACE_LEN 7 + +struct morse_trig_data { + unsigned long delay; + char *msg; + + unsigned char morse; + unsigned char state; + char *msgpos; + struct timer_list timer; +}; + +const unsigned char morsetable[] = { + 0122, 0, 0310, 0, 0, 0163, /* "#$%&' */ + 055, 0155, 0, 0, 0163, 0141, 0152, 0051, /* ()*+,-./ */ + 077, 076, 074, 070, 060, 040, 041, 043, 047, 057, /* 0-9 */ + 0107, 0125, 0, 0061, 0, 0114, 0, /* :;<=>?@ */ + 006, 021, 025, 011, 002, 024, 013, 020, 004, /* A-I */ + 036, 015, 022, 007, 005, 017, 026, 033, 012, /* J-R */ + 010, 003, 014, 030, 016, 031, 035, 023, /* S-Z */ + 0, 0, 0, 0, 0154 /* [\]^_ */ +}; + +static inline unsigned char tomorse(char c) { + if (c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + if (c >= '"' && c <= '_') { + return morsetable[c - '"']; + } else + return 0; +} + +static inline unsigned long dit_len(struct morse_trig_data *morse_data) +{ + return MORSE_DIT_LEN*morse_data->delay; +} + +static inline unsigned long dah_len(struct morse_trig_data *morse_data) +{ + return MORSE_DAH_LEN*morse_data->delay; +} + +static inline unsigned long space_len(struct morse_trig_data *morse_data) +{ + return MORSE_SPACE_LEN*morse_data->delay; +} + +static void morse_timer_function(unsigned long data) +{ + struct led_classdev *led_cdev = (struct led_classdev *)data; + struct morse_trig_data *morse_data = led_cdev->trigger_data; + unsigned long brightness = LED_OFF; + unsigned long delay = 0; + + if (!morse_data->msg) + goto set_led; + + switch (morse_data->state) { + case MORSE_STATE_BLINK_START: + /* Starting a new blink. We have a valid code in morse. */ + delay = (morse_data->morse & 001) ? dah_len(morse_data): + dit_len(morse_data); + brightness = LED_FULL; + morse_data->state = MORSE_STATE_BLINK_STOP; + morse_data->morse >>= 1; + break; + case MORSE_STATE_BLINK_STOP: + /* Coming off of a blink. */ + morse_data->state = MORSE_STATE_BLINK_START; + + if (morse_data->morse > 1) { + /* Not done yet, just a one-dit pause. */ + delay = dit_len(morse_data); + break; + } + + /* Get a new char, figure out how much space. */ + /* First time through */ + if (!morse_data->msgpos) + morse_data->msgpos = (char *)morse_data->msg; + + if (!*morse_data->msgpos) { + /* Repeating */ + morse_data->msgpos = (char *)morse_data->msg; + delay = space_len(morse_data); + } else { + /* Inter-letter space */ + delay = dah_len(morse_data); + } + + if (!(morse_data->morse = tomorse(*morse_data->msgpos))) { + delay = space_len(morse_data); + /* And get us back here */ + morse_data->state = MORSE_STATE_BLINK_STOP; + } + morse_data->msgpos++; + break; + } + + mod_timer(&morse_data->timer, jiffies + msecs_to_jiffies(delay)); + +set_led: + led_set_brightness(led_cdev, brightness); +} + +static ssize_t _morse_delay_show(struct led_classdev *led_cdev, char *buf) +{ + struct morse_trig_data *morse_data = led_cdev->trigger_data; + + sprintf(buf, "%lu\n", morse_data->delay); + + return strlen(buf) + 1; +} + +static ssize_t _morse_delay_store(struct led_classdev *led_cdev, + const char *buf, size_t size) +{ + struct morse_trig_data *morse_data = led_cdev->trigger_data; + char *after; + unsigned long state = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + int ret = -EINVAL; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + morse_data->delay = state; + mod_timer(&morse_data->timer, jiffies + 1); + ret = count; + } + + return ret; +} + +static ssize_t _morse_msg_show(struct led_classdev *led_cdev, char *buf) +{ + struct morse_trig_data *morse_data = led_cdev->trigger_data; + + if (!morse_data->msg) + sprintf(buf, "<none>\n"); + else + sprintf(buf, "%s\n", morse_data->msg); + + return strlen(buf) + 1; +} + +static ssize_t _morse_msg_store(struct led_classdev *led_cdev, + const char *buf, size_t size) +{ + struct morse_trig_data *morse_data = led_cdev->trigger_data; + char *m; + + m = kmalloc(size, GFP_KERNEL); + if (!m) + return -ENOMEM; + + memcpy(m,buf,size); + m[size]='\0'; + + if (morse_data->msg) + kfree(morse_data->msg); + + morse_data->msg = m; + morse_data->msgpos = NULL; + morse_data->state = MORSE_STATE_BLINK_STOP; + + mod_timer(&morse_data->timer, jiffies + 1); + + return size; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23) +static ssize_t morse_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return _morse_delay_show(led_cdev, buf); +} + +static ssize_t morse_delay_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return _morse_delay_store(led_cdev, buf, size); +} + +static ssize_t morse_msg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return _morse_msg_show(led_cdev, buf); +} + +static ssize_t morse_msg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return _morse_msg_store(led_cdev, buf, size); +} + +static DEVICE_ATTR(delay, 0644, morse_delay_show, morse_delay_store); +static DEVICE_ATTR(message, 0644, morse_msg_show, morse_msg_store); + +#define led_device_create_file(leddev, attr) \ + device_create_file(leddev->dev, &dev_attr_ ## attr) +#define led_device_remove_file(leddev, attr) \ + device_remove_file(leddev->dev, &dev_attr_ ## attr) + +#else +static ssize_t morse_delay_show(struct class_device *dev, char *buf) +{ + struct led_classdev *led_cdev = class_get_devdata(dev); + + return _morse_delay_show(led_cdev, buf); +} + +static ssize_t morse_delay_store(struct class_device *dev, const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = class_get_devdata(dev); + + return _morse_delay_store(led_cdev, buf, size); +} + +static ssize_t morse_msg_show(struct class_device *dev, char *buf) +{ + struct led_classdev *led_cdev = class_get_devdata(dev); + + return _morse_msg_show(led_cdev, buf); +} + +static ssize_t morse_msg_store(struct class_device *dev, const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = class_get_devdata(dev); + + return _morse_msg_store(led_cdev, buf, size); +} + +static CLASS_DEVICE_ATTR(delay, 0644, morse_delay_show, morse_delay_store); +static CLASS_DEVICE_ATTR(message, 0644, morse_msg_show, morse_msg_store); + +#define led_device_create_file(leddev, attr) \ + class_device_create_file(leddev->class_dev, &class_device_attr_ ## attr) +#define led_device_remove_file(leddev, attr) \ + class_device_remove_file(leddev->class_dev, &class_device_attr_ ## attr) + +#endif + +static void morse_trig_activate(struct led_classdev *led_cdev) +{ + struct morse_trig_data *morse_data; + int rc; + + morse_data = kzalloc(sizeof(*morse_data), GFP_KERNEL); + if (!morse_data) + return; + + morse_data->delay = MORSE_DELAY_BASE; + init_timer(&morse_data->timer); + morse_data->timer.function = morse_timer_function; + morse_data->timer.data = (unsigned long)led_cdev; + + rc = led_device_create_file(led_cdev, delay); + if (rc) goto err; + + rc = led_device_create_file(led_cdev, message); + if (rc) goto err_delay; + + led_cdev->trigger_data = morse_data; + + return; + +err_delay: + led_device_remove_file(led_cdev, delay); +err: + kfree(morse_data); +} + +static void morse_trig_deactivate(struct led_classdev *led_cdev) +{ + struct morse_trig_data *morse_data = led_cdev->trigger_data; + + if (!morse_data) + return; + + led_device_remove_file(led_cdev, message); + led_device_remove_file(led_cdev, delay); + + del_timer_sync(&morse_data->timer); + if (morse_data->msg) + kfree(morse_data->msg); + + kfree(morse_data); +} + +static struct led_trigger morse_led_trigger = { + .name = "morse", + .activate = morse_trig_activate, + .deactivate = morse_trig_deactivate, +}; + +static int __init morse_trig_init(void) +{ + return led_trigger_register(&morse_led_trigger); +} + +static void __exit morse_trig_exit(void) +{ + led_trigger_unregister(&morse_led_trigger); +} + +module_init(morse_trig_init); +module_exit(morse_trig_exit); + +MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>"); +MODULE_DESCRIPTION("Morse LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files/drivers/leds/ledtrig-netdev.c b/target/linux/generic/files/drivers/leds/ledtrig-netdev.c new file mode 100644 index 000000000..6c56acb27 --- /dev/null +++ b/target/linux/generic/files/drivers/leds/ledtrig-netdev.c @@ -0,0 +1,451 @@ +/* + * LED Kernel Netdev Trigger + * + * Toggles the LED to reflect the link and traffic state of a named net device + * + * Copyright 2007 Oliver Jowett <oliver@opencloud.com> + * + * Derived from ledtrig-timer.c which is: + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * 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 <linux/module.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/netdevice.h> +#include <linux/timer.h> +#include <linux/ctype.h> +#include <linux/leds.h> +#include <linux/version.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) +#include <net/net_namespace.h> +#endif + +#include "leds.h" + +/* + * Configurable sysfs attributes: + * + * device_name - network device name to monitor + * + * interval - duration of LED blink, in milliseconds + * + * mode - either "none" (LED is off) or a space separated list of one or more of: + * link: LED's normal state reflects whether the link is up (has carrier) or not + * tx: LED blinks on transmitted data + * rx: LED blinks on receive data + * + * Some suggestions: + * + * Simple link status LED: + * $ echo netdev >someled/trigger + * $ echo eth0 >someled/device_name + * $ echo link >someled/mode + * + * Ethernet-style link/activity LED: + * $ echo netdev >someled/trigger + * $ echo eth0 >someled/device_name + * $ echo "link tx rx" >someled/mode + * + * Modem-style tx/rx LEDs: + * $ echo netdev >led1/trigger + * $ echo ppp0 >led1/device_name + * $ echo tx >led1/mode + * $ echo netdev >led2/trigger + * $ echo ppp0 >led2/device_name + * $ echo rx >led2/mode + * + */ + +#define MODE_LINK 1 +#define MODE_TX 2 +#define MODE_RX 4 + +struct led_netdev_data { + rwlock_t lock; + + struct timer_list timer; + struct notifier_block notifier; + + struct led_classdev *led_cdev; + struct net_device *net_dev; + + char device_name[IFNAMSIZ]; + unsigned interval; + unsigned mode; + unsigned link_up; + unsigned last_activity; +}; + +static void set_baseline_state(struct led_netdev_data *trigger_data) +{ + if ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) + led_set_brightness(trigger_data->led_cdev, LED_FULL); + else + led_set_brightness(trigger_data->led_cdev, LED_OFF); + + if ((trigger_data->mode & (MODE_TX | MODE_RX)) != 0 && trigger_data->link_up) + mod_timer(&trigger_data->timer, jiffies + trigger_data->interval); + else + del_timer(&trigger_data->timer); +} + +static ssize_t led_device_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + read_lock(&trigger_data->lock); + sprintf(buf, "%s\n", trigger_data->device_name); + read_unlock(&trigger_data->lock); + + return strlen(buf) + 1; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21) +extern struct net init_net; +#endif + +static ssize_t led_device_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (size < 0 || size >= IFNAMSIZ) + return -EINVAL; + + write_lock(&trigger_data->lock); + + strcpy(trigger_data->device_name, buf); + if (size > 0 && trigger_data->device_name[size-1] == '\n') + trigger_data->device_name[size-1] = 0; + + if (trigger_data->device_name[0] != 0) { + /* check for existing device to update from */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) + trigger_data->net_dev = dev_get_by_name(&init_net, trigger_data->device_name); +#else + trigger_data->net_dev = dev_get_by_name(trigger_data->device_name); +#endif + if (trigger_data->net_dev != NULL) + trigger_data->link_up = (dev_get_flags(trigger_data->net_dev) & IFF_LOWER_UP) != 0; + set_baseline_state(trigger_data); /* updates LEDs, may start timers */ + } + + write_unlock(&trigger_data->lock); + return size; +} + +static DEVICE_ATTR(device_name, 0644, led_device_name_show, led_device_name_store); + +static ssize_t led_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + read_lock(&trigger_data->lock); + + if (trigger_data->mode == 0) { + strcpy(buf, "none\n"); + } else { + if (trigger_data->mode & MODE_LINK) + strcat(buf, "link "); + if (trigger_data->mode & MODE_TX) + strcat(buf, "tx "); + if (trigger_data->mode & MODE_RX) + strcat(buf, "rx "); + strcat(buf, "\n"); + } + + read_unlock(&trigger_data->lock); + + return strlen(buf)+1; +} + +static ssize_t led_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + char copybuf[128]; + int new_mode = -1; + char *p, *token; + + /* take a copy since we don't want to trash the inbound buffer when using strsep */ + strncpy(copybuf, buf, sizeof(copybuf)); + copybuf[sizeof(copybuf) - 1] = 0; + p = copybuf; + + while ((token = strsep(&p, " \t\n")) != NULL) { + if (!*token) + continue; + + if (new_mode == -1) + new_mode = 0; + + if (!strcmp(token, "none")) + new_mode = 0; + else if (!strcmp(token, "tx")) + new_mode |= MODE_TX; + else if (!strcmp(token, "rx")) + new_mode |= MODE_RX; + else if (!strcmp(token, "link")) + new_mode |= MODE_LINK; + else + return -EINVAL; + } + + if (new_mode == -1) + return -EINVAL; + + write_lock(&trigger_data->lock); + trigger_data->mode = new_mode; + set_baseline_state(trigger_data); + write_unlock(&trigger_data->lock); + + return size; +} + +static DEVICE_ATTR(mode, 0644, led_mode_show, led_mode_store); + +static ssize_t led_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + read_lock(&trigger_data->lock); + sprintf(buf, "%u\n", jiffies_to_msecs(trigger_data->interval)); + read_unlock(&trigger_data->lock); + + return strlen(buf) + 1; +} + +static ssize_t led_interval_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + int ret = -EINVAL; + char *after; + unsigned long value = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + + if (*after && isspace(*after)) + count++; + + /* impose some basic bounds on the timer interval */ + if (count == size && value >= 5 && value <= 10000) { + write_lock(&trigger_data->lock); + trigger_data->interval = msecs_to_jiffies(value); + set_baseline_state(trigger_data); // resets timer + write_unlock(&trigger_data->lock); + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(interval, 0644, led_interval_show, led_interval_store); + +static int netdev_trig_notify(struct notifier_block *nb, + unsigned long evt, + void *dv) +{ + struct net_device *dev = dv; + struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); + + if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER) + return NOTIFY_DONE; + + write_lock(&trigger_data->lock); + + if (strcmp(dev->name, trigger_data->device_name)) + goto done; + + if (evt == NETDEV_REGISTER) { + if (trigger_data->net_dev != NULL) + dev_put(trigger_data->net_dev); + dev_hold(dev); + trigger_data->net_dev = dev; + trigger_data->link_up = 0; + goto done; + } + + if (evt == NETDEV_UNREGISTER && trigger_data->net_dev != NULL) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + goto done; + } + + /* UP / DOWN / CHANGE */ + + trigger_data->link_up = (evt != NETDEV_DOWN && netif_carrier_ok(dev)); + set_baseline_state(trigger_data); + +done: + write_unlock(&trigger_data->lock); + return NOTIFY_DONE; +} + +/* here's the real work! */ +static void netdev_trig_timer(unsigned long arg) +{ + struct led_netdev_data *trigger_data = (struct led_netdev_data *)arg; + const struct net_device_stats *dev_stats; + unsigned new_activity; + + write_lock(&trigger_data->lock); + + if (!trigger_data->link_up || !trigger_data->net_dev || (trigger_data->mode & (MODE_TX | MODE_RX)) == 0) { + /* we don't need to do timer work, just reflect link state. */ + led_set_brightness(trigger_data->led_cdev, ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) ? LED_FULL : LED_OFF); + goto no_restart; + } + + dev_stats = dev_get_stats(trigger_data->net_dev); + new_activity = + ((trigger_data->mode & MODE_TX) ? dev_stats->tx_packets : 0) + + ((trigger_data->mode & MODE_RX) ? dev_stats->rx_packets : 0); + + if (trigger_data->mode & MODE_LINK) { + /* base state is ON (link present) */ + /* if there's no link, we don't get this far and the LED is off */ + + /* OFF -> ON always */ + /* ON -> OFF on activity */ + if (trigger_data->led_cdev->brightness == LED_OFF) { + led_set_brightness(trigger_data->led_cdev, LED_FULL); + } else if (trigger_data->last_activity != new_activity) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + } + } else { + /* base state is OFF */ + /* ON -> OFF always */ + /* OFF -> ON on activity */ + if (trigger_data->led_cdev->brightness == LED_FULL) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + } else if (trigger_data->last_activity != new_activity) { + led_set_brightness(trigger_data->led_cdev, LED_FULL); + } + } + + trigger_data->last_activity = new_activity; + mod_timer(&trigger_data->timer, jiffies + trigger_data->interval); + +no_restart: + write_unlock(&trigger_data->lock); +} + +static void netdev_trig_activate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data; + int rc; + + trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); + if (!trigger_data) + return; + + rwlock_init(&trigger_data->lock); + + trigger_data->notifier.notifier_call = netdev_trig_notify; + trigger_data->notifier.priority = 10; + + setup_timer(&trigger_data->timer, netdev_trig_timer, (unsigned long) trigger_data); + + trigger_data->led_cdev = led_cdev; + trigger_data->net_dev = NULL; + trigger_data->device_name[0] = 0; + + trigger_data->mode = 0; + trigger_data->interval = msecs_to_jiffies(50); + trigger_data->link_up = 0; + trigger_data->last_activity = 0; + + led_cdev->trigger_data = trigger_data; + + rc = device_create_file(led_cdev->dev, &dev_attr_device_name); + if (rc) + goto err_out; + rc = device_create_file(led_cdev->dev, &dev_attr_mode); + if (rc) + goto err_out_device_name; + rc = device_create_file(led_cdev->dev, &dev_attr_interval); + if (rc) + goto err_out_mode; + + register_netdevice_notifier(&trigger_data->notifier); + return; + +err_out_mode: + device_remove_file(led_cdev->dev, &dev_attr_mode); +err_out_device_name: + device_remove_file(led_cdev->dev, &dev_attr_device_name); +err_out: + led_cdev->trigger_data = NULL; + kfree(trigger_data); +} + +static void netdev_trig_deactivate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (trigger_data) { + unregister_netdevice_notifier(&trigger_data->notifier); + + device_remove_file(led_cdev->dev, &dev_attr_device_name); + device_remove_file(led_cdev->dev, &dev_attr_mode); + device_remove_file(led_cdev->dev, &dev_attr_interval); + + write_lock(&trigger_data->lock); + + if (trigger_data->net_dev) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + } + + write_unlock(&trigger_data->lock); + + del_timer_sync(&trigger_data->timer); + + kfree(trigger_data); + } +} + +static struct led_trigger netdev_led_trigger = { + .name = "netdev", + .activate = netdev_trig_activate, + .deactivate = netdev_trig_deactivate, +}; + +static int __init netdev_trig_init(void) +{ + return led_trigger_register(&netdev_led_trigger); +} + +static void __exit netdev_trig_exit(void) +{ + led_trigger_unregister(&netdev_led_trigger); +} + +module_init(netdev_trig_init); +module_exit(netdev_trig_exit); + +MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>"); +MODULE_DESCRIPTION("Netdev LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files/drivers/leds/ledtrig-usbdev.c b/target/linux/generic/files/drivers/leds/ledtrig-usbdev.c new file mode 100644 index 000000000..70b0e392a --- /dev/null +++ b/target/linux/generic/files/drivers/leds/ledtrig-usbdev.c @@ -0,0 +1,348 @@ +/* + * LED USB device Trigger + * + * Toggles the LED to reflect the presence and activity of an USB device + * Copyright (C) Gabor Juhos <juhosg@openwrt.org> + * + * derived from ledtrig-netdev.c: + * Copyright 2007 Oliver Jowett <oliver@opencloud.com> + * + * ledtrig-netdev.c derived from ledtrig-timer.c: + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * 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 <linux/module.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/timer.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/leds.h> +#include <linux/usb.h> + +#include "leds.h" + +#define DEV_BUS_ID_SIZE 32 + +/* + * Configurable sysfs attributes: + * + * device_name - name of the USB device to monitor + * activity_interval - duration of LED blink, in milliseconds + */ + +struct usbdev_trig_data { + rwlock_t lock; + + struct timer_list timer; + struct notifier_block notifier; + + struct led_classdev *led_cdev; + struct usb_device *usb_dev; + + char device_name[DEV_BUS_ID_SIZE]; + unsigned interval; + int last_urbnum; +}; + +static void usbdev_trig_update_state(struct usbdev_trig_data *td) +{ + if (td->usb_dev) + led_set_brightness(td->led_cdev, LED_FULL); + else + led_set_brightness(td->led_cdev, LED_OFF); + + if (td->interval && td->usb_dev) + mod_timer(&td->timer, jiffies + td->interval); + else + del_timer(&td->timer); +} + +static ssize_t usbdev_trig_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct usbdev_trig_data *td = led_cdev->trigger_data; + + read_lock(&td->lock); + sprintf(buf, "%s\n", td->device_name); + read_unlock(&td->lock); + + return strlen(buf) + 1; +} + +static ssize_t usbdev_trig_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct usbdev_trig_data *td = led_cdev->trigger_data; + + if (size < 0 || size >= DEV_BUS_ID_SIZE) + return -EINVAL; + + write_lock(&td->lock); + + strcpy(td->device_name, buf); + if (size > 0 && td->device_name[size - 1] == '\n') + td->device_name[size - 1] = 0; + + if (td->device_name[0] != 0) { + struct usb_device *usb_dev; + + /* check for existing device to update from */ + usb_dev = usb_find_device_by_name(td->device_name); + if (usb_dev) { + if (td->usb_dev) + usb_put_dev(td->usb_dev); + + td->usb_dev = usb_dev; + td->last_urbnum = atomic_read(&usb_dev->urbnum); + } + + /* updates LEDs, may start timers */ + usbdev_trig_update_state(td); + } + + write_unlock(&td->lock); + return size; +} + +static DEVICE_ATTR(device_name, 0644, usbdev_trig_name_show, + usbdev_trig_name_store); + +static ssize_t usbdev_trig_interval_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct usbdev_trig_data *td = led_cdev->trigger_data; + + read_lock(&td->lock); + sprintf(buf, "%u\n", jiffies_to_msecs(td->interval)); + read_unlock(&td->lock); + + return strlen(buf) + 1; +} + +static ssize_t usbdev_trig_interval_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct usbdev_trig_data *td = led_cdev->trigger_data; + int ret = -EINVAL; + char *after; + unsigned long value = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size && value <= 10000) { + write_lock(&td->lock); + td->interval = msecs_to_jiffies(value); + usbdev_trig_update_state(td); /* resets timer */ + write_unlock(&td->lock); + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(activity_interval, 0644, usbdev_trig_interval_show, + usbdev_trig_interval_store); + +static int usbdev_trig_notify(struct notifier_block *nb, + unsigned long evt, + void *data) +{ + struct usb_device *usb_dev; + struct usbdev_trig_data *td; + + if (evt != USB_DEVICE_ADD && evt != USB_DEVICE_REMOVE) + return NOTIFY_DONE; + + usb_dev = data; + td = container_of(nb, struct usbdev_trig_data, notifier); + + write_lock(&td->lock); + + if (strcmp(dev_name(&usb_dev->dev), td->device_name)) + goto done; + + if (evt == USB_DEVICE_ADD) { + usb_get_dev(usb_dev); + if (td->usb_dev != NULL) + usb_put_dev(td->usb_dev); + td->usb_dev = usb_dev; + td->last_urbnum = atomic_read(&usb_dev->urbnum); + } else if (evt == USB_DEVICE_REMOVE) { + if (td->usb_dev != NULL) { + usb_put_dev(td->usb_dev); + td->usb_dev = NULL; + } + } + + usbdev_trig_update_state(td); + +done: + write_unlock(&td->lock); + return NOTIFY_DONE; +} + +/* here's the real work! */ +static void usbdev_trig_timer(unsigned long arg) +{ + struct usbdev_trig_data *td = (struct usbdev_trig_data *)arg; + int new_urbnum; + + write_lock(&td->lock); + + if (!td->usb_dev || td->interval == 0) { + /* + * we don't need to do timer work, just reflect device presence + */ + if (td->usb_dev) + led_set_brightness(td->led_cdev, LED_FULL); + else + led_set_brightness(td->led_cdev, LED_OFF); + + goto no_restart; + } + + if (td->interval) + new_urbnum = atomic_read(&td->usb_dev->urbnum); + else + new_urbnum = 0; + + if (td->usb_dev) { + /* + * Base state is ON (device is present). If there's no device, + * we don't get this far and the LED is off. + * OFF -> ON always + * ON -> OFF on activity + */ + if (td->led_cdev->brightness == LED_OFF) + led_set_brightness(td->led_cdev, LED_FULL); + else if (td->last_urbnum != new_urbnum) + led_set_brightness(td->led_cdev, LED_OFF); + } else { + /* + * base state is OFF + * ON -> OFF always + * OFF -> ON on activity + */ + if (td->led_cdev->brightness == LED_FULL) + led_set_brightness(td->led_cdev, LED_OFF); + else if (td->last_urbnum != new_urbnum) + led_set_brightness(td->led_cdev, LED_FULL); + } + + td->last_urbnum = new_urbnum; + mod_timer(&td->timer, jiffies + td->interval); + +no_restart: + write_unlock(&td->lock); +} + +static void usbdev_trig_activate(struct led_classdev *led_cdev) +{ + struct usbdev_trig_data *td; + int rc; + + td = kzalloc(sizeof(struct usbdev_trig_data), GFP_KERNEL); + if (!td) + return; + + rwlock_init(&td->lock); + + td->notifier.notifier_call = usbdev_trig_notify; + td->notifier.priority = 10; + + setup_timer(&td->timer, usbdev_trig_timer, (unsigned long) td); + + td->led_cdev = led_cdev; + td->interval = msecs_to_jiffies(50); + + led_cdev->trigger_data = td; + + rc = device_create_file(led_cdev->dev, &dev_attr_device_name); + if (rc) + goto err_out; + + rc = device_create_file(led_cdev->dev, &dev_attr_activity_interval); + if (rc) + goto err_out_device_name; + + usb_register_notify(&td->notifier); + return; + +err_out_device_name: + device_remove_file(led_cdev->dev, &dev_attr_device_name); +err_out: + led_cdev->trigger_data = NULL; + kfree(td); +} + +static void usbdev_trig_deactivate(struct led_classdev *led_cdev) +{ + struct usbdev_trig_data *td = led_cdev->trigger_data; + + if (td) { + usb_unregister_notify(&td->notifier); + + device_remove_file(led_cdev->dev, &dev_attr_device_name); + device_remove_file(led_cdev->dev, &dev_attr_activity_interval); + + write_lock(&td->lock); + + if (td->usb_dev) { + usb_put_dev(td->usb_dev); + td->usb_dev = NULL; + } + + write_unlock(&td->lock); + + del_timer_sync(&td->timer); + + kfree(td); + } +} + +static struct led_trigger usbdev_led_trigger = { + .name = "usbdev", + .activate = usbdev_trig_activate, + .deactivate = usbdev_trig_deactivate, +}; + +static int __init usbdev_trig_init(void) +{ + return led_trigger_register(&usbdev_led_trigger); +} + +static void __exit usbdev_trig_exit(void) +{ + led_trigger_unregister(&usbdev_led_trigger); +} + +module_init(usbdev_trig_init); +module_exit(usbdev_trig_exit); + +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_DESCRIPTION("USB device LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files/drivers/mtd/myloader.c b/target/linux/generic/files/drivers/mtd/myloader.c new file mode 100644 index 000000000..a13752dd9 --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/myloader.c @@ -0,0 +1,186 @@ +/* + * Parse MyLoader-style flash partition tables and produce a Linux partition + * array to match. + * + * Copyright (C) 2007-2009 Gabor Juhos <juhosg@openwrt.org> + * + * This file was based on drivers/mtd/redboot.c + * Author: Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/byteorder/generic.h> +#include <linux/myloader.h> + +#define BLOCK_LEN_MIN 0x10000 +#define PART_NAME_LEN 32 + +struct part_data { + struct mylo_partition_table tab; + char names[MYLO_MAX_PARTITIONS][PART_NAME_LEN]; +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)) +static int myloader_parse_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +#else +static int myloader_parse_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + unsigned long origin) +#endif +{ + struct part_data *buf; + struct mylo_partition_table *tab; + struct mylo_partition *part; + struct mtd_partition *mtd_parts; + struct mtd_partition *mtd_part; + int num_parts; + int ret, i; + size_t retlen; + char *names; + unsigned long offset; + unsigned long blocklen; + + buf = vmalloc(sizeof(*buf)); + if (!buf) { + return -ENOMEM; + goto out; + } + tab = &buf->tab; + + blocklen = master->erasesize; + if (blocklen < BLOCK_LEN_MIN) + blocklen = BLOCK_LEN_MIN; + + offset = blocklen; + + /* Find the partition table */ + for (i = 0; i < 4; i++, offset += blocklen) { + printk(KERN_DEBUG "%s: searching for MyLoader partition table" + " at offset 0x%lx\n", master->name, offset); + + ret = master->read(master, offset, sizeof(*buf), &retlen, + (void *)buf); + if (ret) + goto out_free_buf; + + if (retlen != sizeof(*buf)) { + ret = -EIO; + goto out_free_buf; + } + + /* Check for Partition Table magic number */ + if (tab->magic == le32_to_cpu(MYLO_MAGIC_PARTITIONS)) + break; + + } + + if (tab->magic != le32_to_cpu(MYLO_MAGIC_PARTITIONS)) { + printk(KERN_DEBUG "%s: no MyLoader partition table found\n", + master->name); + ret = 0; + goto out_free_buf; + } + + /* The MyLoader and the Partition Table is always present */ + num_parts = 2; + + /* Detect number of used partitions */ + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + num_parts++; + } + + mtd_parts = kzalloc((num_parts * sizeof(*mtd_part) + + num_parts * PART_NAME_LEN), GFP_KERNEL); + + if (!mtd_parts) { + ret = -ENOMEM; + goto out_free_buf; + } + + mtd_part = mtd_parts; + names = (char *)&mtd_parts[num_parts]; + + strncpy(names, "myloader", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = 0; + mtd_part->size = offset; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + strncpy(names, "partition_table", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = offset; + mtd_part->size = blocklen; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + if ((buf->names[i][0]) && (buf->names[i][0] != '\xff')) + strncpy(names, buf->names[i], PART_NAME_LEN); + else + snprintf(names, PART_NAME_LEN, "partition%d", i); + + mtd_part->offset = le32_to_cpu(part->addr); + mtd_part->size = le32_to_cpu(part->size); + mtd_part->name = names; + mtd_part++; + names += PART_NAME_LEN; + } + + *pparts = mtd_parts; + ret = num_parts; + + out_free_buf: + vfree(buf); + out: + return ret; +} + +static struct mtd_part_parser myloader_mtd_parser = { + .owner = THIS_MODULE, + .parse_fn = myloader_parse_partitions, + .name = "MyLoader", +}; + +static int __init myloader_mtd_parser_init(void) +{ + return register_mtd_parser(&myloader_mtd_parser); +} + +static void __exit myloader_mtd_parser_exit(void) +{ + deregister_mtd_parser(&myloader_mtd_parser); +} + +module_init(myloader_mtd_parser_init); +module_exit(myloader_mtd_parser_exit); + +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_DESCRIPTION("Parsing code for MyLoader partition tables"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files/drivers/net/phy/adm6996.c b/target/linux/generic/files/drivers/net/phy/adm6996.c new file mode 100644 index 000000000..347c12970 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/adm6996.c @@ -0,0 +1,737 @@ +/* + * ADM6996 switch driver + * + * swconfig interface based on ar8216.c + * + * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> + * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +/*#define DEBUG 1*/ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> +#include <linux/switch.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> +#include "adm6996.h" + +MODULE_DESCRIPTION("Infineon ADM6996 Switch"); +MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>"); +MODULE_LICENSE("GPL"); + +enum adm6996_model { + ADM6996FC, + ADM6996M +}; + +static const char * const adm6996_model_name[] = +{ + "ADM6996FC", + "ADM6996M" +}; + +struct adm6996_priv { + struct switch_dev dev; + struct phy_device *phydev; + + enum adm6996_model model; + + bool enable_vlan; + bool vlan_enabled; /* Current hardware state */ + +#ifdef DEBUG + u16 addr; /* Debugging: register address to operate on */ +#endif + + u16 pvid[ADM_NUM_PORTS]; /* Primary VLAN ID */ + + u16 vlan_id[ADM_NUM_VLANS]; + u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */ + u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */ + + struct mutex reg_mutex; + + /* use abstraction for regops, we want to add gpio support in the future */ + u16 (*read)(struct phy_device *phydev, enum admreg reg); + void (*write)(struct phy_device *phydev, enum admreg reg, u16 val); +}; + +#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev) +#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) + +static inline u16 +r16(struct phy_device *pdev, enum admreg reg) +{ + return phy_to_adm(pdev)->read(pdev, reg); +} + +static inline void +w16(struct phy_device *pdev, enum admreg reg, u16 val) +{ + phy_to_adm(pdev)->write(pdev, reg, val); +} + + +static u16 +adm6996_read_mii_reg(struct phy_device *phydev, enum admreg reg) +{ + return phydev->bus->read(phydev->bus, PHYADDR(reg)); +} + +static void +adm6996_write_mii_reg(struct phy_device *phydev, enum admreg reg, u16 val) +{ + phydev->bus->write(phydev->bus, PHYADDR(reg), val); +} + +static int +adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1) + return -EINVAL; + + priv->enable_vlan = val->value.i; + + return 0; +}; + +static int +adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->enable_vlan; + + return 0; +}; + +#ifdef DEBUG + +static int +adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1023) + return -EINVAL; + + priv->addr = val->value.i; + + return 0; +}; + +static int +adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->addr; + + return 0; +}; + +static int +adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 65535) + return -EINVAL; + + w16(priv->phydev, priv->addr, val->value.i); + + return 0; +}; + +static int +adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = r16(priv->phydev, priv->addr); + + return 0; +}; + +#endif /* def DEBUG */ + +static int +adm6996_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "set_pvid port %d vlan %d\n", port + , vlan); + + if (vlan > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->pvid[port] = vlan; + + return 0; +} + +static int +adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "get_pvid port %d\n", port); + *vlan = priv->pvid[port]; + + return 0; +} + +static int +adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "set_vid port %d vid %d\n", val->port_vlan, + val->value.i); + + if (val->value.i > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->vlan_id[val->port_vlan] = val->value.i; + + return 0; +}; + +static int +adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "get_vid port %d\n", val->port_vlan); + + val->value.i = priv->vlan_id[val->port_vlan]; + + return 0; +}; + +static int +adm6996_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + u8 tagged = priv->vlan_tagged[val->port_vlan]; + int i; + + dev_dbg (&priv->phydev->dev, "get_ports port_vlan %d\n", + val->port_vlan); + + val->len = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + + return 0; +}; + +static int +adm6996_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 *ports = &priv->vlan_table[val->port_vlan]; + u8 *tagged = &priv->vlan_tagged[val->port_vlan]; + int i; + + dev_dbg (&priv->phydev->dev, "set_ports port_vlan %d ports", + val->port_vlan); + + *ports = 0; + *tagged = 0; + + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + +#ifdef DEBUG + pr_cont(" %d%s", p->id, + ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" : + "")); +#endif + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + *tagged |= (1 << p->id); + + *ports |= (1 << p->id); + } + +#ifdef DEBUG + pr_cont("\n"); +#endif + + return 0; +}; + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_enable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + + reg = r16(priv->phydev, ADM_OTBE_P2_PVID); + reg &= ~(ADM_OTBE_MASK); + w16(priv->phydev, ADM_OTBE_P2_PVID, reg); + reg = r16(priv->phydev, ADM_IFNTE); + reg &= ~(ADM_IFNTE_MASK); + w16(priv->phydev, ADM_IFNTE, reg); + reg = r16(priv->phydev, ADM_VID_CHECK); + reg |= ADM_VID_CHECK_MASK; + w16(priv->phydev, ADM_VID_CHECK, reg); + reg = r16(priv->phydev, ADM_SYSC0); + reg |= ADM_NTTE; + reg &= ~(ADM_RVID1); + w16(priv->phydev, ADM_SYSC0, reg); + reg = r16(priv->phydev, ADM_SYSC3); + reg |= ADM_TBV; + w16(priv->phydev, ADM_SYSC3, reg); + +}; + +/* + * Disable VLANs + * + * Sets VLAN mapping for port-based VLAN with all ports connected to + * eachother (this is also the power-on default). + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_disable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + reg = ADM_VLAN_FILT_MEMBER_MASK; + w16(priv->phydev, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1); + w16(priv->phydev, ADM_VLAN_FILT_H(i), reg); + } + + reg = r16(priv->phydev, ADM_OTBE_P2_PVID); + reg |= ADM_OTBE_MASK; + w16(priv->phydev, ADM_OTBE_P2_PVID, reg); + reg = r16(priv->phydev, ADM_IFNTE); + reg |= ADM_IFNTE_MASK; + w16(priv->phydev, ADM_IFNTE, reg); + reg = r16(priv->phydev, ADM_VID_CHECK); + reg &= ~(ADM_VID_CHECK_MASK); + w16(priv->phydev, ADM_VID_CHECK, reg); + reg = r16(priv->phydev, ADM_SYSC0); + reg &= ~(ADM_NTTE); + reg |= ADM_RVID1; + w16(priv->phydev, ADM_SYSC0, reg); + reg = r16(priv->phydev, ADM_SYSC3); + reg &= ~(ADM_TBV); + w16(priv->phydev, ADM_SYSC3, reg); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_port_pvids(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + reg = r16(priv->phydev, adm_portcfg[i]); + reg &= ~(ADM_PORTCFG_PVID_MASK); + reg |= ADM_PORTCFG_PVID(priv->pvid[i]); + w16(priv->phydev, adm_portcfg[i], reg); + } + + w16(priv->phydev, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0])); + w16(priv->phydev, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1])); + reg = r16(priv->phydev, ADM_OTBE_P2_PVID); + reg &= ~(ADM_P2_PVID_MASK); + reg |= ADM_P2_PVID_VAL(priv->pvid[2]); + w16(priv->phydev, ADM_OTBE_P2_PVID, reg); + reg = ADM_P3_PVID_VAL(priv->pvid[3]); + reg |= ADM_P4_PVID_VAL(priv->pvid[4]); + w16(priv->phydev, ADM_P3_P4_PVID, reg); + w16(priv->phydev, ADM_P5_PVID, ADM_P5_PVID_VAL(priv->pvid[5])); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_vlan_filters(struct adm6996_priv *priv) +{ + u8 ports, tagged; + u16 vid, reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + vid = priv->vlan_id[i]; + ports = priv->vlan_table[i]; + tagged = priv->vlan_tagged[i]; + + if (ports == 0) { + /* Disable VLAN entry */ + w16(priv->phydev, ADM_VLAN_FILT_H(i), 0); + w16(priv->phydev, ADM_VLAN_FILT_L(i), 0); + continue; + } + + reg = ADM_VLAN_FILT_MEMBER(ports); + reg |= ADM_VLAN_FILT_TAGGED(tagged); + w16(priv->phydev, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid); + w16(priv->phydev, ADM_VLAN_FILT_H(i), reg); + } +} + +static int +adm6996_hw_apply(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg(&priv->phydev->dev, "hw_apply\n"); + + mutex_lock(&priv->reg_mutex); + + if (!priv->enable_vlan) { + if (priv->vlan_enabled) { + adm6996_disable_vlan(priv); + priv->vlan_enabled = 0; + } + goto out; + } + + if (!priv->vlan_enabled) { + adm6996_enable_vlan(priv); + priv->vlan_enabled = 1; + } + + adm6996_apply_port_pvids(priv); + adm6996_apply_vlan_filters(priv); + +out: + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +/* + * Reset the switch + * + * The ADM6996 can't do a software-initiated reset, so we just initialise the + * registers we support in this driver. + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_perform_reset (struct adm6996_priv *priv) +{ + int i; + + /* initialize port and vlan settings */ + for (i = 0; i < ADM_NUM_PORTS - 1; i++) { + w16(priv->phydev, adm_portcfg[i], ADM_PORTCFG_INIT | + ADM_PORTCFG_PVID(0)); + } + w16(priv->phydev, adm_portcfg[5], ADM_PORTCFG_CPU); + + /* reset all PHY ports */ + for (i = 0; i < ADM_PHY_PORTS; i++) { + w16(priv->phydev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT); + } + + priv->enable_vlan = 0; + priv->vlan_enabled = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + priv->pvid[i] = 0; + } + + for (i = 0; i < ADM_NUM_VLANS; i++) { + priv->vlan_id[i] = i; + priv->vlan_table[i] = 0; + priv->vlan_tagged[i] = 0; + } + + if (priv->model == ADM6996M) { + /* Clear VLAN priority map so prio's are unused */ + w16 (priv->phydev, ADM_VLAN_PRIOMAP, 0); + + adm6996_disable_vlan(priv); + adm6996_apply_port_pvids(priv); + } +} + +static int +adm6996_reset_switch(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + dev_dbg (&priv->phydev->dev, "reset\n"); + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static struct switch_attr adm6996_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLANs", + .set = adm6996_set_enable_vlan, + .get = adm6996_get_enable_vlan, + }, +#ifdef DEBUG + { + .type = SWITCH_TYPE_INT, + .name = "addr", + .description = + "Direct register access: set register address (0 - 1023)", + .set = adm6996_set_addr, + .get = adm6996_get_addr, + }, + { + .type = SWITCH_TYPE_INT, + .name = "data", + .description = + "Direct register access: read/write to register (0 - 65535)", + .set = adm6996_set_data, + .get = adm6996_get_data, + }, +#endif /* def DEBUG */ +}; + +static struct switch_attr adm6996_port[] = { +}; + +static struct switch_attr adm6996_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID", + .set = adm6996_set_vid, + .get = adm6996_get_vid, + }, +}; + +static const struct switch_dev_ops adm6996_ops = { + .attr_global = { + .attr = adm6996_globals, + .n_attr = ARRAY_SIZE(adm6996_globals), + }, + .attr_port = { + .attr = adm6996_port, + .n_attr = ARRAY_SIZE(adm6996_port), + }, + .attr_vlan = { + .attr = adm6996_vlan, + .n_attr = ARRAY_SIZE(adm6996_vlan), + }, + .get_port_pvid = adm6996_get_pvid, + .set_port_pvid = adm6996_set_pvid, + .get_vlan_ports = adm6996_get_ports, + .set_vlan_ports = adm6996_set_ports, + .apply_config = adm6996_hw_apply, + .reset_switch = adm6996_reset_switch, +}; + +static int adm6996_config_init(struct phy_device *pdev) +{ + struct adm6996_priv *priv; + struct switch_dev *swdev; + + int ret; + u16 test, old; + + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + + if (pdev->addr != 0) { + pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n" + , pdev->attached_dev->name, pdev->addr); + return 0; + } + + priv = kzalloc(sizeof(struct adm6996_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + mutex_init(&priv->reg_mutex); + priv->phydev = pdev; + priv->read = adm6996_read_mii_reg; + priv->write = adm6996_write_mii_reg; + pdev->priv = priv; + + /* Detect type of chip */ + old = r16(pdev, ADM_VID_CHECK); + test = old ^ (1 << 12); + w16(pdev, ADM_VID_CHECK, test); + test ^= r16(pdev, ADM_VID_CHECK); + if (test & (1 << 12)) { + /* + * Bit 12 of this register is read-only. + * This is the FC model. + */ + priv->model = ADM6996FC; + } else { + /* Bit 12 is read-write. This is the M model. */ + priv->model = ADM6996M; + w16(pdev, ADM_VID_CHECK, old); + } + + swdev = &priv->dev; + swdev->name = (adm6996_model_name[priv->model]); + swdev->cpu_port = ADM_CPU_PORT; + swdev->ports = ADM_NUM_PORTS; + swdev->vlans = ADM_NUM_VLANS; + swdev->ops = &adm6996_ops; + + pr_info ("%s: %s model PHY found.\n", pdev->attached_dev->name, + swdev->name); + + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + + if (priv->model == ADM6996M) { + if ((ret = register_switch(swdev, pdev->attached_dev)) < 0) { + kfree(priv); + return ret; + } + } + + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->addr != 0 + */ +static int adm6996_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->addr != 0 + */ +static int adm6996_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int adm6996_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->bus; + u16 reg; + + /* Our custom registers are at PHY addresses 0-10. Claim those. */ + if (dev->addr > 10) + return 0; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK; + if (reg != ADM_SIG0_VAL) + return 0; + + reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK; + if (reg != ADM_SIG1_VAL) + return 0; + + dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL; + + return 0; +} + +static int adm6996_probe(struct phy_device *pdev) +{ + return 0; +} + +static void adm6996_remove(struct phy_device *pdev) +{ + struct adm6996_priv *priv = phy_to_adm(pdev); + + if (priv != NULL && priv->model == ADM6996M) + unregister_switch(&priv->dev); + + kfree(priv); +} + + +static struct phy_driver adm6996_driver = { + .name = "Infineon ADM6996", + .phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = adm6996_probe, + .remove = adm6996_remove, + .config_init = &adm6996_config_init, + .config_aneg = &adm6996_config_aneg, + .read_status = &adm6996_read_status, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init adm6996_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup); + return phy_driver_register(&adm6996_driver); +} + +static void __exit adm6996_exit(void) +{ + phy_driver_unregister(&adm6996_driver); +} + +module_init(adm6996_init); +module_exit(adm6996_exit); diff --git a/target/linux/generic/files/drivers/net/phy/adm6996.h b/target/linux/generic/files/drivers/net/phy/adm6996.h new file mode 100644 index 000000000..6922dfcbb --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/adm6996.h @@ -0,0 +1,162 @@ +/* + * ADM6996 switch driver + * + * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> + * Copyright (c) 2010,2011 Peter Lebbing <peter@digitalbrains.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __ADM6996_H +#define __ADM6996_H + +/* + * ADM_PHY_PORTS: Number of ports with a PHY. + * We only control ports 0 to 3, because if 4 is connected, it is most likely + * not connected to the switch but to a separate MII and MAC for the WAN port. + */ +#define ADM_PHY_PORTS 4 +#define ADM_NUM_PORTS 6 +#define ADM_CPU_PORT 5 + +#define ADM_NUM_VLANS 16 +#define ADM_VLAN_MAX_ID 4094 + +enum admreg { + ADM_EEPROM_BASE = 0x0, + ADM_P0_CFG = ADM_EEPROM_BASE + 1, + ADM_P1_CFG = ADM_EEPROM_BASE + 3, + ADM_P2_CFG = ADM_EEPROM_BASE + 5, + ADM_P3_CFG = ADM_EEPROM_BASE + 7, + ADM_P4_CFG = ADM_EEPROM_BASE + 8, + ADM_P5_CFG = ADM_EEPROM_BASE + 9, + ADM_SYSC0 = ADM_EEPROM_BASE + 0xa, + ADM_VLAN_PRIOMAP = ADM_EEPROM_BASE + 0xe, + ADM_SYSC3 = ADM_EEPROM_BASE + 0x11, + /* Input Force No Tag Enable */ + ADM_IFNTE = ADM_EEPROM_BASE + 0x20, + ADM_VID_CHECK = ADM_EEPROM_BASE + 0x26, + ADM_P0_PVID = ADM_EEPROM_BASE + 0x28, + ADM_P1_PVID = ADM_EEPROM_BASE + 0x29, + /* Output Tag Bypass Enable and P2 PVID */ + ADM_OTBE_P2_PVID = ADM_EEPROM_BASE + 0x2a, + ADM_P3_P4_PVID = ADM_EEPROM_BASE + 0x2b, + ADM_P5_PVID = ADM_EEPROM_BASE + 0x2c, + ADM_EEPROM_EXT_BASE = 0x40, +#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n)) +#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n)) + ADM_COUNTER_BASE = 0xa0, + ADM_SIG0 = ADM_COUNTER_BASE + 0, + ADM_SIG1 = ADM_COUNTER_BASE + 1, + ADM_PHY_BASE = 0x200, +#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n)) +}; + +/* Chip identification patterns */ +#define ADM_SIG0_MASK 0xffff +#define ADM_SIG0_VAL 0x1023 +#define ADM_SIG1_MASK 0xffff +#define ADM_SIG1_VAL 0x0007 + +enum { + ADM_PHYCFG_COLTST = (1 << 7), /* Enable collision test */ + ADM_PHYCFG_DPLX = (1 << 8), /* Enable full duplex */ + ADM_PHYCFG_ANEN_RST = (1 << 9), /* Restart auto negotiation (self clear) */ + ADM_PHYCFG_ISO = (1 << 10), /* Isolate PHY */ + ADM_PHYCFG_PDN = (1 << 11), /* Power down PHY */ + ADM_PHYCFG_ANEN = (1 << 12), /* Enable auto negotiation */ + ADM_PHYCFG_SPEED_100 = (1 << 13), /* Enable 100 Mbit/s */ + ADM_PHYCFG_LPBK = (1 << 14), /* Enable loopback operation */ + ADM_PHYCFG_RST = (1 << 15), /* Reset the port (self clear) */ + ADM_PHYCFG_INIT = ( + ADM_PHYCFG_RST | + ADM_PHYCFG_SPEED_100 | + ADM_PHYCFG_ANEN | + ADM_PHYCFG_ANEN_RST + ) +}; + +enum { + ADM_PORTCFG_FC = (1 << 0), /* Enable 802.x flow control */ + ADM_PORTCFG_AN = (1 << 1), /* Enable auto-negotiation */ + ADM_PORTCFG_SPEED_100 = (1 << 2), /* Enable 100 Mbit/s */ + ADM_PORTCFG_DPLX = (1 << 3), /* Enable full duplex */ + ADM_PORTCFG_OT = (1 << 4), /* Output tagged packets */ + ADM_PORTCFG_PD = (1 << 5), /* Port disable */ + ADM_PORTCFG_TV_PRIO = (1 << 6), /* 0 = VLAN based priority + * 1 = TOS based priority */ + ADM_PORTCFG_PPE = (1 << 7), /* Port based priority enable */ + ADM_PORTCFG_PP_S = (1 << 8), /* Port based priority, 2 bits */ + ADM_PORTCFG_PVID_BASE = (1 << 10), /* Primary VLAN id, 4 bits */ + ADM_PORTCFG_FSE = (1 << 14), /* Fx select enable */ + ADM_PORTCFG_CAM = (1 << 15), /* Crossover Auto MDIX */ + + ADM_PORTCFG_INIT = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_AN | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_DPLX | + ADM_PORTCFG_CAM + ), + ADM_PORTCFG_CPU = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_OT | + ADM_PORTCFG_DPLX + ), +}; + +#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8) +#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10) +#define ADM_PORTCFG_PVID_MASK (0xf << 10) + +#define ADM_IFNTE_MASK (0x3f << 9) +#define ADM_VID_CHECK_MASK (0x3f << 6) + +#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8) +#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_MASK 0xff + +#define ADM_OTBE(n) (((n) & 0x3f) << 8) +#define ADM_OTBE_MASK (0x3f << 8) + +/* ADM_SYSC0 */ +enum { + ADM_NTTE = (1 << 2), /* New Tag Transmit Enable */ + ADM_RVID1 = (1 << 8) /* Replace VLAN ID 1 */ +}; + +/* Tag Based VLAN in ADM_SYSC3 */ +#define ADM_TBV (1 << 5) + +static const u8 adm_portcfg[] = { + [0] = ADM_P0_CFG, + [1] = ADM_P1_CFG, + [2] = ADM_P2_CFG, + [3] = ADM_P3_CFG, + [4] = ADM_P4_CFG, + [5] = ADM_P5_CFG, +}; + +/* Fields in ADM_VLAN_FILT_L(x) */ +#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12) +#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6) +#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0) +#define ADM_VLAN_FILT_MEMBER_MASK 0x3f +/* Fields in ADM_VLAN_FILT_H(x) */ +#define ADM_VLAN_FILT_VALID (1 << 15) +#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0) + + +/* + * Split the register address in phy id and register + * it will get combined again by the mdio bus op + */ +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +#endif diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.c b/target/linux/generic/files/drivers/net/phy/ar8216.c new file mode 100644 index 000000000..4df2dbbdb --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/ar8216.c @@ -0,0 +1,1536 @@ +/* + * ar8216.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org> + * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org> + * + * 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. + */ + +#include <linux/if.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/if_ether.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/netlink.h> +#include <linux/bitops.h> +#include <net/genetlink.h> +#include <linux/switch.h> +#include <linux/delay.h> +#include <linux/phy.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/lockdep.h> +#include <linux/ar8216_platform.h> +#include "ar8216.h" + +/* size of the vlan table */ +#define AR8X16_MAX_VLANS 128 +#define AR8X16_PROBE_RETRIES 10 +#define AR8X16_MAX_PORTS 8 + +struct ar8216_priv; + +#define AR8XXX_CAP_GIGE BIT(0) + +enum { + AR8XXX_VER_AR8216 = 0x01, + AR8XXX_VER_AR8236 = 0x03, + AR8XXX_VER_AR8316 = 0x10, + AR8XXX_VER_AR8327 = 0x12, +}; + +struct ar8xxx_chip { + unsigned long caps; + + int (*hw_init)(struct ar8216_priv *priv); + void (*init_globals)(struct ar8216_priv *priv); + void (*init_port)(struct ar8216_priv *priv, int port); + void (*setup_port)(struct ar8216_priv *priv, int port, u32 egress, + u32 ingress, u32 members, u32 pvid); + u32 (*read_port_status)(struct ar8216_priv *priv, int port); + int (*atu_flush)(struct ar8216_priv *priv); + void (*vtu_flush)(struct ar8216_priv *priv); + void (*vtu_load_vlan)(struct ar8216_priv *priv, u32 vid, u32 port_mask); +}; + +struct ar8216_priv { + struct switch_dev dev; + struct phy_device *phy; + u32 (*read)(struct ar8216_priv *priv, int reg); + void (*write)(struct ar8216_priv *priv, int reg, u32 val); + const struct net_device_ops *ndo_old; + struct net_device_ops ndo; + struct mutex reg_mutex; + u8 chip_ver; + u8 chip_rev; + const struct ar8xxx_chip *chip; + bool initialized; + bool port4_phy; + char buf[80]; + + bool init; + bool mii_lo_first; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[AR8X16_MAX_VLANS]; + u8 vlan_table[AR8X16_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[AR8X16_MAX_PORTS]; +}; + +#define to_ar8216(_dev) container_of(_dev, struct ar8216_priv, dev) + +static inline bool ar8xxx_has_gige(struct ar8216_priv *priv) +{ + return priv->chip->caps & AR8XXX_CAP_GIGE; +} + +static inline bool chip_is_ar8216(struct ar8216_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8216; +} + +static inline bool chip_is_ar8236(struct ar8216_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8236; +} + +static inline bool chip_is_ar8316(struct ar8216_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8316; +} + +static inline bool chip_is_ar8327(struct ar8216_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8327; +} + +static inline void +split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) +{ + regaddr >>= 1; + *r1 = regaddr & 0x1e; + + regaddr >>= 5; + *r2 = regaddr & 0x7; + + regaddr >>= 3; + *page = regaddr & 0x1ff; +} + +static u32 +ar8216_mii_read(struct ar8216_priv *priv, int reg) +{ + struct phy_device *phy = priv->phy; + struct mii_bus *bus = phy->bus; + u16 r1, r2, page; + u16 lo, hi; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + usleep_range(1000, 2000); /* wait for the page switch to propagate */ + lo = bus->read(bus, 0x10 | r2, r1); + hi = bus->read(bus, 0x10 | r2, r1 + 1); + + mutex_unlock(&bus->mdio_lock); + + return (hi << 16) | lo; +} + +static void +ar8216_mii_write(struct ar8216_priv *priv, int reg, u32 val) +{ + struct phy_device *phy = priv->phy; + struct mii_bus *bus = phy->bus; + u16 r1, r2, r3; + u16 lo, hi; + + split_addr((u32) reg, &r1, &r2, &r3); + lo = val & 0xffff; + hi = (u16) (val >> 16); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, r3); + usleep_range(1000, 2000); /* wait for the page switch to propagate */ + if (priv->mii_lo_first) { + bus->write(bus, 0x10 | r2, r1, lo); + bus->write(bus, 0x10 | r2, r1 + 1, hi); + } else { + bus->write(bus, 0x10 | r2, r1 + 1, hi); + bus->write(bus, 0x10 | r2, r1, lo); + } + + mutex_unlock(&bus->mdio_lock); +} + +static void +ar8216_phy_dbg_write(struct ar8216_priv *priv, int phy_addr, + u16 dbg_addr, u16 dbg_data) +{ + struct mii_bus *bus = priv->phy->bus; + + mutex_lock(&bus->mdio_lock); + bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); + bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); + mutex_unlock(&bus->mdio_lock); +} + +static void +ar8216_phy_mmd_write(struct ar8216_priv *priv, int phy_addr, u16 addr, u16 data) +{ + struct mii_bus *bus = priv->phy->bus; + + mutex_lock(&bus->mdio_lock); + bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); + bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); + mutex_unlock(&bus->mdio_lock); +} + +static u32 +ar8216_rmw(struct ar8216_priv *priv, int reg, u32 mask, u32 val) +{ + u32 v; + + lockdep_assert_held(&priv->reg_mutex); + + v = priv->read(priv, reg); + v &= ~mask; + v |= val; + priv->write(priv, reg, v); + + return v; +} + +static void +ar8216_read_port_link(struct ar8216_priv *priv, int port, + struct switch_port_link *link) +{ + u32 status; + u32 speed; + + memset(link, '\0', sizeof(*link)); + + status = priv->chip->read_port_status(priv, port); + + link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); + if (link->aneg) { + link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); + if (!link->link) + return; + } else { + link->link = true; + } + + link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); + link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); + link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); + + speed = (status & AR8216_PORT_STATUS_SPEED) >> + AR8216_PORT_STATUS_SPEED_S; + + switch (speed) { + case AR8216_PORT_SPEED_10M: + link->speed = SWITCH_PORT_SPEED_10; + break; + case AR8216_PORT_SPEED_100M: + link->speed = SWITCH_PORT_SPEED_100; + break; + case AR8216_PORT_SPEED_1000M: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } +} + +static struct sk_buff * +ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8216_priv *priv = dev->phy_ptr; + unsigned char *buf; + + if (unlikely(!priv)) + goto error; + + if (!priv->vlan) + goto send; + + if (unlikely(skb_headroom(skb) < 2)) { + if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) + goto error; + } + + buf = skb_push(skb, 2); + buf[0] = 0x10; + buf[1] = 0x80; + +send: + return skb; + +error: + dev_kfree_skb_any(skb); + return NULL; +} + +static void +ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8216_priv *priv; + unsigned char *buf; + int port, vlan; + + priv = dev->phy_ptr; + if (!priv) + return; + + /* don't strip the header if vlan mode is disabled */ + if (!priv->vlan) + return; + + /* strip header, get vlan id */ + buf = skb->data; + skb_pull(skb, 2); + + /* check for vlan header presence */ + if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) + return; + + port = buf[0] & 0xf; + + /* no need to fix up packets coming from a tagged source */ + if (priv->vlan_tagged & (1 << port)) + return; + + /* lookup port vid from local table, the switch passes an invalid vlan id */ + vlan = priv->vlan_id[priv->pvid[port]]; + + buf[14 + 2] &= 0xf0; + buf[14 + 2] |= vlan >> 8; + buf[15 + 2] = vlan & 0xff; +} + +static int +ar8216_wait_bit(struct ar8216_priv *priv, int reg, u32 mask, u32 val) +{ + int timeout = 20; + u32 t = 0; + + while (1) { + t = priv->read(priv, reg); + if ((t & mask) == val) + return 0; + + if (timeout-- <= 0) + break; + + udelay(10); + } + + pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", + (unsigned int) reg, t, mask, val); + return -ETIMEDOUT; +} + +static void +ar8216_vtu_op(struct ar8216_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) + return; + if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { + val &= AR8216_VTUDATA_MEMBER; + val |= AR8216_VTUDATA_VALID; + priv->write(priv, AR8216_REG_VTU_DATA, val); + } + op |= AR8216_VTU_ACTIVE; + priv->write(priv, AR8216_REG_VTU, op); +} + +static void +ar8216_vtu_flush(struct ar8216_priv *priv) +{ + ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); +} + +static void +ar8216_vtu_load_vlan(struct ar8216_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + + op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); + ar8216_vtu_op(priv, op, port_mask); +} + +static int +ar8216_atu_flush(struct ar8216_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8216_REG_ATU, AR8216_ATU_ACTIVE, 0); + if (!ret) + priv->write(priv, AR8216_REG_ATU, AR8216_ATU_OP_FLUSH); + + return ret; +} + +static u32 +ar8216_read_port_status(struct ar8216_priv *priv, int port) +{ + return priv->read(priv, AR8216_REG_PORT_STATUS(port)); +} + +static void +ar8216_setup_port(struct ar8216_priv *priv, int port, u32 egress, u32 ingress, + u32 members, u32 pvid) +{ + u32 header; + + if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) + header = AR8216_PORT_CTRL_HEADER; + else + header = 0; + + ar8216_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | header | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8216_rmw(priv, AR8216_REG_PORT_VLAN(port), + AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | + AR8216_PORT_VLAN_DEFAULT_ID, + (members << AR8216_PORT_VLAN_DEST_PORTS_S) | + (ingress << AR8216_PORT_VLAN_MODE_S) | + (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); +} + +static int +ar8216_hw_init(struct ar8216_priv *priv) +{ + return 0; +} + +static void +ar8216_init_globals(struct ar8216_priv *priv) +{ + /* standard atheros magic */ + priv->write(priv, 0x38, 0xc000050e); + + ar8216_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8216_GCTRL_MTU, 1518 + 8 + 2); +} + +static void +ar8216_init_port(struct ar8216_priv *priv, int port) +{ + /* Enable port learning and tx */ + priv->write(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | + (4 << AR8216_PORT_CTRL_STATE_S)); + + priv->write(priv, AR8216_REG_PORT_VLAN(port), 0); + + if (port == AR8216_PORT_CPU) { + priv->write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_UP | + (ar8xxx_has_gige(priv) ? + AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | + AR8216_PORT_STATUS_TXMAC | + AR8216_PORT_STATUS_RXMAC | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | + AR8216_PORT_STATUS_DUPLEX); + } else { + priv->write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_AUTO); + } +} + +static const struct ar8xxx_chip ar8216_chip = { + .hw_init = ar8216_hw_init, + .init_globals = ar8216_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, +}; + +static void +ar8236_setup_port(struct ar8216_priv *priv, int port, u32 egress, u32 ingress, + u32 members, u32 pvid) +{ + ar8216_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8216_rmw(priv, AR8236_REG_PORT_VLAN(port), + AR8236_PORT_VLAN_DEFAULT_ID, + (pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); + + ar8216_rmw(priv, AR8236_REG_PORT_VLAN2(port), + AR8236_PORT_VLAN2_VLAN_MODE | + AR8236_PORT_VLAN2_MEMBER, + (ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | + (members << AR8236_PORT_VLAN2_MEMBER_S)); +} + +static int +ar8236_hw_init(struct ar8216_priv *priv) +{ + int i; + struct mii_bus *bus; + + if (priv->initialized) + return 0; + + /* Initialize the PHYs */ + bus = priv->phy->bus; + for (i = 0; i < 5; i++) { + mdiobus_write(bus, i, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | + ADVERTISE_PAUSE_ASYM); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + } + msleep(1000); + + priv->initialized = true; + return 0; +} + +static void +ar8236_init_globals(struct ar8216_priv *priv) +{ + /* enable jumbo frames */ + ar8216_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); +} + +static const struct ar8xxx_chip ar8236_chip = { + .hw_init = ar8236_hw_init, + .init_globals = ar8236_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8236_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, +}; + +static int +ar8316_hw_init(struct ar8216_priv *priv) +{ + int i; + u32 val, newval; + struct mii_bus *bus; + + val = priv->read(priv, 0x8); + + if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + if (priv->port4_phy) { + /* value taken from Ubiquiti RouterStation Pro */ + newval = 0x81461bea; + printk(KERN_INFO "ar8316: Using port 4 as PHY\n"); + } else { + newval = 0x01261be2; + printk(KERN_INFO "ar8316: Using port 4 as switch port\n"); + } + } else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { + /* value taken from AVM Fritz!Box 7390 sources */ + newval = 0x010e5b71; + } else { + /* no known value for phy interface */ + printk(KERN_ERR "ar8316: unsupported mii mode: %d.\n", + priv->phy->interface); + return -EINVAL; + } + + if (val == newval) + goto out; + + priv->write(priv, 0x8, newval); + + /* Initialize the ports */ + bus = priv->phy->bus; + for (i = 0; i < 5; i++) { + if ((i == 4) && priv->port4_phy && + priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + /* work around for phy4 rgmii mode */ + ar8216_phy_dbg_write(priv, i, 0x12, 0x480c); + /* rx delay */ + ar8216_phy_dbg_write(priv, i, 0x0, 0x824e); + /* tx delay */ + ar8216_phy_dbg_write(priv, i, 0x5, 0x3d47); + msleep(1000); + } + + /* initialize the port itself */ + mdiobus_write(bus, i, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + msleep(1000); + } + +out: + priv->initialized = true; + return 0; +} + +static void +ar8316_init_globals(struct ar8216_priv *priv) +{ + /* standard atheros magic */ + priv->write(priv, 0x38, 0xc000050e); + + /* enable cpu port to receive multicast and broadcast frames */ + priv->write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); + + /* enable jumbo frames */ + ar8216_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); +} + +static const struct ar8xxx_chip ar8316_chip = { + .caps = AR8XXX_CAP_GIGE, + .hw_init = ar8316_hw_init, + .init_globals = ar8316_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, +}; + +static u32 +ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg) +{ + u32 t; + + if (!cfg) + return 0; + + t = 0; + switch (cfg->mode) { + case AR8327_PAD_NC: + break; + + case AR8327_PAD_MAC2MAC_MII: + t = AR8327_PAD_MAC_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2MAC_GMII: + t = AR8327_PAD_MAC_GMII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_SGMII: + t = AR8327_PAD_SGMII_EN; + break; + + case AR8327_PAD_MAC2PHY_MII: + t = AR8327_PAD_PHY_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2PHY_GMII: + t = AR8327_PAD_PHY_GMII_EN; + if (cfg->pipe_rxclk_sel) + t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_RGMII: + t = AR8327_PAD_RGMII_EN; + t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; + t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; + if (cfg->rxclk_delay_en) + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + if (cfg->txclk_delay_en) + t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; + break; + + case AR8327_PAD_PHY_GMII: + t = AR8327_PAD_PHYX_GMII_EN; + break; + + case AR8327_PAD_PHY_RGMII: + t = AR8327_PAD_PHYX_RGMII_EN; + break; + + case AR8327_PAD_PHY_MII: + t = AR8327_PAD_PHYX_MII_EN; + break; + } + + return t; +} + +static void +ar8327_phy_fixup(struct ar8216_priv *priv, int phy) +{ + switch (priv->chip_rev) { + case 1: + /* For 100M waveform */ + ar8216_phy_dbg_write(priv, phy, 0, 0x02ea); + /* Turn on Gigabit clock */ + ar8216_phy_dbg_write(priv, phy, 0x3d, 0x68a0); + break; + + case 2: + ar8216_phy_mmd_write(priv, phy, 0x7, 0x3c); + ar8216_phy_mmd_write(priv, phy, 0x4007, 0x0); + /* fallthrough */ + case 4: + ar8216_phy_mmd_write(priv, phy, 0x3, 0x800d); + ar8216_phy_mmd_write(priv, phy, 0x4003, 0x803f); + + ar8216_phy_dbg_write(priv, phy, 0x3d, 0x6860); + ar8216_phy_dbg_write(priv, phy, 0x5, 0x2c46); + ar8216_phy_dbg_write(priv, phy, 0x3c, 0x6000); + break; + } +} + +static int +ar8327_hw_init(struct ar8216_priv *priv) +{ + struct ar8327_platform_data *pdata; + struct ar8327_led_cfg *led_cfg; + struct mii_bus *bus; + u32 pos, new_pos; + u32 t; + int i; + + pdata = priv->phy->dev.platform_data; + if (!pdata) + return -EINVAL; + + t = ar8327_get_pad_cfg(pdata->pad0_cfg); + priv->write(priv, AR8327_REG_PAD0_MODE, t); + t = ar8327_get_pad_cfg(pdata->pad5_cfg); + priv->write(priv, AR8327_REG_PAD5_MODE, t); + t = ar8327_get_pad_cfg(pdata->pad6_cfg); + priv->write(priv, AR8327_REG_PAD6_MODE, t); + + pos = priv->read(priv, AR8327_REG_POWER_ON_STRIP); + new_pos = pos; + + led_cfg = pdata->led_cfg; + if (led_cfg) { + if (led_cfg->open_drain) + new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN; + else + new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN; + + priv->write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0); + priv->write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1); + priv->write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2); + priv->write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3); + } + + if (new_pos != pos) { + new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL; + priv->write(priv, AR8327_REG_POWER_ON_STRIP, new_pos); + } + + bus = priv->phy->bus; + for (i = 0; i < AR8327_NUM_PHYS; i++) { + ar8327_phy_fixup(priv, i); + + /* start aneg on the PHY */ + mdiobus_write(bus, i, MII_ADVERTISE, ADVERTISE_ALL | + ADVERTISE_PAUSE_CAP | + ADVERTISE_PAUSE_ASYM); + mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + } + + msleep(1000); + + return 0; +} + +static void +ar8327_init_globals(struct ar8216_priv *priv) +{ + u32 t; + + /* enable CPU port and disable mirror port */ + t = AR8327_FWD_CTRL0_CPU_PORT_EN | + AR8327_FWD_CTRL0_MIRROR_PORT; + priv->write(priv, AR8327_REG_FWD_CTRL0, t); + + /* forward multicast and broadcast frames to CPU */ + t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S); + priv->write(priv, AR8327_REG_FWD_CTRL1, t); + + /* setup MTU */ + ar8216_rmw(priv, AR8327_REG_MAX_FRAME_SIZE, + AR8327_MAX_FRAME_SIZE_MTU, 1518 + 8 + 2); +} + +static void +ar8327_init_cpuport(struct ar8216_priv *priv) +{ + struct ar8327_platform_data *pdata; + struct ar8327_port_cfg *cfg; + u32 t; + + pdata = priv->phy->dev.platform_data; + if (!pdata) + return; + + cfg = &pdata->cpuport_cfg; + if (!cfg->force_link) { + priv->write(priv, AR8327_REG_PORT_STATUS(AR8216_PORT_CPU), + AR8216_PORT_STATUS_LINK_AUTO); + return; + } + + t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC; + t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0; + t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0; + t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0; + switch (cfg->speed) { + case AR8327_PORT_SPEED_10: + t |= AR8216_PORT_SPEED_10M; + break; + case AR8327_PORT_SPEED_100: + t |= AR8216_PORT_SPEED_100M; + break; + case AR8327_PORT_SPEED_1000: + t |= AR8216_PORT_SPEED_1000M; + break; + } + + priv->write(priv, AR8327_REG_PORT_STATUS(AR8216_PORT_CPU), t); +} + +static void +ar8327_init_port(struct ar8216_priv *priv, int port) +{ + u32 t; + + if (port == AR8216_PORT_CPU) { + ar8327_init_cpuport(priv); + } else { + t = AR8216_PORT_STATUS_LINK_AUTO; + priv->write(priv, AR8327_REG_PORT_STATUS(port), t); + } + + priv->write(priv, AR8327_REG_PORT_HEADER(port), 0); + + priv->write(priv, AR8327_REG_PORT_VLAN0(port), 0); + + t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; + priv->write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = AR8327_PORT_LOOKUP_LEARN; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + priv->write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static u32 +ar8327_read_port_status(struct ar8216_priv *priv, int port) +{ + return priv->read(priv, AR8327_REG_PORT_STATUS(port)); +} + +static int +ar8327_atu_flush(struct ar8216_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) + priv->write(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_OP_FLUSH); + + return ret; +} + +static void +ar8327_vtu_op(struct ar8216_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1, + AR8327_VTU_FUNC1_BUSY, 0)) + return; + + if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) + priv->write(priv, AR8327_REG_VTU_FUNC0, val); + + op |= AR8327_VTU_FUNC1_BUSY; + priv->write(priv, AR8327_REG_VTU_FUNC1, op); +} + +static void +ar8327_vtu_flush(struct ar8216_priv *priv) +{ + ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0); +} + +static void +ar8327_vtu_load_vlan(struct ar8216_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + u32 val; + int i; + + op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S); + val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL; + for (i = 0; i < AR8327_NUM_PORTS; i++) { + u32 mode; + + if ((port_mask & BIT(i)) == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_NOT; + else if (priv->vlan == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_KEEP; + else if (priv->vlan_tagged & BIT(i)) + mode = AR8327_VTU_FUNC0_EG_MODE_TAG; + else + mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG; + + val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i); + } + ar8327_vtu_op(priv, op, val); +} + +static void +ar8327_setup_port(struct ar8216_priv *priv, int port, u32 egress, u32 ingress, + u32 members, u32 pvid) +{ + u32 t; + u32 mode; + + t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S; + t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S; + priv->write(priv, AR8327_REG_PORT_VLAN0(port), t); + + mode = AR8327_PORT_VLAN1_OUT_MODE_UNMOD; + switch (egress) { + case AR8216_OUT_KEEP: + mode = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH; + break; + case AR8216_OUT_STRIP_VLAN: + mode = AR8327_PORT_VLAN1_OUT_MODE_UNTAG; + break; + case AR8216_OUT_ADD_VLAN: + mode = AR8327_PORT_VLAN1_OUT_MODE_TAG; + break; + } + + t = AR8327_PORT_VLAN1_PORT_VLAN_PROP; + t |= mode << AR8327_PORT_VLAN1_OUT_MODE_S; + priv->write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = members; + t |= AR8327_PORT_LOOKUP_LEARN; + t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + priv->write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static const struct ar8xxx_chip ar8327_chip = { + .caps = AR8XXX_CAP_GIGE, + .hw_init = ar8327_hw_init, + .init_globals = ar8327_init_globals, + .init_port = ar8327_init_port, + .setup_port = ar8327_setup_port, + .read_port_status = ar8327_read_port_status, + .atu_flush = ar8327_atu_flush, + .vtu_flush = ar8327_vtu_flush, + .vtu_load_vlan = ar8327_vtu_load_vlan, +}; + +static int +ar8216_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + priv->vlan = !!val->value.i; + return 0; +} + +static int +ar8216_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + val->value.i = priv->vlan; + return 0; +} + + +static int +ar8216_sw_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct ar8216_priv *priv = to_ar8216(dev); + + /* make sure no invalid PVIDs get set */ + + if (vlan >= dev->vlans) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +static int +ar8216_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct ar8216_priv *priv = to_ar8216(dev); + *vlan = priv->pvid[port]; + return 0; +} + +static int +ar8216_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +ar8216_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +static int +ar8216_sw_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct ar8216_priv *priv = to_ar8216(dev); + + ar8216_read_port_link(priv, port, link); + return 0; +} + +static int +ar8216_sw_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < dev->ports; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (priv->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar8216_sw_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8216_priv *priv = to_ar8216(dev); + u8 *vt = &priv->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)) { + priv->vlan_tagged |= (1 << p->id); + } else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static int +ar8216_sw_hw_apply(struct switch_dev *dev) +{ + struct ar8216_priv *priv = to_ar8216(dev); + u8 portmask[AR8X16_MAX_PORTS]; + int i, j; + + mutex_lock(&priv->reg_mutex); + /* flush all vlan translation unit entries */ + priv->chip->vtu_flush(priv); + + memset(portmask, 0, sizeof(portmask)); + if (!priv->init) { + /* calculate the port destination masks and load vlans + * into the vlan translation unit */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (!vp) + continue; + + for (i = 0; i < dev->ports; i++) { + u8 mask = (1 << i); + if (vp & mask) + portmask[i] |= vp & ~mask; + } + + priv->chip->vtu_load_vlan(priv, priv->vlan_id[j], + priv->vlan_table[j]); + } + } else { + /* vlan disabled: + * isolate all ports, but connect them to the cpu port */ + for (i = 0; i < dev->ports; i++) { + if (i == AR8216_PORT_CPU) + continue; + + portmask[i] = 1 << AR8216_PORT_CPU; + portmask[AR8216_PORT_CPU] |= (1 << i); + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < dev->ports; i++) { + int egress, ingress; + int pvid; + + if (priv->vlan) { + pvid = priv->vlan_id[priv->pvid[i]]; + if (priv->vlan_tagged & (1 << i)) + egress = AR8216_OUT_ADD_VLAN; + else + egress = AR8216_OUT_STRIP_VLAN; + ingress = AR8216_IN_SECURE; + } else { + pvid = i; + egress = AR8216_OUT_KEEP; + ingress = AR8216_IN_PORT_ONLY; + } + + priv->chip->setup_port(priv, i, egress, ingress, portmask[i], + pvid); + } + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int +ar8216_sw_reset_switch(struct switch_dev *dev) +{ + struct ar8216_priv *priv = to_ar8216(dev); + int i; + + mutex_lock(&priv->reg_mutex); + memset(&priv->vlan, 0, sizeof(struct ar8216_priv) - + offsetof(struct ar8216_priv, vlan)); + + for (i = 0; i < AR8X16_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + /* Configure all ports */ + for (i = 0; i < dev->ports; i++) + priv->chip->init_port(priv, i); + + priv->chip->init_globals(priv); + mutex_unlock(&priv->reg_mutex); + + return ar8216_sw_hw_apply(dev); +} + +static struct switch_attr ar8216_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar8216_sw_set_vlan, + .get = ar8216_sw_get_vlan, + .max = 1 + }, +}; + +static struct switch_attr ar8216_port[] = { +}; + +static struct switch_attr ar8216_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = ar8216_sw_set_vid, + .get = ar8216_sw_get_vid, + .max = 4094, + }, +}; + +static const struct switch_dev_ops ar8216_sw_ops = { + .attr_global = { + .attr = ar8216_globals, + .n_attr = ARRAY_SIZE(ar8216_globals), + }, + .attr_port = { + .attr = ar8216_port, + .n_attr = ARRAY_SIZE(ar8216_port), + }, + .attr_vlan = { + .attr = ar8216_vlan, + .n_attr = ARRAY_SIZE(ar8216_vlan), + }, + .get_port_pvid = ar8216_sw_get_pvid, + .set_port_pvid = ar8216_sw_set_pvid, + .get_vlan_ports = ar8216_sw_get_ports, + .set_vlan_ports = ar8216_sw_set_ports, + .apply_config = ar8216_sw_hw_apply, + .reset_switch = ar8216_sw_reset_switch, + .get_port_link = ar8216_sw_get_port_link, +}; + +static int +ar8216_id_chip(struct ar8216_priv *priv) +{ + u32 val; + u16 id; + int i; + + val = ar8216_mii_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { + u16 t; + + val = ar8216_mii_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + if (t != id) + return -ENODEV; + } + + priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; + priv->chip_rev = (id & AR8216_CTRL_REVISION); + + switch (priv->chip_ver) { + case AR8XXX_VER_AR8216: + priv->chip = &ar8216_chip; + break; + case AR8XXX_VER_AR8236: + priv->chip = &ar8236_chip; + break; + case AR8XXX_VER_AR8316: + priv->chip = &ar8316_chip; + break; + case AR8XXX_VER_AR8327: + priv->mii_lo_first = true; + priv->chip = &ar8327_chip; + break; + default: + printk(KERN_DEBUG + "ar8216: Unknown Atheros device [ver=%d, rev=%d, phy_id=%04x%04x]\n", + priv->chip_ver, priv->chip_rev, + mdiobus_read(priv->phy->bus, priv->phy->addr, 2), + mdiobus_read(priv->phy->bus, priv->phy->addr, 3)); + + return -ENODEV; + } + + return 0; +} + +static int +ar8216_config_init(struct phy_device *pdev) +{ + struct ar8216_priv *priv = pdev->priv; + struct net_device *dev = pdev->attached_dev; + struct switch_dev *swdev; + int ret; + + if (!priv) { + priv = kzalloc(sizeof(struct ar8216_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + } + + priv->phy = pdev; + + ret = ar8216_id_chip(priv); + if (ret) + goto err_free_priv; + + if (pdev->addr != 0) { + if (ar8xxx_has_gige(priv)) { + pdev->supported |= SUPPORTED_1000baseT_Full; + pdev->advertising |= ADVERTISED_1000baseT_Full; + } + + if (chip_is_ar8316(priv)) { + /* check if we're attaching to the switch twice */ + pdev = pdev->bus->phy_map[0]; + if (!pdev) { + kfree(priv); + return 0; + } + + /* switch device has not been initialized, reuse priv */ + if (!pdev->priv) { + priv->port4_phy = true; + pdev->priv = priv; + return 0; + } + + kfree(priv); + + /* switch device has been initialized, reinit */ + priv = pdev->priv; + priv->dev.ports = (AR8216_NUM_PORTS - 1); + priv->initialized = false; + priv->port4_phy = true; + ar8316_hw_init(priv); + return 0; + } + + kfree(priv); + return 0; + } + + if (ar8xxx_has_gige(priv)) + pdev->supported = SUPPORTED_1000baseT_Full; + else + pdev->supported = SUPPORTED_100baseT_Full; + pdev->advertising = pdev->supported; + + mutex_init(&priv->reg_mutex); + priv->read = ar8216_mii_read; + priv->write = ar8216_mii_write; + + pdev->priv = priv; + + swdev = &priv->dev; + swdev->cpu_port = AR8216_PORT_CPU; + swdev->ops = &ar8216_sw_ops; + swdev->ports = AR8216_NUM_PORTS; + + if (chip_is_ar8316(priv)) { + swdev->name = "Atheros AR8316"; + swdev->vlans = AR8X16_MAX_VLANS; + + if (priv->port4_phy) { + /* port 5 connected to the other mac, therefore unusable */ + swdev->ports = (AR8216_NUM_PORTS - 1); + } + } else if (chip_is_ar8236(priv)) { + swdev->name = "Atheros AR8236"; + swdev->vlans = AR8216_NUM_VLANS; + swdev->ports = AR8216_NUM_PORTS; + } else if (chip_is_ar8327(priv)) { + swdev->name = "Atheros AR8327"; + swdev->vlans = AR8X16_MAX_VLANS; + swdev->ports = AR8327_NUM_PORTS; + } else { + swdev->name = "Atheros AR8216"; + swdev->vlans = AR8216_NUM_VLANS; + } + + ret = register_switch(&priv->dev, pdev->attached_dev); + if (ret) + goto err_free_priv; + + printk(KERN_INFO "%s: %s switch driver attached.\n", + pdev->attached_dev->name, swdev->name); + + priv->init = true; + + ret = priv->chip->hw_init(priv); + if (ret) + goto err_free_priv; + + ret = ar8216_sw_reset_switch(&priv->dev); + if (ret) + goto err_free_priv; + + dev->phy_ptr = priv; + + /* VID fixup only needed on ar8216 */ + if (chip_is_ar8216(priv) && pdev->addr == 0) { + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = ar8216_mangle_rx; + dev->eth_mangle_tx = ar8216_mangle_tx; + } + + priv->init = false; + + return 0; + +err_free_priv: + kfree(priv); + return ret; +} + +static int +ar8216_read_status(struct phy_device *phydev) +{ + struct ar8216_priv *priv = phydev->priv; + struct switch_port_link link; + int ret; + + if (phydev->addr != 0) + return genphy_read_status(phydev); + + ar8216_read_port_link(priv, phydev->addr, &link); + phydev->link = !!link.link; + if (!phydev->link) + return 0; + + switch (link.speed) { + case SWITCH_PORT_SPEED_10: + phydev->speed = SPEED_10; + break; + case SWITCH_PORT_SPEED_100: + phydev->speed = SPEED_100; + break; + case SWITCH_PORT_SPEED_1000: + phydev->speed = SPEED_1000; + break; + default: + phydev->speed = 0; + } + phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; + + /* flush the address translation unit */ + mutex_lock(&priv->reg_mutex); + ret = priv->chip->atu_flush(priv); + mutex_unlock(&priv->reg_mutex); + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return ret; +} + +static int +ar8216_config_aneg(struct phy_device *phydev) +{ + if (phydev->addr == 0) + return 0; + + return genphy_config_aneg(phydev); +} + +static int +ar8216_probe(struct phy_device *pdev) +{ + struct ar8216_priv priv; + + priv.phy = pdev; + return ar8216_id_chip(&priv); +} + +static void +ar8216_remove(struct phy_device *pdev) +{ + struct ar8216_priv *priv = pdev->priv; + struct net_device *dev = pdev->attached_dev; + + if (!priv) + return; + + dev->priv_flags &= ~IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; + + if (pdev->addr == 0) + unregister_switch(&priv->dev); + kfree(priv); +} + +static struct phy_driver ar8216_driver = { + .phy_id = 0x004d0000, + .name = "Atheros AR8216/AR8236/AR8316", + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = ar8216_probe, + .remove = ar8216_remove, + .config_init = &ar8216_config_init, + .config_aneg = &ar8216_config_aneg, + .read_status = &ar8216_read_status, + .driver = { .owner = THIS_MODULE }, +}; + +int __init +ar8216_init(void) +{ + return phy_driver_register(&ar8216_driver); +} + +void __exit +ar8216_exit(void) +{ + phy_driver_unregister(&ar8216_driver); +} + +module_init(ar8216_init); +module_exit(ar8216_exit); +MODULE_LICENSE("GPL"); + diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.h b/target/linux/generic/files/drivers/net/phy/ar8216.h new file mode 100644 index 000000000..8948c5205 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/ar8216.h @@ -0,0 +1,341 @@ +/* + * ar8216.h: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org> + * + * 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. + */ + +#ifndef __AR8216_H +#define __AR8216_H + +#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) + +#define AR8216_PORT_CPU 0 +#define AR8216_NUM_PORTS 6 +#define AR8216_NUM_VLANS 16 +#define AR8316_NUM_VLANS 4096 + +/* Atheros specific MII registers */ +#define MII_ATH_MMD_ADDR 0x0d +#define MII_ATH_MMD_DATA 0x0e +#define MII_ATH_DBG_ADDR 0x1d +#define MII_ATH_DBG_DATA 0x1e + +#define AR8216_REG_CTRL 0x0000 +#define AR8216_CTRL_REVISION BITS(0, 8) +#define AR8216_CTRL_REVISION_S 0 +#define AR8216_CTRL_VERSION BITS(8, 8) +#define AR8216_CTRL_VERSION_S 8 +#define AR8216_CTRL_RESET BIT(31) + +#define AR8216_REG_FLOOD_MASK 0x002C +#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6) +#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6) + +#define AR8216_REG_GLOBAL_CTRL 0x0030 +#define AR8216_GCTRL_MTU BITS(0, 11) +#define AR8236_GCTRL_MTU BITS(0, 14) +#define AR8316_GCTRL_MTU BITS(0, 14) + +#define AR8216_REG_VTU 0x0040 +#define AR8216_VTU_OP BITS(0, 3) +#define AR8216_VTU_OP_NOOP 0x0 +#define AR8216_VTU_OP_FLUSH 0x1 +#define AR8216_VTU_OP_LOAD 0x2 +#define AR8216_VTU_OP_PURGE 0x3 +#define AR8216_VTU_OP_REMOVE_PORT 0x4 +#define AR8216_VTU_ACTIVE BIT(3) +#define AR8216_VTU_FULL BIT(4) +#define AR8216_VTU_PORT BITS(8, 4) +#define AR8216_VTU_PORT_S 8 +#define AR8216_VTU_VID BITS(16, 12) +#define AR8216_VTU_VID_S 16 +#define AR8216_VTU_PRIO BITS(28, 3) +#define AR8216_VTU_PRIO_S 28 +#define AR8216_VTU_PRIO_EN BIT(31) + +#define AR8216_REG_VTU_DATA 0x0044 +#define AR8216_VTUDATA_MEMBER BITS(0, 10) +#define AR8236_VTUDATA_MEMBER BITS(0, 7) +#define AR8216_VTUDATA_VALID BIT(11) + +#define AR8216_REG_ATU 0x0050 +#define AR8216_ATU_OP BITS(0, 3) +#define AR8216_ATU_OP_NOOP 0x0 +#define AR8216_ATU_OP_FLUSH 0x1 +#define AR8216_ATU_OP_LOAD 0x2 +#define AR8216_ATU_OP_PURGE 0x3 +#define AR8216_ATU_OP_FLUSH_LOCKED 0x4 +#define AR8216_ATU_OP_FLUSH_UNICAST 0x5 +#define AR8216_ATU_OP_GET_NEXT 0x6 +#define AR8216_ATU_ACTIVE BIT(3) +#define AR8216_ATU_PORT_NUM BITS(8, 4) +#define AR8216_ATU_FULL_VIO BIT(12) +#define AR8216_ATU_ADDR4 BITS(16, 8) +#define AR8216_ATU_ADDR5 BITS(24, 8) + +#define AR8216_REG_ATU_DATA 0x0054 +#define AR8216_ATU_ADDR3 BITS(0, 8) +#define AR8216_ATU_ADDR2 BITS(8, 8) +#define AR8216_ATU_ADDR1 BITS(16, 8) +#define AR8216_ATU_ADDR0 BITS(24, 8) + +#define AR8216_REG_ATU_CTRL 0x005C +#define AR8216_ATU_CTRL_AGE_EN BIT(17) +#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16) +#define AR8216_ATU_CTRL_AGE_TIME_S 0 + +#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1)) +#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000) +#define AR8216_PORT_STATUS_SPEED BITS(0,2) +#define AR8216_PORT_STATUS_SPEED_S 0 +#define AR8216_PORT_STATUS_TXMAC BIT(2) +#define AR8216_PORT_STATUS_RXMAC BIT(3) +#define AR8216_PORT_STATUS_TXFLOW BIT(4) +#define AR8216_PORT_STATUS_RXFLOW BIT(5) +#define AR8216_PORT_STATUS_DUPLEX BIT(6) +#define AR8216_PORT_STATUS_LINK_UP BIT(8) +#define AR8216_PORT_STATUS_LINK_AUTO BIT(9) +#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10) + +#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004) + +/* port forwarding state */ +#define AR8216_PORT_CTRL_STATE BITS(0, 3) +#define AR8216_PORT_CTRL_STATE_S 0 + +#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7) + +/* egress 802.1q mode */ +#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2) +#define AR8216_PORT_CTRL_VLAN_MODE_S 8 + +#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10) +#define AR8216_PORT_CTRL_HEADER BIT(11) +#define AR8216_PORT_CTRL_MAC_LOOP BIT(12) +#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13) +#define AR8216_PORT_CTRL_LEARN BIT(14) +#define AR8216_PORT_CTRL_MIRROR_TX BIT(16) +#define AR8216_PORT_CTRL_MIRROR_RX BIT(17) + +#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008) + +#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12) +#define AR8216_PORT_VLAN_DEFAULT_ID_S 0 + +#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9) +#define AR8216_PORT_VLAN_DEST_PORTS_S 16 + +/* bit0 added to the priority field of egress frames */ +#define AR8216_PORT_VLAN_TX_PRIO BIT(27) + +/* port default priority */ +#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2) +#define AR8216_PORT_VLAN_PRIORITY_S 28 + +/* ingress 802.1q mode */ +#define AR8216_PORT_VLAN_MODE BITS(30, 2) +#define AR8216_PORT_VLAN_MODE_S 30 + +#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c) +#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010) + + +#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008) +#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) +#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 +#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) +#define AR8236_PORT_VLAN_PRIORITY_S 28 + +#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c) +#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) +#define AR8236_PORT_VLAN2_MEMBER_S 16 +#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) +#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) +#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 + +#define AR8327_NUM_PORTS 7 +#define AR8327_NUM_PHYS 5 +#define AR8327_PORTS_ALL 0x7f + +#define AR8327_REG_MASK 0x000 + +#define AR8327_REG_PAD0_MODE 0x004 +#define AR8327_REG_PAD5_MODE 0x008 +#define AR8327_REG_PAD6_MODE 0x00c +#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0) +#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1) +#define AR8327_PAD_MAC_MII_EN BIT(2) +#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4) +#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5) +#define AR8327_PAD_MAC_GMII_EN BIT(6) +#define AR8327_PAD_SGMII_EN BIT(7) +#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8) +#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9) +#define AR8327_PAD_PHY_MII_EN BIT(10) +#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11) +#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12) +#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13) +#define AR8327_PAD_PHY_GMII_EN BIT(14) +#define AR8327_PAD_PHYX_GMII_EN BIT(16) +#define AR8327_PAD_PHYX_RGMII_EN BIT(17) +#define AR8327_PAD_PHYX_MII_EN BIT(18) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20 +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2) +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22 +#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24) +#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25) +#define AR8327_PAD_RGMII_EN BIT(26) + +#define AR8327_REG_POWER_ON_STRIP 0x010 +#define AR8327_POWER_ON_STRIP_POWER_ON_SEL BIT(31) +#define AR8327_POWER_ON_STRIP_LED_OPEN_EN BIT(24) + +#define AR8327_REG_INT_STATUS0 0x020 +#define AR8327_INT0_VT_DONE BIT(20) + +#define AR8327_REG_INT_STATUS1 0x024 +#define AR8327_REG_INT_MASK0 0x028 +#define AR8327_REG_INT_MASK1 0x02c +#define AR8327_REG_SERVICE_TAG 0x048 +#define AR8327_REG_LED_CTRL0 0x050 +#define AR8327_REG_LED_CTRL1 0x054 +#define AR8327_REG_LED_CTRL2 0x058 +#define AR8327_REG_LED_CTRL3 0x05c +#define AR8327_REG_MAC_ADDR0 0x060 +#define AR8327_REG_MAC_ADDR1 0x064 + +#define AR8327_REG_MAX_FRAME_SIZE 0x078 +#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14) + +#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) + +#define AR8327_REG_HEADER_CTRL 0x098 +#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) + +#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) +#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12) +#define AR8327_PORT_VLAN0_DEF_SVID_S 0 +#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12) +#define AR8327_PORT_VLAN0_DEF_CVID_S 16 + +#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) +#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6) +#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2) +#define AR8327_PORT_VLAN1_OUT_MODE_S 12 +#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1 +#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3 + +#define AR8327_REG_ATU_DATA0 0x600 +#define AR8327_REG_ATU_DATA1 0x604 +#define AR8327_REG_ATU_DATA2 0x608 + +#define AR8327_REG_ATU_FUNC 0x60c +#define AR8327_ATU_FUNC_OP BITS(0, 4) +#define AR8327_ATU_FUNC_OP_NOOP 0x0 +#define AR8327_ATU_FUNC_OP_FLUSH 0x1 +#define AR8327_ATU_FUNC_OP_LOAD 0x2 +#define AR8327_ATU_FUNC_OP_PURGE 0x3 +#define AR8327_ATU_FUNC_OP_FLUSH_LOCKED 0x4 +#define AR8327_ATU_FUNC_OP_FLUSH_UNICAST 0x5 +#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6 +#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7 +#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8 +#define AR8327_ATU_FUNC_BUSY BIT(31) + +#define AR8327_REG_VTU_FUNC0 0x0610 +#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14) +#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) +#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0 +#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1 +#define AR8327_VTU_FUNC0_EG_MODE_TAG 2 +#define AR8327_VTU_FUNC0_EG_MODE_NOT 3 +#define AR8327_VTU_FUNC0_IVL BIT(19) +#define AR8327_VTU_FUNC0_VALID BIT(20) + +#define AR8327_REG_VTU_FUNC1 0x0614 +#define AR8327_VTU_FUNC1_OP BITS(0, 3) +#define AR8327_VTU_FUNC1_OP_NOOP 0 +#define AR8327_VTU_FUNC1_OP_FLUSH 1 +#define AR8327_VTU_FUNC1_OP_LOAD 2 +#define AR8327_VTU_FUNC1_OP_PURGE 3 +#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4 +#define AR8327_VTU_FUNC1_OP_GET_NEXT 5 +#define AR8327_VTU_FUNC1_OP_GET_ONE 6 +#define AR8327_VTU_FUNC1_FULL BIT(4) +#define AR8327_VTU_FUNC1_PORT BIT(8, 4) +#define AR8327_VTU_FUNC1_PORT_S 8 +#define AR8327_VTU_FUNC1_VID BIT(16, 12) +#define AR8327_VTU_FUNC1_VID_S 16 +#define AR8327_VTU_FUNC1_BUSY BIT(31) + +#define AR8327_REG_FWD_CTRL0 0x620 +#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10) +#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4) +#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4 + +#define AR8327_REG_FWD_CTRL1 0x624 +#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7) +#define AR8327_FWD_CTRL1_UC_FLOOD_S 0 +#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7) +#define AR8327_FWD_CTRL1_MC_FLOOD_S 8 +#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7) +#define AR8327_FWD_CTRL1_BC_FLOOD_S 16 +#define AR8327_FWD_CTRL1_IGMP BITS(24, 7) +#define AR8327_FWD_CTRL1_IGMP_S 24 + +#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) +#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7) +#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2) +#define AR8327_PORT_LOOKUP_IN_MODE_S 8 +#define AR8327_PORT_LOOKUP_STATE BITS(16, 3) +#define AR8327_PORT_LOOKUP_STATE_S 16 +#define AR8327_PORT_LOOKUP_LEARN BIT(20) + +#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc) + +/* port speed */ +enum { + AR8216_PORT_SPEED_10M = 0, + AR8216_PORT_SPEED_100M = 1, + AR8216_PORT_SPEED_1000M = 2, + AR8216_PORT_SPEED_ERR = 3, +}; + +/* ingress 802.1q mode */ +enum { + AR8216_IN_PORT_ONLY = 0, + AR8216_IN_PORT_FALLBACK = 1, + AR8216_IN_VLAN_ONLY = 2, + AR8216_IN_SECURE = 3 +}; + +/* egress 802.1q mode */ +enum { + AR8216_OUT_KEEP = 0, + AR8216_OUT_STRIP_VLAN = 1, + AR8216_OUT_ADD_VLAN = 2 +}; + +/* port forwarding state */ +enum { + AR8216_PORT_STATE_DISABLED = 0, + AR8216_PORT_STATE_BLOCK = 1, + AR8216_PORT_STATE_LISTEN = 2, + AR8216_PORT_STATE_LEARN = 3, + AR8216_PORT_STATE_FORWARD = 4 +}; + +#endif diff --git a/target/linux/generic/files/drivers/net/phy/ip17xx.c b/target/linux/generic/files/drivers/net/phy/ip17xx.c new file mode 100644 index 000000000..c82c39e05 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/ip17xx.c @@ -0,0 +1,1410 @@ +/* + * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family + * + * Copyright (C) 2008 Patrick Horn <patrick.horn@gmail.com> + * Copyright (C) 2008, 2010 Martin Mares <mj@ucw.cz> + * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/skbuff.h> +#include <linux/mii.h> +#include <linux/phy.h> +#include <linux/delay.h> +#include <linux/switch.h> +#include <linux/device.h> + +#define MAX_VLANS 16 +#define MAX_PORTS 9 +#undef DUMP_MII_IO + +typedef struct ip17xx_reg { + u16 p; // phy + u16 m; // mii +} reg; +typedef char bitnum; + +#define NOTSUPPORTED {-1,-1} + +#define REG_SUPP(x) (((x).m != ((u16)-1)) && ((x).p != (u16)-1)) + +struct ip17xx_state; + +/*********** CONSTANTS ***********/ +struct register_mappings { + char *NAME; + u16 MODEL_NO; // Compare to bits 4-9 of MII register 0,3. + bitnum NUM_PORTS; + bitnum CPU_PORT; + +/* The default VLAN for each port. + Default: 0x0001 for Ports 0,1,2,3 + 0x0002 for Ports 4,5 */ + reg VLAN_DEFAULT_TAG_REG[MAX_PORTS]; + +/* These ports are tagged. + Default: 0x00 */ + reg ADD_TAG_REG; + reg REMOVE_TAG_REG; + bitnum ADD_TAG_BIT[MAX_PORTS]; +/* These ports are untagged. + Default: 0x00 (i.e. do not alter any VLAN tags...) + Maybe set to 0 if user disables VLANs. */ + bitnum REMOVE_TAG_BIT[MAX_PORTS]; + +/* Port M and Port N are on the same VLAN. + Default: All ports on all VLANs. */ +// Use register {29, 19+N/2} + reg VLAN_LOOKUP_REG; +// Port 5 uses register {30, 18} but same as odd bits. + reg VLAN_LOOKUP_REG_5; // in a different register on IP175C. + bitnum VLAN_LOOKUP_EVEN_BIT[MAX_PORTS]; + bitnum VLAN_LOOKUP_ODD_BIT[MAX_PORTS]; + +/* This VLAN corresponds to which ports. + Default: 0x2f,0x30,0x3f,0x3f... */ + reg TAG_VLAN_MASK_REG; + bitnum TAG_VLAN_MASK_EVEN_BIT[MAX_PORTS]; + bitnum TAG_VLAN_MASK_ODD_BIT[MAX_PORTS]; + + int RESET_VAL; + reg RESET_REG; + + reg MODE_REG; + int MODE_VAL; + +/* General flags */ + reg ROUTER_CONTROL_REG; + reg VLAN_CONTROL_REG; + bitnum TAG_VLAN_BIT; + bitnum ROUTER_EN_BIT; + bitnum NUMLAN_GROUPS_MAX; + bitnum NUMLAN_GROUPS_BIT; + + reg MII_REGISTER_EN; + bitnum MII_REGISTER_EN_BIT; + + // set to 1 for 178C, 0 for 175C. + bitnum SIMPLE_VLAN_REGISTERS; // 175C has two vlans per register but 178C has only one. + + // Pointers to functions which manipulate hardware state + int (*update_state)(struct ip17xx_state *state); + int (*set_vlan_mode)(struct ip17xx_state *state); + int (*reset)(struct ip17xx_state *state); +}; + +static int ip175c_update_state(struct ip17xx_state *state); +static int ip175c_set_vlan_mode(struct ip17xx_state *state); +static int ip175c_reset(struct ip17xx_state *state); + +static const struct register_mappings IP178C = { + .NAME = "IP178C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {30,3},{30,4},{30,5},{30,6},{30,7},{30,8}, + {30,9},{30,10},{30,11}, + }, + + .ADD_TAG_REG = {30,12}, + .ADD_TAG_BIT = {0,1,2,3,4,5,6,7,8}, + .REMOVE_TAG_REG = {30,13}, + .REMOVE_TAG_BIT = {4,5,6,7,8,9,10,11,12}, + + .SIMPLE_VLAN_REGISTERS = 1, + + .VLAN_LOOKUP_REG = {31,0},// +N + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, // not used with SIMPLE_VLAN_REGISTERS + .VLAN_LOOKUP_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .TAG_VLAN_MASK_REG = {30,14}, // +N + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .TAG_VLAN_MASK_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .RESET_VAL = 0x55AA, + .RESET_REG = {30,0}, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = {30,30}, + .ROUTER_EN_BIT = 11, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 8, // {0-2} + + .VLAN_CONTROL_REG = {30,13}, + .TAG_VLAN_BIT = 3, + + .CPU_PORT = 8, + .NUM_PORTS = 9, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175C = { + .NAME = "IP175C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {29,24},{29,25},{29,26},{29,27},{29,28},{29,30}, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {29,23}, + .REMOVE_TAG_REG = {29,23}, + .ADD_TAG_BIT = {11,12,13,14,15,1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,0,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = {29,19},// +N/2 + .VLAN_LOOKUP_REG_5 = {30,18}, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,15,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,7,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = {30,1}, // +N/2 + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {8,9,10,11,12,13,-1,-1,-1}, + + .RESET_VAL = 0x175C, + .RESET_REG = {30,0}, + .MODE_VAL = 0x175C, + .MODE_REG = {29,31}, + + .ROUTER_CONTROL_REG = {30,9}, + .ROUTER_EN_BIT = 3, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 0, // {0-2} + + .VLAN_CONTROL_REG = {30,9}, + .TAG_VLAN_BIT = 7, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175A = { + .NAME = "IP175A", + .MODEL_NO = 0x05, + .VLAN_DEFAULT_TAG_REG = { + {0,24},{0,25},{0,26},{0,27},{0,28},NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {0,23}, + .REMOVE_TAG_REG = {0,23}, + .ADD_TAG_BIT = {11,12,13,14,15,-1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,-1,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + // Only programmable via EEPROM + .VLAN_LOOKUP_REG = NOTSUPPORTED,// +N/2 + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,-1,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,-1,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = NOTSUPPORTED, // +N/2, + .TAG_VLAN_MASK_EVEN_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + + .RESET_VAL = -1, + .RESET_REG = NOTSUPPORTED, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_MAX = -1, + .NUMLAN_GROUPS_BIT = -1, // {0-2} + + .NUM_PORTS = 5, + .CPU_PORT = 4, + + .MII_REGISTER_EN = {0, 18}, + .MII_REGISTER_EN_BIT = 7, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + + +static int ip175d_update_state(struct ip17xx_state *state); +static int ip175d_set_vlan_mode(struct ip17xx_state *state); +static int ip175d_reset(struct ip17xx_state *state); + +static const struct register_mappings IP175D = { + .NAME = "IP175D", + .MODEL_NO = 0x18, + + // The IP175D has a completely different interface, so we leave most + // of the registers undefined and switch to different code paths. + + .VLAN_DEFAULT_TAG_REG = { + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + }, + + .ADD_TAG_REG = NOTSUPPORTED, + .REMOVE_TAG_REG = NOTSUPPORTED, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = NOTSUPPORTED, + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .TAG_VLAN_MASK_REG = NOTSUPPORTED, + + .RESET_VAL = 0x175D, + .RESET_REG = {20,2}, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_BIT = -1, + + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175d_update_state, + .set_vlan_mode = ip175d_set_vlan_mode, + .reset = ip175d_reset, +}; + +struct ip17xx_state { + struct switch_dev dev; + struct mii_bus *mii_bus; + bool registered; + + int router_mode; // ROUTER_EN + int vlan_enabled; // TAG_VLAN_EN + struct port_state { + u16 pvid; + unsigned int shareports; + } ports[MAX_PORTS]; + unsigned int add_tag; + unsigned int remove_tag; + int num_vlans; + struct vlan_state { + unsigned int ports; + unsigned int tag; // VLAN tag (IP175D only) + } vlans[MAX_VLANS]; + const struct register_mappings *regs; + reg proc_mii; // phy/reg for the low level register access via swconfig + + char buf[80]; +}; + +#define get_state(_dev) container_of((_dev), struct ip17xx_state, dev) + +static int ip_phy_read(struct ip17xx_state *state, int port, int reg) +{ + int val = mdiobus_read(state->mii_bus, port, reg); + if (val < 0) + pr_warning("IP17xx: Unable to get MII register %d,%d: error %d\n", port, reg, -val); +#ifdef DUMP_MII_IO + else + pr_debug("IP17xx: Read MII(%d,%d) -> %04x\n", port, reg, val); +#endif + return val; +} + +static int ip_phy_write(struct ip17xx_state *state, int port, int reg, u16 val) +{ + int err; + +#ifdef DUMP_MII_IO + pr_debug("IP17xx: Write MII(%d,%d) <- %04x\n", port, reg, val); +#endif + err = mdiobus_write(state->mii_bus, port, reg, val); + if (err < 0) + pr_warning("IP17xx: Unable to write MII register %d,%d: error %d\n", port, reg, -err); + return err; +} + +static int ip_phy_write_masked(struct ip17xx_state *state, int port, int reg, unsigned int mask, unsigned int data) +{ + int val = ip_phy_read(state, port, reg); + if (val < 0) + return 0; + return ip_phy_write(state, port, reg, (val & ~mask) | data); +} + +static int getPhy(struct ip17xx_state *state, reg mii) +{ + if (!REG_SUPP(mii)) + return -EFAULT; + return ip_phy_read(state, mii.p, mii.m); +} + +static int setPhy(struct ip17xx_state *state, reg mii, u16 value) +{ + int err; + + if (!REG_SUPP(mii)) + return -EFAULT; + err = ip_phy_write(state, mii.p, mii.m, value); + if (err < 0) + return err; + mdelay(2); + getPhy(state, mii); + return 0; +} + + +/** + * These two macros are to simplify the mapping of logical bits to the bits in hardware. + * NOTE: these macros will return if there is an error! + */ + +#define GET_PORT_BITS(state, bits, addr, bit_lookup) \ + do { \ + int i, val = getPhy((state), (addr)); \ + if (val < 0) \ + return val; \ + (bits) = 0; \ + for (i = 0; i < MAX_PORTS; i++) { \ + if ((bit_lookup)[i] == -1) continue; \ + if (val & (1<<(bit_lookup)[i])) \ + (bits) |= (1<<i); \ + } \ + } while (0) + +#define SET_PORT_BITS(state, bits, addr, bit_lookup) \ + do { \ + int i, val = getPhy((state), (addr)); \ + if (val < 0) \ + return val; \ + for (i = 0; i < MAX_PORTS; i++) { \ + unsigned int newmask = ((bits)&(1<<i)); \ + if ((bit_lookup)[i] == -1) continue; \ + val &= ~(1<<(bit_lookup)[i]); \ + val |= ((newmask>>i)<<(bit_lookup)[i]); \ + } \ + val = setPhy((state), (addr), val); \ + if (val < 0) \ + return val; \ + } while (0) + + +static int get_model(struct ip17xx_state *state) +{ + int id1, id2; + int oui_id, model_no, rev_no, chip_no; + + id1 = ip_phy_read(state, 0, 2); + id2 = ip_phy_read(state, 0, 3); + oui_id = (id1 << 6) | ((id2 >> 10) & 0x3f); + model_no = (id2 >> 4) & 0x3f; + rev_no = id2 & 0xf; + pr_debug("IP17xx: Identified oui=%06x model=%02x rev=%X\n", oui_id, model_no, rev_no); + + if (oui_id != 0x0090c3) // No other oui_id should have reached us anyway + return -ENODEV; + + if (model_no == IP175A.MODEL_NO) { + state->regs = &IP175A; + } else if (model_no == IP175C.MODEL_NO) { + /* + * Several models share the same model_no: + * 178C has more PHYs, so we try whether the device responds to a read from PHY5 + * 175D has a new chip ID register + * 175C has neither + */ + if (ip_phy_read(state, 5, 2) == 0x0243) { + state->regs = &IP178C; + } else { + chip_no = ip_phy_read(state, 20, 0); + pr_debug("IP17xx: Chip ID register reads %04x\n", chip_no); + if (chip_no == 0x175d) { + state->regs = &IP175D; + } else { + state->regs = &IP175C; + } + } + } else { + pr_warning("IP17xx: Found an unknown IC+ switch with model number %02x, revision %X.\n", model_no, rev_no); + return -EPERM; + } + return 0; +} + +/*** Low-level functions for the older models ***/ + +/** Only set vlan and router flags in the switch **/ +static int ip175c_set_flags(struct ip17xx_state *state) +{ + int val; + + if (!REG_SUPP(state->regs->ROUTER_CONTROL_REG)) { + return 0; + } + + val = getPhy(state, state->regs->ROUTER_CONTROL_REG); + if (val < 0) { + return val; + } + if (state->regs->ROUTER_EN_BIT >= 0) { + if (state->router_mode) { + val |= (1<<state->regs->ROUTER_EN_BIT); + } else { + val &= (~(1<<state->regs->ROUTER_EN_BIT)); + } + } + if (state->regs->TAG_VLAN_BIT >= 0) { + if (state->vlan_enabled) { + val |= (1<<state->regs->TAG_VLAN_BIT); + } else { + val &= (~(1<<state->regs->TAG_VLAN_BIT)); + } + } + if (state->regs->NUMLAN_GROUPS_BIT >= 0) { + val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<<state->regs->NUMLAN_GROUPS_BIT)); + if (state->num_vlans > state->regs->NUMLAN_GROUPS_MAX) { + val |= state->regs->NUMLAN_GROUPS_MAX << state->regs->NUMLAN_GROUPS_BIT; + } else if (state->num_vlans >= 1) { + val |= (state->num_vlans-1) << state->regs->NUMLAN_GROUPS_BIT; + } + } + return setPhy(state, state->regs->ROUTER_CONTROL_REG, val); +} + +/** Set all VLAN and port state. Usually you should call "correct_vlan_state" first. **/ +static int ip175c_set_state(struct ip17xx_state *state) +{ + int j; + int i; + SET_PORT_BITS(state, state->add_tag, + state->regs->ADD_TAG_REG, state->regs->ADD_TAG_BIT); + SET_PORT_BITS(state, state->remove_tag, + state->regs->REMOVE_TAG_REG, state->regs->REMOVE_TAG_BIT); + + if (REG_SUPP(state->regs->VLAN_LOOKUP_REG)) { + for (j=0; j<state->regs->NUM_PORTS; j++) { + reg addr; + const bitnum *bit_lookup = (j%2==0)? + state->regs->VLAN_LOOKUP_EVEN_BIT: + state->regs->VLAN_LOOKUP_ODD_BIT; + + addr = state->regs->VLAN_LOOKUP_REG; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + switch (j) { + case 0: + case 1: + break; + case 2: + case 3: + addr.m+=1; + break; + case 4: + addr.m+=2; + break; + case 5: + addr = state->regs->VLAN_LOOKUP_REG_5; + break; + default: + addr.m = -1; // shouldn't get here, but... + break; + } + } + //printf("shareports for %d is %02X\n",j,state->ports[j].shareports); + if (REG_SUPP(addr)) { + SET_PORT_BITS(state, state->ports[j].shareports, addr, bit_lookup); + } + } + } + if (REG_SUPP(state->regs->TAG_VLAN_MASK_REG)) { + for (j=0; j<MAX_VLANS; j++) { + reg addr = state->regs->TAG_VLAN_MASK_REG; + const bitnum *bit_lookup = (j%2==0)? + state->regs->TAG_VLAN_MASK_EVEN_BIT: + state->regs->TAG_VLAN_MASK_ODD_BIT; + unsigned int vlan_mask; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + addr.m += j/2; + } + vlan_mask = state->vlans[j].ports; + SET_PORT_BITS(state, vlan_mask, addr, bit_lookup); + } + } + + for (i=0; i<MAX_PORTS; i++) { + if (REG_SUPP(state->regs->VLAN_DEFAULT_TAG_REG[i])) { + int err = setPhy(state, state->regs->VLAN_DEFAULT_TAG_REG[i], + state->ports[i].pvid); + if (err < 0) { + return err; + } + } + } + + return ip175c_set_flags(state); +} + +/** + * Uses only the VLAN port mask and the add tag mask to generate the other fields: + * which ports are part of the same VLAN, removing vlan tags, and VLAN tag ids. + */ +static void ip175c_correct_vlan_state(struct ip17xx_state *state) +{ + int i, j; + state->num_vlans = 0; + for (i=0; i<MAX_VLANS; i++) { + if (state->vlans[i].ports != 0) { + state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere... + } + } + + for (i=0; i<state->regs->NUM_PORTS; i++) { + unsigned int portmask = (1<<i); + if (!state->vlan_enabled) { + // Share with everybody! + state->ports[i].shareports = (1<<state->regs->NUM_PORTS)-1; + continue; + } + state->ports[i].shareports = portmask; + for (j=0; j<MAX_VLANS; j++) { + if (state->vlans[j].ports & portmask) + state->ports[i].shareports |= state->vlans[j].ports; + } + } +} + +static int ip175c_update_state(struct ip17xx_state *state) +{ + ip175c_correct_vlan_state(state); + return ip175c_set_state(state); +} + +static int ip175c_set_vlan_mode(struct ip17xx_state *state) +{ + return ip175c_update_state(state); +} + +static int ip175c_reset(struct ip17xx_state *state) +{ + int err; + + if (REG_SUPP(state->regs->MODE_REG)) { + err = setPhy(state, state->regs->MODE_REG, state->regs->MODE_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->MODE_REG); + if (err < 0) + return err; + } + + return ip175c_update_state(state); +} + +/*** Low-level functions for IP175D ***/ + +static int ip175d_update_state(struct ip17xx_state *state) +{ + unsigned int filter_mask = 0; + unsigned int ports[16], add[16], rem[16]; + int i, j; + int err = 0; + + for (i = 0; i < 16; i++) { + ports[i] = 0; + add[i] = 0; + rem[i] = 0; + if (!state->vlan_enabled) { + err |= ip_phy_write(state, 22, 14+i, i+1); // default tags + ports[i] = 0x3f; + continue; + } + if (!state->vlans[i].tag) { + // Reset the filter + err |= ip_phy_write(state, 22, 14+i, 0); // tag + continue; + } + filter_mask |= 1 << i; + err |= ip_phy_write(state, 22, 14+i, state->vlans[i].tag); + ports[i] = state->vlans[i].ports; + for (j = 0; j < 6; j++) { + if (ports[i] & (1 << j)) { + if (state->add_tag & (1 << j)) + add[i] |= 1 << j; + if (state->remove_tag & (1 << j)) + rem[i] |= 1 << j; + } + } + } + + // Port masks, tag adds and removals + for (i = 0; i < 8; i++) { + err |= ip_phy_write(state, 23, i, ports[2*i] | (ports[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 8+i, add[2*i] | (add[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 16+i, rem[2*i] | (rem[2*i+1] << 8)); + } + err |= ip_phy_write(state, 22, 10, filter_mask); + + // Default VLAN tag for each port + for (i = 0; i < 6; i++) + err |= ip_phy_write(state, 22, 4+i, state->vlans[state->ports[i].pvid].tag); + + return (err ? -EIO : 0); +} + +static int ip175d_set_vlan_mode(struct ip17xx_state *state) +{ + int i; + int err = 0; + + if (state->vlan_enabled) { + // VLAN classification rules: tag-based VLANs, use VID to classify, + // drop packets that cannot be classified. + err |= ip_phy_write_masked(state, 22, 0, 0x3fff, 0x003f); + + // Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed, + // VID=0xfff discarded, admin both tagged and untagged, ingress + // filters enabled. + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + + // Egress rules: IGMP processing off, keep VLAN header off + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } else { + // VLAN classification rules: everything off & clear table + err |= ip_phy_write_masked(state, 22, 0, 0xbfff, 0x8000); + + // Ingress and egress rules: set to defaults + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } + + // Reset default VLAN for each port to 0 + for (i = 0; i < 6; i++) + state->ports[i].pvid = 0; + + err |= ip175d_update_state(state); + + return (err ? -EIO : 0); +} + +static int ip175d_reset(struct ip17xx_state *state) +{ + int err = 0; + + // Disable the special tagging mode + err |= ip_phy_write_masked(state, 21, 22, 0x0003, 0x0000); + + // Set 802.1q protocol type + err |= ip_phy_write(state, 22, 3, 0x8100); + + state->vlan_enabled = 0; + err |= ip175d_set_vlan_mode(state); + + return (err ? -EIO : 0); +} + +/*** High-level functions ***/ + +static int ip17xx_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + return 0; +} + +static void ip17xx_reset_vlan_config(struct ip17xx_state *state) +{ + int i; + + state->remove_tag = (state->vlan_enabled ? ((1<<state->regs->NUM_PORTS)-1) : 0x0000); + state->add_tag = 0x0000; + for (i = 0; i < MAX_VLANS; i++) { + state->vlans[i].ports = 0x0000; + state->vlans[i].tag = (i ? i : 16); + } + for (i = 0; i < MAX_PORTS; i++) + state->ports[i].pvid = 0; +} + +static int ip17xx_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int enable; + + enable = val->value.i; + if (state->vlan_enabled == enable) { + // Do not change any state. + return 0; + } + state->vlan_enabled = enable; + + // Otherwise, if we are switching state, set fields to a known default. + ip17xx_reset_vlan_config(state); + + return state->regs->set_vlan_mode(state); +} + +static int ip17xx_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int b; + int ind; + unsigned int ports; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + ports = state->vlans[val->port_vlan].ports; + b = 0; + ind = 0; + while (b < MAX_PORTS) { + if (ports&1) { + int istagged = ((state->add_tag >> b) & 1); + val->value.ports[ind].id = b; + val->value.ports[ind].flags = (istagged << SWITCH_PORT_FLAG_TAGGED); + ind++; + } + b++; + ports >>= 1; + } + val->len = ind; + + return 0; +} + +static int ip17xx_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int i; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + state->vlans[val->port_vlan].ports = 0; + for (i = 0; i < val->len; i++) { + unsigned int bitmask = (1<<val->value.ports[i].id); + state->vlans[val->port_vlan].ports |= bitmask; + if (val->value.ports[i].flags & (1<<SWITCH_PORT_FLAG_TAGGED)) { + state->add_tag |= bitmask; + state->remove_tag &= (~bitmask); + } else { + state->add_tag &= (~bitmask); + state->remove_tag |= bitmask; + } + } + + return state->regs->update_state(state); +} + +static int ip17xx_apply(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + + if (REG_SUPP(state->regs->MII_REGISTER_EN)) { + int val = getPhy(state, state->regs->MII_REGISTER_EN); + if (val < 0) { + return val; + } + val |= (1<<state->regs->MII_REGISTER_EN_BIT); + return setPhy(state, state->regs->MII_REGISTER_EN, val); + } + return 0; +} + +static int ip17xx_reset(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + int i, err; + + if (REG_SUPP(state->regs->RESET_REG)) { + err = setPhy(state, state->regs->RESET_REG, state->regs->RESET_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->RESET_REG); + + /* + * Data sheet specifies reset period to be 2 msec. + * (I don't see any mention of the 2ms delay in the IP178C spec, only + * in IP175C, but it can't hurt.) + */ + mdelay(2); + } + + /* reset switch ports */ + for (i = 0; i < state->regs->NUM_PORTS-1; i++) { + err = ip_phy_write(state, i, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + } + + state->router_mode = 0; + state->vlan_enabled = 0; + ip17xx_reset_vlan_config(state); + + return state->regs->reset(state); +} + +static int ip17xx_get_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + if (state->add_tag & (1<<val->port_vlan)) { + if (state->remove_tag & (1<<val->port_vlan)) + val->value.i = 3; // shouldn't ever happen. + else + val->value.i = 1; + } else { + if (state->remove_tag & (1<<val->port_vlan)) + val->value.i = 0; + else + val->value.i = 2; + } + return 0; +} + +static int ip17xx_set_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + state->add_tag &= ~(1<<val->port_vlan); + state->remove_tag &= ~(1<<val->port_vlan); + + if (val->value.i == 0) + state->remove_tag |= (1<<val->port_vlan); + if (val->value.i == 1) + state->add_tag |= (1<<val->port_vlan); + + return state->regs->update_state(state); +} + +/** Get the current phy address */ +static int ip17xx_get_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.p; + return 0; +} + +/** Set a new phy address for low level access to registers */ +static int ip17xx_set_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.p = (u16)-1; + else + state->proc_mii.p = (u16)new_reg; + return 0; +} + +/** Get the current register number */ +static int ip17xx_get_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.m; + return 0; +} + +/** Set a new register address for low level access to registers */ +static int ip17xx_set_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.m = (u16)-1; + else + state->proc_mii.m = (u16)new_reg; + return 0; +} + +/** Get the register content of state->proc_mii */ +static int ip17xx_get_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int retval = -EINVAL; + if (REG_SUPP(state->proc_mii)) + retval = getPhy(state, state->proc_mii); + + if (retval < 0) { + return retval; + } else { + val->value.i = retval; + return 0; + } +} + +/** Write a value to the register defined by phy/reg above */ +static int ip17xx_set_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int myval, err = -EINVAL; + + myval = val->value.i; + if (myval <= 0xffff && myval >= 0 && REG_SUPP(state->proc_mii)) { + err = setPhy(state, state->proc_mii, (u16)myval); + } + return err; +} + +static int ip17xx_read_name(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + val->value.s = state->regs->NAME; // Just a const pointer, won't be freed by swconfig. + return 0; +} + +static int ip17xx_get_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + val->value.i = state->vlans[vlan].tag; + return 0; +} + +static int ip17xx_set_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + int tag = val->value.i; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + if (tag < 0 || tag > 4095) + return -EINVAL; + + state->vlans[vlan].tag = tag; + return state->regs->update_state(state); +} + +static int ip17xx_set_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int ctrl; + int autoneg; + int speed; + if (val->value.i == 100) { + speed = 1; + autoneg = 0; + } else if (val->value.i == 10) { + speed = 0; + autoneg = 0; + } else { + autoneg = 1; + speed = 1; + } + + /* Can't set speed for cpu port */ + if (nr == state->regs->CPU_PORT) + return -EINVAL; + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + if (ctrl < 0) + return -EIO; + + ctrl &= (~(1<<12)); + ctrl &= (~(1<<13)); + ctrl |= (autoneg<<12); + ctrl |= (speed<<13); + + return ip_phy_write(state, nr, 0, ctrl); +} + +static int ip17xx_get_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int speed, status; + + if (nr == state->regs->CPU_PORT) { + val->value.i = 100; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (status < 0 || speed < 0) + return -EIO; + + if (status & 4) + val->value.i = ((speed & (1<<11)) ? 100 : 10); + else + val->value.i = 0; + + return 0; +} + +static int ip17xx_get_port_status(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int ctrl, speed, status; + int nr = val->port_vlan; + int len; + char *buf = state->buf; // fixed-length at 80. + + if (nr == state->regs->CPU_PORT) { + sprintf(buf, "up, 100 Mbps, cpu port"); + val->value.s = buf; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (ctrl < 0 || status < 0 || speed < 0) + return -EIO; + + if (status & 4) + len = sprintf(buf, "up, %d Mbps, %s duplex", + ((speed & (1<<11)) ? 100 : 10), + ((speed & (1<<10)) ? "full" : "half")); + else + len = sprintf(buf, "down"); + + if (ctrl & (1<<12)) { + len += sprintf(buf+len, ", auto-negotiate"); + if (!(status & (1<<5))) + len += sprintf(buf+len, " (in progress)"); + } else { + len += sprintf(buf+len, ", fixed speed (%d)", + ((ctrl & (1<<13)) ? 100 : 10)); + } + + buf[len] = '\0'; + val->value.s = buf; + return 0; +} + +static int ip17xx_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct ip17xx_state *state = get_state(dev); + + *val = state->ports[port].pvid; + return 0; +} + +static int ip17xx_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct ip17xx_state *state = get_state(dev); + + if (val < 0 || val >= MAX_VLANS) + return -EINVAL; + + state->ports[port].pvid = val; + return state->regs->update_state(state); +} + + +enum Ports { + IP17XX_PORT_STATUS, + IP17XX_PORT_LINK, + IP17XX_PORT_TAGGED, + IP17XX_PORT_PVID, +}; + +enum Globals { + IP17XX_ENABLE_VLAN, + IP17XX_GET_NAME, + IP17XX_REGISTER_PHY, + IP17XX_REGISTER_MII, + IP17XX_REGISTER_VALUE, + IP17XX_REGISTER_ERRNO, +}; + +enum Vlans { + IP17XX_VLAN_TAG, +}; + +static const struct switch_attr ip17xx_global[] = { + [IP17XX_ENABLE_VLAN] = { + .id = IP17XX_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Flag to enable or disable VLANs and tagging", + .get = ip17xx_get_enable_vlan, + .set = ip17xx_set_enable_vlan, + }, + [IP17XX_GET_NAME] = { + .id = IP17XX_GET_NAME, + .type = SWITCH_TYPE_STRING, + .description = "Returns the type of IC+ chip.", + .name = "name", + .get = ip17xx_read_name, + .set = NULL, + }, + /* jal: added for low level debugging etc. */ + [IP17XX_REGISTER_PHY] = { + .id = IP17XX_REGISTER_PHY, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set PHY (0-4, or 29,30,31)", + .name = "phy", + .get = ip17xx_get_phy, + .set = ip17xx_set_phy, + }, + [IP17XX_REGISTER_MII] = { + .id = IP17XX_REGISTER_MII, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set MII register number (0-31)", + .name = "reg", + .get = ip17xx_get_reg, + .set = ip17xx_set_reg, + }, + [IP17XX_REGISTER_VALUE] = { + .id = IP17XX_REGISTER_VALUE, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: read/write to register (0-65535)", + .name = "val", + .get = ip17xx_get_val, + .set = ip17xx_set_val, + }, +}; + +static const struct switch_attr ip17xx_vlan[] = { + [IP17XX_VLAN_TAG] = { + .id = IP17XX_VLAN_TAG, + .type = SWITCH_TYPE_INT, + .description = "VLAN ID (0-4095) [IP175D only]", + .name = "vid", + .get = ip17xx_get_tag, + .set = ip17xx_set_tag, + } +}; + +static const struct switch_attr ip17xx_port[] = { + [IP17XX_PORT_STATUS] = { + .id = IP17XX_PORT_STATUS, + .type = SWITCH_TYPE_STRING, + .description = "Returns Detailed port status", + .name = "status", + .get = ip17xx_get_port_status, + .set = NULL, + }, + [IP17XX_PORT_LINK] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "Link speed. Can write 0 for auto-negotiate, or 10 or 100", + .name = "link", + .get = ip17xx_get_port_speed, + .set = ip17xx_set_port_speed, + }, + [IP17XX_PORT_TAGGED] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "0 = untag, 1 = add tags, 2 = do not alter (This value is reset if vlans are altered)", + .name = "tagged", + .get = ip17xx_get_tagged, + .set = ip17xx_set_tagged, + }, +}; + +static const struct switch_dev_ops ip17xx_ops = { + .attr_global = { + .attr = ip17xx_global, + .n_attr = ARRAY_SIZE(ip17xx_global), + }, + .attr_port = { + .attr = ip17xx_port, + .n_attr = ARRAY_SIZE(ip17xx_port), + }, + .attr_vlan = { + .attr = ip17xx_vlan, + .n_attr = ARRAY_SIZE(ip17xx_vlan), + }, + + .get_port_pvid = ip17xx_get_pvid, + .set_port_pvid = ip17xx_set_pvid, + .get_vlan_ports = ip17xx_get_ports, + .set_vlan_ports = ip17xx_set_ports, + .apply_config = ip17xx_apply, + .reset_switch = ip17xx_reset, +}; + +static int ip17xx_probe(struct phy_device *pdev) +{ + struct ip17xx_state *state; + struct switch_dev *dev; + int err; + + /* We only attach to PHY 0, but use all available PHYs */ + if (pdev->addr != 0) + return -ENODEV; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + dev = &state->dev; + + pdev->priv = state; + state->mii_bus = pdev->bus; + + err = get_model(state); + if (err < 0) + goto error; + + dev->vlans = MAX_VLANS; + dev->cpu_port = state->regs->CPU_PORT; + dev->ports = state->regs->NUM_PORTS; + dev->name = state->regs->NAME; + dev->ops = &ip17xx_ops; + + pr_info("IP17xx: Found %s at %s\n", dev->name, dev_name(&pdev->dev)); + return 0; + +error: + kfree(state); + return err; +} + +static int ip17xx_config_init(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + struct net_device *dev = pdev->attached_dev; + int err; + + err = register_switch(&state->dev, dev); + if (err < 0) + return err; + + state->registered = true; + ip17xx_reset(&state->dev); + return 0; +} + +static void ip17xx_remove(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + + if (state->registered) + unregister_switch(&state->dev); + kfree(state); +} + +static int ip17xx_config_aneg(struct phy_device *pdev) +{ + return 0; +} + +static int ip17xx_aneg_done(struct phy_device *pdev) +{ + return BMSR_ANEGCOMPLETE; +} + +static int ip17xx_update_link(struct phy_device *pdev) +{ + pdev->link = 1; + return 0; +} + +static int ip17xx_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->pause = pdev->asym_pause = 0; + pdev->link = 1; + + return 0; +} + +static struct phy_driver ip17xx_driver = { + .name = "IC+ IP17xx", + .phy_id = 0x02430c00, + .phy_id_mask = 0x0ffffc00, + .features = PHY_BASIC_FEATURES, + .probe = ip17xx_probe, + .remove = ip17xx_remove, + .config_init = ip17xx_config_init, + .config_aneg = ip17xx_config_aneg, + .aneg_done = ip17xx_aneg_done, + .update_link = ip17xx_update_link, + .read_status = ip17xx_read_status, + .driver = { .owner = THIS_MODULE }, +}; + +static struct phy_driver ip175a_driver = { + .name = "IC+ IP175A", + .phy_id = 0x02430c50, + .phy_id_mask = 0x0ffffff0, + .features = PHY_BASIC_FEATURES, + .probe = ip17xx_probe, + .remove = ip17xx_remove, + .config_init = ip17xx_config_init, + .config_aneg = ip17xx_config_aneg, + .aneg_done = ip17xx_aneg_done, + .update_link = ip17xx_update_link, + .read_status = ip17xx_read_status, + .driver = { .owner = THIS_MODULE }, +}; + + +int __init ip17xx_init(void) +{ + int ret; + + ret = phy_driver_register(&ip175a_driver); + if (ret < 0) + return ret; + + return phy_driver_register(&ip17xx_driver); +} + +void __exit ip17xx_exit(void) +{ + phy_driver_unregister(&ip17xx_driver); + phy_driver_unregister(&ip175a_driver); +} + +MODULE_AUTHOR("Patrick Horn <patrick.horn@gmail.com>"); +MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>"); +MODULE_AUTHOR("Martin Mares <mj@ucw.cz>"); +MODULE_LICENSE("GPL"); + +module_init(ip17xx_init); +module_exit(ip17xx_exit); diff --git a/target/linux/generic/files/drivers/net/phy/micrel.c b/target/linux/generic/files/drivers/net/phy/micrel.c new file mode 100644 index 000000000..1499d4ace --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/micrel.c @@ -0,0 +1,83 @@ +/* + * Driver for Micrel/Kendin PHYs + * + * Copyright (c) 2008-2009 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> + * + * 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 <linux/module.h> +#include <linux/delay.h> +#include <linux/skbuff.h> +#include <linux/phy.h> + +#define KSZ_REG_INT_CTRL 0x1b + +#define KSZ_INT_LU_EN (1 << 8) /* enable Link Up interrupt */ +#define KSZ_INT_RF_EN (1 << 9) /* enable Remote Fault interrupt */ +#define KSZ_INT_LD_EN (1 << 10) /* enable Link Down interrupt */ + +#define KSZ_INT_INIT (KSZ_INT_LU_EN | KSZ_INT_LD_EN) + +static int ksz8041_ack_interrupt(struct phy_device *phydev) +{ + int err; + + err = phy_read(phydev, KSZ_REG_INT_CTRL); + + return (err < 0) ? err : 0; +} + +static int ksz8041_config_intr(struct phy_device *phydev) +{ + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) + err = phy_write(phydev, KSZ_REG_INT_CTRL, + KSZ_INT_INIT); + else + err = phy_write(phydev, KSZ_REG_INT_CTRL, 0); + + return err; +} + +static struct phy_driver ksz8041_phy_driver = { + .phy_id = 0x00221512, + .name = "Micrel KSZ8041", + .phy_id_mask = 0x001fffff, + .features = PHY_BASIC_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = genphy_config_aneg, + .read_status = genphy_read_status, + .ack_interrupt = ksz8041_ack_interrupt, + .config_intr = ksz8041_config_intr, + .driver = { + .owner = THIS_MODULE, + }, +}; + +static int __init micrel_phy_init(void) +{ + return phy_driver_register(&ksz8041_phy_driver); +} + +static void __exit micrel_phy_exit(void) +{ + phy_driver_unregister(&ksz8041_phy_driver); +} + +#ifdef MODULE +module_init(micrel_phy_init); +module_exit(micrel_phy_exit); +#else +subsys_initcall(micrel_phy_init); +#endif + +MODULE_DESCRIPTION("Micrel/Kendin PHY driver"); +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files/drivers/net/phy/mvswitch.c b/target/linux/generic/files/drivers/net/phy/mvswitch.c new file mode 100644 index 000000000..d754951ac --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/mvswitch.c @@ -0,0 +1,422 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> +#include <linux/if_vlan.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> +#include "mvswitch.h" + +/* Undefine this to use trailer mode instead. + * I don't know if header mode works with all chips */ +#define HEADER_MODE 1 + +MODULE_DESCRIPTION("Marvell 88E6060 Switch driver"); +MODULE_AUTHOR("Felix Fietkau"); +MODULE_LICENSE("GPL"); + +#define MVSWITCH_MAGIC 0x88E6060 + +struct mvswitch_priv { + netdev_features_t orig_features; + u8 vlans[16]; +}; + +#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv) + +static inline u16 +r16(struct phy_device *phydev, int addr, int reg) +{ + return phydev->bus->read(phydev->bus, addr, reg); +} + +static inline void +w16(struct phy_device *phydev, int addr, int reg, u16 val) +{ + phydev->bus->write(phydev->bus, addr, reg, val); +} + + +static struct sk_buff * +mvswitch_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + char *buf = NULL; + u16 vid; + + priv = dev->phy_ptr; + if (unlikely(!priv)) + goto error; + + if (unlikely(skb->len < 16)) + goto error; + +#ifdef HEADER_MODE + if (__vlan_hwaccel_get_tag(skb, &vid)) + goto error; + + if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) { + if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC)) + goto error_expand; + if (skb->len < 62) + skb->len = 62; + } + buf = skb_push(skb, MV_HEADER_SIZE); +#else + if (__vlan_get_tag(skb, &vid)) + goto error; + + if (unlikely((vid > 15 || !priv->vlans[vid]))) + goto error; + + if (skb->len <= 64) { + if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC)) + goto error_expand; + + buf = skb->data + 64; + skb->len = 64 + MV_TRAILER_SIZE; + } else { + if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) { + if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC)) + goto error_expand; + } + buf = skb_put(skb, 4); + } + + /* move the ethernet header 4 bytes forward, overwriting the vlan tag */ + memmove(skb->data + 4, skb->data, 12); + skb->data += 4; + skb->len -= 4; + skb->mac_header += 4; +#endif + + if (!buf) + goto error; + + +#ifdef HEADER_MODE + /* prepend the tag */ + *((__be16 *) buf) = cpu_to_be16( + ((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) | + ((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M) + ); +#else + /* append the tag */ + *((__be32 *) buf) = cpu_to_be32(( + (MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) | + ((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S) + )); +#endif + + return skb; + +error_expand: + if (net_ratelimit()) + printk("%s: failed to expand/update skb for the switch\n", dev->name); + +error: + /* any errors? drop the packet! */ + dev_kfree_skb_any(skb); + return NULL; +} + +static void +mvswitch_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + unsigned char *buf; + int vlan = -1; + int i; + + priv = dev->phy_ptr; + if (WARN_ON_ONCE(!priv)) + return; + +#ifdef HEADER_MODE + buf = skb->data; + skb_pull(skb, MV_HEADER_SIZE); +#else + buf = skb->data + skb->len - MV_TRAILER_SIZE; + if (buf[0] != 0x80) + return; +#endif + + /* look for the vlan matching the incoming port */ + for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) { + if ((1 << buf[1]) & priv->vlans[i]) + vlan = i; + } + + if (vlan == -1) + return; + + __vlan_hwaccel_put_tag(skb, vlan); +} + + +static int +mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = r16(pdev, addr, reg) & mask; + if (r == val) + return 0; + } while(--i > 0); + return -ETIMEDOUT; +} + +static int +mvswitch_config_init(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + u8 vlmap = 0; + int i; + + if (!dev) + return -EINVAL; + + printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name); + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + dev->phy_ptr = priv; + pdev->irq = PHY_POLL; +#ifdef HEADER_MODE + dev->flags |= IFF_PROMISC; +#endif + + /* initialize default vlans */ + for (i = 0; i < MV_PORTS; i++) + priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i); + + /* before entering reset, disable all ports */ + for (i = 0; i < MV_PORTS; i++) + w16(pdev, MV_PORTREG(CONTROL, i), 0x00); + + msleep(2); /* wait for the status change to settle in */ + + /* put the ATU in reset */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); + + i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0); + if (i < 0) { + printk("%s: Timeout waiting for the switch to reset.\n", dev->name); + return i; + } + + /* set the ATU flags */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), + MV_ATUCTL_NO_LEARN | + MV_ATUCTL_ATU_1K | + MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */ + ); + + /* initialize the cpu port */ + w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT), +#ifdef HEADER_MODE + MV_PORTCTRL_HEADER | +#else + MV_PORTCTRL_RXTR | + MV_PORTCTRL_TXTR | +#endif + MV_PORTCTRL_ENABLED + ); + /* wait for the phy change to settle in */ + msleep(2); + for (i = 0; i < MV_PORTS; i++) { + u8 pvid = 0; + int j; + + vlmap = 0; + + /* look for the matching vlan */ + for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) { + if (priv->vlans[j] & (1 << i)) { + vlmap = priv->vlans[j]; + pvid = j; + } + } + /* leave port unconfigured if it's not part of a vlan */ + if (!vlmap) + continue; + + /* add the cpu port to the allowed destinations list */ + vlmap |= (1 << MV_CPUPORT); + + /* take port out of its own vlan destination map */ + vlmap &= ~(1 << i); + + /* apply vlan settings */ + w16(pdev, MV_PORTREG(VLANMAP, i), + MV_PORTVLAN_PORTS(vlmap) | + MV_PORTVLAN_ID(i) + ); + + /* re-enable port */ + w16(pdev, MV_PORTREG(CONTROL, i), + MV_PORTCTRL_ENABLED + ); + } + + w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT), + MV_PORTVLAN_ID(MV_CPUPORT) + ); + + /* set the port association vector */ + for (i = 0; i <= MV_PORTS; i++) { + w16(pdev, MV_PORTREG(ASSOC, i), + MV_PORTASSOC_PORTS(1 << i) + ); + } + + /* init switch control */ + w16(pdev, MV_SWITCHREG(CTRL), + MV_SWITCHCTL_MSIZE | + MV_SWITCHCTL_DROP + ); + + dev->eth_mangle_rx = mvswitch_mangle_rx; + dev->eth_mangle_tx = mvswitch_mangle_tx; + priv->orig_features = dev->features; + +#ifdef HEADER_MODE + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->features |= NETIF_F_HW_VLAN_RX | NETIF_F_HW_VLAN_TX; +#else + dev->features |= NETIF_F_HW_VLAN_RX; +#endif + + return 0; +} + +static int +mvswitch_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + + /* XXX ugly workaround: we can't force the switch + * to gracefully handle hosts moving from one port to another, + * so we have to regularly clear the ATU database */ + + /* wait for the ATU to become available */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + /* flush the ATU */ + w16(pdev, MV_SWITCHREG(ATU_OP), + MV_ATUOP_INPROGRESS | + MV_ATUOP_FLUSH_ALL + ); + + /* wait for operation to complete */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + return 0; +} + +static int +mvswitch_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static void +mvswitch_remove(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + + dev->phy_ptr = NULL; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; + dev->features = priv->orig_features; + dev->priv_flags &= ~IFF_NO_IP_ALIGN; + kfree(priv); +} + +static int +mvswitch_probe(struct phy_device *pdev) +{ + struct mvswitch_priv *priv; + + priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + pdev->priv = priv; + + return 0; +} + +static int +mvswitch_fixup(struct phy_device *dev) +{ + u16 reg; + + if (dev->addr != 0x10) + return 0; + + reg = dev->bus->read(dev->bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + if (reg != MV_IDENT_VALUE) + return 0; + + dev->phy_id = MVSWITCH_MAGIC; + return 0; +} + + +static struct phy_driver mvswitch_driver = { + .name = "Marvell 88E6060", + .phy_id = MVSWITCH_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &mvswitch_probe, + .remove = &mvswitch_remove, + .config_init = &mvswitch_config_init, + .config_aneg = &mvswitch_config_aneg, + .read_status = &mvswitch_read_status, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init +mvswitch_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup); + return phy_driver_register(&mvswitch_driver); +} + +static void __exit +mvswitch_exit(void) +{ + phy_driver_unregister(&mvswitch_driver); +} + +module_init(mvswitch_init); +module_exit(mvswitch_exit); diff --git a/target/linux/generic/files/drivers/net/phy/mvswitch.h b/target/linux/generic/files/drivers/net/phy/mvswitch.h new file mode 100644 index 000000000..1563eec4d --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/mvswitch.h @@ -0,0 +1,145 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __MVSWITCH_H +#define __MVSWITCH_H + +#define MV_HEADER_SIZE 2 +#define MV_HEADER_PORTS_M 0x001f +#define MV_HEADER_PORTS_S 0 +#define MV_HEADER_VLAN_M 0xf000 +#define MV_HEADER_VLAN_S 12 + +#define MV_TRAILER_SIZE 4 +#define MV_TRAILER_PORTS_M 0x1f +#define MV_TRAILER_PORTS_S 16 +#define MV_TRAILER_FLAGS_S 24 +#define MV_TRAILER_OVERRIDE 0x80 + + +#define MV_PORTS 5 +#define MV_WANPORT 4 +#define MV_CPUPORT 5 + +#define MV_BASE 0x10 + +#define MV_PHYPORT_BASE (MV_BASE + 0x0) +#define MV_PHYPORT(_n) (MV_PHYPORT_BASE + (_n)) +#define MV_SWITCHPORT_BASE (MV_BASE + 0x8) +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xf) + +enum { + MV_PHY_CONTROL = 0x00, + MV_PHY_STATUS = 0x01, + MV_PHY_IDENT0 = 0x02, + MV_PHY_IDENT1 = 0x03, + MV_PHY_ANEG = 0x04, + MV_PHY_LINK_ABILITY = 0x05, + MV_PHY_ANEG_EXPAND = 0x06, + MV_PHY_XMIT_NEXTP = 0x07, + MV_PHY_LINK_NEXTP = 0x08, + MV_PHY_CONTROL1 = 0x10, + MV_PHY_STATUS1 = 0x11, + MV_PHY_INTR_EN = 0x12, + MV_PHY_INTR_STATUS = 0x13, + MV_PHY_INTR_PORT = 0x14, + MV_PHY_RECV_COUNTER = 0x16, + MV_PHY_LED_PARALLEL = 0x16, + MV_PHY_LED_STREAM = 0x17, + MV_PHY_LED_CTRL = 0x18, + MV_PHY_LED_OVERRIDE = 0x19, + MV_PHY_VCT_CTRL = 0x1a, + MV_PHY_VCT_STATUS = 0x1b, + MV_PHY_CONTROL2 = 0x1e +}; +#define MV_PHYREG(_type, _port) MV_PHYPORT(_port), MV_PHY_##_type + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_VLANMAP = 0x06, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RXCOUNT = 0x10, + MV_PORT_TXCOUNT = 0x11, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORTCTRL_BLOCK = (1 << 0), + MV_PORTCTRL_LEARN = (2 << 0), + MV_PORTCTRL_ENABLED = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), /* Enforce VLANs on packets */ + MV_PORTCTRL_RXTR = (1 << 8), /* Enable Marvell packet trailer for ingress */ + MV_PORTCTRL_HEADER = (1 << 11), /* Enable Marvell packet header mode for port */ + MV_PORTCTRL_TXTR = (1 << 14), /* Enable Marvell packet trailer for egress */ + MV_PORTCTRL_FORCEFL = (1 << 15), /* force flow control */ +}; + +#define MV_PORTVLAN_ID(_n) (((_n) & 0xf) << 12) +#define MV_PORTVLAN_PORTS(_n) ((_n) & 0x3f) + +#define MV_PORTASSOC_PORTS(_n) ((_n) & 0x1f) +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_MAC0 = 0x01, + MV_SWITCH_MAC1 = 0x02, + MV_SWITCH_MAC2 = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), /* EEPROM interrupt enable */ + MV_SWITCHCTL_PHYIE = (1 << 1), /* PHY interrupt enable */ + MV_SWITCHCTL_ATUDONE= (1 << 2), /* ATU done interrupt enable */ + MV_SWITCHCTL_ATUIE = (1 << 3), /* ATU interrupt enable */ + MV_SWITCHCTL_CTRMODE= (1 << 8), /* statistics for rx and tx errors */ + MV_SWITCHCTL_RELOAD = (1 << 9), /* reload registers from eeprom */ + MV_SWITCHCTL_MSIZE = (1 << 10), /* increase maximum frame size */ + MV_SWITCHCTL_DROP = (1 << 13), /* discard frames with excessive collisions */ +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU= (6 << 12), + + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +#define MV_IDENT_MASK 0xfff0 +#define MV_IDENT_VALUE 0x0600 + +#endif diff --git a/target/linux/generic/files/drivers/net/phy/psb6970.c b/target/linux/generic/files/drivers/net/phy/psb6970.c new file mode 100644 index 000000000..2fcd29901 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/psb6970.c @@ -0,0 +1,438 @@ +/* + * Lantiq PSB6970 (Tantos) Switch driver + * + * Copyright (c) 2009,2010 Team Embedded. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * The switch programming done in this driver follows the + * "Ethernet Traffic Separation using VLAN" Application Note as + * published by Lantiq. + */ + +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/switch.h> +#include <linux/phy.h> + +#define PSB6970_MAX_VLANS 16 +#define PSB6970_NUM_PORTS 7 +#define PSB6970_DEFAULT_PORT_CPU 6 +#define PSB6970_IS_CPU_PORT(x) ((x) > 4) + +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +/* --- Identification --- */ +#define PSB6970_CI0 0x0100 +#define PSB6970_CI0_MASK 0x000f +#define PSB6970_CI1 0x0101 +#define PSB6970_CI1_VAL 0x2599 +#define PSB6970_CI1_MASK 0xffff + +/* --- VLAN filter table --- */ +#define PSB6970_VFxL(i) ((i)*2+0x10) /* VLAN Filter Low */ +#define PSB6970_VFxL_VV (1 << 15) /* VLAN_Valid */ + +#define PSB6970_VFxH(i) ((i)*2+0x11) /* VLAN Filter High */ +#define PSB6970_VFxH_TM_SHIFT 7 /* Tagged Member */ + +/* --- Port registers --- */ +#define PSB6970_EC(p) ((p)*0x20+2) /* Extended Control */ +#define PSB6970_EC_IFNTE (1 << 1) /* Input Force No Tag Enable */ + +#define PSB6970_PBVM(p) ((p)*0x20+3) /* Port Base VLAN Map */ +#define PSB6970_PBVM_VMCE (1 << 8) +#define PSB6970_PBVM_AOVTP (1 << 9) +#define PSB6970_PBVM_VSD (1 << 10) +#define PSB6970_PBVM_VC (1 << 11) /* VID Check with VID table */ +#define PSB6970_PBVM_TBVE (1 << 13) /* Tag-Based VLAN enable */ + +#define PSB6970_DVID(p) ((p)*0x20+4) /* Default VLAN ID & Priority */ + +struct psb6970_priv { + struct switch_dev dev; + struct phy_device *phy; + u16 (*read) (struct phy_device* phydev, int reg); + void (*write) (struct phy_device* phydev, int reg, u16 val); + struct mutex reg_mutex; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[PSB6970_MAX_VLANS]; + u8 vlan_table[PSB6970_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[PSB6970_NUM_PORTS]; +}; + +#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev) + +static u16 psb6970_mii_read(struct phy_device *phydev, int reg) +{ + return phydev->bus->read(phydev->bus, PHYADDR(reg)); +} + +static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val) +{ + phydev->bus->write(phydev->bus, PHYADDR(reg), val); +} + +static int +psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + priv->vlan = !!val->value.i; + return 0; +} + +static int +psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + val->value.i = priv->vlan; + return 0; +} + +static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct psb6970_priv *priv = to_psb6970(dev); + + /* make sure no invalid PVIDs get set */ + if (vlan >= dev->vlans) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct psb6970_priv *priv = to_psb6970(dev); + *vlan = priv->pvid[port]; + return 0; +} + +static int +psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +static struct switch_attr psb6970_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = psb6970_set_vlan, + .get = psb6970_get_vlan, + .max = 1}, +}; + +static struct switch_attr psb6970_port[] = { +}; + +static struct switch_attr psb6970_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = psb6970_set_vid, + .get = psb6970_get_vid, + .max = 4094, + }, +}; + +static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < PSB6970_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (priv->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + u8 *vt = &priv->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)) + priv->vlan_tagged |= (1 << p->id); + else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < PSB6970_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static int psb6970_hw_apply(struct switch_dev *dev) +{ + struct psb6970_priv *priv = to_psb6970(dev); + int i, j; + + mutex_lock(&priv->reg_mutex); + + if (priv->vlan) { + /* into the vlan translation unit */ + for (j = 0; j < PSB6970_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (vp) { + priv->write(priv->phy, PSB6970_VFxL(j), + PSB6970_VFxL_VV | priv->vlan_id[j]); + priv->write(priv->phy, PSB6970_VFxH(j), + ((vp & priv-> + vlan_tagged) << + PSB6970_VFxH_TM_SHIFT) | vp); + } else /* clear VLAN Valid flag for unused vlans */ + priv->write(priv->phy, PSB6970_VFxL(j), 0); + + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < PSB6970_NUM_PORTS; i++) { + int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0; + + if (priv->vlan) { + ec = PSB6970_EC_IFNTE; + dvid = priv->vlan_id[priv->pvid[i]]; + pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE; + + if ((i << 1) & priv->vlan_tagged) + pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC; + } + + priv->write(priv->phy, PSB6970_PBVM(i), pbvm); + + if (!PSB6970_IS_CPU_PORT(i)) { + priv->write(priv->phy, PSB6970_EC(i), ec); + priv->write(priv->phy, PSB6970_DVID(i), dvid); + } + } + + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int psb6970_reset_switch(struct switch_dev *dev) +{ + struct psb6970_priv *priv = to_psb6970(dev); + int i; + + mutex_lock(&priv->reg_mutex); + + memset(&priv->vlan, 0, sizeof(struct psb6970_priv) - + offsetof(struct psb6970_priv, vlan)); + + for (i = 0; i < PSB6970_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + mutex_unlock(&priv->reg_mutex); + + return psb6970_hw_apply(dev); +} + +static const struct switch_dev_ops psb6970_ops = { + .attr_global = { + .attr = psb6970_globals, + .n_attr = ARRAY_SIZE(psb6970_globals), + }, + .attr_port = { + .attr = psb6970_port, + .n_attr = ARRAY_SIZE(psb6970_port), + }, + .attr_vlan = { + .attr = psb6970_vlan, + .n_attr = ARRAY_SIZE(psb6970_vlan), + }, + .get_port_pvid = psb6970_get_pvid, + .set_port_pvid = psb6970_set_pvid, + .get_vlan_ports = psb6970_get_ports, + .set_vlan_ports = psb6970_set_ports, + .apply_config = psb6970_hw_apply, + .reset_switch = psb6970_reset_switch, +}; + +static int psb6970_config_init(struct phy_device *pdev) +{ + struct psb6970_priv *priv; + struct net_device *dev = pdev->attached_dev; + struct switch_dev *swdev; + int ret; + + priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->phy = pdev; + + if (pdev->addr == 0) + printk(KERN_INFO "%s: psb6970 switch driver attached.\n", + pdev->attached_dev->name); + + if (pdev->addr != 0) { + kfree(priv); + return 0; + } + + pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full; + + mutex_init(&priv->reg_mutex); + priv->read = psb6970_mii_read; + priv->write = psb6970_mii_write; + + pdev->priv = priv; + + swdev = &priv->dev; + swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU; + swdev->ops = &psb6970_ops; + + swdev->name = "Lantiq PSB6970"; + swdev->vlans = PSB6970_MAX_VLANS; + swdev->ports = PSB6970_NUM_PORTS; + + if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { + kfree(priv); + goto done; + } + + ret = psb6970_reset_switch(&priv->dev); + if (ret) { + kfree(priv); + goto done; + } + + dev->phy_ptr = priv; + +done: + return ret; +} + +static int psb6970_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +static int psb6970_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int psb6970_probe(struct phy_device *pdev) +{ + return 0; +} + +static void psb6970_remove(struct phy_device *pdev) +{ + struct psb6970_priv *priv = pdev->priv; + + if (!priv) + return; + + if (pdev->addr == 0) + unregister_switch(&priv->dev); + kfree(priv); +} + +static int psb6970_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->bus; + u16 reg; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK; + if (reg != PSB6970_CI1_VAL) + return 0; + + dev->phy_id = (reg << 16); + dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK; + + return 0; +} + +static struct phy_driver psb6970_driver = { + .name = "Lantiq PSB6970", + .phy_id = PSB6970_CI1_VAL << 16, + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = psb6970_probe, + .remove = psb6970_remove, + .config_init = &psb6970_config_init, + .config_aneg = &psb6970_config_aneg, + .read_status = &psb6970_read_status, + .driver = {.owner = THIS_MODULE}, +}; + +int __init psb6970_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup); + return phy_driver_register(&psb6970_driver); +} + +module_init(psb6970_init); + +void __exit psb6970_exit(void) +{ + phy_driver_unregister(&psb6970_driver); +} + +module_exit(psb6970_exit); + +MODULE_DESCRIPTION("Lantiq PSB6970 Switch"); +MODULE_AUTHOR("Ithamar R. Adema <ithamar.adema@team-embedded.nl>"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8306.c b/target/linux/generic/files/drivers/net/phy/rtl8306.c new file mode 100644 index 000000000..78ded0ace --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/rtl8306.c @@ -0,0 +1,1056 @@ +/* + * rtl8306.c: RTL8306S switch driver + * + * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org> + * + * 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. + * + * 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. + */ + +#include <linux/if.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/if_ether.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/netlink.h> +#include <net/genetlink.h> +#include <linux/switch.h> +#include <linux/delay.h> +#include <linux/phy.h> + +//#define DEBUG 1 + +/* Global (PHY0) */ +#define RTL8306_REG_PAGE 16 +#define RTL8306_REG_PAGE_LO (1 << 15) +#define RTL8306_REG_PAGE_HI (1 << 1) /* inverted */ + +#define RTL8306_NUM_VLANS 16 +#define RTL8306_NUM_PORTS 6 +#define RTL8306_PORT_CPU 5 +#define RTL8306_NUM_PAGES 4 +#define RTL8306_NUM_REGS 32 + +#define RTL_NAME_S "RTL8306S" +#define RTL_NAME_SD "RTL8306SD" +#define RTL_NAME_SDM "RTL8306SDM" +#define RTL_NAME_UNKNOWN "RTL8306(unknown)" + +#define RTL8306_MAGIC 0x8306 + +static LIST_HEAD(phydevs); + +struct rtl_priv { + struct list_head list; + struct switch_dev dev; + int page; + int type; + int do_cpu; + struct mii_bus *bus; + char hwname[sizeof(RTL_NAME_UNKNOWN)]; + bool fixup; +}; + +struct rtl_phyregs { + int nway; + int speed; + int duplex; +}; + +#define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev) + +enum { + RTL_TYPE_S, + RTL_TYPE_SD, + RTL_TYPE_SDM, +}; + +struct rtl_reg { + int page; + int phy; + int reg; + int bits; + int shift; + int inverted; +}; + +#define RTL_VLAN_REGOFS(name) \ + (RTL_REG_VLAN1_##name - RTL_REG_VLAN0_##name) + +#define RTL_PORT_REGOFS(name) \ + (RTL_REG_PORT1_##name - RTL_REG_PORT0_##name) + +#define RTL_PORT_REG(id, reg) \ + (RTL_REG_PORT0_##reg + (id * RTL_PORT_REGOFS(reg))) + +#define RTL_VLAN_REG(id, reg) \ + (RTL_REG_VLAN0_##reg + (id * RTL_VLAN_REGOFS(reg))) + +#define RTL_GLOBAL_REGATTR(reg) \ + .id = RTL_REG_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = 0, \ + .set = rtl_attr_set_int, \ + .get = rtl_attr_get_int + +#define RTL_PORT_REGATTR(reg) \ + .id = RTL_REG_PORT0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_PORT_REGOFS(reg), \ + .set = rtl_attr_set_port_int, \ + .get = rtl_attr_get_port_int + +#define RTL_VLAN_REGATTR(reg) \ + .id = RTL_REG_VLAN0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_VLAN_REGOFS(reg), \ + .set = rtl_attr_set_vlan_int, \ + .get = rtl_attr_get_vlan_int + +enum rtl_regidx { + RTL_REG_CHIPID, + RTL_REG_CHIPVER, + RTL_REG_CHIPTYPE, + RTL_REG_CPUPORT, + + RTL_REG_EN_CPUPORT, + RTL_REG_EN_TAG_OUT, + RTL_REG_EN_TAG_CLR, + RTL_REG_EN_TAG_IN, + RTL_REG_TRAP_CPU, + RTL_REG_TRUNK_PORTSEL, + RTL_REG_EN_TRUNK, + RTL_REG_RESET, + + RTL_REG_VLAN_ENABLE, + RTL_REG_VLAN_FILTER, + RTL_REG_VLAN_TAG_ONLY, + RTL_REG_VLAN_TAG_AWARE, +#define RTL_VLAN_ENUM(id) \ + RTL_REG_VLAN##id##_VID, \ + RTL_REG_VLAN##id##_PORTMASK + RTL_VLAN_ENUM(0), + RTL_VLAN_ENUM(1), + RTL_VLAN_ENUM(2), + RTL_VLAN_ENUM(3), + RTL_VLAN_ENUM(4), + RTL_VLAN_ENUM(5), + RTL_VLAN_ENUM(6), + RTL_VLAN_ENUM(7), + RTL_VLAN_ENUM(8), + RTL_VLAN_ENUM(9), + RTL_VLAN_ENUM(10), + RTL_VLAN_ENUM(11), + RTL_VLAN_ENUM(12), + RTL_VLAN_ENUM(13), + RTL_VLAN_ENUM(14), + RTL_VLAN_ENUM(15), +#define RTL_PORT_ENUM(id) \ + RTL_REG_PORT##id##_PVID, \ + RTL_REG_PORT##id##_NULL_VID_REPLACE, \ + RTL_REG_PORT##id##_NON_PVID_DISCARD, \ + RTL_REG_PORT##id##_VID_INSERT, \ + RTL_REG_PORT##id##_TAG_INSERT, \ + RTL_REG_PORT##id##_LINK, \ + RTL_REG_PORT##id##_SPEED, \ + RTL_REG_PORT##id##_NWAY, \ + RTL_REG_PORT##id##_NRESTART, \ + RTL_REG_PORT##id##_DUPLEX, \ + RTL_REG_PORT##id##_RXEN, \ + RTL_REG_PORT##id##_TXEN + RTL_PORT_ENUM(0), + RTL_PORT_ENUM(1), + RTL_PORT_ENUM(2), + RTL_PORT_ENUM(3), + RTL_PORT_ENUM(4), + RTL_PORT_ENUM(5), +}; + +static const struct rtl_reg rtl_regs[] = { + [RTL_REG_CHIPID] = { 0, 4, 30, 16, 0, 0 }, + [RTL_REG_CHIPVER] = { 0, 4, 31, 8, 0, 0 }, + [RTL_REG_CHIPTYPE] = { 0, 4, 31, 2, 8, 0 }, + + /* CPU port number */ + [RTL_REG_CPUPORT] = { 2, 4, 21, 3, 0, 0 }, + /* Enable CPU port function */ + [RTL_REG_EN_CPUPORT] = { 3, 2, 21, 1, 15, 1 }, + /* Enable CPU port tag insertion */ + [RTL_REG_EN_TAG_OUT] = { 3, 2, 21, 1, 12, 0 }, + /* Enable CPU port tag removal */ + [RTL_REG_EN_TAG_CLR] = { 3, 2, 21, 1, 11, 0 }, + /* Enable CPU port tag checking */ + [RTL_REG_EN_TAG_IN] = { 0, 4, 21, 1, 7, 0 }, + [RTL_REG_EN_TRUNK] = { 0, 0, 19, 1, 11, 1 }, + [RTL_REG_TRUNK_PORTSEL] = { 0, 0, 16, 1, 6, 1 }, + [RTL_REG_RESET] = { 0, 0, 16, 1, 12, 0 }, + + [RTL_REG_TRAP_CPU] = { 3, 2, 22, 1, 6, 0 }, + + [RTL_REG_VLAN_TAG_ONLY] = { 0, 0, 16, 1, 8, 1 }, + [RTL_REG_VLAN_FILTER] = { 0, 0, 16, 1, 9, 1 }, + [RTL_REG_VLAN_TAG_AWARE] = { 0, 0, 16, 1, 10, 1 }, + [RTL_REG_VLAN_ENABLE] = { 0, 0, 18, 1, 8, 1 }, + +#define RTL_VLAN_REGS(id, phy, page, regofs) \ + [RTL_REG_VLAN##id##_VID] = { page, phy, 25 + regofs, 12, 0, 0 }, \ + [RTL_REG_VLAN##id##_PORTMASK] = { page, phy, 24 + regofs, 6, 0, 0 } + RTL_VLAN_REGS( 0, 0, 0, 0), + RTL_VLAN_REGS( 1, 1, 0, 0), + RTL_VLAN_REGS( 2, 2, 0, 0), + RTL_VLAN_REGS( 3, 3, 0, 0), + RTL_VLAN_REGS( 4, 4, 0, 0), + RTL_VLAN_REGS( 5, 0, 1, 2), + RTL_VLAN_REGS( 6, 1, 1, 2), + RTL_VLAN_REGS( 7, 2, 1, 2), + RTL_VLAN_REGS( 8, 3, 1, 2), + RTL_VLAN_REGS( 9, 4, 1, 2), + RTL_VLAN_REGS(10, 0, 1, 4), + RTL_VLAN_REGS(11, 1, 1, 4), + RTL_VLAN_REGS(12, 2, 1, 4), + RTL_VLAN_REGS(13, 3, 1, 4), + RTL_VLAN_REGS(14, 4, 1, 4), + RTL_VLAN_REGS(15, 0, 1, 6), + +#define REG_PORT_SETTING(port, phy) \ + [RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \ + [RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \ + [RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \ + [RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \ + [RTL_REG_PORT##port##_LINK] = { 0, phy, 1, 1, 2, 0 }, \ + [RTL_REG_PORT##port##_NULL_VID_REPLACE] = { 0, phy, 22, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NON_PVID_DISCARD] = { 0, phy, 22, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_VID_INSERT] = { 0, phy, 22, 2, 9, 0 }, \ + [RTL_REG_PORT##port##_TAG_INSERT] = { 0, phy, 22, 2, 0, 0 } + + REG_PORT_SETTING(0, 0), + REG_PORT_SETTING(1, 1), + REG_PORT_SETTING(2, 2), + REG_PORT_SETTING(3, 3), + REG_PORT_SETTING(4, 4), + REG_PORT_SETTING(5, 6), + +#define REG_PORT_PVID(phy, page, regofs) \ + { page, phy, 24 + regofs, 4, 12, 0 } + [RTL_REG_PORT0_PVID] = REG_PORT_PVID(0, 0, 0), + [RTL_REG_PORT1_PVID] = REG_PORT_PVID(1, 0, 0), + [RTL_REG_PORT2_PVID] = REG_PORT_PVID(2, 0, 0), + [RTL_REG_PORT3_PVID] = REG_PORT_PVID(3, 0, 0), + [RTL_REG_PORT4_PVID] = REG_PORT_PVID(4, 0, 0), + [RTL_REG_PORT5_PVID] = REG_PORT_PVID(0, 1, 2), +}; + + +static inline void +rtl_set_page(struct rtl_priv *priv, unsigned int page) +{ + struct mii_bus *bus = priv->bus; + u16 pgsel; + + if (priv->fixup) + return; + + if (priv->page == page) + return; + + BUG_ON(page > RTL8306_NUM_PAGES); + pgsel = bus->read(bus, 0, RTL8306_REG_PAGE); + pgsel &= ~(RTL8306_REG_PAGE_LO | RTL8306_REG_PAGE_HI); + if (page & (1 << 0)) + pgsel |= RTL8306_REG_PAGE_LO; + if (!(page & (1 << 1))) /* bit is inverted */ + pgsel |= RTL8306_REG_PAGE_HI; + bus->write(bus, 0, RTL8306_REG_PAGE, pgsel); +} + +static inline int +rtl_w16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + bus->write(bus, phy, reg, val); + bus->read(bus, phy, reg); /* flush */ + return 0; +} + +static inline int +rtl_r16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + return bus->read(bus, phy, reg); +} + +static inline u16 +rtl_rmw(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 mask, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + u16 r; + + rtl_set_page(priv, page); + r = bus->read(bus, phy, reg); + r &= ~mask; + r |= val; + bus->write(bus, phy, reg, r); + return bus->read(bus, phy, reg); /* flush */ +} + + +static inline int +rtl_get(struct switch_dev *dev, enum rtl_regidx s) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 val; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + if (r->bits == 0) /* unimplemented */ + return 0; + + val = rtl_r16(dev, r->page, r->phy, r->reg); + + if (r->shift > 0) + val >>= r->shift; + + if (r->inverted) + val = ~val; + + val &= (1 << r->bits) - 1; + + return val; +} + +static int +rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 mask = 0xffff; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + + if (r->bits == 0) /* unimplemented */ + return 0; + + if (r->shift > 0) + val <<= r->shift; + + if (r->inverted) + val = ~val; + + if (r->bits != 16) { + mask = (1 << r->bits) - 1; + mask <<= r->shift; + } + val &= mask; + return rtl_rmw(dev, r->page, r->phy, r->reg, mask, val); +} + +static void +rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); + regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); +} + +static void +rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway); + rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed); + rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex); +} + +static void +rtl_port_set_enable(struct switch_dev *dev, int port, int enabled) +{ + rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled); + rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled); + + if ((port >= 5) || !enabled) + return; + + /* restart autonegotiation if enabled */ + rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1); +} + +static int +rtl_hw_apply(struct switch_dev *dev) +{ + int i; + int trunk_en, trunk_psel; + struct rtl_phyregs port5; + + rtl_phy_save(dev, 5, &port5); + + /* disable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 0); + } + + /* save trunking status */ + trunk_en = rtl_get(dev, RTL_REG_EN_TRUNK); + trunk_psel = rtl_get(dev, RTL_REG_TRUNK_PORTSEL); + + /* trunk port 3 and 4 + * XXX: Big WTF, but RealTek seems to do it */ + rtl_set(dev, RTL_REG_EN_TRUNK, 1); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 1); + + /* execute the software reset */ + rtl_set(dev, RTL_REG_RESET, 1); + + /* wait for the reset to complete, + * but don't wait for too long */ + for (i = 0; i < 10; i++) { + if (rtl_get(dev, RTL_REG_RESET) == 0) + break; + + msleep(1); + } + + /* enable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 1); + } + + /* restore trunking settings */ + rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel); + rtl_phy_restore(dev, 5, &port5); + + return 0; +} + +static void +rtl_hw_init(struct switch_dev *dev) +{ + struct rtl_priv *priv = to_rtl(dev); + int cpu_mask = 1 << dev->cpu_port; + int i; + + rtl_set(dev, RTL_REG_VLAN_ENABLE, 0); + rtl_set(dev, RTL_REG_VLAN_FILTER, 0); + rtl_set(dev, RTL_REG_EN_TRUNK, 0); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 0); + + /* initialize cpu port settings */ + if (priv->do_cpu) { + rtl_set(dev, RTL_REG_CPUPORT, dev->cpu_port); + rtl_set(dev, RTL_REG_EN_CPUPORT, 1); + } else { + rtl_set(dev, RTL_REG_CPUPORT, 7); + rtl_set(dev, RTL_REG_EN_CPUPORT, 0); + } + rtl_set(dev, RTL_REG_EN_TAG_OUT, 0); + rtl_set(dev, RTL_REG_EN_TAG_IN, 0); + rtl_set(dev, RTL_REG_EN_TAG_CLR, 0); + + /* reset all vlans */ + for (i = 0; i < RTL8306_NUM_VLANS; i++) { + rtl_set(dev, RTL_VLAN_REG(i, VID), i); + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), 0); + } + + /* default to port isolation */ + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + unsigned long mask; + + if ((1 << i) == cpu_mask) + mask = ((1 << RTL8306_NUM_PORTS) - 1) & ~cpu_mask; /* all bits set */ + else + mask = cpu_mask | (1 << i); + + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), mask); + rtl_set(dev, RTL_PORT_REG(i, PVID), i); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), 1); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), 3); + } + rtl_hw_apply(dev); +} + +#ifdef DEBUG +static int +rtl_set_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + priv->do_cpu = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + val->value.i = priv->do_cpu; + return 0; +} + +static int +rtl_set_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + dev->cpu_port = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + val->value.i = dev->cpu_port; + return 0; +} +#endif + +static int +rtl_reset(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + rtl_hw_init(dev); + return 0; +} + +static int +rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + struct rtl_phyregs port; + + if (attr->id >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + if ((attr->max > 0) && (val->value.i > attr->max)) + return -EINVAL; + + /* access to phy register 22 on port 4/5 + * needs phy status save/restore */ + if ((val->port_vlan > 3) && + (rtl_regs[idx].reg == 22) && + (rtl_regs[idx].page == 0)) { + + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, idx, val->value.i); + rtl_phy_restore(dev, val->port_vlan, &port); + } else { + rtl_set(dev, idx, val->value.i); + } + + return 0; +} + +static int +rtl_attr_get_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + + if (idx >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + val->value.i = rtl_get(dev, idx); + return 0; +} + +static int +rtl_attr_set_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_attr_set_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int i, mask; + + mask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + struct switch_port *port; + + if (!(mask & (1 << i))) + continue; + + port = &val->value.ports[val->len]; + port->id = i; + port->flags = 0; + val->len++; + } + + return 0; +} + +static int +rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct rtl_phyregs port; + int en = val->value.i; + int i; + + rtl_set(dev, RTL_REG_EN_TAG_OUT, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_IN, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_CLR, en && priv->do_cpu); + rtl_set(dev, RTL_REG_VLAN_TAG_AWARE, en); + if (en) + rtl_set(dev, RTL_REG_VLAN_FILTER, en); + + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (i > 3) + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1)); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3)); + if (i > 3) + rtl_phy_restore(dev, val->port_vlan, &port); + } + rtl_set(dev, RTL_REG_VLAN_ENABLE, en); + + return 0; +} + +static int +rtl_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + return rtl_get(dev, RTL_REG_VLAN_ENABLE); +} + +static int +rtl_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int mask = 0; + unsigned int oldmask; + int i; + + for(i = 0; i < val->len; i++) + { + struct switch_port *port = &val->value.ports[i]; + bool tagged = false; + + mask |= (1 << port->id); + + if (port->id == dev->cpu_port) + continue; + + if ((i == dev->cpu_port) || + (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + tagged = true; + + /* fix up PVIDs for added ports */ + if (!tagged) + rtl_set(dev, RTL_PORT_REG(port->id, PVID), val->port_vlan); + + rtl_set(dev, RTL_PORT_REG(port->id, NON_PVID_DISCARD), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, VID_INSERT), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, TAG_INSERT), (tagged ? 2 : 1)); + } + + oldmask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + rtl_set(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK), mask); + + /* fix up PVIDs for removed ports, default to last vlan */ + oldmask &= ~mask; + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (!(oldmask & (1 << i))) + continue; + + if (i == dev->cpu_port) + continue; + + if (rtl_get(dev, RTL_PORT_REG(i, PVID)) == val->port_vlan) + rtl_set(dev, RTL_PORT_REG(i, PVID), dev->vlans - 1); + } + + return 0; +} + +static struct switch_attr rtl_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "reset", + .description = "Reset the switch", + .set = rtl_reset, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .max = 1, + .set = rtl_set_vlan, + .get = rtl_get_vlan, + }, + { + RTL_GLOBAL_REGATTR(EN_TRUNK), + .name = "trunk", + .description = "Enable port trunking", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(TRUNK_PORTSEL), + .name = "trunk_sel", + .description = "Select ports for trunking (0: 0,1 - 1: 3,4)", + .max = 1, + }, +#ifdef DEBUG + { + RTL_GLOBAL_REGATTR(VLAN_FILTER), + .name = "vlan_filter", + .description = "Filter incoming packets for allowed VLANS", + .max = 1, + }, + { + .type = SWITCH_TYPE_INT, + .name = "cpuport", + .description = "CPU Port", + .set = rtl_set_cpuport, + .get = rtl_get_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + .type = SWITCH_TYPE_INT, + .name = "use_cpuport", + .description = "CPU Port handling flag", + .set = rtl_set_use_cpuport, + .get = rtl_get_use_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + RTL_GLOBAL_REGATTR(TRAP_CPU), + .name = "trap_cpu", + .description = "VLAN trap to CPU", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_AWARE), + .name = "vlan_tag_aware", + .description = "Enable VLAN tag awareness", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_ONLY), + .name = "tag_only", + .description = "Only accept tagged packets", + .max = 1, + }, +#endif +}; +static struct switch_attr rtl_port[] = { + { + RTL_PORT_REGATTR(PVID), + .name = "pvid", + .description = "Port VLAN ID", + .max = RTL8306_NUM_VLANS - 1, + }, + { + RTL_PORT_REGATTR(LINK), + .name = "link", + .description = "get the current link state", + .max = 1, + .set = NULL, + }, +#ifdef DEBUG + { + RTL_PORT_REGATTR(NULL_VID_REPLACE), + .name = "null_vid", + .description = "NULL VID gets replaced by port default vid", + .max = 1, + }, + { + RTL_PORT_REGATTR(NON_PVID_DISCARD), + .name = "non_pvid_discard", + .description = "discard packets with VID != PVID", + .max = 1, + }, + { + RTL_PORT_REGATTR(VID_INSERT), + .name = "vid_insert_remove", + .description = "how should the switch insert and remove vids ?", + .max = 3, + }, + { + RTL_PORT_REGATTR(TAG_INSERT), + .name = "tag_insert", + .description = "tag insertion handling", + .max = 3, + }, +#endif + { + RTL_PORT_REGATTR(SPEED), + .name = "speed", + .description = "current link speed", + .max = 1, + }, + { + RTL_PORT_REGATTR(NWAY), + .name = "nway", + .description = "enable autonegotiation", + .max = 1, + }, +}; + +static struct switch_attr rtl_vlan[] = { + { + RTL_VLAN_REGATTR(VID), + .name = "vid", + .description = "VLAN ID (1-4095)", + .max = 4095, + }, +}; + +static const struct switch_dev_ops rtl8306_ops = { + .attr_global = { + .attr = rtl_globals, + .n_attr = ARRAY_SIZE(rtl_globals), + }, + .attr_port = { + .attr = rtl_port, + .n_attr = ARRAY_SIZE(rtl_port), + }, + .attr_vlan = { + .attr = rtl_vlan, + .n_attr = ARRAY_SIZE(rtl_vlan), + }, + + .get_vlan_ports = rtl_get_ports, + .set_vlan_ports = rtl_set_ports, + .apply_config = rtl_hw_apply, +}; + +static int +rtl8306_config_init(struct phy_device *pdev) +{ + struct net_device *netdev = pdev->attached_dev; + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + struct switch_val val; + unsigned int chipid, chipver, chiptype; + int err; + + /* Only init the switch for the primary PHY */ + if (pdev->addr != 0) + return 0; + + val.value.i = 1; + priv->dev.cpu_port = RTL8306_PORT_CPU; + priv->dev.ports = RTL8306_NUM_PORTS; + priv->dev.vlans = RTL8306_NUM_VLANS; + priv->dev.ops = &rtl8306_ops; + priv->do_cpu = 0; + priv->page = -1; + priv->bus = pdev->bus; + + chipid = rtl_get(dev, RTL_REG_CHIPID); + chipver = rtl_get(dev, RTL_REG_CHIPVER); + chiptype = rtl_get(dev, RTL_REG_CHIPTYPE); + switch(chiptype) { + case 0: + case 2: + strncpy(priv->hwname, RTL_NAME_S, sizeof(priv->hwname)); + priv->type = RTL_TYPE_S; + break; + case 1: + strncpy(priv->hwname, RTL_NAME_SD, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SD; + break; + case 3: + strncpy(priv->hwname, RTL_NAME_SDM, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SDM; + break; + default: + strncpy(priv->hwname, RTL_NAME_UNKNOWN, sizeof(priv->hwname)); + break; + } + + dev->name = priv->hwname; + rtl_hw_init(dev); + + printk(KERN_INFO "Registering %s switch with Chip ID: 0x%04x, version: 0x%04x\n", priv->hwname, chipid, chipver); + + err = register_switch(dev, netdev); + if (err < 0) { + kfree(priv); + return err; + } + + return 0; +} + + +static int +rtl8306_fixup(struct phy_device *pdev) +{ + struct rtl_priv priv; + u16 chipid; + + /* Attach to primary LAN port and WAN port */ + if (pdev->addr != 0 && pdev->addr != 4) + return 0; + + memset(&priv, 0, sizeof(priv)); + priv.fixup = true; + priv.page = -1; + priv.bus = pdev->bus; + chipid = rtl_get(&priv.dev, RTL_REG_CHIPID); + if (chipid == 0x5988) + pdev->phy_id = RTL8306_MAGIC; + + return 0; +} + +static int +rtl8306_probe(struct phy_device *pdev) +{ + struct rtl_priv *priv; + + list_for_each_entry(priv, &phydevs, list) { + /* + * share one rtl_priv instance between virtual phy + * devices on the same bus + */ + if (priv->bus == pdev->bus) + goto found; + } + priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = pdev->bus; + +found: + pdev->priv = priv; + return 0; +} + +static void +rtl8306_remove(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + unregister_switch(&priv->dev); + kfree(priv); +} + +static int +rtl8306_config_aneg(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + + /* Only for WAN */ + if (pdev->addr == 0) + return 0; + + /* Restart autonegotiation */ + rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1); + rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1); + + return 0; +} + +static int +rtl8306_read_status(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + + if (pdev->addr == 4) { + /* WAN */ + pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10; + pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF; + pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK)); + } else { + /* LAN */ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + } + + /* + * Bypass generic PHY status read, + * it doesn't work with this switch + */ + if (pdev->link) { + pdev->state = PHY_RUNNING; + netif_carrier_on(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } else { + pdev->state = PHY_NOLINK; + netif_carrier_off(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } + + return 0; +} + + +static struct phy_driver rtl8306_driver = { + .name = "Realtek RTL8306S", + .flags = PHY_HAS_MAGICANEG, + .phy_id = RTL8306_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &rtl8306_probe, + .remove = &rtl8306_remove, + .config_init = &rtl8306_config_init, + .config_aneg = &rtl8306_config_aneg, + .read_status = &rtl8306_read_status, + .driver = { .owner = THIS_MODULE,}, +}; + + +static int __init +rtl_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, rtl8306_fixup); + return phy_driver_register(&rtl8306_driver); +} + +static void __exit +rtl_exit(void) +{ + phy_driver_unregister(&rtl8306_driver); +} + +module_init(rtl_init); +module_exit(rtl_exit); +MODULE_LICENSE("GPL"); + diff --git a/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c b/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c new file mode 100644 index 000000000..be85274e7 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c @@ -0,0 +1,1375 @@ +/* + * Realtek RTL8366 SMI interface driver + * + * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/spinlock.h> +#include <linux/skbuff.h> +#include <linux/rtl8366.h> + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +#include <linux/debugfs.h> +#endif + +#include "rtl8366_smi.h" + +#define RTL8366_SMI_ACK_RETRY_COUNT 5 + +#define RTL8366_SMI_HW_STOP_DELAY 25 /* msecs */ +#define RTL8366_SMI_HW_START_DELAY 100 /* msecs */ + +static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) +{ + ndelay(smi->clk_delay); +} + +static void rtl8366_smi_start(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + /* + * Set GPIO pins to output mode, with initial state: + * SCK = 0, SDA = 1 + */ + gpio_direction_output(sck, 0); + gpio_direction_output(sda, 1); + rtl8366_smi_clk_delay(smi); + + /* CLK 1: 0 -> 1, 1 -> 0 */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + + /* CLK 2: */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); +} + +static void rtl8366_smi_stop(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* add a click */ + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* set GPIO pins to input mode */ + gpio_direction_input(sda); + gpio_direction_input(sck); +} + +static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + for (; len > 0; len--) { + rtl8366_smi_clk_delay(smi); + + /* prepare data */ + gpio_set_value(sda, !!(data & ( 1 << (len - 1)))); + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + } +} + +static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + gpio_direction_input(sda); + + for (*data = 0; len > 0; len--) { + u32 u; + + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + u = !!gpio_get_value(sda); + gpio_set_value(sck, 0); + + *data |= (u << (len - 1)); + } + + gpio_direction_output(sda, 0); +} + +static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) +{ + int retry_cnt; + + retry_cnt = 0; + do { + u32 ack; + + rtl8366_smi_read_bits(smi, 1, &ack); + if (ack == 0) + break; + + if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) { + dev_err(smi->parent, "ACK timeout\n"); + return -ETIMEDOUT; + } + } while (1); + + return 0; +} + +static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return rtl8366_smi_wait_for_ack(smi); +} + +static int rtl8366_smi_write_byte_noack(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return 0; +} + +static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x00, 1); + + return 0; +} + +static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x01, 1); + + return 0; +} + +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + unsigned long flags; + u8 lo = 0; + u8 hi = 0; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send READ command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_read); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* read DATA[7:0] */ + rtl8366_smi_read_byte0(smi, &lo); + /* read DATA[15:8] */ + rtl8366_smi_read_byte1(smi, &hi); + + *data = ((u32) lo) | (((u32) hi) << 8); + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg); + +static int __rtl8366_smi_write_reg(struct rtl8366_smi *smi, + u32 addr, u32 data, bool ack) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send WRITE command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_write); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* write DATA[7:0] */ + ret = rtl8366_smi_write_byte(smi, data & 0xff); + if (ret) + goto out; + + /* write DATA[15:8] */ + if (ack) + ret = rtl8366_smi_write_byte(smi, data >> 8); + else + ret = rtl8366_smi_write_byte_noack(smi, data >> 8); + if (ret) + goto out; + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + return __rtl8366_smi_write_reg(smi, addr, data, true); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg); + +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + return __rtl8366_smi_write_reg(smi, addr, data, false); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg_noack); + +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data) +{ + u32 t; + int err; + + err = rtl8366_smi_read_reg(smi, addr, &t); + if (err) + return err; + + err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data); + return err; + +} +EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr); + +static int rtl8366_reset(struct rtl8366_smi *smi) +{ + if (smi->hw_reset) { + smi->hw_reset(true); + msleep(RTL8366_SMI_HW_STOP_DELAY); + smi->hw_reset(false); + msleep(RTL8366_SMI_HW_START_DELAY); + return 0; + } + + return smi->ops->reset_chip(smi); +} + +static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used) +{ + int err; + int i; + + *used = 0; + for (i = 0; i < smi->num_ports; i++) { + int index = 0; + + err = smi->ops->get_mc_index(smi, i, &index); + if (err) + return err; + + if (mc_index == index) { + *used = 1; + break; + } + } + + return 0; +} + +static int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member, + u32 untag, u32 fid) +{ + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Update the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlan4k.member = member; + vlan4k.untag = untag; + vlan4k.fid = fid; + err = smi->ops->set_vlan_4k(smi, &vlan4k); + if (err) + return err; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + struct rtl8366_vlan_mc vlanmc; + + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + /* update the MC entry */ + vlanmc.member = member; + vlanmc.untag = untag; + vlanmc.fid = fid; + + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + break; + } + } + + return err; +} + +static int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int index; + + err = smi->ops->get_mc_index(smi, port, &index); + if (err) + return err; + + err = smi->ops->get_vlan_mc(smi, index, &vlanmc); + if (err) + return err; + + *val = vlanmc.vid; + return 0; +} + +static int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port, + unsigned vid) +{ + struct rtl8366_vlan_mc vlanmc; + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* We have no MC entry for this VID, try to find an empty one */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vlanmc.vid == 0 && vlanmc.member == 0) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* MC table is full, try to find an unused entry and replace it */ + for (i = 0; i < smi->num_vlan_mc; i++) { + int used; + + err = rtl8366_mc_is_used(smi, i, &used); + if (err) + return err; + + if (!used) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + dev_err(smi->parent, + "all VLAN member configurations are in use\n"); + + return -ENOSPC; +} + +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + int err; + + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + + if (!enable) { + smi->vlan4k_enabled = 0; + err = smi->ops->enable_vlan4k(smi, enable); + } + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_vlan); + +static int rtl8366_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + int err; + + if (enable) { + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + } + + err = smi->ops->enable_vlan4k(smi, enable); + if (err) + return err; + + smi->vlan4k_enabled = enable; + return 0; +} + +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable) +{ + int port; + int err; + + for (port = 0; port < smi->num_ports; port++) { + err = smi->ops->enable_port(smi, port, enable); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_all_ports); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int i; + + rtl8366_enable_vlan(smi, 0); + rtl8366_enable_vlan4k(smi, 0); + + /* clear VLAN member configurations */ + vlanmc.vid = 0; + vlanmc.priority = 0; + vlanmc.member = 0; + vlanmc.untag = 0; + vlanmc.fid = 0; + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); + +static int rtl8366_init_vlan(struct rtl8366_smi *smi) +{ + int port; + int err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + for (port = 0; port < smi->num_ports; port++) { + u32 mask; + + if (port == smi->cpu_port) + mask = (1 << smi->num_ports) - 1; + else + mask = (1 << port) | (1 << smi->cpu_port); + + err = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0); + if (err) + return err; + + err = rtl8366_set_pvid(smi, port, (port + 1)); + if (err) + return err; + } + + return rtl8366_enable_vlan(smi, 1); +} + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_debugfs_open); + +static ssize_t rtl8366_read_debugfs_vlan_mc(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2s %6s %4s %6s %6s %3s\n", + "id", "vid","prio", "member", "untag", "fid"); + + for (i = 0; i < smi->num_vlan_mc; ++i) { + struct rtl8366_vlan_mc vlanmc; + + smi->ops->get_vlan_mc(smi, i, &vlanmc); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2d %6d %4d 0x%04x 0x%04x %3d\n", + i, vlanmc.vid, vlanmc.priority, + vlanmc.member, vlanmc.untag, vlanmc.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +#define RTL8366_VLAN4K_PAGE_SIZE 64 +#define RTL8366_VLAN4K_NUM_PAGES (4096 / RTL8366_VLAN4K_PAGE_SIZE) + +static ssize_t rtl8366_read_debugfs_vlan_4k(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + int offset; + char *buf = smi->buf; + + if (smi->dbg_vlan_4k_page >= RTL8366_VLAN4K_NUM_PAGES) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "invalid page: %u\n", smi->dbg_vlan_4k_page); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4s %6s %6s %3s\n", + "vid", "member", "untag", "fid"); + + offset = RTL8366_VLAN4K_PAGE_SIZE * smi->dbg_vlan_4k_page; + for (i = 0; i < RTL8366_VLAN4K_PAGE_SIZE; i++) { + struct rtl8366_vlan_4k vlan4k; + + smi->ops->get_vlan_4k(smi, offset + i, &vlan4k); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d 0x%04x 0x%04x %3d\n", + vlan4k.vid, vlan4k.member, + vlan4k.untag, vlan4k.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_pvid(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + char *buf = smi->buf; + int len = 0; + int i; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%4s %4s\n", + "port", "pvid"); + + for (i = 0; i < smi->num_ports; i++) { + int pvid; + int err; + + err = rtl8366_get_pvid(smi, i, &pvid); + if (err) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d error\n", i); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d %4d\n", i, pvid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_reg(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + u32 t, reg = smi->dbg_reg; + int err, len = 0; + char *buf = smi->buf; + + memset(buf, '\0', sizeof(smi->buf)); + + err = rtl8366_smi_read_reg(smi, reg, &t); + if (err) { + len += snprintf(buf, sizeof(smi->buf), + "Read failed (reg: 0x%04x)\n", reg); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf, sizeof(smi->buf), "reg = 0x%04x, val = 0x%04x\n", + reg, t); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_write_debugfs_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + unsigned long data; + u32 reg = smi->dbg_reg; + int err; + size_t len; + char *buf = smi->buf; + + len = min(count, sizeof(smi->buf) - 1); + if (copy_from_user(buf, user_buf, len)) { + dev_err(smi->parent, "copy from user failed\n"); + return -EFAULT; + } + + buf[len] = '\0'; + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + + if (strict_strtoul(buf, 16, &data)) { + dev_err(smi->parent, "Invalid reg value %s\n", buf); + } else { + err = rtl8366_smi_write_reg(smi, reg, data); + if (err) { + dev_err(smi->parent, + "writing reg 0x%04x val 0x%04lx failed\n", + reg, data); + } + } + + return count; +} + +static ssize_t rtl8366_read_debugfs_mibs(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = file->private_data; + int i, j, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s", + "Counter"); + + for (i = 0; i < smi->num_ports; i++) { + char port_buf[10]; + + snprintf(port_buf, sizeof(port_buf), "Port %d", i); + len += snprintf(buf + len, sizeof(smi->buf) - len, " %12s", + port_buf); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + + for (i = 0; i < smi->num_mib_counters; i++) { + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s ", + smi->mib_counters[i].name); + for (j = 0; j < smi->num_ports; j++) { + unsigned long long counter = 0; + + if (!smi->ops->get_mib_counter(smi, i, j, &counter)) + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12llu ", counter); + else + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12s ", "error"); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_rtl8366_regs = { + .read = rtl8366_read_debugfs_reg, + .write = rtl8366_write_debugfs_reg, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_mc = { + .read = rtl8366_read_debugfs_vlan_mc, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_4k = { + .read = rtl8366_read_debugfs_vlan_4k, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_pvid = { + .read = rtl8366_read_debugfs_pvid, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_mibs = { + .read = rtl8366_read_debugfs_mibs, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static void rtl8366_debugfs_init(struct rtl8366_smi *smi) +{ + struct dentry *node; + struct dentry *root; + + if (!smi->debugfs_root) + smi->debugfs_root = debugfs_create_dir(dev_name(smi->parent), + NULL); + + if (!smi->debugfs_root) { + dev_err(smi->parent, "Unable to create debugfs dir\n"); + return; + } + root = smi->debugfs_root; + + node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root, + &smi->dbg_reg); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "reg"); + return; + } + + node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, smi, + &fops_rtl8366_regs); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "val"); + return; + } + + node = debugfs_create_file("vlan_mc", S_IRUSR, root, smi, + &fops_rtl8366_vlan_mc); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_mc"); + return; + } + + node = debugfs_create_u8("vlan_4k_page", S_IRUGO | S_IWUSR, root, + &smi->dbg_vlan_4k_page); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k_page"); + return; + } + + node = debugfs_create_file("vlan_4k", S_IRUSR, root, smi, + &fops_rtl8366_vlan_4k); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k"); + return; + } + + node = debugfs_create_file("pvid", S_IRUSR, root, smi, + &fops_rtl8366_pvid); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "pvid"); + return; + } + + node = debugfs_create_file("mibs", S_IRUSR, smi->debugfs_root, smi, + &fops_rtl8366_mibs); + if (!node) + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "mibs"); +} + +static void rtl8366_debugfs_remove(struct rtl8366_smi *smi) +{ + if (smi->debugfs_root) { + debugfs_remove_recursive(smi->debugfs_root); + smi->debugfs_root = NULL; + } +} +#else +static inline void rtl8366_debugfs_init(struct rtl8366_smi *smi) {} +static inline void rtl8366_debugfs_remove(struct rtl8366_smi *smi) {} +#endif /* CONFIG_RTL8366_SMI_DEBUG_FS */ + +static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) +{ + int ret; + int i; + + smi->mii_bus = mdiobus_alloc(); + if (smi->mii_bus == NULL) { + ret = -ENOMEM; + goto err; + } + + smi->mii_bus->priv = (void *) smi; + smi->mii_bus->name = dev_name(smi->parent); + smi->mii_bus->read = smi->ops->mii_read; + smi->mii_bus->write = smi->ops->mii_write; + snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", + dev_name(smi->parent)); + smi->mii_bus->parent = smi->parent; + smi->mii_bus->phy_mask = ~(0x1f); + smi->mii_bus->irq = smi->mii_irq; + for (i = 0; i < PHY_MAX_ADDR; i++) + smi->mii_irq[i] = PHY_POLL; + + ret = mdiobus_register(smi->mii_bus); + if (ret) + goto err_free; + + return 0; + + err_free: + mdiobus_free(smi->mii_bus); + err: + return ret; +} + +static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) +{ + mdiobus_unregister(smi->mii_bus); + mdiobus_free(smi->mii_bus); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + err = rtl8366_reset(smi); + if (err) + return err; + + err = smi->ops->setup(smi); + if (err) + return err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + err = rtl8366_enable_vlan(smi, 1); + if (err) + return err; + + return rtl8366_enable_all_ports(smi, 1); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_reset_switch); + +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_get_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_pvid); + +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_set_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_port_pvid); + +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int i, len = 0; + unsigned long long counter = 0; + char *buf = smi->buf; + + if (val->port_vlan >= smi->num_ports) + return -EINVAL; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "Port %d MIB counters\n", + val->port_vlan); + + for (i = 0; i < smi->num_mib_counters; ++i) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%-36s: ", smi->mib_counters[i].name); + if (!smi->ops->get_mib_counter(smi, i, val->port_vlan, + &counter)) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%llu\n", counter); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%s\n", "error"); + } + + val->value.s = buf; + val->len = len; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_mib); + +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int i; + u32 len = 0; + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char *buf = smi->buf; + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + memset(buf, '\0', sizeof(smi->buf)); + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "VLAN %d: Ports: '", vlan4k.vid); + + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & (1 << i))) + continue; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%d%s", i, + (vlan4k.untag & (1 << i)) ? "" : "t"); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "', members=%04x, untag=%04x, fid=%u", + vlan4k.member, vlan4k.untag, vlan4k.fid); + + val->value.s = buf; + val->len = len; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_info); + +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + struct rtl8366_vlan_4k vlan4k; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + + port = &val->value.ports[0]; + val->len = 0; + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & BIT(i))) + continue; + + port->id = i; + port->flags = (vlan4k.untag & BIT(i)) ? + 0 : BIT(SWITCH_PORT_FLAG_TAGGED); + val->len++; + port++; + } + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_ports); + +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + u32 member = 0; + u32 untag = 0; + int err; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + port = &val->value.ports[0]; + for (i = 0; i < val->len; i++, port++) { + member |= BIT(port->id); + + if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) + untag |= BIT(port->id); + + /* + * To ensure that we have a valid MC entry for this VLAN, + * initialize the port VLAN ID here. + */ + err = rtl8366_set_pvid(smi, port->id, val->port_vlan); + if (err < 0) + return err; + } + + return rtl8366_set_vlan(smi, val->port_vlan, member, untag, 0); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_ports); + +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + val->value.i = vlan4k.fid; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_fid); + +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + if (val->value.i < 0 || val->value.i > attr->max) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + return rtl8366_set_vlan(smi, val->port_vlan, + vlan4k.member, + vlan4k.untag, + val->value.i); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_fid); + +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + val->value.i = smi->vlan_enabled; + else + val->value.i = smi->vlan4k_enabled; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_enable); + +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + err = rtl8366_enable_vlan(smi, val->value.i); + else + err = rtl8366_enable_vlan4k(smi, val->value.i); + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_enable); + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent) +{ + struct rtl8366_smi *smi; + + BUG_ON(!parent); + + smi = kzalloc(sizeof(*smi), GFP_KERNEL); + if (!smi) { + dev_err(parent, "no memory for private data\n"); + return NULL; + } + + smi->parent = parent; + return smi; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_alloc); + +static int __rtl8366_smi_init(struct rtl8366_smi *smi, const char *name) +{ + int err; + + err = gpio_request(smi->gpio_sda, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sda, err); + goto err_out; + } + + err = gpio_request(smi->gpio_sck, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sck, err); + goto err_free_sda; + } + + spin_lock_init(&smi->lock); + + /* start the switch */ + if (smi->hw_reset) { + smi->hw_reset(false); + msleep(RTL8366_SMI_HW_START_DELAY); + } + + return 0; + + err_free_sda: + gpio_free(smi->gpio_sda); + err_out: + return err; +} + +static void __rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + if (smi->hw_reset) + smi->hw_reset(true); + + gpio_free(smi->gpio_sck); + gpio_free(smi->gpio_sda); +} + +enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata) +{ + static struct rtl8366_smi smi; + enum rtl8366_type type = RTL8366_TYPE_UNKNOWN; + u32 reg = 0; + + memset(&smi, 0, sizeof(smi)); + smi.gpio_sda = pdata->gpio_sda; + smi.gpio_sck = pdata->gpio_sck; + smi.clk_delay = 10; + smi.cmd_read = 0xa9; + smi.cmd_write = 0xa8; + + if (__rtl8366_smi_init(&smi, "rtl8366")) + goto out; + + if (rtl8366_smi_read_reg(&smi, 0x5c, ®)) + goto cleanup; + + switch(reg) { + case 0x6027: + printk("Found an RTL8366S switch\n"); + type = RTL8366_TYPE_S; + break; + case 0x5937: + printk("Found an RTL8366RB switch\n"); + type = RTL8366_TYPE_RB; + break; + default: + printk("Found an Unknown RTL8366 switch (id=0x%04x)\n", reg); + break; + } + +cleanup: + __rtl8366_smi_cleanup(&smi); +out: + return type; +} + +int rtl8366_smi_init(struct rtl8366_smi *smi) +{ + int err; + + if (!smi->ops) + return -EINVAL; + + err = __rtl8366_smi_init(smi, dev_name(smi->parent)); + if (err) + goto err_out; + + dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n", + smi->gpio_sda, smi->gpio_sck); + + err = smi->ops->detect(smi); + if (err) { + dev_err(smi->parent, "chip detection failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_reset(smi); + if (err) + goto err_free_sck; + + err = smi->ops->setup(smi); + if (err) { + dev_err(smi->parent, "chip setup failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_init_vlan(smi); + if (err) { + dev_err(smi->parent, "VLAN initialization failed, err=%d\n", + err); + goto err_free_sck; + } + + err = rtl8366_enable_all_ports(smi, 1); + if (err) + goto err_free_sck; + + err = rtl8366_smi_mii_init(smi); + if (err) + goto err_free_sck; + + rtl8366_debugfs_init(smi); + + return 0; + + err_free_sck: + __rtl8366_smi_cleanup(smi); + err_out: + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_init); + +void rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + rtl8366_debugfs_remove(smi); + rtl8366_smi_mii_cleanup(smi); + __rtl8366_smi_cleanup(smi); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup); + +MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver"); +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h b/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h new file mode 100644 index 000000000..700f83c1a --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h @@ -0,0 +1,149 @@ +/* + * Realtek RTL8366 SMI interface driver defines + * + * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> + * + * 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 _RTL8366_SMI_H +#define _RTL8366_SMI_H + +#include <linux/phy.h> +#include <linux/switch.h> + +struct rtl8366_smi_ops; +struct rtl8366_vlan_ops; +struct mii_bus; +struct dentry; +struct inode; +struct file; + +struct rtl8366_mib_counter { + unsigned base; + unsigned offset; + unsigned length; + const char *name; +}; + +struct rtl8366_smi { + struct device *parent; + unsigned int gpio_sda; + unsigned int gpio_sck; + void (*hw_reset)(bool active); + unsigned int clk_delay; /* ns */ + u8 cmd_read; + u8 cmd_write; + spinlock_t lock; + struct mii_bus *mii_bus; + int mii_irq[PHY_MAX_ADDR]; + struct switch_dev sw_dev; + + unsigned int cpu_port; + unsigned int num_ports; + unsigned int num_vlan_mc; + unsigned int num_mib_counters; + struct rtl8366_mib_counter *mib_counters; + + struct rtl8366_smi_ops *ops; + + int vlan_enabled; + int vlan4k_enabled; + + char buf[4096]; +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS + struct dentry *debugfs_root; + u16 dbg_reg; + u8 dbg_vlan_4k_page; +#endif +}; + +struct rtl8366_vlan_mc { + u16 vid; + u16 untag; + u16 member; + u8 fid; + u8 priority; +}; + +struct rtl8366_vlan_4k { + u16 vid; + u16 untag; + u16 member; + u8 fid; +}; + +struct rtl8366_smi_ops { + int (*detect)(struct rtl8366_smi *smi); + int (*reset_chip)(struct rtl8366_smi *smi); + int (*setup)(struct rtl8366_smi *smi); + + int (*mii_read)(struct mii_bus *bus, int addr, int reg); + int (*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val); + + int (*get_vlan_mc)(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc); + int (*set_vlan_mc)(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc); + int (*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k); + int (*set_vlan_4k)(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k); + int (*get_mc_index)(struct rtl8366_smi *smi, int port, int *val); + int (*set_mc_index)(struct rtl8366_smi *smi, int port, int index); + int (*get_mib_counter)(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val); + int (*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan); + int (*enable_vlan)(struct rtl8366_smi *smi, int enable); + int (*enable_vlan4k)(struct rtl8366_smi *smi, int enable); + int (*enable_port)(struct rtl8366_smi *smi, int port, int enable); +}; + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent); +int rtl8366_smi_init(struct rtl8366_smi *smi); +void rtl8366_smi_cleanup(struct rtl8366_smi *smi); +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data); +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi); +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable); +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable); + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file); +#endif + +static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw) +{ + return container_of(sw, struct rtl8366_smi, sw_dev); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev); +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val); +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val); +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); + +#endif /* _RTL8366_SMI_H */ diff --git a/target/linux/generic/files/drivers/net/phy/rtl8366rb.c b/target/linux/generic/files/drivers/net/phy/rtl8366rb.c new file mode 100644 index 000000000..775949792 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/rtl8366rb.c @@ -0,0 +1,1271 @@ +/* + * Platform driver for the Realtek RTL8366RB ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com> + * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/skbuff.h> +#include <linux/rtl8366.h> + +#include "rtl8366_smi.h" + +#define RTL8366RB_DRIVER_DESC "Realtek RTL8366RB ethernet switch driver" +#define RTL8366RB_DRIVER_VER "0.2.3" + +#define RTL8366RB_PHY_NO_MAX 4 +#define RTL8366RB_PHY_PAGE_MAX 7 +#define RTL8366RB_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366RB_SGCR 0x0000 +#define RTL8366RB_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366RB_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366RB_SGCR_MAX_LENGTH_MASK RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_MAX_LENGTH_1522 RTL8366RB_SGCR_MAX_LENGTH(0x0) +#define RTL8366RB_SGCR_MAX_LENGTH_1536 RTL8366RB_SGCR_MAX_LENGTH(0x1) +#define RTL8366RB_SGCR_MAX_LENGTH_1552 RTL8366RB_SGCR_MAX_LENGTH(0x2) +#define RTL8366RB_SGCR_MAX_LENGTH_9216 RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_EN_VLAN BIT(13) +#define RTL8366RB_SGCR_EN_VLAN_4KTB BIT(14) + +/* Port Enable Control register */ +#define RTL8366RB_PECR 0x0001 + +/* Switch Security Control registers */ +#define RTL8366RB_SSCR0 0x0002 +#define RTL8366RB_SSCR1 0x0003 +#define RTL8366RB_SSCR2 0x0004 +#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366RB_RESET_CTRL_REG 0x0100 +#define RTL8366RB_CHIP_CTRL_RESET_HW 1 +#define RTL8366RB_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366RB_CHIP_VERSION_CTRL_REG 0x050A +#define RTL8366RB_CHIP_VERSION_MASK 0xf +#define RTL8366RB_CHIP_ID_REG 0x0509 +#define RTL8366RB_CHIP_ID_8366 0x5937 + +/* PHY registers control */ +#define RTL8366RB_PHY_ACCESS_CTRL_REG 0x8000 +#define RTL8366RB_PHY_ACCESS_DATA_REG 0x8002 + +#define RTL8366RB_PHY_CTRL_READ 1 +#define RTL8366RB_PHY_CTRL_WRITE 0 + +#define RTL8366RB_PHY_REG_MASK 0x1f +#define RTL8366RB_PHY_PAGE_OFFSET 5 +#define RTL8366RB_PHY_PAGE_MASK (0xf << 5) +#define RTL8366RB_PHY_NO_OFFSET 9 +#define RTL8366RB_PHY_NO_MASK (0x1f << 9) + +#define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f + +/* LED control registers */ +#define RTL8366RB_LED_BLINKRATE_REG 0x0430 +#define RTL8366RB_LED_BLINKRATE_BIT 0 +#define RTL8366RB_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366RB_LED_CTRL_REG 0x0431 +#define RTL8366RB_LED_0_1_CTRL_REG 0x0432 +#define RTL8366RB_LED_2_3_CTRL_REG 0x0433 + +#define RTL8366RB_MIB_COUNT 33 +#define RTL8366RB_GLOBAL_MIB_COUNT 1 +#define RTL8366RB_MIB_COUNTER_PORT_OFFSET 0x0050 +#define RTL8366RB_MIB_COUNTER_BASE 0x1000 +#define RTL8366RB_MIB_CTRL_REG 0x13F0 +#define RTL8366RB_MIB_CTRL_USER_MASK 0x0FFC +#define RTL8366RB_MIB_CTRL_BUSY_MASK BIT(0) +#define RTL8366RB_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8366RB_MIB_CTRL_PORT_RESET(_p) BIT(2 + (_p)) +#define RTL8366RB_MIB_CTRL_GLOBAL_RESET BIT(11) + +#define RTL8366RB_PORT_VLAN_CTRL_BASE 0x0063 +#define RTL8366RB_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366RB_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366RB_VLAN_TABLE_READ_BASE 0x018C +#define RTL8366RB_VLAN_TABLE_WRITE_BASE 0x0185 + + +#define RTL8366RB_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366RB_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366RB_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366RB_VLAN_MC_BASE(_x) (0x0020 + (_x) * 3) + + +#define RTL8366RB_PORT_LINK_STATUS_BASE 0x0014 +#define RTL8366RB_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366RB_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366RB_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366RB_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366RB_PORT_NUM_CPU 5 +#define RTL8366RB_NUM_PORTS 6 +#define RTL8366RB_NUM_VLANS 16 +#define RTL8366RB_NUM_LEDGROUPS 4 +#define RTL8366RB_NUM_VIDS 4096 +#define RTL8366RB_PRIORITYMAX 7 +#define RTL8366RB_FIDMAX 7 + + +#define RTL8366RB_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366RB_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366RB_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366RB_PORT_4 (1 << 3) /* In userspace port 3 */ +#define RTL8366RB_PORT_5 (1 << 4) /* In userspace port 4 */ + +#define RTL8366RB_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366RB_PORT_ALL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5 | \ + RTL8366RB_PORT_CPU) + +#define RTL8366RB_PORT_ALL_BUT_CPU (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5) + +#define RTL8366RB_PORT_ALL_EXTERNAL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4) + +#define RTL8366RB_PORT_ALL_INTERNAL RTL8366RB_PORT_CPU + +#define RTL8366RB_VLAN_VID_MASK 0xfff +#define RTL8366RB_VLAN_PRIORITY_SHIFT 12 +#define RTL8366RB_VLAN_PRIORITY_MASK 0x7 +#define RTL8366RB_VLAN_UNTAG_SHIFT 8 +#define RTL8366RB_VLAN_UNTAG_MASK 0xff +#define RTL8366RB_VLAN_MEMBER_MASK 0xff +#define RTL8366RB_VLAN_FID_MASK 0x7 + + +/* Port ingress bandwidth control */ +#define RTL8366RB_IB_BASE 0x0200 +#define RTL8366RB_IB_REG(pnum) (RTL8366RB_IB_BASE + pnum) +#define RTL8366RB_IB_BDTH_MASK 0x3fff +#define RTL8366RB_IB_PREIFG_OFFSET 14 +#define RTL8366RB_IB_PREIFG_MASK (1 << RTL8366RB_IB_PREIFG_OFFSET) + +/* Port egress bandwidth control */ +#define RTL8366RB_EB_BASE 0x02d1 +#define RTL8366RB_EB_REG(pnum) (RTL8366RB_EB_BASE + pnum) +#define RTL8366RB_EB_BDTH_MASK 0x3fff +#define RTL8366RB_EB_PREIFG_REG 0x02f8 +#define RTL8366RB_EB_PREIFG_OFFSET 9 +#define RTL8366RB_EB_PREIFG_MASK (1 << RTL8366RB_EB_PREIFG_OFFSET) + +#define RTL8366RB_BDTH_SW_MAX 1048512 +#define RTL8366RB_BDTH_UNIT 64 +#define RTL8366RB_BDTH_REG_DEFAULT 16383 + +/* QOS */ +#define RTL8366RB_QOS_BIT 15 +#define RTL8366RB_QOS_MASK (1 << RTL8366RB_QOS_BIT) +/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */ +#define RTL8366RB_QOS_DEFAULT_PREIFG 1 + + +static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366rb_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG, + RTL8366RB_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366RB_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366RB_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366rb_setup(struct rtl8366_smi *smi) +{ + int err; + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_MAX_LENGTH_MASK, + RTL8366RB_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366RB_VLAN_INGRESS_CTRL2_REG, RTL8366RB_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366RB_SSCR2, RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366rb_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366RB_NUM_PORTS || counter >= RTL8366RB_MIB_COUNT) + return -EINVAL; + + addr = RTL8366RB_MIB_COUNTER_BASE + + RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) + + rtl8366rb_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366RB_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366RB_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366RB_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366rb_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366rb_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366RB_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_VLAN_TABLE_WRITE_BASE, + vid & RTL8366RB_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + if (vlan4k->vid >= RTL8366RB_NUM_VIDS || + vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366rb_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) & + RTL8366RB_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + if (index >= RTL8366RB_NUM_VLANS || + vlanmc->vid >= RTL8366RB_NUM_VIDS || + vlanmc->priority > RTL8366RB_PRIORITYMAX || + vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) << + RTL8366RB_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366rb_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366RB_PORT_VLAN_CTRL_MASK; + + return 0; + +} + +static int rtl8366rb_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366RB_NUM_PORTS || index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + RTL8366RB_PORT_VLAN_CTRL_MASK << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366RB_PORT_VLAN_CTRL_MASK) << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366rb_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366RB_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366RB_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366rb_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN, + (enable) ? RTL8366RB_SGCR_EN_VLAN : 0); +} + +static int rtl8366rb_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, + RTL8366RB_SGCR_EN_VLAN_4KTB, + (enable) ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0); +} + +static int rtl8366rb_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366rb_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_GLOBAL_RESET); +} + +static int rtl8366rb_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366RB_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366rb_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_LED_BLINKRATE_REG, + RTL8366RB_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366rb_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366rb_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366RB_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, portmask); + + return 0; +} + +static int rtl8366rb_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366RB_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366RB_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366RB_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366RB_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366RB_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366RB_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366rb_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->port_vlan == RTL8366RB_PORT_NUM_CPU) { + reg = RTL8366RB_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366RB_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366rb_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366RB_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366rb_sw_set_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 mask, data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + mask = 1 << val->port_vlan ; + if (val->value.i) + data = mask; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, mask, data); +} + +static int rtl8366rb_sw_get_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PECR, &data); + if (data & (1 << val->port_vlan)) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_IB_REG(val->port_vlan), + RTL8366RB_IB_BDTH_MASK | RTL8366RB_IB_PREIFG_MASK, + val->value.i | + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_IB_PREIFG_OFFSET)); + +} + +static int rtl8366rb_sw_get_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_IB_REG(val->port_vlan), &data); + data &= RTL8366RB_IB_BDTH_MASK; + if (data < RTL8366RB_IB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_rmwr(smi, RTL8366RB_EB_PREIFG_REG, + RTL8366RB_EB_PREIFG_MASK, + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_EB_PREIFG_OFFSET)); + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_EB_REG(val->port_vlan), + RTL8366RB_EB_BDTH_MASK, val->value.i ); + +} + +static int rtl8366rb_sw_get_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_EB_REG(val->port_vlan), &data); + data &= RTL8366RB_EB_BDTH_MASK; + if (data < RTL8366RB_EB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_QOS_MASK; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_QOS_MASK, data); +} + +static int rtl8366rb_sw_get_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SGCR, &data); + if (data & RTL8366RB_QOS_MASK) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_PORT_RESET(val->port_vlan)); +} + +static struct switch_attr rtl8366rb_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366rb_sw_set_learning_enable, + .get = rtl8366rb_sw_get_learning_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366rb_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366rb_sw_set_blinkrate, + .get = rtl8366rb_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_qos", + .description = "Enable QOS", + .set = rtl8366rb_sw_set_qos_enable, + .get = rtl8366rb_sw_get_qos_enable, + .max = 1 + }, +}; + +static struct switch_attr rtl8366rb_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366rb_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366rb_sw_set_port_led, + .get = rtl8366rb_sw_get_port_led, + }, { + .type = SWITCH_TYPE_INT, + .name = "disable", + .description = "Get/Set port state (enabled or disabled)", + .max = 1, + .set = rtl8366rb_sw_set_port_disable, + .get = rtl8366rb_sw_get_port_disable, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_in", + .description = "Get/Set port ingress (incoming) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_in, + .get = rtl8366rb_sw_get_port_rate_in, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_out", + .description = "Get/Set port egress (outgoing) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_out, + .get = rtl8366rb_sw_get_port_rate_out, + }, +}; + +static struct switch_attr rtl8366rb_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366RB_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366rb_globals, + .n_attr = ARRAY_SIZE(rtl8366rb_globals), + }, + .attr_port = { + .attr = rtl8366rb_port, + .n_attr = ARRAY_SIZE(rtl8366rb_port), + }, + .attr_vlan = { + .attr = rtl8366rb_vlan, + .n_attr = ARRAY_SIZE(rtl8366rb_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366rb_sw_get_port_link, +}; + +static int rtl8366rb_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366RB"; + dev->cpu_port = RTL8366RB_PORT_NUM_CPU; + dev->ports = RTL8366RB_NUM_PORTS; + dev->vlans = RTL8366RB_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366rb_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366rb_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366rb_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366rb_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366rb_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366rb_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366rb_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366RB_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366rb_smi_ops = { + .detect = rtl8366rb_detect, + .reset_chip = rtl8366rb_reset_chip, + .setup = rtl8366rb_setup, + + .mii_read = rtl8366rb_mii_read, + .mii_write = rtl8366rb_mii_write, + + .get_vlan_mc = rtl8366rb_get_vlan_mc, + .set_vlan_mc = rtl8366rb_set_vlan_mc, + .get_vlan_4k = rtl8366rb_get_vlan_4k, + .set_vlan_4k = rtl8366rb_set_vlan_4k, + .get_mc_index = rtl8366rb_get_mc_index, + .set_mc_index = rtl8366rb_set_mc_index, + .get_mib_counter = rtl8366rb_get_mib_counter, + .is_vlan_valid = rtl8366rb_is_vlan_valid, + .enable_vlan = rtl8366rb_enable_vlan, + .enable_vlan4k = rtl8366rb_enable_vlan4k, + .enable_port = rtl8366rb_enable_port, +}; + +static int __devinit rtl8366rb_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_platform_data *pdata; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366RB_DRIVER_DESC + " version " RTL8366RB_DRIVER_VER"\n"); + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + err = -EINVAL; + goto err_out; + } + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) { + err = -ENOMEM; + goto err_out; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->hw_reset = pdata->hw_reset; + + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366rb_smi_ops; + smi->cpu_port = RTL8366RB_PORT_NUM_CPU; + smi->num_ports = RTL8366RB_NUM_PORTS; + smi->num_vlan_mc = RTL8366RB_NUM_VLANS; + smi->mib_counters = rtl8366rb_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366rb_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + err_out: + return err; +} + +static int __devexit rtl8366rb_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366rb_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static struct platform_driver rtl8366rb_driver = { + .driver = { + .name = RTL8366RB_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = rtl8366rb_probe, + .remove = __devexit_p(rtl8366rb_remove), +}; + +static int __init rtl8366rb_module_init(void) +{ + return platform_driver_register(&rtl8366rb_driver); +} +module_init(rtl8366rb_module_init); + +static void __exit rtl8366rb_module_exit(void) +{ + platform_driver_unregister(&rtl8366rb_driver); +} +module_exit(rtl8366rb_module_exit); + +MODULE_DESCRIPTION(RTL8366RB_DRIVER_DESC); +MODULE_VERSION(RTL8366RB_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>"); +MODULE_AUTHOR("Roman Yeryomin <roman@advem.lv>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8366s.c b/target/linux/generic/files/drivers/net/phy/rtl8366s.c new file mode 100644 index 000000000..77427d6c9 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/rtl8366s.c @@ -0,0 +1,1150 @@ +/* + * Platform driver for the Realtek RTL8366S ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/skbuff.h> +#include <linux/rtl8366.h> + +#include "rtl8366_smi.h" + +#define RTL8366S_DRIVER_DESC "Realtek RTL8366S ethernet switch driver" +#define RTL8366S_DRIVER_VER "0.2.2" + +#define RTL8366S_PHY_NO_MAX 4 +#define RTL8366S_PHY_PAGE_MAX 7 +#define RTL8366S_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366S_SGCR 0x0000 +#define RTL8366S_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366S_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366S_SGCR_MAX_LENGTH_MASK RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_MAX_LENGTH_1522 RTL8366S_SGCR_MAX_LENGTH(0x0) +#define RTL8366S_SGCR_MAX_LENGTH_1536 RTL8366S_SGCR_MAX_LENGTH(0x1) +#define RTL8366S_SGCR_MAX_LENGTH_1552 RTL8366S_SGCR_MAX_LENGTH(0x2) +#define RTL8366S_SGCR_MAX_LENGTH_16000 RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_EN_VLAN BIT(13) + +/* Port Enable Control register */ +#define RTL8366S_PECR 0x0001 + +/* Switch Security Control registers */ +#define RTL8366S_SSCR0 0x0002 +#define RTL8366S_SSCR1 0x0003 +#define RTL8366S_SSCR2 0x0004 +#define RTL8366S_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366S_RESET_CTRL_REG 0x0100 +#define RTL8366S_CHIP_CTRL_RESET_HW 1 +#define RTL8366S_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366S_CHIP_VERSION_CTRL_REG 0x0104 +#define RTL8366S_CHIP_VERSION_MASK 0xf +#define RTL8366S_CHIP_ID_REG 0x0105 +#define RTL8366S_CHIP_ID_8366 0x8366 + +/* PHY registers control */ +#define RTL8366S_PHY_ACCESS_CTRL_REG 0x8028 +#define RTL8366S_PHY_ACCESS_DATA_REG 0x8029 + +#define RTL8366S_PHY_CTRL_READ 1 +#define RTL8366S_PHY_CTRL_WRITE 0 + +#define RTL8366S_PHY_REG_MASK 0x1f +#define RTL8366S_PHY_PAGE_OFFSET 5 +#define RTL8366S_PHY_PAGE_MASK (0x7 << 5) +#define RTL8366S_PHY_NO_OFFSET 9 +#define RTL8366S_PHY_NO_MASK (0x1f << 9) + +/* LED control registers */ +#define RTL8366S_LED_BLINKRATE_REG 0x0420 +#define RTL8366S_LED_BLINKRATE_BIT 0 +#define RTL8366S_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366S_LED_CTRL_REG 0x0421 +#define RTL8366S_LED_0_1_CTRL_REG 0x0422 +#define RTL8366S_LED_2_3_CTRL_REG 0x0423 + +#define RTL8366S_MIB_COUNT 33 +#define RTL8366S_GLOBAL_MIB_COUNT 1 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET 0x0040 +#define RTL8366S_MIB_COUNTER_BASE 0x1000 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET2 0x0008 +#define RTL8366S_MIB_COUNTER_BASE2 0x1180 +#define RTL8366S_MIB_CTRL_REG 0x11F0 +#define RTL8366S_MIB_CTRL_USER_MASK 0x01FF +#define RTL8366S_MIB_CTRL_BUSY_MASK 0x0001 +#define RTL8366S_MIB_CTRL_RESET_MASK 0x0002 + +#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK 0x0004 +#define RTL8366S_MIB_CTRL_PORT_RESET_BIT 0x0003 +#define RTL8366S_MIB_CTRL_PORT_RESET_MASK 0x01FC + + +#define RTL8366S_PORT_VLAN_CTRL_BASE 0x0058 +#define RTL8366S_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366S_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366S_VLAN_TABLE_READ_BASE 0x018B +#define RTL8366S_VLAN_TABLE_WRITE_BASE 0x0185 + +#define RTL8366S_VLAN_TB_CTRL_REG 0x010F + +#define RTL8366S_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366S_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366S_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366S_VLAN_MC_BASE(_x) (0x0016 + (_x) * 2) + +#define RTL8366S_VLAN_MEMBERINGRESS_REG 0x0379 + +#define RTL8366S_PORT_LINK_STATUS_BASE 0x0060 +#define RTL8366S_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366S_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366S_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366S_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366S_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366S_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366S_PORT_NUM_CPU 5 +#define RTL8366S_NUM_PORTS 6 +#define RTL8366S_NUM_VLANS 16 +#define RTL8366S_NUM_LEDGROUPS 4 +#define RTL8366S_NUM_VIDS 4096 +#define RTL8366S_PRIORITYMAX 7 +#define RTL8366S_FIDMAX 7 + + +#define RTL8366S_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366S_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366S_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366S_PORT_4 (1 << 3) /* In userspace port 3 */ + +#define RTL8366S_PORT_UNKNOWN (1 << 4) /* No known connection */ +#define RTL8366S_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366S_PORT_ALL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_PORT_ALL_BUT_CPU (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN) + +#define RTL8366S_PORT_ALL_EXTERNAL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4) + +#define RTL8366S_PORT_ALL_INTERNAL (RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_VLAN_VID_MASK 0xfff +#define RTL8366S_VLAN_PRIORITY_SHIFT 12 +#define RTL8366S_VLAN_PRIORITY_MASK 0x7 +#define RTL8366S_VLAN_MEMBER_MASK 0x3f +#define RTL8366S_VLAN_UNTAG_SHIFT 6 +#define RTL8366S_VLAN_UNTAG_MASK 0x3f +#define RTL8366S_VLAN_FID_SHIFT 12 +#define RTL8366S_VLAN_FID_MASK 0x7 + +static struct rtl8366_mib_counter rtl8366s_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + + /* + * The following counters are accessible at a different + * base address. + */ + { 1, 0, 2, "Dot1dTpPortInDiscards" }, + { 1, 2, 2, "IfOutUcastPkts" }, + { 1, 4, 2, "IfOutMulticastPkts" }, + { 1, 6, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366s_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG, + RTL8366S_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366S_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366S_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366s_setup(struct rtl8366_smi *smi) +{ + struct rtl8366_platform_data *pdata; + int err; + + pdata = smi->parent->platform_data; + if (pdata->num_initvals && pdata->initvals) { + unsigned i; + + dev_info(smi->parent, "applying initvals\n"); + for (i = 0; i < pdata->num_initvals; i++) + REG_WR(smi, pdata->initvals[i].reg, + pdata->initvals[i].val); + } + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366S_SGCR, RTL8366S_SGCR_MAX_LENGTH_MASK, + RTL8366S_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366S_VLAN_MEMBERINGRESS_REG, RTL8366S_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366S_SSCR2, RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366s_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366s_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366S_NUM_PORTS || counter >= RTL8366S_MIB_COUNT) + return -EINVAL; + + switch (rtl8366s_mib_counters[counter].base) { + case 0: + addr = RTL8366S_MIB_COUNTER_BASE + + RTL8366S_MIB_COUNTER_PORT_OFFSET * port; + break; + + case 1: + addr = RTL8366S_MIB_COUNTER_BASE2 + + RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port; + break; + + default: + return -EINVAL; + } + + addr += rtl8366s_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366S_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366S_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366S_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366s_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366s_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366S_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366S_VLAN_TABLE_WRITE_BASE, + vid & RTL8366S_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + if (vlan4k->vid >= RTL8366S_NUM_VIDS || + vlan4k->member > RTL8366S_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlan4k->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366s_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) & + RTL8366S_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + if (index >= RTL8366S_NUM_VLANS || + vlanmc->vid >= RTL8366S_NUM_VIDS || + vlanmc->priority > RTL8366S_PRIORITYMAX || + vlanmc->member > RTL8366S_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) << + RTL8366S_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlanmc->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366s_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366S_PORT_VLAN_CTRL_MASK; + + return 0; +} + +static int rtl8366s_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + RTL8366S_PORT_VLAN_CTRL_MASK << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366S_PORT_VLAN_CTRL_MASK) << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366s_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN, + (enable) ? RTL8366S_SGCR_EN_VLAN : 0); +} + +static int rtl8366s_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_VLAN_TB_CTRL_REG, + 1, (enable) ? 1 : 0); +} + +static int rtl8366s_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366S_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366S_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366s_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366s_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, 0, (1 << 2)); +} + +static int rtl8366s_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366S_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366s_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_LED_BLINKRATE_REG, + RTL8366S_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366s_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_SGCR, &data); + + val->value.i = ((data & (RTL8366S_SGCR_MAX_LENGTH_MASK)) >> 4); + + return 0; +} + +static int rtl8366s_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char length_code; + + switch (val->value.i) { + case 0: + length_code = RTL8366S_SGCR_MAX_LENGTH_1522; + break; + case 1: + length_code = RTL8366S_SGCR_MAX_LENGTH_1536; + break; + case 2: + length_code = RTL8366S_SGCR_MAX_LENGTH_1552; + break; + case 3: + length_code = RTL8366S_SGCR_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, + RTL8366S_SGCR_MAX_LENGTH_MASK, + length_code); +} + +static int rtl8366s_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi,RTL8366S_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366s_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366S_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, portmask); + + return 0; +} + +static int rtl8366s_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366S_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366S_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366S_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366S_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366S_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366S_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366s_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366S_NUM_PORTS || + (1 << val->port_vlan) == RTL8366S_PORT_UNKNOWN) + return -EINVAL; + + if (val->port_vlan == RTL8366S_PORT_NUM_CPU) { + reg = RTL8366S_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366S_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366s_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366S_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366s_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366S_NUM_PORTS) + return -EINVAL; + + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, + 0, (1 << (val->port_vlan + 3))); +} + +static struct switch_attr rtl8366s_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366s_sw_set_learning_enable, + .get = rtl8366s_sw_get_learning_enable, + .max = 1, + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366s_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366s_sw_set_blinkrate, + .get = rtl8366s_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + " (0 = 1522, 1 = 1536, 2 = 1552, 3 = 16000 (9216?))", + .set = rtl8366s_sw_set_max_length, + .get = rtl8366s_sw_get_max_length, + .max = 3, + }, +}; + +static struct switch_attr rtl8366s_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366s_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366s_sw_set_port_led, + .get = rtl8366s_sw_get_port_led, + }, +}; + +static struct switch_attr rtl8366s_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366S_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366s_globals, + .n_attr = ARRAY_SIZE(rtl8366s_globals), + }, + .attr_port = { + .attr = rtl8366s_port, + .n_attr = ARRAY_SIZE(rtl8366s_port), + }, + .attr_vlan = { + .attr = rtl8366s_vlan, + .n_attr = ARRAY_SIZE(rtl8366s_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366s_sw_get_port_link, +}; + +static int rtl8366s_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366S"; + dev->cpu_port = RTL8366S_PORT_NUM_CPU; + dev->ports = RTL8366S_NUM_PORTS; + dev->vlans = RTL8366S_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366s_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366s_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366s_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366s_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366s_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366s_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366s_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366S_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366s_smi_ops = { + .detect = rtl8366s_detect, + .reset_chip = rtl8366s_reset_chip, + .setup = rtl8366s_setup, + + .mii_read = rtl8366s_mii_read, + .mii_write = rtl8366s_mii_write, + + .get_vlan_mc = rtl8366s_get_vlan_mc, + .set_vlan_mc = rtl8366s_set_vlan_mc, + .get_vlan_4k = rtl8366s_get_vlan_4k, + .set_vlan_4k = rtl8366s_set_vlan_4k, + .get_mc_index = rtl8366s_get_mc_index, + .set_mc_index = rtl8366s_set_mc_index, + .get_mib_counter = rtl8366_get_mib_counter, + .is_vlan_valid = rtl8366s_is_vlan_valid, + .enable_vlan = rtl8366s_enable_vlan, + .enable_vlan4k = rtl8366s_enable_vlan4k, + .enable_port = rtl8366s_enable_port, +}; + +static int __devinit rtl8366s_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_platform_data *pdata; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366S_DRIVER_DESC + " version " RTL8366S_DRIVER_VER"\n"); + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + err = -EINVAL; + goto err_out; + } + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) { + err = -ENOMEM; + goto err_out; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->hw_reset = pdata->hw_reset; + + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366s_smi_ops; + smi->cpu_port = RTL8366S_PORT_NUM_CPU; + smi->num_ports = RTL8366S_NUM_PORTS; + smi->num_vlan_mc = RTL8366S_NUM_VLANS; + smi->mib_counters = rtl8366s_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366s_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + err_out: + return err; +} + +static int __devexit rtl8366s_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366s_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static struct platform_driver rtl8366s_driver = { + .driver = { + .name = RTL8366S_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = rtl8366s_probe, + .remove = __devexit_p(rtl8366s_remove), +}; + +static int __init rtl8366s_module_init(void) +{ + return platform_driver_register(&rtl8366s_driver); +} +module_init(rtl8366s_module_init); + +static void __exit rtl8366s_module_exit(void) +{ + platform_driver_unregister(&rtl8366s_driver); +} +module_exit(rtl8366s_module_exit); + +MODULE_DESCRIPTION(RTL8366S_DRIVER_DESC); +MODULE_VERSION(RTL8366S_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8367.c b/target/linux/generic/files/drivers/net/phy/rtl8367.c new file mode 100644 index 000000000..ee2a04701 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/rtl8367.c @@ -0,0 +1,1775 @@ +/* + * Platform driver for the Realtek RTL8367R/M ethernet switches + * + * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/skbuff.h> +#include <linux/rtl8367.h> + +#include "rtl8366_smi.h" + +#define RTL8367_RESET_DELAY 1000 /* msecs*/ + +#define RTL8367_PHY_ADDR_MAX 8 +#define RTL8367_PHY_REG_MAX 31 + +#define RTL8367_VID_MASK 0xffff +#define RTL8367_FID_MASK 0xfff +#define RTL8367_UNTAG_MASK 0xffff +#define RTL8367_MEMBER_MASK 0xffff + +#define RTL8367_PORT_CFG_REG(_p) (0x000e + 0x20 * (_p)) +#define RTL8367_PORT_CFG_EGRESS_MODE_SHIFT 4 +#define RTL8367_PORT_CFG_EGRESS_MODE_MASK 0x3 +#define RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL 0 +#define RTL8367_PORT_CFG_EGRESS_MODE_KEEP 1 +#define RTL8367_PORT_CFG_EGRESS_MODE_PRI 2 +#define RTL8367_PORT_CFG_EGRESS_MODE_REAL 3 + +#define RTL8367_BYPASS_LINE_RATE_REG 0x03f7 + +#define RTL8367_TA_CTRL_REG 0x0500 +#define RTL8367_TA_CTRL_STATUS BIT(12) +#define RTL8367_TA_CTRL_METHOD BIT(5) +#define RTL8367_TA_CTRL_CMD_SHIFT 4 +#define RTL8367_TA_CTRL_CMD_READ 0 +#define RTL8367_TA_CTRL_CMD_WRITE 1 +#define RTL8367_TA_CTRL_TABLE_SHIFT 0 +#define RTL8367_TA_CTRL_TABLE_ACLRULE 1 +#define RTL8367_TA_CTRL_TABLE_ACLACT 2 +#define RTL8367_TA_CTRL_TABLE_CVLAN 3 +#define RTL8367_TA_CTRL_TABLE_L2 4 +#define RTL8367_TA_CTRL_CVLAN_READ \ + ((RTL8367_TA_CTRL_CMD_READ << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) +#define RTL8367_TA_CTRL_CVLAN_WRITE \ + ((RTL8367_TA_CTRL_CMD_WRITE << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) + +#define RTL8367_TA_ADDR_REG 0x0501 +#define RTL8367_TA_ADDR_MASK 0x3fff + +#define RTL8367_TA_DATA_REG(_x) (0x0503 + (_x)) +#define RTL8367_TA_VLAN_DATA_SIZE 4 +#define RTL8367_TA_VLAN_VID_MASK RTL8367_VID_MASK +#define RTL8367_TA_VLAN_MEMBER_SHIFT 0 +#define RTL8367_TA_VLAN_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_TA_VLAN_FID_SHIFT 0 +#define RTL8367_TA_VLAN_FID_MASK RTL8367_FID_MASK +#define RTL8367_TA_VLAN_UNTAG1_SHIFT 14 +#define RTL8367_TA_VLAN_UNTAG1_MASK 0x3 +#define RTL8367_TA_VLAN_UNTAG2_SHIFT 0 +#define RTL8367_TA_VLAN_UNTAG2_MASK 0x3fff + +#define RTL8367_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) +#define RTL8367_VLAN_PVID_CTRL_MASK 0x1f +#define RTL8367_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) + +#define RTL8367_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) +#define RTL8367_VLAN_MC_DATA_SIZE 4 +#define RTL8367_VLAN_MC_MEMBER_SHIFT 0 +#define RTL8367_VLAN_MC_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_VLAN_MC_FID_SHIFT 0 +#define RTL8367_VLAN_MC_FID_MASK RTL8367_FID_MASK +#define RTL8367_VLAN_MC_EVID_SHIFT 0 +#define RTL8367_VLAN_MC_EVID_MASK RTL8367_VID_MASK + +#define RTL8367_VLAN_CTRL_REG 0x07a8 +#define RTL8367_VLAN_CTRL_ENABLE BIT(0) + +#define RTL8367_VLAN_INGRESS_REG 0x07a9 + +#define RTL8367_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) + +#define RTL8367_MIB_COUNTER_REG(_x) (0x1000 + (_x)) + +#define RTL8367_MIB_ADDRESS_REG 0x1004 + +#define RTL8367_MIB_CTRL_REG(_x) (0x1005 + (_x)) +#define RTL8367_MIB_CTRL_GLOBAL_RESET_MASK BIT(11) +#define RTL8367_MIB_CTRL_QM_RESET_MASK BIT(10) +#define RTL8367_MIB_CTRL_PORT_RESET_MASK(_p) BIT(2 + (_p)) +#define RTL8367_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8367_MIB_CTRL_BUSY_MASK BIT(0) + +#define RTL8367_MIB_COUNT 36 +#define RTL8367_MIB_COUNTER_PORT_OFFSET 0x0050 + +#define RTL8367_SWC0_REG 0x1200 +#define RTL8367_SWC0_MAX_LENGTH_SHIFT 13 +#define RTL8367_SWC0_MAX_LENGTH(_x) ((_x) << 13) +#define RTL8367_SWC0_MAX_LENGTH_MASK RTL8367_SWC0_MAX_LENGTH(0x3) +#define RTL8367_SWC0_MAX_LENGTH_1522 RTL8367_SWC0_MAX_LENGTH(0) +#define RTL8367_SWC0_MAX_LENGTH_1536 RTL8367_SWC0_MAX_LENGTH(1) +#define RTL8367_SWC0_MAX_LENGTH_1552 RTL8367_SWC0_MAX_LENGTH(2) +#define RTL8367_SWC0_MAX_LENGTH_16000 RTL8367_SWC0_MAX_LENGTH(3) + +#define RTL8367_CHIP_NUMBER_REG 0x1300 + +#define RTL8367_CHIP_VER_REG 0x1301 +#define RTL8367_CHIP_VER_RLVID_SHIFT 12 +#define RTL8367_CHIP_VER_RLVID_MASK 0xf +#define RTL8367_CHIP_VER_MCID_SHIFT 8 +#define RTL8367_CHIP_VER_MCID_MASK 0xf +#define RTL8367_CHIP_VER_BOID_SHIFT 4 +#define RTL8367_CHIP_VER_BOID_MASK 0xf + +#define RTL8367_CHIP_MODE_REG 0x1302 +#define RTL8367_CHIP_MODE_MASK 0x7 + +#define RTL8367_CHIP_DEBUG0_REG 0x1303 +#define RTL8367_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) + +#define RTL8367_CHIP_DEBUG1_REG 0x1304 + +#define RTL8367_DIS_REG 0x1305 +#define RTL8367_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) +#define RTL8367_DIS_RGMII_SHIFT(_x) (4 * (_x)) +#define RTL8367_DIS_RGMII_MASK 0x7 + +#define RTL8367_EXT_RGMXF_REG(_x) (0x1306 + (_x)) +#define RTL8367_EXT_RGMXF_DUMMY0_SHIFT 5 +#define RTL8367_EXT_RGMXF_DUMMY0_MASK 0x7ff +#define RTL8367_EXT_RGMXF_TXDELAY_SHIFT 3 +#define RTL8367_EXT_RGMXF_TXDELAY_MASK 1 +#define RTL8367_EXT_RGMXF_RXDELAY_MASK 0x7 + +#define RTL8367_DI_FORCE_REG(_x) (0x1310 + (_x)) +#define RTL8367_DI_FORCE_MODE BIT(12) +#define RTL8367_DI_FORCE_NWAY BIT(7) +#define RTL8367_DI_FORCE_TXPAUSE BIT(6) +#define RTL8367_DI_FORCE_RXPAUSE BIT(5) +#define RTL8367_DI_FORCE_LINK BIT(4) +#define RTL8367_DI_FORCE_DUPLEX BIT(2) +#define RTL8367_DI_FORCE_SPEED_MASK 3 +#define RTL8367_DI_FORCE_SPEED_10 0 +#define RTL8367_DI_FORCE_SPEED_100 1 +#define RTL8367_DI_FORCE_SPEED_1000 2 + +#define RTL8367_MAC_FORCE_REG(_x) (0x1312 + (_x)) + +#define RTL8367_CHIP_RESET_REG 0x1322 +#define RTL8367_CHIP_RESET_SW BIT(1) +#define RTL8367_CHIP_RESET_HW BIT(0) + +#define RTL8367_PORT_STATUS_REG(_p) (0x1352 + (_p)) +#define RTL8367_PORT_STATUS_NWAY BIT(7) +#define RTL8367_PORT_STATUS_TXPAUSE BIT(6) +#define RTL8367_PORT_STATUS_RXPAUSE BIT(5) +#define RTL8367_PORT_STATUS_LINK BIT(4) +#define RTL8367_PORT_STATUS_DUPLEX BIT(2) +#define RTL8367_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8367_PORT_STATUS_SPEED_10 0 +#define RTL8367_PORT_STATUS_SPEED_100 1 +#define RTL8367_PORT_STATUS_SPEED_1000 2 + +#define RTL8367_RTL_NO_REG 0x13c0 +#define RTL8367_RTL_NO_8367R 0x3670 +#define RTL8367_RTL_NO_8367M 0x3671 + +#define RTL8367_RTL_VER_REG 0x13c1 +#define RTL8367_RTL_VER_MASK 0xf + +#define RTL8367_RTL_MAGIC_ID_REG 0x13c2 +#define RTL8367_RTL_MAGIC_ID_VAL 0x0249 + +#define RTL8367_LED_SYS_CONFIG_REG 0x1b00 +#define RTL8367_LED_MODE_REG 0x1b02 +#define RTL8367_LED_MODE_RATE_M 0x7 +#define RTL8367_LED_MODE_RATE_S 1 + +#define RTL8367_LED_CONFIG_REG 0x1b03 +#define RTL8367_LED_CONFIG_DATA_S 12 +#define RTL8367_LED_CONFIG_DATA_M 0x3 +#define RTL8367_LED_CONFIG_SEL BIT(14) +#define RTL8367_LED_CONFIG_LED_CFG_M 0xf + +#define RTL8367_PARA_LED_IO_EN1_REG 0x1b24 +#define RTL8367_PARA_LED_IO_EN2_REG 0x1b25 +#define RTL8367_PARA_LED_IO_EN_PMASK 0xff + +#define RTL8367_IA_CTRL_REG 0x1f00 +#define RTL8367_IA_CTRL_RW(_x) ((_x) << 1) +#define RTL8367_IA_CTRL_RW_READ RTL8367_IA_CTRL_RW(0) +#define RTL8367_IA_CTRL_RW_WRITE RTL8367_IA_CTRL_RW(1) +#define RTL8367_IA_CTRL_CMD_MASK BIT(0) + +#define RTL8367_IA_STATUS_REG 0x1f01 +#define RTL8367_IA_STATUS_PHY_BUSY BIT(2) +#define RTL8367_IA_STATUS_SDS_BUSY BIT(1) +#define RTL8367_IA_STATUS_MDX_BUSY BIT(0) + +#define RTL8367_IA_ADDRESS_REG 0x1f02 + +#define RTL8367_IA_WRITE_DATA_REG 0x1f03 +#define RTL8367_IA_READ_DATA_REG 0x1f04 + +#define RTL8367_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) + +#define RTL8367_CPU_PORT_NUM 9 +#define RTL8367_NUM_PORTS 10 +#define RTL8367_NUM_VLANS 32 +#define RTL8367_NUM_LEDGROUPS 4 +#define RTL8367_NUM_VIDS 4096 +#define RTL8367_PRIORITYMAX 7 +#define RTL8367_FIDMAX 7 + +#define RTL8367_PORT_0 BIT(0) +#define RTL8367_PORT_1 BIT(1) +#define RTL8367_PORT_2 BIT(2) +#define RTL8367_PORT_3 BIT(3) +#define RTL8367_PORT_4 BIT(4) +#define RTL8367_PORT_5 BIT(5) +#define RTL8367_PORT_6 BIT(6) +#define RTL8367_PORT_7 BIT(7) +#define RTL8367_PORT_E1 BIT(8) /* external port 1 */ +#define RTL8367_PORT_E0 BIT(9) /* external port 0 */ + +#define RTL8367_PORTS_ALL \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1 | \ + RTL8367_PORT_E0) + +#define RTL8367_PORTS_ALL_BUT_CPU \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1) + +struct rtl8367_initval { + u16 reg; + u16 val; +}; + +static struct rtl8366_mib_counter rtl8367_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 2, "Dot3StatsFCSErrors" }, + { 0, 6, 2, "Dot3StatsSymbolErrors" }, + { 0, 8, 2, "Dot3InPauseFrames" }, + { 0, 10, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 12, 2, "EtherStatsFragments" }, + { 0, 14, 2, "EtherStatsJabbers" }, + { 0, 16, 2, "IfInUcastPkts" }, + { 0, 18, 2, "EtherStatsDropEvents" }, + { 0, 20, 4, "EtherStatsOctets" }, + + { 0, 24, 2, "EtherStatsUnderSizePkts" }, + { 0, 26, 2, "EtherOversizeStats" }, + { 0, 28, 2, "EtherStatsPkts64Octets" }, + { 0, 30, 2, "EtherStatsPkts65to127Octets" }, + { 0, 32, 2, "EtherStatsPkts128to255Octets" }, + { 0, 34, 2, "EtherStatsPkts256to511Octets" }, + { 0, 36, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 38, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 40, 2, "EtherStatsMulticastPkts" }, + { 0, 42, 2, "EtherStatsBroadcastPkts" }, + + { 0, 44, 4, "IfOutOctets" }, + + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, + { 0, 72, 2, "OutOampduPkts" }, + { 0, 74, 2, "InOampduPkts" }, + { 0, 76, 2, "PktgenPkts" }, +}; + +#define REG_RD(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_read_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static const struct rtl8367_initval rtl8367_initvals_0_0[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1006}, {0x121e, 0x03e8}, + {0x121f, 0x02b3}, {0x1220, 0x028f}, {0x1221, 0x029b}, {0x1222, 0x0277}, + {0x1223, 0x02b3}, {0x1224, 0x028f}, {0x1225, 0x029b}, {0x1226, 0x0277}, + {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, {0x1230, 0x00b4}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x03e8}, {0x0201, 0x03e8}, {0x0202, 0x03e8}, + {0x0203, 0x03e8}, {0x0204, 0x03e8}, {0x0205, 0x03e8}, {0x0206, 0x03e8}, + {0x0207, 0x03e8}, {0x0218, 0x0032}, {0x0208, 0x029b}, {0x0209, 0x029b}, + {0x020a, 0x029b}, {0x020b, 0x029b}, {0x020c, 0x029b}, {0x020d, 0x029b}, + {0x020e, 0x029b}, {0x020f, 0x029b}, {0x0210, 0x029b}, {0x0211, 0x029b}, + {0x0212, 0x029b}, {0x0213, 0x029b}, {0x0214, 0x029b}, {0x0215, 0x029b}, + {0x0216, 0x029b}, {0x0217, 0x029b}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x014C}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, {0x20A0, 0x1940}, {0x20C0, 0x1940}, {0x20E0, 0x1940}, +}; + +static const struct rtl8367_initval rtl8367_initvals_0_1[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1b06}, {0x121e, 0x07f0}, + {0x121f, 0x0438}, {0x1220, 0x040f}, {0x1221, 0x040f}, {0x1222, 0x03eb}, + {0x1223, 0x0438}, {0x1224, 0x040f}, {0x1225, 0x040f}, {0x1226, 0x03eb}, + {0x1227, 0x0144}, {0x1228, 0x0138}, {0x122f, 0x0144}, {0x1230, 0x0138}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x07d0}, {0x0201, 0x07d0}, {0x0202, 0x07d0}, + {0x0203, 0x07d0}, {0x0204, 0x07d0}, {0x0205, 0x07d0}, {0x0206, 0x07d0}, + {0x0207, 0x07d0}, {0x0218, 0x0032}, {0x0208, 0x0190}, {0x0209, 0x0190}, + {0x020a, 0x0190}, {0x020b, 0x0190}, {0x020c, 0x0190}, {0x020d, 0x0190}, + {0x020e, 0x0190}, {0x020f, 0x0190}, {0x0210, 0x0190}, {0x0211, 0x0190}, + {0x0212, 0x0190}, {0x0213, 0x0190}, {0x0214, 0x0190}, {0x0215, 0x0190}, + {0x0216, 0x0190}, {0x0217, 0x0190}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x0125}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_0[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x121D, 0x7D16}, {0x121E, 0x03E8}, {0x121F, 0x024E}, {0x1220, 0x0230}, + {0x1221, 0x0244}, {0x1222, 0x0226}, {0x1223, 0x024E}, {0x1224, 0x0230}, + {0x1225, 0x0244}, {0x1226, 0x0226}, {0x1227, 0x00C0}, {0x1228, 0x00B4}, + {0x122F, 0x00C0}, {0x1230, 0x00B4}, {0x0208, 0x03E8}, {0x0209, 0x03E8}, + {0x020A, 0x03E8}, {0x020B, 0x03E8}, {0x020C, 0x03E8}, {0x020D, 0x03E8}, + {0x020E, 0x03E8}, {0x020F, 0x03E8}, {0x0210, 0x03E8}, {0x0211, 0x03E8}, + {0x0212, 0x03E8}, {0x0213, 0x03E8}, {0x0214, 0x03E8}, {0x0215, 0x03E8}, + {0x0216, 0x03E8}, {0x0217, 0x03E8}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087B, 0x0000}, + {0x087C, 0xFF00}, {0x087D, 0x0000}, {0x087E, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, + {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, {0x2200, 0x1340}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_1[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, + {0x0865, 0x3210}, {0x087B, 0x0000}, {0x087C, 0xFF00}, {0x087D, 0x0000}, + {0x087E, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, + {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, + {0x0A25, 0x2040}, {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, + {0x0A29, 0x2040}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, + {0x2200, 0x1340}, {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, + {0x1B03, 0x0876}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_0[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x7D16}, + {0x121e, 0x03e8}, {0x121f, 0x024e}, {0x1220, 0x0230}, {0x1221, 0x0244}, + {0x1222, 0x0226}, {0x1223, 0x024e}, {0x1224, 0x0230}, {0x1225, 0x0244}, + {0x1226, 0x0226}, {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, + {0x1230, 0x00b4}, {0x0208, 0x03e8}, {0x0209, 0x03e8}, {0x020a, 0x03e8}, + {0x020b, 0x03e8}, {0x020c, 0x03e8}, {0x020d, 0x03e8}, {0x020e, 0x03e8}, + {0x020f, 0x03e8}, {0x0210, 0x03e8}, {0x0211, 0x03e8}, {0x0212, 0x03e8}, + {0x0213, 0x03e8}, {0x0214, 0x03e8}, {0x0215, 0x03e8}, {0x0216, 0x03e8}, + {0x0217, 0x03e8}, {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, + {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, {0x087c, 0xff00}, + {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, + {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, + {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_1[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x0900, 0x0000}, + {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, + {0x087b, 0x0000}, {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, + {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, + {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A25, 0x2040}, + {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x130c, 0x0050}, +}; + +static int rtl8367_write_initvals(struct rtl8366_smi *smi, + const struct rtl8367_initval *initvals, + int count) +{ + int err; + int i; + + for (i = 0; i < count; i++) + REG_WR(smi, initvals[i].reg, initvals[i].val); + + return 0; +} + +static int rtl8367_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 *val) +{ + int timeout; + u32 data; + int err; + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send read command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_READ); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy read timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + /* read data */ + REG_RD(smi, RTL8367_IA_READ_DATA_REG, val); + + dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, *val); + return 0; +} + +static int rtl8367_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 val) +{ + int timeout; + u32 data; + int err; + + dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, val); + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* preapre data */ + REG_WR(smi, RTL8367_IA_WRITE_DATA_REG, val); + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send write command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_WRITE); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy write timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + return 0; +} + +static int rtl8367_init_regs0(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + int err; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_0_0; + count = ARRAY_SIZE(rtl8367_initvals_0_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_0_1; + count = ARRAY_SIZE(rtl8367_initvals_0_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + err = rtl8367_write_initvals(smi, initvals, count); + if (err) + return err; + + /* TODO: complete this */ + + return 0; +} + +static int rtl8367_init_regs1(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_1_0; + count = ARRAY_SIZE(rtl8367_initvals_1_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_1_1; + count = ARRAY_SIZE(rtl8367_initvals_1_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs2(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_2_0; + count = ARRAY_SIZE(rtl8367_initvals_2_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_2_1; + count = ARRAY_SIZE(rtl8367_initvals_2_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs(struct rtl8366_smi *smi) +{ + u32 data; + u32 rlvid; + u32 mode; + int err; + + REG_WR(smi, RTL8367_RTL_MAGIC_ID_REG, RTL8367_RTL_MAGIC_ID_VAL); + + REG_RD(smi, RTL8367_CHIP_VER_REG, &data); + rlvid = (data >> RTL8367_CHIP_VER_RLVID_SHIFT) & + RTL8367_CHIP_VER_RLVID_MASK; + + REG_RD(smi, RTL8367_CHIP_MODE_REG, &data); + mode = data & RTL8367_CHIP_MODE_MASK; + + switch (rlvid) { + case 0: + err = rtl8367_init_regs0(smi, mode); + break; + + case 1: + err = rtl8367_write_phy_reg(smi, 0, 31, 5); + if (err) + break; + + err = rtl8367_write_phy_reg(smi, 0, 5, 0x3ffe); + if (err) + break; + + err = rtl8367_read_phy_reg(smi, 0, 6, &data); + if (err) + break; + + if (data == 0x94eb) { + err = rtl8367_init_regs1(smi, mode); + } else if (data == 0x2104) { + err = rtl8367_init_regs2(smi, mode); + } else { + dev_err(smi->parent, "unknow phy data %04x\n", data); + return -ENODEV; + } + + break; + + default: + dev_err(smi->parent, "unknow rlvid %u\n", rlvid); + err = -ENODEV; + break; + } + + return err; +} + +static int rtl8367_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + int err; + u32 data; + + REG_WR(smi, RTL8367_CHIP_RESET_REG, RTL8367_CHIP_RESET_HW); + msleep(RTL8367_RESET_DELAY); + + do { + REG_RD(smi, RTL8367_CHIP_RESET_REG, &data); + if (!(data & RTL8367_CHIP_RESET_HW)) + break; + + msleep(1); + } while (--timeout); + + if (!timeout) { + dev_err(smi->parent, "chip reset timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8367_extif_set_mode(struct rtl8366_smi *smi, int id, + enum rtl8367_extif_mode mode) +{ + int err; + + /* set port mode */ + switch (mode) { + case RTL8367_EXTIF_MODE_RGMII: + case RTL8367_EXTIF_MODE_RGMII_33V: + REG_WR(smi, RTL8367_CHIP_DEBUG0_REG, 0x0367); + REG_WR(smi, RTL8367_CHIP_DEBUG1_REG, 0x7777); + break; + + case RTL8367_EXTIF_MODE_TMII_MAC: + case RTL8367_EXTIF_MODE_TMII_PHY: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), BIT((id + 1) % 2)); + break; + + case RTL8367_EXTIF_MODE_GMII: + REG_RMW(smi, RTL8367_CHIP_DEBUG0_REG, + RTL8367_CHIP_DEBUG0_DUMMY0(id), + RTL8367_CHIP_DEBUG0_DUMMY0(id)); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), BIT(6)); + break; + + case RTL8367_EXTIF_MODE_MII_MAC: + case RTL8367_EXTIF_MODE_MII_PHY: + case RTL8367_EXTIF_MODE_DISABLED: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), 0); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), 0); + break; + + default: + dev_err(smi->parent, + "invalid mode for external interface %d\n", id); + return -EINVAL; + } + + REG_RMW(smi, RTL8367_DIS_REG, + RTL8367_DIS_RGMII_MASK << RTL8367_DIS_RGMII_SHIFT(id), + mode << RTL8367_DIS_RGMII_SHIFT(id)); + + return 0; +} + +static int rtl8367_extif_set_force(struct rtl8366_smi *smi, int id, + struct rtl8367_port_ability *pa) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_DI_FORCE_MODE | + RTL8367_DI_FORCE_NWAY | + RTL8367_DI_FORCE_TXPAUSE | + RTL8367_DI_FORCE_RXPAUSE | + RTL8367_DI_FORCE_LINK | + RTL8367_DI_FORCE_DUPLEX | + RTL8367_DI_FORCE_SPEED_MASK); + + val = pa->speed; + val |= pa->force_mode ? RTL8367_DI_FORCE_MODE : 0; + val |= pa->nway ? RTL8367_DI_FORCE_NWAY : 0; + val |= pa->txpause ? RTL8367_DI_FORCE_TXPAUSE : 0; + val |= pa->rxpause ? RTL8367_DI_FORCE_RXPAUSE : 0; + val |= pa->link ? RTL8367_DI_FORCE_LINK : 0; + val |= pa->duplex ? RTL8367_DI_FORCE_DUPLEX : 0; + + REG_RMW(smi, RTL8367_DI_FORCE_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, + unsigned txdelay, unsigned rxdelay) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_EXT_RGMXF_RXDELAY_MASK | + (RTL8367_EXT_RGMXF_TXDELAY_MASK << + RTL8367_EXT_RGMXF_TXDELAY_SHIFT)); + + val = rxdelay; + val |= txdelay << RTL8367_EXT_RGMXF_TXDELAY_SHIFT; + + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_init(struct rtl8366_smi *smi, int id, + struct rtl8367_extif_config *cfg) +{ + enum rtl8367_extif_mode mode; + int err; + + mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; + + err = rtl8367_extif_set_mode(smi, id, mode); + if (err) + return err; + + if (mode != RTL8367_EXTIF_MODE_DISABLED) { + err = rtl8367_extif_set_force(smi, id, &cfg->ability); + if (err) + return err; + + err = rtl8367_extif_set_rgmii_delay(smi, id, cfg->txdelay, + cfg->rxdelay); + if (err) + return err; + } + + return 0; +} + +static int rtl8367_led_group_set_ports(struct rtl8366_smi *smi, + unsigned int group, u16 port_mask) +{ + u32 reg; + u32 s; + int err; + + port_mask &= RTL8367_PARA_LED_IO_EN_PMASK; + s = (group % 2) * 8; + reg = RTL8367_PARA_LED_IO_EN1_REG + (group / 2); + + REG_RMW(smi, reg, (RTL8367_PARA_LED_IO_EN_PMASK << s), port_mask << s); + + return 0; +} + +static int rtl8367_led_group_set_mode(struct rtl8366_smi *smi, + unsigned int mode) +{ + u16 mask; + u16 set; + int err; + + mode &= RTL8367_LED_CONFIG_DATA_M; + + mask = (RTL8367_LED_CONFIG_DATA_M << RTL8367_LED_CONFIG_DATA_S) | + RTL8367_LED_CONFIG_SEL; + set = (mode << RTL8367_LED_CONFIG_DATA_S) | RTL8367_LED_CONFIG_SEL; + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + + return 0; +} + +static int rtl8367_led_group_set_config(struct rtl8366_smi *smi, + unsigned int led, unsigned int cfg) +{ + u16 mask; + u16 set; + int err; + + mask = (RTL8367_LED_CONFIG_LED_CFG_M << (led * 4)) | + RTL8367_LED_CONFIG_SEL; + set = (cfg & RTL8367_LED_CONFIG_LED_CFG_M) << (led * 4); + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + return 0; +} + +static int rtl8367_led_op_select_parallel(struct rtl8366_smi *smi) +{ + int err; + + REG_WR(smi, RTL8367_LED_SYS_CONFIG_REG, 0x1472); + return 0; +} + +static int rtl8367_led_blinkrate_set(struct rtl8366_smi *smi, unsigned int rate) +{ + u16 mask; + u16 set; + int err; + + mask = RTL8367_LED_MODE_RATE_M << RTL8367_LED_MODE_RATE_S; + set = (rate & RTL8367_LED_MODE_RATE_M) << RTL8367_LED_MODE_RATE_S; + REG_RMW(smi, RTL8367_LED_MODE_REG, mask, set); + + return 0; +} + +static int rtl8367_setup(struct rtl8366_smi *smi) +{ + struct rtl8367_platform_data *pdata; + int err; + int i; + + pdata = smi->parent->platform_data; + + err = rtl8367_init_regs(smi); + if (err) + return err; + + /* initialize external interfaces */ + err = rtl8367_extif_init(smi, 0, pdata->extif0_cfg); + if (err) + return err; + + err = rtl8367_extif_init(smi, 1, pdata->extif1_cfg); + if (err) + return err; + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8367_SWC0_REG, RTL8367_SWC0_MAX_LENGTH_MASK, + RTL8367_SWC0_MAX_LENGTH_1536); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8367_VLAN_INGRESS_REG, RTL8367_PORTS_ALL); + + /* + * Setup egress tag mode for each port. + */ + for (i = 0; i < RTL8367_NUM_PORTS; i++) + REG_RMW(smi, + RTL8367_PORT_CFG_REG(i), + RTL8367_PORT_CFG_EGRESS_MODE_MASK << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT, + RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT); + + /* setup LEDs */ + err = rtl8367_led_group_set_ports(smi, 0, RTL8367_PORTS_ALL); + if (err) + return err; + + err = rtl8367_led_group_set_mode(smi, 0); + if (err) + return err; + + err = rtl8367_led_op_select_parallel(smi); + if (err) + return err; + + err = rtl8367_led_blinkrate_set(smi, 1); + if (err) + return err; + + err = rtl8367_led_group_set_config(smi, 0, 2); + if (err) + return err; + + return 0; +} + +static int rtl8367_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + struct rtl8366_mib_counter *mib; + int offset; + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8367_NUM_PORTS || counter >= RTL8367_MIB_COUNT) + return -EINVAL; + + mib = &rtl8367_mib_counters[counter]; + addr = RTL8367_MIB_COUNTER_PORT_OFFSET * port + mib->offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + REG_WR(smi, RTL8367_MIB_ADDRESS_REG, addr >> 2); + + /* read MIB control register */ + REG_RD(smi, RTL8367_MIB_CTRL_REG(0), &data); + + if (data & RTL8367_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8367_MIB_CTRL_RESET_MASK) + return -EIO; + + if (mib->length == 4) + offset = 3; + else + offset = (mib->offset + 1) % 4; + + mibvalue = 0; + for (i = 0; i < mib->length; i++) { + REG_RD(smi, RTL8367_MIB_COUNTER_REG(offset - i), &data); + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8367_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8367_NUM_VIDS) + return -EINVAL; + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, vid); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_READ); + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_TA_DATA_REG(i), &data[i]); + + vlan4k->vid = vid; + vlan4k->member = (data[0] >> RTL8367_TA_VLAN_MEMBER_SHIFT) & + RTL8367_TA_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8367_TA_VLAN_FID_SHIFT) & + RTL8367_TA_VLAN_FID_MASK; + vlan4k->untag = (data[2] >> RTL8367_TA_VLAN_UNTAG1_SHIFT) & + RTL8367_TA_VLAN_UNTAG1_MASK; + vlan4k->untag |= ((data[3] >> RTL8367_TA_VLAN_UNTAG2_SHIFT) & + RTL8367_TA_VLAN_UNTAG2_MASK) << 2; + + return 0; +} + +static int rtl8367_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + if (vlan4k->vid >= RTL8367_NUM_VIDS || + vlan4k->member > RTL8367_TA_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8367_UNTAG_MASK || + vlan4k->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlan4k->member & RTL8367_TA_VLAN_MEMBER_MASK) << + RTL8367_TA_VLAN_MEMBER_SHIFT; + data[1] = (vlan4k->fid & RTL8367_TA_VLAN_FID_MASK) << + RTL8367_TA_VLAN_FID_SHIFT; + data[2] = (vlan4k->untag & RTL8367_TA_VLAN_UNTAG1_MASK) << + RTL8367_TA_VLAN_UNTAG1_SHIFT; + data[3] = ((vlan4k->untag >> 2) & RTL8367_TA_VLAN_UNTAG2_MASK) << + RTL8367_TA_VLAN_UNTAG2_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_TA_DATA_REG(i), data[i]); + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, + vlan4k->vid & RTL8367_TA_VLAN_VID_MASK); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_WRITE); + + return 0; +} + +static int rtl8367_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8367_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_VLAN_MC_BASE(index) + i, &data[i]); + + vlanmc->member = (data[0] >> RTL8367_VLAN_MC_MEMBER_SHIFT) & + RTL8367_VLAN_MC_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8367_VLAN_MC_FID_SHIFT) & + RTL8367_VLAN_MC_FID_MASK; + vlanmc->vid = (data[3] >> RTL8367_VLAN_MC_EVID_SHIFT) & + RTL8367_VLAN_MC_EVID_MASK; + + return 0; +} + +static int rtl8367_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + if (index >= RTL8367_NUM_VLANS || + vlanmc->vid >= RTL8367_NUM_VIDS || + vlanmc->priority > RTL8367_PRIORITYMAX || + vlanmc->member > RTL8367_VLAN_MC_MEMBER_MASK || + vlanmc->untag > RTL8367_UNTAG_MASK || + vlanmc->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->member & RTL8367_VLAN_MC_MEMBER_MASK) << + RTL8367_VLAN_MC_MEMBER_SHIFT; + data[1] = (vlanmc->fid & RTL8367_VLAN_MC_FID_MASK) << + RTL8367_VLAN_MC_FID_SHIFT; + data[2] = 0; + data[3] = (vlanmc->vid & RTL8367_VLAN_MC_EVID_MASK) << + RTL8367_VLAN_MC_EVID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_VLAN_MC_BASE(index) + i, data[i]); + + return 0; +} + +static int rtl8367_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + REG_RD(smi, RTL8367_VLAN_PVID_CTRL_REG(port), &data); + + *val = (data >> RTL8367_VLAN_PVID_CTRL_SHIFT(port)) & + RTL8367_VLAN_PVID_CTRL_MASK; + + return 0; +} + +static int rtl8367_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8367_NUM_PORTS || index >= RTL8367_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_PVID_CTRL_REG(port), + RTL8367_VLAN_PVID_CTRL_MASK << + RTL8367_VLAN_PVID_CTRL_SHIFT(port), + (index & RTL8367_VLAN_PVID_CTRL_MASK) << + RTL8367_VLAN_PVID_CTRL_SHIFT(port)); +} + +static int rtl8367_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_CTRL_REG, + RTL8367_VLAN_CTRL_ENABLE, + (enable) ? RTL8367_VLAN_CTRL_ENABLE : 0); +} + +static int rtl8367_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return 0; +} + +static int rtl8367_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8367_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8367_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8367_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + + REG_WR(smi, RTL8367_PORT_ISOLATION_REG(port), + (enable) ? RTL8367_PORTS_ALL : 0); + + return 0; +} + +static int rtl8367_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(0), 0, + RTL8367_MIB_CTRL_GLOBAL_RESET_MASK); +} + +static int rtl8367_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8367_PORT_STATUS_REG(port), &data); + + link->link = !!(data & RTL8367_PORT_STATUS_LINK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8367_PORT_STATUS_DUPLEX); + link->rx_flow = !!(data & RTL8367_PORT_STATUS_RXPAUSE); + link->tx_flow = !!(data & RTL8367_PORT_STATUS_TXPAUSE); + link->aneg = !!(data & RTL8367_PORT_STATUS_NWAY); + + speed = (data & RTL8367_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8367_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8367_SWC0_REG, &data); + val->value.i = (data & RTL8367_SWC0_MAX_LENGTH_MASK) >> + RTL8367_SWC0_MAX_LENGTH_SHIFT; + + return 0; +} + +static int rtl8367_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 max_len; + + switch (val->value.i) { + case 0: + max_len = RTL8367_SWC0_MAX_LENGTH_1522; + break; + case 1: + max_len = RTL8367_SWC0_MAX_LENGTH_1536; + break; + case 2: + max_len = RTL8367_SWC0_MAX_LENGTH_1552; + break; + case 3: + max_len = RTL8367_SWC0_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8367_SWC0_REG, + RTL8367_SWC0_MAX_LENGTH_MASK, max_len); +} + + +static int rtl8367_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int port; + + port = val->port_vlan; + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(port / 8), 0, + RTL8367_MIB_CTRL_PORT_RESET_MASK(port % 8)); +} + +static struct switch_attr rtl8367_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8367_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + "(0:1522, 1:1536, 2:1552, 3:16000)", + .set = rtl8367_sw_set_max_length, + .get = rtl8367_sw_get_max_length, + .max = 3, + } +}; + +static struct switch_attr rtl8367_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8367_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, +}; + +static struct switch_attr rtl8367_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, +}; + +static const struct switch_dev_ops rtl8367_sw_ops = { + .attr_global = { + .attr = rtl8367_globals, + .n_attr = ARRAY_SIZE(rtl8367_globals), + }, + .attr_port = { + .attr = rtl8367_port, + .n_attr = ARRAY_SIZE(rtl8367_port), + }, + .attr_vlan = { + .attr = rtl8367_vlan, + .n_attr = ARRAY_SIZE(rtl8367_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8367_sw_get_port_link, +}; + +static int rtl8367_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8367"; + dev->cpu_port = RTL8367_CPU_PORT_NUM; + dev->ports = RTL8367_NUM_PORTS; + dev->vlans = RTL8367_NUM_VIDS; + dev->ops = &rtl8367_sw_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8367_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8367_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8367_read_phy_reg(smi, addr, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8367_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8367_write_phy_reg(smi, addr, reg, val); + if (err) + return err; + + /* flush write */ + (void) rtl8367_read_phy_reg(smi, addr, reg, &t); + + return err; +} + +static int rtl8367_detect(struct rtl8366_smi *smi) +{ + u32 rtl_no = 0; + u32 rtl_ver = 0; + char *chip_name; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_NO_REG, &rtl_no); + if (ret) { + dev_err(smi->parent, "unable to read chip number\n"); + return ret; + } + + switch (rtl_no) { + case RTL8367_RTL_NO_8367R: + chip_name = "8367R"; + break; + case RTL8367_RTL_NO_8367M: + chip_name = "8367M"; + break; + default: + dev_err(smi->parent, "unknown chip number (%04x)\n", rtl_no); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_VER_REG, &rtl_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%s ver. %u chip found\n", + chip_name, rtl_ver & RTL8367_RTL_VER_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8367_smi_ops = { + .detect = rtl8367_detect, + .reset_chip = rtl8367_reset_chip, + .setup = rtl8367_setup, + + .mii_read = rtl8367_mii_read, + .mii_write = rtl8367_mii_write, + + .get_vlan_mc = rtl8367_get_vlan_mc, + .set_vlan_mc = rtl8367_set_vlan_mc, + .get_vlan_4k = rtl8367_get_vlan_4k, + .set_vlan_4k = rtl8367_set_vlan_4k, + .get_mc_index = rtl8367_get_mc_index, + .set_mc_index = rtl8367_set_mc_index, + .get_mib_counter = rtl8367_get_mib_counter, + .is_vlan_valid = rtl8367_is_vlan_valid, + .enable_vlan = rtl8367_enable_vlan, + .enable_vlan4k = rtl8367_enable_vlan4k, + .enable_port = rtl8367_enable_port, +}; + +static int __devinit rtl8367_probe(struct platform_device *pdev) +{ + struct rtl8367_platform_data *pdata; + struct rtl8366_smi *smi; + int err; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data specified\n"); + err = -EINVAL; + goto err_out; + } + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) { + err = -ENOMEM; + goto err_out; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->hw_reset = pdata->hw_reset; + + smi->clk_delay = 1500; + smi->cmd_read = 0xb9; + smi->cmd_write = 0xb8; + smi->ops = &rtl8367_smi_ops; + smi->cpu_port = RTL8367_CPU_PORT_NUM; + smi->num_ports = RTL8367_NUM_PORTS; + smi->num_vlan_mc = RTL8367_NUM_VLANS; + smi->mib_counters = rtl8367_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8367_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + err_out: + return err; +} + +static int __devexit rtl8367_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8367_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static void rtl8367_shutdown(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) + rtl8367_reset_chip(smi); +} + +static struct platform_driver rtl8367_driver = { + .driver = { + .name = RTL8367_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = rtl8367_probe, + .remove = __devexit_p(rtl8367_remove), + .shutdown = rtl8367_shutdown, +}; + +static int __init rtl8367_module_init(void) +{ + return platform_driver_register(&rtl8367_driver); +} +module_init(rtl8367_module_init); + +static void __exit rtl8367_module_exit(void) +{ + platform_driver_unregister(&rtl8367_driver); +} +module_exit(rtl8367_module_exit); + +MODULE_DESCRIPTION(RTL8367_DRIVER_DESC); +MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME); diff --git a/target/linux/generic/files/drivers/net/phy/swconfig.c b/target/linux/generic/files/drivers/net/phy/swconfig.c new file mode 100644 index 000000000..e772c9448 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/swconfig.c @@ -0,0 +1,1043 @@ +/* + * swconfig.c: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> + * + * 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. + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/if.h> +#include <linux/if_ether.h> +#include <linux/capability.h> +#include <linux/skbuff.h> +#include <linux/switch.h> + +//#define DEBUG 1 +#ifdef DEBUG +#define DPRINTF(format, ...) printk("%s: " format, __func__, ##__VA_ARGS__) +#else +#define DPRINTF(...) do {} while(0) +#endif + +#define SWCONFIG_DEVNAME "switch%d" + +#include "swconfig_leds.c" + +MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>"); +MODULE_LICENSE("GPL"); + +static int swdev_id = 0; +static struct list_head swdevs; +static DEFINE_SPINLOCK(swdevs_lock); +struct swconfig_callback; + +struct swconfig_callback +{ + struct sk_buff *msg; + struct genlmsghdr *hdr; + struct genl_info *info; + int cmd; + + /* callback for filling in the message data */ + int (*fill)(struct swconfig_callback *cb, void *arg); + + /* callback for closing the message before sending it */ + int (*close)(struct swconfig_callback *cb, void *arg); + + struct nlattr *nest[4]; + int args[4]; +}; + +/* defaults */ + +static int +swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int ret; + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + if (!dev->ops->get_vlan_ports) + return -EOPNOTSUPP; + + ret = dev->ops->get_vlan_ports(dev, val); + return ret; +} + +static int +swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct switch_port *ports = val->value.ports; + const struct switch_dev_ops *ops = dev->ops; + int i; + + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + /* validate ports */ + if (val->len > dev->ports) + return -EINVAL; + + if (!ops->set_vlan_ports) + return -EOPNOTSUPP; + + for (i = 0; i < val->len; i++) { + if (ports[i].id >= dev->ports) + return -EINVAL; + + if (ops->set_port_pvid && + !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + ops->set_port_pvid(dev, ports[i].id, val->port_vlan); + } + + return ops->set_vlan_ports(dev, val); +} + +static int +swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->set_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i); +} + +static int +swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i); +} + +static const char * +swconfig_speed_str(enum switch_port_speed speed) +{ + switch (speed) { + case SWITCH_PORT_SPEED_10: + return "10baseT"; + case SWITCH_PORT_SPEED_100: + return "100baseT"; + case SWITCH_PORT_SPEED_1000: + return "1000baseT"; + default: + break; + } + + return "unknown"; +} + +static int +swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct switch_port_link link; + int len; + int ret; + + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_link) + return -EOPNOTSUPP; + + memset(&link, 0, sizeof(link)); + ret = dev->ops->get_port_link(dev, val->port_vlan, &link); + if (ret) + return ret; + + memset(dev->buf, 0, sizeof(dev->buf)); + + if (link.link) + len = snprintf(dev->buf, sizeof(dev->buf), + "port:%d link:up speed:%s %s-duplex %s%s%s", + val->port_vlan, + swconfig_speed_str(link.speed), + link.duplex ? "full" : "half", + link.tx_flow ? "txflow ": "", + link.rx_flow ? "rxflow " : "", + link.aneg ? "auto" : ""); + else + len = snprintf(dev->buf, sizeof(dev->buf), "port:%d link:down", + val->port_vlan); + + val->value.s = dev->buf; + val->len = len; + + return 0; +} + +static int +swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->apply_config) + return 0; + + return dev->ops->apply_config(dev); +} + +static int +swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->reset_switch) + return 0; + + return dev->ops->reset_switch(dev); +} + +enum global_defaults { + GLOBAL_APPLY, + GLOBAL_RESET, +}; + +enum vlan_defaults { + VLAN_PORTS, +}; + +enum port_defaults { + PORT_PVID, + PORT_LINK, +}; + +static struct switch_attr default_global[] = { + [GLOBAL_APPLY] = { + .type = SWITCH_TYPE_NOVAL, + .name = "apply", + .description = "Activate changes in the hardware", + .set = swconfig_apply_config, + }, + [GLOBAL_RESET] = { + .type = SWITCH_TYPE_NOVAL, + .name = "reset", + .description = "Reset the switch", + .set = swconfig_reset_switch, + } +}; + +static struct switch_attr default_port[] = { + [PORT_PVID] = { + .type = SWITCH_TYPE_INT, + .name = "pvid", + .description = "Primary VLAN ID", + .set = swconfig_set_pvid, + .get = swconfig_get_pvid, + }, + [PORT_LINK] = { + .type = SWITCH_TYPE_STRING, + .name = "link", + .description = "Get port link information", + .set = NULL, + .get = swconfig_get_link, + } +}; + +static struct switch_attr default_vlan[] = { + [VLAN_PORTS] = { + .type = SWITCH_TYPE_PORTS, + .name = "ports", + .description = "VLAN port mapping", + .set = swconfig_set_vlan_ports, + .get = swconfig_get_vlan_ports, + }, +}; + +static const struct switch_attr * +swconfig_find_attr_by_name(const struct switch_attrlist *alist, const char *name) +{ + int i; + + for (i = 0; i < alist->n_attr; i++) + if (strcmp(name, alist->attr[i].name) == 0) + return &alist->attr[i]; + + return NULL; +} + +static void swconfig_defaults_init(struct switch_dev *dev) +{ + const struct switch_dev_ops *ops = dev->ops; + + dev->def_global = 0; + dev->def_vlan = 0; + dev->def_port = 0; + + if (ops->get_vlan_ports || ops->set_vlan_ports) + set_bit(VLAN_PORTS, &dev->def_vlan); + + if (ops->get_port_pvid || ops->set_port_pvid) + set_bit(PORT_PVID, &dev->def_port); + + if (ops->get_port_link && + !swconfig_find_attr_by_name(&ops->attr_port, "link")) + set_bit(PORT_LINK, &dev->def_port); + + /* always present, can be no-op */ + set_bit(GLOBAL_APPLY, &dev->def_global); + set_bit(GLOBAL_RESET, &dev->def_global); +} + + +static struct genl_family switch_fam = { + .id = GENL_ID_GENERATE, + .name = "switch", + .hdrsize = 0, + .version = 1, + .maxattr = SWITCH_ATTR_MAX, +}; + +static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { + [SWITCH_ATTR_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, + [SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, + [SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, +}; + +static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { + [SWITCH_PORT_ID] = { .type = NLA_U32 }, + [SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, +}; + +static inline void +swconfig_lock(void) +{ + spin_lock(&swdevs_lock); +} + +static inline void +swconfig_unlock(void) +{ + spin_unlock(&swdevs_lock); +} + +static struct switch_dev * +swconfig_get_dev(struct genl_info *info) +{ + struct switch_dev *dev = NULL; + struct switch_dev *p; + int id; + + if (!info->attrs[SWITCH_ATTR_ID]) + goto done; + + id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); + swconfig_lock(); + list_for_each_entry(p, &swdevs, dev_list) { + if (id != p->id) + continue; + + dev = p; + break; + } + if (dev) + mutex_lock(&dev->sw_mutex); + else + DPRINTF("device %d not found\n", id); + swconfig_unlock(); +done: + return dev; +} + +static inline void +swconfig_put_dev(struct switch_dev *dev) +{ + mutex_unlock(&dev->sw_mutex); +} + +static int +swconfig_dump_attr(struct swconfig_callback *cb, void *arg) +{ + struct switch_attr *op = arg; + struct genl_info *info = cb->info; + struct sk_buff *msg = cb->msg; + int id = cb->args[0]; + void *hdr; + + hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, &switch_fam, + NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + NLA_PUT_U32(msg, SWITCH_ATTR_OP_ID, id); + NLA_PUT_U32(msg, SWITCH_ATTR_OP_TYPE, op->type); + NLA_PUT_STRING(msg, SWITCH_ATTR_OP_NAME, op->name); + if (op->description) + NLA_PUT_STRING(msg, SWITCH_ATTR_OP_DESCRIPTION, + op->description); + + return genlmsg_end(msg, hdr); +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +/* spread multipart messages across multiple message buffers */ +static int +swconfig_send_multipart(struct swconfig_callback *cb, void *arg) +{ + struct genl_info *info = cb->info; + int restart = 0; + int err; + + do { + if (!cb->msg) { + cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (cb->msg == NULL) + goto error; + } + + if (!(cb->fill(cb, arg) < 0)) + break; + + /* fill failed, check if this was already the second attempt */ + if (restart) + goto error; + + /* try again in a new message, send the current one */ + restart = 1; + if (cb->close) { + if (cb->close(cb, arg) < 0) + goto error; + } + err = genlmsg_reply(cb->msg, info); + cb->msg = NULL; + if (err < 0) + goto error; + + } while (restart); + + return 0; + +error: + if (cb->msg) + nlmsg_free(cb->msg); + return -1; +} + +static int +swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + struct switch_dev *dev; + struct swconfig_callback cb; + int err = -EINVAL; + int i; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + switch(hdr->cmd) { + case SWITCH_CMD_LIST_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_LIST_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + break; + case SWITCH_CMD_LIST_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + break; + default: + WARN_ON(1); + goto out; + } + + memset(&cb, 0, sizeof(cb)); + cb.info = info; + cb.fill = swconfig_dump_attr; + for (i = 0; i < alist->n_attr; i++) { + if (alist->attr[i].disabled) + continue; + cb.args[0] = i; + err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]); + if (err < 0) + goto error; + } + + /* defaults */ + for (i = 0; i < n_def; i++) { + if (!test_bit(i, def_active)) + continue; + cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; + err = swconfig_send_multipart(&cb, (void *) &def_list[i]); + if (err < 0) + goto error; + } + swconfig_put_dev(dev); + + if (!cb.msg) + return 0; + + return genlmsg_reply(cb.msg, info); + +error: + if (cb.msg) + nlmsg_free(cb.msg); +out: + swconfig_put_dev(dev); + return err; +} + +static const struct switch_attr * +swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, + struct switch_val *val) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + const struct switch_attr *attr = NULL; + int attr_id; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + if (!info->attrs[SWITCH_ATTR_OP_ID]) + goto done; + + switch(hdr->cmd) { + case SWITCH_CMD_SET_GLOBAL: + case SWITCH_CMD_GET_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_SET_VLAN: + case SWITCH_CMD_GET_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + if (!info->attrs[SWITCH_ATTR_OP_VLAN]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); + if (val->port_vlan >= dev->vlans) + goto done; + break; + case SWITCH_CMD_SET_PORT: + case SWITCH_CMD_GET_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + if (!info->attrs[SWITCH_ATTR_OP_PORT]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); + if (val->port_vlan >= dev->ports) + goto done; + break; + default: + WARN_ON(1); + goto done; + } + + if (!alist) + goto done; + + attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); + if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { + attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; + if (attr_id >= n_def) + goto done; + if (!test_bit(attr_id, def_active)) + goto done; + attr = &def_list[attr_id]; + } else { + if (attr_id >= alist->n_attr) + goto done; + attr = &alist->attr[attr_id]; + } + + if (attr->disabled) + attr = NULL; + +done: + if (!attr) + DPRINTF("attribute lookup failed\n"); + val->attr = attr; + return attr; +} + +static int +swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, + struct switch_val *val, int max) +{ + struct nlattr *nla; + int rem; + + val->len = 0; + nla_for_each_nested(nla, head, rem) { + struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; + struct switch_port *port = &val->value.ports[val->len]; + + if (val->len >= max) + return -EINVAL; + + if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, + port_policy)) + return -EINVAL; + + if (!tb[SWITCH_PORT_ID]) + return -EINVAL; + + port->id = nla_get_u32(tb[SWITCH_PORT_ID]); + if (tb[SWITCH_PORT_FLAG_TAGGED]) + port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); + val->len++; + } + + return 0; +} + +static int +swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) +{ + const struct switch_attr *attr; + struct switch_dev *dev; + struct switch_val val; + int err = -EINVAL; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->set) + goto error; + + val.attr = attr; + switch(attr->type) { + case SWITCH_TYPE_NOVAL: + break; + case SWITCH_TYPE_INT: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) + goto error; + val.value.i = + nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); + break; + case SWITCH_TYPE_STRING: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) + goto error; + val.value.s = + nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); + break; + case SWITCH_TYPE_PORTS: + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + + /* TODO: implement multipart? */ + if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { + err = swconfig_parse_ports(skb, + info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], &val, dev->ports); + if (err < 0) + goto error; + } else { + val.len = 0; + err = 0; + } + break; + default: + goto error; + } + + err = attr->set(dev, attr, &val); +error: + swconfig_put_dev(dev); + return err; +} + +static int +swconfig_close_portlist(struct swconfig_callback *cb, void *arg) +{ + if (cb->nest[0]) + nla_nest_end(cb->msg, cb->nest[0]); + return 0; +} + +static int +swconfig_send_port(struct swconfig_callback *cb, void *arg) +{ + const struct switch_port *port = arg; + struct nlattr *p = NULL; + + if (!cb->nest[0]) { + cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); + if (!cb->nest[0]) + return -1; + } + + p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); + if (!p) + goto error; + + NLA_PUT_U32(cb->msg, SWITCH_PORT_ID, port->id); + if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + NLA_PUT_FLAG(cb->msg, SWITCH_PORT_FLAG_TAGGED); + + nla_nest_end(cb->msg, p); + return 0; + +nla_put_failure: + nla_nest_cancel(cb->msg, p); +error: + nla_nest_cancel(cb->msg, cb->nest[0]); + return -1; +} + +static int +swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, + const struct switch_val *val) +{ + struct swconfig_callback cb; + int err = 0; + int i; + + if (!val->value.ports) + return -EINVAL; + + memset(&cb, 0, sizeof(cb)); + cb.cmd = attr; + cb.msg = *msg; + cb.info = info; + cb.fill = swconfig_send_port; + cb.close = swconfig_close_portlist; + + cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); + for (i = 0; i < val->len; i++) { + err = swconfig_send_multipart(&cb, &val->value.ports[i]); + if (err) + goto done; + } + err = val->len; + swconfig_close_portlist(&cb, NULL); + *msg = cb.msg; + +done: + return err; +} + +static int +swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attr *attr; + struct switch_dev *dev; + struct sk_buff *msg = NULL; + struct switch_val val; + int err = -EINVAL; + int cmd = hdr->cmd; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->get) + goto error; + + if (attr->type == SWITCH_TYPE_PORTS) { + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + } + + err = attr->get(dev, attr, &val); + if (err) + goto error; + + msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + goto error; + + hdr = genlmsg_put(msg, info->snd_pid, info->snd_seq, &switch_fam, + 0, cmd); + if (IS_ERR(hdr)) + goto nla_put_failure; + + switch(attr->type) { + case SWITCH_TYPE_INT: + NLA_PUT_U32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i); + break; + case SWITCH_TYPE_STRING: + NLA_PUT_STRING(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s); + break; + case SWITCH_TYPE_PORTS: + err = swconfig_send_ports(&msg, info, + SWITCH_ATTR_OP_VALUE_PORTS, &val); + if (err < 0) + goto nla_put_failure; + break; + default: + DPRINTF("invalid type in attribute\n"); + err = -EINVAL; + goto error; + } + err = genlmsg_end(msg, hdr); + if (err < 0) + goto nla_put_failure; + + swconfig_put_dev(dev); + return genlmsg_reply(msg, info); + +nla_put_failure: + if (msg) + nlmsg_free(msg); +error: + swconfig_put_dev(dev); + if (!err) + err = -ENOMEM; + return err; +} + +static int +swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, + const struct switch_dev *dev) +{ + void *hdr; + + hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, + SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + NLA_PUT_U32(msg, SWITCH_ATTR_ID, dev->id); + NLA_PUT_STRING(msg, SWITCH_ATTR_DEV_NAME, dev->devname); + NLA_PUT_STRING(msg, SWITCH_ATTR_ALIAS, dev->alias); + NLA_PUT_STRING(msg, SWITCH_ATTR_NAME, dev->name); + NLA_PUT_U32(msg, SWITCH_ATTR_VLANS, dev->vlans); + NLA_PUT_U32(msg, SWITCH_ATTR_PORTS, dev->ports); + NLA_PUT_U32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port); + + return genlmsg_end(msg, hdr); +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +static int swconfig_dump_switches(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct switch_dev *dev; + int start = cb->args[0]; + int idx = 0; + + swconfig_lock(); + list_for_each_entry(dev, &swdevs, dev_list) { + if (++idx <= start) + continue; + if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).pid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + dev) < 0) + break; + } + swconfig_unlock(); + cb->args[0] = idx; + + return skb->len; +} + +static int +swconfig_done(struct netlink_callback *cb) +{ + return 0; +} + +static struct genl_ops swconfig_ops[] = { + { + .cmd = SWITCH_CMD_LIST_GLOBAL, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_VLAN, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_PORT, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_GLOBAL, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_VLAN, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_PORT, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_GLOBAL, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_VLAN, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_PORT, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_SWITCH, + .dumpit = swconfig_dump_switches, + .policy = switch_policy, + .done = swconfig_done, + } +}; + +int +register_switch(struct switch_dev *dev, struct net_device *netdev) +{ + struct switch_dev *sdev; + const int max_switches = 8 * sizeof(unsigned long); + unsigned long in_use = 0; + int err; + int i; + + INIT_LIST_HEAD(&dev->dev_list); + if (netdev) { + dev->netdev = netdev; + if (!dev->alias) + dev->alias = netdev->name; + } + BUG_ON(!dev->alias); + + if (dev->ports > 0) { + dev->portbuf = kzalloc(sizeof(struct switch_port) * dev->ports, + GFP_KERNEL); + if (!dev->portbuf) + return -ENOMEM; + } + swconfig_defaults_init(dev); + mutex_init(&dev->sw_mutex); + swconfig_lock(); + dev->id = ++swdev_id; + + list_for_each_entry(sdev, &swdevs, dev_list) { + if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i)) + continue; + if (i < 0 || i > max_switches) + continue; + + set_bit(i, &in_use); + } + i = find_first_zero_bit(&in_use, max_switches); + + if (i == max_switches) { + swconfig_unlock(); + return -ENFILE; + } + + /* fill device name */ + snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); + + list_add(&dev->dev_list, &swdevs); + swconfig_unlock(); + + err = swconfig_create_led_trigger(dev); + if (err) + return err; + + return 0; +} +EXPORT_SYMBOL_GPL(register_switch); + +void +unregister_switch(struct switch_dev *dev) +{ + swconfig_destroy_led_trigger(dev); + kfree(dev->portbuf); + mutex_lock(&dev->sw_mutex); + swconfig_lock(); + list_del(&dev->dev_list); + swconfig_unlock(); + mutex_unlock(&dev->sw_mutex); +} +EXPORT_SYMBOL_GPL(unregister_switch); + + +static int __init +swconfig_init(void) +{ + int i, err; + + INIT_LIST_HEAD(&swdevs); + err = genl_register_family(&switch_fam); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(swconfig_ops); i++) { + err = genl_register_ops(&switch_fam, &swconfig_ops[i]); + if (err) + goto unregister; + } + + return 0; + +unregister: + genl_unregister_family(&switch_fam); + return err; +} + +static void __exit +swconfig_exit(void) +{ + genl_unregister_family(&switch_fam); +} + +module_init(swconfig_init); +module_exit(swconfig_exit); + diff --git a/target/linux/generic/files/drivers/net/phy/swconfig_leds.c b/target/linux/generic/files/drivers/net/phy/swconfig_leds.c new file mode 100644 index 000000000..6f54cc15b --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/swconfig_leds.c @@ -0,0 +1,354 @@ +/* + * swconfig_led.c: LED trigger support for the switch configuration API + * + * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> + * + * 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. + * + */ + +#ifdef CONFIG_SWCONFIG_LEDS + +#include <linux/leds.h> +#include <linux/ctype.h> +#include <linux/device.h> +#include <linux/workqueue.h> + +#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) +#define SWCONFIG_LED_NUM_PORTS 32 + +struct switch_led_trigger { + struct led_trigger trig; + struct switch_dev *swdev; + + struct delayed_work sw_led_work; + u32 port_mask; + u32 port_link; + unsigned long port_traffic[SWCONFIG_LED_NUM_PORTS]; +}; + +struct swconfig_trig_data { + struct led_classdev *led_cdev; + struct switch_dev *swdev; + + rwlock_t lock; + u32 port_mask; + + bool prev_link; + unsigned long prev_traffic; + enum led_brightness prev_brightness; +}; + +static void +swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, + enum led_brightness brightness) +{ + led_brightness_set(trig_data->led_cdev, brightness); + trig_data->prev_brightness = brightness; +} + +static void +swconfig_trig_update_port_mask(struct led_trigger *trigger) +{ + struct list_head *entry; + struct switch_led_trigger *sw_trig; + u32 port_mask; + + if (!trigger) + return; + + sw_trig = (void *) trigger; + + port_mask = 0; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + struct swconfig_trig_data *trig_data; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + trig_data = led_cdev->trigger_data; + if (trig_data) { + read_lock(&trig_data->lock); + port_mask |= trig_data->port_mask; + read_unlock(&trig_data->lock); + } + } + read_unlock(&trigger->leddev_list_lock); + + sw_trig->port_mask = port_mask; + + if (port_mask) + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); + else + cancel_delayed_work_sync(&sw_trig->sw_led_work); +} + +static ssize_t +swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + unsigned long port_mask; + ssize_t ret = -EINVAL; + char *after; + size_t count; + + port_mask = simple_strtoul(buf, &after, 16); + count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + bool changed; + + write_lock(&trig_data->lock); + + changed = (trig_data->port_mask != port_mask); + if (changed) { + trig_data->port_mask = port_mask; + if (port_mask == 0) + swconfig_trig_set_brightness(trig_data, LED_OFF); + } + + write_unlock(&trig_data->lock); + + if (changed) + swconfig_trig_update_port_mask(led_cdev->trigger); + + ret = count; + } + + return ret; +} + +static ssize_t +swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + + read_lock(&trig_data->lock); + sprintf(buf, "%#x\n", trig_data->port_mask); + read_unlock(&trig_data->lock); + + return strlen(buf) + 1; +} + +static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, + swconfig_trig_port_mask_store); + +static void +swconfig_trig_activate(struct led_classdev *led_cdev) +{ + struct switch_led_trigger *sw_trig; + struct swconfig_trig_data *trig_data; + int err; + + if (led_cdev->trigger->activate != swconfig_trig_activate) + return; + + trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); + if (!trig_data) + return; + + sw_trig = (void *) led_cdev->trigger; + + rwlock_init(&trig_data->lock); + trig_data->led_cdev = led_cdev; + trig_data->swdev = sw_trig->swdev; + led_cdev->trigger_data = trig_data; + + err = device_create_file(led_cdev->dev, &dev_attr_port_mask); + if (err) + goto err_free; + + return; + +err_free: + led_cdev->trigger_data = NULL; + kfree(trig_data); +} + +static void +swconfig_trig_deactivate(struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + + swconfig_trig_update_port_mask(led_cdev->trigger); + + trig_data = (void *) led_cdev->trigger_data; + if (trig_data) { + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + kfree(trig_data); + } +} + +static void +swconfig_trig_led_event(struct switch_led_trigger *sw_trig, + struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + u32 port_mask; + bool link; + + trig_data = led_cdev->trigger_data; + if (!trig_data) + return; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + read_unlock(&trig_data->lock); + + link = !!(sw_trig->port_link & port_mask); + if (!link) { + if (link != trig_data->prev_link) + led_brightness_set(trig_data->led_cdev, LED_OFF); + } else { + unsigned long traffic; + int i; + + traffic = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + if (port_mask & (1 << i)) + traffic += sw_trig->port_traffic[i]; + } + + if (trig_data->prev_brightness != LED_FULL) + swconfig_trig_set_brightness(trig_data, LED_FULL); + else if (traffic != trig_data->prev_traffic) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + trig_data->prev_traffic = traffic; + } + + trig_data->prev_link = link; +} + +static void +swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) +{ + struct list_head *entry; + struct led_trigger *trigger; + + trigger = &sw_trig->trig; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + swconfig_trig_led_event(sw_trig, led_cdev); + } + read_unlock(&trigger->leddev_list_lock); +} + +static void +swconfig_led_work_func(struct work_struct *work) +{ + struct switch_led_trigger *sw_trig; + struct switch_dev *swdev; + u32 port_mask; + u32 link; + int i; + + sw_trig = container_of(work, struct switch_led_trigger, + sw_led_work.work); + + port_mask = sw_trig->port_mask; + swdev = sw_trig->swdev; + + link = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + u32 port_bit; + + port_bit = BIT(i); + if ((port_mask & port_bit) == 0) + continue; + + if (swdev->ops->get_port_link) { + struct switch_port_link port_link; + + memset(&port_link, '\0', sizeof(port_link)); + swdev->ops->get_port_link(swdev, i, &port_link); + + if (port_link.link) + link |= port_bit; + } + + if (swdev->ops->get_port_stats) { + struct switch_port_stats port_stats; + + memset(&port_stats, '\0', sizeof(port_stats)); + swdev->ops->get_port_stats(swdev, i, &port_stats); + sw_trig->port_traffic[i] = port_stats.tx_bytes + + port_stats.rx_bytes; + } + } + + sw_trig->port_link = link; + + swconfig_trig_update_leds(sw_trig); + + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); +} + +static int +swconfig_create_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + int err; + + if (!swdev->ops->get_port_link) + return 0; + + sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); + if (!sw_trig) + return -ENOMEM; + + sw_trig->swdev = swdev; + sw_trig->trig.name = swdev->devname; + sw_trig->trig.activate = swconfig_trig_activate; + sw_trig->trig.deactivate = swconfig_trig_deactivate; + + INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); + + err = led_trigger_register(&sw_trig->trig); + if (err) + goto err_free; + + swdev->led_trigger = sw_trig; + + return 0; + +err_free: + kfree(sw_trig); + return err; +} + +static void +swconfig_destroy_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + + sw_trig = swdev->led_trigger; + if (sw_trig) { + cancel_delayed_work_sync(&sw_trig->sw_led_work); + led_trigger_unregister(&sw_trig->trig); + kfree(sw_trig); + } +} + +#else /* SWCONFIG_LEDS */ +static inline int +swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } + +static inline void +swconfig_destroy_led_trigger(struct switch_dev *swdev) { } +#endif /* CONFIG_SWCONFIG_LEDS */ diff --git a/target/linux/generic/files/drivers/pwm/Kconfig b/target/linux/generic/files/drivers/pwm/Kconfig new file mode 100644 index 000000000..70f4442ea --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/Kconfig @@ -0,0 +1,20 @@ +# +# PWM infrastructure and devices +# + +menuconfig GENERIC_PWM + tristate "PWM Support" + depends on SYSFS + help + This enables PWM support through the generic PWM library. + If unsure, say N. + +if GENERIC_PWM + +config GPIO_PWM + tristate "PWM emulation using GPIO" + help + This option enables a single-channel PWM device using + a kernel interval timer and a GPIO pin. If unsure, say N. + +endif diff --git a/target/linux/generic/files/drivers/pwm/Makefile b/target/linux/generic/files/drivers/pwm/Makefile new file mode 100644 index 000000000..3a0e99674 --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for pwm devices +# +obj-$(CONFIG_GENERIC_PWM) := pwm.o +obj-$(CONFIG_GPIO_PWM) += gpio-pwm.o diff --git a/target/linux/generic/files/drivers/pwm/gpio-pwm.c b/target/linux/generic/files/drivers/pwm/gpio-pwm.c new file mode 100644 index 000000000..dff5d1d62 --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/gpio-pwm.c @@ -0,0 +1,298 @@ +/* + * drivers/pwm/gpio.c + * + * Models a single-channel PWM device using a timer and a GPIO pin. + * + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com> + * + * This program is free software; you may redistribute and/or modify + * it under the terms of the GNU General Public License Version 2, as + * published by the Free Software Foundation. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/hrtimer.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/pwm/pwm.h> + +struct gpio_pwm { + struct pwm_device pwm; + struct hrtimer timer; + struct work_struct work; + pwm_callback_t callback; + int gpio; + unsigned long polarity : 1; + unsigned long active : 1; +}; + +static inline struct gpio_pwm *to_gpio_pwm(const struct pwm_channel *p) +{ + return container_of(p->pwm, struct gpio_pwm, pwm); +} + +static void +gpio_pwm_work (struct work_struct *work) +{ + struct gpio_pwm *gp = container_of(work, struct gpio_pwm, work); + + if (gp->active) + gpio_direction_output(gp->gpio, gp->polarity ? 1 : 0); + else + gpio_direction_output(gp->gpio, gp->polarity ? 0 : 1); +} + +static enum hrtimer_restart +gpio_pwm_timeout(struct hrtimer *t) +{ + struct gpio_pwm *gp = container_of(t, struct gpio_pwm, timer); + ktime_t tnew; + + if (unlikely(gp->pwm.channels[0].duty_ticks == 0)) + gp->active = 0; + else if (unlikely(gp->pwm.channels[0].duty_ticks + == gp->pwm.channels[0].period_ticks)) + gp->active = 1; + else + gp->active ^= 1; + + if (gpio_cansleep(gp->gpio)) + schedule_work(&gp->work); + else + gpio_pwm_work(&gp->work); + + if (!gp->active && gp->pwm.channels[0].callback) + gp->pwm.channels[0].callback(&gp->pwm.channels[0]); + + if (unlikely(!gp->active && + (gp->pwm.channels[0].flags & BIT(FLAG_STOP)))) { + clear_bit(FLAG_STOP, &gp->pwm.channels[0].flags); + complete_all(&gp->pwm.channels[0].complete); + return HRTIMER_NORESTART; + } + + if (gp->active) + tnew = ktime_set(0, gp->pwm.channels[0].duty_ticks); + else + tnew = ktime_set(0, gp->pwm.channels[0].period_ticks + - gp->pwm.channels[0].duty_ticks); + hrtimer_start(&gp->timer, tnew, HRTIMER_MODE_REL); + + return HRTIMER_NORESTART; +} + +static void gpio_pwm_start(struct pwm_channel *p) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + + gp->active = 0; + gpio_pwm_timeout(&gp->timer); +} + +static int +gpio_pwm_config_nosleep(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + + switch (c->config_mask) { + + case PWM_CONFIG_DUTY_TICKS: + p->duty_ticks = c->duty_ticks; + break; + + case PWM_CONFIG_START: + if (!hrtimer_active(&gp->timer)) { + gpio_pwm_start(p); + } + break; + default: + ret = -EINVAL; + break; + } + + spin_unlock_irqrestore(&p->lock, flags); + return ret; +} + +static int +gpio_pwm_stop_sync(struct pwm_channel *p) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + int ret; + int was_on = hrtimer_active(&gp->timer); + + if (was_on) { + do { + init_completion(&p->complete); + set_bit(FLAG_STOP, &p->flags); + ret = wait_for_completion_interruptible(&p->complete); + if (ret) + return ret; + } while (p->flags & BIT(FLAG_STOP)); + } + + return was_on; +} + +static int +gpio_pwm_config(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + int was_on = 0; + + if (p->pwm->config_nosleep) { + if (!p->pwm->config_nosleep(p, c)) + return 0; + } + + might_sleep(); + + was_on = gpio_pwm_stop_sync(p); + if (was_on < 0) + return was_on; + + if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) + p->period_ticks = c->period_ticks; + + if (c->config_mask & PWM_CONFIG_DUTY_TICKS) + p->duty_ticks = c->duty_ticks; + + if (c->config_mask & PWM_CONFIG_POLARITY) { + gp->polarity = c->polarity ? 1 : 0; + p->active_high = gp->polarity; + } + + if ((c->config_mask & PWM_CONFIG_START) + || (was_on && !(c->config_mask & PWM_CONFIG_STOP))) + gpio_pwm_start(p); + + return 0; +} + +static int +gpio_pwm_set_callback(struct pwm_channel *p, + pwm_callback_t callback) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + gp->callback = callback; + return 0; +} + +static int +gpio_pwm_request(struct pwm_channel *p) +{ + p->tick_hz = 1000000000UL; + return 0; +} + +static int __devinit +gpio_pwm_probe(struct platform_device *pdev) +{ + struct gpio_pwm *gp; + struct gpio_pwm_platform_data *gpd = pdev->dev.platform_data; + int ret = 0; + + /* TODO: create configfs entries, so users can assign GPIOs to + * PWMs at runtime instead of creating a platform_device + * specification and rebuilding their kernel */ + + if (!gpd || gpio_request(gpd->gpio, dev_name(&pdev->dev))) + return -EINVAL; + + gp = kzalloc(sizeof(*gp), GFP_KERNEL); + if (!gp) { + ret = -ENOMEM; + goto err_alloc; + } + + platform_set_drvdata(pdev, gp); + + gp->pwm.dev = &pdev->dev; + gp->pwm.bus_id = dev_name(&pdev->dev); + gp->pwm.nchan = 1; + gp->gpio = gpd->gpio; + + INIT_WORK(&gp->work, gpio_pwm_work); + + hrtimer_init(&gp->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + gp->timer.function = gpio_pwm_timeout; + + gp->pwm.owner = THIS_MODULE; + gp->pwm.config_nosleep = gpio_pwm_config_nosleep; + gp->pwm.config = gpio_pwm_config; + gp->pwm.request = gpio_pwm_request; + gp->pwm.set_callback = gpio_pwm_set_callback; + + ret = pwm_register(&gp->pwm); + if (ret) + goto err_pwm_register; + + return 0; + +err_pwm_register: + platform_set_drvdata(pdev, 0); + kfree(gp); +err_alloc: + return ret; +} + +static int __devexit +gpio_pwm_remove(struct platform_device *pdev) +{ + struct gpio_pwm *gp = platform_get_drvdata(pdev); + int ret; + + ret = pwm_unregister(&gp->pwm); + hrtimer_cancel(&gp->timer); + cancel_work_sync(&gp->work); + platform_set_drvdata(pdev, 0); + kfree(gp); + + return 0; +} + +static struct platform_driver gpio_pwm_driver = { + .driver = { + .name = "gpio_pwm", + .owner = THIS_MODULE, + }, + .probe = gpio_pwm_probe, + .remove = __devexit_p(gpio_pwm_remove), +}; + +static int __init gpio_pwm_init(void) +{ + return platform_driver_register(&gpio_pwm_driver); +} +module_init(gpio_pwm_init); + +static void __exit gpio_pwm_exit(void) +{ + platform_driver_unregister(&gpio_pwm_driver); +} +module_exit(gpio_pwm_exit); + +MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>"); +MODULE_DESCRIPTION("PWM output using GPIO and a high-resolution timer"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio_pwm"); diff --git a/target/linux/generic/files/drivers/pwm/pwm.c b/target/linux/generic/files/drivers/pwm/pwm.c new file mode 100644 index 000000000..c1596e9e7 --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/pwm.c @@ -0,0 +1,643 @@ +/* + * drivers/pwm/pwm.c + * + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com> + * + * This program is free software; you may redistribute and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/fs.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/slab.h> /*kcalloc, kfree since 2.6.34 */ +#include <linux/pwm/pwm.h> + +static int __pwm_create_sysfs(struct pwm_device *pwm); + +static const char *REQUEST_SYSFS = "sysfs"; +static LIST_HEAD(pwm_device_list); +static DEFINE_MUTEX(device_list_mutex); +static struct class pwm_class; +static struct workqueue_struct *pwm_handler_workqueue; + +int pwm_register(struct pwm_device *pwm) +{ + struct pwm_channel *p; + int wchan; + int ret; + + spin_lock_init(&pwm->list_lock); + + p = kcalloc(pwm->nchan, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + for (wchan = 0; wchan < pwm->nchan; wchan++) { + spin_lock_init(&p[wchan].lock); + init_completion(&p[wchan].complete); + p[wchan].chan = wchan; + p[wchan].pwm = pwm; + } + + pwm->channels = p; + + mutex_lock(&device_list_mutex); + + list_add_tail(&pwm->list, &pwm_device_list); + ret = __pwm_create_sysfs(pwm); + if (ret) { + mutex_unlock(&device_list_mutex); + goto err_create_sysfs; + } + + mutex_unlock(&device_list_mutex); + + dev_info(pwm->dev, "%d channel%s\n", pwm->nchan, + pwm->nchan > 1 ? "s" : ""); + return 0; + +err_create_sysfs: + kfree(p); + + return ret; +} +EXPORT_SYMBOL(pwm_register); + +static int __match_device(struct device *dev, void *data) +{ + return dev_get_drvdata(dev) == data; +} + +int pwm_unregister(struct pwm_device *pwm) +{ + int wchan; + struct device *dev; + + mutex_lock(&device_list_mutex); + + for (wchan = 0; wchan < pwm->nchan; wchan++) { + if (pwm->channels[wchan].flags & BIT(FLAG_REQUESTED)) { + mutex_unlock(&device_list_mutex); + return -EBUSY; + } + } + + for (wchan = 0; wchan < pwm->nchan; wchan++) { + dev = class_find_device(&pwm_class, NULL, + &pwm->channels[wchan], + __match_device); + if (dev) { + put_device(dev); + device_unregister(dev); + } + } + + kfree(pwm->channels); + list_del(&pwm->list); + mutex_unlock(&device_list_mutex); + + return 0; +} +EXPORT_SYMBOL(pwm_unregister); + +static struct pwm_device * +__pwm_find_device(const char *bus_id) +{ + struct pwm_device *p; + + list_for_each_entry(p, &pwm_device_list, list) { + if (!strcmp(bus_id, p->bus_id)) + return p; + } + return NULL; +} + +static int +__pwm_request_channel(struct pwm_channel *p, + const char *requester) +{ + int ret; + + if (test_and_set_bit(FLAG_REQUESTED, &p->flags)) + return -EBUSY; + + if (p->pwm->request) { + ret = p->pwm->request(p); + if (ret) { + clear_bit(FLAG_REQUESTED, &p->flags); + return ret; + } + } + + p->requester = requester; + if (!strcmp(requester, REQUEST_SYSFS)) + p->pid = current->pid; + + return 0; +} + +struct pwm_channel * +pwm_request(const char *bus_id, + int chan, + const char *requester) +{ + struct pwm_device *p; + int ret; + + mutex_lock(&device_list_mutex); + + p = __pwm_find_device(bus_id); + if (!p || chan >= p->nchan) + goto err_no_device; + + if (!try_module_get(p->owner)) + goto err_module_get_failed; + + ret = __pwm_request_channel(&p->channels[chan], requester); + if (ret) + goto err_request_failed; + + mutex_unlock(&device_list_mutex); + return &p->channels[chan]; + +err_request_failed: + module_put(p->owner); +err_module_get_failed: +err_no_device: + mutex_unlock(&device_list_mutex); + return NULL; +} +EXPORT_SYMBOL(pwm_request); + +void pwm_free(struct pwm_channel *p) +{ + mutex_lock(&device_list_mutex); + + if (!test_and_clear_bit(FLAG_REQUESTED, &p->flags)) + goto done; + + pwm_stop(p); + pwm_unsynchronize(p, NULL); + pwm_set_handler(p, NULL, NULL); + + if (p->pwm->free) + p->pwm->free(p); + module_put(p->pwm->owner); +done: + mutex_unlock(&device_list_mutex); +} +EXPORT_SYMBOL(pwm_free); + +unsigned long pwm_ns_to_ticks(struct pwm_channel *p, + unsigned long nsecs) +{ + unsigned long long ticks; + + ticks = nsecs; + ticks *= p->tick_hz; + do_div(ticks, 1000000000); + return ticks; +} +EXPORT_SYMBOL(pwm_ns_to_ticks); + +unsigned long pwm_ticks_to_ns(struct pwm_channel *p, + unsigned long ticks) +{ + unsigned long long ns; + + if (!p->tick_hz) + return 0; + + ns = ticks; + ns *= 1000000000UL; + do_div(ns, p->tick_hz); + return ns; +} +EXPORT_SYMBOL(pwm_ticks_to_ns); + +static void +pwm_config_ns_to_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + if (c->config_mask & PWM_CONFIG_PERIOD_NS) { + c->period_ticks = pwm_ns_to_ticks(p, c->period_ns); + c->config_mask &= ~PWM_CONFIG_PERIOD_NS; + c->config_mask |= PWM_CONFIG_PERIOD_TICKS; + } + + if (c->config_mask & PWM_CONFIG_DUTY_NS) { + c->duty_ticks = pwm_ns_to_ticks(p, c->duty_ns); + c->config_mask &= ~PWM_CONFIG_DUTY_NS; + c->config_mask |= PWM_CONFIG_DUTY_TICKS; + } +} + +static void +pwm_config_percent_to_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + if (c->config_mask & PWM_CONFIG_DUTY_PERCENT) { + if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) + c->duty_ticks = c->period_ticks; + else + c->duty_ticks = p->period_ticks; + + c->duty_ticks *= c->duty_percent; + c->duty_ticks /= 100; + c->config_mask &= ~PWM_CONFIG_DUTY_PERCENT; + c->config_mask |= PWM_CONFIG_DUTY_TICKS; + } +} + +int pwm_config_nosleep(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + if (!p->pwm->config_nosleep) + return -EINVAL; + + pwm_config_ns_to_ticks(p, c); + pwm_config_percent_to_ticks(p, c); + + return p->pwm->config_nosleep(p, c); +} +EXPORT_SYMBOL(pwm_config_nosleep); + +int pwm_config(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + int ret = 0; + + if (unlikely(!p->pwm->config)) + return -EINVAL; + + pwm_config_ns_to_ticks(p, c); + pwm_config_percent_to_ticks(p, c); + + switch (c->config_mask & (PWM_CONFIG_PERIOD_TICKS + | PWM_CONFIG_DUTY_TICKS)) { + case PWM_CONFIG_PERIOD_TICKS: + if (p->duty_ticks > c->period_ticks) { + ret = -EINVAL; + goto err; + } + break; + case PWM_CONFIG_DUTY_TICKS: + if (p->period_ticks < c->duty_ticks) { + ret = -EINVAL; + goto err; + } + break; + case PWM_CONFIG_DUTY_TICKS | PWM_CONFIG_PERIOD_TICKS: + if (c->duty_ticks > c->period_ticks) { + ret = -EINVAL; + goto err; + } + break; + default: + break; + } + +err: + dev_dbg(p->pwm->dev, "%s: config_mask %d period_ticks %lu duty_ticks %lu" + " polarity %d duty_ns %lu period_ns %lu duty_percent %d\n", + __func__, c->config_mask, c->period_ticks, c->duty_ticks, + c->polarity, c->duty_ns, c->period_ns, c->duty_percent); + + if (ret) + return ret; + return p->pwm->config(p, c); +} +EXPORT_SYMBOL(pwm_config); + +int pwm_set_period_ns(struct pwm_channel *p, + unsigned long period_ns) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_PERIOD_TICKS, + .period_ticks = pwm_ns_to_ticks(p, period_ns), + }; + + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_set_period_ns); + +unsigned long pwm_get_period_ns(struct pwm_channel *p) +{ + return pwm_ticks_to_ns(p, p->period_ticks); +} +EXPORT_SYMBOL(pwm_get_period_ns); + +int pwm_set_duty_ns(struct pwm_channel *p, + unsigned long duty_ns) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_DUTY_TICKS, + .duty_ticks = pwm_ns_to_ticks(p, duty_ns), + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_set_duty_ns); + +unsigned long pwm_get_duty_ns(struct pwm_channel *p) +{ + return pwm_ticks_to_ns(p, p->duty_ticks); +} +EXPORT_SYMBOL(pwm_get_duty_ns); + +int pwm_set_duty_percent(struct pwm_channel *p, + int percent) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_DUTY_PERCENT, + .duty_percent = percent, + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_set_duty_percent); + +int pwm_set_polarity(struct pwm_channel *p, + int active_high) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_POLARITY, + .polarity = active_high, + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_set_polarity); + +int pwm_start(struct pwm_channel *p) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_START, + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_start); + +int pwm_stop(struct pwm_channel *p) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_STOP, + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_stop); + +int pwm_synchronize(struct pwm_channel *p, + struct pwm_channel *to_p) +{ + if (p->pwm != to_p->pwm) { + /* TODO: support cross-device synchronization */ + return -EINVAL; + } + + if (!p->pwm->synchronize) + return -EINVAL; + + return p->pwm->synchronize(p, to_p); +} +EXPORT_SYMBOL(pwm_synchronize); + +int pwm_unsynchronize(struct pwm_channel *p, + struct pwm_channel *from_p) +{ + if (from_p && (p->pwm != from_p->pwm)) { + /* TODO: support cross-device synchronization */ + return -EINVAL; + } + + if (!p->pwm->unsynchronize) + return -EINVAL; + + return p->pwm->unsynchronize(p, from_p); +} +EXPORT_SYMBOL(pwm_unsynchronize); + +static void pwm_handler(struct work_struct *w) +{ + struct pwm_channel *p = container_of(w, struct pwm_channel, + handler_work); + if (p->handler && p->handler(p, p->handler_data)) + pwm_stop(p); +} + +static void __pwm_callback(struct pwm_channel *p) +{ + queue_work(pwm_handler_workqueue, &p->handler_work); + dev_dbg(p->pwm->dev, "handler %p scheduled with data %p\n", + p->handler, p->handler_data); +} + +int pwm_set_handler(struct pwm_channel *p, + pwm_handler_t handler, + void *data) +{ + if (p->pwm->set_callback) { + p->handler_data = data; + p->handler = handler; + INIT_WORK(&p->handler_work, pwm_handler); + return p->pwm->set_callback(p, handler ? __pwm_callback : NULL); + } + return -EINVAL; +} +EXPORT_SYMBOL(pwm_set_handler); + +static ssize_t pwm_run_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + if (sysfs_streq(buf, "1")) + pwm_start(p); + else if (sysfs_streq(buf, "0")) + pwm_stop(p); + return len; +} +static DEVICE_ATTR(run, 0200, NULL, pwm_run_store); + +static ssize_t pwm_duty_ns_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + return sprintf(buf, "%lu\n", pwm_get_duty_ns(p)); +} + +static ssize_t pwm_duty_ns_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + unsigned long duty_ns; + struct pwm_channel *p = dev_get_drvdata(dev); + + if (1 == sscanf(buf, "%lu", &duty_ns)) + pwm_set_duty_ns(p, duty_ns); + return len; +} +static DEVICE_ATTR(duty_ns, 0644, pwm_duty_ns_show, pwm_duty_ns_store); + +static ssize_t pwm_period_ns_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + return sprintf(buf, "%lu\n", pwm_get_period_ns(p)); +} + +static ssize_t pwm_period_ns_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + unsigned long period_ns; + struct pwm_channel *p = dev_get_drvdata(dev); + + if (1 == sscanf(buf, "%lu", &period_ns)) + pwm_set_period_ns(p, period_ns); + return len; +} +static DEVICE_ATTR(period_ns, 0644, pwm_period_ns_show, pwm_period_ns_store); + +static ssize_t pwm_polarity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", p->active_high ? 1 : 0); +} + +static ssize_t pwm_polarity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int polarity; + struct pwm_channel *p = dev_get_drvdata(dev); + + if (1 == sscanf(buf, "%d", &polarity)) + pwm_set_polarity(p, polarity); + return len; +} +static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store); + +static ssize_t pwm_request_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + mutex_lock(&device_list_mutex); + __pwm_request_channel(p, REQUEST_SYSFS); + mutex_unlock(&device_list_mutex); + + if (p->pid) + return sprintf(buf, "%s %d\n", p->requester, p->pid); + else + return sprintf(buf, "%s\n", p->requester); +} + +static ssize_t pwm_request_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + pwm_free(p); + return len; +} +static DEVICE_ATTR(request, 0644, pwm_request_show, pwm_request_store); + +static const struct attribute *pwm_attrs[] = +{ + &dev_attr_run.attr, + &dev_attr_polarity.attr, + &dev_attr_duty_ns.attr, + &dev_attr_period_ns.attr, + &dev_attr_request.attr, + NULL, +}; + +static const struct attribute_group pwm_device_attr_group = { + .attrs = (struct attribute **)pwm_attrs, +}; + +static int __pwm_create_sysfs(struct pwm_device *pwm) +{ + int ret = 0; + struct device *dev; + int wchan; + + for (wchan = 0; wchan < pwm->nchan; wchan++) { + dev = device_create(&pwm_class, pwm->dev, MKDEV(0, 0), + pwm->channels + wchan, + "%s:%d", pwm->bus_id, wchan); + if (!dev) + goto err_dev_create; + ret = sysfs_create_group(&dev->kobj, &pwm_device_attr_group); + if (ret) + goto err_dev_create; + } + + return ret; + +err_dev_create: + for (wchan = 0; wchan < pwm->nchan; wchan++) { + dev = class_find_device(&pwm_class, NULL, + &pwm->channels[wchan], + __match_device); + if (dev) { + put_device(dev); + device_unregister(dev); + } + } + + return ret; +} + +static struct class_attribute pwm_class_attrs[] = { + __ATTR_NULL, +}; + +static struct class pwm_class = { + .name = "pwm", + .owner = THIS_MODULE, + + .class_attrs = pwm_class_attrs, +}; + +static int __init pwm_init(void) +{ + int ret; + + /* TODO: how to deal with devices that register very early? */ + pr_err("%s\n", __func__); + ret = class_register(&pwm_class); + if (ret < 0) + return ret; + + pwm_handler_workqueue = create_workqueue("pwmd"); + + return 0; +} +postcore_initcall(pwm_init); |