[PATCH 1/1] media: i2c: alvium: add camera sysfs attributes

From: Tommaso Merciai
Date: Mon Sep 09 2024 - 06:59:07 EST


Add the following sysfs attributes:

- cci_register_layout_version: dump current cci regs layout version
- csi_clock_mhz: dump current csi2 freq
- device_capabilities: dump alvium device capabilities
- device_guid: dump alvium device guid
- device_version: dump device version
- family_name: dump alvium device family name
- genio: generic camera input/output xfer
- lane_count: dump alvium device data lanes number
- manufacturer_info: custom manufacturer info
- manufacturer_name: manufacturer info
- mode: show/set bcm/gencp mode
- model_name: dump alvium device model name
- serial_number: dump alvium device serial number
- user_defined_name: show user defined name

alvium_sysfs_add_dev_info() create alvium sysfs attributes.
alvium_sysfs_rm_dev_info() remove alvium attributes.

Signed-off-by: Tommaso Merciai <tomm.merciai@xxxxxxxxx>
---
drivers/media/i2c/alvium-csi2.c | 484 ++++++++++++++++++++++++++++++++
drivers/media/i2c/alvium-csi2.h | 25 ++
2 files changed, 509 insertions(+)

diff --git a/drivers/media/i2c/alvium-csi2.c b/drivers/media/i2c/alvium-csi2.c
index 5ddfd3dcb188..11bfeb3bb1fd 100644
--- a/drivers/media/i2c/alvium-csi2.c
+++ b/drivers/media/i2c/alvium-csi2.c
@@ -1653,6 +1653,482 @@ static int alvium_hw_init(struct alvium_dev *alvium)
return 0;
}

