376 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| // Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd.
 | |
| 
 | |
| #include <linux/delay.h>
 | |
| #include <linux/i2c.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/pm_runtime.h>
 | |
| #include <linux/rk-camera-module.h>
 | |
| #include <media/v4l2-ctrls.h>
 | |
| #include <media/v4l2-device.h>
 | |
| #include "imx258_eeprom_head.h"
 | |
| 
 | |
| #define DEVICE_NAME			"imx258_eeprom"
 | |
| 
 | |
| static inline struct imx258_eeprom_device
 | |
| 	*sd_to_imx258_eeprom(struct v4l2_subdev *subdev)
 | |
| {
 | |
| 	return container_of(subdev, struct imx258_eeprom_device, sd);
 | |
| }
 | |
| 
 | |
| /* Read registers up to 4 at a time */
 | |
| static int imx258_read_reg_otp(struct i2c_client *client, u16 reg,
 | |
| 	unsigned int len, u32 *val)
 | |
| {
 | |
| 	struct i2c_msg msgs[2];
 | |
| 	u8 *data_be_p;
 | |
| 	__be32 data_be = 0;
 | |
| 	__be16 reg_addr_be = cpu_to_be16(reg);
 | |
| 	int ret;
 | |
| 
 | |
| 	if (len > 4 || !len)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	data_be_p = (u8 *)&data_be;
 | |
| 	/* Write register address */
 | |
| 	msgs[0].addr = client->addr;
 | |
| 	msgs[0].flags = 0;
 | |
| 	msgs[0].len = 2;
 | |
| 	msgs[0].buf = (u8 *)®_addr_be;
 | |
| 
 | |
| 	/* Read data from register */
 | |
| 	msgs[1].addr = client->addr;
 | |
| 	msgs[1].flags = I2C_M_RD;
 | |
| 	msgs[1].len = len;
 | |
| 	msgs[1].buf = &data_be_p[4 - len];
 | |
| 
 | |
| 	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
 | |
| 	if (ret != ARRAY_SIZE(msgs))
 | |
| 		return -EIO;
 | |
| 
 | |
| 	*val = be32_to_cpu(data_be);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static u8 get_vendor_flag(struct i2c_client *client)
 | |
| {
 | |
| 	u8 vendor_flag = 0;
 | |
| 
 | |
| 	if (client->addr == SLAVE_ADDRESS_GZ)
 | |
| 		vendor_flag |= 0x80;
 | |
| 	return vendor_flag;
 | |
| }
 | |
| 
 | |
| static int imx258_otp_read_gz(struct imx258_eeprom_device *imx258_eeprom_dev)
 | |
| {
 | |
| 	struct i2c_client *client = imx258_eeprom_dev->client;
 | |
| 	int otp_flag, i;
 | |
| 	struct imx258_otp_info *otp_ptr;
 | |
| 	struct device *dev = &imx258_eeprom_dev->client->dev;
 | |
| 	int ret = 0;
 | |
| 	u32 r_value, gr_value, gb_value, b_value;
 | |
| 	u32 temp = 0;
 | |
| 	u32 checksum = 0;
 | |
| 
 | |
| 	otp_ptr = kzalloc(sizeof(*otp_ptr), GFP_KERNEL);
 | |
| 	if (!otp_ptr)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	otp_flag = 0;
 | |
| 	/* OTP base information*/
 | |
| 	ret = imx258_read_reg_otp(client, GZ_INFO_FLAG_REG,
 | |
| 		1, &otp_flag);
 | |
| 	if (otp_flag == 0x01) {
 | |
| 		otp_ptr->flag = 0x80; /* valid INFO in OTP */
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_ID_REG,
 | |
| 			1, &otp_ptr->module_id);
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_LENS_ID_REG,
 | |
| 			1, &otp_ptr->lens_id);
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_PRODUCT_YEAR_REG,
 | |
| 			1, &otp_ptr->year);
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_PRODUCT_MONTH_REG,
 | |
| 			1, &otp_ptr->month);
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_PRODUCT_DAY_REG,
 | |
| 			1, &otp_ptr->day);
 | |
| 		dev_dbg(dev, "fac info: module(0x%x) lens(0x%x) time(%d_%d_%d)!\n",
 | |
| 			otp_ptr->module_id,
 | |
| 			otp_ptr->lens_id,
 | |
| 			otp_ptr->year,
 | |
| 			otp_ptr->month,
 | |
| 			otp_ptr->day);
 | |
