blob: de03ed307f3888e9e9eca9d041709325c0220d38 [file] [log] [blame]
/*
* Base driver for ASR PM826
*
* Copyright 2024 ASR Microelectronics (Shanghai) Co., Ltd.
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file "COPYING" in the main directory of this
* archive for more details.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/mfd/core.h>
#include <linux/mfd/88pm80x.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/reboot.h>
#include <soc/asr/addr-map.h>
#include <linux/cputype.h>
#define PM826_REG_NUM 0xF3
static struct pm80x_chip *pm826_chip_g;
static const struct i2c_device_id pm80x_id_table[] = {
{"pm826", 0},
{} /* NULL terminated */
};
MODULE_DEVICE_TABLE(i2c, pm80x_id_table);
static const struct of_device_id pm80x_dt_ids[] = {
{ .compatible = "asr,pm826", },
{},
};
MODULE_DEVICE_TABLE(of, pm80x_dt_ids);
static struct mfd_cell dvc_devs[] = {
{
.name = "88pm8xx-dvc",
.of_compatible = "marvell,88pm8xx-dvc",
.id = -1,
},
};
static const struct regmap_config pm826_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = PM826_REG_NUM,
};
int pm826_extern_read(int page, int reg)
{
int ret;
unsigned int val;
struct pm80x_chip *chip = pm826_chip_g;
if (!chip) {
pr_err("%s: chip is NULL\n", __func__);
return -EINVAL;
}
switch (page) {
case PM826_BASE_PAGE:
ret = regmap_read(chip->regmap, reg, &val);
break;
case PM826_POWER_PAGE:
ret = regmap_read(chip->subchip->regmap_power,
reg, &val);
break;
default:
ret = -1;
break;
}
if (ret < 0) {
pr_err("fail to read reg 0x%x\n", reg);
return ret;
}
return val;
}
EXPORT_SYMBOL(pm826_extern_read);
int pm826_extern_write(int page, int reg, unsigned char val)
{
int ret;
struct pm80x_chip *chip = pm826_chip_g;
if (!chip) {
pr_err("%s: chip is NULL\n", __func__);
return -EINVAL;
}
switch (page) {
case PM826_BASE_PAGE:
ret = regmap_write(chip->regmap, reg, val);
break;
case PM826_POWER_PAGE:
ret = regmap_write(chip->subchip->regmap_power,
reg, val);
break;
default:
ret = -1;
break;
}
return ret;
}
EXPORT_SYMBOL(pm826_extern_write);
int pm826_extern_setbits(int page, int reg,
unsigned char mask, unsigned char val)
{
int ret;
struct pm80x_chip *chip = pm826_chip_g;
if (!chip) {
pr_err("%s: chip is NULL\n", __func__);
return -EINVAL;
}
switch (page) {
case PM826_BASE_PAGE:
ret = regmap_update_bits(chip->regmap, reg, mask, val);
break;
case PM826_POWER_PAGE:
ret = regmap_update_bits(chip->subchip->regmap_power,
reg, mask, val);
break;
default:
ret = -1;
break;
}
return ret;
}
EXPORT_SYMBOL(pm826_extern_setbits);
int pm826_init(struct i2c_client *client)
{
struct pm80x_chip *chip;
struct regmap *map;
unsigned int val;
int ret = 0;
chip =
devm_kzalloc(&client->dev, sizeof(struct pm80x_chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
map = devm_regmap_init_i2c(client, &pm826_regmap_config);
if (IS_ERR(map)) {
ret = PTR_ERR(map);
dev_err(&client->dev, "Failed to allocate register map: %d\n",
ret);
return ret;
}
chip->client = client;
chip->regmap = map;
chip->dev = &client->dev;
dev_set_drvdata(chip->dev, chip);
i2c_set_clientdata(chip->client, chip);
ret = regmap_read(chip->regmap, 0x0, &val);
if (ret < 0) {
dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
return ret;
}
chip->chip_id = val;
dev_info(chip->dev, "pm826 id: 0x%x\n", val);
chip->type = CHIP_PM826;
ret = regmap_reinit_cache(chip->regmap, &pm826_regmap_config);
if (ret != 0) {
dev_err(chip->dev, "Failed to reinit register cache: %d\n", ret);
return ret;
}
//device_init_wakeup(&client->dev, 1);
pm826_chip_g = chip;
return 0;
}
int pm826_deinit(void)
{
pm826_chip_g = NULL;
return 0;
}
static int device_dvc_init(struct pm80x_chip *chip,
struct pm80x_platform_data *pdata)
{
int ret;
ret = mfd_add_devices(chip->dev, 0, &dvc_devs[0],
ARRAY_SIZE(dvc_devs), NULL, 0, NULL);
if (ret) {
dev_err(chip->dev, "Failed to add dvc subdev\n");
return ret;
}
return 0;
}
static int pm826_pages_init(struct pm80x_chip *chip)
{
struct pm80x_subchip *subchip;
struct i2c_client *client = chip->client;
int ret = 0;
subchip = chip->subchip;
if (!subchip || !subchip->power_page_addr)
return -ENODEV;
/* PM826 block power page */
subchip->power_page = i2c_new_dummy(client->adapter,
subchip->power_page_addr);
if (subchip->power_page == NULL) {
ret = -ENODEV;
goto out;
}
subchip->regmap_power = devm_regmap_init_i2c(subchip->power_page,
&pm80x_regmap_config);
if (IS_ERR(subchip->regmap_power)) {
ret = PTR_ERR(subchip->regmap_power);
dev_err(chip->dev,
"Failed to allocate regmap_power: %d\n", ret);
goto out;
}
i2c_set_clientdata(subchip->power_page, chip);
out:
return ret;
}
static void pm826_pages_exit(struct pm80x_chip *chip)
{
struct pm80x_subchip *subchip;
subchip = chip->subchip;
if (subchip && subchip->power_page)
i2c_unregister_device(subchip->power_page);
}
static int device_802_init(struct pm80x_chip *chip,
struct pm80x_platform_data *pdata)
{
int ret;
ret = device_dvc_init(chip, pdata);
if (ret)
dev_warn(chip->dev, "Failed to add dvc subdev\n");
return ret;
}
static void parse_status_log(struct pm80x_chip *chip)
{
int powerup, bit;
char *powerup_name[5] = {
"UVLO ",
"OVTEMP ",
"OVP ",
"HICUR_BK1 ",
"HICUR_BK2 ",
};
/*power up log*/
regmap_read(chip->regmap, 0x1, &powerup);
dev_info(chip->dev, "Powerup status 0x1: 0x%x\n", powerup);
printk(KERN_DEBUG " -------------------------------\n");
printk(KERN_DEBUG "| name(status) | status |\n");
printk(KERN_DEBUG "|--------------------|----------|\n");
for (bit = 0; bit < 5; bit++)
printk(KERN_DEBUG "| %s | %x |\n",
powerup_name[bit], (powerup >> bit) & 1);
printk(KERN_DEBUG " -------------------------------\n");
}
static int pm826_init_config(struct pm80x_chip *chip, struct device_node *np)
{
if (!chip || !chip->regmap || !chip->subchip
|| !chip->subchip->regmap_power) {
pr_err("%s:chip is not availiable!\n", __func__);
return -EINVAL;
}
/* dump power up log */
parse_status_log(chip);
return 0;
}
static int pm826_dt_init(struct device_node *np,
struct device *dev,
struct pm80x_platform_data *pdata)
{
return 0;
}
static int pm826_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret = 0;
struct pm80x_chip *chip;
struct pm80x_platform_data *pdata = client->dev.platform_data;
struct device_node *node = client->dev.of_node;
struct pm80x_subchip *subchip;
if (IS_ENABLED(CONFIG_OF)) {
if (!pdata) {
pdata = devm_kzalloc(&client->dev,
sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
}
ret = pm826_dt_init(node, &client->dev, pdata);
if (ret)
return ret;
} else if (!pdata) {
return -EINVAL;
}
ret = pm826_init(client);
if (ret) {
dev_err(&client->dev, "PM826_init fail\n");
goto out_init;
}
chip = i2c_get_clientdata(client);
if (chip->type != CHIP_PM826) {
dev_err(&client->dev, "PM826 not present\n");
ret = -ENODEV;
goto out_init;
}
/* init subchip for PM826 */
subchip =
devm_kzalloc(&client->dev, sizeof(struct pm80x_subchip),
GFP_KERNEL);
if (!subchip) {
ret = -ENOMEM;
goto err_subchip_alloc;
}
/* PM826 has 2 addtional pages to support power. */
subchip->power_page_addr = client->addr + 1;
chip->subchip = subchip;
ret = pm826_pages_init(chip);
if (ret) {
dev_err(&client->dev, "PM826_pages_init failed!\n");
goto err_page_init;
}
ret = device_802_init(chip, pdata);
if (ret) {
dev_err(chip->dev, "Failed to initialize 88PM826 devices\n");
goto err_device_init;
}
if (pdata && pdata->plat_config)
pdata->plat_config(chip, pdata);
pm826_init_config(chip, NULL);
return 0;
err_device_init:
pm826_pages_exit(chip);
err_page_init:
err_subchip_alloc:
pm826_deinit();
out_init:
return ret;
}
static int pm826_remove(struct i2c_client *client)
{
struct pm80x_chip *chip = i2c_get_clientdata(client);
mfd_remove_devices(chip->dev);
pm826_pages_exit(chip);
pm80x_deinit();
return 0;
}
static struct i2c_driver pm826_driver = {
.driver = {
.name = "ASRPM826",
.owner = THIS_MODULE,
.pm = &pm80x_pm_ops,
.of_match_table = of_match_ptr(pm80x_dt_ids),
},
.probe = pm826_probe,
.remove = pm826_remove,
.id_table = pm80x_id_table,
};
static int __init pm826_i2c_init(void)
{
return i2c_add_driver(&pm826_driver);
}
subsys_initcall(pm826_i2c_init);
static void __exit pm826_i2c_exit(void)
{
i2c_del_driver(&pm826_driver);
}
module_exit(pm826_i2c_exit);
MODULE_DESCRIPTION("PMIC Driver for ASR PM826");
MODULE_LICENSE("GPL");