Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Mauro Carvalho Chehab 3964 100.00% 5 100.00%
Total 3964 5


// SPDX-License-Identifier: GPL-2.0
/*
 * LED flash driver for LM3554
 *
 * Copyright (c) 2010-2012 Intel Corporation. All Rights Reserved.
 *
 * 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 <linux/module.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/slab.h>

#include "../include/media/lm3554.h"
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <linux/acpi.h>
#include <linux/gpio/consumer.h>
#include "../include/linux/atomisp_gmin_platform.h"
#include "../include/linux/atomisp.h"

/* Registers */

#define LM3554_TORCH_BRIGHTNESS_REG	0xA0
#define LM3554_TORCH_MODE_SHIFT		0
#define LM3554_TORCH_CURRENT_SHIFT	3
#define LM3554_INDICATOR_CURRENT_SHIFT	6

#define LM3554_FLASH_BRIGHTNESS_REG	0xB0
#define LM3554_FLASH_MODE_SHIFT		0
#define LM3554_FLASH_CURRENT_SHIFT	3
#define LM3554_STROBE_SENSITIVITY_SHIFT	7

#define LM3554_FLASH_DURATION_REG	0xC0
#define LM3554_FLASH_TIMEOUT_SHIFT	0
#define LM3554_CURRENT_LIMIT_SHIFT	5

#define LM3554_FLAGS_REG		0xD0
#define LM3554_FLAG_TIMEOUT		BIT(0)
#define LM3554_FLAG_THERMAL_SHUTDOWN	BIT(1)
#define LM3554_FLAG_LED_FAULT		BIT(2)
#define LM3554_FLAG_TX1_INTERRUPT	BIT(3)
#define LM3554_FLAG_TX2_INTERRUPT	BIT(4)
#define LM3554_FLAG_LED_THERMAL_FAULT	BIT(5)
#define LM3554_FLAG_UNUSED		BIT(6)
#define LM3554_FLAG_INPUT_VOLTAGE_LOW	BIT(7)

#define LM3554_CONFIG_REG_1		0xE0
#define LM3554_ENVM_TX2_SHIFT		5
#define LM3554_TX2_POLARITY_SHIFT	6

struct lm3554 {
	struct v4l2_subdev sd;

	struct mutex power_lock;
	struct v4l2_ctrl_handler ctrl_handler;
	int power_count;

	unsigned int mode;
	int timeout;
	u8 torch_current;
	u8 indicator_current;
	u8 flash_current;

	struct timer_list flash_off_delay;
	struct lm3554_platform_data *pdata;
};

#define to_lm3554(p_sd)	container_of(p_sd, struct lm3554, sd)

/* Return negative errno else zero on success */
static int lm3554_write(struct lm3554 *flash, u8 addr, u8 val)
{
	struct i2c_client *client = v4l2_get_subdevdata(&flash->sd);
	int ret;

	ret = i2c_smbus_write_byte_data(client, addr, val);

	dev_dbg(&client->dev, "Write Addr:%02X Val:%02X %s\n", addr, val,
		ret < 0 ? "fail" : "ok");

	return ret;
}

/* Return negative errno else a data byte received from the device. */
static int lm3554_read(struct lm3554 *flash, u8 addr)
{
	struct i2c_client *client = v4l2_get_subdevdata(&flash->sd);
	int ret;

	ret = i2c_smbus_read_byte_data(client, addr);

	dev_dbg(&client->dev, "Read Addr:%02X Val:%02X %s\n", addr, ret,
		ret < 0 ? "fail" : "ok");

	return ret;
}

/* -----------------------------------------------------------------------------
 * Hardware configuration
 */

static int lm3554_set_mode(struct lm3554 *flash, unsigned int mode)
{
	u8 val;
	int ret;

	val = (mode << LM3554_FLASH_MODE_SHIFT) |
	      (flash->flash_current << LM3554_FLASH_CURRENT_SHIFT);

	ret = lm3554_write(flash, LM3554_FLASH_BRIGHTNESS_REG, val);
	if (ret == 0)
		flash->mode = mode;
	return ret;
}

