/* include/asm/mach-msm/leds-microp.c * * Copyright (C) 2009 HTC Corporation. * * 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. * */ #ifdef CONFIG_MICROP_COMMON #include #include #include #include #include #include #include static int microp_write_led_mode(struct led_classdev *led_cdev, uint8_t mode, uint16_t off_timer) { struct microp_led_data *ldata; uint8_t data[7]; int ret; ldata = container_of(led_cdev, struct microp_led_data, ldev); if (!strcmp(ldata->ldev.name, "green")) { data[0] = 0x01; data[1] = mode; data[2] = off_timer >> 8; data[3] = off_timer & 0xFF; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; } else if (!strcmp(ldata->ldev.name, "amber")) { data[0] = 0x02; data[1] = 0x00; data[2] = 0x00; data[3] = 0x00; data[4] = mode; data[5] = off_timer >> 8; data[6] = off_timer & 0xFF; } else if (!strcmp(ldata->ldev.name, "blue")) { data[0] = 0x04; data[1] = mode; data[2] = off_timer >> 8; data[3] = off_timer & 0xFF; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; } ret = microp_i2c_write(MICROP_I2C_WCMD_LED_MODE, data, 7); if (ret == 0) { mutex_lock(&ldata->led_data_mutex); if (mode > 1) ldata->blink = mode; ldata->mode = mode; mutex_unlock(&ldata->led_data_mutex); } return ret; } static void microp_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct microp_led_data *ldata; unsigned long flags; int ret; uint8_t mode; ldata = container_of(led_cdev, struct microp_led_data, ldev); if (brightness > 255) brightness = 255; led_cdev->brightness = brightness; spin_lock_irqsave(&ldata->brightness_lock, flags); ldata->brightness = brightness; spin_unlock_irqrestore(&ldata->brightness_lock, flags); if (brightness) mode = 1; else mode = 0; ret = microp_write_led_mode(led_cdev, mode, 0xffff); if (ret) pr_err("%s: led_brightness_set failed to set mode\n", __func__); } static void microp_led_jogball_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct microp_led_data *ldata; unsigned long flags; uint8_t data[3] = {0, 0, 0}; int ret = 0; ldata = container_of(led_cdev, struct microp_led_data, ldev); spin_lock_irqsave(&ldata->brightness_lock, flags); ldata->brightness = brightness; spin_unlock_irqrestore(&ldata->brightness_lock, flags); switch (brightness) { case 0: data[0] = 0; break; case 1: data[0] = 3; data[1] = data[2] = 0xFF; break; case 3: data[0] = 1; data[1] = data[2] = 0xFF; break; case 7: data[0] = 2; data[1] = 0; data[2] = 60; break; default: pr_warning("%s: unknown value: %d\n", __func__, brightness); break; } ret = microp_i2c_write(MICROP_I2C_WCMD_JOGBALL_LED_MODE, data, 3); if (ret < 0) pr_err("%s failed on set jogball mode:0x%2.2X\n", __func__, data[0]); } static void microp_led_mobeam_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { ; } static void microp_led_wimax_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct microp_led_data *ldata; unsigned long flags; uint8_t data[3] = {0, 0, 0}; int ret = 0; ldata = container_of(led_cdev, struct microp_led_data, ldev); spin_lock_irqsave(&ldata->brightness_lock, flags); ldata->brightness = brightness; spin_unlock_irqrestore(&ldata->brightness_lock, flags); switch (brightness) { case 0: data[0] = 0; break; case 1: case 2: case 3: case 4: case 5: case 129: case 130: case 131: data[0] = brightness; data[1] = data[2] = 0xFF; break; default: pr_warning("%s: unknown value: %d\n", __func__, brightness); break; } ret = microp_i2c_write(MICROP_I2C_WCMD_JOGBALL_LED_MODE, data, 3); if (ret < 0) pr_err("%s failed on set wimax mode:0x%2.2X\n", __func__, data[0]); } static void microp_led_gpo_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct microp_led_data *ldata; unsigned long flags; uint8_t enable, addr, data[3] = {0, 0, 0}; int ret = 0; ldata = container_of(led_cdev, struct microp_led_data, ldev); spin_lock_irqsave(&ldata->brightness_lock, flags); ldata->brightness = brightness; spin_unlock_irqrestore(&ldata->brightness_lock, flags); enable = brightness ? 1 : 0; if (enable) addr = MICROP_I2C_WCMD_GPO_LED_STATUS_EN; else addr = MICROP_I2C_WCMD_GPO_LED_STATUS_DIS; data[0] = ldata->led_config->mask_w[0]; data[1] = ldata->led_config->mask_w[1]; data[2] = ldata->led_config->mask_w[2]; ret = microp_i2c_write(addr, data, 3);; if (ret < 0) pr_err("%s failed on set gpo led mode:%d\n", __func__, brightness); } static void microp_led_pwm_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct microp_led_data *ldata; unsigned long flags; uint8_t data[4] = {0, 0, 0, 0}; int ret = 0; uint8_t value; ldata = container_of(led_cdev, struct microp_led_data, ldev); spin_lock_irqsave(&ldata->brightness_lock, flags); ldata->brightness = brightness; spin_unlock_irqrestore(&ldata->brightness_lock, flags); value = brightness >= 255 ? 0x20 : 0; data[0] = ldata->led_config->fade_time; if (brightness) data[1] = ldata->led_config->init_value ? ldata->led_config->init_value : brightness; else data[1] = 0x00; data[2] = ldata->led_config->led_pin >> 8; data[3] = ldata->led_config->led_pin; ret = microp_i2c_write(MICROP_I2C_WCMD_LED_PWM, data, 4); if (ret < 0) pr_err("%s failed on set pwm led mode:0x%2.2X\n", __func__, data[1]); } static ssize_t microp_led_blink_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev; struct microp_led_data *ldata; int ret; led_cdev = (struct led_classdev *)dev_get_drvdata(dev); ldata = container_of(led_cdev, struct microp_led_data, ldev); mutex_lock(&ldata->led_data_mutex); ret = sprintf(buf, "%d\n", ldata->blink ? ldata->blink - 1 : 0); mutex_unlock(&ldata->led_data_mutex); return ret; } static ssize_t microp_led_blink_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev *led_cdev; struct microp_led_data *ldata; int val, ret; uint8_t mode; val = -1; sscanf(buf, "%u", &val); if (val < 0 || val > 255) return -EINVAL; led_cdev = (struct led_classdev *)dev_get_drvdata(dev); ldata = container_of(led_cdev, struct microp_led_data, ldev); mutex_lock(&ldata->led_data_mutex); switch (val) { case 0: /* stop flashing */ ldata->blink = 0; if (led_cdev->brightness) mode = 1; else mode = 0; break; case 1: case 2: case 3: mode = val + 1; break; case 4: if (!strcmp(ldata->ldev.name, "amber")) { mode = val + 1; break; } default: mutex_unlock(&ldata->led_data_mutex); return -EINVAL; } mutex_unlock(&ldata->led_data_mutex); ret = microp_write_led_mode(led_cdev, mode, 0xffff); if (ret) pr_err("%s:%s set blink failed\n", __func__, led_cdev->name); return count; } static DEVICE_ATTR(blink, 0644, microp_led_blink_show, microp_led_blink_store); static ssize_t microp_led_off_timer_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev; struct microp_led_data *ldata; uint8_t data[2]; int ret, offtime; led_cdev = (struct led_classdev *)dev_get_drvdata(dev); ldata = container_of(led_cdev, struct microp_led_data, ldev); dev_dbg(dev, "Getting %s remaining time\n", led_cdev->name); if (!strcmp(ldata->ldev.name, "green")) ret = microp_i2c_read(MICROP_I2C_RCMD_GREEN_LED_REMAIN_TIME, data, 2); else if (!strcmp(ldata->ldev.name, "amber")) ret = microp_i2c_read(MICROP_I2C_RCMD_AMBER_LED_REMAIN_TIME, data, 2); else if (!strcmp(ldata->ldev.name, "blue")) ret = microp_i2c_read(MICROP_I2C_RCMD_BLUE_LED_REMAIN_TIME, data, 2); else { pr_err("%s: Unknown led %s\n", __func__, ldata->ldev.name); return -EINVAL; } if (ret) pr_err("%s: %s get off_timer failed\n", __func__, led_cdev->name); offtime = (int)((data[1] | data[0] << 8) * 2); ret = sprintf(buf, "Time remains %d:%d\n", offtime / 60, offtime % 60); return ret; } static ssize_t microp_led_off_timer_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev *led_cdev; struct microp_led_data *ldata; int min, sec, ret; uint16_t off_timer; min = -1; sec = -1; sscanf(buf, "%d %d", &min, &sec); if (min < 0 || min > 255) return -EINVAL; if (sec < 0 || sec > 255) return -EINVAL; led_cdev = (struct led_classdev *)dev_get_drvdata(dev); ldata = container_of(led_cdev, struct microp_led_data, ldev); dev_dbg(dev, "Setting %s off_timer to %d min %d sec\n", led_cdev->name, min, sec); if (!min && !sec) off_timer = 0xFFFF; else off_timer = (min * 60 + sec) / 2; ret = microp_write_led_mode(led_cdev, ldata->mode, off_timer); if (ret) pr_err("%s: %s set off_timer %d min %d sec failed\n", __func__, led_cdev->name, min, sec); return count; } static DEVICE_ATTR(off_timer, 0644, microp_led_off_timer_show, microp_led_off_timer_store); static ssize_t microp_jogball_color_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev *led_cdev; struct microp_led_data *ldata; struct i2c_client *client; int rpwm, gpwm, bpwm, ret; uint8_t data[4]; rpwm = -1; gpwm = -1; bpwm = -1; sscanf(buf, "%d %d %d", &rpwm, &gpwm, &bpwm); if (rpwm < 0 || rpwm > 255) return -EINVAL; if (gpwm < 0 || gpwm > 255) return -EINVAL; if (bpwm < 0 || bpwm > 255) return -EINVAL; led_cdev = (struct led_classdev *)dev_get_drvdata(dev); ldata = container_of(led_cdev, struct microp_led_data, ldev); client = to_i2c_client(dev->parent); dev_dbg(&client->dev, "Setting %s color to R=%d, G=%d, B=%d\n", led_cdev->name, rpwm, gpwm, bpwm); data[0] = rpwm; data[1] = gpwm; data[2] = bpwm; data[3] = 0x00; ret = microp_i2c_write(MICROP_I2C_WCMD_JOGBALL_LED_PWM_SET, data, 4); if (ret) { dev_err(&client->dev, "%s set color R=%d G=%d B=%d failed\n", led_cdev->name, rpwm, gpwm, bpwm); } return count; } static DEVICE_ATTR(color, 0644, NULL, microp_jogball_color_store); static ssize_t microp_mobeam_read_status_show(struct device *dev, struct device_attribute *attr, char *buf) { uint8_t data[2] = {0, 0}; int ret; pr_info("%s\n", __func__); ret = microp_i2c_read(MICROP_I2C_RCMD_MOBEAM_STATUS, data, 2); if (ret == 0) ret = sprintf(buf, "%d %d\n", data[0], data[1]); else data[0] = data[1] = 0; return ret; } static DEVICE_ATTR(read_status, 0444, microp_mobeam_read_status_show, NULL); static ssize_t microp_mobeam_download_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int i, ret, num; uint8_t data[73] ; /* Size of cbitstream array MAX 73 bytes. */ pr_info("%s\n", __func__); memset(data, 0x00, sizeof(data)); num = *(buf++); if (num < 0 || num > 73) return -EINVAL; pr_info("%s: count=%d\n", __func__, count); for (i = 0; i < count; i++) data[i] = *(buf + i); ret = microp_i2c_write(MICROP_I2C_WCMD_MOBEAM_DL, data, num); if (ret != 0) count = 0; return count; } static DEVICE_ATTR(data_download, 0644, NULL, microp_mobeam_download_store); static ssize_t microp_mobeam_send_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { uint8_t data[2]; unsigned char num; int ret; pr_info("%s\n", __func__); num = *buf; if (num < 0 || num > 73) return -EINVAL; data[0] = (uint8_t)num; data[1] = 0; ret = microp_i2c_write(MICROP_I2C_WCMD_MOBEAM_SEND, data, 2); if (ret != 0) count = 0; return count; } static DEVICE_ATTR(send_data, 0644, NULL, microp_mobeam_send_store); static uint8_t leds_data[7]; static ssize_t microp_mobeam_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val; uint8_t data[7]; int ret; pr_info("%s\n", __func__); val = -1; sscanf(buf, "%u", &val); if (val != 1 && val != 0) return -EINVAL; if (val) { ret = microp_i2c_read(MICROP_I2C_RCMD_LED_STATUS, leds_data, 7); data[0] = 0x03; data[1] = 0x00; data[2] = 0x00; data[3] = 0x00; data[4] = 0x00; data[5] = 0x00; data[6] = 0x00; ret = microp_i2c_write(MICROP_I2C_WCMD_LED_MODE, data, 7); pr_info("%s: enabled\n", __func__); msleep(150); microp_mobeam_enable(1); } else { microp_mobeam_enable(0); ret = microp_i2c_write(MICROP_I2C_WCMD_LED_MODE, leds_data, 7); } return count; } static DEVICE_ATTR(mobeam_enable, 0644, NULL, microp_mobeam_enable_store); static ssize_t microp_mobeam_stop_led(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val; uint8_t data[2]; pr_info("%s\n", __func__); val = -1; sscanf(buf, "%u", &val); data[0] = 0x00; data[1] = 0x01; return count; } static DEVICE_ATTR(stop_led, 0644, NULL, microp_mobeam_stop_led); static int microp_led_probe(struct platform_device *pdev) { struct microp_led_platform_data *pdata; struct microp_led_data *ldata; int i, ret; pdata = pdev->dev.platform_data; if (pdata == NULL) { pr_err("%s: platform data is NULL\n", __func__); return -ENODEV; } ldata = kzalloc(sizeof(struct microp_led_data) * pdata->num_leds, GFP_KERNEL); if (!ldata && pdata->num_leds) { ret = -ENOMEM; pr_err("%s: failed on allocate ldata\n", __func__); goto err_exit; } dev_set_drvdata(&pdev->dev, ldata); for (i = 0; i < pdata->num_leds; i++) { ldata[i].led_config = pdata->led_config + i; ldata[i].ldev.name = pdata->led_config[i].name; if (pdata->led_config[i].type == LED_JOGBALL) ldata[i].ldev.brightness_set = microp_led_jogball_brightness_set; else if (pdata->led_config[i].type == LED_GPO) ldata[i].ldev.brightness_set = microp_led_gpo_brightness_set; else if (pdata->led_config[i].type == LED_PWM) ldata[i].ldev.brightness_set = microp_led_pwm_brightness_set; else if (pdata->led_config[i].type == LED_RGB) ldata[i].ldev.brightness_set = microp_led_brightness_set; else if (pdata->led_config[i].type == LED_WIMAX) ldata[i].ldev.brightness_set = microp_led_wimax_brightness_set; else if (pdata->led_config[i].type == LED_MOBEAM) ldata[i].ldev.brightness_set = microp_led_mobeam_brightness_set; mutex_init(&ldata[i].led_data_mutex); spin_lock_init(&ldata[i].brightness_lock); ret = led_classdev_register(&pdev->dev, &ldata[i].ldev); if (ret < 0) { pr_err("%s: failed on led_classdev_register [%s]\n", __func__, ldata[i].ldev.name); goto err_register_led_cdev; } } for (i = 0; i < pdata->num_leds; i++) { if (pdata->led_config[i].type != LED_RGB) continue; ret = device_create_file(ldata[i].ldev.dev, &dev_attr_blink); if (ret < 0) { pr_err("%s: failed on create attr blink [%d]\n", __func__, i); goto err_register_attr_blink; } } for (i = 0; i < pdata->num_leds; i++) { if (pdata->led_config[i].type != LED_RGB) continue; ret = device_create_file(ldata[i].ldev.dev, &dev_attr_off_timer); if (ret < 0) { pr_err("%s: failed on create attr off_timer [%d]\n", __func__, i); goto err_register_attr_off_timer; } } for (i = 0; i < pdata->num_leds; i++) { if (pdata->led_config[i].type != LED_JOGBALL) continue; ret = device_create_file(ldata[i].ldev.dev, &dev_attr_color); if (ret < 0) { pr_err("%s: failed on create attr jogball color\n", __func__); goto err_register_attr_color; } else break; } for (i = 0; i < pdata->num_leds; i++) { if (pdata->led_config[i].type != LED_MOBEAM) continue; ret = device_create_file(ldata[i].ldev.dev, &dev_attr_data_download); if (ret < 0) { pr_err("%s: failed on create attr data download\n", __func__); goto err_create_mo_download_attr_file; } ret = device_create_file(ldata[i].ldev.dev, &dev_attr_send_data); if (ret < 0) { pr_err("%s: failed on create attr send data\n", __func__); goto err_create_mo_send_attr_file; } ret = device_create_file(ldata[i].ldev.dev, &dev_attr_read_status); if (ret < 0) { pr_err("%s: failed on create attr read status\n", __func__); goto err_create_mo_read_attr_file; } ret = device_create_file(ldata[i].ldev.dev, &dev_attr_mobeam_enable); if (ret < 0) { pr_err("%s: failed on create attr enable\n", __func__); goto err_create_mo_enable_attr_file; } ret = device_create_file(ldata[i].ldev.dev, &dev_attr_stop_led); if (ret < 0) { pr_err("%s: failed on create attr stop led\n", __func__); goto err_create_mo_stop_attr_file; } break; } pr_info("%s: succeeded\n", __func__); return 0; err_create_mo_stop_attr_file: device_remove_file(ldata[i].ldev.dev, &dev_attr_mobeam_enable); err_create_mo_enable_attr_file: device_remove_file(ldata[i].ldev.dev, &dev_attr_read_status); err_create_mo_read_attr_file: device_remove_file(ldata[i].ldev.dev, &dev_attr_send_data); err_create_mo_send_attr_file: device_remove_file(ldata[i].ldev.dev, &dev_attr_data_download); err_create_mo_download_attr_file: i = pdata->num_leds; err_register_attr_color: for (i--; i >= 0; i--) { if (pdata->led_config[i].type != LED_JOGBALL) continue; device_remove_file(ldata[i].ldev.dev, &dev_attr_color); } i = pdata->num_leds; err_register_attr_off_timer: for (i--; i >= 0; i--) { if (pdata->led_config[i].type != LED_RGB) continue; device_remove_file(ldata[i].ldev.dev, &dev_attr_off_timer); } i = pdata->num_leds; err_register_attr_blink: for (i--; i >= 0; i--) { if (pdata->led_config[i].type != LED_RGB) continue; device_remove_file(ldata[i].ldev.dev, &dev_attr_blink); } i = pdata->num_leds; err_register_led_cdev: for (i--; i >= 0; i--) led_classdev_unregister(&ldata[i].ldev); kfree(ldata); err_exit: return ret; } static int __devexit microp_led_remove(struct platform_device *pdev) { struct microp_led_platform_data *pdata; struct microp_led_data *ldata; int i; pdata = pdev->dev.platform_data; ldata = platform_get_drvdata(pdev); for (i = 0; i < pdata->num_leds; i++) { led_classdev_unregister(&ldata[i].ldev); if (pdata->led_config[i].type == LED_RGB) { device_remove_file(ldata[i].ldev.dev, &dev_attr_off_timer); device_remove_file(ldata[i].ldev.dev, &dev_attr_blink); } else if (pdata->led_config[i].type == LED_JOGBALL) device_remove_file(ldata[i].ldev.dev, &dev_attr_color); } kfree(ldata); return 0; } static struct platform_driver microp_led_driver = { .probe = microp_led_probe, .remove = __devexit_p(microp_led_remove), .driver = { .name = "leds-microp", .owner = THIS_MODULE, }, }; int __init microp_led_init(void) { return platform_driver_register(µp_led_driver); } void microp_led_exit(void) { platform_driver_unregister(µp_led_driver); } module_init(microp_led_init); module_exit(microp_led_exit); MODULE_DESCRIPTION("Atmega MicroP led driver"); MODULE_LICENSE("GPL"); #endif /* end of #ifdef CONFIG_MICROP_COMMON*/