378 lines
9.8 KiB
C
378 lines
9.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (c) 2022 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 "imx214_eeprom_head.h"
|
|
|
|
#define DEVICE_NAME "imx214_eeprom"
|
|
|
|
static inline struct imx214_eeprom_device
|
|
*sd_to_imx214_eeprom(struct v4l2_subdev *subdev)
|
|
{
|
|
return container_of(subdev, struct imx214_eeprom_device, sd);
|
|
}
|
|
|
|
/* Read registers up to 4 at a time */
|
|
static int imx214_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 imx214_otp_read_gz(struct imx214_eeprom_device *imx214_eeprom_dev)
|
|
{
|
|
struct i2c_client *client = imx214_eeprom_dev->client;
|
|
int otp_flag, i;
|
|
struct imx214_otp_info *otp_ptr;
|
|
struct device *dev = &imx214_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 = imx214_read_reg_otp(client, GZ_INFO_FLAG_REG,
|
|
1, &otp_flag);
|
|
if (otp_flag == 0x01) {
|
|
otp_ptr->flag = 0x80; /* valid INFO in OTP */
|
|
ret |= imx214_read_reg_otp(client, GZ_ID_REG,
|
|
1, &otp_ptr->module_id);
|
|
ret |= imx214_read_reg_otp(client, GZ_LENS_ID_REG,
|
|
1, &otp_ptr->lens_id);
|
|
ret |= imx214_read_reg_otp(client, GZ_PRODUCT_YEAR_REG,
|
|
1, &otp_ptr->year);
|
|
ret |= imx214_read_reg_otp(client, GZ_PRODUCT_MONTH_REG,
|
|
1, &otp_ptr->month);
|
|
ret |= imx214_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 = imx214_read_reg_otp(client, GZ_AWB_FLAG_REG,
|
|
1, &otp_flag);
|
|
if (otp_flag == 0x01) {
|
|
otp_ptr->flag |= 0x40; /* valid AWB in OTP */
|
|
ret |= imx214_read_reg_otp(client, GZ_CUR_R_REG,
|
|
1, &r_value);
|
|
checksum += r_value;
|
|
ret |= imx214_read_reg_otp(client, GZ_CUR_GR_REG,
|
|
1, &gr_value);
|
|
checksum += gr_value;
|
|
ret |= imx214_read_reg_otp(client, GZ_CUR_GB_REG,
|
|
1, &gb_value);
|
|
checksum += gb_value;
|
|
ret |= imx214_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 |= imx214_read_reg_otp(client, GZ_GOLDEN_R_REG,
|
|
1, &r_value);
|
|
checksum += r_value;
|
|
ret |= imx214_read_reg_otp(client, GZ_GOLDEN_GR_REG,
|
|
1, &gr_value);
|
|
checksum += gr_value;
|
|
ret |= imx214_read_reg_otp(client, GZ_GOLDEN_GB_REG,
|
|
1, &gb_value);
|
|
checksum += gb_value;
|
|
ret |= imx214_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 |= imx214_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 = imx214_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 |= imx214_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 |= imx214_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 = imx214_read_reg_otp(client, GZ_VCM_FLAG_REG,
|
|
1, &otp_flag);
|
|
if (otp_flag == 0x01) {
|
|
otp_ptr->flag |= 0x20; /* valid VCM in OTP */
|
|
ret |= imx214_read_reg_otp(client, GZ_VCM_DIR_REG,
|
|
1, &otp_ptr->vcm_dir);
|
|
checksum += otp_ptr->vcm_dir;
|
|
ret |= imx214_read_reg_otp(client, GZ_VCM_START_REG,
|
|
1, &temp);
|
|
checksum += temp;
|
|
ret |= imx214_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 |= imx214_read_reg_otp(client, GZ_VCM_END_REG,
|
|
1, &temp);
|
|
checksum += temp;
|
|
ret |= imx214_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 |= imx214_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 = imx214_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 |= imx214_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 |= imx214_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) {
|
|
imx214_eeprom_dev->otp = otp_ptr;
|
|
} else {
|
|
imx214_eeprom_dev->otp = NULL;
|
|
kfree(otp_ptr);
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
imx214_eeprom_dev->otp = NULL;
|
|
kfree(otp_ptr);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int imx214_otp_read(struct imx214_eeprom_device *imx214_eeprom_dev)
|
|
{
|
|
u8 vendor_flag = 0;
|
|
struct i2c_client *client = imx214_eeprom_dev->client;
|
|
|
|
vendor_flag = get_vendor_flag(client);
|
|
if (vendor_flag == 0x80)
|
|
imx214_otp_read_gz(imx214_eeprom_dev);
|
|
return 0;
|
|
}
|
|
|
|
static long imx214_eeprom_ioctl(struct v4l2_subdev *sd,
|
|
unsigned int cmd, void *arg)
|
|
{
|
|
struct imx214_eeprom_device *imx214_eeprom_dev =
|
|
sd_to_imx214_eeprom(sd);
|
|
imx214_otp_read(imx214_eeprom_dev);
|
|
if (arg && imx214_eeprom_dev->otp)
|
|
memcpy(arg, imx214_eeprom_dev->otp,
|
|
sizeof(struct imx214_otp_info));
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_core_ops imx214_eeprom_core_ops = {
|
|
.ioctl = imx214_eeprom_ioctl,
|
|
};
|
|
|
|
static const struct v4l2_subdev_ops imx214_eeprom_ops = {
|
|
.core = &imx214_eeprom_core_ops,
|
|
};
|
|
|
|
static void imx214_eeprom_subdev_cleanup(struct imx214_eeprom_device *dev)
|
|
{
|
|
v4l2_device_unregister_subdev(&dev->sd);
|
|
media_entity_cleanup(&dev->sd.entity);
|
|
}
|
|
|
|
static int imx214_eeprom_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct imx214_eeprom_device *imx214_eeprom_dev;
|
|
|
|
dev_info(&client->dev, "probing...\n");
|
|
imx214_eeprom_dev = devm_kzalloc(&client->dev,
|
|
sizeof(*imx214_eeprom_dev),
|
|
GFP_KERNEL);
|
|
|
|
if (!imx214_eeprom_dev) {
|
|
dev_err(&client->dev, "Probe failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
v4l2_i2c_subdev_init(&imx214_eeprom_dev->sd,
|
|
client, &imx214_eeprom_ops);
|
|
imx214_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 int imx214_eeprom_remove(struct i2c_client *client)
|
|
{
|
|
struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
|
struct imx214_eeprom_device *imx214_eeprom_dev =
|
|
sd_to_imx214_eeprom(sd);
|
|
kfree(imx214_eeprom_dev->otp);
|
|
pm_runtime_disable(&client->dev);
|
|
imx214_eeprom_subdev_cleanup(imx214_eeprom_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused imx214_eeprom_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused imx214_eeprom_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id imx214_eeprom_id_table[] = {
|
|
{ DEVICE_NAME, 0 },
|
|
{ { 0 } }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, imx214_eeprom_id_table);
|
|
|
|
static const struct of_device_id imx214_eeprom_of_table[] = {
|
|
{ .compatible = "sony,imx214_eeprom" },
|
|
{ { 0 } }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, imx214_eeprom_of_table);
|
|
|
|
static const struct dev_pm_ops imx214_eeprom_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(imx214_eeprom_suspend, imx214_eeprom_resume)
|
|
SET_RUNTIME_PM_OPS(imx214_eeprom_suspend, imx214_eeprom_resume, NULL)
|
|
};
|
|
|
|
static struct i2c_driver imx214_eeprom_i2c_driver = {
|
|
.driver = {
|
|
.name = DEVICE_NAME,
|
|
.pm = &imx214_eeprom_pm_ops,
|
|
.of_match_table = imx214_eeprom_of_table,
|
|
},
|
|
.probe = &imx214_eeprom_probe,
|
|
.remove = &imx214_eeprom_remove,
|
|
.id_table = imx214_eeprom_id_table,
|
|
};
|
|
|
|
module_i2c_driver(imx214_eeprom_i2c_driver);
|
|
|
|
MODULE_DESCRIPTION("IMX214 OTP driver");
|
|
MODULE_LICENSE("GPL v2");
|