/* * drivers/media/i2c/lm3644.c * General device driver for TI LM3644, FLASH LED Driver * * Copyright (C) 2014 Texas Instruments * * Contact: Daniel Jeong * Ldd-Mlp * * 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. * * 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 /* registers definitions */ #define REG_ENABLE 0x01 #define REG_FLASH_LED0_BR 0x03 #define REG_FLASH_LED1_BR 0x04 #define REG_TORCH_LED0_BR 0x05 #define REG_TORCH_LED1_BR 0x06 #define REG_FLASH_TOUT 0x08 #define REG_FLAG0 0x0a #define REG_FLAG1 0x0b /* Fault Mask */ #define FAULT_TIMEOUT (1<<0) #define FAULT_SHORT_CIRCUIT (0x70) #define FAULT_UVLO (1<<1) #define FAULT_IVFM (1<<2) #define FAULT_OCP (1<<3) #define FAULT_OVERTEMP (1<<2) #define FAULT_NTC_TRIP (1<<0) #define FAULT_OVP (1<<1) #define MASK_MODE 0x0C enum led_mode { MODE_SHDN = 0x00, MODE_IR_DRV = 0x04, MODE_TORCH = 0x08, MODE_FLASH = 0x0C, }; /* * struct lm3644_flash * * @pdata: platform data * @regmap: reg. map for i2c * @lock: muxtex for serial access. * @led_mode: V4L2 LED mode * @ctrls_led: V4L2 contols * @subdev_led: V4L2 subdev */ struct lm3644_flash { struct device *dev; struct lm3644_platform_data *pdata; struct regmap *regmap; struct mutex lock; struct v4l2_ctrl_handler ctrls_led[LM3644_LED_MAX]; struct v4l2_subdev subdev_led[LM3644_LED_MAX]; /* caching control register to reduce redundant i2c accesses */ u8 enable_reg; }; #define to_lm3644_flash(_ctrl, _no) \ container_of(_ctrl->handler, struct lm3644_flash, ctrls_led[_no]) /* enable mode control */ static int lm3644_mode_ctrl(struct lm3644_flash *flash, enum v4l2_flash_led_mode led_mode) { switch (led_mode) { case V4L2_FLASH_LED_MODE_NONE: return regmap_write(flash->regmap, REG_ENABLE, flash->enable_reg | MODE_SHDN); case V4L2_FLASH_LED_MODE_IR: return regmap_write(flash->regmap, REG_ENABLE, flash->enable_reg | MODE_IR_DRV); case V4L2_FLASH_LED_MODE_TORCH: return regmap_write(flash->regmap, REG_ENABLE, flash->enable_reg | MODE_TORCH); case V4L2_FLASH_LED_MODE_FLASH: return regmap_write(flash->regmap, REG_ENABLE, flash->enable_reg | MODE_FLASH); } return -EINVAL; } /* torch0/1 brightness control */ static int lm3644_torch_brt_ctrl(struct lm3644_flash *flash, enum lm3644_led_id led_no, unsigned int brt) { int rval; u8 br_bits; br_bits = LM3644_TORCH_BRT_uA_TO_REG(brt); if (led_no == LM3644_LED0) rval = regmap_update_bits(flash->regmap, REG_TORCH_LED0_BR, 0x7f, br_bits); else rval = regmap_update_bits(flash->regmap, REG_TORCH_LED1_BR, 0x7f, br_bits); return rval; } /* flash0/1 brightness control */ static int lm3644_flash_brt_ctrl(struct lm3644_flash *flash, enum lm3644_led_id led_no, unsigned int brt) { int rval; u8 br_bits; br_bits = LM3644_FLASH_BRT_uA_TO_REG(brt); if (led_no == LM3644_LED0) rval = regmap_update_bits(flash->regmap, REG_FLASH_LED0_BR, 0x7f, br_bits); else rval = regmap_update_bits(flash->regmap, REG_FLASH_LED1_BR, 0x7f, br_bits); return rval; } static int lm3644_get_ctrl(struct v4l2_ctrl *ctrl, enum lm3644_led_id led_no) { struct lm3644_flash *flash = to_lm3644_flash(ctrl, led_no); int rval = -EINVAL; mutex_lock(&flash->lock); if (ctrl->id == V4L2_CID_FLASH_FAULT) { unsigned int reg_val; rval = regmap_read(flash->regmap, REG_FLAG0, ®_val); if (rval < 0) goto out; ctrl->val = 0; if (reg_val & FAULT_TIMEOUT) ctrl->val |= V4L2_FLASH_FAULT_TIMEOUT; if (reg_val & FAULT_UVLO) ctrl->val |= V4L2_FLASH_FAULT_UNDER_VOLTAGE; if (reg_val & FAULT_OVERTEMP) ctrl->val |= V4L2_FLASH_FAULT_OVER_TEMPERATURE; if (reg_val & FAULT_OCP) ctrl->val |= V4L2_FLASH_FAULT_OVER_CURRENT; if (reg_val & FAULT_SHORT_CIRCUIT) ctrl->val |= V4L2_FLASH_FAULT_SHORT_CIRCUIT; rval = regmap_read(flash->regmap, REG_FLAG1, ®_val); if (rval < 0) goto out; if (reg_val & FAULT_IVFM) ctrl->val |= V4L2_FLASH_FAULT_INPUT_VOLTAGE; if (reg_val & FAULT_NTC_TRIP) ctrl->val |= V4L2_FLASH_FAULT_LED_OVER_TEMPERATURE; if (reg_val & FAULT_OVP) ctrl->val |= V4L2_FLASH_FAULT_OVER_VOLTAGE; } out: mutex_unlock(&flash->lock); return rval; } static int lm3644_set_ctrl(struct v4l2_ctrl *ctrl, enum lm3644_led_id led_no) { struct lm3644_flash *flash = to_lm3644_flash(ctrl, led_no); u8 tout_bits; int rval = -EINVAL; unsigned int reg_val; mutex_lock(&flash->lock); switch (ctrl->id) { case V4L2_CID_FLASH_LED_MODE: if (ctrl->val != V4L2_FLASH_LED_MODE_FLASH) { rval = lm3644_mode_ctrl(flash, ctrl->val); break; } /* switch to SHDN mode before flash strobe on */ rval = lm3644_mode_ctrl(flash, V4L2_FLASH_LED_MODE_NONE); break; case V4L2_CID_FLASH_STROBE_SOURCE: rval = regmap_update_bits(flash->regmap, REG_ENABLE, 0x20, (ctrl->val) << 6); if (rval < 0) goto err_out; rval = regmap_read(flash->regmap, REG_ENABLE, ®_val); if (rval < 0) goto err_out; flash->enable_reg = reg_val & (~MASK_MODE); break; case V4L2_CID_FLASH_STROBE: /* read and check current mode of chip to start flash */ rval = regmap_read(flash->regmap, REG_ENABLE, ®_val); if (rval < 0 || ((reg_val & MASK_MODE) != MODE_SHDN)) goto err_out; /* flash on */ rval = lm3644_mode_ctrl(flash, V4L2_FLASH_LED_MODE_FLASH); break; case V4L2_CID_FLASH_STROBE_STOP: /* * flash mode will be turned automatically * from FLASH mode to SHDN mode after flash duration timeout * read and check current mode of chip to stop flash */ rval = regmap_read(flash->regmap, REG_ENABLE, ®_val); if (rval < 0) goto err_out; if ((reg_val & MASK_MODE) == MODE_FLASH) rval = lm3644_mode_ctrl(flash, V4L2_FLASH_LED_MODE_NONE); break; case V4L2_CID_FLASH_TIMEOUT: tout_bits = LM3644_FLASH_TOUT_ms_TO_REG(ctrl->val); rval = regmap_update_bits(flash->regmap, REG_FLASH_TOUT, 0x0f, tout_bits); break; case V4L2_CID_FLASH_INTENSITY: rval = lm3644_flash_brt_ctrl(flash, led_no, ctrl->val); break; case V4L2_CID_FLASH_TORCH_INTENSITY: rval = lm3644_torch_brt_ctrl(flash, led_no, ctrl->val); break; } err_out: mutex_unlock(&flash->lock); return rval; } static int lm3644_led1_get_ctrl(struct v4l2_ctrl *ctrl) { return lm3644_get_ctrl(ctrl, LM3644_LED1); } static int lm3644_led1_set_ctrl(struct v4l2_ctrl *ctrl) { return lm3644_set_ctrl(ctrl, LM3644_LED1); } static int lm3644_led0_get_ctrl(struct v4l2_ctrl *ctrl) { return lm3644_get_ctrl(ctrl, LM3644_LED0); } static int lm3644_led0_set_ctrl(struct v4l2_ctrl *ctrl) { return lm3644_set_ctrl(ctrl, LM3644_LED0); } static const struct v4l2_ctrl_ops lm3644_led_ctrl_ops[LM3644_LED_MAX] = { [LM3644_LED0] = { .g_volatile_ctrl = lm3644_led0_get_ctrl, .s_ctrl = lm3644_led0_set_ctrl, }, [LM3644_LED1] = { .g_volatile_ctrl = lm3644_led1_get_ctrl, .s_ctrl = lm3644_led1_set_ctrl, } }; static int lm3644_init_controls(struct lm3644_flash *flash, enum lm3644_led_id led_no) { struct v4l2_ctrl *fault; struct v4l2_ctrl_handler *hdl = &flash->ctrls_led[led_no]; const struct v4l2_ctrl_ops *ops = &lm3644_led_ctrl_ops[led_no]; v4l2_ctrl_handler_init(hdl, 8); /* flash mode */ v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_FLASH_LED_MODE, V4L2_FLASH_LED_MODE_IR, ~0x7, V4L2_FLASH_LED_MODE_NONE); /* flash source */ v4l2_ctrl_new_std_menu(hdl, ops, V4L2_CID_FLASH_STROBE_SOURCE, 0x1, ~0x3, V4L2_FLASH_STROBE_SOURCE_SOFTWARE); /* flash strobe */ v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); /* flash strobe stop */ v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); /* flash strobe timeout */ v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_TIMEOUT, LM3644_FLASH_TOUT_MIN, LM3644_FLASH_TOUT_MAX, LM3644_FLASH_TOUT_LOW_STEP, LM3644_FLASH_TOUT_MAX); /* flash brt */ v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_INTENSITY, LM3644_FLASH_BRT_MIN, LM3644_FLASH_BRT_MAX, LM3644_FLASH_BRT_STEP, LM3644_FLASH_BRT_MAX); /* torch brt */ v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_TORCH_INTENSITY, LM3644_TORCH_BRT_MIN, LM3644_TORCH_BRT_MAX, LM3644_TORCH_BRT_STEP, LM3644_TORCH_BRT_MAX); /* fault */ fault = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FLASH_FAULT, 0, V4L2_FLASH_FAULT_TIMEOUT | V4L2_FLASH_FAULT_SHORT_CIRCUIT | V4L2_FLASH_FAULT_UNDER_VOLTAGE | V4L2_FLASH_FAULT_INPUT_VOLTAGE | V4L2_FLASH_FAULT_OVER_CURRENT | V4L2_FLASH_FAULT_OVER_TEMPERATURE | V4L2_FLASH_FAULT_LED_OVER_TEMPERATURE | V4L2_FLASH_FAULT_OVER_VOLTAGE, 0, 0); if (fault != NULL) fault->flags |= V4L2_CTRL_FLAG_VOLATILE; if (hdl->error) return hdl->error; flash->subdev_led[led_no].ctrl_handler = hdl; return 0; } /* initialize device */ static const struct v4l2_subdev_ops lm3644_ops = { .core = NULL, }; static const struct regmap_config lm3644_regmap = { .reg_bits = 8, .val_bits = 8, .max_register = 0xFF, }; static int lm3644_subdev_init(struct lm3644_flash *flash, enum lm3644_led_id led_no, char *led_name) { struct i2c_client *client = to_i2c_client(flash->dev); int rval; v4l2_i2c_subdev_init(&flash->subdev_led[led_no], client, &lm3644_ops); flash->subdev_led[led_no].flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; strcpy(flash->subdev_led[led_no].name, led_name); rval = lm3644_init_controls(flash, led_no); if (rval) goto err_out; rval = media_entity_init(&flash->subdev_led[led_no].entity, 0, NULL, 0); if (rval < 0) goto err_out; flash->subdev_led[led_no].entity.type = MEDIA_ENT_T_V4L2_SUBDEV_FLASH; return rval; err_out: v4l2_ctrl_handler_free(&flash->ctrls_led[led_no]); return rval; } static int lm3644_init_device(struct lm3644_flash *flash) { struct lm3644_platform_data *pdata = flash->pdata; int rval; unsigned int reg_val; rval = regmap_read(flash->regmap, REG_ENABLE, ®_val); if (rval < 0) return rval; /* set led0/1 enable bits */ reg_val |= flash->pdata->led0_enable; reg_val |= flash->pdata->led1_enable; /* save enable register to reduce redundant i2c accesses */ flash->enable_reg = reg_val & 0xf3; rval = regmap_write(flash->regmap, REG_ENABLE, reg_val); if (rval < 0) return rval; /* set output mode */ rval = lm3644_mode_ctrl(flash, V4L2_FLASH_LED_MODE_NONE); if (rval < 0) return rval; /* set override */ rval = regmap_update_bits(flash->regmap, REG_FLASH_LED0_BR, 0x80, pdata->flash1_override); rval |= regmap_update_bits(flash->regmap, REG_TORCH_LED0_BR, 0x80, pdata->torch1_override); if (rval < 0) return rval; /* Reset flag 0 register */ rval = regmap_read(flash->regmap, REG_FLAG0, ®_val); if (rval < 0) return rval; /* Reset flag 1 register */ return regmap_read(flash->regmap, REG_FLAG1, ®_val); } static int lm3644_probe(struct i2c_client *client, const struct i2c_device_id *devid) { struct lm3644_flash *flash; struct lm3644_platform_data *pdata = dev_get_platdata(&client->dev); int rval; flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL); if (flash == NULL) return -ENOMEM; flash->regmap = devm_regmap_init_i2c(client, &lm3644_regmap); if (IS_ERR(flash->regmap)) return PTR_ERR(flash->regmap); /* if there is no platform data, use chip default value */ if (pdata == NULL) { pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); if (pdata == NULL) return -ENODEV; pdata->led0_enable = 0x01; pdata->led1_enable = 0x01 << 1; pdata->flash1_override = 0x01 << 7; pdata->torch1_override = 0x01 << 7; } flash->pdata = pdata; flash->dev = &client->dev; mutex_init(&flash->lock); rval = lm3644_subdev_init(flash, LM3644_LED0, "lm3644-led0"); if (rval < 0) return rval; rval = lm3644_subdev_init(flash, LM3644_LED1, "lm3644-led1"); if (rval < 0) return rval; rval = lm3644_init_device(flash); if (rval < 0) return rval; i2c_set_clientdata(client, flash); return 0; } static int lm3644_remove(struct i2c_client *client) { struct lm3644_flash *flash = i2c_get_clientdata(client); unsigned int i; for (i = LM3644_LED0; i < LM3644_LED_MAX; i++) { v4l2_device_unregister_subdev(&flash->subdev_led[i]); v4l2_ctrl_handler_free(&flash->ctrls_led[i]); media_entity_cleanup(&flash->subdev_led[i].entity); } return 0; } static const struct i2c_device_id lm3644_id_table[] = { {LM3644_NAME, 0}, {} }; MODULE_DEVICE_TABLE(i2c, lm3644_id_table); static struct i2c_driver lm3644_i2c_driver = { .driver = { .name = LM3644_NAME, .pm = NULL, }, .probe = lm3644_probe, .remove = lm3644_remove, .id_table = lm3644_id_table, }; module_i2c_driver(lm3644_i2c_driver); MODULE_AUTHOR("Daniel Jeong "); MODULE_AUTHOR("Ldd Mlp "); MODULE_DESCRIPTION("Texas Instruments LM3644 LED flash driver"); MODULE_LICENSE("GPL");