| 		if (ret)
 | |
| 			goto err;
 | |
| 	}
 | |
| 
 | |
| 	/* OTP WB calibration data */
 | |
| 	ret = imx258_read_reg_otp(client, GZ_AWB_FLAG_REG,
 | |
| 		1, &otp_flag);
 | |
| 	if (otp_flag == 0x01) {
 | |
| 		otp_ptr->flag |= 0x40; /* valid AWB in OTP */
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_CUR_R_REG,
 | |
| 			1, &r_value);
 | |
| 		checksum += r_value;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_CUR_GR_REG,
 | |
| 			1, &gr_value);
 | |
| 		checksum += gr_value;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_CUR_GB_REG,
 | |
| 			1, &gb_value);
 | |
| 		checksum += gb_value;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_CUR_B_REG,
 | |
| 			1, &b_value);
 | |
| 		checksum += b_value;
 | |
| 		otp_ptr->rg_ratio =
 | |
| 			r_value * 1024 / ((gr_value + gb_value) / 2);
 | |
| 		otp_ptr->bg_ratio =
 | |
| 			b_value * 1024 / ((gr_value + gb_value) / 2);
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_GOLDEN_R_REG,
 | |
| 			1, &r_value);
 | |
| 		checksum += r_value;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_GOLDEN_GR_REG,
 | |
| 			1, &gr_value);
 | |
| 		checksum += gr_value;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_GOLDEN_GB_REG,
 | |
| 			1, &gb_value);
 | |
| 		checksum += gb_value;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_GOLDEN_B_REG,
 | |
| 			1, &b_value);
 | |
| 		checksum += b_value;
 | |
| 		otp_ptr->rg_golden =
 | |
| 			r_value * 1024 / ((gr_value + gb_value) / 2);
 | |
| 		otp_ptr->bg_golden =
 | |
| 			b_value * 1024 / ((gr_value + gb_value) / 2);
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_AWB_CHECKSUM_REG,
 | |
| 			1, &temp);
 | |
| 		if (ret != 0 || (checksum % 0xff) != temp) {
 | |
| 			dev_err(dev, "otp awb info: check sum (%d,%d),ret = %d !\n",
 | |
| 				checksum,
 | |
| 				temp,
 | |
| 				ret);
 | |
| 			goto err;
 | |
| 		}
 | |
| 		dev_dbg(dev, "awb cur:(rg 0x%x, bg 0x%x,)\n",
 | |
| 			otp_ptr->rg_ratio, otp_ptr->bg_ratio);
 | |
| 		dev_dbg(dev, "awb gol:(rg 0x%x, bg 0x%x)\n",
 | |
| 			otp_ptr->rg_golden, otp_ptr->bg_golden);
 | |
| 	}
 | |
| 
 | |
| 	checksum = 0;
 | |
| 	/* OTP LSC calibration data */
 | |
| 	ret = imx258_read_reg_otp(client, GZ_LSC_FLAG_REG,
 | |
| 		1, &otp_flag);
 | |
| 	if (otp_flag == 0x01) {
 | |
| 		otp_ptr->flag |= 0x10; /* valid LSC in OTP */
 | |
| 		for (i = 0; i < 504; i++) {
 | |
| 			ret |= imx258_read_reg_otp(client,
 | |
| 				GZ_LSC_DATA_START_REG + i,
 | |
| 				1, &temp);
 | |
| 			otp_ptr->lenc[i] = temp;
 | |
| 			checksum += temp;
 | |
| 			dev_dbg(dev,
 | |
| 				"otp read lsc addr = 0x%04x, lenc[%d] = %d\n",
 | |
| 				GZ_LSC_DATA_START_REG + i, i, temp);
 | |
| 		}
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_LSC_CHECKSUM_REG,
 | |
| 			1, &temp);
 | |
| 		if (ret != 0 || (checksum % 0xff) != temp) {
 | |
| 			dev_err(dev,
 | |
| 				"otp lsc info: check sum (%d,%d),ret = %d !\n",
 | |
| 				checksum, temp, ret);
 | |
| 			goto err;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	checksum = 0;
 | |
| 	/* OTP VCM calibration data */
 | |
| 	ret = imx258_read_reg_otp(client, GZ_VCM_FLAG_REG,
 | |
| 		1, &otp_flag);
 | |
| 	if (otp_flag == 0x01) {
 | |
| 		otp_ptr->flag |= 0x20; /* valid VCM in OTP */
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_VCM_DIR_REG,
 | |
| 			1, &otp_ptr->vcm_dir);
 | |
| 		checksum += otp_ptr->vcm_dir;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_VCM_START_REG,
 | |
| 			1, &temp);
 | |
