274 lines
6.4 KiB
C
Executable File
274 lines
6.4 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/soc/qcom/wcd939x-i2c.h>
|
|
#include "wcd-usbss-priv.h"
|
|
|
|
#define REG_BYTES 2
|
|
#define VAL_BYTES 1
|
|
#define PAGE_REG_ADDR 0x00
|
|
|
|
static int wcd_usbss_i2c_write_device(struct wcd_usbss_ctxt *ctxt, u16 reg, u8 *value,
|
|
u32 bytes)
|
|
{
|
|
struct i2c_msg *msg, xfer_msg[2];
|
|
int ret = 0;
|
|
u8 reg_addr = 0;
|
|
u8 *data = NULL;
|
|
struct i2c_client *client = NULL;
|
|
|
|
if (ctxt == NULL || ctxt->client == NULL)
|
|
return -ENODEV;
|
|
|
|
client = ctxt->client;
|
|
data = kzalloc(bytes + 1, GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
reg_addr = (u8)reg;
|
|
msg = &xfer_msg[0];
|
|
msg->addr = client->addr;
|
|
msg->len = bytes + 1;
|
|
msg->flags = 0;
|
|
data[0] = reg;
|
|
data[1] = *value;
|
|
msg->buf = data;
|
|
ret = i2c_transfer(client->adapter, xfer_msg, 1);
|
|
/* Try again if the write fails */
|
|
if (ret != 1) {
|
|
ret = i2c_transfer(client->adapter, xfer_msg, 1);
|
|
if (ret != 1) {
|
|
pr_err("failed to write the device\n");
|
|
goto fail;
|
|
}
|
|
}
|
|
pr_debug("write success register = %x val = %x\n", reg, data[1]);
|
|
ret = 0;
|
|
fail:
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int wcd_usbss_i2c_read_device(struct wcd_usbss_ctxt *ctxt, unsigned short reg,
|
|
int bytes, unsigned char *dest)
|
|
{
|
|
struct i2c_msg *msg, xfer_msg[2];
|
|
int ret = 0;
|
|
u8 reg_addr = 0;
|
|
u8 i = 0;
|
|
struct i2c_client *client = NULL;
|
|
|
|
if (ctxt == NULL || ctxt->client == NULL)
|
|
return -ENODEV;
|
|
|
|
client = ctxt->client;
|
|
for (i = 0; i < bytes; i++) {
|
|
reg_addr = (u8)reg++;
|
|
msg = &xfer_msg[0];
|
|
msg->addr = client->addr;
|
|
msg->len = 1;
|
|
msg->flags = 0;
|
|
msg->buf = ®_addr;
|
|
|
|
msg = &xfer_msg[1];
|
|
msg->addr = client->addr;
|
|
msg->len = 1;
|
|
msg->flags = I2C_M_RD;
|
|
msg->buf = dest++;
|
|
ret = i2c_transfer(client->adapter, xfer_msg, 2);
|
|
|
|
/* Try again if read fails first time */
|
|
if (ret != 2) {
|
|
ret = i2c_transfer(client->adapter, xfer_msg, 2);
|
|
if (ret != 2) {
|
|
pr_err("failed to read wcd usbss register\n");
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* wcd_usbss_page_write:
|
|
* Retrieve page number from register and
|
|
* write that page number to the page address.
|
|
*
|
|
* @ctxt: pointer to wcd_usbss_ctxt
|
|
* @reg: Register address from which page number is retrieved
|
|
*
|
|
* Returns 0 for success and negative error code for failure.
|
|
*/
|
|
static int wcd_usbss_page_write(struct wcd_usbss_ctxt *ctxt, unsigned short *reg)
|
|
{
|
|
int ret = 0;
|
|
unsigned short c_reg = 0, reg_addr = 0;
|
|
u8 pg_num = 0, prev_pg_num = 0;
|
|
|
|
c_reg = *reg;
|
|
pg_num = c_reg >> 8;
|
|
reg_addr = c_reg & 0xff;
|
|
if (ctxt->prev_pg_valid) {
|
|
prev_pg_num = ctxt->prev_pg;
|
|
if (prev_pg_num != pg_num) {
|
|
ret = wcd_usbss_i2c_write_device(
|
|
ctxt, PAGE_REG_ADDR, (void *) &pg_num, 1);
|
|
if (ret < 0) {
|
|
dev_err(ctxt->dev, "page write error, pg_num: 0x%x\n", pg_num);
|
|
} else {
|
|
ctxt->prev_pg = pg_num;
|
|
dev_dbg(ctxt->dev, "Page 0x%x Write to 0x00\n", pg_num);
|
|
}
|
|
}
|
|
} else {
|
|
ret = wcd_usbss_i2c_write_device(
|
|
ctxt, PAGE_REG_ADDR, (void *) &pg_num, 1);
|
|
if (ret < 0) {
|
|
pr_err("page write error, pg_num: 0x%x\n", pg_num);
|
|
} else {
|
|
ctxt->prev_pg = pg_num;
|
|
ctxt->prev_pg_valid = true;
|
|
dev_dbg(ctxt->dev, "Page 0x%x Write to 0x00\n", pg_num);
|
|
}
|
|
}
|
|
*reg = reg_addr;
|
|
return ret;
|
|
}
|
|
|
|
static int regmap_bus_read(void *context, const void *reg, size_t reg_size,
|
|
void *val, size_t val_size)
|
|
{
|
|
struct device *dev = context;
|
|
struct wcd_usbss_ctxt *ctxt = dev_get_drvdata(dev);
|
|
unsigned short c_reg = 0, rreg = 0;
|
|
int ret = 0, i;
|
|
|
|
if (!ctxt) {
|
|
dev_err(dev, "%s: ctxt is NULL\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
if (!reg || !val) {
|
|
dev_err(dev, "%s: reg or val is NULL\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (reg_size != REG_BYTES) {
|
|
dev_err(dev, "%s: register size %zd bytes, not supported\n",
|
|
__func__, reg_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&ctxt->io_lock);
|
|
c_reg = *(u16 *)reg;
|
|
rreg = c_reg;
|
|
|
|
ret = wcd_usbss_page_write(ctxt, &c_reg);
|
|
if (ret)
|
|
goto err;
|
|
ret = wcd_usbss_i2c_read_device(ctxt, c_reg, val_size, val);
|
|
if (ret < 0)
|
|
dev_err(dev, "%s: Codec read failed (%d), reg: 0x%x, size:%zd\n",
|
|
__func__, ret, rreg, val_size);
|
|
else {
|
|
for (i = 0; i < val_size; i++)
|
|
dev_dbg(dev, "%s: Read 0x%02x from 0x%x\n",
|
|
__func__, ((u8 *)val)[i], rreg + i);
|
|
}
|
|
err:
|
|
mutex_unlock(&ctxt->io_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int regmap_bus_gather_write(void *context,
|
|
const void *reg, size_t reg_size,
|
|
const void *val, size_t val_size)
|
|
{
|
|
struct device *dev = context;
|
|
struct wcd_usbss_ctxt *ctxt = dev_get_drvdata(dev);
|
|
unsigned short c_reg, rreg;
|
|
int ret, i;
|
|
|
|
if (!ctxt) {
|
|
dev_err(dev, "%s: ctxt is NULL\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
if (!reg || !val) {
|
|
dev_err(dev, "%s: reg or val is NULL\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
if (reg_size != REG_BYTES) {
|
|
dev_err(dev, "%s: register size %zd bytes, not supported\n",
|
|
__func__, reg_size);
|
|
return -EINVAL;
|
|
}
|
|
mutex_lock(&ctxt->io_lock);
|
|
c_reg = *(u16 *)reg;
|
|
rreg = c_reg;
|
|
|
|
ret = wcd_usbss_page_write(ctxt, &c_reg);
|
|
if (ret)
|
|
goto err;
|
|
|
|
for (i = 0; i < val_size; i++)
|
|
dev_dbg(dev, "Write %02x to 0x%x\n", ((u8 *)val)[i],
|
|
rreg + i);
|
|
|
|
ret = wcd_usbss_i2c_write_device(ctxt, c_reg, (void *) val, val_size);
|
|
if (ret < 0)
|
|
dev_err(dev, "%s: Codec write failed (%d), reg:0x%x, size:%zd\n",
|
|
__func__, ret, rreg, val_size);
|
|
|
|
err:
|
|
mutex_unlock(&ctxt->io_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int regmap_bus_write(void *context, const void *data, size_t count)
|
|
{
|
|
struct device *dev = context;
|
|
struct wcd_usbss_ctxt *ctxt = dev_get_drvdata(dev);
|
|
|
|
if (!ctxt)
|
|
return -EINVAL;
|
|
|
|
WARN_ON(count < REG_BYTES);
|
|
WARN_ON(count > (REG_BYTES + VAL_BYTES));
|
|
|
|
if (count <= (REG_BYTES + VAL_BYTES))
|
|
return regmap_bus_gather_write(context, data, REG_BYTES,
|
|
data + REG_BYTES,
|
|
count - REG_BYTES);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static struct regmap_bus regmap_bus_config = {
|
|
.write = regmap_bus_write,
|
|
.gather_write = regmap_bus_gather_write,
|
|
.read = regmap_bus_read,
|
|
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
|
.val_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
|
};
|
|
|
|
/*
|
|
* wcd_usbss_regmap_init:
|
|
* Initialize wcd_usbss register map
|
|
*
|
|
* @dev: pointer to wcd usbss i2c device
|
|
* @config: pointer to register map config
|
|
*
|
|
* Returns pointer to regmap structure for success
|
|
* or NULL in case of failure.
|
|
*/
|
|
struct regmap *wcd_usbss_regmap_init(struct device *dev,
|
|
const struct regmap_config *config)
|
|
{
|
|
return devm_regmap_init(dev, ®map_bus_config, dev, config);
|
|
}
|
|
EXPORT_SYMBOL_GPL(wcd_usbss_regmap_init);
|