您的位置:首页 > 运维架构 > Linux

lwn拾遗:[sn3218 led driver]-api解释-2

2016-02-18 19:32 2146 查看
regcache_hw_init
有些i2c设备的寄存器是volatile的,是不能cache的.
1,先计算不是volatile类型的寄存器个数
轮询0到map->num_reg_defaults_raw,并用regmap_volatile判断当前的寄存器是否是volatile的.记录下不是volatile的寄存器的个数.如果全部寄存器都是volatile的,那直接返回没必要在往下走.
2,调用kmalloc_array分配count个reg_default寄存器所占的空间.
3, reg_defaults_raw描述的寄存器默认的原始值为空时.需要分配出来并调用regmap_raw_read拿到该raw数据.
4,填充寄存器值.调用regcache_get_val从raw数据中拿到对应寄存器的值并写到对应的def中.

regmap_raw_read
regmap_raw_read
1,如果目标寄存器index是volatile范围的,或者i2c设备配置是bypass的,或者i2c设备的cache类型是REGCACHE_NONE,这些含义是没有cache的.
直接调用_regmap_raw_read从硬件读取
2,否则的话语_regmap_read从cache中读取.并调用map->format.format_val取出格式化的val值.

_regmap_raw_read
1,从红黑树中找到当前reg的node.
2,如果拿到了node,调用_regmap_select_page修正reg的值,该reg和window相操作.
3,调用map->format.format_reg描述的格式化的寄存器.比如地址+值 是 6+7
4,调用map->bus->read描述的read操作.

regcache_get_val
regcache_get_val

_regmap_select_page

_regmap_select_page
Reg是当前寄存器的index,
Range_min是当前i2c范围的开始地址,window_len是”窗”的长度.
1,计算偏移
win_offset = (*reg - range->range_min) % range->window_len;
win_page = (*reg - range->range_min) / range->window_len;
2,如果目标寄存器跨越1个寄存器(超过4个字节了)
调用_regmap_update_bits来拿到新的数据.
_regmap_update_bits
调用_regmap_read先从i2c中目标寄存器中读出值orig,然后把该值orig掩码掉mask,然后或上val,将得到的结果调用_regmap_write写入到目标寄存器.

 

_regmap_write
写有两种方式,一种通过cache,一种不过cache.这里的cache并不是cpu中的那种,是一种缓冲的机制.
1,调用_regmap_map_get_context拿到上下文.这个context是为了在没有cache情况下,或者cache_only为false情况下,通过reg_write来写时,会用到这个context.
_regmap_map_get_context
return (map->bus) ? map : map->bus_context;   //   如果bus存在返回map,不存在,返回bus_context

2,调用regmap_writeable判断是否可写,不可写就返回EIO错误.
regmap_writeable
可写满足条件:
map->max_register存在且reg<=map->max_register
如果map->writeable_reg存在,返回map->writeable_reg()针对reg这个index的结果
如果map->writeable_reg不存在,但是map->wr_table存在,调用regmap_check_range_table返回reg这个index在table中的结果.
如果都不存在,默认是true.
regmap_check_range_table
1,针对yes_range和no_range做基本的判断
2,调用regmap_reg_in_ranges来判断reg描述的index是否在range内.其中regmap_reg_in_ranges比较reg这个index是否处于(ange->range_min ,range->range_max)间.

3,针对cache写情况,调用regcache_write来写.
regcache_write
调用map->cache_ops->write()函数.

4,不经cache
map->reg_write(context, reg, val);
在regmap_init中根据不同的情况
Reg_write会被赋予:
reg_write
map->format.format_write 存在时, _regmap_bus_formatted_write 

map->format.format_val存在时, _regmap_bus_raw_write
(!bus->read || !bus->write)时,  _regmap_bus_reg_write  //  这个bus代表前边的regmap_i2c描述的i2c的bus
(!bus)时, map->reg_write = config->reg_write;

_regmap_bus_formatted_write
直接调用regmap_i2c的map->bus->write描述的regmap_i2c_write.

