一、概述

在编写 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 文件,内容如下

    1. /* 驱动名称 */
    2. #define DRIVER_NAME "rn6752"
    3. /* 驱动版本信息 */
    4. #define DRIVER_VERSION KERNEL_VERSION(0, 0x00, 0x01)
    5. /**
    6. * @brief 系统检测到与该驱动程序匹配的 I2C 设备时
    7. * @param client 指向 I2C 客户端结构体的指针。该结构体包含了有关 I2C 设备的信息,如设备地址、总线信息等
    8. * @param id 指向 I2C 设备 ID 的指针。这个参数用于在多个相同类型的设备中进行区分和匹配
    9. * @return 返回初始化结构
    10. */
    11. static int rn6752_probe(struct i2c_client *client, const struct i2c_device_id *id)
    12. {
    13. struct device *dev = &client->dev;
    14. /* 打印驱动的版本信息的函数,其作用相当于 printk() 函数 */
    15. dev_info(dev, "driver version: %02x.%02x.%02x", DRIVER_VERSION >> 16,
    16. (DRIVER_VERSION & 0xff00) >> 8, DRIVER_VERSION & 0x00ff);
    17. return 0;
    18. }
    19. /**
    20. * @brief 当设备驱动被删除释放时,执行此函数
    21. * @param client 指向 I2C 客户端结构体的指针。该结构体包含了有关 I2C 设备的信息,如设备地址、总线信息等
    22. * @return 返回操作结果
    23. */
    24. static int rn6752_remove(struct i2c_client *client)
    25. {
    26. return 0;
    27. }
    28. /* 设备树节点匹配表格,与设备树中的节点描述信息一样时,匹配成功 */
    29. #if IS_ENABLED(CONFIG_OF)
    30. static const struct of_device_id rn6752_of_match[] = {
    31. { .compatible = "richnex,rn6752v1" },
    32. { /* sentinel */ },
    33. };
    34. MODULE_DEVICE_TABLE(of, rn6752_of_match);
    35. #endif
    36. /* 设备 ID 表格,与设备名称一样时匹配成功,主要用于低版本linux内核的匹配方式 */
    37. static const struct i2c_device_id rn6752_match_id[] = {
    38. { "richnex,rn6752v1", 0 },
    39. { /* sentinel */ },
    40. };
    41. /* 描述和注册一个 I2C 设备驱动程序 */
    42. static struct i2c_driver rn6752_i2c_driver = {
    43. .driver = { /* 驱动程序信息的子结构体 */
    44. .name = DRIVER_NAME, /* 设置驱动程序的名称 */
    45. .of_match_table = of_match_ptr(rn6752_of_match), /* 用于匹配设备树节点的表格 */
    46. },
    47. .probe = rn6752_probe, /* I2C 设备被检测到时进行设备初始化和处理 */
    48. .remove = rn6752_remove, /* I2C 设备从系统中移除时进行清理和资源释放 */
    49. .id_table = rn6752_match_id, /* I2C 设备 ID 表格,平台设备匹配方式之一,用设备和驱动的名称进行匹配 */
    50. };
    51. static int __init sensor_mod_init(void)
    52. {
    53. /* 注册 I2C 驱动程序 */
    54. return i2c_add_driver(&rn6752_i2c_driver);
    55. }
    56. static void __exit sensor_mod_exit(void)
    57. {
    58. /* 注销 I2C 驱动程序 */
    59. i2c_del_driver(&rn6752_i2c_driver);
    60. }
    61. device_initcall_sync(sensor_mod_init); /* 注册一个设备初始化函数 */
    62. module_exit(sensor_mod_exit); /* 注销一个设备初始化函数 */
    63. MODULE_AUTHOR("jiaozhu <cn_jiaozhu@qq.com>");
    64. 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 文件中添加配置信息,内容如下

    1. config VIDEO_RN6752V1
    2. tristate "Richnex RN6752V1 sensor support"
    3. depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
    4. depends on MEDIA_CAMERA_SUPPORT
    5. help
    6. This is a Video4Linux2 sensor driver for the Sony
    7. RN6752V1 camera.
    8. To compile this driver as a module, choose M here: the
    9. 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 解码芯片的设备信息,内容如下

    1. rn6752: rn6752@2c {
    2. compatible = "richnex,rn6752v1";
    3. reg = <0x2c>;
    4. clocks = <&cru CLK_MIPICSI_OUT>;
    5. clock-names = "xvclk";
    6. power-domains = <&power RV1126_PD_VI>;
    7. pinctrl-names = "rockchip,camera_default";
    8. pinctrl-0 = <&mipicsi_clk0>;
    9. avdd-supply = <&vcc_avdd>;
    10. dovdd-supply = <&vcc_dovdd>;
    11. dvdd-supply = <&vcc_dvdd>;
    12. pwdn-gpios = <&gpio1 RK_PD4 GPIO_ACTIVE_HIGH>;
    13. reset-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>;
    14. rockchip,camera-hdr-mode = <0>;
    15. rockchip,camera-module-index = <0>;
    16. rockchip,camera-module-facing = "front";
    17. rockchip,camera-module-name = "abcd";
    18. rockchip,camera-module-lens-name = "a-bc-d";
    19. port {
    20. ucam_out0: endpoint {
    21. remote-endpoint = <&mipi_in_ucam0>;
    22. data-lanes = <1 2 3 4>;
    23. };
    24. };
    25. };
  6. 完成以上内容后,准备工作基本完成了,编译并重写烧写内核程序后,会在启动日志中打印版本信息,如下图所示