static int lm3554_set_torch(struct lm3554 *flash)
{
	u8 val;

	val = (flash->mode << LM3554_TORCH_MODE_SHIFT) |
	      (flash->torch_current << LM3554_TORCH_CURRENT_SHIFT) |
	      (flash->indicator_current << LM3554_INDICATOR_CURRENT_SHIFT);

	return lm3554_write(flash, LM3554_TORCH_BRIGHTNESS_REG, val);
}

static int lm3554_set_flash(struct lm3554 *flash)
{
	u8 val;

	val = (flash->mode << LM3554_FLASH_MODE_SHIFT) |
	      (flash->flash_current << LM3554_FLASH_CURRENT_SHIFT);

	return lm3554_write(flash, LM3554_FLASH_BRIGHTNESS_REG, val);
}

static int lm3554_set_duration(struct lm3554 *flash)
{
	u8 val;

	val = (flash->timeout << LM3554_FLASH_TIMEOUT_SHIFT) |
	      (flash->pdata->current_limit << LM3554_CURRENT_LIMIT_SHIFT);

	return lm3554_write(flash, LM3554_FLASH_DURATION_REG, val);
}

static int lm3554_set_config1(struct lm3554 *flash)
{
	u8 val;

	val = (flash->pdata->envm_tx2 << LM3554_ENVM_TX2_SHIFT) |
	      (flash->pdata->tx2_polarity << LM3554_TX2_POLARITY_SHIFT);
	return lm3554_write(flash, LM3554_CONFIG_REG_1, val);
}

/* -----------------------------------------------------------------------------
 * Hardware trigger
 */
static void lm3554_flash_off_delay(struct timer_list *t)
{
	struct lm3554 *flash = from_timer(flash, t, flash_off_delay);
	struct lm3554_platform_data *pdata = flash->pdata;

	gpio_set_value(pdata->gpio_strobe, 0);
}

static int lm3554_hw_strobe(struct i2c_client *client, bool strobe)
{
	int ret, timer_pending;
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct lm3554 *flash = to_lm3554(sd);
	struct lm3554_platform_data *pdata = flash->pdata;

	/*
	 * An abnormal high flash current is observed when strobe off the
	 * flash. Workaround here is firstly set flash current to lower level,
	 * wait a short moment, and then strobe off the flash.
	 */

	timer_pending = del_timer_sync(&flash->flash_off_delay);

	/* Flash off */
	if (!strobe) {
		/* set current to 70mA and wait a while */
		ret = lm3554_write(flash, LM3554_FLASH_BRIGHTNESS_REG, 0);
		if (ret < 0)
			goto err;
		mod_timer(&flash->flash_off_delay,
			  jiffies + msecs_to_jiffies(LM3554_TIMER_DELAY));
		return 0;
	}

	/* Flash on */

	/*
	 * If timer is killed before run, flash is not strobe off,
	 * so must strobe off here
	 */
	if (timer_pending)
		gpio_set_value(pdata->gpio_strobe, 0);

	/* Restore flash current settings */
	ret = lm3554_set_flash(flash);
	if (ret < 0)
		goto err;

	/* Strobe on Flash */
	gpio_set_value(pdata->gpio_strobe, 1);

	return 0;
err:
	dev_err(&client->dev, "failed to %s flash strobe (%d)\n",
		strobe ? "on" : "off", ret);
	return ret;
}

/* -----------------------------------------------------------------------------
 * V4L2 controls
 */

static int lm3554_read_status(struct lm3554 *flash)
{
	int ret;
	struct i2c_client *client = v4l2_get_subdevdata(&flash->sd);

	/* NOTE: reading register clear fault status */
	ret = lm3554_read(flash, LM3554_FLAGS_REG);
	if (ret < 0)
		return ret;

	/*
	 * Accordingly to datasheet we read back '1' in bit 6.
	 * Clear it first.
	 */
	ret &= ~LM3554_FLAG_UNUSED;

	/*
	 * Do not take TX1/TX2 signal as an error
	 * because MSIC will not turn off flash, but turn to
	 * torch mode according to gsm modem signal by hardware.
	 */
	ret &= ~(LM3554_FLAG_TX1_INTERRUPT | LM3554_FLAG_TX2_INTERRUPT);

	if (ret > 0)
		dev_dbg(&client->dev, "LM3554 flag status: %02x\n", ret);

	return ret;
}

