From add07bf1a254bbfe16637bb67a02c78f2a2ee0ee Mon Sep 17 00:00:00 2001 From: Zhenke Fan Date: Sat, 9 Oct 2021 10:38:09 +0800 Subject: [PATCH] media: i2c: otp_eeprom: add sensor otp eeprom driver Signed-off-by: Zhenke Fan Change-Id: If08548c4613289c15947428c8468e7fb3769c60e --- drivers/media/i2c/Kconfig | 7 + drivers/media/i2c/Makefile | 2 + drivers/media/i2c/otp_eeprom.c | 475 +++++++++++++++++++++++++++++++++ drivers/media/i2c/otp_eeprom.h | 137 ++++++++++ 4 files changed, 621 insertions(+) create mode 100644 drivers/media/i2c/otp_eeprom.c create mode 100644 drivers/media/i2c/otp_eeprom.h diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 0f301b609136..5e66cf7dbf22 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -386,6 +386,13 @@ config VIDEO_NVP6324 To compile this driver as a module, choose M here: the module will be called jaguar1_drv. +config VIDEO_OTP_EEPROM + tristate "sensor otp from eeprom support" + depends on VIDEO_V4L2 && I2C + select V4L2_FWNODE + help + This driver supports OTP load from eeprom. + config VIDEO_SAA7110 tristate "Philips SAA7110 video decoder" depends on VIDEO_V4L2 && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index ecad684f08f6..ca2a97313311 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -163,3 +163,5 @@ obj-$(CONFIG_VIDEO_RDACM20) += rdacm20-camera_module.o obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o obj-$(CONFIG_SDR_MAX2175) += max2175.o + +obj-$(CONFIG_VIDEO_OTP_EEPROM) += otp_eeprom.o diff --git a/drivers/media/i2c/otp_eeprom.c b/drivers/media/i2c/otp_eeprom.c new file mode 100644 index 000000000000..f9e6f7df7f26 --- /dev/null +++ b/drivers/media/i2c/otp_eeprom.c @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020 Rockchip Electronics Co., Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "otp_eeprom.h" + +#define DEVICE_NAME "otp_eeprom" + +static inline struct eeprom_device + *sd_to_eeprom(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct eeprom_device, sd); +} + +/* Read registers up to 4 at a time */ +static int 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) + vendor_flag |= 0x80; + return vendor_flag; +} + +static int otp_read_data(struct eeprom_device *eeprom_dev) +{ + struct i2c_client *client = eeprom_dev->client; + int i; + struct otp_info *otp_ptr; + struct device *dev = &eeprom_dev->client->dev; + int ret = 0; + u32 temp = 0; + + otp_ptr = kzalloc(sizeof(*otp_ptr), GFP_KERNEL); + if (!otp_ptr) + return -ENOMEM; + /* OTP base information*/ + ret = read_reg_otp(client, INFO_FLAG_REG, + 1, &otp_ptr->basic_data.flag); + if (otp_ptr->basic_data.flag == 0x01) { + ret |= read_reg_otp(client, INFO_ID_REG, + 1, &otp_ptr->basic_data.id.supplier_id); + ret |= read_reg_otp(client, INFO_ID_REG + 1, + 1, &otp_ptr->basic_data.id.year); + ret |= read_reg_otp(client, INFO_ID_REG + 2, + 1, &otp_ptr->basic_data.id.month); + ret |= read_reg_otp(client, INFO_ID_REG + 3, + 1, &otp_ptr->basic_data.id.day); + ret |= read_reg_otp(client, INFO_ID_REG + 4, + 1, &otp_ptr->basic_data.id.sensor_id); + ret |= read_reg_otp(client, INFO_ID_REG + 5, + 1, &otp_ptr->basic_data.id.lens_id); + ret |= read_reg_otp(client, INFO_ID_REG + 6, + 1, &otp_ptr->basic_data.id.vcm_id); + ret |= read_reg_otp(client, INFO_ID_REG + 7, + 1, &otp_ptr->basic_data.id.driver_ic_id); + ret |= read_reg_otp(client, INFO_ID_REG + 8, + 1, &otp_ptr->basic_data.id.color_temperature_id); + for (i = 0; i < SMARTISAN_PN_SIZE; i++) { + ret |= read_reg_otp(client, SMARTISAN_PN_REG + i, + 1, &temp); + otp_ptr->basic_data.smartisan_pn[i] = temp; + } + for (i = 0; i < MOUDLE_ID_SIZE; i++) { + ret |= read_reg_otp(client, MOUDLE_ID_REG + i, + 1, &temp); + otp_ptr->basic_data.modul_id[i] = temp; + } + ret |= read_reg_otp(client, MIRROR_FLIP_REG, + 1, &otp_ptr->basic_data.mirror_flip); + ret |= read_reg_otp(client, FULL_SIZE_WIGHT_REG, + 2, &temp); + otp_ptr->basic_data.size.width = temp; + ret |= read_reg_otp(client, FULL_SIZE_HEIGHT_REG, + 2, &temp); + otp_ptr->basic_data.size.height = temp; + ret |= read_reg_otp(client, INFO_CHECKSUM_REG, + 1, &otp_ptr->basic_data.checksum); + + dev_dbg(dev, "fasic info: supplier_id(0x%x) lens(0x%x) time(%d_%d_%d)\n", + otp_ptr->basic_data.id.supplier_id, + otp_ptr->basic_data.id.lens_id, + otp_ptr->basic_data.id.year, + otp_ptr->basic_data.id.month, + otp_ptr->basic_data.id.day); + if (ret) + goto err; + } + + /* OTP WB calibration data */ + ret = read_reg_otp(client, AWB_FLAG_REG, + 1, &otp_ptr->awb_data.flag); + if (otp_ptr->awb_data.flag == 0x01) { + ret |= read_reg_otp(client, AWB_VERSION_REG, + 1, &otp_ptr->awb_data.version); + ret |= read_reg_otp(client, CUR_R_REG, + 2, &otp_ptr->awb_data.r_ratio); + ret |= read_reg_otp(client, CUR_B_REG, + 2, &otp_ptr->awb_data.b_ratio); + ret |= read_reg_otp(client, CUR_G_REG, + 2, &otp_ptr->awb_data.g_ratio); + ret |= read_reg_otp(client, GOLDEN_R_REG, + 2, &otp_ptr->awb_data.r_golden); + ret |= read_reg_otp(client, GOLDEN_B_REG, + 2, &otp_ptr->awb_data.b_golden); + ret |= read_reg_otp(client, GOLDEN_G_REG, + 2, &otp_ptr->awb_data.g_golden); + ret |= read_reg_otp(client, AWB_CHECKSUM_REG, + 1, &otp_ptr->awb_data.checksum); + + dev_dbg(dev, "awb version:0x%x\n", + otp_ptr->awb_data.version); + if (ret) + goto err; + } + + /* OTP LSC calibration data */ + ret = read_reg_otp(client, LSC_FLAG_REG, + 1, &otp_ptr->lsc_data.flag); + if (otp_ptr->lsc_data.flag == 0x01) { + ret |= read_reg_otp(client, LSC_VERSION_REG, + 1, &otp_ptr->lsc_data.version); + ret |= read_reg_otp(client, LSC_TABLE_SIZE_REG, + 2, &temp); + otp_ptr->lsc_data.table_size = temp; + for (i = 0; i < LSC_DATA_SIZE; i++) { + ret |= read_reg_otp(client, LSC_DATA_START_REG + i, + 1, &temp); + otp_ptr->lsc_data.data[i] = temp; + } + ret |= read_reg_otp(client, LSC_CHECKSUM_REG, + 1, &otp_ptr->lsc_data.checksum); + dev_dbg(dev, "lsc cur:(version 0x%x, table_size 0x%x checksum 0x%x)\n", + otp_ptr->lsc_data.version, + otp_ptr->lsc_data.table_size, + otp_ptr->lsc_data.checksum); + if (ret) + goto err; + } + + /* OTP sfr calibration data */ + ret = read_reg_otp(client, LSC_FLAG_REG, + 1, &otp_ptr->sfr_otp_data.flag); + if (otp_ptr->sfr_otp_data.flag == 0x01) { + ret |= read_reg_otp(client, SFR_EQUIQ_NUM_REG, + 1, &otp_ptr->sfr_otp_data.equip_num); + ret |= read_reg_otp(client, SFR_C_HOR_REG, + 2, &otp_ptr->sfr_otp_data.center_horizontal); + ret |= read_reg_otp(client, SFR_C_VER_REG, + 2, &otp_ptr->sfr_otp_data.center_vertical); + for (i = 0; i < 3; i++) { + ret |= read_reg_otp(client, SFR_TOP_L_HOR_REG + 16 * i, + 2, &otp_ptr->sfr_otp_data.data[i].top_l_horizontal); + ret |= read_reg_otp(client, SFR_TOP_L_VER_REG + 16 * i, + 2, &otp_ptr->sfr_otp_data.data[i].top_l_vertical); + ret |= read_reg_otp(client, SFR_TOP_R_HOR_REG + 16 * i, + 2, &otp_ptr->sfr_otp_data.data[i].top_r_horizontal); + ret |= read_reg_otp(client, SFR_TOP_R_VER_REG + 16 * i, + 2, &otp_ptr->sfr_otp_data.data[i].top_r_vertical); + ret |= read_reg_otp(client, SFR_BOTTOM_L_HOR_REG + 16 * i, + 2, &otp_ptr->sfr_otp_data.data[i].bottom_l_horizontal); + ret |= read_reg_otp(client, SFR_BOTTOM_L_VER_REG + 16 * i, + 2, &otp_ptr->sfr_otp_data.data[i].bottom_l_vertical); + ret |= read_reg_otp(client, SFR_BOTTOM_R_HOR_REG + 16 * i, + 2, &otp_ptr->sfr_otp_data.data[i].bottom_r_horizontal); + ret |= read_reg_otp(client, SFR_BOTTOM_R_VER_REG + 16 * i, + 2, &otp_ptr->sfr_otp_data.data[i].bottom_r_vertical); + } + + ret |= read_reg_otp(client, SFR_CHECKSUM_REG, + 1, &otp_ptr->sfr_otp_data.checksum); + if (ret) + goto err; + } + + ret = read_reg_otp(client, TOTAL_CHECKSUM_REG, + 1, &otp_ptr->total_checksum); + if (ret) + goto err; + + if (otp_ptr->total_checksum) { + eeprom_dev->otp = otp_ptr; + } else { + eeprom_dev->otp = NULL; + kfree(otp_ptr); + } + + return 0; +err: + eeprom_dev->otp = NULL; + kfree(otp_ptr); + return -EINVAL; +} + +static int otp_read(struct eeprom_device *eeprom_dev) +{ + u8 vendor_flag = 0; + struct i2c_client *client = eeprom_dev->client; + + vendor_flag = get_vendor_flag(client); + if (vendor_flag == 0x80) + otp_read_data(eeprom_dev); + return 0; +} + +static long eeprom_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + struct eeprom_device *eeprom_dev = + sd_to_eeprom(sd); + if (!eeprom_dev->otp) + otp_read(eeprom_dev); + if (arg && eeprom_dev->otp) + memcpy(arg, eeprom_dev->otp, + sizeof(struct otp_info)); + return 0; +} + +#ifdef CONFIG_PROC_FS +static int otp_eeprom_show(struct seq_file *p, void *v) +{ + struct eeprom_device *dev = p->private; + int i = 0; + int j = 0; + + if (dev) { + seq_puts(p, "[Header]\n"); + seq_puts(p, "version=1.0;\n\n"); + + seq_puts(p, "[RKAWBOTPParam]\n"); + seq_printf(p, "flag=%d;\n", dev->otp->awb_data.flag); + seq_printf(p, "r_value=%d;\n", dev->otp->awb_data.r_ratio); + seq_printf(p, "b_value=%d;\n", dev->otp->awb_data.b_ratio); + seq_printf(p, "gr_value=%d;\n", dev->otp->awb_data.g_ratio); + seq_puts(p, "gb_value=-1;\n"); + seq_printf(p, "golden_r_value=%d;\n", dev->otp->awb_data.r_golden); + seq_printf(p, "golden_b_value=%d;\n", dev->otp->awb_data.b_golden); + seq_printf(p, "golden_gr_value=%d;\n", dev->otp->awb_data.g_golden); + seq_puts(p, "golden_gb_value=-1;\n\n"); + + seq_puts(p, "[RKLSCOTPParam]\n"); + seq_printf(p, "flag=%d;\n", dev->otp->lsc_data.flag); + seq_printf(p, "width=%d;\n", dev->otp->basic_data.size.width); + seq_printf(p, "height=%d;\n", dev->otp->basic_data.size.height); + seq_printf(p, "tablesize=%d;\n\n", dev->otp->lsc_data.table_size); + + seq_puts(p, "lsc_r_table=\n"); + for (i = 0; i < 17; i++) { + for (j = 0; j < 17; j++) { + seq_printf(p, "%d", (dev->otp->lsc_data.data[(i * 17 + j) * 2] << 8) + | dev->otp->lsc_data.data[(i * 17 + j) * 2 + 1]); + if (j < 16) + seq_puts(p, " "); + } + if (i < 16) + seq_puts(p, "\n"); + } + seq_puts(p, "\n\n"); + + seq_puts(p, "lsc_b_table=\n"); + for (i = 0; i < 17; i++) { + for (j = 0; j < 17; j++) { + seq_printf(p, "%d", (dev->otp->lsc_data.data[(i * 17 + j) * 2 + + 1734] << 8) | dev->otp->lsc_data.data[(i * 17 + j) * + 2 + 1735]); + if (j < 16) + seq_puts(p, " "); + } + if (i < 16) + seq_puts(p, "\n"); + } + seq_puts(p, "\n\n"); + + seq_puts(p, "lsc_gr_table=\n"); + for (i = 0; i < 17; i++) { + for (j = 0; j < 17; j++) { + seq_printf(p, "%d", (dev->otp->lsc_data.data[(i * 17 + j) * 2 + + 578] << 8) | dev->otp->lsc_data.data[(i * 17 + j) * + 2 + 579]); + if (j < 16) + seq_puts(p, " "); + } + if (i < 16) + seq_puts(p, "\n"); + } + seq_puts(p, "\n\n"); + + seq_puts(p, "lsc_gb_table=\n"); + for (i = 0; i < 17; i++) { + for (j = 0; j < 17; j++) { + seq_printf(p, "%d", (dev->otp->lsc_data.data[(i * 17 + j) * 2 + + 1156] << 8) | dev->otp->lsc_data.data[(i * 17 + j) * + 2 + 1157]); + if (j < 16) + seq_puts(p, " "); + } + if (i < 16) + seq_puts(p, "\n"); + } + seq_puts(p, "\n\n"); + } + return 0; +} + +static int eeprom_open(struct inode *inode, struct file *file) +{ + struct eeprom_device *data = PDE_DATA(inode); + + return single_open(file, otp_eeprom_show, data); +} + +static const struct proc_ops ops = { + .proc_open = eeprom_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +static int eeprom_proc_init(struct eeprom_device *dev) +{ + dev->procfs = proc_create_data(dev->name, 0, NULL, &ops, dev); + if (!dev->procfs) + return -EINVAL; + return 0; +} + +static void eeprom_proc_cleanup(struct eeprom_device *dev) +{ + if (dev->procfs) + remove_proc_entry(dev->name, NULL); + dev->procfs = NULL; +} + +#endif + +static const struct v4l2_subdev_core_ops eeprom_core_ops = { + .ioctl = eeprom_ioctl, +}; + +static const struct v4l2_subdev_ops eeprom_ops = { + .core = &eeprom_core_ops, +}; + +static void eeprom_subdev_cleanup(struct eeprom_device *dev) +{ + v4l2_device_unregister_subdev(&dev->sd); + media_entity_cleanup(&dev->sd.entity); +} + +static int eeprom_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct eeprom_device *eeprom_dev; + + dev_info(&client->dev, "probing...\n"); + eeprom_dev = devm_kzalloc(&client->dev, + sizeof(*eeprom_dev), + GFP_KERNEL); + + if (eeprom_dev == NULL) { + dev_err(&client->dev, "Probe failed\n"); + return -ENOMEM; + } + v4l2_i2c_subdev_init(&eeprom_dev->sd, + client, &eeprom_ops); + eeprom_dev->client = client; + sprintf(eeprom_dev->name, "%s", DEVICE_NAME); + eeprom_proc_init(eeprom_dev); + 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 eeprom_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct eeprom_device *eeprom_dev = + sd_to_eeprom(sd); + kfree(eeprom_dev->otp); + pm_runtime_disable(&client->dev); + eeprom_subdev_cleanup(eeprom_dev); + eeprom_proc_cleanup(eeprom_dev); + + return 0; +} + +static int __maybe_unused eeprom_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused eeprom_resume(struct device *dev) +{ + return 0; +} + +static const struct i2c_device_id eeprom_id_table[] = { + { DEVICE_NAME, 0 }, + { { 0 } } +}; +MODULE_DEVICE_TABLE(i2c, eeprom_id_table); + +static const struct of_device_id eeprom_of_table[] = { + { .compatible = "rk,otp_eeprom" }, + { { 0 } } +}; +MODULE_DEVICE_TABLE(of, eeprom_of_table); + +static const struct dev_pm_ops eeprom_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(eeprom_suspend, eeprom_resume) + SET_RUNTIME_PM_OPS(eeprom_suspend, eeprom_resume, NULL) +}; + +static struct i2c_driver eeprom_i2c_driver = { + .driver = { + .name = DEVICE_NAME, + .pm = &eeprom_pm_ops, + .of_match_table = eeprom_of_table, + }, + .probe = &eeprom_probe, + .remove = &eeprom_remove, + .id_table = eeprom_id_table, +}; + +module_i2c_driver(eeprom_i2c_driver); + +MODULE_DESCRIPTION("OTP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/otp_eeprom.h b/drivers/media/i2c/otp_eeprom.h new file mode 100644 index 000000000000..077af3f94e88 --- /dev/null +++ b/drivers/media/i2c/otp_eeprom.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2020 Rockchip Electronics Co., Ltd. */ + +#ifndef OTP_EEPROM_H +#define OTP_EEPROM_H + +#define SLAVE_ADDRESS 0x50 + +#define INFO_FLAG_REG 0X0000 +#define INFO_ID_REG 0X0001 +#define SMARTISAN_PN_REG 0X000A +#define SMARTISAN_PN_SIZE 0x000C //12 +#define MOUDLE_ID_REG 0X0016 +#define MOUDLE_ID_SIZE 0X0010 //16 +#define MIRROR_FLIP_REG 0X0026 +#define FULL_SIZE_WIGHT_REG 0X0027 +#define FULL_SIZE_HEIGHT_REG 0X0029 +#define INFO_CHECKSUM_REG 0X0033 + +#define AWB_FLAG_REG 0x0034 +#define AWB_VERSION_REG 0x0035 +#define CUR_R_REG 0x0036 +#define CUR_B_REG 0x0038 +#define CUR_G_REG 0x003A +#define GOLDEN_R_REG 0x003C +#define GOLDEN_B_REG 0x003E +#define GOLDEN_G_REG 0x0040 +#define AWB_CHECKSUM_REG 0x0062 + +#define LSC_FLAG_REG 0X0063 +#define LSC_VERSION_REG 0x0064 +#define LSC_TABLE_SIZE_REG 0x0065 +#define LSC_DATA_START_REG 0x0067 +#define LSC_DATA_SIZE 0x0908 //2312 +#define LSC_CHECKSUM_REG 0x097B + +#define SFR_FLAG_REG 0X097C +#define SFR_EQUIQ_NUM_REG 0X097D +#define SFR_C_HOR_REG 0X097E +#define SFR_C_VER_REG 0X0980 +#define SFR_TOP_L_HOR_REG 0X0982 +#define SFR_TOP_L_VER_REG 0X0984 +#define SFR_TOP_R_HOR_REG 0X0986 +#define SFR_TOP_R_VER_REG 0X0988 +#define SFR_BOTTOM_L_HOR_REG 0X098A +#define SFR_BOTTOM_L_VER_REG 0X098C +#define SFR_BOTTOM_R_HOR_REG 0X098E +#define SFR_BOTTOM_R_VER_REG 0X0990 +#define SFR_CHECKSUM_REG 0x09BE + +#define TOTAL_CHECKSUM_REG 0x09BF + +struct id_defination { + u32 supplier_id; + u32 year; + u32 month; + u32 day; + u32 sensor_id; + u32 lens_id; + u32 vcm_id; + u32 driver_ic_id; + u32 color_temperature_id; +}; + +struct full_size { + u16 width; + u16 height; +}; + +struct basic_info { + u32 flag; + struct id_defination id; + u32 smartisan_pn[SMARTISAN_PN_SIZE]; + u32 modul_id[MOUDLE_ID_SIZE]; + u32 mirror_flip; + struct full_size size; + u32 checksum; +}; + +struct awb_otp_info { + u32 flag; + u32 version; + u32 r_ratio; + u32 b_ratio; + u32 g_ratio; + u32 r_golden; + u32 b_golden; + u32 g_golden; + u32 checksum; +}; + +struct lsc_otp_info { + u32 flag; + u32 version; + u16 table_size; + u8 data[LSC_DATA_SIZE]; + u32 checksum; +}; + +struct sfr_data { + u32 top_l_horizontal; + u32 top_l_vertical; + u32 top_r_horizontal; + u32 top_r_vertical; + u32 bottom_l_horizontal; + u32 bottom_l_vertical; + u32 bottom_r_horizontal; + u32 bottom_r_vertical; +}; + +struct sfr_otp_info { + u32 flag; + u32 equip_num; + u32 center_horizontal; + u32 center_vertical; + struct sfr_data data[3]; + u32 checksum; +}; + +struct otp_info { + struct basic_info basic_data; + struct awb_otp_info awb_data; + struct lsc_otp_info lsc_data; + struct sfr_otp_info sfr_otp_data; + u32 total_checksum; +}; + +/* eeprom device structure */ +struct eeprom_device { + struct v4l2_subdev sd; + struct i2c_client *client; + struct otp_info *otp; + struct proc_dir_entry *procfs; + char name[128]; +}; + +#endif /* OTP_EEPROM_H */