四、probe 函数实现

  1. 内存申请

    首先需要申请一块内存,用于存放 RN6752 的结构体

    1. /* 为设备分配内存,并将内存与设备进行关联。在驱动程序退出时,内存会自动被释放。被称为“设备内存管理” */
    2. rn6752 = devm_kzalloc(dev, sizeof(*rn6752), GFP_KERNEL);
    3. if (!rn6752)
    4. {
    5. dev_err(dev, "Memory control request failed\n");
    6. return -ENOMEM;
    7. }
  2. 获取设备树配置的信息

    1. /* 获取设备树信息 */
    2. ret = rn6752_device_tree_info(rn6752);
    3. if (ret != 0)
    4. return -EINVAL;
  3. 保留驱动的所有帧格式,方便在其他函数中使用

    1. /* rn6752_mipi_framesizes 是 rn6752 mipi 通信支持的所有帧格式 */
    2. rn6752->framesize_cfg = rn6752_mipi_framesizes;
    3. rn6752->cfg_num = ARRAY_SIZE(rn6752_mipi_framesizes);
    4. /* 获取摄像头传感器支持的图像帧格式 */
    5. rn6752_get_default_format(rn6752, &rn6752->format);
    6. rn6752->frame_size = &rn6752->framesize_cfg[0]; /* 设置帧大小 */
    7. rn6752->format.width = rn6752->framesize_cfg[0].width; /* 设置宽度 */
    8. rn6752->format.height = rn6752->framesize_cfg[0].height; /* 设置高度 */
    9. rn6752->fps = DIV_ROUND_CLOSEST(rn6752->framesize_cfg[0].max_fps.denominator,
    10. rn6752->framesize_cfg[0].max_fps.numerator); /* 设置最大帧速率 */
  4. 初始化互斥锁

    1. mutex_init(&rn6752->lock);
  5. 注册一个V4L2子设备

    1. v4l2_i2c_subdev_init(sd, client, &rn6752_subdev_ops);
  6. 绑定硬件操作函数

    1. ret = rn6752_initialize_controls(rn6752);
    2. if (ret)
    3. {
    4. dev_info(dev, "V4l2 control menu initialization failed");
    5. goto err_destroy_mutex;
    6. }

    注意: 此函数的作用是绑定硬件部分的控制功能,也就是或可以通过相应的设备节点更改设备的引荐参数,比如亮度、对比度、饱和度、色调等。

    可以通过命令 v4l2-ctl -d /dev/v4l-subdevX --list-ctrls 查看,如下图所示:

  7. 关联 V4l2 子设备内部操作函数

    1. /* 关联子设备的内部操作函数,用于打开摄像头操作 */
    2. sd->internal_ops = &rn6752_subdev_internal_ops;
    3. sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | /* 设备有对应的设备节点文件,可以通过该文件进行访问 */
    4. V4L2_SUBDEV_FL_HAS_EVENTS; /* 设备具有事件机制,可以作为事件源供其他驱动程序使用 */
  8. 打开电源

    mipi 设备一般都有复位引脚和休眠引脚,工作是需要对相应的引脚进行操作

    1. /* 打开设备的电源 */
    2. ret = __rn6752_power_on(rn6752);
    3. if (ret)
    4. goto err_free_handler;
  9. 读取设备产品号

    通过读取 mipi 设备的产品 id 进行对比,判断初始化时,设备是否连接,最终确定时候正常加载驱动

    1. /* 通过读取 RN6752 的产品 ID 判断设备是否存在 */
    2. ret = rn6752_check_sersor_id(rn6752);
    3. if (ret)
    4. goto err_power_off;
  10. 创建 media 设备

    1. rn6752->pad.flags = MEDIA_PAD_FL_SOURCE; /* 表明该子设备是媒体管道中的源设备,即数据的起始点 */
    2. sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; /* 表示该实体是一个相机传感器 */
    3. ret = media_entity_pads_init(&sd->entity, 1, &rn6752->pad); /* 初始化 sd->entity 媒体实体的 pads(端口)属性 */
    4. if (ret < 0) {
    5. dev_err(dev, "Media pad port initialization failed\n");
    6. goto err_power_off;
    7. }
  11. 将传感器子设备添加到V4L2异步子系统中

    这里需要注意一下,在执行此函数之前,在系统中是不会生成 media 设备节点的

    1. /* 根据设备树提供的信息,判断摄像头的方向 */
    2. memset(facing, 0, sizeof(facing));
    3. if (strcmp(rn6752->module_facing, "back") == 0)
    4. facing[0] = 'b';
    5. else
    6. facing[0] = 'f';
    7. /* 名称格式如 m00_f_rn6752 1-002c:bus */
    8. snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
    9. rn6752->module_index, facing, DRIVER_NAME, dev_name(sd->dev));
    10. /* 将传感器子设备添加到V4L2异步子系统中,以便能够与其他V4L2组件进行交互。 */
    11. /* 它会自动设置子设备的相关回调函数,并与异步框架进行适当的关联,以管理传感器的采集和控制操作 */
    12. ret = v4l2_async_register_subdev_sensor_common(sd);
    13. if (ret)
    14. {
    15. dev_err(dev, "Failed to add sensor to V4L2 asynchronous subsystem\n");
    16. goto err_clean_entity;
    17. }

    注意: 在 RV1126 开发板中,创建 media 设备时,需要注意 mipi 连接的通道,连接 mipi

    csi0 时,需要获取设备的数据格式,否则会出现内存地址错误。解决此错误的方法是在 rn6752_get_fmt 函数中默认一个类型值即可,如下图所示:

  12. 进入休眠模式

    完成初始化后,使传感器进入休眠模式

    1. /* 完成初始化后,使 rn6752 进入睡眠模式 */
    2. rn6752->power_on = false;
    3. if (!IS_ERR(rn6752->pwdn_gpio))
    4. gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1);

到此整个驱动程序就算完成了,由于内容较多,所以分成了两篇笔记,相关的控制函数的实现请看下一篇笔记,下面是 MIPI 驱动框架的程序

五、程序源码