static int lm3554_s_flash_timeout(struct v4l2_subdev *sd, u32 val)
{
	struct lm3554 *flash = to_lm3554(sd);

	val = clamp(val, LM3554_MIN_TIMEOUT, LM3554_MAX_TIMEOUT);
	val = val / LM3554_TIMEOUT_STEPSIZE - 1;

	flash->timeout = val;

	return lm3554_set_duration(flash);
}

static int lm3554_g_flash_timeout(struct v4l2_subdev *sd, s32 *val)
{
	struct lm3554 *flash = to_lm3554(sd);

	*val = (u32)(flash->timeout + 1) * LM3554_TIMEOUT_STEPSIZE;

	return 0;
}

static int lm3554_s_flash_intensity(struct v4l2_subdev *sd, u32 intensity)
{
	struct lm3554 *flash = to_lm3554(sd);

	intensity = LM3554_CLAMP_PERCENTAGE(intensity);
	intensity = LM3554_PERCENT_TO_VALUE(intensity, LM3554_FLASH_STEP);

	flash->flash_current = intensity;

	return lm3554_set_flash(flash);
}

static int lm3554_g_flash_intensity(struct v4l2_subdev *sd, s32 *val)
{
	struct lm3554 *flash = to_lm3554(sd);

	*val = LM3554_VALUE_TO_PERCENT((u32)flash->flash_current,
				       LM3554_FLASH_STEP);

	return 0;
}

static int lm3554_s_torch_intensity(struct v4l2_subdev *sd, u32 intensity)
{
	struct lm3554 *flash = to_lm3554(sd);

	intensity = LM3554_CLAMP_PERCENTAGE(intensity);
	intensity = LM3554_PERCENT_TO_VALUE(intensity, LM3554_TORCH_STEP);

	flash->torch_current = intensity;

	return lm3554_set_torch(flash);
}

static int lm3554_g_torch_intensity(struct v4l2_subdev *sd, s32 *val)
{
	struct lm3554 *flash = to_lm3554(sd);

	*val = LM3554_VALUE_TO_PERCENT((u32)flash->torch_current,
				       LM3554_TORCH_STEP);

	return 0;
}

static int lm3554_s_indicator_intensity(struct v4l2_subdev *sd, u32 intensity)
{
	struct lm3554 *flash = to_lm3554(sd);

	intensity = LM3554_CLAMP_PERCENTAGE(intensity);
	intensity = LM3554_PERCENT_TO_VALUE(intensity, LM3554_INDICATOR_STEP);

	flash->indicator_current = intensity;

	return lm3554_set_torch(flash);
}

static int lm3554_g_indicator_intensity(struct v4l2_subdev *sd, s32 *val)
{
	struct lm3554 *flash = to_lm3554(sd);

	*val = LM3554_VALUE_TO_PERCENT((u32)flash->indicator_current,
				       LM3554_INDICATOR_STEP);

	return 0;
}

static int lm3554_s_flash_strobe(struct v4l2_subdev *sd, u32 val)
{
	struct i2c_client *client = v4l2_get_subdevdata(sd);

	return lm3554_hw_strobe(client, val);
}

static int lm3554_s_flash_mode(struct v4l2_subdev *sd, u32 new_mode)
{
	struct lm3554 *flash = to_lm3554(sd);
	unsigned int mode;

	switch (new_mode) {
	case ATOMISP_FLASH_MODE_OFF:
		mode = LM3554_MODE_SHUTDOWN;
		break;
	case ATOMISP_FLASH_MODE_FLASH:
		mode = LM3554_MODE_FLASH;
		break;
	case ATOMISP_FLASH_MODE_INDICATOR:
		mode = LM3554_MODE_INDICATOR;
		break;
	case ATOMISP_FLASH_MODE_TORCH:
		mode = LM3554_MODE_TORCH;
		break;
	default:
		return -EINVAL;
	}

	return lm3554_set_mode(flash, mode);
}

static int lm3554_g_flash_mode(struct v4l2_subdev *sd, s32 *val)
{
	struct lm3554 *flash = to_lm3554(sd);
	*val = flash->mode;
	return 0;
}

