alistair23-linux/drivers/mfd/rk808.c
Thomas Gleixner 2025cf9e19 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 288
Based on 1 normalized pattern(s):

  this program is free software you can redistribute it and or modify
  it under the terms and conditions of the gnu general public license
  version 2 as published by the free software foundation this program
  is distributed in the hope 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

extracted by the scancode license scanner the SPDX license identifier

  GPL-2.0-only

has been chosen to replace the boilerplate/reference in 263 file(s).

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Allison Randal <allison@lohutok.net>
Reviewed-by: Alexios Zavras <alexios.zavras@intel.com>
Cc: linux-spdx@vger.kernel.org
Link: https://lkml.kernel.org/r/20190529141901.208660670@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-06-05 17:36:37 +02:00

579 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* MFD core driver for Rockchip RK808/RK818
*
* Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
*
* Author: Chris Zhong <zyw@rock-chips.com>
* Author: Zhang Qing <zhangqing@rock-chips.com>
*
* Copyright (C) 2016 PHYTEC Messtechnik GmbH
*
* Author: Wadim Egorov <w.egorov@phytec.de>
*/
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/mfd/rk808.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
struct rk808_reg_data {
int addr;
int mask;
int value;
};
static bool rk808_is_volatile_reg(struct device *dev, unsigned int reg)
{
/*
* Notes:
* - Technically the ROUND_30s bit makes RTC_CTRL_REG volatile, but
* we don't use that feature. It's better to cache.
* - It's unlikely we care that RK808_DEVCTRL_REG is volatile since
* bits are cleared in case when we shutoff anyway, but better safe.
*/
switch (reg) {
case RK808_SECONDS_REG ... RK808_WEEKS_REG:
case RK808_RTC_STATUS_REG:
case RK808_VB_MON_REG:
case RK808_THERMAL_REG:
case RK808_DCDC_UV_STS_REG:
case RK808_LDO_UV_STS_REG:
case RK808_DCDC_PG_REG:
case RK808_LDO_PG_REG:
case RK808_DEVCTRL_REG:
case RK808_INT_STS_REG1:
case RK808_INT_STS_REG2:
return true;
}
return false;
}
static const struct regmap_config rk818_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = RK818_USB_CTRL_REG,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = rk808_is_volatile_reg,
};
static const struct regmap_config rk805_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = RK805_OFF_SOURCE_REG,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = rk808_is_volatile_reg,
};
static const struct regmap_config rk808_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = RK808_IO_POL_REG,
.cache_type = REGCACHE_RBTREE,
.volatile_reg = rk808_is_volatile_reg,
};
static struct resource rtc_resources[] = {
{
.start = RK808_IRQ_RTC_ALARM,
.end = RK808_IRQ_RTC_ALARM,
.flags = IORESOURCE_IRQ,
}
};
static struct resource rk805_key_resources[] = {
{
.start = RK805_IRQ_PWRON_FALL,
.end = RK805_IRQ_PWRON_FALL,
.flags = IORESOURCE_IRQ,
},
{
.start = RK805_IRQ_PWRON_RISE,
.end = RK805_IRQ_PWRON_RISE,
.flags = IORESOURCE_IRQ,
}
};
static const struct mfd_cell rk805s[] = {
{ .name = "rk808-clkout", },
{ .name = "rk808-regulator", },
{ .name = "rk805-pinctrl", },
{
.name = "rk808-rtc",
.num_resources = ARRAY_SIZE(rtc_resources),
.resources = &rtc_resources[0],
},
{ .name = "rk805-pwrkey",
.num_resources = ARRAY_SIZE(rk805_key_resources),
.resources = &rk805_key_resources[0],
},
};
static const struct mfd_cell rk808s[] = {
{ .name = "rk808-clkout", },
{ .name = "rk808-regulator", },
{
.name = "rk808-rtc",
.num_resources = ARRAY_SIZE(rtc_resources),
.resources = rtc_resources,
},
};
static const struct mfd_cell rk818s[] = {
{ .name = "rk808-clkout", },
{ .name = "rk808-regulator", },
{
.name = "rk808-rtc",
.num_resources = ARRAY_SIZE(rtc_resources),
.resources = rtc_resources,
},
};
static const struct rk808_reg_data rk805_pre_init_reg[] = {
{RK805_BUCK1_CONFIG_REG, RK805_BUCK1_2_ILMAX_MASK,
RK805_BUCK1_2_ILMAX_4000MA},
{RK805_BUCK2_CONFIG_REG, RK805_BUCK1_2_ILMAX_MASK,
RK805_BUCK1_2_ILMAX_4000MA},
{RK805_BUCK3_CONFIG_REG, RK805_BUCK3_4_ILMAX_MASK,
RK805_BUCK3_ILMAX_3000MA},
{RK805_BUCK4_CONFIG_REG, RK805_BUCK3_4_ILMAX_MASK,
RK805_BUCK4_ILMAX_3500MA},
{RK805_BUCK4_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_400MA},
{RK805_GPIO_IO_POL_REG, SLP_SD_MSK, SLEEP_FUN},
{RK805_THERMAL_REG, TEMP_HOTDIE_MSK, TEMP115C},
};
static const struct rk808_reg_data rk808_pre_init_reg[] = {
{ RK808_BUCK3_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_150MA },
{ RK808_BUCK4_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_200MA },
{ RK808_BOOST_CONFIG_REG, BOOST_ILMIN_MASK, BOOST_ILMIN_100MA },
{ RK808_BUCK1_CONFIG_REG, BUCK1_RATE_MASK, BUCK_ILMIN_200MA },
{ RK808_BUCK2_CONFIG_REG, BUCK2_RATE_MASK, BUCK_ILMIN_200MA },
{ RK808_DCDC_UV_ACT_REG, BUCK_UV_ACT_MASK, BUCK_UV_ACT_DISABLE},
{ RK808_VB_MON_REG, MASK_ALL, VB_LO_ACT |
VB_LO_SEL_3500MV },
};
static const struct rk808_reg_data rk818_pre_init_reg[] = {
/* improve efficiency */
{ RK818_BUCK2_CONFIG_REG, BUCK2_RATE_MASK, BUCK_ILMIN_250MA },
{ RK818_BUCK4_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_250MA },
{ RK818_BOOST_CONFIG_REG, BOOST_ILMIN_MASK, BOOST_ILMIN_100MA },
{ RK818_USB_CTRL_REG, RK818_USB_ILIM_SEL_MASK,
RK818_USB_ILMIN_2000MA },
/* close charger when usb lower then 3.4V */
{ RK818_USB_CTRL_REG, RK818_USB_CHG_SD_VSEL_MASK,
(0x7 << 4) },
/* no action when vref */
{ RK818_H5V_EN_REG, BIT(1), RK818_REF_RDY_CTRL },
/* enable HDMI 5V */
{ RK818_H5V_EN_REG, BIT(0), RK818_H5V_EN },
{ RK808_VB_MON_REG, MASK_ALL, VB_LO_ACT |
VB_LO_SEL_3500MV },
};
static const struct regmap_irq rk805_irqs[] = {
[RK805_IRQ_PWRON_RISE] = {
.mask = RK805_IRQ_PWRON_RISE_MSK,
.reg_offset = 0,
},
[RK805_IRQ_VB_LOW] = {
.mask = RK805_IRQ_VB_LOW_MSK,
.reg_offset = 0,
},
[RK805_IRQ_PWRON] = {
.mask = RK805_IRQ_PWRON_MSK,
.reg_offset = 0,
},
[RK805_IRQ_PWRON_LP] = {
.mask = RK805_IRQ_PWRON_LP_MSK,
.reg_offset = 0,
},
[RK805_IRQ_HOTDIE] = {
.mask = RK805_IRQ_HOTDIE_MSK,
.reg_offset = 0,
},
[RK805_IRQ_RTC_ALARM] = {
.mask = RK805_IRQ_RTC_ALARM_MSK,
.reg_offset = 0,
},
[RK805_IRQ_RTC_PERIOD] = {
.mask = RK805_IRQ_RTC_PERIOD_MSK,
.reg_offset = 0,
},
[RK805_IRQ_PWRON_FALL] = {
.mask = RK805_IRQ_PWRON_FALL_MSK,
.reg_offset = 0,
},
};
static const struct regmap_irq rk808_irqs[] = {
/* INT_STS */
[RK808_IRQ_VOUT_LO] = {
.mask = RK808_IRQ_VOUT_LO_MSK,
.reg_offset = 0,
},
[RK808_IRQ_VB_LO] = {
.mask = RK808_IRQ_VB_LO_MSK,
.reg_offset = 0,
},
[RK808_IRQ_PWRON] = {
.mask = RK808_IRQ_PWRON_MSK,
.reg_offset = 0,
},
[RK808_IRQ_PWRON_LP] = {
.mask = RK808_IRQ_PWRON_LP_MSK,
.reg_offset = 0,
},
[RK808_IRQ_HOTDIE] = {
.mask = RK808_IRQ_HOTDIE_MSK,
.reg_offset = 0,
},
[RK808_IRQ_RTC_ALARM] = {
.mask = RK808_IRQ_RTC_ALARM_MSK,
.reg_offset = 0,
},
[RK808_IRQ_RTC_PERIOD] = {
.mask = RK808_IRQ_RTC_PERIOD_MSK,
.reg_offset = 0,
},
/* INT_STS2 */
[RK808_IRQ_PLUG_IN_INT] = {
.mask = RK808_IRQ_PLUG_IN_INT_MSK,
.reg_offset = 1,
},
[RK808_IRQ_PLUG_OUT_INT] = {
.mask = RK808_IRQ_PLUG_OUT_INT_MSK,
.reg_offset = 1,
},
};
static const struct regmap_irq rk818_irqs[] = {
/* INT_STS */
[RK818_IRQ_VOUT_LO] = {
.mask = RK818_IRQ_VOUT_LO_MSK,
.reg_offset = 0,
},
[RK818_IRQ_VB_LO] = {
.mask = RK818_IRQ_VB_LO_MSK,
.reg_offset = 0,
},
[RK818_IRQ_PWRON] = {
.mask = RK818_IRQ_PWRON_MSK,
.reg_offset = 0,
},
[RK818_IRQ_PWRON_LP] = {
.mask = RK818_IRQ_PWRON_LP_MSK,
.reg_offset = 0,
},
[RK818_IRQ_HOTDIE] = {
.mask = RK818_IRQ_HOTDIE_MSK,
.reg_offset = 0,
},
[RK818_IRQ_RTC_ALARM] = {
.mask = RK818_IRQ_RTC_ALARM_MSK,
.reg_offset = 0,
},
[RK818_IRQ_RTC_PERIOD] = {
.mask = RK818_IRQ_RTC_PERIOD_MSK,
.reg_offset = 0,
},
[RK818_IRQ_USB_OV] = {
.mask = RK818_IRQ_USB_OV_MSK,
.reg_offset = 0,
},
/* INT_STS2 */
[RK818_IRQ_PLUG_IN] = {
.mask = RK818_IRQ_PLUG_IN_MSK,
.reg_offset = 1,
},
[RK818_IRQ_PLUG_OUT] = {
.mask = RK818_IRQ_PLUG_OUT_MSK,
.reg_offset = 1,
},
[RK818_IRQ_CHG_OK] = {
.mask = RK818_IRQ_CHG_OK_MSK,
.reg_offset = 1,
},
[RK818_IRQ_CHG_TE] = {
.mask = RK818_IRQ_CHG_TE_MSK,
.reg_offset = 1,
},
[RK818_IRQ_CHG_TS1] = {
.mask = RK818_IRQ_CHG_TS1_MSK,
.reg_offset = 1,
},
[RK818_IRQ_TS2] = {
.mask = RK818_IRQ_TS2_MSK,
.reg_offset = 1,
},
[RK818_IRQ_CHG_CVTLIM] = {
.mask = RK818_IRQ_CHG_CVTLIM_MSK,
.reg_offset = 1,
},
[RK818_IRQ_DISCHG_ILIM] = {
.mask = RK818_IRQ_DISCHG_ILIM_MSK,
.reg_offset = 1,
},
};
static struct regmap_irq_chip rk805_irq_chip = {
.name = "rk805",
.irqs = rk805_irqs,
.num_irqs = ARRAY_SIZE(rk805_irqs),
.num_regs = 1,
.status_base = RK805_INT_STS_REG,
.mask_base = RK805_INT_STS_MSK_REG,
.ack_base = RK805_INT_STS_REG,
.init_ack_masked = true,
};
static const struct regmap_irq_chip rk808_irq_chip = {
.name = "rk808",
.irqs = rk808_irqs,
.num_irqs = ARRAY_SIZE(rk808_irqs),
.num_regs = 2,
.irq_reg_stride = 2,
.status_base = RK808_INT_STS_REG1,
.mask_base = RK808_INT_STS_MSK_REG1,
.ack_base = RK808_INT_STS_REG1,
.init_ack_masked = true,
};
static const struct regmap_irq_chip rk818_irq_chip = {
.name = "rk818",
.irqs = rk818_irqs,
.num_irqs = ARRAY_SIZE(rk818_irqs),
.num_regs = 2,
.irq_reg_stride = 2,
.status_base = RK818_INT_STS_REG1,
.mask_base = RK818_INT_STS_MSK_REG1,
.ack_base = RK818_INT_STS_REG1,
.init_ack_masked = true,
};
static struct i2c_client *rk808_i2c_client;
static void rk805_device_shutdown(void)
{
int ret;
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
if (!rk808) {
dev_warn(&rk808_i2c_client->dev,
"have no rk805, so do nothing here\n");
return;
}
ret = regmap_update_bits(rk808->regmap,
RK805_DEV_CTRL_REG,
DEV_OFF, DEV_OFF);
if (ret)
dev_err(&rk808_i2c_client->dev, "power off error!\n");
}
static void rk808_device_shutdown(void)
{
int ret;
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
if (!rk808) {
dev_warn(&rk808_i2c_client->dev,
"have no rk808, so do nothing here\n");
return;
}
ret = regmap_update_bits(rk808->regmap,
RK808_DEVCTRL_REG,
DEV_OFF_RST, DEV_OFF_RST);
if (ret)
dev_err(&rk808_i2c_client->dev, "power off error!\n");
}
static void rk818_device_shutdown(void)
{
int ret;
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
if (!rk808) {
dev_warn(&rk808_i2c_client->dev,
"have no rk818, so do nothing here\n");
return;
}
ret = regmap_update_bits(rk808->regmap,
RK818_DEVCTRL_REG,
DEV_OFF, DEV_OFF);
if (ret)
dev_err(&rk808_i2c_client->dev, "power off error!\n");
}
static const struct of_device_id rk808_of_match[] = {
{ .compatible = "rockchip,rk805" },
{ .compatible = "rockchip,rk808" },
{ .compatible = "rockchip,rk818" },
{ },
};
MODULE_DEVICE_TABLE(of, rk808_of_match);
static int rk808_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device_node *np = client->dev.of_node;
struct rk808 *rk808;
const struct rk808_reg_data *pre_init_reg;
const struct mfd_cell *cells;
void (*pm_pwroff_fn)(void);
int nr_pre_init_regs;
int nr_cells;
int pm_off = 0, msb, lsb;
int ret;
int i;
rk808 = devm_kzalloc(&client->dev, sizeof(*rk808), GFP_KERNEL);
if (!rk808)
return -ENOMEM;
/* Read chip variant */
msb = i2c_smbus_read_byte_data(client, RK808_ID_MSB);
if (msb < 0) {
dev_err(&client->dev, "failed to read the chip id at 0x%x\n",
RK808_ID_MSB);
return msb;
}
lsb = i2c_smbus_read_byte_data(client, RK808_ID_LSB);
if (lsb < 0) {
dev_err(&client->dev, "failed to read the chip id at 0x%x\n",
RK808_ID_LSB);
return lsb;
}
rk808->variant = ((msb << 8) | lsb) & RK8XX_ID_MSK;
dev_info(&client->dev, "chip id: 0x%x\n", (unsigned int)rk808->variant);
switch (rk808->variant) {
case RK805_ID:
rk808->regmap_cfg = &rk805_regmap_config;
rk808->regmap_irq_chip = &rk805_irq_chip;
pre_init_reg = rk805_pre_init_reg;
nr_pre_init_regs = ARRAY_SIZE(rk805_pre_init_reg);
cells = rk805s;
nr_cells = ARRAY_SIZE(rk805s);
pm_pwroff_fn = rk805_device_shutdown;
break;
case RK808_ID:
rk808->regmap_cfg = &rk808_regmap_config;
rk808->regmap_irq_chip = &rk808_irq_chip;
pre_init_reg = rk808_pre_init_reg;
nr_pre_init_regs = ARRAY_SIZE(rk808_pre_init_reg);
cells = rk808s;
nr_cells = ARRAY_SIZE(rk808s);
pm_pwroff_fn = rk808_device_shutdown;
break;
case RK818_ID:
rk808->regmap_cfg = &rk818_regmap_config;
rk808->regmap_irq_chip = &rk818_irq_chip;
pre_init_reg = rk818_pre_init_reg;
nr_pre_init_regs = ARRAY_SIZE(rk818_pre_init_reg);
cells = rk818s;
nr_cells = ARRAY_SIZE(rk818s);
pm_pwroff_fn = rk818_device_shutdown;
break;
default:
dev_err(&client->dev, "Unsupported RK8XX ID %lu\n",
rk808->variant);
return -EINVAL;
}
rk808->i2c = client;
i2c_set_clientdata(client, rk808);
rk808->regmap = devm_regmap_init_i2c(client, rk808->regmap_cfg);
if (IS_ERR(rk808->regmap)) {
dev_err(&client->dev, "regmap initialization failed\n");
return PTR_ERR(rk808->regmap);
}
if (!client->irq) {
dev_err(&client->dev, "No interrupt support, no core IRQ\n");
return -EINVAL;
}
ret = regmap_add_irq_chip(rk808->regmap, client->irq,
IRQF_ONESHOT, -1,
rk808->regmap_irq_chip, &rk808->irq_data);
if (ret) {
dev_err(&client->dev, "Failed to add irq_chip %d\n", ret);
return ret;
}
for (i = 0; i < nr_pre_init_regs; i++) {
ret = regmap_update_bits(rk808->regmap,
pre_init_reg[i].addr,
pre_init_reg[i].mask,
pre_init_reg[i].value);
if (ret) {
dev_err(&client->dev,
"0x%x write err\n",
pre_init_reg[i].addr);
return ret;
}
}
ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
cells, nr_cells, NULL, 0,
regmap_irq_get_domain(rk808->irq_data));
if (ret) {
dev_err(&client->dev, "failed to add MFD devices %d\n", ret);
goto err_irq;
}
pm_off = of_property_read_bool(np,
"rockchip,system-power-controller");
if (pm_off && !pm_power_off) {
rk808_i2c_client = client;
pm_power_off = pm_pwroff_fn;
}
return 0;
err_irq:
regmap_del_irq_chip(client->irq, rk808->irq_data);
return ret;
}
static int rk808_remove(struct i2c_client *client)
{
struct rk808 *rk808 = i2c_get_clientdata(client);
regmap_del_irq_chip(client->irq, rk808->irq_data);
pm_power_off = NULL;
return 0;
}
static struct i2c_driver rk808_i2c_driver = {
.driver = {
.name = "rk808",
.of_match_table = rk808_of_match,
},
.probe = rk808_probe,
.remove = rk808_remove,
};
module_i2c_driver(rk808_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
MODULE_AUTHOR("Zhang Qing <zhangqing@rock-chips.com>");
MODULE_AUTHOR("Wadim Egorov <w.egorov@phytec.de>");
MODULE_DESCRIPTION("RK808/RK818 PMIC driver");