| target/linux/generic-2.6/files-2.6.31/drivers/char/gpio_dev.c |
| 1 | /* |
| 2 | * character device wrapper for generic gpio layer |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU General Public License as published by |
| 6 | * the Free Software Foundation; either version 2 of the License, or |
| 7 | * (at your option) any later version. |
| 8 | * |
| 9 | * This program is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | * GNU General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU General Public License |
| 15 | * along with this program; if not, write to the Free Software |
| 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA |
| 17 | * |
| 18 | * Feedback, Bugs... blogic@openwrt.org |
| 19 | * |
| 20 | */ |
| 21 | |
| 22 | #include <linux/module.h> |
| 23 | #include <linux/errno.h> |
| 24 | #include <linux/init.h> |
| 25 | #include <asm/uaccess.h> |
| 26 | #include <asm/io.h> |
| 27 | #include <asm/gpio.h> |
| 28 | #include <asm/atomic.h> |
| 29 | #include <linux/init.h> |
| 30 | #include <linux/genhd.h> |
| 31 | #include <linux/device.h> |
| 32 | #include <linux/platform_device.h> |
| 33 | #include <linux/gpio_dev.h> |
| 34 | |
| 35 | #define DRVNAME "gpiodev" |
| 36 | #define DEVNAME "gpio" |
| 37 | |
| 38 | static int dev_major; |
| 39 | static unsigned int gpio_access_mask; |
| 40 | static struct class *gpiodev_class; |
| 41 | |
| 42 | /* Counter is 1, if the device is not opened and zero (or less) if opened. */ |
| 43 | static atomic_t gpio_open_cnt = ATOMIC_INIT(1); |
| 44 | |
| 45 | static int |
| 46 | gpio_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) |
| 47 | { |
| 48 | int retval = 0; |
| 49 | |
| 50 | if (((1 << arg) & gpio_access_mask) != (1 << arg)) |
| 51 | { |
| 52 | retval = -EINVAL; |
| 53 | goto out; |
| 54 | } |
| 55 | |
| 56 | switch (cmd) |
| 57 | { |
| 58 | case GPIO_GET: |
| 59 | retval = gpio_get_value(arg); |
| 60 | break; |
| 61 | |
| 62 | case GPIO_SET: |
| 63 | gpio_set_value(arg, 1); |
| 64 | break; |
| 65 | |
| 66 | case GPIO_CLEAR: |
| 67 | gpio_set_value(arg, 0); |
| 68 | break; |
| 69 | |
| 70 | case GPIO_DIR_IN: |
| 71 | gpio_direction_input(arg); |
| 72 | break; |
| 73 | |
| 74 | case GPIO_DIR_OUT: |
| 75 | gpio_direction_output(arg, 0); |
| 76 | break; |
| 77 | |
| 78 | default: |
| 79 | retval = -EINVAL; |
| 80 | break; |
| 81 | } |
| 82 | |
| 83 | out: |
| 84 | return retval; |
| 85 | } |
| 86 | |
| 87 | static int |
| 88 | gpio_open(struct inode *inode, struct file *file) |
| 89 | { |
| 90 | int result = 0; |
| 91 | unsigned int dev_minor = MINOR(inode->i_rdev); |
| 92 | |
| 93 | if (dev_minor != 0) |
| 94 | { |
| 95 | printk(KERN_ERR DRVNAME ": trying to access unknown minor device -> %d\n", dev_minor); |
| 96 | result = -ENODEV; |
| 97 | goto out; |
| 98 | } |
| 99 | |
| 100 | /* FIXME: We should really allow multiple applications to open the device |
| 101 | * at the same time, as long as the apps access different IO pins. |
| 102 | * The generic gpio-registration functions can be used for that. |
| 103 | * Two new IOCTLs have to be introduced for that. Need to check userspace |
| 104 | * compatibility first. --mb */ |
| 105 | if (!atomic_dec_and_test(&gpio_open_cnt)) { |
| 106 | atomic_inc(&gpio_open_cnt); |
| 107 | printk(KERN_ERR DRVNAME ": Device with minor ID %d already in use\n", dev_minor); |
| 108 | result = -EBUSY; |
| 109 | goto out; |
| 110 | } |
| 111 | |
| 112 | out: |
| 113 | return result; |
| 114 | } |
| 115 | |
| 116 | static int |
| 117 | gpio_close(struct inode * inode, struct file * file) |
| 118 | { |
| 119 | smp_mb__before_atomic_inc(); |
| 120 | atomic_inc(&gpio_open_cnt); |
| 121 | |
| 122 | return 0; |
| 123 | } |
| 124 | |
| 125 | struct file_operations gpio_fops = { |
| 126 | ioctl: gpio_ioctl, |
| 127 | open: gpio_open, |
| 128 | release: gpio_close |
| 129 | }; |
| 130 | |
| 131 | static int |
| 132 | gpio_probe(struct platform_device *dev) |
| 133 | { |
| 134 | int result = 0; |
| 135 | |
| 136 | dev_major = register_chrdev(0, DEVNAME, &gpio_fops); |
| 137 | if (!dev_major) |
| 138 | { |
| 139 | printk(KERN_ERR DRVNAME ": Error whilst opening %s \n", DEVNAME); |
| 140 | result = -ENODEV; |
| 141 | goto out; |
| 142 | } |
| 143 | |
| 144 | gpiodev_class = class_create(THIS_MODULE, DRVNAME); |
| 145 | device_create(gpiodev_class, NULL, MKDEV(dev_major, 0), dev, DEVNAME); |
| 146 | |
| 147 | printk(KERN_INFO DRVNAME ": gpio device registered with major %d\n", dev_major); |
| 148 | |
| 149 | if (dev->num_resources != 1) |
| 150 | { |
| 151 | printk(KERN_ERR DRVNAME ": device may only have 1 resource\n"); |
| 152 | result = -ENODEV; |
| 153 | goto out; |
| 154 | } |
| 155 | |
| 156 | gpio_access_mask = dev->resource[0].start; |
| 157 | |
| 158 | printk(KERN_INFO DRVNAME ": gpio platform device registered with access mask %08X\n", gpio_access_mask); |
| 159 | out: |
| 160 | return result; |
| 161 | } |
| 162 | |
| 163 | static int |
| 164 | gpio_remove(struct platform_device *dev) |
| 165 | { |
| 166 | unregister_chrdev(dev_major, DEVNAME); |
| 167 | return 0; |
| 168 | } |
| 169 | |
| 170 | static struct |
| 171 | platform_driver gpio_driver = { |
| 172 | .probe = gpio_probe, |
| 173 | .remove = gpio_remove, |
| 174 | .driver = { |
| 175 | .name = "GPIODEV", |
| 176 | .owner = THIS_MODULE, |
| 177 | }, |
| 178 | }; |
| 179 | |
| 180 | static int __init |
| 181 | gpio_mod_init(void) |
| 182 | { |
| 183 | int ret = platform_driver_register(&gpio_driver); |
| 184 | if (ret) |
| 185 | printk(KERN_INFO DRVNAME ": Error registering platfom driver!"); |
| 186 | |
| 187 | return ret; |
| 188 | } |
| 189 | |
| 190 | static void __exit |
| 191 | gpio_mod_exit(void) |
| 192 | { |
| 193 | platform_driver_unregister(&gpio_driver); |
| 194 | } |
| 195 | |
| 196 | module_init (gpio_mod_init); |
| 197 | module_exit (gpio_mod_exit); |
| 198 | |
| 199 | MODULE_LICENSE("GPL"); |
| 200 | MODULE_AUTHOR("John Crispin / OpenWrt"); |
| 201 | MODULE_DESCRIPTION("Character device for for generic gpio api"); |
| target/linux/generic-2.6/files-2.6.31/drivers/input/misc/gpio_buttons.c |
| 1 | /* |
| 2 | * Driver for buttons on GPIO lines not capable of generating interrupts |
| 3 | * |
| 4 | * Copyright (C) 2007,2008 Gabor Juhos <juhosg at openwrt.org> |
| 5 | * |
| 6 | * This file was based on: /drivers/input/misc/cobalt_btns.c |
| 7 | * Copyright (C) 2007 Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp> |
| 8 | * |
| 9 | * also was based on: /drivers/input/keyboard/gpio_keys.c |
| 10 | * Copyright 2005 Phil Blundell |
| 11 | * |
| 12 | * This program is free software; you can redistribute it and/or modify |
| 13 | * it under the terms of the GNU General Public License version 2 as |
| 14 | * published by the Free Software Foundation. |
| 15 | * |
| 16 | */ |
| 17 | |
| 18 | #include <linux/kernel.h> |
| 19 | #include <linux/module.h> |
| 20 | #include <linux/init.h> |
| 21 | |
| 22 | #include <linux/input.h> |
| 23 | #include <linux/input-polldev.h> |
| 24 | #include <linux/ioport.h> |
| 25 | #include <linux/platform_device.h> |
| 26 | |
| 27 | #include <linux/gpio_buttons.h> |
| 28 | |
| 29 | #include <asm/gpio.h> |
| 30 | |
| 31 | #define DRV_NAME "gpio-buttons" |
| 32 | #define DRV_VERSION "0.1.1" |
| 33 | #define PFX DRV_NAME ": " |
| 34 | |
| 35 | struct gpio_buttons_dev { |
| 36 | struct input_polled_dev *poll_dev; |
| 37 | struct gpio_buttons_platform_data *pdata; |
| 38 | }; |
| 39 | |
| 40 | static void gpio_buttons_poll(struct input_polled_dev *dev) |
| 41 | { |
| 42 | struct gpio_buttons_dev *bdev = dev->private; |
| 43 | struct gpio_buttons_platform_data *pdata = bdev->pdata; |
| 44 | struct input_dev *input = dev->input; |
| 45 | int i; |
| 46 | |
| 47 | for (i = 0; i < bdev->pdata->nbuttons; i++) { |
| 48 | struct gpio_button *button = &pdata->buttons[i]; |
| 49 | unsigned int type = button->type ?: EV_KEY; |
| 50 | int state; |
| 51 | |
| 52 | state = gpio_get_value(button->gpio) ? 1 : 0; |
| 53 | state ^= button->active_low; |
| 54 | |
| 55 | if (state) { |
| 56 | button->count++; |
| 57 | } else { |
| 58 | if (button->count >= button->threshold) { |
| 59 | input_event(input, type, button->code, 1); |
| 60 | input_sync(input); |
| 61 | } |
| 62 | button->count = 0; |
| 63 | } |
| 64 | |
| 65 | if (button->count == button->threshold) { |
| 66 | input_event(input, type, button->code, 0); |
| 67 | input_sync(input); |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | static int __devinit gpio_buttons_probe(struct platform_device *pdev) |
| 73 | { |
| 74 | struct gpio_buttons_platform_data *pdata = pdev->dev.platform_data; |
| 75 | struct gpio_buttons_dev *bdev; |
| 76 | struct input_polled_dev *poll_dev; |
| 77 | struct input_dev *input; |
| 78 | int error, i; |
| 79 | |
| 80 | |
| 81 | if (!pdata) |
| 82 | return -ENXIO; |
| 83 | |
| 84 | bdev = kzalloc(sizeof(*bdev), GFP_KERNEL); |
| 85 | if (!bdev) { |
| 86 | printk(KERN_ERR DRV_NAME "no memory for device\n"); |
| 87 | return -ENOMEM; |
| 88 | } |
| 89 | |
| 90 | poll_dev = input_allocate_polled_device(); |
| 91 | if (!poll_dev) { |
| 92 | printk(KERN_ERR DRV_NAME "no memory for polled device\n"); |
| 93 | error = -ENOMEM; |
| 94 | goto err_free_bdev; |
| 95 | } |
| 96 | |
| 97 | poll_dev->private = bdev; |
| 98 | poll_dev->poll = gpio_buttons_poll; |
| 99 | poll_dev->poll_interval = pdata->poll_interval; |
| 100 | |
| 101 | input = poll_dev->input; |
| 102 | |
| 103 | input->evbit[0] = BIT(EV_KEY); |
| 104 | input->name = pdev->name; |
| 105 | input->phys = "gpio-buttons/input0"; |
| 106 | input->dev.parent = &pdev->dev; |
| 107 | |
| 108 | input->id.bustype = BUS_HOST; |
| 109 | input->id.vendor = 0x0001; |
| 110 | input->id.product = 0x0001; |
| 111 | input->id.version = 0x0100; |
| 112 | |
| 113 | for (i = 0; i < pdata->nbuttons; i++) { |
| 114 | struct gpio_button *button = &pdata->buttons[i]; |
| 115 | unsigned int gpio = button->gpio; |
| 116 | unsigned int type = button->type ?: EV_KEY; |
| 117 | |
| 118 | error = gpio_request(gpio, button->desc ? |
| 119 | button->desc : DRV_NAME); |
| 120 | if (error) { |
| 121 | printk(KERN_ERR PFX "unable to claim gpio %u, " |
| 122 | "error %d\n", gpio, error); |
| 123 | goto err_free_gpio; |
| 124 | } |
| 125 | |
| 126 | error = gpio_direction_input(gpio); |
| 127 | if (error) { |
| 128 | printk(KERN_ERR PFX "unable to set direction on " |
| 129 | "gpio %u, error %d\n", gpio, error); |
| 130 | goto err_free_gpio; |
| 131 | } |
| 132 | |
| 133 | input_set_capability(input, type, button->code); |
| 134 | button->count = 0; |
| 135 | } |
| 136 | |
| 137 | bdev->poll_dev = poll_dev; |
| 138 | bdev->pdata = pdata; |
| 139 | platform_set_drvdata(pdev, bdev); |
| 140 | |
| 141 | error = input_register_polled_device(poll_dev); |
| 142 | if (error) { |
| 143 | printk(KERN_ERR PFX "unable to register polled device, " |
| 144 | "error %d\n", error); |
| 145 | goto err_free_gpio; |
| 146 | } |
| 147 | |
| 148 | return 0; |
| 149 | |
| 150 | err_free_gpio: |
| 151 | for (i = i - 1; i >= 0; i--) |
| 152 | gpio_free(pdata->buttons[i].gpio); |
| 153 | |
| 154 | input_free_polled_device(poll_dev); |
| 155 | |
| 156 | err_free_bdev: |
| 157 | kfree(bdev); |
| 158 | |
| 159 | platform_set_drvdata(pdev, NULL); |
| 160 | return error; |
| 161 | } |
| 162 | |
| 163 | static int __devexit gpio_buttons_remove(struct platform_device *pdev) |
| 164 | { |
| 165 | struct gpio_buttons_dev *bdev = platform_get_drvdata(pdev); |
| 166 | struct gpio_buttons_platform_data *pdata = bdev->pdata; |
| 167 | int i; |
| 168 | |
| 169 | input_unregister_polled_device(bdev->poll_dev); |
| 170 | |
| 171 | for (i = 0; i < pdata->nbuttons; i++) |
| 172 | gpio_free(pdata->buttons[i].gpio); |
| 173 | |
| 174 | input_free_polled_device(bdev->poll_dev); |
| 175 | |
| 176 | kfree(bdev); |
| 177 | platform_set_drvdata(pdev, NULL); |
| 178 | |
| 179 | return 0; |
| 180 | } |
| 181 | |
| 182 | static struct platform_driver gpio_buttons_driver = { |
| 183 | .probe = gpio_buttons_probe, |
| 184 | .remove = __devexit_p(gpio_buttons_remove), |
| 185 | .driver = { |
| 186 | .name = DRV_NAME, |
| 187 | .owner = THIS_MODULE, |
| 188 | }, |
| 189 | }; |
| 190 | |
| 191 | static int __init gpio_buttons_init(void) |
| 192 | { |
| 193 | printk(KERN_INFO DRV_NAME " driver version " DRV_VERSION "\n"); |
| 194 | return platform_driver_register(&gpio_buttons_driver); |
| 195 | } |
| 196 | |
| 197 | static void __exit gpio_buttons_exit(void) |
| 198 | { |
| 199 | platform_driver_unregister(&gpio_buttons_driver); |
| 200 | } |
| 201 | |
| 202 | module_init(gpio_buttons_init); |
| 203 | module_exit(gpio_buttons_exit); |
| 204 | |
| 205 | MODULE_LICENSE("GPL"); |
| 206 | MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>"); |
| 207 | MODULE_VERSION(DRV_VERSION); |
| 208 | MODULE_DESCRIPTION("Polled buttons driver for CPU GPIOs"); |
| 209 | |
| target/linux/generic-2.6/files-2.6.31/drivers/leds/leds-alix.c |
| 1 | /* |
| 2 | * LEDs driver for PCEngines ALIX 2/3 series |
| 3 | * |
| 4 | * Copyright (C) 2007 Petr Liebman |
| 5 | * |
| 6 | * Based on leds-wrap.c |
| 7 | * |
| 8 | * This program is free software; you can redistribute it and/or modify |
| 9 | * it under the terms of the GNU General Public License version 2 as |
| 10 | * published by the Free Software Foundation. |
| 11 | */ |
| 12 | |
| 13 | #include <linux/kernel.h> |
| 14 | #include <linux/init.h> |
| 15 | #include <linux/platform_device.h> |
| 16 | #include <linux/leds.h> |
| 17 | #include <linux/err.h> |
| 18 | #include <asm/io.h> |
| 19 | |
| 20 | #define DRVNAME "alix-led" |
| 21 | |
| 22 | #define ALIX_LED1_PORT (0x6100) |
| 23 | #define ALIX_LED1_ON (1<<22) |
| 24 | #define ALIX_LED1_OFF (1<<6) |
| 25 | |
| 26 | #define ALIX_LED2_PORT (0x6180) |
| 27 | #define ALIX_LED2_ON (1<<25) |
| 28 | #define ALIX_LED2_OFF (1<<9) |
| 29 | |
| 30 | #define ALIX_LED3_PORT (0x6180) |
| 31 | #define ALIX_LED3_ON (1<<27) |
| 32 | #define ALIX_LED3_OFF (1<<11) |
| 33 | |
| 34 | |
| 35 | static struct platform_device *pdev; |
| 36 | |
| 37 | static void alix_led_set_1(struct led_classdev *led_cdev, |
| 38 | enum led_brightness value) |
| 39 | { |
| 40 | if (value) |
| 41 | outl(ALIX_LED1_ON, ALIX_LED1_PORT); |
| 42 | else |
| 43 | outl(ALIX_LED1_OFF, ALIX_LED1_PORT); |
| 44 | } |
| 45 | |
| 46 | static void alix_led_set_2(struct led_classdev *led_cdev, |
| 47 | enum led_brightness value) |
| 48 | { |
| 49 | if (value) |
| 50 | outl(ALIX_LED2_ON, ALIX_LED2_PORT); |
| 51 | else |
| 52 | outl(ALIX_LED2_OFF, ALIX_LED2_PORT); |
| 53 | } |
| 54 | |
| 55 | static void alix_led_set_3(struct led_classdev *led_cdev, |
| 56 | enum led_brightness value) |
| 57 | { |
| 58 | if (value) |
| 59 | outl(ALIX_LED3_ON, ALIX_LED3_PORT); |
| 60 | else |
| 61 | outl(ALIX_LED3_OFF, ALIX_LED3_PORT); |
| 62 | } |
| 63 | |
| 64 | static struct led_classdev alix_led_1 = { |
| 65 | .name = "alix:1", |
| 66 | .brightness_set = alix_led_set_1, |
| 67 | }; |
| 68 | |
| 69 | static struct led_classdev alix_led_2 = { |
| 70 | .name = "alix:2", |
| 71 | .brightness_set = alix_led_set_2, |
| 72 | }; |
| 73 | |
| 74 | static struct led_classdev alix_led_3 = { |
| 75 | .name = "alix:3", |
| 76 | .brightness_set = alix_led_set_3, |
| 77 | }; |
| 78 | |
| 79 | |
| 80 | #ifdef CONFIG_PM |
| 81 | static int alix_led_suspend(struct platform_device *dev, |
| 82 | pm_message_t state) |
| 83 | { |
| 84 | led_classdev_suspend(&alix_led_1); |
| 85 | led_classdev_suspend(&alix_led_2); |
| 86 | led_classdev_suspend(&alix_led_3); |
| 87 | return 0; |
| 88 | } |
| 89 | |
| 90 | static int alix_led_resume(struct platform_device *dev) |
| 91 | { |
| 92 | led_classdev_resume(&alix_led_1); |
| 93 | led_classdev_resume(&alix_led_2); |
| 94 | led_classdev_resume(&alix_led_3); |
| 95 | return 0; |
| 96 | } |
| 97 | #else |
| 98 | #define alix_led_suspend NULL |
| 99 | #define alix_led_resume NULL |
| 100 | #endif |
| 101 | |
| 102 | static int alix_led_probe(struct platform_device *pdev) |
| 103 | { |
| 104 | int ret; |
| 105 | |
| 106 | ret = led_classdev_register(&pdev->dev, &alix_led_1); |
| 107 | if (ret >= 0) |
| 108 | { |
| 109 | ret = led_classdev_register(&pdev->dev, &alix_led_2); |
| 110 | if (ret >= 0) |
| 111 | { |
| 112 | ret = led_classdev_register(&pdev->dev, &alix_led_3); |
| 113 | if (ret < 0) |
| 114 | led_classdev_unregister(&alix_led_2); |
| 115 | } |
| 116 | if (ret < 0) |
| 117 | led_classdev_unregister(&alix_led_1); |
| 118 | } |
| 119 | return ret; |
| 120 | } |
| 121 | |
| 122 | static int alix_led_remove(struct platform_device *pdev) |
| 123 | { |
| 124 | led_classdev_unregister(&alix_led_1); |
| 125 | led_classdev_unregister(&alix_led_2); |
| 126 | led_classdev_unregister(&alix_led_3); |
| 127 | return 0; |
| 128 | } |
| 129 | |
| 130 | static struct platform_driver alix_led_driver = { |
| 131 | .probe = alix_led_probe, |
| 132 | .remove = alix_led_remove, |
| 133 | .suspend = alix_led_suspend, |
| 134 | .resume = alix_led_resume, |
| 135 | .driver = { |
| 136 | .name = DRVNAME, |
| 137 | .owner = THIS_MODULE, |
| 138 | }, |
| 139 | }; |
| 140 | |
| 141 | static int __init alix_led_init(void) |
| 142 | { |
| 143 | int ret; |
| 144 | |
| 145 | ret = platform_driver_register(&alix_led_driver); |
| 146 | if (ret < 0) |
| 147 | goto out; |
| 148 | |
| 149 | pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); |
| 150 | if (IS_ERR(pdev)) { |
| 151 | ret = PTR_ERR(pdev); |
| 152 | platform_driver_unregister(&alix_led_driver); |
| 153 | goto out; |
| 154 | } |
| 155 | |
| 156 | out: |
| 157 | return ret; |
| 158 | } |
| 159 | |
| 160 | static void __exit alix_led_exit(void) |
| 161 | { |
| 162 | platform_device_unregister(pdev); |
| 163 | platform_driver_unregister(&alix_led_driver); |
| 164 | } |
| 165 | |
| 166 | module_init(alix_led_init); |
| 167 | module_exit(alix_led_exit); |
| 168 | |
| 169 | MODULE_AUTHOR("Petr Liebman"); |
| 170 | MODULE_DESCRIPTION("PCEngines ALIX LED driver"); |
| 171 | MODULE_LICENSE("GPL"); |
| 172 | |
| target/linux/generic-2.6/files-2.6.31/drivers/leds/ledtrig-morse.c |
| 1 | /* |
| 2 | * LED Morse Trigger |
| 3 | * |
| 4 | * Copyright (C) 2007 Gabor Juhos <juhosg at openwrt.org> |
| 5 | * |
| 6 | * This file was based on: drivers/led/ledtrig-timer.c |
| 7 | * Copyright 2005-2006 Openedhand Ltd. |
| 8 | * Author: Richard Purdie <rpurdie@openedhand.com> |
| 9 | * |
| 10 | * also based on the patch '[PATCH] 2.5.59 morse code panics' posted |
| 11 | * in the LKML by Tomas Szepe at Thu, 30 Jan 2003 |
| 12 | * Copyright (C) 2002 Andrew Rodland <arodland@noln.com> |
| 13 | * Copyright (C) 2003 Tomas Szepe <szepe@pinerecords.com> |
| 14 | * |
| 15 | * This program is free software; you can redistribute it and/or modify it |
| 16 | * under the terms of the GNU General Public License version 2 as published |
| 17 | * by the Free Software Foundation. |
| 18 | * |
| 19 | */ |
| 20 | |
| 21 | #include <linux/kernel.h> |
| 22 | #include <linux/version.h> |
| 23 | #include <linux/module.h> |
| 24 | #include <linux/jiffies.h> |
| 25 | #include <linux/init.h> |
| 26 | #include <linux/list.h> |
| 27 | #include <linux/spinlock.h> |
| 28 | #include <linux/device.h> |
| 29 | #include <linux/sysdev.h> |
| 30 | #include <linux/timer.h> |
| 31 | #include <linux/ctype.h> |
| 32 | #include <linux/leds.h> |
| 33 | |
| 34 | #include "leds.h" |
| 35 | |
| 36 | #define MORSE_DELAY_BASE (HZ/2) |
| 37 | |
| 38 | #define MORSE_STATE_BLINK_START 0 |
| 39 | #define MORSE_STATE_BLINK_STOP 1 |
| 40 | |
| 41 | #define MORSE_DIT_LEN 1 |
| 42 | #define MORSE_DAH_LEN 3 |
| 43 | #define MORSE_SPACE_LEN 7 |
| 44 | |
| 45 | struct morse_trig_data { |
| 46 | unsigned long delay; |
| 47 | char *msg; |
| 48 | |
| 49 | unsigned char morse; |
| 50 | unsigned char state; |
| 51 | char *msgpos; |
| 52 | struct timer_list timer; |
| 53 | }; |
| 54 | |
| 55 | const unsigned char morsetable[] = { |
| 56 | 0122, 0, 0310, 0, 0, 0163, /* "#$%&' */ |
| 57 | 055, 0155, 0, 0, 0163, 0141, 0152, 0051, /* ()*+,-./ */ |
| 58 | 077, 076, 074, 070, 060, 040, 041, 043, 047, 057, /* 0-9 */ |
| 59 | 0107, 0125, 0, 0061, 0, 0114, 0, /* :;<=>?@ */ |
| 60 | 006, 021, 025, 011, 002, 024, 013, 020, 004, /* A-I */ |
| 61 | 036, 015, 022, 007, 005, 017, 026, 033, 012, /* J-R */ |
| 62 | 010, 003, 014, 030, 016, 031, 035, 023, /* S-Z */ |
| 63 | 0, 0, 0, 0, 0154 /* [\]^_ */ |
| 64 | }; |
| 65 | |
| 66 | static inline unsigned char tomorse(char c) { |
| 67 | if (c >= 'a' && c <= 'z') |
| 68 | c = c - 'a' + 'A'; |
| 69 | if (c >= '"' && c <= '_') { |
| 70 | return morsetable[c - '"']; |
| 71 | } else |
| 72 | return 0; |
| 73 | } |
| 74 | |
| 75 | static inline unsigned long dit_len(struct morse_trig_data *morse_data) |
| 76 | { |
| 77 | return MORSE_DIT_LEN*morse_data->delay; |
| 78 | } |
| 79 | |
| 80 | static inline unsigned long dah_len(struct morse_trig_data *morse_data) |
| 81 | { |
| 82 | return MORSE_DAH_LEN*morse_data->delay; |
| 83 | } |
| 84 | |
| 85 | static inline unsigned long space_len(struct morse_trig_data *morse_data) |
| 86 | { |
| 87 | return MORSE_SPACE_LEN*morse_data->delay; |
| 88 | } |
| 89 | |
| 90 | static void morse_timer_function(unsigned long data) |
| 91 | { |
| 92 | struct led_classdev *led_cdev = (struct led_classdev *)data; |
| 93 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 94 | unsigned long brightness = LED_OFF; |
| 95 | unsigned long delay = 0; |
| 96 | |
| 97 | if (!morse_data->msg) |
| 98 | goto set_led; |
| 99 | |
| 100 | switch (morse_data->state) { |
| 101 | case MORSE_STATE_BLINK_START: |
| 102 | /* Starting a new blink. We have a valid code in morse. */ |
| 103 | delay = (morse_data->morse & 001) ? dah_len(morse_data): |
| 104 | dit_len(morse_data); |
| 105 | brightness = LED_FULL; |
| 106 | morse_data->state = MORSE_STATE_BLINK_STOP; |
| 107 | morse_data->morse >>= 1; |
| 108 | break; |
| 109 | case MORSE_STATE_BLINK_STOP: |
| 110 | /* Coming off of a blink. */ |
| 111 | morse_data->state = MORSE_STATE_BLINK_START; |
| 112 | |
| 113 | if (morse_data->morse > 1) { |
| 114 | /* Not done yet, just a one-dit pause. */ |
| 115 | delay = dit_len(morse_data); |
| 116 | break; |
| 117 | } |
| 118 | |
| 119 | /* Get a new char, figure out how much space. */ |
| 120 | /* First time through */ |
| 121 | if (!morse_data->msgpos) |
| 122 | morse_data->msgpos = (char *)morse_data->msg; |
| 123 | |
| 124 | if (!*morse_data->msgpos) { |
| 125 | /* Repeating */ |
| 126 | morse_data->msgpos = (char *)morse_data->msg; |
| 127 | delay = space_len(morse_data); |
| 128 | } else { |
| 129 | /* Inter-letter space */ |
| 130 | delay = dah_len(morse_data); |
| 131 | } |
| 132 | |
| 133 | if (!(morse_data->morse = tomorse(*morse_data->msgpos))) { |
| 134 | delay = space_len(morse_data); |
| 135 | /* And get us back here */ |
| 136 | morse_data->state = MORSE_STATE_BLINK_STOP; |
| 137 | } |
| 138 | morse_data->msgpos++; |
| 139 | break; |
| 140 | } |
| 141 | |
| 142 | mod_timer(&morse_data->timer, jiffies + msecs_to_jiffies(delay)); |
| 143 | |
| 144 | set_led: |
| 145 | led_set_brightness(led_cdev, brightness); |
| 146 | } |
| 147 | |
| 148 | static ssize_t _morse_delay_show(struct led_classdev *led_cdev, char *buf) |
| 149 | { |
| 150 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 151 | |
| 152 | sprintf(buf, "%lu\n", morse_data->delay); |
| 153 | |
| 154 | return strlen(buf) + 1; |
| 155 | } |
| 156 | |
| 157 | static ssize_t _morse_delay_store(struct led_classdev *led_cdev, |
| 158 | const char *buf, size_t size) |
| 159 | { |
| 160 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 161 | char *after; |
| 162 | unsigned long state = simple_strtoul(buf, &after, 10); |
| 163 | size_t count = after - buf; |
| 164 | int ret = -EINVAL; |
| 165 | |
| 166 | if (*after && isspace(*after)) |
| 167 | count++; |
| 168 | |
| 169 | if (count == size) { |
| 170 | morse_data->delay = state; |
| 171 | mod_timer(&morse_data->timer, jiffies + 1); |
| 172 | ret = count; |
| 173 | } |
| 174 | |
| 175 | return ret; |
| 176 | } |
| 177 | |
| 178 | static ssize_t _morse_msg_show(struct led_classdev *led_cdev, char *buf) |
| 179 | { |
| 180 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 181 | |
| 182 | if (!morse_data->msg) |
| 183 | sprintf(buf, "<none>\n"); |
| 184 | else |
| 185 | sprintf(buf, "%s\n", morse_data->msg); |
| 186 | |
| 187 | return strlen(buf) + 1; |
| 188 | } |
| 189 | |
| 190 | static ssize_t _morse_msg_store(struct led_classdev *led_cdev, |
| 191 | const char *buf, size_t size) |
| 192 | { |
| 193 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 194 | char *m; |
| 195 | |
| 196 | m = kmalloc(size, GFP_KERNEL); |
| 197 | if (!m) |
| 198 | return -ENOMEM; |
| 199 | |
| 200 | memcpy(m,buf,size); |
| 201 | m[size]='\0'; |
| 202 | |
| 203 | if (morse_data->msg) |
| 204 | kfree(morse_data->msg); |
| 205 | |
| 206 | morse_data->msg = m; |
| 207 | morse_data->msgpos = NULL; |
| 208 | morse_data->state = MORSE_STATE_BLINK_STOP; |
| 209 | |
| 210 | mod_timer(&morse_data->timer, jiffies + 1); |
| 211 | |
| 212 | return size; |
| 213 | } |
| 214 | |
| 215 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23) |
| 216 | static ssize_t morse_delay_show(struct device *dev, |
| 217 | struct device_attribute *attr, char *buf) |
| 218 | { |
| 219 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 220 | |
| 221 | return _morse_delay_show(led_cdev, buf); |
| 222 | } |
| 223 | |
| 224 | static ssize_t morse_delay_store(struct device *dev, |
| 225 | struct device_attribute *attr, const char *buf, size_t size) |
| 226 | { |
| 227 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 228 | |
| 229 | return _morse_delay_store(led_cdev, buf, size); |
| 230 | } |
| 231 | |
| 232 | static ssize_t morse_msg_show(struct device *dev, |
| 233 | struct device_attribute *attr, char *buf) |
| 234 | { |
| 235 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 236 | |
| 237 | return _morse_msg_show(led_cdev, buf); |
| 238 | } |
| 239 | |
| 240 | static ssize_t morse_msg_store(struct device *dev, |
| 241 | struct device_attribute *attr, const char *buf, size_t size) |
| 242 | { |
| 243 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 244 | |
| 245 | return _morse_msg_store(led_cdev, buf, size); |
| 246 | } |
| 247 | |
| 248 | static DEVICE_ATTR(delay, 0644, morse_delay_show, morse_delay_store); |
| 249 | static DEVICE_ATTR(message, 0644, morse_msg_show, morse_msg_store); |
| 250 | |
| 251 | #define led_device_create_file(leddev, attr) \ |
| 252 | device_create_file(leddev->dev, &dev_attr_ ## attr) |
| 253 | #define led_device_remove_file(leddev, attr) \ |
| 254 | device_remove_file(leddev->dev, &dev_attr_ ## attr) |
| 255 | |
| 256 | #else |
| 257 | static ssize_t morse_delay_show(struct class_device *dev, char *buf) |
| 258 | { |
| 259 | struct led_classdev *led_cdev = class_get_devdata(dev); |
| 260 | |
| 261 | return _morse_delay_show(led_cdev, buf); |
| 262 | } |
| 263 | |
| 264 | static ssize_t morse_delay_store(struct class_device *dev, const char *buf, |
| 265 | size_t size) |
| 266 | { |
| 267 | struct led_classdev *led_cdev = class_get_devdata(dev); |
| 268 | |
| 269 | return _morse_delay_store(led_cdev, buf, size); |
| 270 | } |
| 271 | |
| 272 | static ssize_t morse_msg_show(struct class_device *dev, char *buf) |
| 273 | { |
| 274 | struct led_classdev *led_cdev = class_get_devdata(dev); |
| 275 | |
| 276 | return _morse_msg_show(led_cdev, buf); |
| 277 | } |
| 278 | |
| 279 | static ssize_t morse_msg_store(struct class_device *dev, const char *buf, |
| 280 | size_t size) |
| 281 | { |
| 282 | struct led_classdev *led_cdev = class_get_devdata(dev); |
| 283 | |
| 284 | return _morse_msg_store(led_cdev, buf, size); |
| 285 | } |
| 286 | |
| 287 | static CLASS_DEVICE_ATTR(delay, 0644, morse_delay_show, morse_delay_store); |
| 288 | static CLASS_DEVICE_ATTR(message, 0644, morse_msg_show, morse_msg_store); |
| 289 | |
| 290 | #define led_device_create_file(leddev, attr) \ |
| 291 | class_device_create_file(leddev->class_dev, &class_device_attr_ ## attr) |
| 292 | #define led_device_remove_file(leddev, attr) \ |
| 293 | class_device_remove_file(leddev->class_dev, &class_device_attr_ ## attr) |
| 294 | |
| 295 | #endif |
| 296 | |
| 297 | static void morse_trig_activate(struct led_classdev *led_cdev) |
| 298 | { |
| 299 | struct morse_trig_data *morse_data; |
| 300 | int rc; |
| 301 | |
| 302 | morse_data = kzalloc(sizeof(*morse_data), GFP_KERNEL); |
| 303 | if (!morse_data) |
| 304 | return; |
| 305 | |
| 306 | morse_data->delay = MORSE_DELAY_BASE; |
| 307 | init_timer(&morse_data->timer); |
| 308 | morse_data->timer.function = morse_timer_function; |
| 309 | morse_data->timer.data = (unsigned long)led_cdev; |
| 310 | |
| 311 | rc = led_device_create_file(led_cdev, delay); |
| 312 | if (rc) goto err; |
| 313 | |
| 314 | rc = led_device_create_file(led_cdev, message); |
| 315 | if (rc) goto err_delay; |
| 316 | |
| 317 | led_cdev->trigger_data = morse_data; |
| 318 | |
| 319 | return; |
| 320 | |
| 321 | err_delay: |
| 322 | led_device_remove_file(led_cdev, delay); |
| 323 | err: |
| 324 | kfree(morse_data); |
| 325 | } |
| 326 | |
| 327 | static void morse_trig_deactivate(struct led_classdev *led_cdev) |
| 328 | { |
| 329 | struct morse_trig_data *morse_data = led_cdev->trigger_data; |
| 330 | |
| 331 | if (!morse_data) |
| 332 | return; |
| 333 | |
| 334 | led_device_remove_file(led_cdev, message); |
| 335 | led_device_remove_file(led_cdev, delay); |
| 336 | |
| 337 | del_timer_sync(&morse_data->timer); |
| 338 | if (morse_data->msg) |
| 339 | kfree(morse_data->msg); |
| 340 | |
| 341 | kfree(morse_data); |
| 342 | } |
| 343 | |
| 344 | static struct led_trigger morse_led_trigger = { |
| 345 | .name = "morse", |
| 346 | .activate = morse_trig_activate, |
| 347 | .deactivate = morse_trig_deactivate, |
| 348 | }; |
| 349 | |
| 350 | static int __init morse_trig_init(void) |
| 351 | { |
| 352 | return led_trigger_register(&morse_led_trigger); |
| 353 | } |
| 354 | |
| 355 | static void __exit morse_trig_exit(void) |
| 356 | { |
| 357 | led_trigger_unregister(&morse_led_trigger); |
| 358 | } |
| 359 | |
| 360 | module_init(morse_trig_init); |
| 361 | module_exit(morse_trig_exit); |
| 362 | |
| 363 | MODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>"); |
| 364 | MODULE_DESCRIPTION("Morse LED trigger"); |
| 365 | MODULE_LICENSE("GPL"); |
| target/linux/generic-2.6/files-2.6.31/drivers/leds/ledtrig-netdev.c |
| 1 | /* |
| 2 | * LED Kernel Netdev Trigger |
| 3 | * |
| 4 | * Toggles the LED to reflect the link and traffic state of a named net device |
| 5 | * |
| 6 | * Copyright 2007 Oliver Jowett <oliver@opencloud.com> |
| 7 | * |
| 8 | * Derived from ledtrig-timer.c which is: |
| 9 | * Copyright 2005-2006 Openedhand Ltd. |
| 10 | * Author: Richard Purdie <rpurdie@openedhand.com> |
| 11 | * |
| 12 | * This program is free software; you can redistribute it and/or modify |
| 13 | * it under the terms of the GNU General Public License version 2 as |
| 14 | * published by the Free Software Foundation. |
| 15 | * |
| 16 | */ |
| 17 | |
| 18 | #include <linux/module.h> |
| 19 | #include <linux/jiffies.h> |
| 20 | #include <linux/kernel.h> |
| 21 | #include <linux/init.h> |
| 22 | #include <linux/list.h> |
| 23 | #include <linux/spinlock.h> |
| 24 | #include <linux/device.h> |
| 25 | #include <linux/sysdev.h> |
| 26 | #include <linux/netdevice.h> |
| 27 | #include <linux/timer.h> |
| 28 | #include <linux/ctype.h> |
| 29 | #include <linux/leds.h> |
| 30 | #include <linux/version.h> |
| 31 | |
| 32 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| 33 | #include <net/net_namespace.h> |
| 34 | #endif |
| 35 | |
| 36 | #include "leds.h" |
| 37 | |
| 38 | /* |
| 39 | * Configurable sysfs attributes: |
| 40 | * |
| 41 | * device_name - network device name to monitor |
| 42 | * |
| 43 | * interval - duration of LED blink, in milliseconds |
| 44 | * |
| 45 | * mode - either "none" (LED is off) or a space separated list of one or more of: |
| 46 | * link: LED's normal state reflects whether the link is up (has carrier) or not |
| 47 | * tx: LED blinks on transmitted data |
| 48 | * rx: LED blinks on receive data |
| 49 | * |
| 50 | * Some suggestions: |
| 51 | * |
| 52 | * Simple link status LED: |
| 53 | * $ echo netdev >someled/trigger |
| 54 | * $ echo eth0 >someled/device_name |
| 55 | * $ echo link >someled/mode |
| 56 | * |
| 57 | * Ethernet-style link/activity LED: |
| 58 | * $ echo netdev >someled/trigger |
| 59 | * $ echo eth0 >someled/device_name |
| 60 | * $ echo "link tx rx" >someled/mode |
| 61 | * |
| 62 | * Modem-style tx/rx LEDs: |
| 63 | * $ echo netdev >led1/trigger |
| 64 | * $ echo ppp0 >led1/device_name |
| 65 | * $ echo tx >led1/mode |
| 66 | * $ echo netdev >led2/trigger |
| 67 | * $ echo ppp0 >led2/device_name |
| 68 | * $ echo rx >led2/mode |
| 69 | * |
| 70 | */ |
| 71 | |
| 72 | #define MODE_LINK 1 |
| 73 | #define MODE_TX 2 |
| 74 | #define MODE_RX 4 |
| 75 | |
| 76 | struct led_netdev_data { |
| 77 | rwlock_t lock; |
| 78 | |
| 79 | struct timer_list timer; |
| 80 | struct notifier_block notifier; |
| 81 | |
| 82 | struct led_classdev *led_cdev; |
| 83 | struct net_device *net_dev; |
| 84 | |
| 85 | char device_name[IFNAMSIZ]; |
| 86 | unsigned interval; |
| 87 | unsigned mode; |
| 88 | unsigned link_up; |
| 89 | unsigned last_activity; |
| 90 | }; |
| 91 | |
| 92 | static void set_baseline_state(struct led_netdev_data *trigger_data) |
| 93 | { |
| 94 | if ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) |
| 95 | led_set_brightness(trigger_data->led_cdev, LED_FULL); |
| 96 | else |
| 97 | led_set_brightness(trigger_data->led_cdev, LED_OFF); |
| 98 | |
| 99 | if ((trigger_data->mode & (MODE_TX | MODE_RX)) != 0 && trigger_data->link_up) |
| 100 | mod_timer(&trigger_data->timer, jiffies + trigger_data->interval); |
| 101 | else |
| 102 | del_timer(&trigger_data->timer); |
| 103 | } |
| 104 | |
| 105 | static ssize_t led_device_name_show(struct device *dev, |
| 106 | struct device_attribute *attr, char *buf) |
| 107 | { |
| 108 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 109 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; |
| 110 | |
| 111 | read_lock(&trigger_data->lock); |
| 112 | sprintf(buf, "%s\n", trigger_data->device_name); |
| 113 | read_unlock(&trigger_data->lock); |
| 114 | |
| 115 | return strlen(buf) + 1; |
| 116 | } |
| 117 | |
| 118 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21) |
| 119 | extern struct net init_net; |
| 120 | #endif |
| 121 | |
| 122 | static ssize_t led_device_name_store(struct device *dev, |
| 123 | struct device_attribute *attr, const char *buf, size_t size) |
| 124 | { |
| 125 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 126 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; |
| 127 | |
| 128 | if (size < 0 || size >= IFNAMSIZ) |
| 129 | return -EINVAL; |
| 130 | |
| 131 | write_lock(&trigger_data->lock); |
| 132 | |
| 133 | strcpy(trigger_data->device_name, buf); |
| 134 | if (size > 0 && trigger_data->device_name[size-1] == '\n') |
| 135 | trigger_data->device_name[size-1] = 0; |
| 136 | |
| 137 | if (trigger_data->device_name[0] != 0) { |
| 138 | /* check for existing device to update from */ |
| 139 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) |
| 140 | trigger_data->net_dev = dev_get_by_name(&init_net, trigger_data->device_name); |
| 141 | #else |
| 142 | trigger_data->net_dev = dev_get_by_name(trigger_data->device_name); |
| 143 | #endif |
| 144 | if (trigger_data->net_dev != NULL) |
| 145 | trigger_data->link_up = (dev_get_flags(trigger_data->net_dev) & IFF_LOWER_UP) != 0; |
| 146 | set_baseline_state(trigger_data); /* updates LEDs, may start timers */ |
| 147 | } |
| 148 | |
| 149 | write_unlock(&trigger_data->lock); |
| 150 | return size; |
| 151 | } |
| 152 | |
| 153 | static DEVICE_ATTR(device_name, 0644, led_device_name_show, led_device_name_store); |
| 154 | |
| 155 | static ssize_t led_mode_show(struct device *dev, |
| 156 | struct device_attribute *attr, char *buf) |
| 157 | { |
| 158 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 159 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; |
| 160 | |
| 161 | read_lock(&trigger_data->lock); |
| 162 | |
| 163 | if (trigger_data->mode == 0) { |
| 164 | strcpy(buf, "none\n"); |
| 165 | } else { |
| 166 | if (trigger_data->mode & MODE_LINK) |
| 167 | strcat(buf, "link "); |
| 168 | if (trigger_data->mode & MODE_TX) |
| 169 | strcat(buf, "tx "); |
| 170 | if (trigger_data->mode & MODE_RX) |
| 171 | strcat(buf, "rx "); |
| 172 | strcat(buf, "\n"); |
| 173 | } |
| 174 | |
| 175 | read_unlock(&trigger_data->lock); |
| 176 | |
| 177 | return strlen(buf)+1; |
| 178 | } |
| 179 | |
| 180 | static ssize_t led_mode_store(struct device *dev, |
| 181 | struct device_attribute *attr, const char *buf, size_t size) |
| 182 | { |
| 183 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 184 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; |
| 185 | char copybuf[1024]; |
| 186 | int new_mode = -1; |
| 187 | char *p, *token; |
| 188 | |
| 189 | /* take a copy since we don't want to trash the inbound buffer when using strsep */ |
| 190 | strncpy(copybuf, buf, sizeof(copybuf)); |
| 191 | copybuf[1023] = 0; |
| 192 | p = copybuf; |
| 193 | |
| 194 | while ((token = strsep(&p, " \t\n")) != NULL) { |
| 195 | if (!*token) |
| 196 | continue; |
| 197 | |
| 198 | if (new_mode == -1) |
| 199 | new_mode = 0; |
| 200 | |
| 201 | if (!strcmp(token, "none")) |
| 202 | new_mode = 0; |
| 203 | else if (!strcmp(token, "tx")) |
| 204 | new_mode |= MODE_TX; |
| 205 | else if (!strcmp(token, "rx")) |
| 206 | new_mode |= MODE_RX; |
| 207 | else if (!strcmp(token, "link")) |
| 208 | new_mode |= MODE_LINK; |
| 209 | else |
| 210 | return -EINVAL; |
| 211 | } |
| 212 | |
| 213 | if (new_mode == -1) |
| 214 | return -EINVAL; |
| 215 | |
| 216 | write_lock(&trigger_data->lock); |
| 217 | trigger_data->mode = new_mode; |
| 218 | set_baseline_state(trigger_data); |
| 219 | write_unlock(&trigger_data->lock); |
| 220 | |
| 221 | return size; |
| 222 | } |
| 223 | |
| 224 | static DEVICE_ATTR(mode, 0644, led_mode_show, led_mode_store); |
| 225 | |
| 226 | static ssize_t led_interval_show(struct device *dev, |
| 227 | struct device_attribute *attr, char *buf) |
| 228 | { |
| 229 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 230 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; |
| 231 | |
| 232 | read_lock(&trigger_data->lock); |
| 233 | sprintf(buf, "%u\n", jiffies_to_msecs(trigger_data->interval)); |
| 234 | read_unlock(&trigger_data->lock); |
| 235 | |
| 236 | return strlen(buf) + 1; |
| 237 | } |
| 238 | |
| 239 | static ssize_t led_interval_store(struct device *dev, |
| 240 | struct device_attribute *attr, const char *buf, size_t size) |
| 241 | { |
| 242 | struct led_classdev *led_cdev = dev_get_drvdata(dev); |
| 243 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; |
| 244 | int ret = -EINVAL; |
| 245 | char *after; |
| 246 | unsigned long value = simple_strtoul(buf, &after, 10); |
| 247 | size_t count = after - buf; |
| 248 | |
| 249 | if (*after && isspace(*after)) |
| 250 | count++; |
| 251 | |
| 252 | /* impose some basic bounds on the timer interval */ |
| 253 | if (count == size && value >= 5 && value <= 10000) { |
| 254 | write_lock(&trigger_data->lock); |
| 255 | trigger_data->interval = msecs_to_jiffies(value); |
| 256 | set_baseline_state(trigger_data); // resets timer |
| 257 | write_unlock(&trigger_data->lock); |
| 258 | ret = count; |
| 259 | } |
| 260 | |
| 261 | return ret; |
| 262 | } |
| 263 | |
| 264 | static DEVICE_ATTR(interval, 0644, led_interval_show, led_interval_store); |
| 265 | |
| 266 | static int netdev_trig_notify(struct notifier_block *nb, |
| 267 | unsigned long evt, |
| 268 | void *dv) |
| 269 | { |
| 270 | struct net_device *dev = dv; |
| 271 | struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); |
| 272 | |
| 273 | if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER) |
| 274 | return NOTIFY_DONE; |
| 275 | |
| 276 | write_lock(&trigger_data->lock); |
| 277 | |
| 278 | if (strcmp(dev->name, trigger_data->device_name)) |
| 279 | goto done; |
| 280 | |
| 281 | if (evt == NETDEV_REGISTER) { |
| 282 | if (trigger_data->net_dev != NULL) |
| 283 | dev_put(trigger_data->net_dev); |
| 284 | dev_hold(dev); |
| 285 | trigger_data->net_dev = dev; |
| 286 | trigger_data->link_up = 0; |
| 287 | goto done; |
| 288 | } |
| 289 | |
| 290 | if (evt == NETDEV_UNREGISTER && trigger_data->net_dev != NULL) { |
| 291 | dev_put(trigger_data->net_dev); |
| 292 | trigger_data->net_dev = NULL; |
| 293 | goto done; |
| 294 | } |
| 295 | |
| 296 | /* UP / DOWN / CHANGE */ |
| 297 | |
| 298 | trigger_data->link_up = (evt != NETDEV_DOWN && netif_carrier_ok(dev)); |
| 299 | set_baseline_state(trigger_data); |
| 300 | |
| 301 | done: |
| 302 | write_unlock(&trigger_data->lock); |
| 303 | return NOTIFY_DONE; |
| 304 | } |
| 305 | |
| 306 | /* here's the real work! */ |
| 307 | static void netdev_trig_timer(unsigned long arg) |
| 308 | { |
| 309 | struct led_netdev_data *trigger_data = (struct led_netdev_data *)arg; |
| 310 | struct net_device_stats *dev_stats; |
| 311 | unsigned new_activity; |
| 312 | |
| 313 | write_lock(&trigger_data->lock); |
| 314 | |
| 315 | if (!trigger_data->link_up || !trigger_data->net_dev || (trigger_data->mode & (MODE_TX | MODE_RX)) == 0) { |
| 316 | /* we don't need to do timer work, just reflect link state. */ |
| 317 | led_set_brightness(trigger_data->led_cdev, ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) ? LED_FULL : LED_OFF); |
| 318 | goto no_restart; |
| 319 | } |
| 320 | #ifdef CONFIG_COMPAT_NET_DEV_OPS |
| 321 | dev_stats = trigger_data->net_dev->get_stats(trigger_data->net_dev); |
| 322 | #else |
| 323 | dev_stats = trigger_data->net_dev->netdev_ops->ndo_get_stats(trigger_data->net_dev); |
| 324 | #endif |
| 325 | new_activity = |
| 326 | ((trigger_data->mode & MODE_TX) ? dev_stats->tx_packets : 0) + |
| 327 | ((trigger_data->mode & MODE_RX) ? dev_stats->rx_packets : 0); |
| 328 | |
| 329 | if (trigger_data->mode & MODE_LINK) { |
| 330 | /* base state is ON (link present) */ |
| 331 | /* if there's no link, we don't get this far and the LED is off */ |
| 332 | |
| 333 | /* OFF -> ON always */ |
| 334 | /* ON -> OFF on activity */ |
| 335 | if (trigger_data->led_cdev->brightness == LED_OFF) { |
| 336 | led_set_brightness(trigger_data->led_cdev, LED_FULL); |
| 337 | } else if (trigger_data->last_activity != new_activity) { |
| 338 | led_set_brightness(trigger_data->led_cdev, LED_OFF); |
| 339 | } |
| 340 | } else { |
| 341 | /* base state is OFF */ |
| 342 | /* ON -> OFF always */ |
| 343 | /* OFF -> ON on activity */ |
| 344 | if (trigger_data->led_cdev->brightness == LED_FULL) { |
| 345 | led_set_brightness(trigger_data->led_cdev, LED_OFF); |
| 346 | } else if (trigger_data->last_activity != new_activity) { |
| 347 | led_set_brightness(trigger_data->led_cdev, LED_FULL); |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | trigger_data->last_activity = new_activity; |
| 352 | mod_timer(&trigger_data->timer, jiffies + trigger_data->interval); |
| 353 | |
| 354 | no_restart: |
| 355 | write_unlock(&trigger_data->lock); |
| 356 | } |
| 357 | |
| 358 | static void netdev_trig_activate(struct led_classdev *led_cdev) |
| 359 | { |
| 360 | struct led_netdev_data *trigger_data; |
| 361 | int rc; |
| 362 | |
| 363 | trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); |
| 364 | if (!trigger_data) |
| 365 | return; |
| 366 | |
| 367 | rwlock_init(&trigger_data->lock); |
| 368 | |
| 369 | trigger_data->notifier.notifier_call = netdev_trig_notify; |
| 370 | trigger_data->notifier.priority = 10; |
| 371 | |
| 372 | setup_timer(&trigger_data->timer, netdev_trig_timer, (unsigned long) trigger_data); |
| 373 | |
| 374 | trigger_data->led_cdev = led_cdev; |
| 375 | trigger_data->net_dev = NULL; |
| 376 | trigger_data->device_name[0] = 0; |
| 377 | |
| 378 | trigger_data->mode = 0; |
| 379 | trigger_data->interval = msecs_to_jiffies(50); |
| 380 | trigger_data->link_up = 0; |
| 381 | trigger_data->last_activity = 0; |
| 382 | |
| 383 | led_cdev->trigger_data = trigger_data; |
| 384 | |
| 385 | rc = device_create_file(led_cdev->dev, &dev_attr_device_name); |
| 386 | if (rc) |
| 387 | goto err_out; |
| 388 | rc = device_create_file(led_cdev->dev, &dev_attr_mode); |
| 389 | if (rc) |
| 390 | goto err_out_device_name; |
| 391 | rc = device_create_file(led_cdev->dev, &dev_attr_interval); |
| 392 | if (rc) |
| 393 | goto err_out_mode; |
| 394 | |
| 395 | register_netdevice_notifier(&trigger_data->notifier); |
| 396 | return; |
| 397 | |
| 398 | err_out_mode: |
| 399 | device_remove_file(led_cdev->dev, &dev_attr_mode); |
| 400 | err_out_device_name: |
| 401 | device_remove_file(led_cdev->dev, &dev_attr_device_name); |
| 402 | err_out: |
| 403 | led_cdev->trigger_data = NULL; |
| 404 | kfree(trigger_data); |
| 405 | } |
| 406 | |
| 407 | static void netdev_trig_deactivate(struct led_classdev *led_cdev) |
| 408 | { |
| 409 | struct led_netdev_data *trigger_data = led_cdev->trigger_data; |
| 410 | |
| 411 | if (trigger_data) { |
| 412 | unregister_netdevice_notifier(&trigger_data->notifier); |
| 413 | |
| 414 | device_remove_file(led_cdev->dev, &dev_attr_device_name); |
| 415 | device_remove_file(led_cdev->dev, &dev_attr_mode); |
| 416 | device_remove_file(led_cdev->dev, &dev_attr_interval); |
| 417 | |
| 418 | write_lock(&trigger_data->lock); |
| 419 | |
| 420 | if (trigger_data->net_dev) { |
| 421 | dev_put(trigger_data->net_dev); |
| 422 | trigger_data->net_dev = NULL; |
| 423 | } |
| 424 | |
| 425 | write_unlock(&trigger_data->lock); |
| 426 | |
| 427 | del_timer_sync(&trigger_data->timer); |
| 428 | |
| 429 | kfree(trigger_data); |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | static struct led_trigger netdev_led_trigger = { |
| 434 | .name = "netdev", |
| 435 | .activate = netdev_trig_activate, |
| 436 | .deactivate = netdev_trig_deactivate, |
| 437 | }; |
| 438 | |
| 439 | static int __init netdev_trig_init(void) |
| 440 | { |
| 441 | return led_trigger_register(&netdev_led_trigger); |
| 442 | } |
| 443 | |
| 444 | static void __exit netdev_trig_exit(void) |
| 445 | { |
| 446 | led_trigger_unregister(&netdev_led_trigger); |
| 447 | } |
| 448 | |
| 449 | module_init(netdev_trig_init); |
| 450 | module_exit(netdev_trig_exit); |
| 451 | |
| 452 | MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>"); |
| 453 | MODULE_DESCRIPTION("Netdev LED trigger"); |
| 454 | MODULE_LICENSE("GPL"); |
| target/linux/generic-2.6/files-2.6.31/drivers/net/phy/ar8216.c |
| 1 | /* |
| 2 | * ar8216.c: AR8216 switch driver |
| 3 | * |
| 4 | * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU General Public License |
| 8 | * as published by the Free Software Foundation; either version 2 |
| 9 | * of the License, or (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | */ |
| 16 | |
| 17 | #include <linux/if.h> |
| 18 | #include <linux/module.h> |
| 19 | #include <linux/init.h> |
| 20 | #include <linux/list.h> |
| 21 | #include <linux/if_ether.h> |
| 22 | #include <linux/skbuff.h> |
| 23 | #include <linux/netdevice.h> |
| 24 | #include <linux/netlink.h> |
| 25 | #include <linux/bitops.h> |
| 26 | #include <net/genetlink.h> |
| 27 | #include <linux/switch.h> |
| 28 | #include <linux/delay.h> |
| 29 | #include <linux/phy.h> |
| 30 | #include <linux/netdevice.h> |
| 31 | #include <linux/etherdevice.h> |
| 32 | #include "ar8216.h" |
| 33 | |
| 34 | |
| 35 | struct ar8216_priv { |
| 36 | struct switch_dev dev; |
| 37 | struct phy_device *phy; |
| 38 | u32 (*read)(struct ar8216_priv *priv, int reg); |
| 39 | void (*write)(struct ar8216_priv *priv, int reg, u32 val); |
| 40 | const struct net_device_ops *ndo_old; |
| 41 | struct net_device_ops ndo; |
| 42 | |
| 43 | /* all fields below are cleared on reset */ |
| 44 | bool vlan; |
| 45 | u8 vlan_id[AR8216_NUM_VLANS]; |
| 46 | u8 vlan_table[AR8216_NUM_VLANS]; |
| 47 | u8 vlan_tagged; |
| 48 | u16 pvid[AR8216_NUM_PORTS]; |
| 49 | }; |
| 50 | static struct switch_dev athdev; |
| 51 | |
| 52 | #define to_ar8216(_dev) container_of(_dev, struct ar8216_priv, dev) |
| 53 | |
| 54 | static inline void |
| 55 | split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) |
| 56 | { |
| 57 | regaddr >>= 1; |
| 58 | *r1 = regaddr & 0x1e; |
| 59 | |
| 60 | regaddr >>= 5; |
| 61 | *r2 = regaddr & 0x7; |
| 62 | |
| 63 | regaddr >>= 3; |
| 64 | *page = regaddr & 0x1ff; |
| 65 | } |
| 66 | |
| 67 | static u32 |
| 68 | ar8216_mii_read(struct ar8216_priv *priv, int reg) |
| 69 | { |
| 70 | struct phy_device *phy = priv->phy; |
| 71 | u16 r1, r2, page; |
| 72 | u16 lo, hi; |
| 73 | |
| 74 | split_addr((u32) reg, &r1, &r2, &page); |
| 75 | phy->bus->write(phy->bus, 0x18, 0, page); |
| 76 | msleep(1); /* wait for the page switch to propagate */ |
| 77 | lo = phy->bus->read(phy->bus, 0x10 | r2, r1); |
| 78 | hi = phy->bus->read(phy->bus, 0x10 | r2, r1 + 1); |
| 79 | |
| 80 | return (hi << 16) | lo; |
| 81 | } |
| 82 | |
| 83 | static void |
| 84 | ar8216_mii_write(struct ar8216_priv *priv, int reg, u32 val) |
| 85 | { |
| 86 | struct phy_device *phy = priv->phy; |
| 87 | u16 r1, r2, r3; |
| 88 | u16 lo, hi; |
| 89 | |
| 90 | split_addr((u32) reg, &r1, &r2, &r3); |
| 91 | phy->bus->write(phy->bus, 0x18, 0, r3); |
| 92 | msleep(1); /* wait for the page switch to propagate */ |
| 93 | |
| 94 | lo = val & 0xffff; |
| 95 | hi = (u16) (val >> 16); |
| 96 | phy->bus->write(phy->bus, 0x10 | r2, r1 + 1, hi); |
| 97 | phy->bus->write(phy->bus, 0x10 | r2, r1, lo); |
| 98 | } |
| 99 | |
| 100 | static u32 |
| 101 | ar8216_rmw(struct ar8216_priv *priv, int reg, u32 mask, u32 val) |
| 102 | { |
| 103 | u32 v; |
| 104 | |
| 105 | v = priv->read(priv, reg); |
| 106 | v &= ~mask; |
| 107 | v |= val; |
| 108 | priv->write(priv, reg, v); |
| 109 | |
| 110 | return v; |
| 111 | } |
| 112 | |
| 113 | static int |
| 114 | ar8216_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
| 115 | struct switch_val *val) |
| 116 | { |
| 117 | struct ar8216_priv *priv = to_ar8216(dev); |
| 118 | priv->vlan = !!val->value.i; |
| 119 | return 0; |
| 120 | } |
| 121 | |
| 122 | static int |
| 123 | ar8216_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
| 124 | struct switch_val *val) |
| 125 | { |
| 126 | struct ar8216_priv *priv = to_ar8216(dev); |
| 127 | val->value.i = priv->vlan; |
| 128 | return 0; |
| 129 | } |
| 130 | |
| 131 | |
| 132 | static int |
| 133 | ar8216_set_pvid(struct switch_dev *dev, int port, int vlan) |
| 134 | { |
| 135 | struct ar8216_priv *priv = to_ar8216(dev); |
| 136 | priv->pvid[port] = vlan; |
| 137 | return 0; |
| 138 | } |
| 139 | |
| 140 | static int |
| 141 | ar8216_get_pvid(struct switch_dev *dev, int port, int *vlan) |
| 142 | { |
| 143 | struct ar8216_priv *priv = to_ar8216(dev); |
| 144 | *vlan = priv->pvid[port]; |
| 145 | return 0; |
| 146 | } |
| 147 | |
| 148 | static int |
| 149 | ar8216_set_vid(struct switch_dev *dev, const struct switch_attr *attr, |
| 150 | struct switch_val *val) |
| 151 | { |
| 152 | struct ar8216_priv *priv = to_ar8216(dev); |
| 153 | priv->vlan_id[val->port_vlan] = val->value.i; |
| 154 | return 0; |
| 155 | } |
| 156 | |
| 157 | static int |
| 158 | ar8216_get_vid(struct switch_dev *dev, const struct switch_attr *attr, |
| 159 | struct switch_val *val) |
| 160 | { |
| 161 | struct ar8216_priv *priv = to_ar8216(dev); |
| 162 | val->value.i = priv->vlan_id[val->port_vlan]; |
| 163 | return 0; |
| 164 | } |
| 165 | |
| 166 | |
| 167 | static int |
| 168 | ar8216_mangle_tx(struct sk_buff *skb, struct net_device *dev) |
| 169 | { |
| 170 | struct ar8216_priv *priv = dev->phy_ptr; |
| 171 | unsigned char *buf; |
| 172 | |
| 173 | if (unlikely(!priv)) |
| 174 | goto error; |
| 175 | |
| 176 | if (!priv->vlan) |
| 177 | goto send; |
| 178 | |
| 179 | if (unlikely(skb_headroom(skb) < 2)) { |
| 180 | if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) |
| 181 | goto error; |
| 182 | } |
| 183 | |
| 184 | buf = skb_push(skb, 2); |
| 185 | buf[0] = 0x10; |
| 186 | buf[1] = 0x80; |
| 187 | |
| 188 | send: |
| 189 | return priv->ndo_old->ndo_start_xmit(skb, dev); |
| 190 | |
| 191 | error: |
| 192 | dev_kfree_skb_any(skb); |
| 193 | return 0; |
| 194 | } |
| 195 | |
| 196 | static int |
| 197 | ar8216_mangle_rx(struct sk_buff *skb, int napi) |
| 198 | { |
| 199 | struct ar8216_priv *priv; |
| 200 | struct net_device *dev; |
| 201 | unsigned char *buf; |
| 202 | int port, vlan; |
| 203 | |
| 204 | dev = skb->dev; |
| 205 | if (!dev) |
| 206 | goto error; |
| 207 | |
| 208 | priv = dev->phy_ptr; |
| 209 | if (!priv) |
| 210 | goto error; |
| 211 | |
| 212 | /* don't strip the header if vlan mode is disabled */ |
| 213 | if (!priv->vlan) |
| 214 | goto recv; |
| 215 | |
| 216 | /* strip header, get vlan id */ |
| 217 | buf = skb->data; |
| 218 | skb_pull(skb, 2); |
| 219 | |
| 220 | /* check for vlan header presence */ |
| 221 | if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) |
| 222 | goto recv; |
| 223 | |
| 224 | port = buf[0] & 0xf; |
| 225 | |
| 226 | /* no need to fix up packets coming from a tagged source */ |
| 227 | if (priv->vlan_tagged & (1 << port)) |
| 228 | goto recv; |
| 229 | |
| 230 | /* lookup port vid from local table, the switch passes an invalid vlan id */ |
| 231 | vlan = priv->pvid[port]; |
| 232 | |
| 233 | buf[14 + 2] &= 0xf0; |
| 234 | buf[14 + 2] |= vlan >> 8; |
| 235 | buf[15 + 2] = vlan & 0xff; |
| 236 | |
| 237 | recv: |
| 238 | skb->protocol = eth_type_trans(skb, skb->dev); |
| 239 | |
| 240 | if (napi) |
| 241 | return netif_receive_skb(skb); |
| 242 | else |
| 243 | return netif_rx(skb); |
| 244 | |
| 245 | error: |
| 246 | /* no vlan? eat the packet! */ |
| 247 | dev_kfree_skb_any(skb); |
| 248 | return 0; |
| 249 | } |
| 250 | |
| 251 | static int |
| 252 | ar8216_netif_rx(struct sk_buff *skb) |
| 253 | { |
| 254 | return ar8216_mangle_rx(skb, 0); |
| 255 | } |
| 256 | |
| 257 | static int |
| 258 | ar8216_netif_receive_skb(struct sk_buff *skb) |
| 259 | { |
| 260 | return ar8216_mangle_rx(skb, 1); |
| 261 | } |
| 262 | |
| 263 | |
| 264 | static struct switch_attr ar8216_globals[] = { |
| 265 | { |
| 266 | .type = SWITCH_TYPE_INT, |
| 267 | .name = "enable_vlan", |
| 268 | .description = "Enable VLAN mode", |
| 269 | .set = ar8216_set_vlan, |
| 270 | .get = ar8216_get_vlan, |
| 271 | .max = 1 |
| 272 | }, |
| 273 | }; |
| 274 | |
| 275 | static struct switch_attr ar8216_port[] = { |
| 276 | }; |
| 277 | |
| 278 | static struct switch_attr ar8216_vlan[] = { |
| 279 | { |
| 280 | .type = SWITCH_TYPE_INT, |
| 281 | .name = "pvid", |
| 282 | .description = "VLAN ID", |
| 283 | .set = ar8216_set_vid, |
| 284 | .get = ar8216_get_vid, |
| 285 | .max = 4095, |
| 286 | }, |
| 287 | }; |
| 288 | |
| 289 | |
| 290 | static int |
| 291 | ar8216_get_ports(struct switch_dev *dev, struct switch_val *val) |
| 292 | { |
| 293 | struct ar8216_priv *priv = to_ar8216(dev); |
| 294 | u8 ports = priv->vlan_table[val->port_vlan]; |
| 295 | int i; |
| 296 | |
| 297 | val->len = 0; |
| 298 | for (i = 0; i < AR8216_NUM_PORTS; i++) { |
| 299 | struct switch_port *p; |
| 300 | |
| 301 | if (!(ports & (1 << i))) |
| 302 | continue; |
| 303 | |
| 304 | p = &val->value.ports[val->len++]; |
| 305 | p->id = i; |
| 306 | if (priv->vlan_tagged & (1 << i)) |
| 307 | p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); |
| 308 | else |
| 309 | p->flags = 0; |
| 310 | } |
| 311 | return 0; |
| 312 | } |
| 313 | |
| 314 | static int |
| 315 | ar8216_set_ports(struct switch_dev *dev, struct switch_val *val) |
| 316 | { |
| 317 | struct ar8216_priv *priv = to_ar8216(dev); |
| 318 | u8 *vt = &priv->vlan_table[val->port_vlan]; |
| 319 | int i, j; |
| 320 | |
| 321 | *vt = 0; |
| 322 | for (i = 0; i < val->len; i++) { |
| 323 | struct switch_port *p = &val->value.ports[i]; |
| 324 | |
| 325 | if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) |
| 326 | priv->vlan_tagged |= (1 << p->id); |
| 327 | else { |
| 328 | priv->vlan_tagged &= ~(1 << p->id); |
| 329 | priv->pvid[p->id] = val->port_vlan; |
| 330 | |
| 331 | /* make sure that an untagged port does not |
| 332 | * appear in other vlans */ |
| 333 | for (j = 0; j < AR8216_NUM_VLANS; j++) { |
| 334 | if (j == val->port_vlan) |
| 335 | continue; |
| 336 | priv->vlan_table[j] &= ~(1 << p->id); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | *vt |= 1 << p->id; |
| 341 | } |
| 342 | return 0; |
| 343 | } |
| 344 | |
| 345 | static int |
| 346 | ar8216_wait_bit(struct ar8216_priv *priv, int reg, u32 mask, u32 val) |
| 347 | { |
| 348 | int timeout = 20; |
| 349 | |
| 350 | while ((priv->read(priv, reg) & mask) != val) { |
| 351 | if (timeout-- <= 0) { |
| 352 | printk(KERN_ERR "ar8216: timeout waiting for operation to complete\n"); |
| 353 | return 1; |
| 354 | } |
| 355 | } |
| 356 | return 0; |
| 357 | } |
| 358 | |
| 359 | static void |
| 360 | ar8216_vtu_op(struct ar8216_priv *priv, u32 op, u32 val) |
| 361 | { |
| 362 | if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) |
| 363 | return; |
| 364 | if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { |
| 365 | val &= AR8216_VTUDATA_MEMBER; |
| 366 | val |= AR8216_VTUDATA_VALID; |
| 367 | priv->write(priv, AR8216_REG_VTU_DATA, val); |
| 368 | } |
| 369 | op |= AR8216_VTU_ACTIVE; |
| 370 | priv->write(priv, AR8216_REG_VTU, op); |
| 371 | } |
| 372 | |
| 373 | static int |
| 374 | ar8216_hw_apply(struct switch_dev *dev) |
| 375 | { |
| 376 | struct ar8216_priv *priv = to_ar8216(dev); |
| 377 | u8 portmask[AR8216_NUM_PORTS]; |
| 378 | int i, j; |
| 379 | |
| 380 | /* flush all vlan translation unit entries */ |
| 381 | ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); |
| 382 | |
| 383 | memset(portmask, 0, sizeof(portmask)); |
| 384 | if (priv->vlan) { |
| 385 | /* calculate the port destination masks and load vlans |
| 386 | * into the vlan translation unit */ |
| 387 | for (j = 0; j < AR8216_NUM_VLANS; j++) { |
| 388 | u8 vp = priv->vlan_table[j]; |
| 389 | |
| 390 | if (!vp) |
| 391 | continue; |
| 392 | |
| 393 | for (i = 0; i < AR8216_NUM_PORTS; i++) { |
| 394 | u8 mask = (1 << i); |
| 395 | if (vp & mask) |
| 396 | portmask[i] |= vp & ~mask; |
| 397 | } |
| 398 | |
| 399 | if (!priv->vlan_table[j]) |
| 400 | continue; |
| 401 | |
| 402 | ar8216_vtu_op(priv, |
| 403 | AR8216_VTU_OP_LOAD | |
| 404 | (priv->vlan_id[j] << AR8216_VTU_VID_S), |
| 405 | priv->vlan_table[j]); |
| 406 | } |
| 407 | } else { |
| 408 | /* vlan disabled: |
| 409 | * isolate all ports, but connect them to the cpu port */ |
| 410 | for (i = 0; i < AR8216_NUM_PORTS; i++) { |
| 411 | if (i == AR8216_PORT_CPU) |
| 412 | continue; |
| 413 | |
| 414 | portmask[i] = 1 << AR8216_PORT_CPU; |
| 415 | portmask[AR8216_PORT_CPU] |= (1 << i); |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | /* update the port destination mask registers and tag settings */ |
| 420 | for (i = 0; i < AR8216_NUM_PORTS; i++) { |
| 421 | int egress, ingress; |
| 422 | int pvid; |
| 423 | |
| 424 | if (priv->vlan) { |
| 425 | pvid = priv->vlan_id[priv->pvid[i]]; |
| 426 | } else { |
| 427 | pvid = i; |
| 428 | } |
| 429 | |
| 430 | if (priv->vlan && (priv->vlan_tagged & (1 << i))) { |
| 431 | egress = AR8216_OUT_ADD_VLAN; |
| 432 | } else { |
| 433 | egress = AR8216_OUT_STRIP_VLAN; |
| 434 | } |
| 435 | ingress = AR8216_IN_SECURE; |
| 436 | |
| 437 | ar8216_rmw(priv, AR8216_REG_PORT_CTRL(i), |
| 438 | AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | |
| 439 | AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | |
| 440 | AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, |
| 441 | AR8216_PORT_CTRL_LEARN | |
| 442 | (priv->vlan && i == AR8216_PORT_CPU ? |
| 443 | AR8216_PORT_CTRL_HEADER : 0) | |
| 444 | (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | |
| 445 | (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); |
| 446 | |
| 447 | ar8216_rmw(priv, AR8216_REG_PORT_VLAN(i), |
| 448 | AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | |
| 449 | AR8216_PORT_VLAN_DEFAULT_ID, |
| 450 | (portmask[i] << AR8216_PORT_VLAN_DEST_PORTS_S) | |
| 451 | (ingress << AR8216_PORT_VLAN_MODE_S) | |
| 452 | (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); |
| 453 | } |
| 454 | |
| 455 | return 0; |
| 456 | } |
| 457 | |
| 458 | static int |
| 459 | ar8216_reset_switch(struct switch_dev *dev) |
| 460 | { |
| 461 | struct ar8216_priv *priv = to_ar8216(dev); |
| 462 | int i; |
| 463 | |
| 464 | memset(&priv->vlan, 0, sizeof(struct ar8216_priv) - |
| 465 | offsetof(struct ar8216_priv, vlan)); |
| 466 | for (i = 0; i < AR8216_NUM_VLANS; i++) { |
| 467 | priv->vlan_id[i] = i; |
| 468 | } |
| 469 | for (i = 0; i < AR8216_NUM_PORTS; i++) { |
| 470 | /* Enable port learning and tx */ |
| 471 | priv->write(priv, AR8216_REG_PORT_CTRL(i), |
| 472 | AR8216_PORT_CTRL_LEARN | |
| 473 | (4 << AR8216_PORT_CTRL_STATE_S)); |
| 474 | |
| 475 | priv->write(priv, AR8216_REG_PORT_VLAN(i), 0); |
| 476 | |
| 477 | /* Configure all PHYs */ |
| 478 | if (i == AR8216_PORT_CPU) { |
| 479 | priv->write(priv, AR8216_REG_PORT_STATUS(i), |
| 480 | AR8216_PORT_STATUS_LINK_UP | |
| 481 | AR8216_PORT_STATUS_SPEED | |
| 482 | AR8216_PORT_STATUS_TXMAC | |
| 483 | AR8216_PORT_STATUS_RXMAC | |
| 484 | AR8216_PORT_STATUS_DUPLEX); |
| 485 | } else { |
| 486 | priv->write(priv, AR8216_REG_PORT_STATUS(i), |
| 487 | AR8216_PORT_STATUS_LINK_AUTO); |
| 488 | } |
| 489 | } |
| 490 | /* XXX: undocumented magic from atheros, required! */ |
| 491 | priv->write(priv, 0x38, 0xc000050e); |
| 492 | |
| 493 | ar8216_rmw(priv, AR8216_REG_GLOBAL_CTRL, |
| 494 | AR8216_GCTRL_MTU, 1518 + 8 + 2); |
| 495 | |
| 496 | return ar8216_hw_apply(dev); |
| 497 | } |
| 498 | |
| 499 | static int |
| 500 | ar8216_config_init(struct phy_device *pdev) |
| 501 | { |
| 502 | struct ar8216_priv *priv; |
| 503 | struct net_device *dev = pdev->attached_dev; |
| 504 | int ret; |
| 505 | |
| 506 | printk("%s: AR8216 PHY driver attached.\n", pdev->attached_dev->name); |
| 507 | pdev->supported = ADVERTISED_100baseT_Full; |
| 508 | pdev->advertising = ADVERTISED_100baseT_Full; |
| 509 | |
| 510 | priv = kzalloc(sizeof(struct ar8216_priv), GFP_KERNEL); |
| 511 | if (priv == NULL) |
| 512 | return -ENOMEM; |
| 513 | |
| 514 | priv->phy = pdev; |
| 515 | priv->read = ar8216_mii_read; |
| 516 | priv->write = ar8216_mii_write; |
| 517 | memcpy(&priv->dev, &athdev, sizeof(struct switch_dev)); |
| 518 | pdev->priv = priv; |
| 519 | if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { |
| 520 | kfree(priv); |
| 521 | goto done; |
| 522 | } |
| 523 | |
| 524 | ret = ar8216_reset_switch(&priv->dev); |
| 525 | if (ret) |
| 526 | goto done; |
| 527 | |
| 528 | dev->phy_ptr = priv; |
| 529 | pdev->pkt_align = 2; |
| 530 | pdev->netif_receive_skb = ar8216_netif_receive_skb; |
| 531 | pdev->netif_rx = ar8216_netif_rx; |
| 532 | |
| 533 | priv->ndo_old = dev->netdev_ops; |
| 534 | memcpy(&priv->ndo, priv->ndo_old, sizeof(struct net_device_ops)); |
| 535 | priv->ndo.ndo_start_xmit = ar8216_mangle_tx; |
| 536 | dev->netdev_ops = &priv->ndo; |
| 537 | |
| 538 | done: |
| 539 | return ret; |
| 540 | } |
| 541 | |
| 542 | static int |
| 543 | ar8216_read_status(struct phy_device *phydev) |
| 544 | { |
| 545 | struct ar8216_priv *priv = phydev->priv; |
| 546 | |
| 547 | phydev->speed = SPEED_100; |
| 548 | phydev->duplex = DUPLEX_FULL; |
| 549 | phydev->state = PHY_UP; |
| 550 | |
| 551 | /* flush the address translation unit */ |
| 552 | if (ar8216_wait_bit(priv, AR8216_REG_ATU, AR8216_ATU_ACTIVE, 0)) |
| 553 | return -ETIMEDOUT; |
| 554 | |
| 555 | priv->write(priv, AR8216_REG_ATU, AR8216_ATU_OP_FLUSH); |
| 556 | |
| 557 | return 0; |
| 558 | } |
| 559 | |
| 560 | static int |
| 561 | ar8216_config_aneg(struct phy_device *phydev) |
| 562 | { |
| 563 | return 0; |
| 564 | } |
| 565 | |
| 566 | static int |
| 567 | ar8216_probe(struct phy_device *pdev) |
| 568 | { |
| 569 | struct ar8216_priv priv; |
| 570 | |
| 571 | u8 id, rev; |
| 572 | u32 val; |
| 573 | |
| 574 | priv.phy = pdev; |
| 575 | val = ar8216_mii_read(&priv, AR8216_REG_CTRL); |
| 576 | rev = val & 0xff; |
| 577 | id = (val >> 8) & 0xff; |
| 578 | if ((id != 1) || (rev != 1)) |
| 579 | return -ENODEV; |
| 580 | |
| 581 | return 0; |
| 582 | } |
| 583 | |
| 584 | static void |
| 585 | ar8216_remove(struct phy_device *pdev) |
| 586 | { |
| 587 | struct ar8216_priv *priv = pdev->priv; |
| 588 | struct net_device *dev = pdev->attached_dev; |
| 589 | |
| 590 | if (!priv) |
| 591 | return; |
| 592 | |
| 593 | if (priv->ndo_old && dev) |
| 594 | dev->netdev_ops = priv->ndo_old; |
| 595 | unregister_switch(&priv->dev); |
| 596 | kfree(priv); |
| 597 | } |
| 598 | |
| 599 | /* template */ |
| 600 | static struct switch_dev athdev = { |
| 601 | .name = "Atheros AR8216", |
| 602 | .cpu_port = AR8216_PORT_CPU, |
| 603 | .ports = AR8216_NUM_PORTS, |
| 604 | .vlans = AR8216_NUM_VLANS, |
| 605 | .attr_global = { |
| 606 | .attr = ar8216_globals, |
| 607 | .n_attr = ARRAY_SIZE(ar8216_globals), |
| 608 | }, |
| 609 | .attr_port = { |
| 610 | .attr = ar8216_port, |
| 611 | .n_attr = ARRAY_SIZE(ar8216_port), |
| 612 | }, |
| 613 | .attr_vlan = { |
| 614 | .attr = ar8216_vlan, |
| 615 | .n_attr = ARRAY_SIZE(ar8216_vlan), |
| 616 | }, |
| 617 | .get_port_pvid = ar8216_get_pvid, |
| 618 | .set_port_pvid = ar8216_set_pvid, |
| 619 | .get_vlan_ports = ar8216_get_ports, |
| 620 | .set_vlan_ports = ar8216_set_ports, |
| 621 | .apply_config = ar8216_hw_apply, |
| 622 | .reset_switch = ar8216_reset_switch, |
| 623 | }; |
| 624 | |
| 625 | static struct phy_driver ar8216_driver = { |
| 626 | .name = "Atheros AR8216", |
| 627 | .features = PHY_BASIC_FEATURES, |
| 628 | .probe = ar8216_probe, |
| 629 | .remove = ar8216_remove, |
| 630 | .config_init = &ar8216_config_init, |
| 631 | .config_aneg = &ar8216_config_aneg, |
| 632 | .read_status = &ar8216_read_status, |
| 633 | .driver = { .owner = THIS_MODULE }, |
| 634 | }; |
| 635 | |
| 636 | int __init |
| 637 | ar8216_init(void) |
| 638 | { |
| 639 | return phy_driver_register(&ar8216_driver); |
| 640 | } |
| 641 | |
| 642 | void __exit |
| 643 | ar8216_exit(void) |
| 644 | { |
| 645 | phy_driver_unregister(&ar8216_driver); |
| 646 | } |
| 647 | |
| 648 | module_init(ar8216_init); |
| 649 | module_exit(ar8216_exit); |
| 650 | MODULE_LICENSE("GPL"); |
| 651 | |
| target/linux/generic-2.6/files-2.6.31/drivers/net/phy/mvswitch.c |
| 1 | /* |
| 2 | * Marvell 88E6060 switch driver |
| 3 | * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> |
| 4 | * |
| 5 | * This program is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License v2 as published by the |
| 7 | * Free Software Foundation |
| 8 | */ |
| 9 | #include <linux/kernel.h> |
| 10 | #include <linux/string.h> |
| 11 | #include <linux/errno.h> |
| 12 | #include <linux/unistd.h> |
| 13 | #include <linux/slab.h> |
| 14 | #include <linux/interrupt.h> |
| 15 | #include <linux/init.h> |
| 16 | #include <linux/delay.h> |
| 17 | #include <linux/netdevice.h> |
| 18 | #include <linux/etherdevice.h> |
| 19 | #include <linux/skbuff.h> |
| 20 | #include <linux/spinlock.h> |
| 21 | #include <linux/mm.h> |
| 22 | #include <linux/module.h> |
| 23 | #include <linux/mii.h> |
| 24 | #include <linux/ethtool.h> |
| 25 | #include <linux/phy.h> |
| 26 | #include <linux/if_vlan.h> |
| 27 | |
| 28 | #include <asm/io.h> |
| 29 | #include <asm/irq.h> |
| 30 | #include <asm/uaccess.h> |
| 31 | #include "mvswitch.h" |
| 32 | |
| 33 | /* Undefine this to use trailer mode instead. |
| 34 | * I don't know if header mode works with all chips */ |
| 35 | #define HEADER_MODE 1 |
| 36 | |
| 37 | MODULE_DESCRIPTION("Marvell 88E6060 Switch driver"); |
| 38 | MODULE_AUTHOR("Felix Fietkau"); |
| 39 | MODULE_LICENSE("GPL"); |
| 40 | |
| 41 | #define MVSWITCH_MAGIC 0x88E6060 |
| 42 | |
| 43 | struct mvswitch_priv { |
| 44 | const struct net_device_ops *ndo_old; |
| 45 | struct net_device_ops ndo; |
| 46 | struct vlan_group *grp; |
| 47 | u8 vlans[16]; |
| 48 | }; |
| 49 | |
| 50 | #define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv) |
| 51 | |
| 52 | static inline u16 |
| 53 | r16(struct phy_device *phydev, int addr, int reg) |
| 54 | { |
| 55 | return phydev->bus->read(phydev->bus, addr, reg); |
| 56 | } |
| 57 | |
| 58 | static inline void |
| 59 | w16(struct phy_device *phydev, int addr, int reg, u16 val) |
| 60 | { |
| 61 | phydev->bus->write(phydev->bus, addr, reg, val); |
| 62 | } |
| 63 | |
| 64 | |
| 65 | static int |
| 66 | mvswitch_mangle_tx(struct sk_buff *skb, struct net_device *dev) |
| 67 | { |
| 68 | struct mvswitch_priv *priv; |
| 69 | char *buf = NULL; |
| 70 | u16 vid; |
| 71 | |
| 72 | priv = dev->phy_ptr; |
| 73 | if (unlikely(!priv)) |
| 74 | goto error; |
| 75 | |
| 76 | if (unlikely(skb->len < 16)) |
| 77 | goto error; |
| 78 | |
| 79 | #ifdef HEADER_MODE |
| 80 | if (__vlan_hwaccel_get_tag(skb, &vid)) |
| 81 | goto error; |
| 82 | |
| 83 | if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) { |
| 84 | if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC)) |
| 85 | goto error_expand; |
| 86 | if (skb->len < 62) |
| 87 | skb->len = 62; |
| 88 | } |
| 89 | buf = skb_push(skb, MV_HEADER_SIZE); |
| 90 | #else |
| 91 | if (__vlan_get_tag(skb, &vid)) |
| 92 | goto error; |
| 93 | |
| 94 | if (unlikely((vid > 15 || !priv->vlans[vid]))) |
| 95 | goto error; |
| 96 | |
| 97 | if (skb->len <= 64) { |
| 98 | if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC)) |
| 99 | goto error_expand; |
| 100 | |
| 101 | buf = skb->data + 64; |
| 102 | skb->len = 64 + MV_TRAILER_SIZE; |
| 103 | } else { |
| 104 | if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) { |
| 105 | if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC)) |
| 106 | goto error_expand; |
| 107 | } |
| 108 | buf = skb_put(skb, 4); |
| 109 | } |
| 110 | |
| 111 | /* move the ethernet header 4 bytes forward, overwriting the vlan tag */ |
| 112 | memmove(skb->data + 4, skb->data, 12); |
| 113 | skb->data += 4; |
| 114 | skb->len -= 4; |
| 115 | skb->mac_header += 4; |
| 116 | #endif |
| 117 | |
| 118 | if (!buf) |
| 119 | goto error; |
| 120 | |
| 121 | |
| 122 | #ifdef HEADER_MODE |
| 123 | /* prepend the tag */ |
| 124 | *((__be16 *) buf) = cpu_to_be16( |
| 125 | ((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) | |
| 126 | ((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M) |
| 127 | ); |
| 128 | #else |
| 129 | /* append the tag */ |
| 130 | *((__be32 *) buf) = cpu_to_be32(( |
| 131 | (MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) | |
| 132 | ((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S) |
| 133 | )); |
| 134 | #endif |
| 135 | |
| 136 | return priv->ndo_old->ndo_start_xmit(skb, dev); |
| 137 | |
| 138 | error_expand: |
| 139 | if (net_ratelimit()) |
| 140 | printk("%s: failed to expand/update skb for the switch\n", dev->name); |
| 141 | |
| 142 | error: |
| 143 | /* any errors? drop the packet! */ |
| 144 | dev_kfree_skb_any(skb); |
| 145 | return 0; |
| 146 | } |
| 147 | |
| 148 | static int |
| 149 | mvswitch_mangle_rx(struct sk_buff *skb, int napi) |
| 150 | { |
| 151 | struct mvswitch_priv *priv; |
| 152 | struct net_device *dev; |
| 153 | int vlan = -1; |
| 154 | unsigned char *buf; |
| 155 | int i; |
| 156 | |
| 157 | dev = skb->dev; |
| 158 | if (!dev) |
| 159 | goto error; |
| 160 | |
| 161 | priv = dev->phy_ptr; |
| 162 | if (!priv) |
| 163 | goto error; |
| 164 | |
| 165 | if (!priv->grp) |
| 166 | goto error; |
| 167 | |
| 168 | #ifdef HEADER_MODE |
| 169 | buf = skb->data; |
| 170 | skb_pull(skb, MV_HEADER_SIZE); |
| 171 | #else |
| 172 | buf = skb->data + skb->len - MV_TRAILER_SIZE; |
| 173 | if (buf[0] != 0x80) |
| 174 | goto error; |
| 175 | #endif |
| 176 | |
| 177 | /* look for the vlan matching the incoming port */ |
| 178 | for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) { |
| 179 | if ((1 << buf[1]) & priv->vlans[i]) |
| 180 | vlan = i; |
| 181 | } |
| 182 | |
| 183 | if (vlan == -1) |
| 184 | goto error; |
| 185 | |
| 186 | skb->protocol = eth_type_trans(skb, skb->dev); |
| 187 | |
| 188 | if (napi) |
| 189 | return vlan_hwaccel_receive_skb(skb, priv->grp, vlan); |
| 190 | else |
| 191 | return vlan_hwaccel_rx(skb, priv->grp, vlan); |
| 192 | |
| 193 | error: |
| 194 | /* no vlan? eat the packet! */ |
| 195 | dev_kfree_skb_any(skb); |
| 196 | return 0; |
| 197 | } |
| 198 | |
| 199 | |
| 200 | static int |
| 201 | mvswitch_netif_rx(struct sk_buff *skb) |
| 202 | { |
| 203 | return mvswitch_mangle_rx(skb, 0); |
| 204 | } |
| 205 | |
| 206 | static int |
| 207 | mvswitch_netif_receive_skb(struct sk_buff *skb) |
| 208 | { |
| 209 | return mvswitch_mangle_rx(skb, 1); |
| 210 | } |
| 211 | |
| 212 | |
| 213 | static void |
| 214 | mvswitch_vlan_rx_register(struct net_device *dev, struct vlan_group *grp) |
| 215 | { |
| 216 | struct mvswitch_priv *priv = dev->phy_ptr; |
| 217 | priv->grp = grp; |
| 218 | } |
| 219 | |
| 220 | |
| 221 | static int |
| 222 | mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val) |
| 223 | { |
| 224 | int i = 100; |
| 225 | u16 r; |
| 226 | |
| 227 | do { |
| 228 | r = r16(pdev, addr, reg) & mask; |
| 229 | if (r == val) |
| 230 | return 0; |
| 231 | } while(--i > 0); |
| 232 | return -ETIMEDOUT; |
| 233 | } |
| 234 | |
| 235 | static int |
| 236 | mvswitch_config_init(struct phy_device *pdev) |
| 237 | { |
| 238 | struct mvswitch_priv *priv = to_mvsw(pdev); |
| 239 | struct net_device *dev = pdev->attached_dev; |
| 240 | u8 vlmap = 0; |
| 241 | int i; |
| 242 | |
| 243 | if (!dev) |
| 244 | return -EINVAL; |
| 245 | |
| 246 | printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name); |
| 247 | pdev->supported = ADVERTISED_100baseT_Full; |
| 248 | pdev->advertising = ADVERTISED_100baseT_Full; |
| 249 | dev->phy_ptr = priv; |
| 250 | dev->irq = PHY_POLL; |
| 251 | |
| 252 | /* initialize default vlans */ |
| 253 | for (i = 0; i < MV_PORTS; i++) |
| 254 | priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i); |
| 255 | |
| 256 | /* before entering reset, disable all ports */ |
| 257 | for (i = 0; i < MV_PORTS; i++) |
| 258 | w16(pdev, MV_PORTREG(CONTROL, i), 0x00); |
| 259 | |
| 260 | msleep(2); /* wait for the status change to settle in */ |
| 261 | |
| 262 | /* put the ATU in reset */ |
| 263 | w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); |
| 264 | |
| 265 | i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0); |
| 266 | if (i < 0) { |
| 267 | printk("%s: Timeout waiting for the switch to reset.\n", dev->name); |
| 268 | return i; |
| 269 | } |
| 270 | |
| 271 | /* set the ATU flags */ |
| 272 | w16(pdev, MV_SWITCHREG(ATU_CTRL), |
| 273 | MV_ATUCTL_NO_LEARN | |
| 274 | MV_ATUCTL_ATU_1K | |
| 275 | MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */ |
| 276 | ); |
| 277 | |
| 278 | /* initialize the cpu port */ |
| 279 | w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT), |
| 280 | #ifdef HEADER_MODE |
| 281 | MV_PORTCTRL_HEADER | |
| 282 | #else |
| 283 | MV_PORTCTRL_RXTR | |
| 284 | MV_PORTCTRL_TXTR | |
| 285 | #endif |
| 286 | MV_PORTCTRL_ENABLED |
| 287 | ); |
| 288 | /* wait for the phy change to settle in */ |
| 289 | msleep(2); |
| 290 | for (i = 0; i < MV_PORTS; i++) { |
| 291 | u8 pvid = 0; |
| 292 | int j; |
| 293 | |
| 294 | vlmap = 0; |
| 295 | |
| 296 | /* look for the matching vlan */ |
| 297 | for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) { |
| 298 | if (priv->vlans[j] & (1 << i)) { |
| 299 | vlmap = priv->vlans[j]; |
| 300 | pvid = j; |
| 301 | } |
| 302 | } |
| 303 | /* leave port unconfigured if it's not part of a vlan */ |
| 304 | if (!vlmap) |
| 305 | continue; |
| 306 | |
| 307 | /* add the cpu port to the allowed destinations list */ |
| 308 | vlmap |= (1 << MV_CPUPORT); |
| 309 | |
| 310 | /* take port out of its own vlan destination map */ |
| 311 | vlmap &= ~(1 << i); |
| 312 | |
| 313 | /* apply vlan settings */ |
| 314 | w16(pdev, MV_PORTREG(VLANMAP, i), |
| 315 | MV_PORTVLAN_PORTS(vlmap) | |
| 316 | MV_PORTVLAN_ID(i) |
| 317 | ); |
| 318 | |
| 319 | /* re-enable port */ |
| 320 | w16(pdev, MV_PORTREG(CONTROL, i), |
| 321 | MV_PORTCTRL_ENABLED |
| 322 | ); |
| 323 | } |
| 324 | |
| 325 | w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT), |
| 326 | MV_PORTVLAN_ID(MV_CPUPORT) |
| 327 | ); |
| 328 | |
| 329 | /* set the port association vector */ |
| 330 | for (i = 0; i <= MV_PORTS; i++) { |
| 331 | w16(pdev, MV_PORTREG(ASSOC, i), |
| 332 | MV_PORTASSOC_PORTS(1 << i) |
| 333 | ); |
| 334 | } |
| 335 | |
| 336 | /* init switch control */ |
| 337 | w16(pdev, MV_SWITCHREG(CTRL), |
| 338 | MV_SWITCHCTL_MSIZE | |
| 339 | MV_SWITCHCTL_DROP |
| 340 | ); |
| 341 | |
| 342 | /* hook into the tx function */ |
| 343 | priv->ndo_old = dev->netdev_ops; |
| 344 | memcpy(&priv->ndo, priv->ndo_old, sizeof(struct net_device_ops)); |
| 345 | priv->ndo.ndo_start_xmit = mvswitch_mangle_tx; |
| 346 | priv->ndo.ndo_vlan_rx_register = mvswitch_vlan_rx_register; |
| 347 | dev->netdev_ops = &priv->ndo; |
| 348 | |
| 349 | pdev->pkt_align = 2; |
| 350 | pdev->netif_receive_skb = mvswitch_netif_receive_skb; |
| 351 | pdev->netif_rx = mvswitch_netif_rx; |
| 352 | #ifdef HEADER_MODE |
| 353 | dev->features |= NETIF_F_HW_VLAN_RX | NETIF_F_HW_VLAN_TX; |
| 354 | #else |
| 355 | dev->features |= NETIF_F_HW_VLAN_RX; |
| 356 | #endif |
| 357 | |
| 358 | return 0; |
| 359 | } |
| 360 | |
| 361 | static int |
| 362 | mvswitch_read_status(struct phy_device *pdev) |
| 363 | { |
| 364 | pdev->speed = SPEED_100; |
| 365 | pdev->duplex = DUPLEX_FULL; |
| 366 | pdev->state = PHY_UP; |
| 367 | |
| 368 | /* XXX ugly workaround: we can't force the switch |
| 369 | * to gracefully handle hosts moving from one port to another, |
| 370 | * so we have to regularly clear the ATU database */ |
| 371 | |
| 372 | /* wait for the ATU to become available */ |
| 373 | mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); |
| 374 | |
| 375 | /* flush the ATU */ |
| 376 | w16(pdev, MV_SWITCHREG(ATU_OP), |
| 377 | MV_ATUOP_INPROGRESS | |
| 378 | MV_ATUOP_FLUSH_ALL |
| 379 | ); |
| 380 | |
| 381 | /* wait for operation to complete */ |
| 382 | mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); |
| 383 | |
| 384 | return 0; |
| 385 | } |
| 386 | |
| 387 | static int |
| 388 | mvswitch_config_aneg(struct phy_device *phydev) |
| 389 | { |
| 390 | return 0; |
| 391 | } |
| 392 | |
| 393 | static void |
| 394 | mvswitch_remove(struct phy_device *pdev) |
| 395 | { |
| 396 | struct mvswitch_priv *priv = to_mvsw(pdev); |
| 397 | struct net_device *dev = pdev->attached_dev; |
| 398 | |
| 399 | /* restore old netdev ops */ |
| 400 | if (priv->ndo_old && dev) |
| 401 | dev->netdev_ops = priv->ndo_old; |
| 402 | dev->vlan_rx_register = NULL; |
| 403 | dev->vlan_rx_kill_vid = NULL; |
| 404 | dev->phy_ptr = NULL; |
| 405 | dev->features &= ~NETIF_F_HW_VLAN_RX; |
| 406 | kfree(priv); |
| 407 | } |
| 408 | |
| 409 | static int |
| 410 | mvswitch_probe(struct phy_device *pdev) |
| 411 | { |
| 412 | struct mvswitch_priv *priv; |
| 413 | |
| 414 | priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL); |
| 415 | if (priv == NULL) |
| 416 | return -ENOMEM; |
| 417 | |
| 418 | pdev->priv = priv; |
| 419 | |
| 420 | return 0; |
| 421 | } |
| 422 | |
| 423 | static int |
| 424 | mvswitch_fixup(struct phy_device *dev) |
| 425 | { |
| 426 | u16 reg; |
| 427 | |
| 428 | if (dev->addr != 0x10) |
| 429 | return 0; |
| 430 | |
| 431 | reg = dev->bus->read(dev->bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; |
| 432 | if (reg != MV_IDENT_VALUE) |
| 433 | return 0; |
| 434 | |
| 435 | dev->phy_id = MVSWITCH_MAGIC; |
| 436 | return 0; |
| 437 | } |
| 438 | |
| 439 | |
| 440 | static struct phy_driver mvswitch_driver = { |
| 441 | .name = "Marvell 88E6060", |
| 442 | .phy_id = MVSWITCH_MAGIC, |
| 443 | .phy_id_mask = 0xffffffff, |
| 444 | .features = PHY_BASIC_FEATURES, |
| 445 | .probe = &mvswitch_probe, |
| 446 | .remove = &mvswitch_remove, |
| 447 | .config_init = &mvswitch_config_init, |
| 448 | .config_aneg = &mvswitch_config_aneg, |
| 449 | .read_status = &mvswitch_read_status, |
| 450 | .driver = { .owner = THIS_MODULE,}, |
| 451 | }; |
| 452 | |
| 453 | static int __init |
| 454 | mvswitch_init(void) |
| 455 | { |
| 456 | phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup); |
| 457 | return phy_driver_register(&mvswitch_driver); |
| 458 | } |
| 459 | |
| 460 | static void __exit |
| 461 | mvswitch_exit(void) |
| 462 | { |
| 463 | phy_driver_unregister(&mvswitch_driver); |
| 464 | } |
| 465 | |
| 466 | module_init(mvswitch_init); |
| 467 | module_exit(mvswitch_exit); |