static int lm3554_g_flash_status(struct v4l2_subdev *sd, s32 *val)
{
	struct lm3554 *flash = to_lm3554(sd);
	int value;

	value = lm3554_read_status(flash);
	if (value < 0)
		return value;

	if (value & LM3554_FLAG_TIMEOUT)
		*val = ATOMISP_FLASH_STATUS_TIMEOUT;
	else if (value > 0)
		*val = ATOMISP_FLASH_STATUS_HW_ERROR;
	else
		*val = ATOMISP_FLASH_STATUS_OK;

	return 0;
}

static int lm3554_g_flash_status_register(struct v4l2_subdev *sd, s32 *val)
{
	struct lm3554 *flash = to_lm3554(sd);
	int ret;

	ret = lm3554_read(flash, LM3554_FLAGS_REG);

	if (ret < 0)
		return ret;

	*val = ret;
	return 0;
}

static int lm3554_s_ctrl(struct v4l2_ctrl *ctrl)
{
	struct lm3554 *dev =
	    container_of(ctrl->handler, struct lm3554, ctrl_handler);
	int ret = 0;

	switch (ctrl->id) {
	case V4L2_CID_FLASH_TIMEOUT:
		ret = lm3554_s_flash_timeout(&dev->sd, ctrl->val);
		break;
	case V4L2_CID_FLASH_INTENSITY:
		ret = lm3554_s_flash_intensity(&dev->sd, ctrl->val);
		break;
	case V4L2_CID_FLASH_TORCH_INTENSITY:
		ret = lm3554_s_torch_intensity(&dev->sd, ctrl->val);
		break;
	case V4L2_CID_FLASH_INDICATOR_INTENSITY:
		ret = lm3554_s_indicator_intensity(&dev->sd, ctrl->val);
		break;
	case V4L2_CID_FLASH_STROBE:
		ret = lm3554_s_flash_strobe(&dev->sd, ctrl->val);
		break;
	case V4L2_CID_FLASH_MODE:
		ret = lm3554_s_flash_mode(&dev->sd, ctrl->val);
		break;
	default:
		ret = -EINVAL;
	}
	return ret;
}

static int lm3554_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
{
	struct lm3554 *dev =
	    container_of(ctrl->handler, struct lm3554, ctrl_handler);
	int ret = 0;

	switch (ctrl->id) {
	case V4L2_CID_FLASH_TIMEOUT:
		ret = lm3554_g_flash_timeout(&dev->sd, &ctrl->val);
		break;
	case V4L2_CID_FLASH_INTENSITY:
		ret = lm3554_g_flash_intensity(&dev->sd, &ctrl->val);
		break;
	case V4L2_CID_FLASH_TORCH_INTENSITY:
		ret = lm3554_g_torch_intensity(&dev->sd, &ctrl->val);
		break;
	case V4L2_CID_FLASH_INDICATOR_INTENSITY:
		ret = lm3554_g_indicator_intensity(&dev->sd, &ctrl->val);
		break;
	case V4L2_CID_FLASH_MODE:
		ret = lm3554_g_flash_mode(&dev->sd, &ctrl->val);
		break;
	case V4L2_CID_FLASH_STATUS:
		ret = lm3554_g_flash_status(&dev->sd, &ctrl->val);
		break;
	case V4L2_CID_FLASH_STATUS_REGISTER:
		ret = lm3554_g_flash_status_register(&dev->sd, &ctrl->val);
		break;
	default:
		ret = -EINVAL;
	}

	return ret;
}

static const struct v4l2_ctrl_ops ctrl_ops = {
	.s_ctrl = lm3554_s_ctrl,
	.g_volatile_ctrl = lm3554_g_volatile_ctrl
};

