| /* |
| * Richtek regmap with debugfs Driver |
| * |
| * Copyright (C) 2014 Richtek Technology Corp. |
| * Author: Jeff Chang <jeff_chang@richtek.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/err.h> |
| #include <linux/sysfs.h> |
| #include <linux/kernel.h> |
| #include <linux/device.h> |
| #include <linux/fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/slab.h> |
| #include <linux/debugfs.h> |
| #include <linux/platform_device.h> |
| #include <linux/i2c.h> |
| #include <linux/string.h> |
| #include <linux/seq_file.h> |
| #include <linux/semaphore.h> |
| #include <linux/alarmtimer.h> |
| #include <linux/workqueue.h> |
| |
| #include <linux/rt-regmap.h> |
| #define RT_REGMAP_VERSION "1.1.16_G" |
| |
| struct rt_regmap_ops { |
| int (*regmap_block_write)(struct rt_regmap_device *rd, u32 reg, |
| int bytes, const void *data); |
| int (*regmap_block_read)(struct rt_regmap_device *rd, u32 reg, |
| int bytes, void *dest); |
| }; |
| |
| enum { |
| RT_DBG_REG, |
| RT_DBG_DATA, |
| RT_DBG_REGS, |
| RT_DBG_SYNC, |
| RT_DBG_ERROR, |
| RT_DBG_NAME, |
| RT_DBG_BLOCK, |
| RT_DBG_SIZE, |
| RT_DBG_SLAVE_ADDR, |
| RT_DBG_SUPPORT_MODE, |
| RT_DBG_IO_LOG, |
| RT_DBG_CACHE_MODE, |
| RT_DBG_REG_SIZE, |
| RT_DBG_WATCHDOG, |
| RT_DBG_MAX, |
| }; |
| |
| struct reg_index_offset { |
| int index; |
| int offset; |
| }; |
| |
| #ifdef CONFIG_DEBUG_FS |
| struct rt_debug_data { |
| struct reg_index_offset rio; |
| unsigned int reg_addr; |
| unsigned int reg_size; |
| unsigned char part_id; |
| }; |
| |
| struct rt_debug_st { |
| void *info; |
| int id; |
| }; |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| |
| /* rt_regmap_device |
| * |
| * Richtek regmap device. One for each rt_regmap. |
| * |
| */ |
| struct rt_regmap_device { |
| struct rt_regmap_properties props; |
| struct rt_regmap_fops *rops; |
| struct rt_regmap_ops regmap_ops; |
| struct alarm watchdog_alarm; |
| struct delayed_work watchdog_work; |
| struct device dev; |
| void *client; |
| struct semaphore semaphore; |
| struct semaphore write_mode_lock; |
| #ifdef CONFIG_DEBUG_FS |
| struct dentry *rt_den; |
| struct dentry *rt_debug_file[RT_DBG_MAX]; |
| struct rt_debug_st rtdbg_st[RT_DBG_MAX]; |
| struct dentry **rt_reg_file; |
| struct rt_debug_st **reg_st; |
| struct rt_debug_data dbg_data; |
| #endif /* CONFIG_DEBUG_FS */ |
| struct delayed_work rt_work; |
| unsigned char *cache_flag; |
| unsigned char part_size_limit; |
| unsigned char *alloc_data; |
| unsigned char **cache_data; |
| unsigned char *cached; |
| char *err_msg; |
| int slv_addr; |
| |
| int (*rt_block_write[4])(struct rt_regmap_device *rd, |
| const struct rt_register *rm, int size, |
| const struct reg_index_offset *rio, |
| unsigned char *wdata, int *count, int cache_idx); |
| unsigned char cache_inited:1; |
| unsigned char error_occurred:1; |
| unsigned char pending_event:1; |
| }; |
| |
| #ifdef CONFIG_DEBUG_FS |
| struct dentry *rt_regmap_dir; |
| |
| static int get_parameters(char *buf, long int *param1, int num_of_par) |
| { |
| char *token; |
| int base, cnt; |
| |
| token = strsep(&buf, " "); |
| |
| for (cnt = 0; cnt < num_of_par; cnt++) { |
| if (token != NULL) { |
| if ((token[1] == 'x') || (token[1] == 'X')) |
| base = 16; |
| else |
| base = 10; |
| |
| if (kstrtoul(token, base, ¶m1[cnt]) != 0) |
| return -EINVAL; |
| |
| token = strsep(&buf, " "); |
| } else |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int get_datas(const char *buf, const int length, |
| unsigned char *data_buffer, unsigned char data_length) |
| { |
| int i, ptr; |
| long int value; |
| char token[5]; |
| |
| token[0] = '0'; |
| token[1] = 'x'; |
| token[4] = 0; |
| if (buf[0] != '0' || buf[1] != 'x') |
| return -EINVAL; |
| |
| ptr = 2; |
| for (i = 0; (i < data_length) && (ptr + 2 <= length); i++) { |
| token[2] = buf[ptr++]; |
| token[3] = buf[ptr++]; |
| ptr++; |
| if (kstrtoul(token, 16, &value) != 0) |
| return -EINVAL; |
| data_buffer[i] = value; |
| } |
| return 0; |
| } |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| static struct reg_index_offset find_register_index( |
| const struct rt_regmap_device *rd, u32 reg) |
| { |
| const rt_register_map_t *rm = rd->props.rm; |
| int register_num = rd->props.register_num; |
| struct reg_index_offset rio = {0, 0}; |
| int index = 0, i = 0, unit = RT_1BYTE_MODE; |
| |
| for (index = 0; index < register_num; index++) { |
| if (reg == rm[index]->addr) { |
| rio.index = index; |
| rio.offset = 0; |
| break; |
| } else if (reg > rm[index]->addr) { |
| if ((reg - rm[index]->addr) < rm[index]->size) { |
| rio.index = index; |
| while (&rd->props.group[i] != NULL) { |
| if (reg >= rd->props.group[i].start |
| && reg <= rd->props.group[i].end) { |
| unit = |
| rd->props.group[i].mode; |
| break; |
| } |
| i++; |
| unit = RT_1BYTE_MODE; |
| } |
| rio.offset = |
| (reg-rm[index]->addr)*unit; |
| } else |
| rio.offset = rio.index = -1; |
| } |
| } |
| return rio; |
| } |
| |
| static int rt_chip_block_write(struct rt_regmap_device *rd, u32 reg, |
| int bytes, const void *src); |
| |
| /* rt_regmap_cache_sync - sync all cache data to real chip*/ |
| void rt_regmap_cache_sync(struct rt_regmap_device *rd) |
| { |
| int i, rc, num; |
| const rt_register_map_t *rm = rd->props.rm; |
| |
| down(&rd->semaphore); |
| if (!rd->pending_event) |
| goto err_cache_sync; |
| |
| num = rd->props.register_num; |
| for (i = 0; i < num; i++) { |
| if (rd->cache_flag[i] == 1) { |
| rc = rt_chip_block_write(rd, rm[i]->addr, |
| rm[i]->size, rd->cache_data[i]); |
| if (rc < 0) { |
| dev_err(&rd->dev, "rt-regmap sync error\n"); |
| goto err_cache_sync; |
| } |
| *(rd->cache_flag + i) = 0; |
| } |
| } |
| rd->pending_event = 0; |
| dev_info(&rd->dev, "regmap sync successfully\n"); |
| err_cache_sync: |
| up(&rd->semaphore); |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_cache_sync); |
| |
| /* rt_regmap_cache_write_back - write current cache data to chip |
| * @rd: rt_regmap_device pointer. |
| * @reg: register map address |
| */ |
| void rt_regmap_cache_write_back(struct rt_regmap_device *rd, u32 reg) |
| { |
| struct reg_index_offset rio; |
| const rt_register_map_t *rm = rd->props.rm; |
| int rc; |
| |
| rio = find_register_index(rd, reg); |
| if (rio.index < 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of range\n", reg); |
| return; |
| } |
| |
| down(&rd->semaphore); |
| if ((rm[rio.index]->reg_type&RT_REG_TYPE_MASK) != RT_VOLATILE) { |
| rc = rt_chip_block_write(rd, rm[rio.index]->addr, |
| rm[rio.index]->size, |
| rd->cache_data[rio.index]); |
| if (rc < 0) { |
| dev_err(&rd->dev, "rt-regmap sync error\n"); |
| goto err_cache_chip_write; |
| } |
| rd->cache_flag[rio.index] = 0; |
| } |
| dev_info(&rd->dev, "regmap sync successfully\n"); |
| err_cache_chip_write: |
| up(&rd->semaphore); |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_cache_write_back); |
| |
| /* rt_is_reg_volatile - check register map is volatile or not |
| * @rd: rt_regmap_device pointer. |
| * reg: register map address. |
| */ |
| int rt_is_reg_volatile(struct rt_regmap_device *rd, u32 reg) |
| { |
| struct reg_index_offset rio; |
| const rt_register_map_t rm; |
| |
| rio = find_register_index(rd, reg); |
| if (rio.index < 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of range\n", reg); |
| return -EINVAL; |
| } |
| rm = rd->props.rm[rio.index]; |
| |
| return (rm->reg_type&RT_REG_TYPE_MASK) == RT_VOLATILE ? 1 : 0; |
| } |
| EXPORT_SYMBOL_GPL(rt_is_reg_volatile); |
| |
| /* rt_reg_regsize - get register map size for specific register |
| * @rd: rt_regmap_device pointer. |
| * reg: register map address |
| */ |
| int rt_get_regsize(struct rt_regmap_device *rd, u32 reg) |
| { |
| struct reg_index_offset rio; |
| |
| rio = find_register_index(rd, reg); |
| if (rio.index < 0 || rio.offset != 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of map\n", reg); |
| return -EINVAL; |
| } |
| return rd->props.rm[rio.index]->size; |
| } |
| EXPORT_SYMBOL_GPL(rt_get_regsize); |
| |
| static void rt_work_func(struct work_struct *work) |
| { |
| struct rt_regmap_device *rd; |
| |
| pr_info(" %s\n", __func__); |
| rd = container_of(work, struct rt_regmap_device, rt_work.work); |
| rt_regmap_cache_sync(rd); |
| } |
| |
| static int rt_chip_block_write(struct rt_regmap_device *rd, u32 reg, |
| int bytes, const void *src) |
| { |
| int ret; |
| |
| if ((rd->props.rt_regmap_mode & RT_IO_BLK_MODE_MASK) == RT_IO_BLK_ALL || |
| (rd->props.rt_regmap_mode & RT_IO_BLK_MODE_MASK) == RT_IO_BLK_CHIP) |
| return 0; |
| |
| ret = rd->rops->write_device(rd->client, reg, bytes, src); |
| |
| return ret; |
| } |
| |
| static int rt_chip_block_read(struct rt_regmap_device *rd, u32 reg, |
| int bytes, void *dst) |
| { |
| int ret; |
| |
| ret = rd->rops->read_device(rd->client, reg, bytes, dst); |
| return ret; |
| } |
| |
| static int rt_cache_block_write(struct rt_regmap_device *rd, u32 reg, |
| int bytes, const void *data) |
| { |
| int i, j, reg_base = 0, count = 0, ret = 0, size = 0; |
| struct reg_index_offset rio; |
| unsigned char wdata[64]; |
| unsigned char wri_data[128]; |
| unsigned char blk_index; |
| const rt_register_map_t rm; |
| |
| memcpy(wdata, data, bytes); |
| |
| rio = find_register_index(rd, reg); |
| if (rio.index < 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of range\n", reg); |
| return -EINVAL; |
| } |
| |
| reg_base = 0; |
| rm = rd->props.rm[rio.index + reg_base]; |
| while (bytes > 0) { |
| size = ((bytes <= (rm->size-rio.offset)) ? |
| bytes : rm->size-rio.offset); |
| if ((rm->reg_type&RT_REG_TYPE_MASK) == RT_VOLATILE) { |
| ret = rt_chip_block_write(rd, |
| rm->addr+rio.offset, |
| size, |
| &wdata[count]); |
| count += size; |
| } else { |
| blk_index = (rd->props.rt_regmap_mode & |
| RT_IO_BLK_MODE_MASK)>>3; |
| |
| ret = rd->rt_block_write[blk_index] |
| (rd, rm, size, &rio, wdata, |
| &count, rio.index+reg_base); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rd->rt_block_write fail\n"); |
| goto ERR; |
| } |
| } |
| |
| if ((rm->reg_type&RT_REG_TYPE_MASK) != RT_VOLATILE) |
| rd->cache_flag[rio.index+reg_base] = 1; |
| |
| bytes -= size; |
| if (bytes <= 0) |
| goto finished; |
| reg_base++; |
| rm = rd->props.rm[rio.index + reg_base]; |
| if ((rio.index + reg_base) >= rd->props.register_num) { |
| dev_err(&rd->dev, "over regmap size\n"); |
| goto ERR; |
| } |
| } |
| finished: |
| if (rd->props.io_log_en) { |
| j = 0; |
| for (i = 0; i < count; i++) |
| j += snprintf(wri_data + j, sizeof(wri_data) - j, |
| "%02x,", wdata[i]); |
| pr_info("RT_REGMAP [WRITE] reg0x%04x [Data] 0x%s\n", |
| reg, wri_data); |
| } |
| return 0; |
| ERR: |
| return -EIO; |
| } |
| |
| static int rt_asyn_cache_block_write(struct rt_regmap_device *rd, u32 reg, |
| int bytes, const void *data) |
| { |
| int i, j, reg_base, count = 0, ret = 0, size = 0; |
| struct reg_index_offset rio; |
| unsigned char wdata[64]; |
| unsigned char wri_data[128]; |
| unsigned char blk_index; |
| const rt_register_map_t rm; |
| |
| memcpy(wdata, data, bytes); |
| |
| cancel_delayed_work_sync(&rd->rt_work); |
| |
| rio = find_register_index(rd, reg); |
| if (rio.index < 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of range\n", reg); |
| return -EINVAL; |
| } |
| |
| reg_base = 0; |
| rm = rd->props.rm[rio.index + reg_base]; |
| while (bytes > 0) { |
| size = ((bytes <= (rm->size-rio.offset)) ? |
| bytes : rm->size-rio.offset); |
| if ((rm->reg_type&RT_REG_TYPE_MASK) == RT_VOLATILE) { |
| ret = rt_chip_block_write(rd, |
| rm->addr+rio.offset, size, |
| &wdata[count]); |
| count += size; |
| } else { |
| blk_index = (rd->props.rt_regmap_mode & |
| RT_IO_BLK_MODE_MASK)>>3; |
| ret = rd->rt_block_write[blk_index] |
| (rd, rm, size, &rio, wdata, |
| &count, rio.index+reg_base); |
| } |
| if (ret < 0) { |
| dev_err(&rd->dev, "rd->rt_block_write fail\n"); |
| goto ERR; |
| } |
| |
| if ((rm->reg_type&RT_REG_TYPE_MASK) != RT_VOLATILE) { |
| rd->cache_flag[rio.index+reg_base] = 1; |
| rd->pending_event = 1; |
| } |
| |
| bytes -= size; |
| if (bytes <= 0) |
| goto finished; |
| reg_base++; |
| rm = rd->props.rm[rio.index + reg_base]; |
| rio.offset = 0; |
| if ((rio.index + reg_base) >= rd->props.register_num) { |
| dev_err(&rd->dev, "over regmap size\n"); |
| goto ERR; |
| } |
| } |
| finished: |
| if (rd->props.io_log_en) { |
| j = 0; |
| for (i = 0; i < count; i++) |
| j += snprintf(wri_data + j, sizeof(wri_data) - j, |
| "%02x,", wdata[i]); |
| pr_info("RT_REGMAP [WRITE] reg0x%04x [Data] 0x%s\n", |
| reg, wri_data); |
| } |
| |
| schedule_delayed_work(&rd->rt_work, msecs_to_jiffies(1)); |
| return 0; |
| ERR: |
| return -EIO; |
| } |
| |
| static int rt_block_write_blk_all(struct rt_regmap_device *rd, |
| const struct rt_register *rm, int size, |
| const struct reg_index_offset *rio, |
| unsigned char *wdata, int *count, |
| int cache_idx) |
| { |
| int cnt; |
| |
| down(&rd->write_mode_lock); |
| cnt = *count; |
| cnt += size; |
| *count = cnt; |
| up(&rd->write_mode_lock); |
| return 0; |
| } |
| |
| static int rt_block_write_blk_chip(struct rt_regmap_device *rd, |
| const struct rt_register *rm, int size, |
| const struct reg_index_offset *rio, |
| unsigned char *wdata, int *count, |
| int cache_idx) |
| { |
| int i, cnt; |
| |
| down(&rd->write_mode_lock); |
| cnt = *count; |
| for (i = rio->offset; i < rio->offset+size; i++) { |
| if ((rm->reg_type&RT_REG_TYPE_MASK) != RT_VOLATILE) { |
| rd->cache_data[cache_idx][i] = |
| wdata[cnt] & rm->wbit_mask[i]; |
| if (!rd->cached[cache_idx]) |
| rd->cached[cache_idx] = 1; |
| } |
| cnt++; |
| } |
| *count = cnt; |
| up(&rd->write_mode_lock); |
| return 0; |
| } |
| |
| static int rt_block_write_blk_cache(struct rt_regmap_device *rd, |
| const struct rt_register *rm, int size, |
| const struct reg_index_offset *rio, |
| unsigned char *wdata, int *count, |
| int cache_idx) |
| { |
| int ret, cnt; |
| |
| down(&rd->write_mode_lock); |
| cnt = *count; |
| |
| ret = rt_chip_block_write(rd, rm->addr+rio->offset, size, &wdata[cnt]); |
| if (ret < 0) { |
| dev_err(&rd->dev, |
| "rt block write fail at 0x%02x\n", rm->addr + rio->offset); |
| up(&rd->write_mode_lock); |
| return -EIO; |
| } |
| cnt += size; |
| *count = cnt; |
| up(&rd->write_mode_lock); |
| return 0; |
| } |
| |
| static int rt_block_write(struct rt_regmap_device *rd, |
| const struct rt_register *rm, int size, |
| const struct reg_index_offset *rio, |
| unsigned char *wdata, int *count, int cache_idx) |
| { |
| int i, ret = 0, cnt, change = 0; |
| |
| down(&rd->write_mode_lock); |
| cnt = *count; |
| |
| if (!rd->cached[cache_idx]) { |
| for (i = rio->offset; i < size+rio->offset; i++) { |
| if ((rm->reg_type & RT_REG_TYPE_MASK) != RT_VOLATILE) { |
| rd->cache_data[cache_idx][i] = |
| wdata[cnt] & rm->wbit_mask[i]; |
| } |
| cnt++; |
| } |
| rd->cached[cache_idx] = 1; |
| change++; |
| } else { |
| for (i = rio->offset; i < size+rio->offset; i++) { |
| if ((rm->reg_type & RT_REG_TYPE_MASK) != RT_VOLATILE) { |
| if (rm->reg_type&RT_WR_ONCE) { |
| if (rd->cache_data[cache_idx][i] != |
| (wdata[cnt]&rm->wbit_mask[i])) |
| change++; |
| } |
| rd->cache_data[cache_idx][i] = |
| wdata[cnt] & rm->wbit_mask[i]; |
| } |
| cnt++; |
| } |
| } |
| |
| if (!change && (rm->reg_type&RT_WR_ONCE)) |
| goto finish; |
| |
| ret = rt_chip_block_write(rd, |
| rm->addr+rio->offset, size, rd->cache_data[cache_idx]); |
| if (ret < 0) |
| dev_err(&rd->dev, "rt block write fail at 0x%02x\n", |
| rm->addr + rio->offset); |
| finish: |
| *count = cnt; |
| up(&rd->write_mode_lock); |
| return ret; |
| } |
| |
| static int (*rt_block_map[])(struct rt_regmap_device *rd, |
| const struct rt_register *rm, int size, |
| const struct reg_index_offset *rio, |
| unsigned char *wdata, int *count, |
| int cache_idx) = { |
| &rt_block_write, |
| &rt_block_write_blk_all, |
| &rt_block_write_blk_cache, |
| &rt_block_write_blk_chip, |
| }; |
| |
| static int rt_cache_block_read(struct rt_regmap_device *rd, u32 reg, |
| int bytes, void *dest) |
| { |
| int i, ret, count = 0, reg_base = 0, total_bytes = 0; |
| struct reg_index_offset rio; |
| const rt_register_map_t rm; |
| unsigned char data[100]; |
| unsigned char tmp_data[32]; |
| |
| rio = find_register_index(rd, reg); |
| if (rio.index < 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of range\n", reg); |
| return -EINVAL; |
| } |
| |
| rm = rd->props.rm[rio.index]; |
| |
| total_bytes += (rm->size - rio.offset); |
| |
| for (i = rio.index+1; i < rd->props.register_num; i++) |
| total_bytes += rd->props.rm[i]->size; |
| |
| if (bytes > total_bytes) { |
| dev_err(&rd->dev, "out of cache map range\n"); |
| return -EINVAL; |
| } |
| |
| memcpy(data, &rd->cache_data[rio.index][rio.offset], bytes); |
| |
| if ((rm->reg_type&RT_REG_TYPE_MASK) == RT_VOLATILE |
| || rd->cached[rio.index] == 0) { |
| ret = rd->rops->read_device(rd->client, |
| rm->addr, rm->size, tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, |
| "rt_regmap Error at 0x%02x\n", rm->addr); |
| return -EIO; |
| } |
| for (i = rio.offset; i < rm->size; i++) { |
| data[count] = tmp_data[i]; |
| count++; |
| } |
| if (!rd->cached[rio.index]) { |
| memcpy(rd->cache_data[rio.index], &tmp_data, rm->size); |
| rd->cached[rio.index] = 1; |
| } |
| } else |
| count += (rm->size - rio.offset); |
| |
| while (count < bytes) { |
| reg_base++; |
| rm = rd->props.rm[rio.index + reg_base]; |
| if ((rm->reg_type&RT_REG_TYPE_MASK) == RT_VOLATILE || |
| rd->cached[rio.index+reg_base] == 0) { |
| ret = rd->rops->read_device(rd->client, |
| rm->addr, rm->size, &data[count]); |
| if (ret < 0) { |
| dev_err(&rd->dev, |
| "rt_regmap Error at 0x%02x\n", rm->addr); |
| return -EIO; |
| } |
| if (!rd->cached[rio.index+reg_base]) { |
| memcpy(rd->cache_data[rio.index+reg_base], |
| &data[count], rm->size); |
| rd->cached[rio.index+reg_base] = 1; |
| } |
| } |
| count += rm->size; |
| } |
| |
| if (rd->props.io_log_en) |
| pr_info("RT_REGMAP [READ] reg0x%04x\n", reg); |
| |
| memcpy(dest, data, bytes); |
| |
| return 0; |
| } |
| |
| /* rt_regmap_cache_backup - back up all cache register value*/ |
| void rt_regmap_cache_backup(struct rt_regmap_device *rd) |
| { |
| const rt_register_map_t *rm = rd->props.rm; |
| int i; |
| |
| down(&rd->semaphore); |
| for (i = 0; i < rd->props.register_num; i++) |
| if ((rm[i]->reg_type&RT_REG_TYPE_MASK) != RT_VOLATILE) |
| rd->cache_flag[i] = 1; |
| rd->pending_event = 1; |
| up(&rd->semaphore); |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_cache_backup); |
| |
| /* _rt_regmap_reg_write - write data to specific register map |
| * only support 1, 2, 4 bytes regisetr map |
| * @rd: rt_regmap_device pointer. |
| * @rrd: rt_reg_data pointer. |
| */ |
| static int _rt_regmap_reg_write(struct rt_regmap_device *rd, |
| struct rt_reg_data *rrd) |
| { |
| const rt_register_map_t *rm = rd->props.rm; |
| struct reg_index_offset rio; |
| int ret, tmp_data; |
| |
| rio = find_register_index(rd, rrd->reg); |
| if (rio.index < 0 || rio.offset != 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of regmap\n", rrd->reg); |
| return -EINVAL; |
| } |
| |
| down(&rd->semaphore); |
| switch (rm[rio.index]->size) { |
| case 1: |
| ret = rd->regmap_ops.regmap_block_write(rd, |
| rrd->reg, 1, &rrd->rt_data.data_u8); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block write fail\n"); |
| up(&rd->semaphore); |
| return -EIO; |
| } |
| break; |
| case 2: |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| tmp_data = be16_to_cpu(rrd->rt_data.data_u32); |
| ret = rd->regmap_ops.regmap_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block write fail\n"); |
| up(&rd->semaphore); |
| return -EIO; |
| } |
| break; |
| case 3: |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) { |
| tmp_data = be32_to_cpu(rrd->rt_data.data_u32); |
| tmp_data >>= 8; |
| } |
| ret = rd->regmap_ops.regmap_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block write fail\n"); |
| up(&rd->semaphore); |
| return -EIO; |
| } |
| break; |
| case 4: |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| tmp_data = be32_to_cpu(rrd->rt_data.data_u32); |
| ret = rd->regmap_ops.regmap_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block write fail\n"); |
| up(&rd->semaphore); |
| return -EIO; |
| } |
| break; |
| default: |
| dev_err(&rd->dev, |
| "Failed: only support 1~4 bytes regmap write\n"); |
| break; |
| } |
| up(&rd->semaphore); |
| return 0; |
| } |
| |
| int rt_regmap_reg_write(struct rt_regmap_device *rd, |
| struct rt_reg_data *rrd, u32 reg, const u32 data) |
| { |
| rrd->reg = reg; |
| rrd->rt_data.data_u32 = data; |
| return _rt_regmap_reg_write(rd, rrd); |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_reg_write); |
| |
| /* _rt_asyn_regmap_reg_write - asyn write data to specific register map*/ |
| static int _rt_asyn_regmap_reg_write(struct rt_regmap_device *rd, |
| struct rt_reg_data *rrd) |
| { |
| const rt_register_map_t *rm = rd->props.rm; |
| struct reg_index_offset rio; |
| int ret, tmp_data; |
| |
| rio = find_register_index(rd, rrd->reg); |
| if (rio.index < 0 || rio.offset != 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of regmap\n", rrd->reg); |
| return -EINVAL; |
| } |
| |
| down(&rd->semaphore); |
| switch (rm[rio.index]->size) { |
| case 1: |
| ret = rt_asyn_cache_block_write(rd, |
| rrd->reg, 1, &rrd->rt_data.data_u8); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block write fail\n"); |
| ret = -EIO; |
| goto err_regmap_write; |
| } |
| break; |
| case 2: |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| tmp_data = be16_to_cpu(rrd->rt_data.data_u32); |
| ret = rt_asyn_cache_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block write fail\n"); |
| ret = -EIO; |
| goto err_regmap_write; |
| } |
| break; |
| case 3: |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) { |
| tmp_data = be32_to_cpu(rrd->rt_data.data_u32); |
| tmp_data >>= 8; |
| } |
| ret = rt_asyn_cache_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block write fail\n"); |
| ret = -EIO; |
| goto err_regmap_write; |
| } |
| break; |
| case 4: |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| tmp_data = be32_to_cpu(rrd->rt_data.data_u32); |
| ret = rt_asyn_cache_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block write fail\n"); |
| ret = -EIO; |
| goto err_regmap_write; |
| } |
| break; |
| default: |
| dev_err(&rd->dev, |
| "Failed: only support 1~4 bytes regmap write\n"); |
| break; |
| } |
| up(&rd->semaphore); |
| return 0; |
| err_regmap_write: |
| up(&rd->semaphore); |
| return ret; |
| } |
| |
| int rt_asyn_regmap_reg_write(struct rt_regmap_device *rd, |
| struct rt_reg_data *rrd, u32 reg, const u32 data) |
| { |
| rrd->reg = reg; |
| rrd->rt_data.data_u32 = data; |
| return _rt_asyn_regmap_reg_write(rd, rrd); |
| } |
| EXPORT_SYMBOL_GPL(rt_asyn_regmap_reg_write); |
| |
| /* _rt_regmap_update_bits - assign bits specific register map */ |
| static int _rt_regmap_update_bits(struct rt_regmap_device *rd, |
| struct rt_reg_data *rrd) |
| { |
| const rt_register_map_t *rm = rd->props.rm; |
| struct reg_index_offset rio; |
| int ret, new, old; |
| bool change = false; |
| |
| rio = find_register_index(rd, rrd->reg); |
| if (rio.index < 0 || rio.offset != 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of regmap\n", rrd->reg); |
| return -EINVAL; |
| } |
| |
| down(&rd->semaphore); |
| switch (rm[rio.index]->size) { |
| case 1: |
| ret = rd->regmap_ops.regmap_block_read(rd, |
| rrd->reg, 1, &old); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block read fail\n"); |
| goto err_update_bits; |
| } |
| new = (old & ~(rrd->mask)) | (rrd->rt_data.data_u8 & rrd->mask); |
| change = old != new; |
| |
| if (((rm[rio.index]->reg_type & RT_WR_ONCE) && change) || |
| !(rm[rio.index]->reg_type & RT_WR_ONCE)) { |
| ret = rd->regmap_ops.regmap_block_write(rd, |
| rrd->reg, 1, &new); |
| if (ret < 0) { |
| dev_err(&rd->dev, |
| "rt regmap block write fail\n"); |
| goto err_update_bits; |
| } |
| } |
| break; |
| case 2: |
| ret = rd->regmap_ops.regmap_block_read(rd, |
| rrd->reg, rm[rio.index]->size, &old); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block read fail\n"); |
| goto err_update_bits; |
| } |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| old = be16_to_cpu(old); |
| |
| new = (old & ~(rrd->mask)) | |
| (rrd->rt_data.data_u16 & rrd->mask); |
| |
| change = old != new; |
| if (((rm[rio.index]->reg_type & RT_WR_ONCE) && change) || |
| !(rm[rio.index]->reg_type & RT_WR_ONCE)) { |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| new = be16_to_cpu(new); |
| ret = rd->regmap_ops.regmap_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &new); |
| if (ret < 0) { |
| dev_err(&rd->dev, |
| "rt regmap block write fail\n"); |
| goto err_update_bits; |
| } |
| } |
| break; |
| case 3: |
| ret = rd->regmap_ops.regmap_block_read(rd, |
| rrd->reg, rm[rio.index]->size, &old); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block read fail\n"); |
| goto err_update_bits; |
| } |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) { |
| old = be32_to_cpu(old); |
| old >>= 8; |
| } |
| |
| new = (old & ~(rrd->mask)) | |
| (rrd->rt_data.data_u32 & rrd->mask); |
| change = old != new; |
| if (((rm[rio.index]->reg_type & RT_WR_ONCE) && change) || |
| !(rm[rio.index]->reg_type & RT_WR_ONCE)) { |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) { |
| new <<= 8; |
| new = be32_to_cpu(new); |
| } |
| ret = rd->regmap_ops.regmap_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &new); |
| if (ret < 0) { |
| dev_err(&rd->dev, |
| "rt regmap block write fail\n"); |
| goto err_update_bits; |
| } |
| } |
| break; |
| case 4: |
| ret = rd->regmap_ops.regmap_block_read(rd, |
| rrd->reg, rm[rio.index]->size, &old); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block read fail\n"); |
| goto err_update_bits; |
| } |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| old = be32_to_cpu(old); |
| |
| new = (old & ~(rrd->mask)) | |
| (rrd->rt_data.data_u32 & rrd->mask); |
| change = old != new; |
| if (((rm[rio.index]->reg_type & RT_WR_ONCE) && change) || |
| !(rm[rio.index]->reg_type & RT_WR_ONCE)) { |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| new = be32_to_cpu(new); |
| ret = rd->regmap_ops.regmap_block_write(rd, |
| rrd->reg, rm[rio.index]->size, &new); |
| if (ret < 0) { |
| dev_err(&rd->dev, |
| "rt regmap block write fail\n"); |
| goto err_update_bits; |
| } |
| } |
| break; |
| default: |
| dev_err(&rd->dev, |
| "Failed: only support 1~4 bytes regmap write\n"); |
| break; |
| } |
| up(&rd->semaphore); |
| return change; |
| err_update_bits: |
| up(&rd->semaphore); |
| return ret; |
| } |
| |
| int rt_regmap_update_bits(struct rt_regmap_device *rd, |
| struct rt_reg_data *rrd, u32 reg, u32 mask, u32 data) |
| { |
| rrd->reg = reg; |
| rrd->mask = mask; |
| rrd->rt_data.data_u32 = data; |
| return _rt_regmap_update_bits(rd, rrd); |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_update_bits); |
| |
| /* rt_regmap_block_write - block write data to register |
| * @rd: rt_regmap_device pointer |
| * @reg: register address |
| * bytes: leng for write |
| * src: source data |
| */ |
| int rt_regmap_block_write(struct rt_regmap_device *rd, u32 reg, |
| int bytes, const void *src) |
| { |
| int ret; |
| |
| down(&rd->semaphore); |
| ret = rd->regmap_ops.regmap_block_write(rd, reg, bytes, src); |
| up(&rd->semaphore); |
| return ret; |
| }; |
| EXPORT_SYMBOL_GPL(rt_regmap_block_write); |
| |
| /* rt_asyn_regmap_block_write - asyn block write*/ |
| int rt_asyn_regmap_block_write(struct rt_regmap_device *rd, u32 reg, |
| int bytes, const void *src) |
| { |
| int ret; |
| |
| down(&rd->semaphore); |
| ret = rt_asyn_cache_block_write(rd, reg, bytes, src); |
| up(&rd->semaphore); |
| return ret; |
| }; |
| EXPORT_SYMBOL_GPL(rt_asyn_regmap_block_write); |
| |
| /* rt_regmap_block_read - block read data form register |
| * @rd: rt_regmap_device pointer |
| * @reg: register address |
| * @bytes: read length |
| * @dst: destination for read data |
| */ |
| int rt_regmap_block_read(struct rt_regmap_device *rd, u32 reg, |
| int bytes, void *dst) |
| { |
| int ret; |
| |
| down(&rd->semaphore); |
| ret = rd->regmap_ops.regmap_block_read(rd, reg, bytes, dst); |
| up(&rd->semaphore); |
| return ret; |
| }; |
| EXPORT_SYMBOL_GPL(rt_regmap_block_read); |
| |
| /* _rt_regmap_reg_read - register read for specific register map |
| * only support 1, 2, 4 bytes register map. |
| * @rd: rt_regmap_device pointer. |
| * @rrd: rt_reg_data pointer. |
| */ |
| static int _rt_regmap_reg_read( |
| struct rt_regmap_device *rd, struct rt_reg_data *rrd) |
| { |
| const rt_register_map_t *rm = rd->props.rm; |
| struct reg_index_offset rio; |
| int ret, tmp_data = 0; |
| |
| rio = find_register_index(rd, rrd->reg); |
| if (rio.index < 0 || rio.offset != 0) { |
| dev_err(&rd->dev, "reg 0x%02x is out of regmap\n", rrd->reg); |
| return -EINVAL; |
| } |
| |
| down(&rd->semaphore); |
| switch (rm[rio.index]->size) { |
| case 1: |
| ret = rd->regmap_ops.regmap_block_read(rd, |
| rrd->reg, 1, &rrd->rt_data.data_u8); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block read fail\n"); |
| goto err_regmap_reg_read; |
| } |
| break; |
| case 2: |
| ret = rd->regmap_ops.regmap_block_read(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block read fail\n"); |
| goto err_regmap_reg_read; |
| } |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| tmp_data = be16_to_cpu(tmp_data); |
| rrd->rt_data.data_u16 = tmp_data; |
| break; |
| case 3: |
| ret = rd->regmap_ops.regmap_block_read(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block read fail\n"); |
| goto err_regmap_reg_read; |
| } |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| tmp_data = be32_to_cpu(tmp_data); |
| rrd->rt_data.data_u32 = (tmp_data >> 8); |
| break; |
| case 4: |
| ret = rd->regmap_ops.regmap_block_read(rd, |
| rrd->reg, rm[rio.index]->size, &tmp_data); |
| if (ret < 0) { |
| dev_err(&rd->dev, "rt regmap block read fail\n"); |
| goto err_regmap_reg_read; |
| } |
| if (rd->props.rt_format == RT_LITTLE_ENDIAN) |
| tmp_data = be32_to_cpu(tmp_data); |
| rrd->rt_data.data_u32 = tmp_data; |
| break; |
| default: |
| dev_err(&rd->dev, |
| "Failed: only support 1~4 bytes regmap read\n"); |
| break; |
| } |
| up(&rd->semaphore); |
| return 0; |
| err_regmap_reg_read: |
| up(&rd->semaphore); |
| return ret; |
| } |
| |
| int rt_regmap_reg_read(struct rt_regmap_device *rd, |
| struct rt_reg_data *rrd, u32 reg) |
| { |
| rrd->reg = reg; |
| return _rt_regmap_reg_read(rd, rrd); |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_reg_read); |
| |
| void rt_cache_getlasterror(struct rt_regmap_device *rd, char *buf) |
| { |
| down(&rd->semaphore); |
| snprintf(buf, PAGE_SIZE, "%s\n", rd->err_msg); |
| up(&rd->semaphore); |
| } |
| EXPORT_SYMBOL_GPL(rt_cache_getlasterror); |
| |
| void rt_cache_clrlasterror(struct rt_regmap_device *rd) |
| { |
| down(&rd->semaphore); |
| rd->error_occurred = 0; |
| snprintf(rd->err_msg, PAGE_SIZE, "%s", "No Error"); |
| up(&rd->semaphore); |
| } |
| EXPORT_SYMBOL_GPL(rt_cache_clrlasterror); |
| |
| /* initialize cache data from rt_register */ |
| int rt_regmap_cache_init(struct rt_regmap_device *rd) |
| { |
| int i, j, bytes_num = 0, count = 0; |
| const rt_register_map_t *rm = rd->props.rm; |
| |
| dev_info(&rd->dev, "rt register cache data init\n"); |
| |
| down(&rd->semaphore); |
| rd->cache_flag = devm_kzalloc(&rd->dev, |
| rd->props.register_num * sizeof(unsigned char), GFP_KERNEL); |
| rd->cached = devm_kzalloc(&rd->dev, |
| rd->props.register_num * sizeof(unsigned char), GFP_KERNEL); |
| rd->cache_data = devm_kzalloc(&rd->dev, |
| rd->props.register_num * sizeof(unsigned char *), GFP_KERNEL); |
| |
| if (rd->props.group == NULL) { |
| rd->props.group = devm_kzalloc(&rd->dev, |
| sizeof(*rd->props.group), GFP_KERNEL); |
| rd->props.group[0].start = 0x00; |
| rd->props.group[0].end = 0xffff; |
| rd->props.group[0].mode = RT_1BYTE_MODE; |
| } |
| |
| for (i = 0; i < rd->props.register_num; i++) |
| bytes_num += rm[i]->size; |
| |
| rd->alloc_data = devm_kzalloc(&rd->dev, |
| bytes_num * sizeof(unsigned char), GFP_KERNEL); |
| |
| /* reload cache data from real chip */ |
| for (i = 0; i < rd->props.register_num; i++) { |
| rd->cache_data[i] = rd->alloc_data + count; |
| count += rm[i]->size; |
| memset(rd->cache_data[i], 0x00, rm[i]->size); |
| rd->cache_flag[i] = rd->cached[i] = 0; |
| } |
| |
| /* set 0xff writeable mask for NORMAL and RESERVE type */ |
| for (i = 0; i < rd->props.register_num; i++) { |
| if ((rm[i]->reg_type & RT_REG_TYPE_MASK) == RT_NORMAL || |
| (rm[i]->reg_type & RT_REG_TYPE_MASK) == RT_RESERVE) { |
| for (j = 0; j < rm[i]->size; j++) |
| rm[i]->wbit_mask[j] = 0xff; |
| } |
| } |
| |
| rd->cache_inited = 1; |
| dev_info(&rd->dev, "cache cata init successfully\n"); |
| up(&rd->semaphore); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_cache_init); |
| |
| /* rt_regmap_cache_reload - reload cache valuew from real chip*/ |
| int rt_regmap_cache_reload(struct rt_regmap_device *rd) |
| { |
| int i; |
| |
| down(&rd->semaphore); |
| for (i = 0; i < rd->props.register_num; i++) |
| rd->cached[i] = rd->cache_flag[i] = 0; |
| rd->pending_event = 0; |
| up(&rd->semaphore); |
| dev_info(&rd->dev, "cache data reload\n"); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_cache_reload); |
| |
| /* rt_regmap_add_debubfs - add user own debugfs node |
| * @rd: rt_regmap_devcie pointer. |
| * @name: a pointer to a string containing the name of the file to create. |
| * @mode: the permission that the file should have. |
| * @data: a pointer to something that the caller will want to get to later on. |
| * The inode.i_private pointer will point this value on the open() call. |
| * @fops: a pointer to a struct file_operations that should be used for |
| * this file. |
| */ |
| int rt_regmap_add_debugfs(struct rt_regmap_device *rd, const char *name, |
| umode_t mode, void *data, |
| const struct file_operations *fops) |
| { |
| #ifdef CONFIG_DEBUG_FS |
| struct dentry *den; |
| |
| den = debugfs_create_file(name, mode, rd->rt_den, data, fops); |
| if (!den) |
| return -EINVAL; |
| #endif /*CONFIG_DEBUG_FS*/ |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_add_debugfs); |
| |
| /* release cache data*/ |
| static void rt_regmap_cache_release(struct rt_regmap_device *rd) |
| { |
| int i; |
| |
| dev_info(&rd->dev, "cache data release\n"); |
| for (i = 0; i < rd->props.register_num; i++) |
| rd->cache_data[i] = NULL; |
| devm_kfree(&rd->dev, rd->alloc_data); |
| if (rd->cache_flag) |
| devm_kfree(&rd->dev, rd->cache_flag); |
| if (rd->cached) |
| devm_kfree(&rd->dev, rd->cached); |
| rd->cache_inited = 0; |
| } |
| |
| static void rt_regmap_set_cache_mode( |
| struct rt_regmap_device *rd, unsigned char mode) |
| { |
| unsigned char mode_mask; |
| |
| mode_mask = mode & RT_CACHE_MODE_MASK; |
| dev_info(&rd->dev, "%s mode = %d\n", __func__, mode_mask>>1); |
| |
| down(&rd->write_mode_lock); |
| if (mode_mask == RT_CACHE_WR_THROUGH) { |
| rt_regmap_cache_reload(rd); |
| rd->regmap_ops.regmap_block_write = |
| rt_cache_block_write; |
| rd->regmap_ops.regmap_block_read = &rt_cache_block_read; |
| } else if (mode_mask == RT_CACHE_WR_BACK) { |
| rt_regmap_cache_reload(rd); |
| rd->regmap_ops.regmap_block_write = |
| rt_asyn_cache_block_write; |
| rd->regmap_ops.regmap_block_read = &rt_cache_block_read; |
| } else if (mode_mask == RT_CACHE_DISABLE) { |
| rd->regmap_ops.regmap_block_write = |
| rt_chip_block_write; |
| rd->regmap_ops.regmap_block_read = rt_chip_block_read; |
| } else { |
| dev_err(&rd->dev, "%s out of cache mode index\n", __func__); |
| goto mode_err; |
| } |
| |
| rd->props.rt_regmap_mode &= ~RT_CACHE_MODE_MASK; |
| rd->props.rt_regmap_mode |= mode_mask; |
| mode_err: |
| up(&rd->write_mode_lock); |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| static void rt_show_regs(struct rt_regmap_device *rd, struct seq_file *seq_file) |
| { |
| int i = 0, k = 0, ret, count = 0; |
| unsigned char *regval; |
| const rt_register_map_t *rm = rd->props.rm; |
| |
| if (rd->props.map_byte_num == 0) |
| regval = devm_kzalloc(&rd->dev, sizeof(char)*512, GFP_KERNEL); |
| else |
| regval = devm_kzalloc(&rd->dev, |
| rd->props.map_byte_num*sizeof(char), GFP_KERNEL); |
| |
| down(&rd->semaphore); |
| for (i = 0; i < rd->props.register_num; i++) { |
| ret = rd->regmap_ops.regmap_block_read(rd, rm[i]->addr, |
| rm[i]->size, ®val[count]); |
| count += rm[i]->size; |
| if (ret < 0) { |
| dev_err(&rd->dev, "regmap block read fail\n"); |
| if (rd->error_occurred) { |
| snprintf(rd->err_msg + strlen(rd->err_msg), |
| PAGE_SIZE, "Error block read fail at 0x%02x\n", |
| rm[i]->addr); |
| } else { |
| snprintf(rd->err_msg, PAGE_SIZE, |
| "Error block read fail at 0x%02x\n", |
| rm[i]->addr); |
| rd->error_occurred = 1; |
| } |
| goto err_show_regs; |
| } |
| |
| if ((rm[i]->reg_type & RT_REG_TYPE_MASK) != RT_RESERVE) { |
| seq_printf(seq_file, "reg0x%02x:0x", rm[i]->addr); |
| for (k = 0; k < rm[i]->size; k++) |
| seq_printf(seq_file, "%02x,", |
| regval[count - rm[i]->size + k]); |
| seq_puts(seq_file, "\n"); |
| } else |
| seq_printf(seq_file, |
| "reg0x%02x:reserve\n", rm[i]->addr); |
| } |
| err_show_regs: |
| devm_kfree(&rd->dev, regval); |
| up(&rd->semaphore); |
| } |
| |
| static int general_read(struct seq_file *seq_file, void *_data) |
| { |
| struct rt_debug_st *st = (struct rt_debug_st *)seq_file->private; |
| struct rt_regmap_device *rd = st->info; |
| const rt_register_map_t rm; |
| unsigned char *reg_data; |
| unsigned char data; |
| int i = 0, rc = 0, size = 0; |
| |
| switch (st->id) { |
| case RT_DBG_REG: |
| seq_printf(seq_file, "0x%04x\n", rd->dbg_data.reg_addr); |
| break; |
| case RT_DBG_DATA: |
| if (rd->dbg_data.reg_size == 0) |
| rd->dbg_data.reg_size = 1; |
| |
| reg_data = kcalloc(rd->dbg_data.reg_size, sizeof(unsigned char), |
| GFP_KERNEL); |
| memset(reg_data, 0, |
| sizeof(unsigned char)*rd->dbg_data.reg_size); |
| |
| size = rd->dbg_data.reg_size; |
| |
| if (rd->dbg_data.rio.index == -1) { |
| down(&rd->semaphore); |
| rc = rt_chip_block_read(rd, rd->dbg_data.reg_addr, |
| size, reg_data); |
| up(&rd->semaphore); |
| if (rc < 0) { |
| seq_puts(seq_file, "invalid read\n"); |
| break; |
| } |
| goto hiden_read; |
| } |
| |
| rm = rd->props.rm[rd->dbg_data.rio.index]; |
| down(&rd->semaphore); |
| rc = rd->regmap_ops.regmap_block_read(rd, |
| rd->dbg_data.reg_addr, size, reg_data); |
| up(&rd->semaphore); |
| if (rc < 0) { |
| seq_puts(seq_file, "invalid read\n"); |
| break; |
| } |
| hiden_read: |
| if (®_data[i] != NULL) { |
| seq_puts(seq_file, "0x"); |
| for (i = 0; i < size; i++) |
| seq_printf(seq_file, "%02x,", reg_data[i]); |
| seq_puts(seq_file, "\n"); |
| } |
| break; |
| case RT_DBG_ERROR: |
| seq_puts(seq_file, "======== Error Message ========\n"); |
| if (!rd->error_occurred) |
| seq_puts(seq_file, "No Error\n"); |
| else |
| seq_printf(seq_file, rd->err_msg); |
| break; |
| case RT_DBG_REGS: |
| rt_show_regs(rd, seq_file); |
| break; |
| case RT_DBG_NAME: |
| seq_printf(seq_file, "%s\n", rd->props.aliases); |
| break; |
| case RT_DBG_SIZE: |
| seq_printf(seq_file, "%d\n", rd->dbg_data.reg_size); |
| break; |
| case RT_DBG_BLOCK: |
| data = rd->props.rt_regmap_mode & RT_IO_BLK_MODE_MASK; |
| if (data == RT_IO_PASS_THROUGH) |
| seq_puts(seq_file, "0 => IO_PASS_THROUGH\n"); |
| else if (data == RT_IO_BLK_ALL) |
| seq_puts(seq_file, "1 => IO_BLK_ALL\n"); |
| else if (data == RT_IO_BLK_CACHE) |
| seq_puts(seq_file, "2 => IO_BLK_CACHE\n"); |
| else if (data == RT_IO_BLK_CHIP) |
| seq_puts(seq_file, "3 => IO_BLK_CHIP\n"); |
| break; |
| case RT_DBG_SLAVE_ADDR: |
| seq_printf(seq_file, "0x%02x\n", rd->slv_addr); |
| break; |
| case RT_DBG_SUPPORT_MODE: |
| seq_puts(seq_file, " == BLOCK MODE ==\n"); |
| seq_puts(seq_file, "0 => IO_PASS_THROUGH\n"); |
| seq_puts(seq_file, "1 => IO_BLK_ALL\n"); |
| seq_puts(seq_file, "2 => IO_BLK_CHIP\n"); |
| seq_puts(seq_file, "3 => IO_BLK_CACHE\n"); |
| seq_puts(seq_file, " == CACHE MODE ==\n"); |
| seq_puts(seq_file, "0 => CACHE_WR_THROUGH\n"); |
| seq_puts(seq_file, "1 => CACHE_WR_BACK\n"); |
| seq_puts(seq_file, "2 => CACHE_DISABLE\n"); |
| |
| break; |
| case RT_DBG_IO_LOG: |
| seq_printf(seq_file, "%d\n", rd->props.io_log_en); |
| break; |
| case RT_DBG_CACHE_MODE: |
| data = rd->props.rt_regmap_mode & RT_CACHE_MODE_MASK; |
| if (data == RT_CACHE_WR_THROUGH) |
| seq_printf(seq_file, "%s", |
| "0 => Cache Write Through\n"); |
| else if (data == RT_CACHE_WR_BACK) |
| seq_printf(seq_file, "%s", "1 => Cache Write Back\n"); |
| else if (data == RT_CACHE_DISABLE) |
| seq_printf(seq_file, "%s", "2 => Cache Disable\n"); |
| break; |
| case RT_DBG_REG_SIZE: |
| size = rt_get_regsize(rd, rd->dbg_data.reg_addr); |
| if (size < 0) |
| seq_printf(seq_file, "%d\n", 0); |
| else |
| seq_printf(seq_file, "%d\n", size); |
| break; |
| case RT_DBG_WATCHDOG: |
| seq_printf(seq_file, "watchdog = %d\n", rd->props.watchdog); |
| break; |
| } |
| return 0; |
| } |
| |
| static int general_open(struct inode *inode, struct file *file) |
| { |
| if (file->f_mode & FMODE_READ) |
| return single_open(file, general_read, inode->i_private); |
| file->private_data = inode->i_private; |
| return 0; |
| } |
| |
| |
| #define RT_WATCHDOG_TIMEOUT (40000000000) |
| |
| static ssize_t general_write(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| struct rt_debug_st *st = file->private_data; |
| struct rt_regmap_device *rd = st->info; |
| struct reg_index_offset rio; |
| long int param[5]; |
| unsigned char *reg_data; |
| int rc, size = 0; |
| char lbuf[128]; |
| ssize_t res; |
| |
| pr_info("%s @ %p\n", __func__, ubuf); |
| |
| res = simple_write_to_buffer(lbuf, sizeof(lbuf) - 1, ppos, ubuf, count); |
| if (res <= 0) |
| return -EFAULT; |
| |
| lbuf[count] = '\0'; |
| |
| switch (st->id) { |
| case RT_DBG_REG: |
| rc = get_parameters(lbuf, param, 1); |
| rio = find_register_index(rd, param[0]); |
| down(&rd->semaphore); |
| if (rio.index < 0) { |
| pr_info("this is an invalid or hiden register\n"); |
| rd->dbg_data.reg_addr = (unsigned int)param[0]; |
| rd->dbg_data.rio.index = -1; |
| } else { |
| rd->dbg_data.rio = rio; |
| rd->dbg_data.reg_addr = (unsigned int)param[0]; |
| } |
| up(&rd->semaphore); |
| break; |
| case RT_DBG_DATA: |
| if (rd->dbg_data.reg_size == 0) |
| rd->dbg_data.reg_size = 1; |
| reg_data = kcalloc(rd->dbg_data.reg_size, sizeof(unsigned char), |
| GFP_KERNEL); |
| memset(reg_data, 0, |
| sizeof(unsigned char)*rd->dbg_data.reg_size); |
| if (rd->dbg_data.rio.index == -1) { |
| size = rd->dbg_data.reg_size; |
| if ((size - 1)*3 + 5 != count) { |
| dev_err(&rd->dev, "wrong input length\n"); |
| if (rd->error_occurred) { |
| snprintf(rd->err_msg + |
| strlen(rd->err_msg), PAGE_SIZE, |
| "Error, wrong input length\n"); |
| } else { |
| snprintf(rd->err_msg, PAGE_SIZE, |
| "Error, wrong input length\n"); |
| rd->error_occurred = 1; |
| } |
| return -EINVAL; |
| } |
| |
| rc = get_datas(lbuf, count, reg_data, size); |
| if (rc < 0) { |
| dev_err(&rd->dev, "get datas fail\n"); |
| if (rd->error_occurred) { |
| snprintf(rd->err_msg + |
| strlen(rd->err_msg), PAGE_SIZE, |
| "Error, get datas fail\n"); |
| } else { |
| snprintf(rd->err_msg, PAGE_SIZE, |
| "Error, get datas fail\n"); |
| rd->error_occurred = 1; |
| } |
| return -EINVAL; |
| } |
| down(&rd->semaphore); |
| rc = rt_chip_block_write(rd, rd->dbg_data.reg_addr, |
| size, reg_data); |
| up(&rd->semaphore); |
| if (rc < 0) { |
| dev_err(&rd->dev, "chip block write fail\n"); |
| if (rd->error_occurred) { |
| snprintf(rd->err_msg + |
| strlen(rd->err_msg), PAGE_SIZE, |
| "Error chip block write fail at 0x%02x\n", |
| rd->dbg_data.reg_addr); |
| } else { |
| snprintf(rd->err_msg, PAGE_SIZE, |
| "Error chip block write fail at 0x%02x\n", |
| rd->dbg_data.reg_addr); |
| rd->error_occurred = 1; |
| } |
| return -EIO; |
| } |
| break; |
| } |
| |
| size = rd->dbg_data.reg_size; |
| |
| if ((size - 1)*3 + 5 != count) { |
| dev_err(&rd->dev, "wrong input length\n"); |
| if (rd->error_occurred) { |
| snprintf(rd->err_msg + strlen(rd->err_msg), |
| PAGE_SIZE, |
| "Error, wrong input length\n"); |
| } else { |
| snprintf(rd->err_msg, PAGE_SIZE, |
| "Error, wrong input length\n"); |
| rd->error_occurred = 1; |
| } |
| return -EINVAL; |
| } |
| |
| rc = get_datas(lbuf, count, reg_data, size); |
| if (rc < 0) { |
| dev_err(&rd->dev, "get datas fail\n"); |
| if (rd->error_occurred) { |
| snprintf(rd->err_msg + strlen(rd->err_msg), |
| PAGE_SIZE, "Error, get datas fail\n"); |
| } else { |
| snprintf(rd->err_msg, PAGE_SIZE, |
| "Error, get datas fail\n"); |
| rd->error_occurred = 1; |
| } |
| return -EINVAL; |
| } |
| |
| down(&rd->semaphore); |
| rc = rd->regmap_ops.regmap_block_write(rd, |
| rd->dbg_data.reg_addr, size, reg_data); |
| up(&rd->semaphore); |
| if (rc < 0) { |
| dev_err(&rd->dev, "regmap block write fail\n"); |
| if (rd->error_occurred) { |
| snprintf(rd->err_msg + strlen(rd->err_msg), |
| PAGE_SIZE, |
| "Error regmap block write fail at 0x%02x\n", |
| rd->dbg_data.reg_addr); |
| } else { |
| snprintf(rd->err_msg, PAGE_SIZE, |
| "Error regmap block write fail at 0x%02x\n", |
| rd->dbg_data.reg_addr); |
| rd->error_occurred = 1; |
| } |
| return -EIO; |
| } |
| |
| break; |
| case RT_DBG_SYNC: |
| rc = get_parameters(lbuf, param, 1); |
| if (param[0]) |
| rt_regmap_cache_sync(rd); |
| break; |
| case RT_DBG_ERROR: |
| rc = get_parameters(lbuf, param, 1); |
| if (param[0]) |
| rt_cache_clrlasterror(rd); |
| break; |
| case RT_DBG_SIZE: |
| rc = get_parameters(lbuf, param, 1); |
| if (param[0] >= 0) { |
| down(&rd->semaphore); |
| rd->dbg_data.reg_size = param[0]; |
| up(&rd->semaphore); |
| } else { |
| if (rd->error_occurred) { |
| snprintf(rd->err_msg + strlen(rd->err_msg), |
| PAGE_SIZE, "Error, size must > 0\n"); |
| } else { |
| snprintf(rd->err_msg, PAGE_SIZE, |
| "Error, size must > 0\n"); |
| rd->error_occurred = 1; |
| } |
| return -EINVAL; |
| } |
| break; |
| case RT_DBG_BLOCK: |
| rc = get_parameters(lbuf, param, 1); |
| if (param[0] < 0) |
| param[0] = 0; |
| else if (param[0] > 3) |
| param[0] = 3; |
| |
| param[0] <<= 3; |
| |
| down(&rd->semaphore); |
| rd->props.rt_regmap_mode &= ~RT_IO_BLK_MODE_MASK; |
| rd->props.rt_regmap_mode |= param[0]; |
| up(&rd->semaphore); |
| if (param[0] == RT_IO_PASS_THROUGH) |
| rt_regmap_cache_sync(rd); |
| break; |
| case RT_DBG_IO_LOG: |
| rc = get_parameters(lbuf, param, 1); |
| down(&rd->semaphore); |
| if (!param[0]) |
| rd->props.io_log_en = 0; |
| else |
| rd->props.io_log_en = 1; |
| up(&rd->semaphore); |
| break; |
| case RT_DBG_CACHE_MODE: |
| rc = get_parameters(lbuf, param, 1); |
| if (param[0] < 0) |
| param[0] = 0; |
| else if (param[0] > 2) |
| param[0] = 2; |
| param[0] <<= 1; |
| rt_regmap_set_cache_mode(rd, param[0]); |
| break; |
| case RT_DBG_WATCHDOG: |
| rc = get_parameters(lbuf, param, 1); |
| if (param[0]) { |
| dev_info(&rd->dev, "enable watchdog\n"); |
| if (rd->props.watchdog) |
| alarm_cancel(&rd->watchdog_alarm); |
| else |
| rd->props.watchdog = 1; |
| alarm_start_relative(&rd->watchdog_alarm, |
| ns_to_ktime(RT_WATCHDOG_TIMEOUT)); |
| } else { |
| dev_info(&rd->dev, "disable watchdog\n"); |
| if (rd->props.watchdog) { |
| rd->props.watchdog = 0; |
| alarm_cancel(&rd->watchdog_alarm); |
| rt_regmap_set_cache_mode(rd, |
| rd->props.cache_mode_ori); |
| } |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return count; |
| } |
| |
| static int general_release(struct inode *inode, struct file *file) |
| { |
| if (file->f_mode & FMODE_READ) |
| return single_release(inode, file); |
| return 0; |
| } |
| |
| static const struct file_operations general_ops = { |
| .owner = THIS_MODULE, |
| .open = general_open, |
| .write = general_write, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = general_release, |
| }; |
| |
| #define RT_CREATE_GENERAL_FILE(_id, _name, _mode) \ |
| { \ |
| rd->rtdbg_st[_id].info = rd; \ |
| rd->rtdbg_st[_id].id = _id; \ |
| rd->rt_debug_file[_id] = debugfs_create_file(_name, _mode, dir, \ |
| (void *)&rd->rtdbg_st[_id], &general_ops); \ |
| } |
| |
| /* create general debugfs node */ |
| static void rt_create_general_debug(struct rt_regmap_device *rd, |
| struct dentry *dir) |
| { |
| RT_CREATE_GENERAL_FILE(RT_DBG_REG, "reg_addr", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_DATA, "data", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_REGS, "regs", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_SYNC, "sync", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_ERROR, "Error", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_NAME, "name", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_BLOCK, "block", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_SIZE, "size", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_SLAVE_ADDR, |
| "slave_addr", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_SUPPORT_MODE, |
| "support_mode", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_IO_LOG, "io_log", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_CACHE_MODE, |
| "cache_mode", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_REG_SIZE, "reg_size", S_IFREG|S_IRUGO); |
| RT_CREATE_GENERAL_FILE(RT_DBG_WATCHDOG, "watchdog", S_IFREG|S_IRUGO); |
| } |
| |
| static int eachreg_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| return 0; |
| } |
| |
| static ssize_t eachreg_write(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| struct rt_debug_st *st = file->private_data; |
| struct rt_regmap_device *rd = st->info; |
| const rt_register_map_t rm = rd->props.rm[st->id]; |
| int rc; |
| unsigned char *pars; |
| char lbuf[128]; |
| ssize_t res; |
| |
| if ((rm->size - 1)*3 + 5 != count) { |
| dev_err(&rd->dev, "wrong input length\n"); |
| return -EINVAL; |
| } |
| |
| pr_info("%s @ %p\n", __func__, ubuf); |
| |
| res = simple_write_to_buffer(lbuf, sizeof(lbuf) - 1, ppos, ubuf, count); |
| if (res <= 0) |
| return -EFAULT; |
| |
| lbuf[count] = '\0'; |
| |
| pars = kcalloc(rm->size, sizeof(unsigned char), GFP_KERNEL); |
| if (!pars) |
| return -ENOMEM; |
| |
| rc = get_datas(lbuf, count, pars, rm->size); |
| |
| if (rc < 0) { |
| kfree(pars); |
| dev_err(&rd->dev, "get datas fail\n"); |
| return -EINVAL; |
| } |
| |
| down(&rd->semaphore); |
| rc = rd->regmap_ops.regmap_block_write(rd, rm->addr, |
| rm->size, &pars[0]); |
| up(&rd->semaphore); |
| if (rc < 0) { |
| kfree(pars); |
| dev_err(&rd->dev, "regmap block read fail\n"); |
| return -EIO; |
| } |
| |
| kfree(pars); |
| return count; |
| } |
| |
| static ssize_t eachreg_read(struct file *file, char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| struct rt_debug_st *st = file->private_data; |
| struct rt_regmap_device *rd = st->info; |
| ssize_t retval = 0; |
| char *lbuf; |
| unsigned char *regval; |
| const rt_register_map_t rm = rd->props.rm[st->id]; |
| int i, j = 0, rc; |
| |
| if (rd->props.max_byte_size == 0) { |
| regval = devm_kzalloc(&rd->dev, |
| sizeof(unsigned char)*32, GFP_KERNEL); |
| lbuf = devm_kzalloc(&rd->dev, sizeof(char)*200, GFP_KERNEL); |
| } else { |
| regval = devm_kzalloc(&rd->dev, rd->props.max_byte_size * |
| sizeof(unsigned char), GFP_KERNEL); |
| lbuf = devm_kzalloc(&rd->dev, |
| rd->props.max_byte_size*3+2, GFP_KERNEL); |
| } |
| |
| lbuf[0] = '\0'; |
| |
| down(&rd->semaphore); |
| rc = rd->regmap_ops.regmap_block_read(rd, rm->addr, rm->size, regval); |
| up(&rd->semaphore); |
| if (rc < 0) { |
| dev_err(&rd->dev, "regmap block read fail\n"); |
| devm_kfree(&rd->dev, regval); |
| devm_kfree(&rd->dev, lbuf); |
| return -EIO; |
| } |
| |
| j += snprintf(lbuf + j, PAGE_SIZE, "reg0x%02x:0x", rm->addr); |
| for (i = 0; i < rm->size; i++) |
| j += snprintf(lbuf + j, |
| PAGE_SIZE-strlen(lbuf), "%02x,", regval[i]); |
| j += snprintf(lbuf + j, PAGE_SIZE-strlen(lbuf), "\n"); |
| |
| retval = simple_read_from_buffer(ubuf, count, ppos, lbuf, strlen(lbuf)); |
| devm_kfree(&rd->dev, regval); |
| devm_kfree(&rd->dev, lbuf); |
| return retval; |
| } |
| |
| static const struct file_operations eachreg_ops = { |
| .open = eachreg_open, |
| .read = eachreg_read, |
| .write = eachreg_write, |
| }; |
| |
| /* create every register node at debugfs */ |
| static void rt_create_every_debug(struct rt_regmap_device *rd, |
| struct dentry *dir) |
| { |
| int i; |
| char buf[10]; |
| |
| rd->rt_reg_file = devm_kzalloc(&rd->dev, |
| rd->props.register_num*sizeof(struct dentry *), GFP_KERNEL); |
| rd->reg_st = devm_kzalloc(&rd->dev, |
| rd->props.register_num*sizeof(struct rt_debug_st *), |
| GFP_KERNEL); |
| for (i = 0; i < rd->props.register_num; i++) { |
| snprintf(buf, 10, "reg0x%02x", (rd->props.rm[i])->addr); |
| rd->rt_reg_file[i] = devm_kzalloc(&rd->dev, |
| sizeof(*rd->rt_reg_file[i]), |
| GFP_KERNEL); |
| rd->reg_st[i] = |
| devm_kzalloc(&rd->dev, sizeof(*rd->reg_st[i]), GFP_KERNEL); |
| |
| rd->reg_st[i]->info = rd; |
| rd->reg_st[i]->id = i; |
| rd->rt_reg_file[i] = debugfs_create_file(buf, |
| S_IFREG | S_IRUGO, dir, |
| (void *)rd->reg_st[i], |
| &eachreg_ops); |
| } |
| } |
| |
| static void rt_release_every_debug(struct rt_regmap_device *rd) |
| { |
| int num = rd->props.register_num; |
| int i; |
| |
| for (i = 0; i < num; i++) { |
| devm_kfree(&rd->dev, rd->rt_reg_file[i]); |
| devm_kfree(&rd->dev, rd->reg_st[i]); |
| } |
| devm_kfree(&rd->dev, rd->rt_reg_file); |
| devm_kfree(&rd->dev, rd->reg_st); |
| } |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| static void rt_regmap_device_release(struct device *dev) |
| { |
| struct rt_regmap_device *rd = to_rt_regmap_device(dev); |
| |
| devm_kfree(dev, rd); |
| } |
| |
| /* check the rt_register format is correct */ |
| static int rt_regmap_check(struct rt_regmap_device *rd) |
| { |
| const rt_register_map_t *rm = rd->props.rm; |
| int num = rd->props.register_num; |
| int i; |
| |
| /* check name property */ |
| if (!rd->props.name) { |
| pr_info("there is no node name for rt-regmap\n"); |
| return -EINVAL; |
| } |
| |
| if (!(rd->props.rt_regmap_mode & RT_BYTE_MODE_MASK)) |
| goto single_byte; |
| |
| for (i = 0; i < num; i++) { |
| /* check byte size, 1 byte ~ 24 bytes is valid */ |
| if (rm[i]->size < 1 || rm[i]->size > 24) { |
| pr_info("rt register size error at reg 0x%02x\n", |
| rm[i]->addr); |
| return -EINVAL; |
| } |
| } |
| |
| for (i = 0; i < num - 1; i++) { |
| /* check register sequence */ |
| if (rm[i]->addr >= rm[i + 1]->addr) { |
| pr_info("sequence format error at reg 0x%02x\n", |
| rm[i]->addr); |
| return -EINVAL; |
| } |
| } |
| |
| single_byte: |
| /* no default reg_addr and reister_map first addr is not 0x00 */ |
| #ifdef CONFIG_DEBUG_FS |
| if (!rd->dbg_data.reg_addr && rm[0]->addr) { |
| rd->dbg_data.reg_addr = rm[0]->addr; |
| rd->dbg_data.rio.index = 0; |
| rd->dbg_data.rio.offset = 0; |
| } |
| #endif /* CONFIG_DEBUG_FS */ |
| return 0; |
| } |
| |
| static void rt_regmap_watchdog_work(struct work_struct *work) |
| { |
| struct rt_regmap_device *rd = (struct rt_regmap_device *) |
| container_of(work, |
| struct rt_regmap_device, watchdog_work.work); |
| unsigned char current_mode; |
| |
| dev_info(&rd->dev, "%s\n", __func__); |
| current_mode = rd->props.rt_regmap_mode&RT_CACHE_MODE_MASK; |
| if (current_mode != rd->props.cache_mode_ori) |
| rt_regmap_set_cache_mode(rd, rd->props.cache_mode_ori); |
| else |
| dev_info(&rd->dev, "%s same mode, no need change\n", __func__); |
| rd->props.watchdog = 0; |
| } |
| |
| static enum alarmtimer_restart rt_regmap_watchdog_alarm( |
| struct alarm *alarm, ktime_t now) |
| { |
| struct rt_regmap_device *rd = (struct rt_regmap_device *) |
| container_of(alarm, struct rt_regmap_device, watchdog_alarm); |
| |
| dev_info(&rd->dev, "%s\n", __func__); |
| schedule_delayed_work(&rd->watchdog_work, 0); |
| |
| return ALARMTIMER_NORESTART; |
| } |
| |
| struct rt_regmap_device *rt_regmap_device_register_ex |
| (struct rt_regmap_properties *props, |
| struct rt_regmap_fops *rops, |
| struct device *parent, |
| void *client, int slv_addr, void *drvdata) |
| { |
| struct rt_regmap_device *rd; |
| int ret = 0, i; |
| char device_name[32]; |
| unsigned char data; |
| |
| if (!props) { |
| pr_err("%s rt_regmap_properties is NULL\n", __func__); |
| return NULL; |
| } |
| if (!rops) { |
| pr_err("%s rt_regmap_fops is NULL\n", __func__); |
| return NULL; |
| } |
| |
| pr_info("regmap_device_register: name = %s\n", props->name); |
| rd = devm_kzalloc(parent, sizeof(struct rt_regmap_device), GFP_KERNEL); |
| if (!rd) { |
| pr_info("rt_regmap_device memory allocate fail\n"); |
| return NULL; |
| } |
| |
| /* create a binary semaphore */ |
| sema_init(&rd->semaphore, 1); |
| sema_init(&rd->write_mode_lock, 1); |
| rd->dev.parent = parent; |
| rd->client = client; |
| rd->dev.release = rt_regmap_device_release; |
| dev_set_drvdata(&rd->dev, drvdata); |
| snprintf(device_name, 32, "rt_regmap_%s", props->name); |
| dev_set_name(&rd->dev, device_name); |
| memcpy(&rd->props, props, sizeof(struct rt_regmap_properties)); |
| rd->props.cache_mode_ori = rd->props.rt_regmap_mode&RT_CACHE_MODE_MASK; |
| |
| /* check rt_registe_map format */ |
| ret = rt_regmap_check(rd); |
| if (ret) { |
| pr_info("rt register map format error\n"); |
| devm_kfree(parent, rd); |
| return NULL; |
| } |
| |
| ret = device_register(&rd->dev); |
| if (ret) { |
| pr_info("rt-regmap dev register fail\n"); |
| devm_kfree(parent, rd); |
| return NULL; |
| } |
| |
| rd->rops = rops; |
| rd->slv_addr = slv_addr; |
| rd->err_msg = devm_kzalloc(parent, 128*sizeof(char), GFP_KERNEL); |
| |
| /* init cache data */ |
| ret = rt_regmap_cache_init(rd); |
| if (ret < 0) { |
| pr_info(" rt cache data init fail\n"); |
| goto err_cacheinit; |
| } |
| |
| INIT_DELAYED_WORK(&rd->rt_work, rt_work_func); |
| |
| for (i = 0; i <= 3; i++) |
| rd->rt_block_write[i] = rt_block_map[i]; |
| |
| data = rd->props.rt_regmap_mode & RT_CACHE_MODE_MASK; |
| if (data == RT_CACHE_WR_THROUGH) { |
| rd->regmap_ops.regmap_block_write = &rt_cache_block_write; |
| rd->regmap_ops.regmap_block_read = &rt_cache_block_read; |
| } else if (data == RT_CACHE_WR_BACK) { |
| rd->regmap_ops.regmap_block_write = &rt_asyn_cache_block_write; |
| rd->regmap_ops.regmap_block_read = &rt_cache_block_read; |
| } else if (data == RT_CACHE_DISABLE) { |
| rd->regmap_ops.regmap_block_write = &rt_chip_block_write; |
| rd->regmap_ops.regmap_block_read = &rt_chip_block_read; |
| } |
| |
| INIT_DELAYED_WORK(&rd->watchdog_work, rt_regmap_watchdog_work); |
| alarm_init(&rd->watchdog_alarm, ALARM_REALTIME, |
| rt_regmap_watchdog_alarm); |
| |
| #ifdef CONFIG_DEBUG_FS |
| rd->rt_den = debugfs_create_dir(props->name, rt_regmap_dir); |
| if (!IS_ERR(rd->rt_den)) { |
| rt_create_general_debug(rd, rd->rt_den); |
| if (rd->props.rt_regmap_mode & DBG_MODE_MASK) |
| rt_create_every_debug(rd, rd->rt_den); |
| } else |
| goto err_debug; |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| return rd; |
| |
| #ifdef CONFIG_DEBUG_FS |
| err_debug: |
| rt_regmap_cache_release(rd); |
| #endif /* CONFIG_DEBUG_FS */ |
| err_cacheinit: |
| device_unregister(&rd->dev); |
| return NULL; |
| |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_device_register_ex); |
| |
| /* rt_regmap_device_unregister - unregister rt_regmap_device*/ |
| void rt_regmap_device_unregister(struct rt_regmap_device *rd) |
| { |
| if (!rd) |
| return; |
| down(&rd->semaphore); |
| rd->rops = NULL; |
| up(&rd->semaphore); |
| if (rd->cache_inited) |
| rt_regmap_cache_release(rd); |
| #ifdef CONFIG_DEBUG_FS |
| debugfs_remove_recursive(rd->rt_den); |
| if (rd->props.rt_regmap_mode & DBG_MODE_MASK) |
| rt_release_every_debug(rd); |
| #endif /* CONFIG_DEBUG_FS */ |
| device_unregister(&rd->dev); |
| } |
| EXPORT_SYMBOL_GPL(rt_regmap_device_unregister); |
| |
| static int __init regmap_plat_init(void) |
| { |
| pr_info("Init Richtek RegMap %s\n", RT_REGMAP_VERSION); |
| #ifdef CONFIG_DEBUG_FS |
| rt_regmap_dir = debugfs_create_dir("rt-regmap", 0); |
| if (IS_ERR(rt_regmap_dir)) { |
| pr_err("rt-regmap debugfs node create fail\n"); |
| return -EINVAL; |
| } |
| #endif /* CONFIG_DEBUG_FS */ |
| return 0; |
| } |
| |
| subsys_initcall(regmap_plat_init); |
| |
| static void __exit regmap_plat_exit(void) |
| { |
| #ifdef CONFIG_DEBUG_FS |
| debugfs_remove(rt_regmap_dir); |
| #endif /* CONFIG_DEBUG_FS */ |
| } |
| |
| module_exit(regmap_plat_exit); |
| |
| MODULE_DESCRIPTION("Richtek regmap Driver"); |
| MODULE_AUTHOR("Jeff Chang <jeff_chang@richtek.com>"); |
| MODULE_VERSION(RT_REGMAP_VERSION); |
| MODULE_LICENSE("GPL"); |