forked from Minki/linux
2adb3b8e6f
Let's define the voltatile registers (those that can't be cached) and enable caching. The rk808 is accessed almost constantly with cpufreq so this is really nice. As measured by ftrace: before this change: cpu0_set_target() => ~2200us after this change: cpu0_set_target() => ~500us Signed-off-by: Doug Anderson <dianders@chromium.org> Reviewed-by: Chris Zhong <zyw@rock-chips.com> Signed-off-by: Lee Jones <lee.jones@linaro.org>
276 lines
6.6 KiB
C
276 lines
6.6 KiB
C
/*
|
|
* MFD core driver for Rockchip RK808
|
|
*
|
|
* Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
|
|
*
|
|
* Author: Chris Zhong <zyw@rock-chips.com>
|
|
* Author: Zhang Qing <zhangqing@rock-chips.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/mfd/rk808.h>
|
|
#include <linux/mfd/core.h>
|
|
#include <linux/module.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 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 const struct mfd_cell rk808s[] = {
|
|
{ .name = "rk808-clkout", },
|
|
{ .name = "rk808-regulator", },
|
|
{
|
|
.name = "rk808-rtc",
|
|
.num_resources = ARRAY_SIZE(rtc_resources),
|
|
.resources = &rtc_resources[0],
|
|
},
|
|
};
|
|
|
|
static const struct rk808_reg_data 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_VB_MON_REG, MASK_ALL, VB_LO_ACT |
|
|
VB_LO_SEL_3500MV },
|
|
};
|
|
|
|
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 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 struct i2c_client *rk808_i2c_client;
|
|
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 int rk808_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct device_node *np = client->dev.of_node;
|
|
struct rk808 *rk808;
|
|
int pm_off = 0;
|
|
int ret;
|
|
int i;
|
|
|
|
if (!client->irq) {
|
|
dev_err(&client->dev, "No interrupt support, no core IRQ\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rk808 = devm_kzalloc(&client->dev, sizeof(*rk808), GFP_KERNEL);
|
|
if (!rk808)
|
|
return -ENOMEM;
|
|
|
|
rk808->regmap = devm_regmap_init_i2c(client, &rk808_regmap_config);
|
|
if (IS_ERR(rk808->regmap)) {
|
|
dev_err(&client->dev, "regmap initialization failed\n");
|
|
return PTR_ERR(rk808->regmap);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pre_init_reg); 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 = regmap_add_irq_chip(rk808->regmap, client->irq,
|
|
IRQF_ONESHOT, -1,
|
|
&rk808_irq_chip, &rk808->irq_data);
|
|
if (ret) {
|
|
dev_err(&client->dev, "Failed to add irq_chip %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
rk808->i2c = client;
|
|
i2c_set_clientdata(client, rk808);
|
|
|
|
ret = mfd_add_devices(&client->dev, -1,
|
|
rk808s, ARRAY_SIZE(rk808s),
|
|
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 = rk808_device_shutdown;
|
|
}
|
|
|
|
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);
|
|
mfd_remove_devices(&client->dev);
|
|
pm_power_off = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct of_device_id rk808_of_match[] = {
|
|
{ .compatible = "rockchip,rk808" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rk808_of_match);
|
|
|
|
static const struct i2c_device_id rk808_ids[] = {
|
|
{ "rk808" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, rk808_ids);
|
|
|
|
static struct i2c_driver rk808_i2c_driver = {
|
|
.driver = {
|
|
.name = "rk808",
|
|
.of_match_table = rk808_of_match,
|
|
},
|
|
.probe = rk808_probe,
|
|
.remove = rk808_remove,
|
|
.id_table = rk808_ids,
|
|
};
|
|
|
|
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_DESCRIPTION("RK808 PMIC driver");
|