Jetpack/kernel/nvidia/drivers/mfd/tmpm32xi2c.c

386 lines
9.5 KiB
C

/*
* drivers/mfd/tmpm32xi2c.c
*
* Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/mfd/core.h>
#include <linux/delay.h>
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#endif
#include <linux/mfd/tmpm32xi2c.h>
static struct mfd_cell tmpm32xi2c_cells[] = {
{ .name = "tmpm32xi2c-gpio", },
{ .name = "tmpm32xi2c-poweroff", },
};
static int __tmpm32xi2c_write_read(struct tmpm32xi2c_chip *chip,
u8 *tx_buf, int tx_size, u8 *rx_buf, int rx_size)
{
struct i2c_client *client =
container_of(chip->dev, struct i2c_client, dev);
struct i2c_msg msg[2];
int msg_num = 0;
int ret = 0;
if (tx_size > 0) {
msg[msg_num].addr = client->addr;
msg[msg_num].flags = client->flags & I2C_M_TEN;
msg[msg_num].len = tx_size;
msg[msg_num].buf = tx_buf;
msg_num++;
}
if (rx_size > 0) {
msg[msg_num].addr = client->addr;
msg[msg_num].flags = client->flags & I2C_M_TEN;
msg[msg_num].flags |= I2C_M_RD;
msg[msg_num].len = rx_size;
msg[msg_num].buf = rx_buf;
msg_num++;
}
ret = i2c_transfer(client->adapter, msg, msg_num);
if (ret < 0)
dev_err_ratelimited(chip->dev,
"Failed to transfer data, CMD[0x%02x]\n",
tx_buf ? tx_buf[0] : 0);
return ret;
}
static int tmpm32xi2c_write_read(struct tmpm32xi2c_chip *chip,
u8 *tx_buf, int tx_size, u8 *rx_buf, int rx_size)
{
int ret;
mutex_lock(&chip->lock);
ret = __tmpm32xi2c_write_read(chip, tx_buf, tx_size, rx_buf, rx_size);
mutex_unlock(&chip->lock);
return ret;
}
#ifdef CONFIG_DEBUG_FS
static int __tmpm32xi2c_power_get_value(struct tmpm32xi2c_chip *chip,
unsigned int cmd, unsigned int ch)
{
int ret, i, max_bytes;
u8 tx_buf[] = { 0 /*cmd*/, 0 /*ch*/ };
u8 rx_buf[] = { 0 /*val*/ };
uint32_t val = 0;
bool err_flag = false;
if ((cmd != CMD_RD_VOLT) && (cmd != CMD_RD_ADC)) {
dev_err(chip->dev, "%s: Invalid CMD[0x%02x]\n", __func__, cmd);
return 0;
}
dev_dbg(chip->dev, "%s: CMD[0x%02x] CH[0x%02x]\n", __func__, cmd, ch);
/* send channel info */
tx_buf[0] = (u8)cmd;
tx_buf[1] = (u8)ch;
ret = __tmpm32xi2c_write_read(chip, tx_buf, sizeof(tx_buf), NULL, 0);
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to send channel info, %d\n",
__func__, ret);
return 0;
}
/*
* Delay for allowing MCU to read the INA sensor and save the result
* into I2C return value.
*/
usleep_range(10000, 10100);
/* set byte length for reading */
if (cmd == CMD_RD_VOLT)
max_bytes = 4;
else
max_bytes = 2;
for (i = 0; i < max_bytes; i++) {
/* send cmd only */
ret = __tmpm32xi2c_write_read(chip, tx_buf, 1,
rx_buf, sizeof(rx_buf));
if (ret < 0) {
dev_err(chip->dev, "%s: Failed to read %dth byte, %d\n",
__func__, i, ret);
err_flag = true;
}
val <<= 8;
val |= rx_buf[0];
}
if (err_flag)
return 0;
else
return val;
}
static int tmpm32xi2c_ina3221_get_value(struct tmpm32xi2c_chip *chip,
unsigned int ch)
{ int ret;
/* Operations of reading voltage value should be executed in a row. */
mutex_lock(&chip->lock);
ret = __tmpm32xi2c_power_get_value(chip, CMD_RD_VOLT, ch);
mutex_unlock(&chip->lock);
return ret;
}
static int tmpm32xi2c_adc_get_value(struct tmpm32xi2c_chip *chip,
unsigned int ch)
{
int ret;
/* Operations of reading voltage value should be executed in a row. */
mutex_lock(&chip->lock);
ret = __tmpm32xi2c_power_get_value(chip, CMD_RD_ADC, ch);
mutex_unlock(&chip->lock);
return ret;
}
union ina_data {
uint32_t raw;
struct {
uint16_t mv; /* low 2B */
uint16_t ma; /* high 2B */
} w;
};
static int show_tmpm32xi2c_power_stats(struct seq_file *s, void *data)
{
struct tmpm32xi2c_chip *chip = s->private;
uint32_t sys_9v_mv, vbat_mv, vdd_ddr_1v8_ma;
union ina_data vdd_gpu, vdd_bcpu, vdd_mcpu;
union ina_data vdd_soc, vdd_sram, vdd_ddr;
const char *pwr_val_fmt = "%12s\t%6d\t%6d\t%6d\n";
sys_9v_mv = tmpm32xi2c_adc_get_value(chip, R_SYS_9V);
vbat_mv = tmpm32xi2c_adc_get_value(chip, R_VBAT);
vdd_ddr_1v8_ma = tmpm32xi2c_adc_get_value(chip, R_VDD_DDR_1V8);
vdd_gpu.raw = tmpm32xi2c_ina3221_get_value(chip, R_VDD_GPU);
vdd_bcpu.raw = tmpm32xi2c_ina3221_get_value(chip, R_VDD_BCPU);
vdd_mcpu.raw = tmpm32xi2c_ina3221_get_value(chip, R_VDD_MCPU);
vdd_soc.raw = tmpm32xi2c_ina3221_get_value(chip, R_VDD_SOC);
vdd_sram.raw = tmpm32xi2c_ina3221_get_value(chip, R_VDD_SRAM);
vdd_ddr.raw = tmpm32xi2c_ina3221_get_value(chip, R_VDD_DDR);
seq_printf(s, "%12s\t%6s\t%6s\t%6s\n", "PWR RAIL", "mA", "mV", "mW");
/* TMPM32X ADC */
seq_printf(s, pwr_val_fmt, "SYS_9V", 0, sys_9v_mv, 0);
seq_printf(s, pwr_val_fmt, "VBAT_MAIN", 0, vbat_mv, 0);
seq_printf(s, pwr_val_fmt, "VDD_1V8_DDR", vdd_ddr_1v8_ma, 0, 0);
/* INA3221 */
seq_printf(s, pwr_val_fmt, "VDD_GPU",
vdd_gpu.w.ma, vdd_gpu.w.mv,
(vdd_gpu.w.ma * vdd_gpu.w.mv) / 1000);
seq_printf(s, pwr_val_fmt, "VDD_BCPU",
vdd_bcpu.w.ma, vdd_bcpu.w.mv,
(vdd_bcpu.w.ma * vdd_bcpu.w.mv) / 1000);
seq_printf(s, pwr_val_fmt, "VDD_MCPU",
vdd_mcpu.w.ma, vdd_mcpu.w.mv,
(vdd_mcpu.w.ma * vdd_mcpu.w.mv) / 1000);
seq_printf(s, pwr_val_fmt, "VDD_SOC",
vdd_soc.w.ma, vdd_soc.w.mv,
(vdd_soc.w.ma * vdd_soc.w.mv) / 1000);
seq_printf(s, pwr_val_fmt, "VDD_SRAM",
vdd_sram.w.ma, vdd_sram.w.mv,
(vdd_sram.w.ma * vdd_sram.w.mv) / 1000);
seq_printf(s, pwr_val_fmt, "VDD_DDR",
vdd_ddr.w.ma, vdd_ddr.w.mv,
(vdd_ddr.w.ma * vdd_ddr.w.mv) / 1000);
return 0;
}
static int tmpm32xi2c_power_stats_dump(struct inode *inode, struct file *file)
{
return single_open(file, show_tmpm32xi2c_power_stats, inode->i_private);
}
static const struct file_operations tmpm32xi2c_power_stats_fops = {
.open = tmpm32xi2c_power_stats_dump,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void tmpm32xi2c_debugfs_init(struct tmpm32xi2c_chip *chip)
{
struct dentry *root;
root = debugfs_create_dir("tmpm32xi2c", NULL);
if (!root)
return;
if (!debugfs_create_file("pwr_stat", 0444, root, chip,
&tmpm32xi2c_power_stats_fops))
goto err_root;
return;
err_root:
debugfs_remove_recursive(root);
}
#endif /* CONFIG_DEBUG_FS */
static int tmpm32xi2c_read_version(struct tmpm32xi2c_chip *chip)
{
int ret = -1;
u8 tx[] = { CMD_VERSION, 0 /*ver type*/ };
tx[1] = V_FW;
ret = chip->write_read(chip, tx, sizeof(tx), chip->fw_ver, 2);
if (ret < 0)
goto exit;
tx[1] = V_CMD;
ret = chip->write_read(chip, tx, sizeof(tx), &chip->cmd_ver, 1);
if (ret < 0)
goto exit;
dev_info(chip->dev, "FW version [0x%x][0x%x]\n",
chip->fw_ver[0], chip->fw_ver[1]);
dev_info(chip->dev, "CMD version [0x%x]\n", chip->cmd_ver);
exit:
return ret;
}
static int tmpm32xi2c_parse_dt(struct i2c_client *client)
{
struct device_node *np = client->dev.of_node;
struct tmpm32xi2c_chip *chip = i2c_get_clientdata(client);
u32 val = 0;
int ret;
if (!np) {
dev_err(&client->dev, "No device node\n");
return -ENOENT;
}
chip->irq_flags = IRQF_TRIGGER_LOW;
ret = of_property_read_u32(np, "tmpm32xi2c,irq_flags", &val);
if (!ret)
chip->irq_flags = val;
return 0;
}
static int tmpm32xi2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tmpm32xi2c_chip *chip;
int ret;
chip = devm_kzalloc(&client->dev, sizeof(struct tmpm32xi2c_chip),
GFP_KERNEL);
if (!chip)
return -ENOMEM;
if (!client->irq) {
dev_err(&client->dev, "No IRQ\n");
return -EINVAL;
}
chip->dev = &client->dev;
chip->irq = client->irq;
chip->write_read = tmpm32xi2c_write_read;
i2c_set_clientdata(client, chip);
mutex_init(&chip->lock);
ret = tmpm32xi2c_parse_dt(client);
if (ret < 0)
return ret;
ret = tmpm32xi2c_read_version(chip);
if (ret < 0)
return ret;
ret = mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
tmpm32xi2c_cells, ARRAY_SIZE(tmpm32xi2c_cells),
NULL, 0, NULL);
if (ret < 0) {
dev_err(&client->dev, "Failed to add sub devices, %d\n", ret);
return ret;
}
#ifdef CONFIG_DEBUG_FS
tmpm32xi2c_debugfs_init(chip);
#endif
return 0;
}
static int tmpm32xi2c_remove(struct i2c_client *client)
{
struct tmpm32xi2c_chip *chip = i2c_get_clientdata(client);
device_init_wakeup(&client->dev, false);
mutex_destroy(&chip->lock);
return 0;
}
static const struct i2c_device_id tmpm32xi2c_id[] = {
{ "tmpm32xi2c", 0, },
{ }
};
MODULE_DEVICE_TABLE(i2c, tmpm32xi2c_id);
static const struct of_device_id tmpm32xi2c_dt_ids[] = {
{ .compatible = "toshiba,tmpm32xi2c" },
{ .compatible = "nvidia,tmpm32xi2c" },
{ }
};
MODULE_DEVICE_TABLE(of, tmpm32xi2c_dt_ids);
static struct i2c_driver tmpm32xi2c_driver = {
.driver = {
.name = "tmpm32xi2c",
.of_match_table = tmpm32xi2c_dt_ids,
},
.probe = tmpm32xi2c_probe,
.remove = tmpm32xi2c_remove,
.id_table = tmpm32xi2c_id,
};
static int __init tmpm32xi2c_init(void)
{
return i2c_add_driver(&tmpm32xi2c_driver);
}
/* register after postcore initcall and subsys initcall that may rely on I2C. */
subsys_initcall_sync(tmpm32xi2c_init);
static void __exit tmpm32xi2c_exit(void)
{
i2c_del_driver(&tmpm32xi2c_driver);
}
module_exit(tmpm32xi2c_exit);
MODULE_AUTHOR("NVIDIA Corporation");
MODULE_DESCRIPTION("MFD core driver for TMPM32x I2C");
MODULE_LICENSE("GPL");