Linux MIPI 攝像頭驅動框架編寫(RN6752解碼晶片)

澆築菜鳥發表於2023-11-10

一、概述

在編寫 MIPI 攝像頭驅動之前,需要先了解 Media 子系統的鏈路關係,這裡我就不介紹了,需要的看我之前的筆記:Linux Media 子系統鏈路分析

理解 Media 子系統鏈路關係後,會發現 ISP 不論是在攝像頭端,還是整合在 SOC 中,驅動程式都是差不多的。多觀察一下開發板中的其他案例,便會明白 MIPI 攝像頭驅動部分的程式就是一個 I2C 驅動程式,而 D-PHY 部分的驅動相關廠商已經編寫好了,我們只需要透過 I2C 通道配置好攝像頭相關的暫存器即可。

在 linux 中,攝像頭驅動是基於 V4L2 框架進行實現的,所以在編寫驅動之前,還需明白 V4L2 的框架是怎麼回事,需要了解的可以看其他大佬的部落格,這裡我就不深入介紹了,主要內容還是程式的編寫。V4L2 的框架如下圖所示:

二、測試環境

  1. 開發板:RV1126
  2. ARM Linux 版本:4.19.111
  3. 驅動晶片:RN6752V1
  4. MIPI 通道的資料格式:YUV

三、新增驅動檔案

  1. 建立驅動檔案
    在 SDK 的 sdk/kernel/drivers/media/i2c/ 目錄下新增 rn6752.c 檔案,內容如下

    /* 驅動名稱 */
    #define DRIVER_NAME "rn6752"
    
    /* 驅動版本資訊 */
    #define DRIVER_VERSION			KERNEL_VERSION(0, 0x00, 0x01)
    
    /**
    * @brief 系統檢測到與該驅動程式匹配的 I2C 裝置時
    * @param client 指向 I2C 客戶端結構體的指標。該結構體包含了有關 I2C 裝置的資訊,如裝置地址、匯流排資訊等
    * @param id 指向 I2C 裝置 ID 的指標。這個引數用於在多個相同型別的裝置中進行區分和匹配
    * @return 返回初始化結構
    */
    static int rn6752_probe(struct i2c_client *client, const struct i2c_device_id *id)
    {
    	struct device *dev = &client->dev;
    
    	/* 列印驅動的版本資訊的函式,其作用相當於 printk() 函式 */
    	dev_info(dev, "driver version: %02x.%02x.%02x", DRIVER_VERSION >> 16,
    		(DRIVER_VERSION & 0xff00) >> 8,	DRIVER_VERSION & 0x00ff);
    	
    	return 0;
    }
    
    /**
    * @brief 當裝置驅動被刪除釋放時,執行此函式
    * @param client 指向 I2C 客戶端結構體的指標。該結構體包含了有關 I2C 裝置的資訊,如裝置地址、匯流排資訊等
    * @return 返回操作結果
    */
    static int rn6752_remove(struct i2c_client *client)
    {
    
    	return 0;
    }
    
    /* 裝置樹節點匹配表格,與裝置樹中的節點描述資訊一樣時,匹配成功 */
    #if IS_ENABLED(CONFIG_OF)
    static const struct of_device_id rn6752_of_match[] = {
    	{ .compatible = "richnex,rn6752v1" },
    	{ /* sentinel */ },
    };
    MODULE_DEVICE_TABLE(of, rn6752_of_match);
    #endif
    
    /* 裝置 ID 表格,與裝置名稱一樣時匹配成功,主要用於低版本linux核心的匹配方式 */
    static const struct i2c_device_id rn6752_match_id[] = {
    	{ "richnex,rn6752v1", 0 },
    	{ /* sentinel */ },
    };
    
    /* 描述和註冊一個 I2C 裝置驅動程式 */
    static struct i2c_driver rn6752_i2c_driver = {
    	.driver = {                                             /* 驅動程式資訊的子結構體 */
    		.name	= DRIVER_NAME,                              /* 設定驅動程式的名稱 */
    		.of_match_table = of_match_ptr(rn6752_of_match),    /* 用於匹配裝置樹節點的表格 */
    	},
    	.probe		= rn6752_probe,                 /* I2C 裝置被檢測到時進行裝置初始化和處理 */
    	.remove		= rn6752_remove,                /* I2C 裝置從系統中移除時進行清理和資源釋放 */
    	.id_table	= rn6752_match_id,              /* I2C 裝置 ID 表格,平臺裝置匹配方式之一,用裝置和驅動的名稱進行匹配 */
    };
    
    static int __init sensor_mod_init(void)
    {
    	/* 註冊 I2C 驅動程式 */
    	return i2c_add_driver(&rn6752_i2c_driver);
    }
    
    static void __exit sensor_mod_exit(void)
    {
    	/* 登出 I2C 驅動程式 */
    	i2c_del_driver(&rn6752_i2c_driver);
    }
    
    device_initcall_sync(sensor_mod_init);      /* 註冊一個裝置初始化函式 */
    module_exit(sensor_mod_exit);               /* 登出一個裝置初始化函式 */
    
    MODULE_AUTHOR("jiaozhu <cn_jiaozhu@qq.com>");
    MODULE_DESCRIPTION("RN6752 CMOS Image Sensor driver");
    
    

    注意: 這裡的程式碼是 I2C 驅動的基礎,有不明白的小夥伴可以參看相關資料,也可以看我之前寫的一些驅動筆記

  2. 新增編譯選項
    在 SDK 的 sdk/kernel/drivers/media/i2c/Makefile 檔案中新增 obj-$(CONFIG_VIDEO_RN6752V1) += rn6752.o,如下圖所示

  3. 新增配置資訊
    在 SDK 的 sdk/kernel/drivers/media/i2c/Kconfig 檔案中新增配置資訊,內容如下

    config VIDEO_RN6752V1
            tristate "Richnex RN6752V1 sensor support"
            depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
            depends on MEDIA_CAMERA_SUPPORT
            help
              This is a Video4Linux2 sensor driver for the Sony
              RN6752V1 camera.
    
              To compile this driver as a module, choose M here: the
              module will be called rn6752v1.
    
  4. 開啟 RN6752 驅動的編譯選項
    在對應的.config 檔案或 make menuconfig 圖形配置介面中開啟 RN6752 的驅動程式,完後會得到 CONFIG_VIDEO_RN6752V1 = y 的資訊

  5. 添裝置樹資訊
    在 SDK 的 sdk/kernel/arch/arm/boot/dts/rv1126-alientek.dts 檔案的 I2C 節點中新增 RN6752 解碼晶片的裝置資訊,內容如下

    rn6752: rn6752@2c {
            compatible = "richnex,rn6752v1";
            reg = <0x2c>;
            clocks = <&cru CLK_MIPICSI_OUT>;
            clock-names = "xvclk";
            power-domains = <&power RV1126_PD_VI>;
            pinctrl-names = "rockchip,camera_default";
            pinctrl-0 = <&mipicsi_clk0>;
            avdd-supply = <&vcc_avdd>;
            dovdd-supply = <&vcc_dovdd>;
            dvdd-supply = <&vcc_dvdd>;
            pwdn-gpios = <&gpio1 RK_PD4 GPIO_ACTIVE_HIGH>;
            reset-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>;
            rockchip,camera-hdr-mode = <0>;
            rockchip,camera-module-index = <0>;
            rockchip,camera-module-facing = "front";
            rockchip,camera-module-name = "abcd";
            rockchip,camera-module-lens-name = "a-bc-d";
            port {
                ucam_out0: endpoint {
                    remote-endpoint = <&mipi_in_ucam0>;
                    data-lanes = <1 2 3 4>;
                };
            };
        };
    
  6. 完成以上內容後,準備工作基本完成了,編譯並重寫燒寫核心程式後,會在啟動日誌中列印版本資訊,如下圖所示