_regmap_bus_raw_write
_regmap_bus_raw_write 调用_regmap_raw_write.
在_regmap_raw_write中,一个比较关键的数据结构是
void *work_val = map->work_buf + map->format.reg_bytes + map->format.pad_bytes; //  拿到要写入值的容器的地址
这个pad_bytes来源于config
map->format.pad_bytes = config->pad_bits / 8;
1,如果不是cache_bypass,并且format_parse_val存在.
调用map->format.parse_val()先带写入的val格式化成一个ival.
然后调用regcache_write向目标reg写入这个格式化后的ival.
如果cache_only为true,直接返回.
2,其他情况,从regmap中的红黑树中拿到reg对应的node.找到其对应的win_offset win_residue,如果目标reg所含有的值的长度超过了一个”窗”的长度,需要递归调用_regmap_raw_write写入目标值.如果没有超过”窗长度”,调用_regmap_select_page来修正描述寄存器index的reg为相对于当前window的偏移.

3,调用format.format_reg把reg描述的寄存器地址偏移格式化(如果在红黑树中,reg描述的是据当前窗体的偏移,如果不在红黑树中,reg描述的是寄存器的index偏移地址.)

4,如果只对一个寄存器”写”操作,就直接调用i2c的write操作,否则调用i2c的gather_write操作.(核心的写操作)
if (val == work_val)
ret = map->bus->write(map->bus_context, map->work_buf,
      map->format.reg_bytes +
      map->format.pad_bytes +
      val_len);
else if (map->bus->gather_write)
ret = map->bus->gather_write(map->bus_context, map->work_buf,
     map->format.reg_bytes +
     map->format.pad_bytes,
     val, val_len);

_regmap_bus_reg_write
调用map->bus->reg_write函数
但是regmap_i2c没有此函数

总结,regmap的write操作:
1,先尝试向缓冲cache中write.
2,如果bypass了,或者volatile,或者cache none情况,调用对应的底层协议的写( I2C、SPI、AC97、MMIO 和 SPMI 等),这里是i2c

_regmap_read
_regmap_read

1,先拿到context,在以后的map->reg_read会用到
2,如果不是bypass的,调用regcache_read从cache中读
3,如果不能从cache中read到,调用map->reg_read

map->reg_read的来源有三种:
if (!bus) {
map->reg_read  = config->reg_read;   //  bus不存在时从config中
…
} else if (!bus->read || !bus->write) {
map->reg_read = _regmap_bus_reg_read;  //  bus存在,依据bus的read,(i2c的read)
…
} else {
map->reg_read  = _regmap_bus_read;    //   默认
}

_regmap_bus_reg_read调用
map->bus->reg_read

_regmap_bus_read
调用_regmap_raw_read
调用map->bus->read

Macro
DIV_ROUND_UP
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))

devm_regmap_init_i2c
devm_regmap_init_i2c
1,调用regmap_get_i2c_bus从i2c中拿到regmap_bus.

struct regmap_bus {  //  描述的regmap的write read的底层机制
bool fast_io;
regmap_hw_write write;
regmap_hw_gather_write gather_write;
regmap_hw_async_write async_write;
regmap_hw_reg_write reg_write;
regmap_hw_read read;
regmap_hw_reg_read reg_read;
regmap_hw_free_context free_context;
regmap_hw_async_alloc async_alloc;
u8 read_flag_mask;
enum regmap_endian reg_format_endian_default;
enum regmap_endian val_format_endian_default;
};
2,调用devm_regmap_init对regmap进行初始化.主要是把config和描述i2c设备的bus设置到regmap中.
描述driver/led设备的led_classdev结构体.
struct led_classdev {
const char		*name;
enum led_brightness	 brightness;   //   亮度的刻度,不同刻度的亮度
enum led_brightness	 max_brightness;
int			 flags;

/* Lower 16 bits reflect status */
#define LED_SUSPENDED		(1 << 0)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME	(1 << 16)
#define LED_BLINK_ONESHOT	(1 << 17)
#define LED_BLINK_ONESHOT_STOP	(1 << 18)
#define LED_BLINK_INVERT	(1 << 19)
#define LED_SYSFS_DISABLE	(1 << 20)
#define SET_BRIGHTNESS_ASYNC	(1 << 21)
#define SET_BRIGHTNESS_SYNC	(1 << 22)
#define LED_DEV_CAP_FLASH	(1 << 23)

/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
void		(*brightness_set)(struct led_classdev *led_cdev,
  enum led_brightness brightness);             //   设置亮度的函数
/*
 * Set LED brightness level immediately - it can block the caller for
 * the time required for accessing a LED device register.
 */
int		(*brightness_set_sync)(struct led_classdev *led_cdev,
enum led_brightness brightness);              //    同步设置亮度函数
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);      //    获得当前亮度等级