rn6752.c

  1. #include <linux/clk.h>
  2. #include <linux/delay.h>
  3. #include <linux/err.h>
  4. #include <linux/gpio/consumer.h>
  5. #include <linux/init.h>
  6. #include <linux/interrupt.h>
  7. #include <linux/io.h>
  8. #include <linux/i2c.h>
  9. #include <linux/kernel.h>
  10. #include <linux/media.h>
  11. #include <linux/module.h>
  12. #include <linux/of.h>
  13. #include <linux/of_graph.h>
  14. #include <linux/regulator/consumer.h>
  15. #include <linux/slab.h>
  16. #include <linux/uaccess.h>
  17. #include <linux/videodev2.h>
  18. #include <linux/version.h>
  19. #include <linux/rk-camera-module.h>
  20. #include <media/media-entity.h>
  21. #include <media/v4l2-common.h>
  22. #include <media/v4l2-ctrls.h>
  23. #include <media/v4l2-device.h>
  24. #include <media/v4l2-event.h>
  25. #include <media/v4l2-fwnode.h>
  26. #include <media/v4l2-image-sizes.h>
  27. #include <media/v4l2-mediabus.h>
  28. #include <media/v4l2-subdev.h>
  29. /* 驱动名称 */
  30. #define DRIVER_NAME "rn6752"
  31. /* 驱动版本信息 */
  32. #define DRIVER_VERSION KERNEL_VERSION(0, 0x00, 0x01)
  33. #define RN6752_XVCLK_CLOCK 37125000
  34. #define RN6752_PIXEL_RATE (120 * 1000 * 1000)
  35. /* RN6752 产品 ID 的寄存器地址及默认值,用于判断设备是否存在 */
  36. #define REG_SC_CHIP_ID_H 0xFE
  37. #define REG_SC_CHIP_ID_L 0xFD
  38. #define SENSOR_ID(_msb, _lsb) ((_msb) << 8 | (_lsb))
  39. #define RN6752_ID 0x2601
  40. #define REG_NULL 0xFFFF /* Array end token */
  41. struct rn6752_pixfmt {
  42. u32 code;
  43. /* Output format Register Value (REG_FORMAT_CTRL00) */
  44. struct sensor_register *format_ctrl_regs;
  45. };
  46. struct sensor_register {
  47. u16 addr;
  48. u8 value;
  49. };
  50. struct rn6752_framesize {
  51. u16 width;
  52. u16 height;
  53. struct v4l2_fract max_fps;
  54. u16 max_exp_lines;
  55. const struct sensor_register *regs;
  56. };
  57. static const char * const rn6752_supply_names[] = {
  58. "dovdd", /* Digital I/O power */
  59. "avdd", /* Analog power */
  60. "dvdd", /* Digital core power */
  61. };
  62. #define RN6752_NUM_SUPPLIES ARRAY_SIZE(rn6752_supply_names)
  63. struct rn6752 {
  64. struct i2c_client *client; /* I2C 客户端结构体的指针,表示 RN6752 摄像头所连接的 I2C 设备 */
  65. struct v4l2_subdev subdev; /* V4L2 子设备结构体,表示 RN6752 摄像头的 V4L2 子设备 */
  66. struct v4l2_ctrl_handler ctrls; /* V4L2 控制器句柄,用于管理 RN6752 摄像头的各种控制器 */
  67. struct v4l2_ctrl *link_frequency; /* V4L2 链路频率控制器,用于设置和获取 RN6752 摄像头的链路频率 */
  68. struct v4l2_fwnode_endpoint bus_cfg; /* RN6752 摄像头的总线配置,包括数据线数量、数据线极性等信息 */
  69. struct v4l2_mbus_framefmt format; /* V4L2 子设备帧格式结构体,表示 RN6752 摄像头支持的图像帧格式 */
  70. struct media_pad pad; /* 媒体子系统中的媒体端口结构体,表示与 RN6752 摄像头相关联的媒体端口 */
  71. struct gpio_desc *pwdn_gpio; /* RN6752 摄像头的 pwdn GPIO 引脚 */
  72. struct gpio_desc *reset_gpio; /* RN6752 摄像头的复位 GPIO 引脚 */
  73. struct regulator_bulk_data supplies[RN6752_NUM_SUPPLIES]; /* RN6752 摄像头使用的电源资源 */
  74. struct mutex lock; /* 互斥锁,用于保护并发访问摄像头设备 */
  75. unsigned int fps; /* RN6752 摄像头的帧率 */
  76. const struct rn6752_framesize *frame_size; /* RN6752 摄像头支持的帧大小列表的指针 */
  77. const struct rn6752_framesize *framesize_cfg; /* 当前 RN6752 摄像头所选择的帧大小的指针 */
  78. unsigned int cfg_num; /* RN6752 摄像头支持的帧大小的数量 */
  79. int streaming; /* RN6752 摄像头是否正在流式传输 */
  80. bool power_on; /* RN6752 摄像头的电源状态 */
  81. u32 module_index; /* RN6752 摄像头的模块索引 */
  82. const char *module_facing; /* RN6752 摄像头面向的方向 */
  83. const char *module_name; /* RN6752 摄像头的名称 */
  84. const char *len_name; /* RN6752 摄像头的镜头名称 */
  85. struct clk *xvclk; /* RN6752 摄像头使用的时钟资源 */
  86. unsigned int xvclk_frequency; /* RN6752 摄像头的 xvclk 频率 */
  87. };
  88. static const struct rn6752_framesize rn6752_mipi_framesizes[] = {
  89. };
  90. static const s64 link_freq_menu_items[] = {
  91. 594000000,
  92. };
  93. static const struct rn6752_pixfmt rn6752_formats[] = {
  94. {
  95. .code = MEDIA_BUS_FMT_UYVY8_2X8,
  96. }
  97. };
  98. static const char * const rn6752_test_pattern_menu[] = {
  99. "Disabled",
  100. "Vertical Color Bars",
  101. };
  102. /**
  103. * @brief 从结构体中的成员指针获取包含该成员的结构体指针
  104. */
  105. static inline struct rn6752 *to_rn6752(struct v4l2_subdev *sd)
  106. {
  107. return container_of(sd, struct rn6752, subdev);
  108. }
  109. /**
  110. * @brief 打开摄像头电源管理
  111. * @param rn6752 摄像头结构体
  112. * @return 返回摄像头电源设置结果
  113. */
  114. static int __rn6752_power_on(struct rn6752 *rn6752)
  115. {
  116. int ret;
  117. struct device *dev = &rn6752->client->dev;
  118. /* 打印一条调试信息 */
  119. dev_dbg(dev, "%s(%d)\n", __func__, __LINE__);
  120. /* 启用所有的供电器 */
  121. if (!IS_ERR(rn6752->supplies)) {
  122. ret = regulator_bulk_enable(RN6752_NUM_SUPPLIES, rn6752->supplies);
  123. if (ret < 0)
  124. dev_info(dev, "Failed to enable regulators\n");
  125. usleep_range(20000, 50000);
  126. }
  127. /* 首先将其引脚置为低电平(0),延迟一段时间,然后再将其引脚置为高电平(1),再次延迟一段时间 */
  128. if (!IS_ERR(rn6752->reset_gpio)) {
  129. gpiod_set_value_cansleep(rn6752->reset_gpio, 0);
  130. usleep_range(2000, 5000);
  131. gpiod_set_value_cansleep(rn6752->reset_gpio, 1);
  132. usleep_range(2000, 5000);
  133. }
  134. /* 使 RN6752 退出睡眠模式 */
  135. if (!IS_ERR(rn6752->pwdn_gpio)) {
  136. gpiod_set_value_cansleep(rn6752->pwdn_gpio, 0);
  137. usleep_range(2000, 5000);
  138. }
  139. /* 将 xvclk 的频率设置为 RN6752_XVCLK_CLOCK */
  140. if (!IS_ERR(rn6752->xvclk)) {
  141. ret = clk_set_rate(rn6752->xvclk, RN6752_XVCLK_CLOCK);
  142. if (ret < 0)
  143. dev_warn(dev, "Failed to set xvclk rate %d MHz\n", RN6752_XVCLK_CLOCK);
  144. ret = clk_get_rate(rn6752->xvclk);
  145. if (ret != RN6752_XVCLK_CLOCK)
  146. dev_warn(dev, "xvclk mismatched\n");
  147. /* 准备并启用 xvclk */
  148. ret = clk_prepare_enable(rn6752->xvclk);
  149. if (ret < 0)
  150. {
  151. dev_err(dev, "Failed to enable xvclk\n");
  152. return -EINVAL;
  153. }
  154. }
  155. rn6752->power_on = true;
  156. return 0;
  157. }
  158. /**
  159. * @brief 关闭摄像头电源管理
  160. * @param rn6752 摄像头结构体
  161. * @return 返回摄像头电源设置结果
  162. */
  163. static void __rn6752_power_off(struct rn6752 *rn6752)
  164. {
  165. dev_info(&rn6752->client->dev, "%s(%d)\n", __func__, __LINE__);
  166. if (!IS_ERR(rn6752->xvclk))
  167. clk_disable_unprepare(rn6752->xvclk);
  168. if (!IS_ERR(rn6752->supplies))
  169. regulator_bulk_disable(RN6752_NUM_SUPPLIES, rn6752->supplies);
  170. if (!IS_ERR(rn6752->pwdn_gpio))
  171. gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1);
  172. if (!IS_ERR(rn6752->reset_gpio))
  173. gpiod_set_value_cansleep(rn6752->reset_gpio, 0);
  174. rn6752->power_on = false;
  175. }
  176. /**
  177. * @brief 从 I2C 通道中读取一个字节的数据
  178. * @param client I2C 结构体指针
  179. * @param reg 寄存器地址
  180. * @param val 读取的数据
  181. */
  182. static int rn6752_read(struct i2c_client *client, u8 reg, u8 *val)
  183. {
  184. int ret = 0;
  185. struct i2c_msg msg[2];
  186. msg[0].addr = client->addr;
  187. msg[0].flags = 0;
  188. msg[0].buf = &reg;
  189. msg[0].len = 1;
  190. msg[1].addr = client->addr;
  191. msg[1].flags = I2C_M_RD;
  192. msg[1].buf = val;
  193. msg[1].len = 1;
  194. ret = i2c_transfer(client->adapter, msg, 2);
  195. if (ret != 2) {
  196. dev_err(&client->dev, "rn6752 read reg:0x%x failed !\n", reg);
  197. return -1;
  198. }
  199. return 0;
  200. }
  201. /**
  202. * @brief 获取摄像头传感器的默认格式
  203. * @param rn6752 摄像头传感器设备
  204. * @param format 视频帧格式
  205. */
  206. static void rn6752_get_default_format(struct rn6752 *rn6752,
  207. struct v4l2_mbus_framefmt *format)
  208. {
  209. format->width = rn6752->framesize_cfg[0].width; /* 设置默认宽度 */
  210. format->height = rn6752->framesize_cfg[0].height; /* 设置默认高度 */
  211. format->colorspace = V4L2_COLORSPACE_SRGB; /* 设置默认色彩空间为标准的 sRGB 色彩空间 */
  212. format->code = rn6752_formats[0].code; /* 设置默认编码格式 */
  213. format->field = V4L2_FIELD_NONE; /* 设置默认场模式 */
  214. }
  215. /**
  216. * @brief 获取摄像头的图像格式
  217. * @param sd v4l2_subdev 结构体指针
  218. * @param cfg v4l2_subdev_pad_config 结构体指针
  219. * @param fmt v4l2_subdev_format 结构体指针
  220. */
  221. static int rn6752_get_fmt(struct v4l2_subdev *sd,
  222. struct v4l2_subdev_pad_config *cfg,
  223. struct v4l2_subdev_format *fmt)
  224. {
  225. printk(KERN_INFO "rn6752_get_fmt................................................\n");
  226. if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
  227. fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
  228. } else {
  229. fmt->format.width = 1280;
  230. fmt->format.height = 720;
  231. fmt->format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
  232. fmt->format.field = V4L2_FIELD_NONE;
  233. fmt->reserved[0] = 10;
  234. }
  235. return 0;
  236. }
  237. /* 定义V4L2子设备的内部操作函数 */
  238. static const struct v4l2_subdev_internal_ops rn6752_subdev_internal_ops = {
  239. // .open = rn6752_open,
  240. };
  241. /* 结构体定义了控制器操作函数的回调函数 */
  242. static const struct v4l2_ctrl_ops rn6752_ctrl_ops = {
  243. // .s_ctrl = rn6752_s_ctrl,
  244. };
  245. static const struct v4l2_subdev_core_ops rn6752_subdev_core_ops = {
  246. // .ioctl = rn6752_ioctl, /* 用于处理V4L2控制命令 */
  247. // #ifdef CONFIG_COMPAT
  248. // .compat_ioctl32 = rn6752_compat_ioctl32, /* 在启用32位兼容模式时,用于处理32位兼容的V4L2控制命令 */
  249. // #endif
  250. // .s_power = rn6752_power, /* 用于控制子设备的电源状态 */
  251. };
  252. static const struct v4l2_subdev_video_ops rn6752_subdev_video_ops = {
  253. // .s_stream = rn6752_s_stream, /* 用于启动或停止视频流的函数 */
  254. // .g_mbus_config = rn6752_g_mbus_config, /* 用于获取当前媒体总线配置的函数 */
  255. // .g_frame_interval = rn6752_g_frame_interval, /* 用于获取当前帧间隔的函数 */
  256. // .s_frame_interval = rn6752_s_frame_interval, /* 用于设置帧间隔的函数 */
  257. };
  258. static const struct v4l2_subdev_pad_ops rn6752_subdev_pad_ops = {
  259. // .enum_mbus_code = rn6752_enum_mbus_code, /* 用于枚举所有支持的媒体总线编码和格式 */
  260. // .enum_frame_size = rn6752_enum_frame_sizes, /* 用于枚举所有支持的图像尺寸 */
  261. // .enum_frame_interval = rn6752_enum_frame_interval, /* 用于枚举所有支持的帧率和帧间隔 */
  262. .get_fmt = rn6752_get_fmt, /* 用于获取当前端口的图像格式 */
  263. // .set_fmt = rn6752_set_fmt, /* 用于设置当前端口的图像格式 */
  264. };
  265. static const struct v4l2_subdev_ops rn6752_subdev_ops = {
  266. .core = &rn6752_subdev_core_ops, /* 定义了V4L2子设备核心操作的函数指针。包括日志记录、事件订阅和取消订阅、控制命令等 */
  267. .video = &rn6752_subdev_video_ops, /* 定义了V4L2子设备视频操作的函数指针。包括流开关、格式和帧间隔等参数的获取和设置 */
  268. .pad = &rn6752_subdev_pad_ops, /* 定义了V4L2子设备端口操作的函数指针。包括数据编解码格式、帧尺寸和帧间隔等相关参数的获取和设置 */
  269. };
  270. /**
  271. * @brief 用于解析设备树配置的信息和引脚等
  272. * @param rn6752 摄像头结构体
  273. * @return 返回执行结果
  274. */
  275. static int rn6752_device_tree_info(struct rn6752 *rn6752)
  276. {
  277. struct device *dev = &rn6752->client->dev;
  278. struct device_node *node = dev->of_node; /* 设备树节点 */
  279. int ret;
  280. unsigned int i;
  281. /* 获取设备树中 “rockchip,camera-module-index” 节点的信息,用于摄像头模块索引 */
  282. ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
  283. &rn6752->module_index);
  284. /* 获取设备树中 “rockchip,camera-module-facing” 节点的信息,用于摄像头面向的方向 */
  285. ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
  286. &rn6752->module_facing);
  287. /* 获取设备树中 “rockchip,camera-module-name” 节点的信息,用于摄像头的名称 */
  288. ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
  289. &rn6752->module_name);
  290. /* 获取设备树中 “rockchip,camera-module-lens-name” 节点的信息,用于摄像头的镜头名称 */
  291. ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
  292. &rn6752->len_name);
  293. if (ret) {
  294. dev_err(dev, "could not get module information!\n");
  295. return -EINVAL;
  296. }
  297. /* 获取设备 PWDN 和复位的引脚句柄 */
  298. rn6752->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);
  299. if (IS_ERR(rn6752->pwdn_gpio))
  300. dev_warn(dev, "Failed to get pwdn-gpios, maybe no use\n");
  301. rn6752->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
  302. if (IS_ERR(rn6752->reset_gpio))
  303. dev_warn(dev, "Failed to get reset-gpios, maybe no use\n");
  304. /* 获取摄像头的时钟资源 */
  305. rn6752->xvclk = devm_clk_get(dev, "xvclk");
  306. if (IS_ERR(rn6752->xvclk)) {
  307. dev_err(dev, "Failed to get xvclk\n");
  308. return -EINVAL;
  309. }
  310. for (i = 0; i < RN6752_NUM_SUPPLIES; i++)
  311. rn6752->supplies[i].supply = rn6752_supply_names[i];
  312. /* 获取摄像头设备的电源管理器句柄 */
  313. ret = devm_regulator_bulk_get(dev, RN6752_NUM_SUPPLIES, rn6752->supplies);
  314. if (ret)
  315. {
  316. dev_err(dev, "Failed to get power regulators\n");
  317. return -EINVAL;
  318. }
  319. return 0;
  320. }
  321. /**
  322. * @brief 初始化 rn6752 摄像头的硬件控制器函数,通过命令
  323. * “v4l2-ctl -d /dev/v4l-subdev5 --list-ctrls” 可以查看
  324. * @param rn6752 摄像头结构体指针
  325. */
  326. static int rn6752_initialize_controls(struct rn6752 *rn6752)
  327. {
  328. struct v4l2_ctrl_handler *handler;
  329. int ret;
  330. handler = &rn6752->ctrls;
  331. /* 初始化 rn6752->ctrls,该函数需要两个参数:控制处理器对象和控制器的数量 */
  332. ret = v4l2_ctrl_handler_init(handler, 3);
  333. if (ret)
  334. return ret;
  335. handler->lock = &rn6752->lock;
  336. /* 创建一个 V4L2 控制器对象,并将其与 rn6752->ctrls 关联起来 */
  337. rn6752->link_frequency = v4l2_ctrl_new_std(handler, &rn6752_ctrl_ops,
  338. V4L2_CID_PIXEL_RATE, 0, RN6752_PIXEL_RATE, 1, RN6752_PIXEL_RATE);
  339. /* 创建一个带有选项菜单的 V4L2 控制器对象 */
  340. v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
  341. ARRAY_SIZE(link_freq_menu_items) - 1, 0, link_freq_menu_items);
  342. /* 创建另一个带有选项菜单的 V4L2 控制器对象 */
  343. v4l2_ctrl_new_std_menu_items(handler, &rn6752_ctrl_ops, V4L2_CID_TEST_PATTERN,
  344. ARRAY_SIZE(rn6752_test_pattern_menu) - 1, 0, 0, rn6752_test_pattern_menu);
  345. rn6752->subdev.ctrl_handler = &rn6752->ctrls;
  346. if (handler->error) {
  347. ret = handler->error;
  348. dev_err(&rn6752->client->dev, "Failed to init controls(%d)\n", ret);
  349. goto err_free_handler;
  350. }
  351. return 0;
  352. err_free_handler:
  353. v4l2_ctrl_handler_free(handler);
  354. return ret;
  355. }
  356. /**
  357. * @brief 根据读取到的芯片 ID 和版本号计算出一个 id 值,并与预定义的 RN6752_ID 进行比较
  358. * @param rn6752 摄像头设备
  359. */
  360. static int rn6752_check_sersor_id(struct rn6752 *rn6752)
  361. {
  362. struct i2c_client *client = rn6752->client;
  363. u8 pid, ver;
  364. int ret;
  365. dev_dbg(&client->dev, "%s:\n", __func__);
  366. /* Check sensor revision */
  367. ret = rn6752_read(client, REG_SC_CHIP_ID_H, &pid); /* 读取芯片 ID 的高字节 */
  368. if (!ret)
  369. ret = rn6752_read(client, REG_SC_CHIP_ID_L, &ver); /* 读取芯片 ID 的低字节 */
  370. if (!ret) {
  371. unsigned short id;
  372. id = SENSOR_ID(pid, ver);
  373. if (id != RN6752_ID) {
  374. ret = -1;
  375. dev_err(&client->dev, "Sensor detection failed (%04X, %d)\n", id, ret);
  376. } else {
  377. dev_info(&client->dev, "Found %04X sensor\n", id);
  378. }
  379. }
  380. return ret;
  381. }
  382. /**
  383. * @brief 系统检测到与该驱动程序匹配的 I2C 设备时
  384. * @param client 指向 I2C 客户端结构体的指针。该结构体包含了有关 I2C 设备的信息,如设备地址、总线信息等
  385. * @param id 指向 I2C 设备 ID 的指针。这个参数用于在多个相同类型的设备中进行区分和匹配
  386. * @return 返回初始化结构
  387. */
  388. static int rn6752_probe(struct i2c_client *client, const struct i2c_device_id *id)
  389. {
  390. struct device *dev = &client->dev;
  391. struct v4l2_subdev *sd;
  392. struct rn6752 *rn6752;
  393. char facing[2];
  394. int ret;
  395. /* 打印驱动的版本信息的函数,其作用相当于 printk() 函数 */
  396. dev_info(dev, "driver version: %02x.%02x.%02x", DRIVER_VERSION >> 16,
  397. (DRIVER_VERSION & 0xff00) >> 8, DRIVER_VERSION & 0x00ff);
  398. /* 为设备分配内存,并将内存与设备进行关联。在驱动程序退出时,内存会自动被释放。被称为“设备内存管理” */
  399. rn6752 = devm_kzalloc(dev, sizeof(*rn6752), GFP_KERNEL);
  400. if (!rn6752)
  401. {
  402. dev_err(dev, "Memory control request failed\n");
  403. return -ENOMEM;
  404. }
  405. rn6752->client = client;
  406. sd = &rn6752->subdev;
  407. /* 获取设备树信息 */
  408. ret = rn6752_device_tree_info(rn6752);
  409. if (ret != 0)
  410. return -EINVAL;
  411. /* rn6752_mipi_framesizes 是 rn6752 mipi 通信支持的所有帧格式 */
  412. rn6752->framesize_cfg = rn6752_mipi_framesizes;
  413. rn6752->cfg_num = ARRAY_SIZE(rn6752_mipi_framesizes);
  414. /* 获取摄像头传感器支持的图像帧格式 */
  415. rn6752_get_default_format(rn6752, &rn6752->format);
  416. rn6752->frame_size = &rn6752->framesize_cfg[0]; /* 设置帧大小 */
  417. rn6752->format.width = rn6752->framesize_cfg[0].width; /* 设置宽度 */
  418. rn6752->format.height = rn6752->framesize_cfg[0].height; /* 设置高度 */
  419. rn6752->fps = DIV_ROUND_CLOSEST(rn6752->framesize_cfg[0].max_fps.denominator,
  420. rn6752->framesize_cfg[0].max_fps.numerator); /* 设置最大帧速率 */
  421. mutex_init(&rn6752->lock);
  422. #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
  423. /* 通过v4l2框架注册一个V4L2子设备,并初始化该子设备的操作函数和内部操作函数指针 */
  424. v4l2_i2c_subdev_init(sd, client, &rn6752_subdev_ops);
  425. ret = rn6752_initialize_controls(rn6752);
  426. if (ret)
  427. {
  428. dev_info(dev, "V4l2 control menu initialization failed");
  429. goto err_destroy_mutex;
  430. }
  431. /* 关联子设备的内部操作函数,用于打开摄像头操作 */
  432. sd->internal_ops = &rn6752_subdev_internal_ops;
  433. sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | /* 设备有对应的设备节点文件,可以通过该文件进行访问 */
  434. V4L2_SUBDEV_FL_HAS_EVENTS; /* 设备具有事件机制,可以作为事件源供其他驱动程序使用 */
  435. #endif
  436. /* 打开设备的电源 */
  437. ret = __rn6752_power_on(rn6752);
  438. if (ret)
  439. goto err_free_handler;
  440. /* 通过读取 RN6752 的产品 ID 判断设备是否存在 */
  441. ret = rn6752_check_sersor_id(rn6752);
  442. if (ret)
  443. goto err_power_off;
  444. #if defined(CONFIG_MEDIA_CONTROLLER)
  445. rn6752->pad.flags = MEDIA_PAD_FL_SOURCE; /* 表明该子设备是媒体管道中的源设备,即数据的起始点 */
  446. sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; /* 表示该实体是一个相机传感器 */
  447. ret = media_entity_pads_init(&sd->entity, 1, &rn6752->pad); /* 初始化 sd->entity 媒体实体的 pads(端口)属性 */
  448. if (ret < 0) {
  449. dev_err(dev, "Media pad port initialization failed\n");
  450. goto err_power_off;
  451. }
  452. #endif
  453. /* 根据设备树提供的信息,判断摄像头的方向 */
  454. memset(facing, 0, sizeof(facing));
  455. if (strcmp(rn6752->module_facing, "back") == 0)
  456. facing[0] = 'b';
  457. else
  458. facing[0] = 'f';
  459. /* 名称格式如 m00_f_rn6752 1-002c:bus */
  460. snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
  461. rn6752->module_index, facing, DRIVER_NAME, dev_name(sd->dev));
  462. /* 将传感器子设备添加到V4L2异步子系统中,以便能够与其他V4L2组件进行交互。 */
  463. /* 它会自动设置子设备的相关回调函数,并与异步框架进行适当的关联,以管理传感器的采集和控制操作 */
  464. ret = v4l2_async_register_subdev_sensor_common(sd);
  465. if (ret)
  466. {
  467. dev_err(dev, "Failed to add sensor to V4L2 asynchronous subsystem\n");
  468. goto err_clean_entity;
  469. }
  470. /* 摄像头传感器注册成功,并打印日志信息 */
  471. dev_info(dev, "%s sensor driver registered !!\n", sd->name);
  472. /* 完成初始化后,使 rn6752 进入睡眠模式 */
  473. rn6752->power_on = false;
  474. if (!IS_ERR(rn6752->pwdn_gpio))
  475. gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1);
  476. return 0;
  477. err_clean_entity:
  478. #if defined(CONFIG_MEDIA_CONTROLLER)
  479. media_entity_cleanup(&sd->entity);
  480. #endif
  481. err_power_off:
  482. __rn6752_power_off(rn6752);
  483. err_free_handler:
  484. #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
  485. v4l2_ctrl_handler_free(&rn6752->ctrls);
  486. #endif
  487. err_destroy_mutex:
  488. mutex_destroy(&rn6752->lock);
  489. return ret;
  490. }
  491. /**
  492. * @brief 当设备驱动被删除释放时,执行此函数
  493. * @param client 指向 I2C 客户端结构体的指针。该结构体包含了有关 I2C 设备的信息,如设备地址、总线信息等
  494. * @return 返回操作结果
  495. */
  496. static int rn6752_remove(struct i2c_client *client)
  497. {
  498. struct v4l2_subdev *sd = i2c_get_clientdata(client);
  499. struct rn6752 *rn6752 = to_rn6752(sd);
  500. #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
  501. /* 释放V4L2控制器处理器所占用的资源 */
  502. v4l2_ctrl_handler_free(&rn6752->ctrls);
  503. #endif
  504. /* 注销一个V4L2子设备 */
  505. v4l2_async_unregister_subdev(sd);
  506. #if defined(CONFIG_MEDIA_CONTROLLER)
  507. media_entity_cleanup(&sd->entity);
  508. #endif
  509. mutex_destroy(&rn6752->lock);
  510. __rn6752_power_off(rn6752);
  511. return 0;
  512. }
  513. /* 设备树节点匹配表格,与设备树中的节点描述信息一样时,匹配成功 */
  514. #if IS_ENABLED(CONFIG_OF)
  515. static const struct of_device_id rn6752_of_match[] = {
  516. { .compatible = "richnex,rn6752v1" },
  517. { /* sentinel */ },
  518. };
  519. MODULE_DEVICE_TABLE(of, rn6752_of_match);
  520. #endif
  521. /* 设备 ID 表格,与设备名称一样时匹配成功,主要用于低版本linux内核的匹配方式 */
  522. static const struct i2c_device_id rn6752_match_id[] = {
  523. { "richnex,rn6752v1", 0 },
  524. { /* sentinel */ },
  525. };
  526. /* 描述和注册一个 I2C 设备驱动程序 */
  527. static struct i2c_driver rn6752_i2c_driver = {
  528. .driver = { /* 驱动程序信息的子结构体 */
  529. .name = DRIVER_NAME, /* 设置驱动程序的名称 */
  530. .of_match_table = of_match_ptr(rn6752_of_match), /* 用于匹配设备树节点的表格 */
  531. },
  532. .probe = rn6752_probe, /* I2C 设备被检测到时进行设备初始化和处理 */
  533. .remove = rn6752_remove, /* I2C 设备从系统中移除时进行清理和资源释放 */
  534. .id_table = rn6752_match_id, /* I2C 设备 ID 表格,平台设备匹配方式之一,用设备和驱动的名称进行匹配 */
  535. };
  536. static int __init sensor_mod_init(void)
  537. {
  538. /* 注册 I2C 驱动程序 */
  539. return i2c_add_driver(&rn6752_i2c_driver);
  540. }
  541. static void __exit sensor_mod_exit(void)
  542. {
  543. /* 注销 I2C 驱动程序 */
  544. i2c_del_driver(&rn6752_i2c_driver);
  545. }
  546. device_initcall_sync(sensor_mod_init); /* 注册一个设备初始化函数 */
  547. module_exit(sensor_mod_exit); /* 注销一个设备初始化函数 */
  548. MODULE_AUTHOR("jiaozhu <cn_jiaozhu@qq.com>");
  549. MODULE_DESCRIPTION("RN6752 CMOS Image Sensor driver");