四、probe 函式實現

  1. 記憶體申請
    首先需要申請一塊記憶體,用於存放 RN6752 的結構體

    /* 為裝置分配記憶體,並將記憶體與裝置進行關聯。在驅動程式退出時,記憶體會自動被釋放。被稱為“裝置記憶體管理” */
    	rn6752 = devm_kzalloc(dev, sizeof(*rn6752), GFP_KERNEL);
    	if (!rn6752)
    	{
    		dev_err(dev, "Memory control request failed\n");
    		return -ENOMEM;
    	}
    
  2. 獲取裝置樹配置的資訊

    /* 獲取裝置樹資訊 */
    	ret = rn6752_device_tree_info(rn6752);
    	if (ret != 0)
    		return -EINVAL;
    
  3. 保留驅動的所有幀格式,方便在其他函式中使用

    /* rn6752_mipi_framesizes 是 rn6752 mipi 通訊支援的所有幀格式 */
        rn6752->framesize_cfg = rn6752_mipi_framesizes;
    	rn6752->cfg_num = ARRAY_SIZE(rn6752_mipi_framesizes);
    	/* 獲取攝像頭感測器支援的影像幀格式 */
    	rn6752_get_default_format(rn6752, &rn6752->format);
    	rn6752->frame_size = &rn6752->framesize_cfg[0];             /* 設定幀大小 */
    	rn6752->format.width = rn6752->framesize_cfg[0].width;      /* 設定寬度 */
    	rn6752->format.height = rn6752->framesize_cfg[0].height;    /* 設定高度 */
    	rn6752->fps = DIV_ROUND_CLOSEST(rn6752->framesize_cfg[0].max_fps.denominator,
    	rn6752->framesize_cfg[0].max_fps.numerator);        		/* 設定最大幀速率 */
    
  4. 初始化互斥鎖

    mutex_init(&rn6752->lock);
    
  5. 註冊一個V4L2子裝置

    v4l2_i2c_subdev_init(sd, client, &rn6752_subdev_ops);
    
  6. 繫結硬體操作函式

    ret = rn6752_initialize_controls(rn6752);
    	if (ret)
    	{
    		dev_info(dev, "V4l2 control menu initialization failed");
    		goto err_destroy_mutex;
    	}
    

    注意: 此函式的作用是繫結硬體部分的控制功能,也就是或可以透過相應的裝置節點更改裝置的引薦引數,比如亮度、對比度、飽和度、色調等。
    可以透過命令 v4l2-ctl -d /dev/v4l-subdevX --list-ctrls 檢視,如下圖所示:

  7. 關聯 V4l2 子裝置內部操作函式

    /* 關聯子裝置的內部操作函式,用於開啟攝像頭操作 */
    	sd->internal_ops = &rn6752_subdev_internal_ops;
        sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |           /* 裝置有對應的裝置節點檔案,可以透過該檔案進行訪問 */
    		     V4L2_SUBDEV_FL_HAS_EVENTS;                 /* 裝置具有事件機制,可以作為事件源供其他驅動程式使用 */
    
  8. 開啟電源
    mipi 裝置一般都有復位引腳和休眠引腳,工作是需要對相應的引腳進行操作

    /* 開啟裝置的電源 */
    	ret = __rn6752_power_on(rn6752);
    	if (ret)
    		goto err_free_handler;
    
  9. 讀取裝置產品號
    透過讀取 mipi 裝置的產品 id 進行對比,判斷初始化時,裝置是否連線,最終確定時候正常載入驅動

    /* 透過讀取 RN6752 的產品 ID 判斷裝置是否存在 */
    	ret = rn6752_check_sersor_id(rn6752);
    	if (ret)
    		goto err_power_off;
    
  10. 建立 media 裝置

    rn6752->pad.flags = MEDIA_PAD_FL_SOURCE;            /* 表明該子裝置是媒體管道中的源裝置,即資料的起始點 */
    	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;       /* 表示該實體是一個相機感測器 */
    	ret = media_entity_pads_init(&sd->entity, 1, &rn6752->pad);     /* 初始化 sd->entity 媒體實體的 pads(埠)屬性 */
    	if (ret < 0) {
    		dev_err(dev, "Media pad port initialization failed\n");
    		goto err_power_off;
    	}
    
  11. 將感測器子裝置新增到V4L2非同步子系統中
    這裡需要注意一下,在執行此函式之前,在系統中是不會生成 media 裝置節點的

    /* 根據裝置樹提供的資訊,判斷攝像頭的方向 */
        memset(facing, 0, sizeof(facing));
    	if (strcmp(rn6752->module_facing, "back") == 0)
    		facing[0] = 'b';
    	else
    		facing[0] = 'f';
    
        /* 名稱格式如 m00_f_rn6752 1-002c:bus */
    	snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", 
    			rn6752->module_index, facing, DRIVER_NAME, dev_name(sd->dev));
    	/* 將感測器子裝置新增到V4L2非同步子系統中,以便能夠與其他V4L2元件進行互動。 */
    	/* 它會自動設定子裝置的相關回撥函式,並與非同步框架進行適當的關聯,以管理感測器的採集和控制操作 */
    	ret = v4l2_async_register_subdev_sensor_common(sd);
    	if (ret)
    	{
    		dev_err(dev, "Failed to add sensor to V4L2 asynchronous subsystem\n");
    		goto err_clean_entity;
    	}
    

    注意: 在 RV1126 開發板中,建立 media 裝置時,需要注意 mipi 連線的通道,連線 mipi
    csi0 時,需要獲取裝置的資料格式,否則會出現記憶體地址錯誤。解決此錯誤的方法是在 rn6752_get_fmt 函式中預設一個型別值即可,如下圖所示:

  12. 進入休眠模式
    完成初始化後,使感測器進入休眠模式

     /* 完成初始化後,使 rn6752 進入睡眠模式 */
    	rn6752->power_on = false;
        if (!IS_ERR(rn6752->pwdn_gpio))
    				gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1);
    