+/* --------------- Sysfs attributes --------------- */
+static ssize_t cci_register_layout_version_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 min, maj, ver;
+ int ret = 0;
+
+ ret = alvium_read(alvium, REG_BCRM_MINOR_VERSION_R, &min, &ret);
+ ret = alvium_read(alvium, REG_BCRM_MAJOR_VERSION_R, &maj, &ret);
+ if (ret)
+ return ret;
+
+ ver = (maj << 16) | min;
+
+ return sysfs_emit(buf, "%u\n", (u32)ver);
+}
+static DEVICE_ATTR_RO(cci_register_layout_version);
+
+static ssize_t csi_clock_mhz_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val;
+ int ret;
+
+ ret = alvium_read(alvium, REG_BCRM_CSI2_CLOCK_RW, &val, NULL);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", (u32)val);
+}
+static DEVICE_ATTR_RO(csi_clock_mhz);
+
+static ssize_t device_capabilities_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val;
+ int ret;
+
+ ret = alvium_read(alvium, REG_CCI_DEV_CAP_RW, &val, NULL);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "0x%llx\n", val);
+}
+static DEVICE_ATTR_RO(device_capabilities);
+
+static ssize_t device_guid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val[8];
+ int i, ret = 0;
+
+ /* 64 bytes length */
+ for (i = 0; i < 8; i++)
+ alvium_read(alvium, REG_CCI_DEVICE_GUID_R + 8 * i,
+ &val[i], &ret);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", (char *)&val);
+}
+static DEVICE_ATTR_RO(device_guid);
+
+static ssize_t device_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val[8];
+ int i, ret = 0;
+
+ /* 64 bytes length */
+ for (i = 0; i < 8; i++)
+ alvium_read(alvium, REG_CCI_DEVICE_VERSION_R + 8 * i,
+ &val[i], &ret);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", (char *)&val);
+}
+static DEVICE_ATTR_RO(device_version);
+
+static ssize_t family_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val[8];
+ int i, ret = 0;
+
+ /* 64 bytes length */
+ for (i = 0; i < 8; i++)
+ alvium_read(alvium, REG_CCI_DEVICE_FAMILY_NAME_R + 8 * i,
+ &val[i], &ret);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", (char *)&val);
+}
+static DEVICE_ATTR_RO(family_name);
+
+static void alvium_genio_read_prepare(struct alvium_dev *alvium,
+ struct alvium_genio_proto *proto)
+{
+ alvium->req_data.reg = proto->reg;
+ alvium->req_data.count = proto->count;
+}
+
+static ssize_t alvium_genio_xfer(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ struct alvium_genio_proto *proto;
+ char *kbuf;
+ char *data;
+ int ret = 0;
+
+ switch (alvium->genio_op) {
+ case ALVIUM_GENIO_READ:
+ kbuf = kzalloc(alvium->req_data.count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ ret = regmap_bulk_read(alvium->regmap, alvium->req_data.reg,
+ (char *)kbuf, alvium->req_data.count);
+ if (ret)
+ goto genio_xfer_done;
+
+ memcpy((char *)buf, kbuf, alvium->req_data.count);
+
+ ret = alvium->req_data.count;
+ break;
+
+ case ALVIUM_GENIO_WRITE:
+ proto = (struct alvium_genio_proto *)buf;
+ data = (char *)(proto + 1);
+
+ kbuf = kzalloc(proto->count, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ memcpy(kbuf, data, proto->count);
+
+ ret = regmap_bulk_write(alvium->regmap, proto->reg,
+ kbuf, proto->count);
+ if (ret)
+ goto genio_xfer_done;
+
+ ret = proto->count;
+ break;
+ }
+
+genio_xfer_done:
+ kfree(kbuf);
+ return ret;
+}
+
+static ssize_t genio_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return alvium_genio_xfer(dev, attr, buf, 0);
+}
+
+static ssize_t genio_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ struct alvium_genio_proto *proto = (struct alvium_genio_proto *)buf;
+ int ret = 0;
+
+ if (proto->op) {
+ alvium->genio_op = ALVIUM_GENIO_READ;
+ alvium_genio_read_prepare(alvium, proto);
+ return ret;
+ }
+
+ alvium->genio_op = ALVIUM_GENIO_WRITE;
+
+ return alvium_genio_xfer(dev, attr, buf, count);
+}
+static DEVICE_ATTR_RW(genio);
+
+static ssize_t lane_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+
+ return sysfs_emit(buf, "%d\n", alvium->h_sup_csi_lanes);
+}
+static DEVICE_ATTR_RO(lane_count);
+
+static ssize_t manufacturer_info_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val[8];
+ int i, ret = 0;
+
+ /* 64 bytes length */
+ for (i = 0; i < 8; i++)
+ alvium_read(alvium, REG_CCI_MANUFACTURER_INFO_R + 8 * i,
+ &val[i], &ret);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", (char *)&val);
+}
+static DEVICE_ATTR_RO(manufacturer_info);
+
+static ssize_t manufacturer_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val[8];
+ int i, ret = 0;
+
+ /* 64 bytes length */
+ for (i = 0; i < 8; i++)
+ alvium_read(alvium, REG_CCI_MANUFACTURER_NAME_R + 8 * i,
+ &val[i], &ret);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", (char *)&val);
+}
+static DEVICE_ATTR_RO(manufacturer_name);
+
+static ssize_t mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val;
+ int ret = 0;
+
+ if (strncmp(buf, "bcm", strlen(buf) - 1) == 0)
+ alvium->bcrm_mode = ALVIUM_BCM_MODE;
+ else if (strncmp(buf, "gencp", strlen(buf) - 1) == 0)
+ alvium->bcrm_mode = ALVIUM_GENCP_MODE;
+ else
+ return -EINVAL;
+
+ alvium_write(alvium, REG_GENCP_CHANGEMODE_W,
+ (u8)alvium->bcrm_mode, &ret);
+ if (ret)
+ return ret;
+
+ read_poll_timeout(alvium_read, val,
+ (val == alvium->bcrm_mode),
+ 15000, 45000, true,
+ alvium, REG_GENCP_CURRENTMODE_R,
+ &val, &ret);
+ if (ret) {
+ dev_err(dev, "poll curr mode fail\n");
+ return ret;
+ }
+
+ return ret ? ret : len;
+}
+
+static ssize_t mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 mode;
+ int ret = 0;
+
+ ret = alvium_read(alvium, REG_GENCP_CURRENTMODE_R, &mode, NULL);
+ if (ret)
+ return ret;
+
+ switch (mode) {
+ case ALVIUM_BCM_MODE:
+ return sysfs_emit(buf, "bcm\n");
+ case ALVIUM_GENCP_MODE:
+ return sysfs_emit(buf, "gencp\n");
+ }
+
+ return sysfs_emit(buf, "Alvium is in invalid Mode: 0x%x\n", (u8)mode);
+}
+static DEVICE_ATTR_RW(mode);
+
+static ssize_t model_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val[8];
+ int i, ret = 0;
+
+ /* 64 bytes length */
+ for (i = 0; i < 8; i++)
+ alvium_read(alvium, REG_CCI_DEVICE_MODEL_NAME_R + 8 * i,
+ &val[i], &ret);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", (char *)&val);
+}
+static DEVICE_ATTR_RO(model_name);
+
+static ssize_t serial_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val[8];
+ int i, ret = 0;
+
+ /* 64 bytes length */
+ for (i = 0; i < 8; i++)
+ alvium_read(alvium, REG_CCI_DEVICE_SERIAL_NUMBER_R + 8 * i,
+ &val[i], &ret);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", (char *)&val);
+}
+static DEVICE_ATTR_RO(serial_number);
+
+static ssize_t user_defined_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct alvium_dev *alvium = sd_to_alvium(sd);
+ u64 val[8];
+ int i, ret = 0;
+
+ /* 64 bytes length */
+ for (i = 0; i < 8; i++)
+ alvium_read(alvium, REG_CCI_USER_DEFINED_NAME_R + 8 * i,
+ &val[i], &ret);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", (char *)&val);
+}
+static DEVICE_ATTR_RO(user_defined_name);
+
+static int alvium_sysfs_add_dev_info(struct alvium_dev *alvium)
+{
+ struct device *dev = &alvium->i2c_client->dev;
+ int ret;
+
+ ret = device_create_file(dev, &dev_attr_cci_register_layout_version);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_cci_register_layout_version);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_csi_clock_mhz);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_csi_clock_mhz);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_device_capabilities);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_device_capabilities);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_device_guid);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_device_guid);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_device_version);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_device_version);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_family_name);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_family_name);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_genio);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_genio);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_lane_count);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_lane_count);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_manufacturer_info);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_manufacturer_info);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_manufacturer_name);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_manufacturer_name);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_serial_number);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_serial_number);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_mode);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_mode);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_model_name);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_model_name);
+ return ret;
+ }
+
+ ret = device_create_file(dev, &dev_attr_user_defined_name);
+ if (ret) {
+ device_remove_file(dev, &dev_attr_user_defined_name);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void alvium_sysfs_rm_dev_info(struct alvium_dev *alvium)
+{
+ struct device *dev = &alvium->i2c_client->dev;
+
+ device_remove_file(dev, &dev_attr_cci_register_layout_version);
+ device_remove_file(dev, &dev_attr_csi_clock_mhz);
+ device_remove_file(dev, &dev_attr_device_capabilities);
+ device_remove_file(dev, &dev_attr_device_guid);
+ device_remove_file(dev, &dev_attr_device_version);
+ device_remove_file(dev, &dev_attr_family_name);
+ device_remove_file(dev, &dev_attr_genio);
+ device_remove_file(dev, &dev_attr_lane_count);
+ device_remove_file(dev, &dev_attr_manufacturer_info);
+ device_remove_file(dev, &dev_attr_manufacturer_name);
+ device_remove_file(dev, &dev_attr_mode);
+ device_remove_file(dev, &dev_attr_model_name);
+ device_remove_file(dev, &dev_attr_serial_number);
+ device_remove_file(dev, &dev_attr_user_defined_name);
+}
+
/* --------------- Subdev Operations --------------- */
static int alvium_s_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
@@ -2469,6 +2945,12 @@ static int alvium_probe(struct i2c_client *client)
goto err_powerdown;
}

