blob: 8dce4130047978583ddd163972b614919c2281b8 [file] [log] [blame]
/*
* drivers/amlogic/power/aml_pmu/aml_pmu.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/interrupt.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/amlogic/aml_pmu.h>
#ifdef CONFIG_AMLOGIC_M8B_DVFS
#include <linux/amlogic/aml_dvfs.h>
#endif
#ifdef CONFIG_AMLOGIC_1218
struct i2c_client *g_aml1218_client;
#endif
static const struct i2c_device_id aml_pmu_id_table[] = {
#ifdef CONFIG_AMLOGIC_1218
{ AML1218_DRIVER_NAME, 2},
#endif
{},
};
MODULE_DEVICE_TABLE(i2c, aml_pmu_id_table);
#ifdef CONFIG_OF
#define DEBUG_TREE 0
#define DEBUG_PARSE 0
#define DBG(format, args...) pr_info("[AML_PMU]%s, "format, __func__, ##args)
static struct i2c_device_id *find_id_by_name(const struct i2c_device_id *l,
const char *name)
{
while (l->name && l->name[0]) {
pr_info("table name:%s, name:%s\n", l->name, name);
if (!strcmp(l->name, name))
return (struct i2c_device_id *)l;
l++;
}
return NULL;
}
#endif /* CONFIG_OF */
#if defined(CONFIG_AMLOGIC_M8B_DVFS) && defined(CONFIG_AMLOGIC_1218)
static int aml_1218_convert_id_to_dcdc(uint32_t id)
{
int dcdc = 0;
switch (id) {
case AML_DVFS_ID_VCCK:
dcdc = 4;
break;
case AML_DVFS_ID_VDDEE:
dcdc = 2;
break;
case AML_DVFS_ID_DDR:
dcdc = 3;
break;
default:
break;
}
return dcdc;
}
static int aml1218_set_voltage(uint32_t id, uint32_t min_uV, uint32_t max_uV)
{
int dcdc = aml_1218_convert_id_to_dcdc(id);
uint32_t vol = 0;
if (min_uV > max_uV)
return -1;
vol = (min_uV + max_uV) / 2;
if (dcdc >= 1 && dcdc <= 4)
return aml1218_set_dcdc_voltage(dcdc, vol);
return -EINVAL;
}
static int aml1218_get_voltage(uint32_t id, uint32_t *uV)
{
int dcdc = aml_1218_convert_id_to_dcdc(id);
if (dcdc >= 1 && dcdc <= 4)
return aml1218_get_dcdc_voltage(dcdc, uV);
return -EINVAL;
}
struct aml_dvfs_driver aml1218_dvfs_driver = {
.name = "aml1218-dvfs",
.id_mask = (AML_DVFS_ID_VCCK | AML_DVFS_ID_VDDEE | AML_DVFS_ID_DDR),
.set_voltage = aml1218_set_voltage,
.get_voltage = aml1218_get_voltage,
};
#endif
static int aml_pmu_check_device(struct i2c_client *client)
{
int ret;
uint8_t buf[2] = {};
struct i2c_msg msg[] = {
{
.addr = client->addr & 0xff,
.flags = 0,
.len = sizeof(buf),
.buf = buf,
},
{
.addr = client->addr & 0xff,
.flags = I2C_M_RD,
.len = 1,
.buf = &buf[1],
}
};
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0) {
DBG("%s: i2c transfer for %x failed, ret:%d\n",
__func__, client->addr, ret);
return ret;
}
return 0;
}
static int aml_pmu_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
#ifdef CONFIG_OF
const char *subtype;
struct device_node *node;
struct i2c_device_id *type = NULL;
int ret;
if (aml_pmu_check_device(client))
return -ENODEV;
node = client->dev.of_node;
ret = of_property_read_string(node, "sub_type", &subtype);
if (ret) {
pr_err("can't find subtype\n");
return -ENODEV;
}
type = find_id_by_name(aml_pmu_id_table, subtype);
if (!type) { /* sub type is not supported */
DBG("sub_type of '%s' is not match, abort\n", subtype);
goto out_free_chip;
}
#ifdef CONFIG_AMLOGIC_1218
if (type->driver_data == 2) {
g_aml1218_client = client;
#if defined(CONFIG_AMLOGIC_M8B_DVFS) && defined(CONFIG_AMLOGIC_1218)
aml_dvfs_register_driver(&aml1218_dvfs_driver);
#endif
}
#endif
out_free_chip:
#endif /* CONFIG_OF */
return 0;
}
static int aml_pmu_remove(struct i2c_client *client)
{
struct platform_device *pdev = i2c_get_clientdata(client);
#ifdef CONFIG_AMLOGIC_1218
g_aml1218_client = NULL;
#if defined(CONFIG_AMLOGIC_M8B_DVFS) && defined(CONFIG_AMLOGIC_1218)
aml_dvfs_unregister_driver(&aml1218_dvfs_driver);
#endif
#endif
platform_device_del(pdev);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id amlogic_pmu_match_id = {
.compatible = "amlogic, amlogic_pmu",
};
#endif
static struct i2c_driver aml_pmu_driver = {
.driver = {
.name = "amlogic_pmu",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = &amlogic_pmu_match_id,
#endif
},
.probe = aml_pmu_probe,
.remove = aml_pmu_remove,
.id_table = aml_pmu_id_table,
};
static int __init aml_pmu_init(void)
{
pr_info("%s, %d\n", __func__, __LINE__);
return i2c_add_driver(&aml_pmu_driver);
}
subsys_initcall(aml_pmu_init);
static void __exit aml_pmu_exit(void)
{
i2c_del_driver(&aml_pmu_driver);
}
module_exit(aml_pmu_exit);
MODULE_DESCRIPTION("Amlogic PMU device driver");
MODULE_AUTHOR("tao.zeng@amlogic.com");
MODULE_LICENSE("GPL");