static const struct v4l2_ctrl_config lm3554_controls[] = {
	{
		.ops = &ctrl_ops,
		.id = V4L2_CID_FLASH_TIMEOUT,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Flash Timeout",
		.min = 0x0,
		.max = LM3554_MAX_TIMEOUT,
		.step = 0x01,
		.def = LM3554_DEFAULT_TIMEOUT,
		.flags = 0,
	},
	{
		.ops = &ctrl_ops,
		.id = V4L2_CID_FLASH_INTENSITY,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Flash Intensity",
		.min = LM3554_MIN_PERCENT,
		.max = LM3554_MAX_PERCENT,
		.step = 0x01,
		.def = LM3554_FLASH_DEFAULT_BRIGHTNESS,
		.flags = 0,
	},
	{
		.ops = &ctrl_ops,
		.id = V4L2_CID_FLASH_TORCH_INTENSITY,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Torch Intensity",
		.min = LM3554_MIN_PERCENT,
		.max = LM3554_MAX_PERCENT,
		.step = 0x01,
		.def = LM3554_TORCH_DEFAULT_BRIGHTNESS,
		.flags = 0,
	},
	{
		.ops = &ctrl_ops,
		.id = V4L2_CID_FLASH_INDICATOR_INTENSITY,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Indicator Intensity",
		.min = LM3554_MIN_PERCENT,
		.max = LM3554_MAX_PERCENT,
		.step = 0x01,
		.def = LM3554_INDICATOR_DEFAULT_BRIGHTNESS,
		.flags = 0,
	},
	{
		.ops = &ctrl_ops,
		.id = V4L2_CID_FLASH_STROBE,
		.type = V4L2_CTRL_TYPE_BOOLEAN,
		.name = "Flash Strobe",
		.min = 0,
		.max = 1,
		.step = 1,
		.def = 0,
		.flags = 0,
	},
	{
		.ops = &ctrl_ops,
		.id = V4L2_CID_FLASH_MODE,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Flash Mode",
		.min = 0,
		.max = 100,
		.step = 1,
		.def = ATOMISP_FLASH_MODE_OFF,
		.flags = 0,
	},
	{
		.ops = &ctrl_ops,
		.id = V4L2_CID_FLASH_STATUS,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Flash Status",
		.min = ATOMISP_FLASH_STATUS_OK,
		.max = ATOMISP_FLASH_STATUS_TIMEOUT,
		.step = 1,
		.def = ATOMISP_FLASH_STATUS_OK,
		.flags = 0,
	},
	{
		.ops = &ctrl_ops,
		.id = V4L2_CID_FLASH_STATUS_REGISTER,
		.type = V4L2_CTRL_TYPE_INTEGER,
		.name = "Flash Status Register",
		.min = 0,
		.max = 255,
		.step = 1,
		.def = 0,
		.flags = 0,
	},
};

/* -----------------------------------------------------------------------------
 * V4L2 subdev core operations
 */

/* Put device into known state. */
static int lm3554_setup(struct lm3554 *flash)
{
	struct i2c_client *client = v4l2_get_subdevdata(&flash->sd);
	int ret;

	/* clear the flags register */
	ret = lm3554_read(flash, LM3554_FLAGS_REG);
	if (ret < 0)
		return ret;

	dev_dbg(&client->dev, "Fault info: %02x\n", ret);

	ret = lm3554_set_config1(flash);
	if (ret < 0)
		return ret;

	ret = lm3554_set_duration(flash);
	if (ret < 0)
		return ret;

	ret = lm3554_set_torch(flash);
	if (ret < 0)
		return ret;

	ret = lm3554_set_flash(flash);
	if (ret < 0)
		return ret;

	/* read status */
	ret = lm3554_read_status(flash);
	if (ret < 0)
		return ret;

	return ret ? -EIO : 0;
}

static int __lm3554_s_power(struct lm3554 *flash, int power)
{
	struct lm3554_platform_data *pdata = flash->pdata;
	int ret;

	/*initialize flash driver*/
	gpio_set_value(pdata->gpio_reset, power);
	usleep_range(100, 100 + 1);

	if (power) {
		/* Setup default values. This makes sure that the chip
		 * is in a known state.
		 */
		ret = lm3554_setup(flash);
		if (ret < 0) {
			__lm3554_s_power(flash, 0);
			return ret;
		}
	}

	return 0;
}

static int lm3554_s_power(struct v4l2_subdev *sd, int power)
{
	struct lm3554 *flash = to_lm3554(sd);
	int ret = 0;

	mutex_lock(&flash->power_lock);

	if (flash->power_count == !power) {
		ret = __lm3554_s_power(flash, !!power);
		if (ret < 0)
			goto done;
	}

	flash->power_count += power ? 1 : -1;
	WARN_ON(flash->power_count < 0);

done:
	mutex_unlock(&flash->power_lock);
	return ret;
}

static const struct v4l2_subdev_core_ops lm3554_core_ops = {
	.s_power = lm3554_s_power,
};

static const struct v4l2_subdev_ops lm3554_ops = {
	.core = &lm3554_core_ops,
};

