// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #define pr_fmt(fmt) " "fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define hex2int(c) ((c >= '0') && (c <= '9') ? (c - '0') : ((c & 0xf) + 9)) #define C_MAX_REG_LEN (4) int hwmsen_set_bits(struct i2c_client *client, u8 addr, u8 bits) { int err; u8 cur, nxt; err = hwmsen_read_byte(client, addr, &cur); if (err) { pr_debug("read err: 0x%02X\n", addr); return -EFAULT; } nxt = (cur | bits); if (nxt ^ cur) { err = hwmsen_write_byte(client, addr, nxt); if (err) { pr_debug("write err: 0x%02X\n", addr); return -EFAULT; } } return 0; } EXPORT_SYMBOL_GPL(hwmsen_set_bits); int hwmsen_clr_bits(struct i2c_client *client, u8 addr, u8 bits) { int err; u8 cur, nxt; err = hwmsen_read_byte(client, addr, &cur); if (err) { pr_debug("read err: 0x%02X\n", addr); return -EFAULT; } nxt = cur & (~bits); if (nxt ^ cur) { err = hwmsen_write_byte(client, addr, nxt); if (err) { pr_debug("write err: 0x%02X\n", addr); return -EFAULT; } } return 0; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_clr_bits); /*----------------------------------------------------------------------------*/ int hwmsen_read_byte(struct i2c_client *client, u8 addr, u8 *data) { u8 beg = addr; int err; struct i2c_msg msgs[2] = { { .flags = 0, .len = 1, .buf = &beg }, { .flags = I2C_M_RD, .len = 1, .buf = data, } }; if (!client) return -EINVAL; msgs[0].addr = client->addr; msgs[1].addr = client->addr; err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); if (err != 2) { pr_debug("i2c_transfer error: (%d %p) %d\n", addr, data, err); err = -EIO; } err = 0; return err; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_read_byte); /*----------------------------------------------------------------------------*/ int hwmsen_write_byte(struct i2c_client *client, u8 addr, u8 data) { u8 buf[] = {addr, data}; int ret = 0; ret = i2c_master_send(client, (const char *)buf, sizeof(buf)); if (ret < 0) { pr_debug("send command error!!\n"); return -EFAULT; } #if defined(HWMSEN_DEBUG) pr_debug("%s(0x%02X)= %02X\n", __func__, addr, data); #endif return 0; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_write_byte); /*----------------------------------------------------------------------------*/ int hwmsen_read_block(struct i2c_client *client, u8 addr, u8 *data, u8 len) { u8 beg = addr; int err; struct i2c_msg msgs[2] = {{0}, {0} }; if (!client) return -EINVAL; else if (len > C_I2C_FIFO_SIZE) { pr_debug(" length %d exceeds %d\n", len, C_I2C_FIFO_SIZE); return -EINVAL; } if (len == 1) return hwmsen_read_byte(client, addr, data); msgs[0].addr = client->addr; msgs[0].flags = 0; msgs[0].len = 1; msgs[0].buf = &beg; msgs[1].addr = client->addr; msgs[1].flags = I2C_M_RD; msgs[1].len = len; msgs[1].buf = data; err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); if (err != 2) { pr_debug("i2c_transfer error: (%d %p %d) %d\n", addr, data, len, err); err = -EIO; } #if defined(HWMSEN_DEBUG) static char buf[128]; int idx, buflen = 0; for (idx = 0; idx < len; idx++) buflen += snprintf(buf + buflen, sizeof(buf) - buflen, "%02X ", data[idx]); pr_debug("%s(0x%02X,%2d) = %s\n", __func__, addr, len, buf); #endif err = 0; /*no error*/ return err; } EXPORT_SYMBOL_GPL(hwmsen_read_block); /*----------------------------------------------------------------------------*/ int hwmsen_write_block(struct i2c_client *client, u8 addr, u8 *data, u8 len) { int err; u32 idx, num; char buf[C_I2C_FIFO_SIZE]; if (!client) return -EINVAL; else if (len >= C_I2C_FIFO_SIZE) { pr_debug(" length %d exceeds %d\n", len, C_I2C_FIFO_SIZE); return -EINVAL; } num = 0; buf[num++] = addr; for (idx = 0; idx < len; idx++) buf[num++] = data[idx]; err = i2c_master_send(client, buf, num); if (err < 0) { pr_debug("send command error!!\n"); return -EFAULT; } #if defined(HWMSEN_DEBUG) static char buf[128]; int idx, buflen = 0; for (idx = 0; idx < len; idx++) buflen += snprintf(buf + buflen, sizeof(buf) - buflen, "%02X ", data[idx]); pr_debug("%s(0x%02X,%2d)= %s\n", __func__, addr, len, buf); #endif err = 0; /*no error*/ return err; } EXPORT_SYMBOL_GPL(hwmsen_write_block); /*----------------------------------------------------------------------------*/ static int hwmsen_chrs_to_integer(u8 dat[C_MAX_REG_LEN], int datlen) { int idx; u32 val = 0; for (idx = 0; idx < datlen; idx++) val += (dat[idx] << (8 * idx)); return val; } /*----------------------------------------------------------------------------*/ static int hwmsen_byte_to_integer(u8 dat, int datlen) { int idx; u32 val = 0; for (idx = 0; idx < datlen; idx++) val += (dat << (8 * idx)); return val; } /*----------------------------------------------------------------------------*/ void hwmsen_single_rw(struct i2c_client *client, struct hwmsen_reg *regs, int reg_num) { int idx, pos, num, err = 0, cmp1, cmp2; u8 pattern[] = {0x55, 0xAA}; u8 old[C_MAX_REG_LEN], val[C_MAX_REG_LEN], mask; struct hwmsen_reg *ptr; for (idx = 0; idx < reg_num; idx++) { if (regs[idx].mode != REG_RW) continue; if (regs[idx].len > C_MAX_REG_LEN) { pr_debug("exceed maximum length\n"); continue; } ptr = ®s[idx]; err = hwmsen_read_block(client, ptr->addr, old, ptr->len); if (err) { pr_debug("read %s fails\n", ptr->name); continue; } for (pos = 0; pos < ARRAY_SIZE(pattern); pos++) { mask = ptr->mask; for (num = 0; num < ptr->len; num++) val[num] = pattern[pos] & mask; err = hwmsen_write_block(client, ptr->addr, val, ptr->len); if (err) { pr_debug("test: write %s fails, pat[0x%02X]\n", ptr->name, pattern[pos]); continue; } err = hwmsen_read_block(client, ptr->addr, val, ptr->len); if (err) { pr_debug("test: read %s fails\n", ptr->name); continue; } cmp1 = hwmsen_chrs_to_integer(val, ptr->len); cmp2 = hwmsen_byte_to_integer(pattern[pos], ptr->len); if ((cmp1 ^ cmp2) & ptr->mask) pr_debug("test: reg %s[%d] 0x%08X <-> 0x%08X\n", ptr->name, num, cmp1, cmp2); } err = hwmsen_write_block(client, ptr->addr, old, ptr->len); if (err) { pr_debug("restore: write %s\n", ptr->name); continue; } err = hwmsen_read_block(client, ptr->addr, val, ptr->len); if (err) { pr_debug("restore: read %s\n", ptr->name); continue; } cmp1 = hwmsen_chrs_to_integer(val, ptr->len); cmp2 = hwmsen_chrs_to_integer(old, ptr->len); if ((cmp1 ^ cmp2) & ptr->mask) { pr_debug("restore %s fails\n", ptr->name); err = -EFAULT; } } if (!err) pr_debug("%s pass!!\n", __func__); } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_single_rw); /*----------------------------------------------------------------------------*/ void hwmsen_multi_rw(struct i2c_client *client, find_reg_t findreg, struct hwmsen_reg_test_multi *items, int inum) { u8 pattern[] = {0x55, 0xAA}; u8 buf[C_I2C_FIFO_SIZE], dat[C_I2C_FIFO_SIZE], old[C_I2C_FIFO_SIZE]; int pos, idx, p, err = 0; for (idx = 0; idx < inum; idx++) { u8 addr = items[idx].addr; u8 len = items[idx].len; u8 mode = items[idx].mode; for (pos = 0; pos < ARRAY_SIZE(pattern); pos++) { err = hwmsen_read_block(client, addr, old, len); if (err) { pr_debug("(%d) save block fail!!\n", idx); continue; } if (!(mode & REG_WO)) continue; memset(buf, pattern[pos], sizeof(buf)); for (p = 0; p < len; p++) buf[p] = buf[p] & findreg(addr + p)->mask; err = hwmsen_write_block(client, addr, buf, len); if (err) { pr_debug("(%d) block write fail!!\n", idx); continue; } memset(dat, 0x00, sizeof(dat)); err = hwmsen_read_block(client, addr, dat, len); if (err) { pr_debug("(%d) block read fail!!\n", idx); continue; } err = hwmsen_write_block(client, addr, old, len); if (err) { pr_debug("(%d) restore block fail!!\n", idx); continue; } if (memcmp(buf, dat, len)) { pr_debug("(%d) data compare fail!!\n", idx); pr_debug("buf:"); for (p = 0; p < len; p++) pr_debug("%02X", buf[p]); pr_debug("\n"); pr_debug("dat:"); for (p = 0; p < len; p++) pr_debug("%02X", dat[p]); pr_debug("\n"); err = 1; } } } if (!err) pr_debug("%s success : %d cases\n", __func__, inum); else pr_debug("%s: %d cases\n", __func__, inum); } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_multi_rw); /*----------------------------------------------------------------------------*/ ssize_t hwmsen_show_dump(struct i2c_client *client, u8 startAddr, u8 *regtbl, u32 regnum, find_reg_t findreg, char *buf, u32 buflen) { int err = 0; u8 addr = startAddr; u8 max_len = 8, read_len; const char *name = NULL; if (!client) return -EINVAL; memset(regtbl, 0x00, regnum); do { read_len = ((regnum - addr) > max_len) ? (max_len) : (regnum - addr); if (!read_len) break; err = hwmsen_read_block(client, addr, ®tbl[addr - startAddr], read_len); if (!err) addr += read_len; /* SEN_VER("read block data: %d %d\n", addr, read_len); */ } while (!err); if (!err) { int idx; ssize_t len = 0; for (idx = startAddr; idx < regnum; idx++) { name = findreg(idx)->name; if (name != NULL) len += snprintf(buf + len, buflen - len, "%-16s = 0x%02X\n", name, regtbl[idx - startAddr]); } return len; } return 0; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_show_dump); /*----------------------------------------------------------------------------*/ ssize_t hwmsen_read_all_regs(struct i2c_client *client, struct hwmsen_reg *regs, u32 num, char *buf, u32 buflen) { int err = 0, pos, idx, val; struct hwmsen_reg *ptr; u8 dat[4]; ssize_t len = 0; if (!client) return -EINVAL; for (idx = 0; idx < num; idx++) { ptr = ®s[idx]; memset(dat, 0x00, sizeof(dat)); if (ptr->len > sizeof(dat)) { pr_debug("exceeds capacity, %d\n", ptr->len); break; } err = hwmsen_read_block(client, ptr->addr, dat, ptr->len); if (err) { pr_debug("read reg %s (0x%2X) fail!!\n", ptr->name, ptr->addr); break; } val = 0; for (pos = 0; pos < ptr->len; pos++) val += (dat[pos] << (8 * pos)); len += snprintf(buf + len, buflen - len, "%-16s = %8X\n", ptr->name, val); } return (err) ? (0) : len; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_read_all_regs); /*----------------------------------------------------------------------------*/ ssize_t hwmsen_show_reg(struct i2c_client *client, u8 addr, char *buf, u32 buflen) { u8 data = 0; int err; err = hwmsen_read_byte(client, addr, &data); if (err) { pr_debug("reading address 0x%02X fail!!", addr); return 0; } else return snprintf(buf, buflen, "0x%02X\n", data); } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_show_reg); /*----------------------------------------------------------------------------*/ ssize_t hwmsen_store_reg(struct i2c_client *client, u8 addr, const char *buf, size_t count) { int err, val; u8 data; err = kstrtoint(buf, 16, &val); if (err != 0) { pr_debug("format not match: (0xAB) <-> '%s'\n", buf); return count; } data = (u8)val; err = hwmsen_write_byte(client, addr, data); if (err) pr_debug("write address 0x%02X fail!!\n", addr); return count; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_store_reg); /*----------------------------------------------------------------------------*/ ssize_t hwmsen_show_byte(struct device *dev, struct device_attribute *attr, char *buf, u32 buflen) { int err, index = to_sensor_dev_attr(attr)->index; struct i2c_client *client = to_i2c_client(dev); u8 addr = (u8)index; u8 dat; err = hwmsen_read_byte(client, addr, &dat); if (err) { pr_debug("reading address 0x%02X fail!!", addr); return 0; } return snprintf(buf, buflen, "0x%02X\n", dat); } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_show_byte); /*----------------------------------------------------------------------------*/ ssize_t hwmsen_store_byte(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int err, val; int index = to_sensor_dev_attr(attr)->index; struct i2c_client *client = to_i2c_client(dev); u8 addr = (u8)index; u8 dat; err = kstrtoint(buf, 16, &val); if (err != 0) { pr_debug("format not match: (0xAB) <-> '%s'\n", buf); return count; } dat = (u8)val; err = hwmsen_write_byte(client, addr, dat); if (err) pr_debug("write address 0x%02X fail!!\n", addr); return count; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_store_byte); /*----------------------------------------------------------------------------*/ ssize_t hwmsen_show_word(struct device *dev, struct device_attribute *attr, char *buf, u32 buflen) { int err, index = to_sensor_dev_attr(attr)->index; struct i2c_client *client = to_i2c_client(dev); u8 addr = (u8)index; u8 dat[2]; err = hwmsen_read_block(client, addr, dat, sizeof(dat)); if (err) { pr_debug("reading address 0x%02X fail!!", addr); return 0; } return snprintf(buf, buflen, "0x%04X\n", (dat[0] | (dat[1] << 8))); } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_show_word); /*----------------------------------------------------------------------------*/ ssize_t hwmsen_store_word(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int err, val; int index = to_sensor_dev_attr(attr)->index; struct i2c_client *client = to_i2c_client(dev); u8 addr = (u8)index; u8 dat[2]; err = kstrtoint(buf, 16, &val); if (err != 0) { pr_debug("format not match: (0xAB) <-> '%s'\n", buf); return count; } dat[0] = (u8)((val & 0x00FF)); dat[1] = (u8)((val & 0xFF00) >> 8); err = hwmsen_write_block(client, addr, dat, sizeof(dat)); if (err) pr_debug("write address 0x%02X fail!!\n", addr); return count; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_store_word); /*----------------------------------------------------------------------------*/ struct hwmsen_convert map[] = { { { 1, 1, 1}, {0, 1, 2} }, { {-1, 1, 1}, {1, 0, 2} }, { {-1, -1, 1}, {0, 1, 2} }, { { 1, -1, 1}, {1, 0, 2} }, { {-1, 1, -1}, {0, 1, 2} }, { { 1, 1, -1}, {1, 0, 2} }, { { 1, -1, -1}, {0, 1, 2} }, { {-1, -1, -1}, {1, 0, 2} }, }; /*----------------------------------------------------------------------------*/ int hwmsen_get_convert(int direction, struct hwmsen_convert *cvt) { unsigned int dir; if (!cvt) return -EINVAL; else if (direction < 0 || direction >= ARRAY_SIZE(map)) return -EINVAL; else dir = direction; *cvt = map[dir]; return 0; } /*----------------------------------------------------------------------------*/ EXPORT_SYMBOL_GPL(hwmsen_get_convert); /*----------------------------------------------------------------------------*/