到此整個驅動程式就算完成了,由於內容較多,所以分成了兩篇筆記,相關的控制函式的實現請看下一篇筆記,下面是 MIPI 驅動框架的程式

五、程式原始碼

rn6752.c

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/media.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/videodev2.h>
#include <linux/version.h>
#include <linux/rk-camera-module.h>
#include <media/media-entity.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-image-sizes.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-subdev.h>

/* 驅動名稱 */
#define DRIVER_NAME "rn6752"

/* 驅動版本資訊 */
#define DRIVER_VERSION			KERNEL_VERSION(0, 0x00, 0x01)

#define RN6752_XVCLK_CLOCK		37125000
	
#define RN6752_PIXEL_RATE		(120 * 1000 * 1000)

/* RN6752 產品 ID 的暫存器地址及預設值,用於判斷裝置是否存在 */
#define REG_SC_CHIP_ID_H		0xFE
#define REG_SC_CHIP_ID_L		0xFD
#define SENSOR_ID(_msb, _lsb)		((_msb) << 8 | (_lsb))
#define RN6752_ID				0x2601
#define REG_NULL			0xFFFF	/* Array end token */

struct rn6752_pixfmt {
	u32 code;
	/* Output format Register Value (REG_FORMAT_CTRL00) */
	struct sensor_register *format_ctrl_regs;
};

struct sensor_register {
	u16 addr;
	u8 value;
};

struct rn6752_framesize {
	u16 width;
	u16 height;
	struct v4l2_fract max_fps;
	u16 max_exp_lines;
	const struct sensor_register *regs;
};

static const char * const rn6752_supply_names[] = {
	"dovdd",	/* Digital I/O power */
	"avdd",		/* Analog power */
	"dvdd",		/* Digital core power */
};

