SPRD
t606_t
原理
闪光灯在展讯t606主要有三种控制方式
-
内置PMIC(也称为假闪)
-
外置闪光灯IC(也称为真闪)
1)GPIO控制(aw3641)
2)GPIO+I2C
3)PWM控制 -
屏幕LCD做补光
代码目录
├── bsp
│ ├── device
│ │ └── qogirl6
│ │ └── androidt
│ │ └── common
│ │ └── modules.cfg //ko模块配置
│ ├── kernel5.4
│ │ └── kernel5.4
│ │ └── arch
│ │ └── arm64
│ │ └── boot
│ │ └── dts
│ │ └── sprd //展讯设备树
│ │ ├── sc2730.dtsi
│ │ └── ums9230-1h10-overlay.dts
│ └── modules
│ └── common
│ └── camera
│ └── flash
│ ├── aw3641 //GPIO类型IC驱动
│ │ ├── flash_ic_aw3641_drv.c
│ │ ├── Kbuild
│ │ └── Makefile
│ ├── flash_drv //通用接口驱动
│ │ ├── flash_drv.c
│ │ ├── flash_drv.h
│ │ ├── Kbuild
│ │ └── Makefile
│ └── sc2730_kpled //PMIC类型驱动
│ ├── Kbuild
│ ├── Makefile
│ ├── sc2730_kpled_drv.c
│ └── sc2730_kpled_reg.h
└── device
└── sprd
└── mpool
└── module
└── others
└── bsp
└── mfeature
└── kernel
└── kernel5.4
└── msoc
└── qogirl6
└── ko
├── km.mk //ko模块配置
└── modules.load //ko模块顺序
代码分析code
-
在接口驱动flash_drv.c中定义了一些接口,包括打开闪光灯open_highlight 和打开手电筒open_torch 等,上层调用驱动时则会调用这些接口
static const struct sprd_flash_driver_ops flash_gpio_ops = { .open_torch = sprd_flash_aw3641_open_torch, .close_torch = sprd_flash_aw3641_close_torch, .open_preflash = sprd_flash_aw3641_open_preflash, .close_preflash = sprd_flash_aw3641_close_preflash, .open_highlight = sprd_flash_aw3641_open_highlight, .close_highlight = sprd_flash_aw3641_close_highlight, .cfg_value_torch = sprd_flash_aw3641_cfg_value_torch, .cfg_value_preflash = sprd_flash_aw3641_cfg_value_preflash, .cfg_value_highlight = sprd_flash_aw3641_cfg_value_highlight, };
-
而具体的接口实现,则需要加上具体的IC驱动,那么如何让通用接口调用到IC驱动对应的函数实现呢?继续看flash_drv.c
在这段代码中,定义了一个设备结构flash_device 与对应的结构体s_flash_dev,结构体中的ops[SPRD_FLASH_MAX]则指向IC驱动中的实现函数driver_data[SPRD_FLASH_MAX]则为驱动的各种数据
函数sprd_flash_register就是用于注册不同IC驱动的函数(指针赋值),使用EXPORT_SYMBOL(sprd_flash_register);导出供其他驱动使用,此函数的形参有三个,分别是实现函数指针ops,驱动数据*drvd,前后摄flash_idxenum { SPRD_FLASH_REAR = 0, SPRD_FLASH_FRONT = 1, SPRD_FLASH_MAX, }; /* Structure Definitions */ struct flash_device { struct miscdevice md; const struct sprd_flash_driver_ops *ops[SPRD_FLASH_MAX]; void *driver_data[SPRD_FLASH_MAX]; int flashlight_status[SPRD_FLASH_MAX]; unsigned short attr_test_value; char *flash_ic_name; }; /* Static Variables Definitions */ static struct platform_device *pdev; static struct flash_device *s_flash_dev; int sprd_flash_register(const struct sprd_flash_driver_ops *ops, void *drvd, uint8_t flash_idx) { if (!s_flash_dev) return -EPROBE_DEFER; s_flash_dev->ops[flash_idx] = ops; s_flash_dev->driver_data[flash_idx] = drvd; return 0; } EXPORT_SYMBOL(sprd_flash_register);
-
在IC驱动的probe中可看到使用注册函数sprd_flash_register
static int sprd_flash_aw3641_probe(struct platform_device *pdev) { //…… ret = sprd_flash_register(&flash_gpio_ops, drv_data, SPRD_FLASH_REAR); exit: return ret; }
static int sprd_flash_SC2730_probe(struct platform_device *pdev) { //…… #ifdef FRONT_FLASH ret = sprd_flash_register(&flash_sc2730_front_ops, drv_data, 1); #endif //…… ret = sprd_flash_register(&flash_SC2730_ops, drv_data, flash_idx); if (ret < 0) goto exit; //…… }
-
接下来看实现函数调用,以下用模拟高闪作为例子
-
通用驱动
static int flash_open_highlight(struct flash_device *dev, uint8_t flash_idx, uint8_t led_idx) { int ret = -EPERM; if (!dev || !dev->ops[flash_idx]) goto exit; if (dev->ops[flash_idx]->open_highlight) ret = dev->ops[flash_idx]->open_highlight( dev->driver_data[flash_idx], led_idx); exit: return ret; }
-
IC驱动
以下是aw3641驱动,调用关系不深,可以看到在open_highlight中先关闭了FLASH口和EN口,然后调用了模拟PWM的函数open_pwm,通过定时开关实现pwm等级控制
static int sprd_flash_aw3641_open_highlight(void *drvd, uint8_t idx) { int ret = 0; int gpio_id = 0; struct flash_driver_data *drv_data = (struct flash_driver_data *)drvd; if (!drv_data) return -EFAULT; pr_info("highlight_opened:%d\n", highlight_opened); if(1 == highlight_opened) { gpio_id = drv_data->gpio_tab[GPIO_FLASH_TORCH_MODE]; if (gpio_is_valid(gpio_id)) { ret = gpio_direction_output(gpio_id, SPRD_FLASH_OFF); if (ret) goto exit; } gpio_id = drv_data->gpio_tab[GPIO_CHIP_EN]; if (gpio_is_valid(gpio_id)) { ret = gpio_direction_output(gpio_id, SPRD_FLASH_OFF); if (ret) goto exit; } udelay(550); } idx = drv_data->torch_led_index; ret = sprd_flash_aw3641_open_pwm(drv_data, idx, g_high_level); if (ret) goto exit; highlight_opened = 1; exit: return ret; } static int sprd_flash_aw3641_open_pwm(void *drvd, uint8_t idx, int level) { int ret = 0; int i = 0; int gpio_id = 0; unsigned long flags; struct flash_driver_data *drv_data = (struct flash_driver_data *)drvd; if (!drv_data) return -EFAULT; if (level > FLASH_MAX_LEVEL) level = FLASH_MAX_LEVEL; if (level < FLASH_MIN_LEVEL) level = FLASH_MIN_LEVEL; idx = drv_data->torch_led_index; if (SPRD_FLASH_LED0 & idx) { gpio_id = drv_data->gpio_tab[GPIO_FLASH_TORCH_MODE]; if (gpio_is_valid(gpio_id)) { ret = gpio_direction_output(gpio_id, SPRD_FLASH_ON); } } if (SPRD_FLASH_LED0 & idx) { gpio_id = drv_data->gpio_tab[GPIO_CHIP_EN]; if (gpio_is_valid(gpio_id)) { ret = gpio_direction_output(gpio_id, SPRD_FLASH_OFF); udelay(550); spin_lock_irqsave(&flash_lock, flags); for (i = 0; i < FLASH_MAX_PWM - level; i++) { pr_info("open_pwm:%d\n", i); ret = gpio_direction_output(gpio_id, SPRD_FLASH_OFF); udelay(2); ret = gpio_direction_output(gpio_id, SPRD_FLASH_ON); udelay(2); } spin_unlock_irqrestore(&flash_lock, flags); } } return ret; }
以下是SC2730驱动,顺着调用关系往下查可看到最终为寄存器操作,通过PMIC寄存器修改对应LDO的状态
static int sprd_flash_SC2730_open_highlight(void *drvd, uint8_t idx) { struct flash_driver_data *drv_data = (struct flash_driver_data *)drvd; #ifdef GPIO_FLASH //…… #endif #ifdef KEYPAD_LED_FLASH///for add kpled flash sprd_kpled_enable(drv_data); #else //…… #endif return 0; } static void sprd_kpled_enable(struct flash_driver_data *led) { if (led->run_mode == 1) /* current mode */ sprd_kpled_current_switch(led, KPLED_SWITCH_ON); else /* ldo mode */ sprd_kpled_ldo_switch(led, KPLED_SWITCH_ON); PRINT_INFO("sprd_kpled_enable\n"); #if defined (JZHK_KPLED_FLASH_60MA) sprd_kpled_set_brightness(led,253); #elif defined (JZHK_KPLED_FLASH_40MA) sprd_kpled_set_brightness(led,251); #else sprd_kpled_set_brightness(led,255); #endif } static void sprd_kpled_set_brightness(struct flash_driver_data *led, unsigned long value) { unsigned long brightness = value; unsigned long brightness_level; unsigned int ldo_reg; unsigned int ldo_v_shift; unsigned int ldo_v_mask; brightness_level = brightness; PRINT_INFO("sprd_kpled_set_brightness:led->run_mode = %d\n", led->run_mode); if (brightness_level > 255) brightness_level = 255; if (brightness_level > led->brightness_max) brightness_level = led->brightness_max; if (brightness_level < led->brightness_min) brightness_level = led->brightness_min; // brightness_level = brightness_level/16; /*brightness steps = 16 */ if (led->run_mode == 1) { regmap_update_bits(led->reg_map, led->reg_kpled_ctrl0, KPLED_V_MSK, ((brightness_level << KPLED_V_SHIFT) & KPLED_V_MSK)); PRINT_INFO("reg:0x%08X set_val:0x%08X brightness:%ld\n", led->reg_kpled_ctrl0, kpled_read(led, led->reg_kpled_ctrl0), brightness); } else { if (led->chip_version == SC2730_KPLED) { ldo_reg = led->reg_kpled_ctrl1, ldo_v_shift = 7; ldo_v_mask = 0xff << ldo_v_shift; } else { ldo_reg = led->reg_kpled_ctrl0, ldo_v_shift = 0; ldo_v_mask = 0xff << ldo_v_shift; } regmap_update_bits(led->reg_map, ldo_reg, ldo_v_mask, ((brightness_level << ldo_v_shift) & ldo_v_mask)); PRINT_INFO("reg:0x%08X set_val:0x%08X brightness:%ld\n", ldo_reg, kpled_read(led, ldo_reg), brightness); } }
-
设备树dts
sc2730
&adi_bus {
sc2730_pmic: pmic@0 {
compatible = "sprd,sc2730";
reg = <0>;
spi-max-frequency = <26000000>;
interrupt-controller;
#interrupt-cells = <1>;
#address-cells = <1>;
#size-cells = <0>;
pmic_kpled: kpled@1b88 {
compatible = "sprd,sc27xx-kpled", "sprd,sc2730-kpled";
brightness_max = <255>;
brightness_min = <0>;
run_mode = <1>; /* default current mode */
reg = <0x1b88>,<0x1b8c>;
};
}
aw3641
/ {
model = "Spreadtrum UMS9230 1H10 Board";
compatible = "sprd,ums9230-1h10";
sprd,sc-id = "ums9230 1h10 1000";
sprd,board_id = <0x1100>; //XX00:base board,XX00~XXFF:customize board
fragment {
target-path = "/";
__overlay__ {
flash-aw3641 {
compatible = "sprd,flash-aw3641";
flash-ic = <0>;
flash-torch-en-gpios = <&ap_gpio 8 GPIO_ACTIVE_HIGH>;
flash-en-gpios = <&ap_gpio 32 GPIO_ACTIVE_HIGH>;
};
}
adb测试节点
sys/class/misc/sprd_flash/test
通过往节点写入0~9来测试闪光灯功能,使用前需要打开sprd_flash驱动flash_drv.c中的宏FLASH_TEST
0:flash_open_preflash
1:flash_close_preflash
2:flash_open_torch
3:flash_close_torch
4:flash_open_highlight
5:flash_close_highlight
6:flash_cfg_value_preflash
7:flash_cfg_value_torch
8:flash_cfg_value_highlight
9:pr_info(“%s %s”, flash_capacity.flash_ic_name, flash_dev->flash_ic_name);
测试节点代码:这段代码主要是通过DEVICE_ATTR和device_create_file来实现目标机节点与驱动的交互
#ifdef FLASH_TEST
static struct sprd_flash_capacity flash_capacity = {0};
static ssize_t flash_sysfs_test(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t size)
{
int ret = 0;
unsigned int val, cmd, led_idx;
unsigned int flash_idx = 0;
struct flash_device *flash_dev = NULL;
struct sprd_flash_element element;
flash_dev = dev->platform_data;
if (!flash_dev) {
pr_err("flash device is null\n");
return 0;
}
ret = kstrtouint(buf, 16, &val);
if (ret)
goto exit;
flash_dev->attr_test_value = val;
cmd = val & 0x0f;
led_idx = (val & 0xf0) >> 4;
element.index = (val & 0x1f00) >> 8;
flash_idx = (val & 0x8000) >> 15;
pr_info("cmd:%d flash:%d element.index %d flash_idx %d\n",
cmd, led_idx, element.index, flash_idx);
switch (cmd) {
case 0:
flash_open_preflash(flash_dev, flash_idx, led_idx);
break;
case 1:
flash_close_preflash(flash_dev, flash_idx, led_idx);
break;
case 2:
flash_dev->flashlight_status[0] = 1;
flash_open_torch(flash_dev, flash_idx, led_idx);
break;
case 3:
flash_dev->flashlight_status[0] = 0;
flash_close_torch(flash_dev, flash_idx, led_idx);
break;
case 4:
flash_open_highlight(flash_dev, flash_idx, led_idx);
break;
case 5:
flash_close_highlight(flash_dev, flash_idx, led_idx);
break;
case 6:
flash_cfg_value_preflash(flash_dev, flash_idx,
led_idx, &element);
break;
case 7:
flash_cfg_value_torch(flash_dev, flash_idx, led_idx, &element);
break;
case 8:
flash_cfg_value_highlight(flash_dev, flash_idx,
led_idx, &element);
break;
case 9:
flash_capacity = flash_get_flash_info(flash_dev, flash_idx,
led_idx, &flash_capacity);
flash_dev->flash_ic_name = flash_capacity.flash_ic_name;
pr_info("%s %s", flash_capacity.flash_ic_name, flash_dev->flash_ic_name);
break;
default:
break;
}
return size;
exit:
return ret;
}
static ssize_t show_help(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct flash_device *flash_dev = NULL;
flash_dev = dev->platform_data;
if (!flash_dev) {
pr_err("flash device is null\n");
return 0;
}
if((flash_dev->attr_test_value & 0x0f) == 0x9)
return sprintf(buf, "%s\n", flash_capacity.flash_ic_name);
return sprintf(buf, "%x\n", flash_dev->attr_test_value);
}
static DEVICE_ATTR(test, S_IRUSR | S_IWUSR, show_help, flash_sysfs_test);
#endif
static int sprd_flash_probe(struct platform_device *pdev)
{
//……
#ifdef FLASH_TEST
ret = device_create_file(flash_dev->md.this_device, &dev_attr_test);
if (ret < 0) {
pr_err("failed to create flash test file");
goto fail;
}
#endif
//……
}