| 		checksum += temp;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_VCM_START_REG + 1,
 | |
| 			1, &otp_ptr->vcm_start);
 | |
| 		checksum += otp_ptr->vcm_start;
 | |
| 		otp_ptr->vcm_start |= (temp << 8);
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_VCM_END_REG,
 | |
| 			1, &temp);
 | |
| 		checksum += temp;
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_VCM_END_REG + 1,
 | |
| 			1, &otp_ptr->vcm_end);
 | |
| 		checksum += otp_ptr->vcm_end;
 | |
| 		otp_ptr->vcm_end |= (temp << 8);
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_VCM_CHECKSUM_REG,
 | |
| 			1, &temp);
 | |
| 		if (ret != 0 || (checksum % 0xff) != temp) {
 | |
| 			dev_err(dev,
 | |
| 				"otp VCM info: check sum (%d,%d),ret = %d !\n",
 | |
| 				checksum, temp, ret);
 | |
| 			goto err;
 | |
| 		}
 | |
| 		dev_dbg(dev, "vcm_info: 0x%x, 0x%x, 0x%x!\n",
 | |
| 			otp_ptr->vcm_start,
 | |
| 			otp_ptr->vcm_end,
 | |
| 			otp_ptr->vcm_dir);
 | |
| 	}
 | |
| 
 | |
| 	checksum = 0;
 | |
| 	/* OTP SPC calibration data */
 | |
| 	ret = imx258_read_reg_otp(client, GZ_SPC_FLAG_REG,
 | |
| 		1, &otp_flag);
 | |