#define RN6752_NUM_SUPPLIES ARRAY_SIZE(rn6752_supply_names)

struct rn6752 {
	struct i2c_client *client;              /* I2C 客戶端結構體的指標,表示 RN6752 攝像頭所連線的 I2C 裝置 */

	struct v4l2_subdev subdev;              /* V4L2 子裝置結構體,表示 RN6752 攝像頭的 V4L2 子裝置 */
	struct v4l2_ctrl_handler ctrls;         /* V4L2 控制器控制程式碼,用於管理 RN6752 攝像頭的各種控制器 */
	struct v4l2_ctrl *link_frequency;       /* V4L2 鏈路頻率控制器,用於設定和獲取 RN6752 攝像頭的鏈路頻率 */
	struct v4l2_fwnode_endpoint bus_cfg;    /* RN6752 攝像頭的匯流排配置,包括資料線數量、資料線極性等資訊 */
	struct v4l2_mbus_framefmt format;       /* V4L2 子裝置幀格式結構體,表示 RN6752 攝像頭支援的影像幀格式 */

	struct media_pad pad;                   /* 媒體子系統中的媒體埠結構體,表示與 RN6752 攝像頭相關聯的媒體埠 */
	
	struct gpio_desc *pwdn_gpio;            /* RN6752 攝像頭的 pwdn GPIO 引腳 */
	struct gpio_desc *reset_gpio;           /* RN6752 攝像頭的復位 GPIO 引腳 */
	struct regulator_bulk_data supplies[RN6752_NUM_SUPPLIES];   /* RN6752 攝像頭使用的電源資源 */
	struct mutex lock;                      /* 互斥鎖,用於保護併發訪問攝像頭裝置 */
	
    unsigned int fps;                       /* RN6752 攝像頭的幀率 */
	const struct rn6752_framesize *frame_size;          /* RN6752 攝像頭支援的幀大小列表的指標 */
	const struct rn6752_framesize *framesize_cfg;       /* 當前 RN6752 攝像頭所選擇的幀大小的指標 */
	unsigned int cfg_num;                   /* RN6752 攝像頭支援的幀大小的數量 */
	int streaming;                          /* RN6752 攝像頭是否正在流式傳輸 */
	bool power_on;                          /* RN6752 攝像頭的電源狀態 */
	
	u32 module_index;                       /* RN6752 攝像頭的模組索引 */
	const char *module_facing;              /* RN6752 攝像頭面向的方向 */
	const char *module_name;                /* RN6752 攝像頭的名稱 */
	const char *len_name;                   /* RN6752 攝像頭的鏡頭名稱 */

	struct clk *xvclk;                      /* RN6752 攝像頭使用的時鐘資源 */
	unsigned int xvclk_frequency;           /* RN6752 攝像頭的 xvclk 頻率 */
};


static const struct rn6752_framesize rn6752_mipi_framesizes[] = {

};

static const s64 link_freq_menu_items[] = {
	594000000,
};

static const struct rn6752_pixfmt rn6752_formats[] = {
	{
		.code = MEDIA_BUS_FMT_UYVY8_2X8,
	}
};

static const char * const rn6752_test_pattern_menu[] = {
	"Disabled",
	"Vertical Color Bars",
};

/**
* @brief 從結構體中的成員指標獲取包含該成員的結構體指標
*/
static inline struct rn6752 *to_rn6752(struct v4l2_subdev *sd)
{
	return container_of(sd, struct rn6752, subdev);
}

/**
* @brief 開啟攝像頭電源管理
* @param rn6752 攝像頭結構體
* @return 返回攝像頭電源設定結果
*/
static int __rn6752_power_on(struct rn6752 *rn6752)
{
	int ret;
	struct device *dev = &rn6752->client->dev;

	/* 列印一條除錯資訊 */
	dev_dbg(dev, "%s(%d)\n", __func__, __LINE__);

	/* 啟用所有的供電器 */
	if (!IS_ERR(rn6752->supplies)) {
		ret = regulator_bulk_enable(RN6752_NUM_SUPPLIES, rn6752->supplies);
		if (ret < 0)
			dev_info(dev, "Failed to enable regulators\n");

		usleep_range(20000, 50000);
	}

	/* 首先將其引腳置為低電平(0),延遲一段時間,然後再將其引腳置為高電平(1),再次延遲一段時間 */
	if (!IS_ERR(rn6752->reset_gpio)) {
		gpiod_set_value_cansleep(rn6752->reset_gpio, 0);
		usleep_range(2000, 5000);
		gpiod_set_value_cansleep(rn6752->reset_gpio, 1);
		usleep_range(2000, 5000);
	}

    /* 使 RN6752 退出睡眠模式 */
	if (!IS_ERR(rn6752->pwdn_gpio)) {
		gpiod_set_value_cansleep(rn6752->pwdn_gpio, 0);
		usleep_range(2000, 5000);
	}

	/* 將 xvclk 的頻率設定為 RN6752_XVCLK_CLOCK */
	if (!IS_ERR(rn6752->xvclk)) {
		ret = clk_set_rate(rn6752->xvclk, RN6752_XVCLK_CLOCK);
		if (ret < 0)
			dev_warn(dev, "Failed to set xvclk rate %d MHz\n", RN6752_XVCLK_CLOCK);

		ret = clk_get_rate(rn6752->xvclk);
		if (ret != RN6752_XVCLK_CLOCK)
			dev_warn(dev, "xvclk mismatched\n");
		
		/* 準備並啟用 xvclk */
		ret = clk_prepare_enable(rn6752->xvclk);
		if (ret < 0)
		{
			dev_err(dev, "Failed to enable xvclk\n");
			return -EINVAL;
		}
			
	}

	rn6752->power_on = true;
	return 0;
}

