406 lines
9.8 KiB
C
406 lines
9.8 KiB
C
|
#include <linux/interrupt.h>
|
||
|
#include <linux/irq.h>
|
||
|
#include <linux/reboot.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/i2c.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/mfd/core.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_irq.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/power/aw_pm.h>
|
||
|
#include "../axp-core.h"
|
||
|
#include "../axp-charger.h"
|
||
|
#include "../axp-regulator.h"
|
||
|
#include "pmu1736.h"
|
||
|
#include "pmu1736-regu.h"
|
||
|
|
||
|
static struct axp_dev *pmu1736_pm_power;
|
||
|
struct axp_config_info pmu1736_config;
|
||
|
struct wakeup_source *pmu1736_ws;
|
||
|
static int pmu1736_pmu_num;
|
||
|
|
||
|
static const struct axp_compatible_name_mapping pmu1736_cn_mapping[] = {
|
||
|
{
|
||
|
.device_name = "pmu1736",
|
||
|
.mfd_name = {
|
||
|
.powerkey_name = "pmu1736-powerkey",
|
||
|
.regulator_name = "pmu1736-regulator",
|
||
|
.gpio_name = "pmu1736-gpio",
|
||
|
},
|
||
|
},
|
||
|
};
|
||
|
static struct axp_regmap_irq_chip pmu1736_regmap_irq_chip = {
|
||
|
.name = "pmu1736_irq_chip",
|
||
|
.status_base = PMU1736_INTSTS1,
|
||
|
.enable_base = PMU1736_INTEN1,
|
||
|
.num_regs = 3,
|
||
|
|
||
|
};
|
||
|
|
||
|
static struct resource pmu1736_pek_resources[] = {
|
||
|
{PMU1736_IRQ_PEKRE, PMU1736_IRQ_PEKRE, "PEK_DBR", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_PEKFE, PMU1736_IRQ_PEKFE, "PEK_DBF", IORESOURCE_IRQ,},
|
||
|
};
|
||
|
|
||
|
static struct resource pmu1736_regulator_resources[] = {
|
||
|
{PMU1736_IRQ_DC6UN, PMU1736_IRQ_DC6UN, "dc6 under 85", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_DC5UN, PMU1736_IRQ_DC5UN, "dc5 under 85", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_DC4UN, PMU1736_IRQ_DC4UN, "dc4 under 85", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_DC3UN, PMU1736_IRQ_DC3UN, "dc3 under 85", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_DC2UN, PMU1736_IRQ_DC2UN, "dc2 under 85", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_DC1UN, PMU1736_IRQ_DC1UN, "dc1 under 85", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_LOWN1, PMU1736_IRQ_LOWN1, "low warning1", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_LOWN2, PMU1736_IRQ_LOWN2, "low warning2", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_DC3OV, PMU1736_IRQ_DC3OV, "dc3 over cur", IORESOURCE_IRQ,},
|
||
|
{PMU1736_IRQ_DC2OV, PMU1736_IRQ_DC2OV, "dc2 over cur", IORESOURCE_IRQ,},
|
||
|
};
|
||
|
|
||
|
static struct mfd_cell pmu1736_cells[] = {
|
||
|
{
|
||
|
.name = "pmu1736-powerkey",
|
||
|
.num_resources = ARRAY_SIZE(pmu1736_pek_resources),
|
||
|
.resources = pmu1736_pek_resources,
|
||
|
},
|
||
|
{
|
||
|
.name = "pmu1736-regulator",
|
||
|
.num_resources = ARRAY_SIZE(pmu1736_regulator_resources),
|
||
|
.resources = pmu1736_regulator_resources,
|
||
|
},
|
||
|
{
|
||
|
.name = "pmu1736-gpio",
|
||
|
},
|
||
|
};
|
||
|
|
||
|
void pmu1736_power_off(void)
|
||
|
{
|
||
|
pr_info("[axp] send power-off command!\n");
|
||
|
axp_regmap_write(pmu1736_pm_power->regmap,
|
||
|
PMU1736_PWR_DISABLE_DOWN, 0x80);
|
||
|
}
|
||
|
|
||
|
static int pmu1736_init_chip(struct axp_dev *pmu1736)
|
||
|
{
|
||
|
uint8_t chip_id;
|
||
|
int err;
|
||
|
|
||
|
err = axp_regmap_read(pmu1736->regmap, PMU1736_IC_TYPE, &chip_id);
|
||
|
if (err) {
|
||
|
pr_err("[%s] try to read chip id failed!\n",
|
||
|
axp_name[pmu1736_pmu_num]);
|
||
|
// return err;
|
||
|
}
|
||
|
if (((chip_id & 0xc0) == 0x40) &&
|
||
|
((chip_id & 0x0f) == 0x04)
|
||
|
) {
|
||
|
pr_info("[%s] chip id detect 0x%x !\n",
|
||
|
axp_name[pmu1736_pmu_num], chip_id);
|
||
|
} else {
|
||
|
pr_info("[%s] chip id not detect 0x%x !\n",
|
||
|
axp_name[pmu1736_pmu_num], chip_id);
|
||
|
}
|
||
|
|
||
|
/* enable dcdc2 dvm */
|
||
|
err = axp_regmap_update(pmu1736->regmap, PMU1736_DCDC_MODE_CTL1,
|
||
|
0x4, 0x4);
|
||
|
if (err) {
|
||
|
pr_err("[%s] enable dcdc2 dvm failed!\n",
|
||
|
axp_name[pmu1736_pmu_num]);
|
||
|
return err;
|
||
|
} else {
|
||
|
pr_info("[%s] enable dcdc2 dvm.\n",
|
||
|
axp_name[pmu1736_pmu_num]);
|
||
|
}
|
||
|
|
||
|
/*init irq wakeup en*/
|
||
|
if (pmu1736_config.pmu_irq_wakeup)
|
||
|
axp_regmap_set_bits(pmu1736->regmap,
|
||
|
PMU1736_IRQ_PWROK_VOFF, 0x80);
|
||
|
else
|
||
|
axp_regmap_clr_bits(pmu1736->regmap,
|
||
|
PMU1736_IRQ_PWROK_VOFF, 0x80);
|
||
|
|
||
|
/*init pmu over temperature protection*/
|
||
|
if (pmu1736_config.pmu_hot_shutdown)
|
||
|
axp_regmap_set_bits(pmu1736->regmap,
|
||
|
PMU1736_PWR_DISABLE_DOWN, 0x02);
|
||
|
else
|
||
|
axp_regmap_clr_bits(pmu1736->regmap,
|
||
|
PMU1736_PWR_DISABLE_DOWN, 0x02);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void pmu1736_wakeup_event(void)
|
||
|
{
|
||
|
__pm_wakeup_event(pmu1736_ws, 0);
|
||
|
}
|
||
|
|
||
|
static s32 pmu1736_usb_det(void)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static s32 pmu1736_usb_vbus_output(int high)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const char *pmu1736_get_pmu_name(void)
|
||
|
{
|
||
|
return axp_name[pmu1736_pmu_num];
|
||
|
}
|
||
|
|
||
|
static struct axp_dev *pmu1736_get_pmu_dev(void)
|
||
|
{
|
||
|
return pmu1736_pm_power;
|
||
|
}
|
||
|
|
||
|
struct axp_platform_ops pmu1736_platform_ops = {
|
||
|
.usb_det = pmu1736_usb_det,
|
||
|
.usb_vbus_output = pmu1736_usb_vbus_output,
|
||
|
.get_pmu_name = pmu1736_get_pmu_name,
|
||
|
.get_pmu_dev = pmu1736_get_pmu_dev,
|
||
|
|
||
|
};
|
||
|
|
||
|
static struct of_device_id pmu1736_match[] = {
|
||
|
{ .compatible = "pmu1736", },
|
||
|
{},
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, pmu1736_match);
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
static int pmu1736_probe(struct i2c_client *client,
|
||
|
const struct i2c_device_id *id)
|
||
|
#else
|
||
|
static int pmu1736_probe(struct platform_device *pdev)
|
||
|
#endif
|
||
|
{
|
||
|
int ret;
|
||
|
struct axp_dev *pmu1736;
|
||
|
struct device_node *node;
|
||
|
struct device *device;
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
node = client->dev.of_node;
|
||
|
device = &client->dev;
|
||
|
#else
|
||
|
node = pdev->dev.of_node;
|
||
|
device = &pdev->dev;
|
||
|
#endif
|
||
|
|
||
|
pmu1736_pmu_num = axp_get_pmu_num(pmu1736_cn_mapping,
|
||
|
ARRAY_SIZE(pmu1736_cn_mapping));
|
||
|
if (pmu1736_pmu_num < 0) {
|
||
|
pr_err("%s: get pmu num failed\n", __func__);
|
||
|
return pmu1736_pmu_num;
|
||
|
}
|
||
|
|
||
|
if (node) {
|
||
|
/* get dt and sysconfig */
|
||
|
if (!of_device_is_available(node)) {
|
||
|
pmu1736_config.pmu_used = 0;
|
||
|
pr_err("%s: pmu_used = %u\n", __func__,
|
||
|
pmu1736_config.pmu_used);
|
||
|
return -EPERM;
|
||
|
} else {
|
||
|
pmu1736_config.pmu_used = 1;
|
||
|
ret = axp_dt_parse(node, pmu1736_pmu_num,
|
||
|
&pmu1736_config);
|
||
|
if (ret) {
|
||
|
pr_err("%s parse device tree err\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
pr_err("pmu1736 device tree err!\n");
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
|
||
|
pmu1736 = devm_kzalloc(device, sizeof(*pmu1736), GFP_KERNEL);
|
||
|
if (!pmu1736)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
pmu1736->dev = device;
|
||
|
pmu1736->nr_cells = ARRAY_SIZE(pmu1736_cells);
|
||
|
pmu1736->cells = pmu1736_cells;
|
||
|
pmu1736->pmu_num = pmu1736_pmu_num;
|
||
|
pmu1736->is_slave = pmu1736_config.pmu_as_slave;
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
pmu1736->irq = client->irq;
|
||
|
#else
|
||
|
pmu1736->irq = platform_get_irq(pdev, 0);
|
||
|
#endif
|
||
|
if (pmu1736->irq < 0) {
|
||
|
pr_err("pmu1736 get irq error!\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = axp_mfd_cell_name_init(pmu1736_cn_mapping,
|
||
|
ARRAY_SIZE(pmu1736_cn_mapping), pmu1736->pmu_num,
|
||
|
pmu1736->nr_cells, pmu1736->cells);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
pmu1736->regmap = axp_regmap_init_i2c(device);
|
||
|
#else
|
||
|
pmu1736->regmap = axp_regmap_init_arisc_rsb(device, 0x2d);
|
||
|
#endif
|
||
|
if (IS_ERR(pmu1736->regmap)) {
|
||
|
ret = PTR_ERR(pmu1736->regmap);
|
||
|
dev_err(device, "regmap init failed: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
i2c_set_clientdata(client, pmu1736);
|
||
|
#else
|
||
|
platform_set_drvdata(pdev, pmu1736);
|
||
|
#endif
|
||
|
ret = pmu1736_init_chip(pmu1736);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = axp_mfd_add_devices(pmu1736);
|
||
|
if (ret) {
|
||
|
dev_err(pmu1736->dev, "failed to add MFD devices: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
pmu1736->irq = gpio_to_irq(40); //i2s0 mclk
|
||
|
pmu1736->irq_data = axp_irq_chip_register(pmu1736->regmap,
|
||
|
pmu1736->irq,
|
||
|
IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||
|
&pmu1736_regmap_irq_chip, NULL);
|
||
|
#else
|
||
|
if (pmu1736->is_slave) {
|
||
|
pmu1736->irq_data = axp_irq_chip_register(pmu1736->regmap,
|
||
|
pmu1736->irq,
|
||
|
IRQF_SHARED | IRQF_NO_SUSPEND,
|
||
|
&pmu1736_regmap_irq_chip,
|
||
|
NULL);
|
||
|
} else {
|
||
|
pmu1736->irq_data = axp_irq_chip_register(pmu1736->regmap,
|
||
|
pmu1736->irq,
|
||
|
IRQF_SHARED | IRQF_NO_SUSPEND,
|
||
|
&pmu1736_regmap_irq_chip,
|
||
|
pmu1736_wakeup_event);
|
||
|
}
|
||
|
|
||
|
if (IS_ERR(pmu1736->irq_data)) {
|
||
|
ret = PTR_ERR(pmu1736->irq_data);
|
||
|
dev_err(device, "axp init irq failed: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
pmu1736_pm_power = pmu1736;
|
||
|
if (!pmu1736->is_slave) {
|
||
|
if (!pm_power_off)
|
||
|
pm_power_off = pmu1736_power_off;
|
||
|
}
|
||
|
|
||
|
axp_platform_ops_set(pmu1736->pmu_num, &pmu1736_platform_ops);
|
||
|
|
||
|
if (!pmu1736->is_slave)
|
||
|
pmu1736_ws = wakeup_source_register("pmu1736_wakeup_source");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
static int pmu1736_remove(struct i2c_client *client)
|
||
|
#else
|
||
|
static int pmu1736_remove(struct platform_device *pdev)
|
||
|
#endif
|
||
|
{
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
struct axp_dev *pmu1736 = i2c_get_clientdata(client);
|
||
|
#else
|
||
|
struct axp_dev *pmu1736 = platform_get_drvdata(pdev);
|
||
|
#endif
|
||
|
|
||
|
if (pmu1736 == pmu1736_pm_power) {
|
||
|
pmu1736_pm_power = NULL;
|
||
|
pm_power_off = NULL;
|
||
|
}
|
||
|
|
||
|
axp_mfd_remove_devices(pmu1736);
|
||
|
axp_irq_chip_unregister(pmu1736->irq, pmu1736->irq_data);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct i2c_device_id pmu1736_id_table[] = {
|
||
|
{ "pmu1736", 0 },
|
||
|
};
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
static const unsigned short pmu1736_i2c_addr[] = {
|
||
|
0x6c,
|
||
|
I2C_CLIENT_END,
|
||
|
};
|
||
|
|
||
|
static int pmu1736_i2c_detect(struct i2c_client *client,
|
||
|
struct i2c_board_info *info)
|
||
|
{
|
||
|
struct i2c_adapter *adapter = client->adapter;
|
||
|
|
||
|
if (adapter->nr == 0) {
|
||
|
strlcpy(info->type, "pmu1736", I2C_NAME_SIZE);
|
||
|
return 0;
|
||
|
}
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
#endif
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
static struct i2c_driver pmu1736_driver = {
|
||
|
#else
|
||
|
static struct platform_driver pmu1736_driver = {
|
||
|
#endif
|
||
|
.driver = {
|
||
|
.name = "pmu1736",
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = pmu1736_match,
|
||
|
},
|
||
|
.probe = pmu1736_probe,
|
||
|
.remove = pmu1736_remove,
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
.id_table = pmu1736_id_table,
|
||
|
.address_list = pmu1736_i2c_addr,
|
||
|
.detect = pmu1736_i2c_detect,
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static int __init pmu1736_init(void)
|
||
|
{
|
||
|
int ret;
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
ret = i2c_add_driver(&pmu1736_driver);
|
||
|
#else
|
||
|
ret = platform_driver_register(&pmu1736_driver);
|
||
|
#endif
|
||
|
if (ret != 0)
|
||
|
pr_err("Failed to register pmu1736 driver: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
subsys_initcall(pmu1736_init);
|
||
|
|
||
|
static void __exit pmu1736_exit(void)
|
||
|
{
|
||
|
#ifdef CONFIG_AXP_TWI_USED
|
||
|
i2c_del_driver(&pmu1736_driver);
|
||
|
#else
|
||
|
platform_driver_unregister(&pmu1736_driver);
|
||
|
#endif
|
||
|
}
|
||
|
module_exit(pmu1736_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION("PMIC Driver for PMU1736");
|
||
|
MODULE_AUTHOR("Qin <qinyongshen@allwinnertech.com>");
|
||
|
MODULE_LICENSE("GPL");
|