From 57d832b0ee8e1add62520f5354e4392ecee0fe21 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Tue, 17 Jul 2012 11:32:18 -0400 Subject: [PATCH 003/123] remaining generic openwrt files Import of the openwrt files from trunk, as of this commit: ---------- commit c1d79f64eed0a7ac36b5b9bca52275b397bec424 Author: nbd Date: Mon Jul 16 16:26:51 2012 +0000 uboot-ar71xx: fix compile on recent mac os x versions git-svn-id: svn://svn.openwrt.org/openwrt/trunk@32750 3c298f89-4303-0410-b956-a3cf2f4a3e73 ---------- Due to the nature of how the openwrt source is managed, new files are found separate from the commits which actually make use of them, so that unused stuff could not easily be left behind, but it is what it is. Path to files in the repo is: target/linux/generic/files Repo is: git://nbd.name/openwrt.git Signed-off-by: Paul Gortmaker diff --git a/Documentation/networking/adm6996.txt b/Documentation/networking/adm6996.txt new file mode 100644 index 0000000..0fca549 --- /dev/null +++ b/Documentation/networking/adm6996.txt @@ -0,0 +1,110 @@ +------- + +ADM6996FC / ADM6996M switch chip driver + + +1. General information + + This driver supports the FC and M models only. The ADM6996F and L are + completely different chips. + + Support for the FC model is extremely limited at the moment. There is no VLAN + support as of yet. The driver will not offer an swconfig interface for the FC + chip. + +1.1 VLAN IDs + + It is possible to define 16 different VLANs. Every VLAN has an identifier, its + VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the + swconfig based configuration is very straightforward. To define two VLANs with + IDs 4 and 5, you can invoke, for example: + + # swconfig dev ethX vlan 4 set ports '0 1t 2 5t' + # swconfig dev ethX vlan 5 set ports '0t 1t 5t' + + The swconfig framework will automatically invoke 'port Y set pvid Z' for every + port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In + this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port + is the VLAN ID associated with untagged packets coming in on that port. + + But if you wish to use VLAN IDs outside the range 0-15, this automatic + behaviour of the swconfig framework becomes a problem. The 16 VLANs that + swconfig can configure on the ADM6996 also have a "vid" setting. By default, + this is the same as the number of the VLAN entry, to make the simple behaviour + above possible. To still support a VLAN with a VLAN ID higher than 15 + (presumably because you are in a network where such VLAN IDs are already in + use), you can change the "vid" setting of the VLAN to anything in the range + 0-1023. But suppose you did the following: + + # swconfig dev ethX vlan 0 set vid 998 + # swconfig dev ethX vlan 0 set ports '0 2 5t' + + Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set pvid + 0'. But the "pvid" should be set to 998, so you are responsible for manually + fixing this! + +1.2 VLAN filtering + + The switch is configured to apply source port filtering. This means that + packets are only accepted when the port the packets came in on is a member of + the VLAN the packet should go to. + + Only membership of a VLAN is tested, it does not matter whether it is a tagged + or untagged membership. + + For untagged packets, the destination VLAN is the Primary VLAN ID of the + incoming port. So if the PVID of a port is 0, but that port is not a member of + the VLAN with ID 0, this means that untagged packets on that port are dropped. + This can be used as a roundabout way of dropping untagged packets from a port, + a mode often referred to as "Admit only tagged packets". + +1.3 Reset + + The two supported chip models do not have a sofware-initiated reset. When the + driver is initialised, as well as when the 'reset' swconfig option is invoked, + the driver will set those registers it knows about and supports to the correct + default value. But there are a lot of registers in the chip that the driver + does not support. If something changed those registers, invoking 'reset' or + performing a warm reboot might still leave the chip in a "broken" state. Only + a hardware reset will bring it back in the default state. + +2. Technical details on PHYs and the ADM6996 + + From the viewpoint of the Linux kernel, it is common that an Ethernet adapter + can be seen as a separate MAC entity and a separate PHY entity. The PHY entity + can be queried and set through registers accessible via an MDIO bus. A PHY + normally has a single address on that bus, in the range 0 through 31. + + The ADM6996 has special-purpose registers in the range of PHYs 0 through 10. + Even though all these registers control a single ADM6996 chip, the Linux + kernel treats this as 11 separate PHYs. The driver will bind to these + addresses to prevent a different PHY driver from binding and corrupting these + registers. + + What Linux sees as the PHY on address 0 is meant for the Ethernet MAC + connected to the CPU port of the ADM6996 switch chip (port 5). This is the + Ethernet MAC you will use to send and receive data through the switch. + + The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of + the switch chip. These can be accessed with the Generic PHY driver, as the + registers have the common layout. + + If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC + needs to bind to PHY address 20 for the port to work correctly. + + The ADM6996 switch driver will reset the ports 0 through 3 on startup and when + 'reset' is invoked. This could clash with a different PHY driver if the kernel + binds a PHY driver to address 16 through 19. + + If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the ADM6996 + driver will simply always report a connected 100 Mbit/s full-duplex link for + that PHY, and provide no other functionality. This is most likely not what you + want. So if you see a message in your log + + ethX: PHY overlaps ADM6996, providing fixed PHY yy. + + This is most likely an indication that ethX will not work properly, and your + kernel needs to be configured to attach a different PHY to that Ethernet MAC. + + Controlling the mapping between MACs and PHYs is usually done in platform- or + board-specific fixup code. The ADM6996 driver has no influence over this. diff --git a/arch/mips/fw/myloader/Makefile b/arch/mips/fw/myloader/Makefile new file mode 100644 index 0000000..34acfd0 --- /dev/null +++ b/arch/mips/fw/myloader/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Compex's MyLoader support on MIPS architecture +# + +lib-y += myloader.o diff --git a/arch/mips/fw/myloader/myloader.c b/arch/mips/fw/myloader/myloader.c new file mode 100644 index 0000000..a26f9ad --- /dev/null +++ b/arch/mips/fw/myloader/myloader.c @@ -0,0 +1,63 @@ +/* + * Compex's MyLoader specific prom routines + * + * Copyright (C) 2007-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +#include +#include + +#define SYS_PARAMS_ADDR KSEG1ADDR(0x80000800) +#define BOARD_PARAMS_ADDR KSEG1ADDR(0x80000A00) +#define PART_TABLE_ADDR KSEG1ADDR(0x80000C00) +#define BOOT_PARAMS_ADDR KSEG1ADDR(0x80000E00) + +static struct myloader_info myloader_info __initdata; +static int myloader_found __initdata; + +struct myloader_info * __init myloader_get_info(void) +{ + struct mylo_system_params *sysp; + struct mylo_board_params *boardp; + struct mylo_partition_table *parts; + + if (myloader_found) + return &myloader_info; + + sysp = (struct mylo_system_params *)(SYS_PARAMS_ADDR); + boardp = (struct mylo_board_params *)(BOARD_PARAMS_ADDR); + parts = (struct mylo_partition_table *)(PART_TABLE_ADDR); + + printk(KERN_DEBUG "MyLoader: sysp=%08x, boardp=%08x, parts=%08x\n", + sysp->magic, boardp->magic, parts->magic); + + /* Check for some magic numbers */ + if (sysp->magic != MYLO_MAGIC_SYS_PARAMS || + boardp->magic != MYLO_MAGIC_BOARD_PARAMS || + le32_to_cpu(parts->magic) != MYLO_MAGIC_PARTITIONS) + return NULL; + + printk(KERN_DEBUG "MyLoader: id=%04x:%04x, sub_id=%04x:%04x\n", + sysp->vid, sysp->did, sysp->svid, sysp->sdid); + + myloader_info.vid = sysp->vid; + myloader_info.did = sysp->did; + myloader_info.svid = sysp->svid; + myloader_info.sdid = sysp->sdid; + + memcpy(myloader_info.macs, boardp->addr, sizeof(myloader_info.macs)); + + myloader_found = 1; + + return &myloader_info; +} diff --git a/drivers/char/gpio_dev.c b/drivers/char/gpio_dev.c new file mode 100644 index 0000000..0f428bd --- /dev/null +++ b/drivers/char/gpio_dev.c @@ -0,0 +1,179 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */ +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 */ + retval = gpio_request(arg, DRVNAME); + break; + case GPIO_FREE: + /* should be last ioctl operation on */ + /* 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 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) +{ + 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/drivers/input/misc/gpio_buttons.c b/drivers/input/misc/gpio_buttons.c new file mode 100644 index 0000000..51288a3 --- /dev/null +++ b/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 + * Copyright (C) 2010 Nuno Goncalves + * + * This file was based on: /drivers/input/misc/cobalt_btns.c + * Copyright (C) 2007 Yoichi Yuasa + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Polled GPIO Buttons driver"); diff --git a/drivers/leds/ledtrig-morse.c b/drivers/leds/ledtrig-morse.c new file mode 100644 index 0000000..9c8fe59 --- /dev/null +++ b/drivers/leds/ledtrig-morse.c @@ -0,0 +1,366 @@ +/* + * LED Morse Trigger + * + * Copyright (C) 2007 Gabor Juhos + * + * This file was based on: drivers/led/ledtrig-timer.c + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie + * + * 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 + * Copyright (C) 2003 Tomas Szepe + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, "\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 "); +MODULE_DESCRIPTION("Morse LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-netdev.c b/drivers/leds/ledtrig-netdev.c new file mode 100644 index 0000000..6c56acb --- /dev/null +++ b/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 + * + * Derived from ledtrig-timer.c which is: + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) +#include +#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 "); +MODULE_DESCRIPTION("Netdev LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-usbdev.c b/drivers/leds/ledtrig-usbdev.c new file mode 100644 index 0000000..20076ad --- /dev/null +++ b/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 + * + * derived from ledtrig-netdev.c: + * Copyright 2007 Oliver Jowett + * + * ledtrig-netdev.c derived from ledtrig-timer.c: + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("USB device LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/myloader.c b/drivers/mtd/myloader.c new file mode 100644 index 0000000..a13752d --- /dev/null +++ b/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 + * + * This file was based on drivers/mtd/redboot.c + * Author: Red Hat, Inc. - David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define 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 "); +MODULE_DESCRIPTION("Parsing code for MyLoader partition tables"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/atmel-pwm.c b/drivers/pwm/atmel-pwm.c new file mode 100644 index 0000000..158bb92 --- /dev/null +++ b/drivers/pwm/atmel-pwm.c @@ -0,0 +1,592 @@ +/* + * drivers/pwm/atmel-pwm.c + * + * Copyright (C) 2010 Bill Gatliff + * Copyright (C) 2007 David Brownell + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + /* registers common to the PWMC peripheral */ + PWMC_MR = 0, + PWMC_ENA = 4, + PWMC_DIS = 8, + PWMC_SR = 0xc, + PWMC_IER = 0x10, + PWMC_IDR = 0x14, + PWMC_IMR = 0x18, + PWMC_ISR = 0x1c, + + /* registers per each PWMC channel */ + PWMC_CMR = 0, + PWMC_CDTY = 4, + PWMC_CPRD = 8, + PWMC_CCNT = 0xc, + PWMC_CUPD = 0x10, + + /* how to find each channel */ + PWMC_CHAN_BASE = 0x200, + PWMC_CHAN_STRIDE = 0x20, + + /* CMR bits of interest */ + PWMC_CMR_CPD = 10, + PWMC_CMR_CPOL = 9, + PWMC_CMR_CALG = 8, + PWMC_CMR_CPRE_MASK = 0xf, +}; + +struct atmel_pwm { + struct pwm_device pwm; + spinlock_t lock; + void __iomem *iobase; + struct clk *clk; + u32 *sync_mask; + int irq; + u32 ccnt_mask; +}; + +static inline struct atmel_pwm *to_atmel_pwm(const struct pwm_channel *p) +{ + return container_of(p->pwm, struct atmel_pwm, pwm); +} + +static inline void +pwmc_writel(const struct atmel_pwm *p, + unsigned offset, u32 val) +{ + __raw_writel(val, p->iobase + offset); +} + +static inline u32 +pwmc_readl(const struct atmel_pwm *p, + unsigned offset) +{ + return __raw_readl(p->iobase + offset); +} + +static inline void +pwmc_chan_writel(const struct pwm_channel *p, + u32 offset, u32 val) +{ + const struct atmel_pwm *ap = to_atmel_pwm(p); + + if (PWMC_CMR == offset) + val &= ((1 << PWMC_CMR_CPD) + | (1 << PWMC_CMR_CPOL) + | (1 << PWMC_CMR_CALG) + | (PWMC_CMR_CPRE_MASK)); + else + val &= ap->ccnt_mask; + + pwmc_writel(ap, offset + PWMC_CHAN_BASE + + (p->chan * PWMC_CHAN_STRIDE), val); +} + +static inline u32 +pwmc_chan_readl(const struct pwm_channel *p, + u32 offset) +{ + const struct atmel_pwm *ap = to_atmel_pwm(p); + + return pwmc_readl(ap, offset + PWMC_CHAN_BASE + + (p->chan * PWMC_CHAN_STRIDE)); +} + +static inline int +__atmel_pwm_is_on(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + return (pwmc_readl(ap, PWMC_SR) & (1 << p->chan)) ? 1 : 0; +} + +static inline void +__atmel_pwm_unsynchronize(struct pwm_channel *p, + struct pwm_channel *to_p) +{ + const struct atmel_pwm *ap = to_atmel_pwm(p); + int wchan; + + if (to_p) { + ap->sync_mask[p->chan] &= ~(1 << to_p->chan); + ap->sync_mask[to_p->chan] &= ~(1 << p->chan); + goto done; + } + + ap->sync_mask[p->chan] = 0; + for (wchan = 0; wchan < ap->pwm.nchan; wchan++) + ap->sync_mask[wchan] &= ~(1 << p->chan); +done: + dev_dbg(p->pwm->dev, "sync_mask %x\n", ap->sync_mask[p->chan]); +} + +static inline void +__atmel_pwm_synchronize(struct pwm_channel *p, + struct pwm_channel *to_p) +{ + const struct atmel_pwm *ap = to_atmel_pwm(p); + + if (!to_p) + return; + + ap->sync_mask[p->chan] |= (1 << to_p->chan); + ap->sync_mask[to_p->chan] |= (1 << p->chan); + + dev_dbg(p->pwm->dev, "sync_mask %x\n", ap->sync_mask[p->chan]); +} + +static inline void +__atmel_pwm_stop(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + u32 chid = 1 << p->chan; + + pwmc_writel(ap, PWMC_DIS, ap->sync_mask[p->chan] | chid); +} + +static inline void +__atmel_pwm_start(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + u32 chid = 1 << p->chan; + + pwmc_writel(ap, PWMC_ENA, ap->sync_mask[p->chan] | chid); +} + +static int +atmel_pwm_synchronize(struct pwm_channel *p, + struct pwm_channel *to_p) +{ + unsigned long flags; + spin_lock_irqsave(&p->lock, flags); + __atmel_pwm_synchronize(p, to_p); + spin_unlock_irqrestore(&p->lock, flags); + return 0; +} + +static int +atmel_pwm_unsynchronize(struct pwm_channel *p, + struct pwm_channel *from_p) +{ + unsigned long flags; + spin_lock_irqsave(&p->lock, flags); + __atmel_pwm_unsynchronize(p, from_p); + spin_unlock_irqrestore(&p->lock, flags); + return 0; +} + +static inline int +__atmel_pwm_config_polarity(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + u32 cmr = pwmc_chan_readl(p, PWMC_CMR); + + if (c->polarity) + cmr &= ~BIT(PWMC_CMR_CPOL); + else + cmr |= BIT(PWMC_CMR_CPOL); + pwmc_chan_writel(p, PWMC_CMR, cmr); + p->active_high = c->polarity ? 1 : 0; + + dev_dbg(p->pwm->dev, "polarity %d\n", c->polarity); + return 0; +} + +static inline int +__atmel_pwm_config_duty_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + u32 cmr, cprd, cpre, cdty; + + cmr = pwmc_chan_readl(p, PWMC_CMR); + cprd = pwmc_chan_readl(p, PWMC_CPRD); + + cpre = cmr & PWMC_CMR_CPRE_MASK; + cmr &= ~BIT(PWMC_CMR_CPD); + + cdty = cprd - (c->duty_ticks >> cpre); + + p->duty_ticks = c->duty_ticks; + + if (__atmel_pwm_is_on(p)) { + pwmc_chan_writel(p, PWMC_CMR, cmr); + pwmc_chan_writel(p, PWMC_CUPD, cdty); + } else + pwmc_chan_writel(p, PWMC_CDTY, cdty); + + dev_dbg(p->pwm->dev, "duty_ticks = %lu cprd = %x" + " cdty = %x cpre = %x\n", p->duty_ticks, + cprd, cdty, cpre); + + return 0; +} + +static inline int +__atmel_pwm_config_period_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + u32 cmr, cprd, cpre; + + cpre = fls(c->period_ticks); + if (cpre < 16) + cpre = 0; + else { + cpre -= 15; + if (cpre > 10) + return -EINVAL; + } + + cmr = pwmc_chan_readl(p, PWMC_CMR); + cmr &= ~PWMC_CMR_CPRE_MASK; + cmr |= cpre; + + cprd = c->period_ticks >> cpre; + + pwmc_chan_writel(p, PWMC_CMR, cmr); + pwmc_chan_writel(p, PWMC_CPRD, cprd); + p->period_ticks = c->period_ticks; + + dev_dbg(p->pwm->dev, "period_ticks = %lu cprd = %x cpre = %x\n", + p->period_ticks, cprd, cpre); + + return 0; +} + +static int +atmel_pwm_config_nosleep(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + + switch (c->config_mask) { + + case PWM_CONFIG_DUTY_TICKS: + __atmel_pwm_config_duty_ticks(p, c); + break; + + case PWM_CONFIG_STOP: + __atmel_pwm_stop(p); + break; + + case PWM_CONFIG_START: + __atmel_pwm_start(p); + break; + + case PWM_CONFIG_POLARITY: + __atmel_pwm_config_polarity(p, c); + break; + + default: + ret = -EINVAL; + break; + } + + spin_unlock_irqrestore(&p->lock, flags); + return ret; +} + +static int +atmel_pwm_stop_sync(struct pwm_channel *p) +{ + struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm); + int ret; + int was_on = __atmel_pwm_is_on(p); + + if (was_on) { + do { + init_completion(&p->complete); + set_bit(FLAG_STOP, &p->flags); + pwmc_writel(ap, PWMC_IER, 1 << p->chan); + + dev_dbg(p->pwm->dev, "waiting on stop_sync completion...\n"); + + ret = wait_for_completion_interruptible(&p->complete); + + dev_dbg(p->pwm->dev, "stop_sync complete (%d)\n", ret); + + if (ret) + return ret; + } while (p->flags & BIT(FLAG_STOP)); + } + + return was_on; +} + +static int +atmel_pwm_config(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + int was_on = 0; + + if (p->pwm->config_nosleep) { + if (!p->pwm->config_nosleep(p, c)) + return 0; + } + + might_sleep(); + + dev_dbg(p->pwm->dev, "config_mask %x\n", c->config_mask); + + was_on = atmel_pwm_stop_sync(p); + if (was_on < 0) + return was_on; + + if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) { + __atmel_pwm_config_period_ticks(p, c); + if (!(c->config_mask & PWM_CONFIG_DUTY_TICKS)) { + struct pwm_channel_config d = { + .config_mask = PWM_CONFIG_DUTY_TICKS, + .duty_ticks = p->duty_ticks, + }; + __atmel_pwm_config_duty_ticks(p, &d); + } + } + + if (c->config_mask & PWM_CONFIG_DUTY_TICKS) + __atmel_pwm_config_duty_ticks(p, c); + + if (c->config_mask & PWM_CONFIG_POLARITY) + __atmel_pwm_config_polarity(p, c); + + if ((c->config_mask & PWM_CONFIG_START) + || (was_on && !(c->config_mask & PWM_CONFIG_STOP))) + __atmel_pwm_start(p); + + return 0; +} + +static void +__atmel_pwm_set_callback(struct pwm_channel *p, + pwm_callback_t callback) +{ + struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm); + + p->callback = callback; + pwmc_writel(ap, p->callback ? PWMC_IER : PWMC_IDR, 1 << p->chan); +} + +static int +atmel_pwm_set_callback(struct pwm_channel *p, + pwm_callback_t callback) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + unsigned long flags; + + spin_lock_irqsave(&ap->lock, flags); + __atmel_pwm_set_callback(p, callback); + spin_unlock_irqrestore(&ap->lock, flags); + + return 0; +} + +static int +atmel_pwm_request(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + clk_enable(ap->clk); + p->tick_hz = clk_get_rate(ap->clk); + __atmel_pwm_unsynchronize(p, NULL); + __atmel_pwm_stop(p); + spin_unlock_irqrestore(&p->lock, flags); + + return 0; +} + +static void +atmel_pwm_free(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + clk_disable(ap->clk); +} + +static irqreturn_t +atmel_pwmc_irq(int irq, void *data) +{ + struct atmel_pwm *ap = data; + struct pwm_channel *p; + u32 isr; + int chid; + unsigned long flags; + + spin_lock_irqsave(&ap->lock, flags); + + isr = pwmc_readl(ap, PWMC_ISR); + for (chid = 0; isr; chid++, isr >>= 1) { + p = &ap->pwm.channels[chid]; + if (isr & 1) { + if (p->callback) + p->callback(p); + if (p->flags & BIT(FLAG_STOP)) { + __atmel_pwm_stop(p); + clear_bit(FLAG_STOP, &p->flags); + } + complete_all(&p->complete); + } + } + + spin_unlock_irqrestore(&ap->lock, flags); + + return IRQ_HANDLED; +} + +static int __devinit +atmel_pwmc_probe(struct platform_device *pdev) +{ + struct atmel_pwm *ap; + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int ret = 0; + + ap = kzalloc(sizeof(*ap), GFP_KERNEL); + if (!ap) { + ret = -ENOMEM; + goto err_atmel_pwm_alloc; + } + + spin_lock_init(&ap->lock); + platform_set_drvdata(pdev, ap); + + ap->pwm.dev = &pdev->dev; + ap->pwm.bus_id = dev_name(&pdev->dev); + + ap->pwm.nchan = 4; /* TODO: true only for SAM9263 and AP7000 */ + ap->ccnt_mask = 0xffffUL; /* TODO: true only for SAM9263 */ + + ap->sync_mask = kzalloc(ap->pwm.nchan * sizeof(u32), GFP_KERNEL); + if (!ap->sync_mask) { + ret = -ENOMEM; + goto err_alloc_sync_masks; + } + + ap->pwm.owner = THIS_MODULE; + ap->pwm.request = atmel_pwm_request; + ap->pwm.free = atmel_pwm_free; + ap->pwm.config_nosleep = atmel_pwm_config_nosleep; + ap->pwm.config = atmel_pwm_config; + ap->pwm.synchronize = atmel_pwm_synchronize; + ap->pwm.unsynchronize = atmel_pwm_unsynchronize; + ap->pwm.set_callback = atmel_pwm_set_callback; + + ap->clk = clk_get(&pdev->dev, "pwm_clk"); + if (PTR_ERR(ap->clk)) { + ret = -ENODEV; + goto err_clk_get; + } + + ap->iobase = ioremap_nocache(r->start, r->end - r->start + 1); + if (!ap->iobase) { + ret = -ENODEV; + goto err_ioremap; + } + + clk_enable(ap->clk); + pwmc_writel(ap, PWMC_DIS, -1); + pwmc_writel(ap, PWMC_IDR, -1); + clk_disable(ap->clk); + + ap->irq = platform_get_irq(pdev, 0); + if (ap->irq != -ENXIO) { + ret = request_irq(ap->irq, atmel_pwmc_irq, 0, + ap->pwm.bus_id, ap); + if (ret) + goto err_request_irq; + } + + ret = pwm_register(&ap->pwm); + if (ret) + goto err_pwm_register; + + return 0; + +err_pwm_register: + if (ap->irq != -ENXIO) + free_irq(ap->irq, ap); +err_request_irq: + iounmap(ap->iobase); +err_ioremap: + clk_put(ap->clk); +err_clk_get: + platform_set_drvdata(pdev, NULL); +err_alloc_sync_masks: + kfree(ap); +err_atmel_pwm_alloc: + return ret; +} + +static int __devexit +atmel_pwmc_remove(struct platform_device *pdev) +{ + struct atmel_pwm *ap = platform_get_drvdata(pdev); + int ret; + + /* TODO: what can we do if this fails? */ + ret = pwm_unregister(&ap->pwm); + + clk_enable(ap->clk); + pwmc_writel(ap, PWMC_IDR, -1); + pwmc_writel(ap, PWMC_DIS, -1); + clk_disable(ap->clk); + + if (ap->irq != -ENXIO) + free_irq(ap->irq, ap); + + clk_put(ap->clk); + iounmap(ap->iobase); + + kfree(ap); + + return 0; +} + +static struct platform_driver atmel_pwm_driver = { + .driver = { + .name = "atmel_pwmc", + .owner = THIS_MODULE, + }, + .probe = atmel_pwmc_probe, + .remove = __devexit_p(atmel_pwmc_remove), +}; + +static int __init atmel_pwm_init(void) +{ + return platform_driver_register(&atmel_pwm_driver); +} +module_init(atmel_pwm_init); + +static void __exit atmel_pwm_exit(void) +{ + platform_driver_unregister(&atmel_pwm_driver); +} +module_exit(atmel_pwm_exit); + +MODULE_AUTHOR("Bill Gatliff "); +MODULE_DESCRIPTION("Driver for Atmel PWMC peripheral"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:atmel_pwmc"); diff --git a/drivers/pwm/gpio-pwm.c b/drivers/pwm/gpio-pwm.c new file mode 100644 index 0000000..dff5d1d --- /dev/null +++ b/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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("PWM output using GPIO and a high-resolution timer"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio_pwm"); diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c new file mode 100644 index 0000000..c1596e9 --- /dev/null +++ b/drivers/pwm/pwm.c @@ -0,0 +1,643 @@ +/* + * drivers/pwm/pwm.c + * + * Copyright (C) 2010 Bill Gatliff + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /*kcalloc, kfree since 2.6.34 */ +#include + +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);