/**
* @brief 關閉攝像頭電源管理
* @param rn6752 攝像頭結構體
* @return 返回攝像頭電源設定結果
*/
static void __rn6752_power_off(struct rn6752 *rn6752)
{
	dev_info(&rn6752->client->dev, "%s(%d)\n", __func__, __LINE__);
	if (!IS_ERR(rn6752->xvclk))
		clk_disable_unprepare(rn6752->xvclk);
	if (!IS_ERR(rn6752->supplies))
		regulator_bulk_disable(RN6752_NUM_SUPPLIES, rn6752->supplies);
	if (!IS_ERR(rn6752->pwdn_gpio))
		gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1);
	if (!IS_ERR(rn6752->reset_gpio))
		gpiod_set_value_cansleep(rn6752->reset_gpio, 0);

	rn6752->power_on = false;
}

/**
 * @brief 從 I2C 通道中讀取一個位元組的資料
 * @param client I2C 結構體指標
 * @param reg 暫存器地址
 * @param val 讀取的資料
*/
static int rn6752_read(struct i2c_client *client, u8 reg, u8 *val)
{
	int ret = 0;
	struct i2c_msg msg[2];

	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].buf = &reg;
	msg[0].len = 1;

	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].buf = val;
	msg[1].len = 1;

	ret = i2c_transfer(client->adapter, msg, 2);
	if (ret != 2) {
		dev_err(&client->dev, "rn6752 read reg:0x%x failed !\n", reg);
		return -1;
	}

	return 0;
}

/**
* @brief 獲取攝像頭感測器的預設格式
* @param rn6752 攝像頭感測器裝置
* @param format 影片幀格式
*/
static void rn6752_get_default_format(struct rn6752 *rn6752,
				      struct v4l2_mbus_framefmt *format)
{
	format->width = rn6752->framesize_cfg[0].width;         /* 設定預設寬度 */
	format->height = rn6752->framesize_cfg[0].height;       /* 設定預設高度 */
	format->colorspace = V4L2_COLORSPACE_SRGB;              /* 設定預設色彩空間為標準的 sRGB 色彩空間 */
	format->code = rn6752_formats[0].code;                  /* 設定預設編碼格式 */
	format->field = V4L2_FIELD_NONE;                        /* 設定預設場模式 */
}

/**
* @brief 獲取攝像頭的影像格式
* @param sd v4l2_subdev 結構體指標
* @param cfg v4l2_subdev_pad_config 結構體指標
* @param fmt v4l2_subdev_format 結構體指標
*/
static int rn6752_get_fmt(struct v4l2_subdev *sd,
			  struct v4l2_subdev_pad_config *cfg,
			  struct v4l2_subdev_format *fmt)
{
    printk(KERN_INFO "rn6752_get_fmt................................................\n");
    if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
		fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
	} else {
		fmt->format.width = 1280;
		fmt->format.height = 720;
        fmt->format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
		fmt->format.field = V4L2_FIELD_NONE;
		fmt->reserved[0] = 10;
	}
    return 0;
}

/* 定義V4L2子裝置的內部操作函式 */
static const struct v4l2_subdev_internal_ops rn6752_subdev_internal_ops = {
	// .open = rn6752_open,
};


/* 結構體定義了控制器操作函式的回撥函式 */
static const struct v4l2_ctrl_ops rn6752_ctrl_ops = {
	// .s_ctrl = rn6752_s_ctrl,
};


static const struct v4l2_subdev_core_ops rn6752_subdev_core_ops = {
// 	.ioctl = rn6752_ioctl,                                  /* 用於處理V4L2控制命令 */
// #ifdef CONFIG_COMPAT
// 	.compat_ioctl32 = rn6752_compat_ioctl32,                /* 在啟用32位相容模式時,用於處理32位相容的V4L2控制命令 */
// #endif
// 	.s_power = rn6752_power,                                /* 用於控制子裝置的電源狀態 */
};

static const struct v4l2_subdev_video_ops rn6752_subdev_video_ops = {
	// .s_stream = rn6752_s_stream,                    /* 用於啟動或停止影片流的函式 */
	// .g_mbus_config = rn6752_g_mbus_config,          /* 用於獲取當前媒體匯流排配置的函式 */
	// .g_frame_interval = rn6752_g_frame_interval,    /* 用於獲取當前幀間隔的函式 */
	// .s_frame_interval = rn6752_s_frame_interval,    /* 用於設定幀間隔的函式 */
};