static int lm3554_detect(struct v4l2_subdev *sd)
{
	struct i2c_client *client = v4l2_get_subdevdata(sd);
	struct i2c_adapter *adapter = client->adapter;
	struct lm3554 *flash = to_lm3554(sd);
	int ret;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev, "lm3554_detect i2c error\n");
		return -ENODEV;
	}

	/* Power up the flash driver and reset it */
	ret = lm3554_s_power(&flash->sd, 1);
	if (ret < 0) {
		dev_err(&client->dev, "Failed to power on lm3554 LED flash\n");
	} else {
		dev_dbg(&client->dev, "Successfully detected lm3554 LED flash\n");
		lm3554_s_power(&flash->sd, 0);
	}

	return ret;
}

static int lm3554_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
	return lm3554_s_power(sd, 1);
}

static int lm3554_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
	return lm3554_s_power(sd, 0);
}

static const struct v4l2_subdev_internal_ops lm3554_internal_ops = {
	.registered = lm3554_detect,
	.open = lm3554_open,
	.close = lm3554_close,
};

/* -----------------------------------------------------------------------------
 *  I2C driver
 */
#ifdef CONFIG_PM

static int lm3554_suspend(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
	struct lm3554 *flash = to_lm3554(subdev);
	int rval;

	if (flash->power_count == 0)
		return 0;

	rval = __lm3554_s_power(flash, 0);

	dev_dbg(&client->dev, "Suspend %s\n", rval < 0 ? "failed" : "ok");

	return rval;
}

static int lm3554_resume(struct device *dev)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
	struct lm3554 *flash = to_lm3554(subdev);
	int rval;

	if (flash->power_count == 0)
		return 0;

	rval = __lm3554_s_power(flash, 1);

	dev_dbg(&client->dev, "Resume %s\n", rval < 0 ? "fail" : "ok");

	return rval;
}

#else

#define lm3554_suspend NULL
#define lm3554_resume  NULL

#endif /* CONFIG_PM */

static int lm3554_gpio_init(struct i2c_client *client)
{
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct lm3554 *flash = to_lm3554(sd);
	struct lm3554_platform_data *pdata = flash->pdata;
	int ret;

	if (!gpio_is_valid(pdata->gpio_reset))
		return -EINVAL;

	ret = gpio_direction_output(pdata->gpio_reset, 0);
	if (ret < 0)
		goto err_gpio_reset;
	dev_info(&client->dev, "flash led reset successfully\n");

	if (!gpio_is_valid(pdata->gpio_strobe)) {
		ret = -EINVAL;
		goto err_gpio_dir_reset;
	}

	ret = gpio_direction_output(pdata->gpio_strobe, 0);
	if (ret < 0)
		goto err_gpio_strobe;

	return 0;

err_gpio_strobe:
	gpio_free(pdata->gpio_strobe);
err_gpio_dir_reset:
	gpio_direction_output(pdata->gpio_reset, 0);
err_gpio_reset:
	gpio_free(pdata->gpio_reset);

	return ret;
}

static int lm3554_gpio_uninit(struct i2c_client *client)
{
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct lm3554 *flash = to_lm3554(sd);
	struct lm3554_platform_data *pdata = flash->pdata;
	int ret;

	ret = gpio_direction_output(pdata->gpio_strobe, 0);
	if (ret < 0)
		return ret;

	ret = gpio_direction_output(pdata->gpio_reset, 0);
	if (ret < 0)
		return ret;

	gpio_free(pdata->gpio_strobe);
	gpio_free(pdata->gpio_reset);
	return 0;
}

static void *lm3554_platform_data_func(struct i2c_client *client)
{
	static struct lm3554_platform_data platform_data;

	platform_data.gpio_reset =
	    desc_to_gpio(gpiod_get_index(&client->dev,
					 NULL, 2, GPIOD_OUT_LOW));
	platform_data.gpio_strobe =
	    desc_to_gpio(gpiod_get_index(&client->dev,
					 NULL, 0, GPIOD_OUT_LOW));
	platform_data.gpio_torch =
	    desc_to_gpio(gpiod_get_index(&client->dev,
					 NULL, 1, GPIOD_OUT_LOW));
	dev_info(&client->dev, "camera pdata: lm3554: reset: %d strobe %d torch %d\n",
		 platform_data.gpio_reset, platform_data.gpio_strobe,
		 platform_data.gpio_torch);

	/* Set to TX2 mode, then ENVM/TX2 pin is a power amplifier sync input:
	 * ENVM/TX pin asserted, flash forced into torch;
	 * ENVM/TX pin desserted, flash set back;
	 */
	platform_data.envm_tx2 = 1;
	platform_data.tx2_polarity = 0;

	/* set peak current limit to be 1000mA */
	platform_data.current_limit = 0;

	return &platform_data;
}

