| /* | |
| * Simple driver for Texas Instruments LM3639 Backlight + Flash LED driver chip | |
| * Copyright (C) 2012 Texas Instruments | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| * | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/slab.h> | |
| #include <linux/i2c.h> | |
| #include <linux/leds.h> | |
| #include <linux/backlight.h> | |
| #include <linux/err.h> | |
| #include <linux/delay.h> | |
| #include <linux/uaccess.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/regmap.h> | |
| #include <linux/platform_data/lm3639_bl.h> | |
| #define REG_DEV_ID 0x00 | |
| #define REG_CHECKSUM 0x01 | |
| #define REG_BL_CONF_1 0x02 | |
| #define REG_BL_CONF_2 0x03 | |
| #define REG_BL_CONF_3 0x04 | |
| #define REG_BL_CONF_4 0x05 | |
| #define REG_FL_CONF_1 0x06 | |
| #define REG_FL_CONF_2 0x07 | |
| #define REG_FL_CONF_3 0x08 | |
| #define REG_IO_CTRL 0x09 | |
| #define REG_ENABLE 0x0A | |
| #define REG_FLAG 0x0B | |
| #define REG_MAX REG_FLAG | |
| struct lm3639_chip_data { | |
| struct device *dev; | |
| struct lm3639_platform_data *pdata; | |
| struct backlight_device *bled; | |
| struct led_classdev cdev_flash; | |
| struct led_classdev cdev_torch; | |
| struct regmap *regmap; | |
| unsigned int bled_mode; | |
| unsigned int bled_map; | |
| unsigned int last_flag; | |
| }; | |
| /* initialize chip */ | |
| static int lm3639_chip_init(struct lm3639_chip_data *pchip) | |
| { | |
| int ret; | |
| unsigned int reg_val; | |
| struct lm3639_platform_data *pdata = pchip->pdata; | |
| /* input pins config. */ | |
| ret = | |
| regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x08, | |
| pdata->pin_pwm); | |
| if (ret < 0) | |
| goto out; | |
| reg_val = (pdata->pin_pwm & 0x40) | pdata->pin_strobe | pdata->pin_tx; | |
| ret = regmap_update_bits(pchip->regmap, REG_IO_CTRL, 0x7C, reg_val); | |
| if (ret < 0) | |
| goto out; | |
| /* init brightness */ | |
| ret = regmap_write(pchip->regmap, REG_BL_CONF_4, pdata->init_brt_led); | |
| if (ret < 0) | |
| goto out; | |
| ret = regmap_write(pchip->regmap, REG_BL_CONF_3, pdata->init_brt_led); | |
| if (ret < 0) | |
| goto out; | |
| /* output pins config. */ | |
| if (!pdata->init_brt_led) { | |
| reg_val = pdata->fled_pins; | |
| reg_val |= pdata->bled_pins; | |
| } else { | |
| reg_val = pdata->fled_pins; | |
| reg_val |= pdata->bled_pins | 0x01; | |
| } | |
| ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x79, reg_val); | |
| if (ret < 0) | |
| goto out; | |
| return ret; | |
| out: | |
| dev_err(pchip->dev, "i2c failed to access register\n"); | |
| return ret; | |
| } | |
| /* update and get brightness */ | |
| static int lm3639_bled_update_status(struct backlight_device *bl) | |
| { | |
| int ret; | |
| unsigned int reg_val; | |
| struct lm3639_chip_data *pchip = bl_get_data(bl); | |
| struct lm3639_platform_data *pdata = pchip->pdata; | |
| ret = regmap_read(pchip->regmap, REG_FLAG, ®_val); | |
| if (ret < 0) | |
| goto out; | |
| if (reg_val != 0) | |
| dev_info(pchip->dev, "last flag is 0x%x\n", reg_val); | |
| /* pwm control */ | |
| if (pdata->pin_pwm) { | |
| if (pdata->pwm_set_intensity) | |
| pdata->pwm_set_intensity(bl->props.brightness, | |
| pdata->max_brt_led); | |
| else | |
| dev_err(pchip->dev, | |
| "No pwm control func. in plat-data\n"); | |
| return bl->props.brightness; | |
| } | |
| /* i2c control and set brigtness */ | |
| ret = regmap_write(pchip->regmap, REG_BL_CONF_4, bl->props.brightness); | |
| if (ret < 0) | |
| goto out; | |
| ret = regmap_write(pchip->regmap, REG_BL_CONF_3, bl->props.brightness); | |
| if (ret < 0) | |
| goto out; | |
| if (!bl->props.brightness) | |
| ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x01, 0x00); | |
| else | |
| ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x01, 0x01); | |
| if (ret < 0) | |
| goto out; | |
| return bl->props.brightness; | |
| out: | |
| dev_err(pchip->dev, "i2c failed to access registers\n"); | |
| return bl->props.brightness; | |
| } | |
| static int lm3639_bled_get_brightness(struct backlight_device *bl) | |
| { | |
| int ret; | |
| unsigned int reg_val; | |
| struct lm3639_chip_data *pchip = bl_get_data(bl); | |
| struct lm3639_platform_data *pdata = pchip->pdata; | |
| if (pdata->pin_pwm) { | |
| if (pdata->pwm_get_intensity) | |
| bl->props.brightness = pdata->pwm_get_intensity(); | |
| else | |
| dev_err(pchip->dev, | |
| "No pwm control func. in plat-data\n"); | |
| return bl->props.brightness; | |
| } | |
| ret = regmap_read(pchip->regmap, REG_BL_CONF_1, ®_val); | |
| if (ret < 0) | |
| goto out; | |
| if (reg_val & 0x10) | |
| ret = regmap_read(pchip->regmap, REG_BL_CONF_4, ®_val); | |
| else | |
| ret = regmap_read(pchip->regmap, REG_BL_CONF_3, ®_val); | |
| if (ret < 0) | |
| goto out; | |
| bl->props.brightness = reg_val; | |
| return bl->props.brightness; | |
| out: | |
| dev_err(pchip->dev, "i2c failed to access register\n"); | |
| return bl->props.brightness; | |
| } | |
| static const struct backlight_ops lm3639_bled_ops = { | |
| .options = BL_CORE_SUSPENDRESUME, | |
| .update_status = lm3639_bled_update_status, | |
| .get_brightness = lm3639_bled_get_brightness, | |
| }; | |
| /* backlight mapping mode */ | |
| static ssize_t lm3639_bled_mode_store(struct device *dev, | |
| struct device_attribute *devAttr, | |
| const char *buf, size_t size) | |
| { | |
| ssize_t ret; | |
| struct lm3639_chip_data *pchip = dev_get_drvdata(dev); | |
| unsigned int state; | |
| ret = kstrtouint(buf, 10, &state); | |
| if (ret) | |
| goto out_input; | |
| if (!state) | |
| ret = | |
| regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x10, | |
| 0x00); | |
| else | |
| ret = | |
| regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x10, | |
| 0x10); | |
| if (ret < 0) | |
| goto out; | |
| return size; | |
| out: | |
| dev_err(pchip->dev, "%s:i2c access fail to register\n", __func__); | |
| return ret; | |
| out_input: | |
| dev_err(pchip->dev, "%s:input conversion fail\n", __func__); | |
| return ret; | |
| } | |
| static DEVICE_ATTR(bled_mode, S_IWUSR, NULL, lm3639_bled_mode_store); | |
| /* torch */ | |
| static void lm3639_torch_brightness_set(struct led_classdev *cdev, | |
| enum led_brightness brightness) | |
| { | |
| int ret; | |
| unsigned int reg_val; | |
| struct lm3639_chip_data *pchip; | |
| pchip = container_of(cdev, struct lm3639_chip_data, cdev_torch); | |
| ret = regmap_read(pchip->regmap, REG_FLAG, ®_val); | |
| if (ret < 0) | |
| goto out; | |
| if (reg_val != 0) | |
| dev_info(pchip->dev, "last flag is 0x%x\n", reg_val); | |
| /* brightness 0 means off state */ | |
| if (!brightness) { | |
| ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x00); | |
| if (ret < 0) | |
| goto out; | |
| return; | |
| } | |
| ret = regmap_update_bits(pchip->regmap, | |
| REG_FL_CONF_1, 0x70, (brightness - 1) << 4); | |
| if (ret < 0) | |
| goto out; | |
| ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x02); | |
| if (ret < 0) | |
| goto out; | |
| return; | |
| out: | |
| dev_err(pchip->dev, "i2c failed to access register\n"); | |
| } | |
| /* flash */ | |
| static void lm3639_flash_brightness_set(struct led_classdev *cdev, | |
| enum led_brightness brightness) | |
| { | |
| int ret; | |
| unsigned int reg_val; | |
| struct lm3639_chip_data *pchip; | |
| pchip = container_of(cdev, struct lm3639_chip_data, cdev_flash); | |
| ret = regmap_read(pchip->regmap, REG_FLAG, ®_val); | |
| if (ret < 0) | |
| goto out; | |
| if (reg_val != 0) | |
| dev_info(pchip->dev, "last flag is 0x%x\n", reg_val); | |
| /* torch off before flash control */ | |
| ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x00); | |
| if (ret < 0) | |
| goto out; | |
| /* brightness 0 means off state */ | |
| if (!brightness) | |
| return; | |
| ret = regmap_update_bits(pchip->regmap, | |
| REG_FL_CONF_1, 0x0F, brightness - 1); | |
| if (ret < 0) | |
| goto out; | |
| ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x06); | |
| if (ret < 0) | |
| goto out; | |
| return; | |
| out: | |
| dev_err(pchip->dev, "i2c failed to access register\n"); | |
| } | |
| static const struct regmap_config lm3639_regmap = { | |
| .reg_bits = 8, | |
| .val_bits = 8, | |
| .max_register = REG_MAX, | |
| }; | |
| static int lm3639_probe(struct i2c_client *client, | |
| const struct i2c_device_id *id) | |
| { | |
| int ret; | |
| struct lm3639_chip_data *pchip; | |
| struct lm3639_platform_data *pdata = dev_get_platdata(&client->dev); | |
| struct backlight_properties props; | |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { | |
| dev_err(&client->dev, "i2c functionality check fail.\n"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (pdata == NULL) { | |
| dev_err(&client->dev, "Needs Platform Data.\n"); | |
| return -ENODATA; | |
| } | |
| pchip = devm_kzalloc(&client->dev, | |
| sizeof(struct lm3639_chip_data), GFP_KERNEL); | |
| if (!pchip) | |
| return -ENOMEM; | |
| pchip->pdata = pdata; | |
| pchip->dev = &client->dev; | |
| pchip->regmap = devm_regmap_init_i2c(client, &lm3639_regmap); | |
| if (IS_ERR(pchip->regmap)) { | |
| ret = PTR_ERR(pchip->regmap); | |
| dev_err(&client->dev, "fail : allocate register map: %d\n", | |
| ret); | |
| return ret; | |
| } | |
| i2c_set_clientdata(client, pchip); | |
| /* chip initialize */ | |
| ret = lm3639_chip_init(pchip); | |
| if (ret < 0) { | |
| dev_err(&client->dev, "fail : chip init\n"); | |
| goto err_out; | |
| } | |
| /* backlight */ | |
| props.type = BACKLIGHT_RAW; | |
| props.brightness = pdata->init_brt_led; | |
| props.max_brightness = pdata->max_brt_led; | |
| pchip->bled = | |
| devm_backlight_device_register(pchip->dev, "lm3639_bled", | |
| pchip->dev, pchip, &lm3639_bled_ops, | |
| &props); | |
| if (IS_ERR(pchip->bled)) { | |
| dev_err(&client->dev, "fail : backlight register\n"); | |
| ret = PTR_ERR(pchip->bled); | |
| goto err_out; | |
| } | |
| ret = device_create_file(&(pchip->bled->dev), &dev_attr_bled_mode); | |
| if (ret < 0) { | |
| dev_err(&client->dev, "failed : add sysfs entries\n"); | |
| goto err_out; | |
| } | |
| /* flash */ | |
| pchip->cdev_flash.name = "lm3639_flash"; | |
| pchip->cdev_flash.max_brightness = 16; | |
| pchip->cdev_flash.brightness_set = lm3639_flash_brightness_set; | |
| ret = led_classdev_register((struct device *) | |
| &client->dev, &pchip->cdev_flash); | |
| if (ret < 0) { | |
| dev_err(&client->dev, "fail : flash register\n"); | |
| goto err_flash; | |
| } | |
| /* torch */ | |
| pchip->cdev_torch.name = "lm3639_torch"; | |
| pchip->cdev_torch.max_brightness = 8; | |
| pchip->cdev_torch.brightness_set = lm3639_torch_brightness_set; | |
| ret = led_classdev_register((struct device *) | |
| &client->dev, &pchip->cdev_torch); | |
| if (ret < 0) { | |
| dev_err(&client->dev, "fail : torch register\n"); | |
| goto err_torch; | |
| } | |
| return 0; | |
| err_torch: | |
| led_classdev_unregister(&pchip->cdev_flash); | |
| err_flash: | |
| device_remove_file(&(pchip->bled->dev), &dev_attr_bled_mode); | |
| err_out: | |
| return ret; | |
| } | |
| static int lm3639_remove(struct i2c_client *client) | |
| { | |
| struct lm3639_chip_data *pchip = i2c_get_clientdata(client); | |
| regmap_write(pchip->regmap, REG_ENABLE, 0x00); | |
| if (&pchip->cdev_torch) | |
| led_classdev_unregister(&pchip->cdev_torch); | |
| if (&pchip->cdev_flash) | |
| led_classdev_unregister(&pchip->cdev_flash); | |
| if (pchip->bled) | |
| device_remove_file(&(pchip->bled->dev), &dev_attr_bled_mode); | |
| return 0; | |
| } | |
| static const struct i2c_device_id lm3639_id[] = { | |
| {LM3639_NAME, 0}, | |
| {} | |
| }; | |
| MODULE_DEVICE_TABLE(i2c, lm3639_id); | |
| static struct i2c_driver lm3639_i2c_driver = { | |
| .driver = { | |
| .name = LM3639_NAME, | |
| }, | |
| .probe = lm3639_probe, | |
| .remove = lm3639_remove, | |
| .id_table = lm3639_id, | |
| }; | |
| module_i2c_driver(lm3639_i2c_driver); | |
| MODULE_DESCRIPTION("Texas Instruments Backlight+Flash LED driver for LM3639"); | |
| MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>"); | |
| MODULE_AUTHOR("Ldd Mlp <ldd-mlp@list.ti.com>"); | |
| MODULE_LICENSE("GPL v2"); |