static const struct v4l2_subdev_pad_ops rn6752_subdev_pad_ops = {
	// .enum_mbus_code = rn6752_enum_mbus_code,            /* 用於列舉所有支援的媒體匯流排編碼和格式 */
	// .enum_frame_size = rn6752_enum_frame_sizes,         /* 用於列舉所有支援的影像尺寸 */
	// .enum_frame_interval = rn6752_enum_frame_interval,  /* 用於列舉所有支援的幀率和幀間隔 */
	.get_fmt = rn6752_get_fmt,                          /* 用於獲取當前埠的影像格式 */
	// .set_fmt = rn6752_set_fmt,                          /* 用於設定當前埠的影像格式 */
};


static const struct v4l2_subdev_ops rn6752_subdev_ops = {
	.core  = &rn6752_subdev_core_ops,       /* 定義了V4L2子裝置核心操作的函式指標。包括日誌記錄、事件訂閱和取消訂閱、控制命令等 */
	.video = &rn6752_subdev_video_ops,      /* 定義了V4L2子裝置影片操作的函式指標。包括流開關、格式和幀間隔等引數的獲取和設定 */
	.pad   = &rn6752_subdev_pad_ops,        /* 定義了V4L2子裝置埠操作的函式指標。包括資料編解碼格式、幀尺寸和幀間隔等相關引數的獲取和設定 */
};




/**
* @brief 用於解析裝置樹配置的資訊和引腳等
* @param rn6752 攝像頭結構體
* @return 返回執行結果
*/
static int rn6752_device_tree_info(struct rn6752 *rn6752)
{
	struct device *dev = &rn6752->client->dev;
	struct device_node *node = dev->of_node;        /* 裝置樹節點 */
	int ret;
    unsigned int i;

	/* 獲取裝置樹中 “rockchip,camera-module-index” 節點的資訊,用於攝像頭模組索引 */
	ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
				   &rn6752->module_index);
	/* 獲取裝置樹中 “rockchip,camera-module-facing” 節點的資訊,用於攝像頭面向的方向 */
	ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
				       &rn6752->module_facing);
	/* 獲取裝置樹中 “rockchip,camera-module-name” 節點的資訊,用於攝像頭的名稱 */
	ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
				       &rn6752->module_name);
	/* 獲取裝置樹中 “rockchip,camera-module-lens-name” 節點的資訊,用於攝像頭的鏡頭名稱 */
	ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
				       &rn6752->len_name);
	if (ret) {
		dev_err(dev, "could not get module information!\n");
		return -EINVAL;
	}

	/* 獲取裝置 PWDN 和復位的引腳控制程式碼 */
	rn6752->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);
	if (IS_ERR(rn6752->pwdn_gpio))
        dev_warn(dev, "Failed to get pwdn-gpios, maybe no use\n");
	rn6752->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
	if (IS_ERR(rn6752->reset_gpio))
        dev_warn(dev, "Failed to get reset-gpios, maybe no use\n");
	
	/* 獲取攝像頭的時鐘資源 */
	rn6752->xvclk = devm_clk_get(dev, "xvclk");
	if (IS_ERR(rn6752->xvclk)) {
		dev_err(dev, "Failed to get xvclk\n");
		return -EINVAL;
	}

    for (i = 0; i < RN6752_NUM_SUPPLIES; i++)
		rn6752->supplies[i].supply = rn6752_supply_names[i];

    /* 獲取攝像頭裝置的電源管理器控制程式碼 */
	ret = devm_regulator_bulk_get(dev, RN6752_NUM_SUPPLIES, rn6752->supplies);
    if (ret)
    {
        dev_err(dev, "Failed to get power regulators\n");
		return -EINVAL;
    }
    
	return 0;
}

/**
 * @brief 初始化 rn6752 攝像頭的硬體控制器函式,透過命令
 * “v4l2-ctl -d /dev/v4l-subdev5 --list-ctrls” 可以檢視
 * @param rn6752 攝像頭結構體指標
*/
static int rn6752_initialize_controls(struct rn6752 *rn6752)
{
	struct v4l2_ctrl_handler *handler;
	int ret;

	handler = &rn6752->ctrls;

	/* 初始化 rn6752->ctrls,該函式需要兩個引數:控制處理器物件和控制器的數量 */
	ret = v4l2_ctrl_handler_init(handler, 3);
	if (ret)
		return ret;
	
	handler->lock = &rn6752->lock;

	/* 建立一個 V4L2 控制器物件,並將其與 rn6752->ctrls 關聯起來 */
	rn6752->link_frequency = v4l2_ctrl_new_std(handler, &rn6752_ctrl_ops,
					  V4L2_CID_PIXEL_RATE, 0, RN6752_PIXEL_RATE, 1, RN6752_PIXEL_RATE);

	/* 建立一個帶有選項選單的 V4L2 控制器物件 */
	v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
				ARRAY_SIZE(link_freq_menu_items) - 1, 0, link_freq_menu_items);

	/* 建立另一個帶有選項選單的 V4L2 控制器物件 */
	v4l2_ctrl_new_std_menu_items(handler, &rn6752_ctrl_ops, V4L2_CID_TEST_PATTERN,
					ARRAY_SIZE(rn6752_test_pattern_menu) - 1, 0, 0, rn6752_test_pattern_menu);
	rn6752->subdev.ctrl_handler = &rn6752->ctrls;

	if (handler->error) {
		ret = handler->error;
		dev_err(&rn6752->client->dev, "Failed to init controls(%d)\n", ret);
		goto err_free_handler;
	}

	return 0;

