/* include/asm/mach-msm/leds-cpld.c * * Copyright (C) 2008 HTC Corporation. * * Author: Farmer Tseng * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #define DEBUG_LED_CHANGE 0 static int _g_cpld_led_addr; struct CPLD_LED_data { spinlock_t data_lock; struct led_classdev leds[4]; /* blue, green, red */ }; static ssize_t led_blink_solid_show(struct device *dev, struct device_attribute *attr, char *buf) { struct CPLD_LED_data *CPLD_LED; int idx = 2; uint8_t reg_val; struct led_classdev *led_cdev = dev_get_drvdata(dev); ssize_t ret = 0; if (!strcmp(led_cdev->name, "red")) idx = 0; else if (!strcmp(led_cdev->name, "green")) idx = 1; else idx = 2; CPLD_LED = container_of(led_cdev, struct CPLD_LED_data, leds[idx]); spin_lock(&CPLD_LED->data_lock); reg_val = readb(_g_cpld_led_addr); reg_val = reg_val >> (2 * idx + 1); reg_val &= 0x1; spin_unlock(&CPLD_LED->data_lock); /* no lock needed for this */ sprintf(buf, "%u\n", reg_val); ret = strlen(buf) + 1; return ret; } static ssize_t led_blink_solid_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct CPLD_LED_data *CPLD_LED; int idx = 2; uint8_t reg_val; char *after; unsigned long state; ssize_t ret = -EINVAL; size_t count; struct led_classdev *led_cdev = dev_get_drvdata(dev); if (!strcmp(led_cdev->name, "red")) idx = 0; else if (!strcmp(led_cdev->name, "green")) idx = 1; else idx = 2; CPLD_LED = container_of(led_cdev, struct CPLD_LED_data, leds[idx]); state = simple_strtoul(buf, &after, 10); count = after - buf; if (*after && isspace(*after)) count++; if (count == size) { ret = count; spin_lock(&CPLD_LED->data_lock); reg_val = readb(_g_cpld_led_addr); if (state) reg_val |= 1 << (2 * idx + 1); else reg_val &= ~(1 << (2 * idx + 1)); writeb(reg_val, _g_cpld_led_addr); spin_unlock(&CPLD_LED->data_lock); } return ret; } static DEVICE_ATTR(blink, 0644, led_blink_solid_show, led_blink_solid_store); static ssize_t cpldled_blink_all_show(struct device *dev, struct device_attribute *attr, char *buf) { uint8_t reg_val; struct CPLD_LED_data *CPLD_LED = dev_get_drvdata(dev); ssize_t ret = 0; spin_lock(&CPLD_LED->data_lock); reg_val = readb(_g_cpld_led_addr); reg_val &= 0x2A; if (reg_val == 0x2A) reg_val = 1; else reg_val = 0; spin_unlock(&CPLD_LED->data_lock); /* no lock needed for this */ sprintf(buf, "%u\n", reg_val); ret = strlen(buf) + 1; return ret; } static ssize_t cpldled_blink_all_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { uint8_t reg_val; char *after; unsigned long state; ssize_t ret = -EINVAL; size_t count; struct CPLD_LED_data *CPLD_LED = dev_get_drvdata(dev); state = simple_strtoul(buf, &after, 10); count = after - buf; if (*after && isspace(*after)) count++; if (count == size) { ret = count; spin_lock(&CPLD_LED->data_lock); reg_val = readb(_g_cpld_led_addr); if (state) reg_val |= 0x2A; else reg_val &= ~0x2A; writeb(reg_val, _g_cpld_led_addr); spin_unlock(&CPLD_LED->data_lock); } return ret; } static struct device_attribute dev_attr_blink_all = { .attr = { .name = "blink", .mode = 0644, }, .show = cpldled_blink_all_show, .store = cpldled_blink_all_store, }; static void led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct CPLD_LED_data *CPLD_LED; int idx = 2; struct led_classdev *led; uint8_t reg_val; if (!strcmp(led_cdev->name, "jogball-backlight")) { if (brightness > 7) reg_val = 1; else reg_val = brightness; writeb(0, _g_cpld_led_addr + 0x8); writeb(reg_val, _g_cpld_led_addr + 0x8); #if DEBUG_LED_CHANGE printk(KERN_INFO "LED change: jogball backlight = %d \n", reg_val); #endif return; } else if (!strcmp(led_cdev->name, "red")) { idx = 0; } else if (!strcmp(led_cdev->name, "green")) { idx = 1; } else { idx = 2; } CPLD_LED = container_of(led_cdev, struct CPLD_LED_data, leds[idx]); spin_lock(&CPLD_LED->data_lock); reg_val = readb(_g_cpld_led_addr); led = &CPLD_LED->leds[idx]; if (led->brightness > LED_OFF) reg_val |= 1 << (2 * idx); else reg_val &= ~(1 << (2 * idx)); writeb(reg_val, _g_cpld_led_addr); #if DEBUG_LED_CHANGE printk(KERN_INFO "LED change: %s = %d \n", led_cdev->name, led->brightness); #endif spin_unlock(&CPLD_LED->data_lock); } static ssize_t cpldled_grpfreq_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", 0); } static ssize_t cpldled_grpfreq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return 0; } static DEVICE_ATTR(grpfreq, 0644, cpldled_grpfreq_show, cpldled_grpfreq_store); static ssize_t cpldled_grppwm_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", 0); } static ssize_t cpldled_grppwm_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return 0; } static DEVICE_ATTR(grppwm, 0644, cpldled_grppwm_show, cpldled_grppwm_store); static int CPLD_LED_probe(struct platform_device *pdev) { int ret = 0; int i, j; struct resource *res; struct CPLD_LED_data *CPLD_LED; CPLD_LED = kzalloc(sizeof(struct CPLD_LED_data), GFP_KERNEL); if (CPLD_LED == NULL) { printk(KERN_ERR "CPLD_LED_probe: no memory for device\n"); ret = -ENOMEM; goto err_alloc_failed; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ret = -ENOMEM; goto err_alloc_failed; } _g_cpld_led_addr = res->start; if (!_g_cpld_led_addr) { ret = -ENOMEM; goto err_alloc_failed; } memset(CPLD_LED, 0, sizeof(struct CPLD_LED_data)); writeb(0x00, _g_cpld_led_addr); CPLD_LED->leds[0].name = "red"; CPLD_LED->leds[0].brightness_set = led_brightness_set; CPLD_LED->leds[1].name = "green"; CPLD_LED->leds[1].brightness_set = led_brightness_set; CPLD_LED->leds[2].name = "blue"; CPLD_LED->leds[2].brightness_set = led_brightness_set; CPLD_LED->leds[3].name = "jogball-backlight"; CPLD_LED->leds[3].brightness_set = led_brightness_set; spin_lock_init(&CPLD_LED->data_lock); for (i = 0; i < 4; i++) { /* red, green, blue jogball */ ret = led_classdev_register(&pdev->dev, &CPLD_LED->leds[i]); if (ret) { printk(KERN_ERR "CPLD_LED: led_classdev_register failed\n"); goto err_led_classdev_register_failed; } } for (i = 0; i < 3; i++) { ret = device_create_file(CPLD_LED->leds[i].dev, &dev_attr_blink); if (ret) { printk(KERN_ERR "CPLD_LED: device_create_file failed\n"); goto err_out_attr_blink; } } dev_set_drvdata(&pdev->dev, CPLD_LED); ret = device_create_file(&pdev->dev, &dev_attr_blink_all); if (ret) { printk(KERN_ERR "CPLD_LED: create dev_attr_blink_all failed\n"); goto err_out_attr_blink; } ret = device_create_file(&pdev->dev, &dev_attr_grppwm); if (ret) { printk(KERN_ERR "CPLD_LED: create dev_attr_grppwm failed\n"); goto err_out_attr_grppwm; } ret = device_create_file(&pdev->dev, &dev_attr_grpfreq); if (ret) { printk(KERN_ERR "CPLD_LED: create dev_attr_grpfreq failed\n"); goto err_out_attr_grpfreq; } return 0; err_out_attr_grpfreq: device_remove_file(&pdev->dev, &dev_attr_grppwm); err_out_attr_grppwm: device_remove_file(&pdev->dev, &dev_attr_blink_all); err_out_attr_blink: for (j = 0; j < i; j++) device_remove_file(CPLD_LED->leds[j].dev, &dev_attr_blink); i = 3; err_led_classdev_register_failed: for (j = 0; j < i; j++) led_classdev_unregister(&CPLD_LED->leds[j]); err_alloc_failed: kfree(CPLD_LED); return ret; } static int CPLD_LED_remove(struct platform_device *pdev) { struct CPLD_LED_data *CPLD_LED; int i; CPLD_LED = platform_get_drvdata(pdev); for (i = 0; i < 3; i++) { device_remove_file(CPLD_LED->leds[i].dev, &dev_attr_blink); led_classdev_unregister(&CPLD_LED->leds[i]); } device_remove_file(&pdev->dev, &dev_attr_blink_all); device_remove_file(&pdev->dev, &dev_attr_grppwm); device_remove_file(&pdev->dev, &dev_attr_grpfreq); kfree(CPLD_LED); return 0; } static struct platform_driver CPLD_LED_driver = { .probe = CPLD_LED_probe, .remove = CPLD_LED_remove, .driver = { .name = "leds-cpld", .owner = THIS_MODULE, }, }; static int __init CPLD_LED_init(void) { return platform_driver_register(&CPLD_LED_driver); } static void __exit CPLD_LED_exit(void) { platform_driver_unregister(&CPLD_LED_driver); } MODULE_AUTHOR("Farmer Tseng"); MODULE_DESCRIPTION("CPLD_LED driver"); MODULE_LICENSE("GPL"); module_init(CPLD_LED_init); module_exit(CPLD_LED_exit);