/*
 * Activate hardware accelerated blink, delays are in milliseconds
 * and if both are zero then a sensible default should be chosen.
 * The call should adjust the timings in that case and if it can't
 * match the values specified exactly.
 * Deactivate blinking again when the brightness is set to a fixed
 * value via the brightness_set() callback.
 */
int		(*blink_set)(struct led_classdev *led_cdev,
     unsigned long *delay_on,
     unsigned long *delay_off);                   //    设置闪烁的延迟ms.

struct device		*dev;
const struct attribute_group	**groups;

struct list_head	 node;			/* LED Device list */
const char		*default_trigger;	/* Trigger to use */

unsigned long		 blink_delay_on, blink_delay_off;
struct timer_list	 blink_timer;
int			 blink_brightness;
void			(*flash_resume)(struct led_classdev *led_cdev);

struct work_struct	set_brightness_work;
int			delayed_set_value;

#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore	 trigger_lock;

struct led_trigger	*trigger;                            //    激活 熄灭led等操作的结构体
struct list_head	 trig_list;
void			*trigger_data;
/* true if activated - deactivate routine uses it to do cleanup */
bool			activated;
#endif

/* Ensures consistent access to the LED Flash Class device */
struct mutex		led_access;
};

struct led_trigger {
/* Trigger Properties */
const char	 *name;
void		(*activate)(struct led_classdev *led_cdev);          //   激活led
void		(*deactivate)(struct led_classdev *led_cdev);        //   熄灭led

/* LEDs under control by this trigger (for simple triggers) */
rwlock_t	  leddev_list_lock;
struct list_head  led_cdevs;

/* Link to next registered trigger */
struct list_head  next_trig;
};

i2c_set_clientdata
static inline void i2c_set_clientdata(struct i2c_client *dev, void *data)
{
dev_set_drvdata(&dev->dev, data);
}
static inline void dev_set_drvdata(struct device *dev, void *data)
{
dev->driver_data = data;
}

devm_led_classdev_register
devm_led_classdev_register
1,调用devres_alloc分配led_classdev结构体.并把devm_led_classdev_release设置成led_classdev的devres_node成员结构体的release方法.当device_ktype的device_release  ->  devres_release_all  ->  release_nodes  ->  该release方法.
2,调用led_classdev_register注册led_cdev描述的led字符设备.

led_classdev_register
1,调用led_classdev_next_name设置name
2,调用device_create_with_groups创建device
3,把当前led字符设备描述的node加到leds_list描述的led设备链中.
list_add_tail(&led_cdev->node, &leds_list);
4,更新led的亮度,调用led_update_brightness
5,调用setup_timer创建led的timer, led_timer_function,而timer是led_cdev->blink_timer.
#define __setup_timer(_timer, _fn, _data, _flags)			\
do {								\
__init_timer((_timer), (_flags));			\
(_timer)->function = (_fn);				\     设置function  
(_timer)->data = (_data);				\     设置data
} while (0)
__init_timer   ->  init_timer_key  ->  do_init_timer
do_init_timer

static DEFINE_PER_CPU(struct tvec_base *, tvec_bases) = &boot_tvec_bases;
#define raw_cpu_read(pcp)		__pcpu_size_call_return(raw_cpu_read_, pcp)
每cpu变量是raw_cpu_read_4_ tvec_bases,用这个值和flags与,然后初始化timer的base字段.

struct tvec_base {
spinlock_t lock;
struct timer_list *running_timer;
unsigned long timer_jiffies;
unsigned long next_timer;
unsigned long active_timers;
unsigned long all_timers;
int cpu;
struct tvec_root tv1;
struct tvec tv2;
struct tvec tv3;
struct tvec tv4;
struct tvec tv5;
} ____cacheline_aligned;


(adsbygoogle = window.adsbygoogle || []).push({});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux driver led regmap i2c