err_free_handler:
	v4l2_ctrl_handler_free(handler);

	return ret;
}

/**
 * @brief 根據讀取到的晶片 ID 和版本號計算出一個 id 值,並與預定義的 RN6752_ID 進行比較
 * @param rn6752 攝像頭裝置
*/
static int rn6752_check_sersor_id(struct rn6752 *rn6752)
{
	struct i2c_client *client = rn6752->client;
	u8 pid, ver;
	int ret;

	dev_dbg(&client->dev, "%s:\n", __func__);

	/* Check sensor revision */
	ret = rn6752_read(client, REG_SC_CHIP_ID_H, &pid);          /* 讀取晶片 ID 的高位元組 */
	if (!ret)
		ret = rn6752_read(client, REG_SC_CHIP_ID_L, &ver);      /* 讀取晶片 ID 的低位元組 */

	if (!ret) {
		unsigned short id;

		id = SENSOR_ID(pid, ver);
		if (id != RN6752_ID) {
			ret = -1;
			dev_err(&client->dev, "Sensor detection failed (%04X, %d)\n", id, ret);
		} else {
			dev_info(&client->dev, "Found %04X sensor\n", id);
		}
	}

	return ret;
}


/**
* @brief 系統檢測到與該驅動程式匹配的 I2C 裝置時
* @param client 指向 I2C 客戶端結構體的指標。該結構體包含了有關 I2C 裝置的資訊,如裝置地址、匯流排資訊等
* @param id 指向 I2C 裝置 ID 的指標。這個引數用於在多個相同型別的裝置中進行區分和匹配
* @return 返回初始化結構
*/
static int rn6752_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct device *dev = &client->dev;
    struct v4l2_subdev *sd;
    struct rn6752 *rn6752;
    char facing[2];
	int ret;

	/* 列印驅動的版本資訊的函式,其作用相當於 printk() 函式 */
	dev_info(dev, "driver version: %02x.%02x.%02x", DRIVER_VERSION >> 16,
		(DRIVER_VERSION & 0xff00) >> 8,	DRIVER_VERSION & 0x00ff);

    /* 為裝置分配記憶體,並將記憶體與裝置進行關聯。在驅動程式退出時,記憶體會自動被釋放。被稱為“裝置記憶體管理” */
	rn6752 = devm_kzalloc(dev, sizeof(*rn6752), GFP_KERNEL);
	if (!rn6752)
	{
		dev_err(dev, "Memory control request failed\n");
		return -ENOMEM;
	}

    rn6752->client = client;
	sd = &rn6752->subdev;

    /* 獲取裝置樹資訊 */
	ret = rn6752_device_tree_info(rn6752);
	if (ret != 0)
		return -EINVAL;

    /* rn6752_mipi_framesizes 是 rn6752 mipi 通訊支援的所有幀格式 */
    rn6752->framesize_cfg = rn6752_mipi_framesizes;
	rn6752->cfg_num = ARRAY_SIZE(rn6752_mipi_framesizes);
	/* 獲取攝像頭感測器支援的影像幀格式 */
	rn6752_get_default_format(rn6752, &rn6752->format);
	rn6752->frame_size = &rn6752->framesize_cfg[0];             /* 設定幀大小 */
	rn6752->format.width = rn6752->framesize_cfg[0].width;      /* 設定寬度 */
	rn6752->format.height = rn6752->framesize_cfg[0].height;    /* 設定高度 */
	rn6752->fps = DIV_ROUND_CLOSEST(rn6752->framesize_cfg[0].max_fps.denominator,
	rn6752->framesize_cfg[0].max_fps.numerator);        		/* 設定最大幀速率 */

    mutex_init(&rn6752->lock);

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
    /* 透過v4l2框架註冊一個V4L2子裝置,並初始化該子裝置的操作函式和內部操作函式指標 */
	v4l2_i2c_subdev_init(sd, client, &rn6752_subdev_ops);
    ret = rn6752_initialize_controls(rn6752);
	if (ret)
	{
		dev_info(dev, "V4l2 control menu initialization failed");
		goto err_destroy_mutex;
	}

    /* 關聯子裝置的內部操作函式,用於開啟攝像頭操作 */
	sd->internal_ops = &rn6752_subdev_internal_ops;
    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |           /* 裝置有對應的裝置節點檔案,可以透過該檔案進行訪問 */
		     V4L2_SUBDEV_FL_HAS_EVENTS;                 /* 裝置具有事件機制,可以作為事件源供其他驅動程式使用 */
#endif

    /* 開啟裝置的電源 */
	ret = __rn6752_power_on(rn6752);
	if (ret)
		goto err_free_handler;

    /* 透過讀取 RN6752 的產品 ID 判斷裝置是否存在 */
	ret = rn6752_check_sersor_id(rn6752);
	if (ret)
		goto err_power_off;