static int lm3554_probe(struct i2c_client *client)
{
	int err = 0;
	struct lm3554 *flash;
	unsigned int i;
	int ret;

	flash = kzalloc(sizeof(*flash), GFP_KERNEL);
	if (!flash)
		return -ENOMEM;

	flash->pdata = lm3554_platform_data_func(client);

	v4l2_i2c_subdev_init(&flash->sd, client, &lm3554_ops);
	flash->sd.internal_ops = &lm3554_internal_ops;
	flash->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	flash->mode = ATOMISP_FLASH_MODE_OFF;
	flash->timeout = LM3554_MAX_TIMEOUT / LM3554_TIMEOUT_STEPSIZE - 1;
	ret =
	    v4l2_ctrl_handler_init(&flash->ctrl_handler,
				   ARRAY_SIZE(lm3554_controls));
	if (ret) {
		dev_err(&client->dev, "error initialize a ctrl_handler.\n");
		goto fail2;
	}

	for (i = 0; i < ARRAY_SIZE(lm3554_controls); i++)
		v4l2_ctrl_new_custom(&flash->ctrl_handler, &lm3554_controls[i],
				     NULL);

	if (flash->ctrl_handler.error) {
		dev_err(&client->dev, "ctrl_handler error.\n");
		goto fail2;
	}

	flash->sd.ctrl_handler = &flash->ctrl_handler;
	err = media_entity_pads_init(&flash->sd.entity, 0, NULL);
	if (err) {
		dev_err(&client->dev, "error initialize a media entity.\n");
		goto fail1;
	}

	flash->sd.entity.function = MEDIA_ENT_F_FLASH;

	mutex_init(&flash->power_lock);

	timer_setup(&flash->flash_off_delay, lm3554_flash_off_delay, 0);

	err = lm3554_gpio_init(client);
	if (err) {
		dev_err(&client->dev, "gpio request/direction_output fail");
		goto fail2;
	}
	return atomisp_register_i2c_module(&flash->sd, NULL, LED_FLASH);
fail2:
	media_entity_cleanup(&flash->sd.entity);
	v4l2_ctrl_handler_free(&flash->ctrl_handler);
fail1:
	v4l2_device_unregister_subdev(&flash->sd);
	kfree(flash);

	return err;
}

static int lm3554_remove(struct i2c_client *client)
{
	struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct lm3554 *flash = to_lm3554(sd);
	int ret;

	media_entity_cleanup(&flash->sd.entity);
	v4l2_ctrl_handler_free(&flash->ctrl_handler);
	v4l2_device_unregister_subdev(sd);

	atomisp_gmin_remove_subdev(sd);

	del_timer_sync(&flash->flash_off_delay);

	ret = lm3554_gpio_uninit(client);
	if (ret < 0)
		goto fail;

	kfree(flash);

	return 0;
fail:
	dev_err(&client->dev, "gpio request/direction_output fail");
	return ret;
}

static const struct dev_pm_ops lm3554_pm_ops = {
	.suspend = lm3554_suspend,
	.resume = lm3554_resume,
};

static const struct acpi_device_id lm3554_acpi_match[] = {
	{ "INTCF1C" },
	{},
};
MODULE_DEVICE_TABLE(acpi, lm3554_acpi_match);

static struct i2c_driver lm3554_driver = {
	.driver = {
		.name = "lm3554",
		.pm   = &lm3554_pm_ops,
		.acpi_match_table = lm3554_acpi_match,
	},
	.probe_new = lm3554_probe,
	.remove = lm3554_remove,
};
module_i2c_driver(lm3554_driver);

MODULE_AUTHOR("Jing Tao <jing.tao@intel.com>");
MODULE_DESCRIPTION("LED flash driver for LM3554");
MODULE_LICENSE("GPL");