| 	if (otp_flag == 0x01) {
 | |
| 		otp_ptr->flag |= 0x08; /* valid LSC in OTP */
 | |
| 		for (i = 0; i < 126; i++) {
 | |
| 			ret |= imx258_read_reg_otp(client,
 | |
| 				GZ_SPC_DATA_START_REG + i,
 | |
| 				1, &temp);
 | |
| 			otp_ptr->spc[i] = (uint8_t)temp;
 | |
| 			checksum += temp;
 | |
| 			dev_dbg(dev,
 | |
| 				"otp read spc addr = 0x%04x, spc[%d] = %d\n",
 | |
| 				GZ_SPC_DATA_START_REG + i, i, temp);
 | |
| 		}
 | |
| 		ret |= imx258_read_reg_otp(client, GZ_SPC_CHECKSUM_REG,
 | |
| 			1, &temp);
 | |
| 		if (ret != 0 || (checksum % 0xff) != temp) {
 | |
| 			dev_err(dev,
 | |
| 				"otp spc info: check sum (%d,%d),ret = %d !\n",
 | |
| 				checksum, temp, ret);
 | |
| 			goto err;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (otp_ptr->flag) {
 | |
| 		imx258_eeprom_dev->otp = otp_ptr;
 | |
| 	} else {
 | |
| 		imx258_eeprom_dev->otp = NULL;
 | |
| 		kfree(otp_ptr);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| err:
 | |
| 	imx258_eeprom_dev->otp = NULL;
 | |
| 	kfree(otp_ptr);
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static int imx258_otp_read(struct imx258_eeprom_device *imx258_eeprom_dev)
 | |
| {
 | |
| 	u8 vendor_flag = 0;
 | |
| 	struct i2c_client *client = imx258_eeprom_dev->client;
 | |
| 
 | |
| 	vendor_flag = get_vendor_flag(client);
 | |
| 	if (vendor_flag == 0x80)
 | |
| 		imx258_otp_read_gz(imx258_eeprom_dev);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static long imx258_eeprom_ioctl(struct v4l2_subdev *sd,
 | |
| 	unsigned int cmd, void *arg)
 | |
| {
 | |
| 	struct imx258_eeprom_device *imx258_eeprom_dev =
 | |
| 		sd_to_imx258_eeprom(sd);
 | |
| 	imx258_otp_read(imx258_eeprom_dev);
 | |
| 	if (arg && imx258_eeprom_dev->otp)
 | |
| 		memcpy(arg, imx258_eeprom_dev->otp,
 | |
| 			sizeof(struct imx258_otp_info));
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct v4l2_subdev_core_ops imx258_eeprom_core_ops = {
 | |
| 	.ioctl = imx258_eeprom_ioctl,
 | |
| };
 | |
| 
 | |
| static const struct v4l2_subdev_ops imx258_eeprom_ops = {
 | |
| 	.core = &imx258_eeprom_core_ops,
 | |
| };
 | |
| 
 | |
| static void imx258_eeprom_subdev_cleanup(struct imx258_eeprom_device *dev)
 | |
| {
 | |
| 	v4l2_device_unregister_subdev(&dev->sd);
 | |
| 	media_entity_cleanup(&dev->sd.entity);
 | |
| }
 | |
| 
 | |
| static int imx258_eeprom_probe(struct i2c_client *client,
 | |
| 	const struct i2c_device_id *id)
 | |
| {
 | |
| 	struct imx258_eeprom_device *imx258_eeprom_dev;
 | |
| 
 | |
| 	dev_info(&client->dev, "probing...\n");
 | |
| 	imx258_eeprom_dev = devm_kzalloc(&client->dev,
 | |
| 		sizeof(*imx258_eeprom_dev),
 | |
| 		GFP_KERNEL);
 | |
| 
 | |
| 	if (imx258_eeprom_dev == NULL) {
 | |
| 		dev_err(&client->dev, "Probe failed\n");
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 	v4l2_i2c_subdev_init(&imx258_eeprom_dev->sd,
 | |
| 		client, &imx258_eeprom_ops);
 | |
| 	imx258_eeprom_dev->client = client;
 | |
| 	pm_runtime_set_active(&client->dev);
 | |
| 	pm_runtime_enable(&client->dev);
 | |
| 	pm_runtime_idle(&client->dev);
 | |
| 
 | |
| 	dev_info(&client->dev, "probing successful\n");
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void imx258_eeprom_remove(struct i2c_client *client)
 | |
| {
 | |
| 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
 | |
| 	struct imx258_eeprom_device *imx258_eeprom_dev =
 | |
| 		sd_to_imx258_eeprom(sd);
 | |
| 	kfree(imx258_eeprom_dev->otp);
 | |
| 	pm_runtime_disable(&client->dev);
 | |
| 	imx258_eeprom_subdev_cleanup(imx258_eeprom_dev);
 | |
| }
 | |
| 
 | |
| static int __maybe_unused imx258_eeprom_suspend(struct device *dev)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int  __maybe_unused imx258_eeprom_resume(struct device *dev)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct i2c_device_id imx258_eeprom_id_table[] = {
 | |
| 	{ DEVICE_NAME, 0 },
 | |
| 	{ { 0 } }
 | |
| };
 | |
| MODULE_DEVICE_TABLE(i2c, imx258_eeprom_id_table);
 | |
| 
 | |
| static const struct of_device_id imx258_eeprom_of_table[] = {
 | |
| 	{ .compatible = "sony,imx258_eeprom" },
 | |
| 	{ { 0 } }
 | |
| };
 | |
| MODULE_DEVICE_TABLE(of, imx258_eeprom_of_table);
 | |
| 
 | |
| static const struct dev_pm_ops imx258_eeprom_pm_ops = {
 | |
| 	SET_SYSTEM_SLEEP_PM_OPS(imx258_eeprom_suspend, imx258_eeprom_resume)
 | |
| 	SET_RUNTIME_PM_OPS(imx258_eeprom_suspend, imx258_eeprom_resume, NULL)
 | |
| };
 | |
| 
 | |
| static struct i2c_driver imx258_eeprom_i2c_driver = {
 | |
| 	.driver = {
 | |
| 		.name = DEVICE_NAME,
 | |
| 		.pm = &imx258_eeprom_pm_ops,
 | |
| 		.of_match_table = imx258_eeprom_of_table,
 | |
| 	},
 | |
| 	.probe = &imx258_eeprom_probe,
 | |
| 	.remove = &imx258_eeprom_remove,
 | |
| 	.id_table = imx258_eeprom_id_table,
 | |
| };
 | |
| 
 | |
| module_i2c_driver(imx258_eeprom_i2c_driver);
 | |
| 
 | |
| MODULE_DESCRIPTION("IMX258 OTP driver");
 | |
| MODULE_LICENSE("GPL v2");
 |