参考资料

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

Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)的更多相关文章

  1. USB摄像头驱动框架分析(五)

    一.USB摄像头驱动框架如下所示:1.构造一个usb_driver2.设置   probe:        2.1. 分配video_device:video_device_alloc        ...

  2. Hi3559AV100外接UVC/MJPEG相机实时采图设计(一):Linux USB摄像头驱动分析

    下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...

  3. 基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)

    作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 FIMC这个名字应该是从S5PC1x0開始出现的.在s5pv210里面的定义是摄像头接口.可是它相 ...

  4. Linux USB摄像头驱动【转】

    本文转载自:http://www.itdadao.com/articles/c15a509940p0.html 在 cortex-a8 中,可接入摄像头的接口通常可以分为两种, CAMERA 接口和 ...

  5. Linux USB 摄像头驱动

    在 cortex-a8 中,可接入摄像头的接口通常可以分为两种, CAMERA 接口和 USB 接口的摄像头.这一章主要是介绍 USB 摄像头的设备驱动程序.在我们印象中,驱动程序都是一个萝卜一个坑, ...

  6. USB摄像头驱动框架分析

    usb摄像头驱动程序,里面涉及硬件的操作.比如说,想设置亮度的时候,需要把亮度的参数发给硬件.去得到真正视频数据的时候,需要访问硬件得到数据.usb摄像头驱动程序框架与虚拟摄像头驱动程序的框架是一样的 ...

  7. Linux下USB驱动框架分析【转】

    转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...

  8. 一步步理解linux字符设备驱动框架(转)

    /* *本文版权归于凌阳教育.如转载请注明 *原作者和原文链接 http://blog.csdn.net/edudriver/article/details/18354313* *特此说明并保留对其追 ...

  9. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  10. 2.1 摄像头V4L2驱动框架分析

    学习目标:学习V4L2(V4L2:vidio for linux version 2)摄像头驱动框架,分析vivi.c(虚拟视频硬件相关)驱动源码程序,总结V4L2硬件相关的驱动的步骤:  一.V4L ...

随机推荐

  1. Ceres简单应用-求解(Powell's Function)鲍威尔函数最小值

    Ceres 求解 Powell's function 的最小化 \(\quad\)现在考虑一个稍微复杂一点的例子-鲍威尔函数的最小化. \(\quad{}\) \(x=[x_1,x_2,x_3,x_4 ...

  2. [mysql]状态检查常用SQL

    前言 使用MySQL自身命令获取数据库服务状态. 连接数 -- 最大使用连接数 show status like 'Max_used_connections'; -- 系统配置的最大连接数 show ...

  3. 《Pro Git》Git分支笔记

    Git分支简介 在Git中,有个校验和的概念,主要用于验证数据完整性,它是一个40位16进制字符串,使用SHA-1哈希算法生成.校验和也标识了Git中每一个对象. 我们由前一章阅读了解到Git保存的是 ...

  4. 【Unity3D】激光雷达特效

    1 由深度纹理重构世界坐标 ​ 屏幕深度和法线纹理简介中对深度和法线纹理的来源.使用及推导过程进行了讲解,本文将介绍使用深度纹理重构世界坐标的方法,并使用重构后的世界坐标模拟激光雷达特效. ​ 本文完 ...

  5. IDA常用的插件

    IDA常用的插件 FindCrypto https://github.com/polymorf/findcrypt-yara 算法识别 缺点:对于魔改的地方难以识别,比如对aes的s盒进行加密,运行时 ...

  6. ENVI+ERDAS实现Hyperion叶绿素含量反演:经验比值法、一阶微分法

    本文介绍基于ENVI与ERDAS软件,依据Hyperion高光谱遥感影像,采用经验比值法.一阶微分法等,对叶绿素含量等地表参数加以反演的具体操作. 目录 1 前期准备与本文理论部分 1.1 几句闲谈 ...

  7. .NET周刊【8月第3期 2023-08-20】

    国内主题 抓的是周树人,与我鲁迅有什么关系? https://www.cnblogs.com/JulianHuang/p/17642511.html 问题:作者看到了一个关于Dictionary.Cl ...

  8. C++算法之旅、04 基础篇 | 第一章

    常用代码模板1--基础算法 - AcWing ios::sync_with_stdio(false) 提高 cin 读取速度,副作用是不能使用 scanf 数据输入规模大于一百万建议用scanf 快速 ...

  9. C++的编译链接与在vs中build提速

    通过gcc或msvc,clang等编译器编译出来的C++源文件是.o文件.在windows上也就是PE文件,linux为ELF文件,在这一步中,调用其它代码文件中的函数的函数地址是未知的(00000) ...

  10. 浅谈Mysql读写分离的坑以及应对的方案

    一.主从架构 为什么我们要进行读写分离?个人觉得还是业务发展到一定的规模,驱动技术架构的改革,读写分离可以减轻单台服务器的压力,将读请求和写请求分流到不同的服务器,分摊单台服务的负载,提高可用性,提高 ...