+ ret = alvium_sysfs_add_dev_info(alvium);
+ if (ret) {
+ dev_err_probe(dev, ret, "sysfs_add_dev_info fail\n");
+ goto err_powerdown;
+ }
+
/*
* Enable runtime PM without autosuspend:
*
@@ -2499,6 +2981,7 @@ static int alvium_probe(struct i2c_client *client)
err_pm:
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
+ alvium_sysfs_rm_dev_info(alvium);
kfree(alvium->alvium_csi2_fmt);
err_powerdown:
alvium_set_power(alvium, false);
@@ -2513,6 +2996,7 @@ static void alvium_remove(struct i2c_client *client)
struct device *dev = &alvium->i2c_client->dev;

v4l2_async_unregister_subdev(sd);
+ alvium_sysfs_rm_dev_info(alvium);
alvium_subdev_cleanup(alvium);
kfree(alvium->alvium_csi2_fmt);
/*
diff --git a/drivers/media/i2c/alvium-csi2.h b/drivers/media/i2c/alvium-csi2.h
index 978af44f76c7..8567898d39ed 100644
--- a/drivers/media/i2c/alvium-csi2.h
+++ b/drivers/media/i2c/alvium-csi2.h
@@ -28,7 +28,16 @@
/* Basic Control Register Map register offsets (BCRM) */
#define REG_BCRM_MINOR_VERSION_R CCI_REG16(0x0000)
#define REG_BCRM_MAJOR_VERSION_R CCI_REG16(0x0002)
+#define REG_CCI_DEV_CAP_RW CCI_REG64(0x0008)
#define REG_BCRM_REG_ADDR_R CCI_REG16(0x0014)
+#define REG_CCI_DEVICE_GUID_R CCI_REG64_LE(0x0018)
+#define REG_CCI_MANUFACTURER_NAME_R CCI_REG64_LE(0x0058)
+#define REG_CCI_DEVICE_MODEL_NAME_R CCI_REG64_LE(0x0098)
+#define REG_CCI_DEVICE_FAMILY_NAME_R CCI_REG64_LE(0x00d8)
+#define REG_CCI_DEVICE_VERSION_R CCI_REG64_LE(0x0118)
+#define REG_CCI_MANUFACTURER_INFO_R CCI_REG64_LE(0x0158)
+#define REG_CCI_DEVICE_SERIAL_NUMBER_R CCI_REG64_LE(0x0198)
+#define REG_CCI_USER_DEFINED_NAME_R CCI_REG64_LE(0x01d8)

#define REG_BCRM_FEATURE_INQUIRY_R REG_BCRM_V4L2_64BIT(0x0008)
#define REG_BCRM_DEVICE_FW REG_BCRM_V4L2_64BIT(0x0010)
@@ -211,6 +220,19 @@
#define BCRM_DEVICE_FW_SPEC_MASK GENMASK_ULL(7, 0)
#define BCRM_DEVICE_FW_SPEC_SHIFT 0

+enum alvium_fw_ops {
+ ALVIUM_GENIO_WRITE,
+ ALVIUM_GENIO_READ,
+ ALVIUM_GENIO_NUM_OPS
+};
+
+struct alvium_genio_proto {
+ u16 reg;
+ u16 count;
+ u8 op;
+ u8 reserved[3];
+};
+
enum alvium_bcrm_mode {
ALVIUM_BCM_MODE,
ALVIUM_GENCP_MODE,
@@ -461,6 +483,9 @@ struct alvium_dev {

u8 streaming;
u8 apply_fiv;
+
+ u8 genio_op;
+ struct alvium_genio_proto req_data;
};

static inline struct alvium_dev *sd_to_alvium(struct v4l2_subdev *sd)
--
2.34.1