#if defined(CONFIG_MEDIA_CONTROLLER)
    rn6752->pad.flags = MEDIA_PAD_FL_SOURCE;            /* 表明該子裝置是媒體管道中的源裝置,即資料的起始點 */
	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;       /* 表示該實體是一個相機感測器 */
	ret = media_entity_pads_init(&sd->entity, 1, &rn6752->pad);     /* 初始化 sd->entity 媒體實體的 pads(埠)屬性 */
	if (ret < 0) {
		dev_err(dev, "Media pad port initialization failed\n");
		goto err_power_off;
	}
#endif

    /* 根據裝置樹提供的資訊,判斷攝像頭的方向 */
    memset(facing, 0, sizeof(facing));
	if (strcmp(rn6752->module_facing, "back") == 0)
		facing[0] = 'b';
	else
		facing[0] = 'f';

    /* 名稱格式如 m00_f_rn6752 1-002c:bus */
	snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s", 
			rn6752->module_index, facing, DRIVER_NAME, dev_name(sd->dev));
	/* 將感測器子裝置新增到V4L2非同步子系統中,以便能夠與其他V4L2元件進行互動。 */
	/* 它會自動設定子裝置的相關回撥函式,並與非同步框架進行適當的關聯,以管理感測器的採集和控制操作 */
	ret = v4l2_async_register_subdev_sensor_common(sd);
	if (ret)
	{
		dev_err(dev, "Failed to add sensor to V4L2 asynchronous subsystem\n");
		goto err_clean_entity;
	}

    /* 攝像頭感測器註冊成功,並列印日誌資訊 */
	dev_info(dev, "%s sensor driver registered !!\n", sd->name);

    /* 完成初始化後,使 rn6752 進入睡眠模式 */
	rn6752->power_on = false;
    if (!IS_ERR(rn6752->pwdn_gpio))
				gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1);

	return 0;

err_clean_entity:
#if defined(CONFIG_MEDIA_CONTROLLER)
	media_entity_cleanup(&sd->entity);
#endif
err_power_off:
	__rn6752_power_off(rn6752);
err_free_handler:
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
	v4l2_ctrl_handler_free(&rn6752->ctrls);
#endif
err_destroy_mutex:
	mutex_destroy(&rn6752->lock);


	return ret;
}

/**
* @brief 當裝置驅動被刪除釋放時,執行此函式
* @param client 指向 I2C 客戶端結構體的指標。該結構體包含了有關 I2C 裝置的資訊,如裝置地址、匯流排資訊等
* @return 返回操作結果
*/
static int rn6752_remove(struct i2c_client *client)
{
    struct v4l2_subdev *sd = i2c_get_clientdata(client);
	struct rn6752 *rn6752 = to_rn6752(sd);

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
	/* 釋放V4L2控制器處理器所佔用的資源 */
	v4l2_ctrl_handler_free(&rn6752->ctrls);
#endif
	/* 登出一個V4L2子裝置 */
	v4l2_async_unregister_subdev(sd);
#if defined(CONFIG_MEDIA_CONTROLLER)
	media_entity_cleanup(&sd->entity);
#endif
	mutex_destroy(&rn6752->lock);

	__rn6752_power_off(rn6752);
	return 0;
}

/* 裝置樹節點匹配表格,與裝置樹中的節點描述資訊一樣時,匹配成功 */
#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id rn6752_of_match[] = {
	{ .compatible = "richnex,rn6752v1" },
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rn6752_of_match);
#endif

/* 裝置 ID 表格,與裝置名稱一樣時匹配成功,主要用於低版本linux核心的匹配方式 */
static const struct i2c_device_id rn6752_match_id[] = {
	{ "richnex,rn6752v1", 0 },
	{ /* sentinel */ },
};

/* 描述和註冊一個 I2C 裝置驅動程式 */
static struct i2c_driver rn6752_i2c_driver = {
	.driver = {                                             /* 驅動程式資訊的子結構體 */
		.name	= DRIVER_NAME,                              /* 設定驅動程式的名稱 */
		.of_match_table = of_match_ptr(rn6752_of_match),    /* 用於匹配裝置樹節點的表格 */
	},
	.probe		= rn6752_probe,                 /* I2C 裝置被檢測到時進行裝置初始化和處理 */
	.remove		= rn6752_remove,                /* I2C 裝置從系統中移除時進行清理和資源釋放 */
	.id_table	= rn6752_match_id,              /* I2C 裝置 ID 表格,平臺裝置匹配方式之一,用裝置和驅動的名稱進行匹配 */
};

static int __init sensor_mod_init(void)
{
	/* 註冊 I2C 驅動程式 */
	return i2c_add_driver(&rn6752_i2c_driver);
}

static void __exit sensor_mod_exit(void)
{
	/* 登出 I2C 驅動程式 */
	i2c_del_driver(&rn6752_i2c_driver);
}

device_initcall_sync(sensor_mod_init);      /* 註冊一個裝置初始化函式 */
module_exit(sensor_mod_exit);               /* 登出一個裝置初始化函式 */

MODULE_AUTHOR("jiaozhu <cn_jiaozhu@qq.com>");
MODULE_DESCRIPTION("RN6752 CMOS Image Sensor driver");

參考資料

  1. MIPI掃盲——D-PHY介紹:https://zhuanlan.zhihu.com/p/638769112?utm_id=0

相關文章