diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index e2d00df3..8b98fac9 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -15,4 +15,5 @@ obj-m += nv_imx318.o obj-m += nv_imx390.o obj-m += nv_ov5693.o obj-m += nv_ar0234.o +obj-m += pca9570.o endif diff --git a/drivers/media/i2c/pca9570.c b/drivers/media/i2c/pca9570.c new file mode 100644 index 00000000..0eb765cf --- /dev/null +++ b/drivers/media/i2c/pca9570.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * pca9570.c - pca9570 IO Expander driver + * + * Copyright (c) 2016-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#define IMX185_PCA9570_I2C_ADDR (0x24) +#define PCA9570_MODE_STEPS 5 +#define PCA9570_FORWARD 0 +#define PCA9570_REVERSE 1 + +#define BA6208_FORWARD 0x02 +#define BA6208_BACKWARD 0x01 +#define BA6208_BREAK 0x03 +#define DRV8838_FORWARD 0x02 +#define DRV8838_BACKWARD 0x03 +#define DRV8838_BREAK 0x00 + +enum drive_ic { + BA6208 = 0, + DRV8838, +}; + +struct pca9570 { + struct i2c_client *i2c_client; + struct regmap *regmap; + const char *channel; + const char *drive_ic_name; + int drive_ic; +}; + +static int pca9570_write_reg(struct pca9570 *priv, + u8 addr, u8 val) +{ + struct i2c_client *i2c_client = priv->i2c_client; + int err; + + err = regmap_write(priv->regmap, addr, val); + if (err) + dev_err(&i2c_client->dev, "%s:i2c write failed, 0x%x = %x\n", + __func__, addr, val); + + return err; +} + +static int pca9570_icr_move(struct pca9570 *priv, u32 direction, u32 val) +{ + struct i2c_client *i2c_client = priv->i2c_client; + int i; + int steps = 0; + int err = 0; + u8 reg = 0; + u8 reg_forward, reg_revert, reg_brake; + + switch (priv->drive_ic) { + case DRV8838: + reg_forward = DRV8838_FORWARD; + reg_revert = DRV8838_BACKWARD; + reg_brake = DRV8838_BREAK; + break; + case BA6208: + default: + reg_forward = BA6208_FORWARD; + reg_revert = BA6208_BACKWARD; + reg_brake = BA6208_BREAK; + break; + } + + steps = val; + if (steps < 1) + steps = PCA9570_MODE_STEPS; + + if (direction == PCA9570_FORWARD) { + dev_info(&i2c_client->dev, "%s, forward val=%d\n", + __func__, steps); + reg = reg_forward; + } else if (direction == PCA9570_REVERSE) { + dev_info(&i2c_client->dev, "%s, reverse val=%d\n", + __func__, steps); + reg = reg_revert; + } else + return -ENOMEM; + + for (i = 0; i < steps; i++) { + usleep_range(1000*100, 1000*110); + err |= pca9570_write_reg(priv, IMX185_PCA9570_I2C_ADDR, 0x48); + err |= pca9570_write_reg(priv, IMX185_PCA9570_I2C_ADDR, reg); + usleep_range(1000*100, 1000*110); + err |= pca9570_write_reg(priv, IMX185_PCA9570_I2C_ADDR, 0x48); + err |= pca9570_write_reg(priv, IMX185_PCA9570_I2C_ADDR, + reg_brake); + } + if (err) + dev_err(&i2c_client->dev, "%s:i2c write failed\n", __func__); + + return err; +} + + +static int pca9570_icr_daymode(struct pca9570 *priv) +{ + int err = 0; + + err = pca9570_icr_move(priv, PCA9570_FORWARD, PCA9570_MODE_STEPS); + return err; +} + + +static int pca9570_icr_nightmode(struct pca9570 *priv) +{ + int err = 0; + + err = pca9570_icr_move(priv, PCA9570_REVERSE, PCA9570_MODE_STEPS); + return err; +} + +static int pca9570_stats_show(struct seq_file *s, void *data) +{ + return 0; +} + +static int pca9570_debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, pca9570_stats_show, inode->i_private); +} + +static ssize_t pca9570_debugfs_write(struct file *s, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct pca9570 *priv = + ((struct seq_file *)s->private_data)->private; + struct i2c_client *i2c_client = priv->i2c_client; + + char buf[255]; + int buf_size; + u32 val = 0; + int err = 0; + + if (!user_buf || count <= 1) + return -EFAULT; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (buf[0] == 'd') { + dev_info(&i2c_client->dev, "%s, set daymode\n", __func__); + err = pca9570_icr_daymode(priv); + if (err) + dev_info(&i2c_client->dev, "%s, set daymode fail\n", + __func__); + return count; + } + + if (buf[0] == 'n') { + dev_info(&i2c_client->dev, "%s, set nightmode\n", __func__); + err = pca9570_icr_nightmode(priv); + if (err) + dev_info(&i2c_client->dev, "%s, set nightmode fail\n", + __func__); + return count; + } + + if (sscanf(buf + 1, "0x%x", &val) == 1) + goto set_attr; + if (sscanf(buf + 1, "0X%x", &val) == 1) + goto set_attr; + if (sscanf(buf + 1, "%d", &val) == 1) + goto set_attr; + + dev_err(&i2c_client->dev, "SYNTAX ERROR: %s\n", buf); + return -EFAULT; + +set_attr: + dev_info(&i2c_client->dev, "%s, val=%c%d\n", __func__, buf[0], val); + switch (buf[0]) { + case 'f': + err = pca9570_icr_move(priv, PCA9570_FORWARD, val); + if (err) + dev_info(&i2c_client->dev, "%s, move forward fail\n", + __func__); + break; + case 'r': + err = pca9570_icr_move(priv, PCA9570_REVERSE, val); + if (err) + dev_info(&i2c_client->dev, "%s, move reverse fail\n", + __func__); + break; + } + + return count; +} + +static const struct file_operations pca9570_debugfs_fops = { + .open = pca9570_debugfs_open, + .read = seq_read, + .write = pca9570_debugfs_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int pca9570_debugfs_init(const char *dir_name, + struct dentry **d_entry, + struct dentry **f_entry, + struct pca9570 *priv) +{ + struct dentry *dp, *fp; + char dev_name[20]; + struct i2c_client *i2c_client = priv->i2c_client; + struct device_node *np = i2c_client->dev.of_node; + int err = 0; + + if (np) { + err = of_property_read_string(np, "channel", &priv->channel); + if (err) + dev_err(&i2c_client->dev, "channel not found\n"); + + err = snprintf(dev_name, sizeof(dev_name), "pca9570_%s", priv->channel); + if (err < 0) + return -EINVAL; + } + + dp = debugfs_create_dir(dev_name, NULL); + if (dp == NULL) { + dev_err(&i2c_client->dev, "%s: debugfs create dir failed\n", + __func__); + return -ENOMEM; + } + + fp = debugfs_create_file("pca9570", 0644, + dp, priv, &pca9570_debugfs_fops); + if (!fp) { + dev_err(&i2c_client->dev, "%s: debugfs create file failed\n", + __func__); + debugfs_remove_recursive(dp); + return -ENOMEM; + } + + if (d_entry) + *d_entry = dp; + if (f_entry) + *f_entry = fp; + return 0; +} + +static struct regmap_config pca9570_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +#if KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE +static int pca9570_probe(struct i2c_client *client) +#else +static int pca9570_probe(struct i2c_client *client, + const struct i2c_device_id *id) +#endif +{ + struct pca9570 *priv; + struct device_node *np = client->dev.of_node; + int err = 0; + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + priv->i2c_client = client; + priv->regmap = devm_regmap_init_i2c(priv->i2c_client, + &pca9570_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&client->dev, + "regmap init failed: %ld\n", PTR_ERR(priv->regmap)); + return -ENODEV; + } + + err = of_property_read_string(np, "drive_ic", &priv->drive_ic_name); + if (!err && !IS_ERR(priv->drive_ic_name)) { + if (!strcmp(priv->drive_ic_name, "BA6208")) + priv->drive_ic = BA6208; + else if (!strcmp(priv->drive_ic_name, "DRV8838")) + priv->drive_ic = DRV8838; + else { + priv->drive_ic = BA6208; + dev_info(&client->dev, + "%s, unsupport driver ic found\n", __func__); + } + } else { + priv->drive_ic = BA6208; + dev_info(&client->dev, + "%s, drive_ic not found\n", __func__); + } + + err = pca9570_debugfs_init(NULL, NULL, NULL, priv); + if (err) + return err; + /*set daymode by fault*/ + err = pca9570_icr_daymode(priv); + if (err) + return err; + dev_info(&client->dev, "%s: success\n", __func__); + + return err; +} + +#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE +static int pca9570_remove(struct i2c_client *client) +#else +static void pca9570_remove(struct i2c_client *client) +#endif +{ + if (client != NULL) { + i2c_unregister_device(client); + client = NULL; + } + +#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE + return 0; +#endif +} + +static const struct i2c_device_id pca9570_id[] = { + { "pca9570", 0 }, + { }, +}; + +static const struct of_device_id pca9570_of_match[] = { + { .compatible = "nxp,pca9570", }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, pca9570_id); + +static struct i2c_driver pca9570_i2c_driver = { + .driver = { + .name = "pca9570", + .owner = THIS_MODULE, + }, + .probe = pca9570_probe, + .remove = pca9570_remove, + .id_table = pca9570_id, +}; + +module_i2c_driver(pca9570_i2c_driver); + +MODULE_DESCRIPTION("IO Expander driver pca9570"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_LICENSE("GPL v2");