351 lines
8.2 KiB
C
351 lines
8.2 KiB
C
|
/* Copyright (c) 2010,2011 The Linux Foundation. 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 version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
*/
|
||
|
/*
|
||
|
* Qualcomm PM8XXX UPL driver
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/mfd/pm8xxx/core.h>
|
||
|
#include <linux/mfd/pm8xxx/upl.h>
|
||
|
|
||
|
/* PMIC8XXX UPL registers */
|
||
|
#define SSBI_REG_UPL_CTRL 0x17B
|
||
|
#define SSBI_REG_UPL_TRUTHTABLE1 0x17C
|
||
|
#define SSBI_REG_UPL_TRUTHTABLE2 0x17D
|
||
|
|
||
|
struct pm8xxx_upl_device {
|
||
|
struct device *dev;
|
||
|
struct mutex upl_mutex;
|
||
|
#if defined(CONFIG_DEBUG_FS)
|
||
|
struct dentry *dent;
|
||
|
#endif
|
||
|
};
|
||
|
static struct pm8xxx_upl_device *upl_dev;
|
||
|
|
||
|
/* APIs */
|
||
|
|
||
|
/*
|
||
|
* pm8xxx_upl_request - request a handle to access UPL device
|
||
|
*/
|
||
|
struct pm8xxx_upl_device *pm8xxx_upl_request(void)
|
||
|
{
|
||
|
return upl_dev;
|
||
|
}
|
||
|
EXPORT_SYMBOL(pm8xxx_upl_request);
|
||
|
|
||
|
/*
|
||
|
* pm8xxx_upl_read_truthtable - read value currently stored in UPL truth table
|
||
|
*
|
||
|
* @upldev: the UPL device
|
||
|
* @truthtable: value read from UPL truth table
|
||
|
*/
|
||
|
int pm8xxx_upl_read_truthtable(struct pm8xxx_upl_device *upldev,
|
||
|
u16 *truthtable)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
u8 table[2];
|
||
|
|
||
|
if (upldev == NULL || IS_ERR(upldev))
|
||
|
return -EINVAL;
|
||
|
|
||
|
mutex_lock(&upldev->upl_mutex);
|
||
|
|
||
|
rc = pm8xxx_readb(upldev->dev->parent, SSBI_REG_UPL_TRUTHTABLE1,
|
||
|
&(table[0]));
|
||
|
if (rc) {
|
||
|
pr_err("%s: FAIL pm8xxx_readb(0x%X)=0x%02X: rc=%d\n",
|
||
|
__func__, SSBI_REG_UPL_TRUTHTABLE1, table[0], rc);
|
||
|
goto upl_read_done;
|
||
|
}
|
||
|
|
||
|
rc = pm8xxx_readb(upldev->dev->parent, SSBI_REG_UPL_TRUTHTABLE2,
|
||
|
&(table[1]));
|
||
|
if (rc)
|
||
|
pr_err("%s: FAIL pm8xxx_readb(0x%X)=0x%02X: rc=%d\n",
|
||
|
__func__, SSBI_REG_UPL_TRUTHTABLE2, table[1], rc);
|
||
|
upl_read_done:
|
||
|
mutex_unlock(&upldev->upl_mutex);
|
||
|
*truthtable = (((u16)table[1]) << 8) | table[0];
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL(pm8xxx_upl_read_truthtable);
|
||
|
|
||
|
/*
|
||
|
* pm8xxx_upl_writes_truthtable - write value into UPL truth table
|
||
|
*
|
||
|
* @upldev: the UPL device
|
||
|
* @truthtable: value written to UPL truth table
|
||
|
*
|
||
|
* Each bit in parameter "truthtable" corresponds to the UPL output for a given
|
||
|
* set of input pin values. For example, if the input pins have the following
|
||
|
* values: A=1, B=1, C=1, D=0, then the UPL would output the value of bit 14
|
||
|
* (0b1110) in parameter "truthtable".
|
||
|
*/
|
||
|
int pm8xxx_upl_write_truthtable(struct pm8xxx_upl_device *upldev,
|
||
|
u16 truthtable)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
u8 table[2];
|
||
|
|
||
|
if (upldev == NULL || IS_ERR(upldev))
|
||
|
return -EINVAL;
|
||
|
|
||
|
table[0] = truthtable & 0xFF;
|
||
|
table[1] = (truthtable >> 8) & 0xFF;
|
||
|
|
||
|
mutex_lock(&upldev->upl_mutex);
|
||
|
|
||
|
rc = pm8xxx_writeb(upldev->dev->parent, SSBI_REG_UPL_TRUTHTABLE1,
|
||
|
table[0]);
|
||
|
if (rc) {
|
||
|
pr_err("%s: FAIL pm8xxx_writeb(0x%X)=0x%04X: rc=%d\n",
|
||
|
__func__, SSBI_REG_UPL_TRUTHTABLE1, table[0], rc);
|
||
|
goto upl_write_done;
|
||
|
}
|
||
|
|
||
|
rc = pm8xxx_writeb(upldev->dev->parent, SSBI_REG_UPL_TRUTHTABLE2,
|
||
|
table[1]);
|
||
|
if (rc)
|
||
|
pr_err("%s: FAIL pm8xxx_writeb(0x%X)=0x%04X: rc=%d\n",
|
||
|
__func__, SSBI_REG_UPL_TRUTHTABLE2, table[1], rc);
|
||
|
upl_write_done:
|
||
|
mutex_unlock(&upldev->upl_mutex);
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL(pm8xxx_upl_write_truthtable);
|
||
|
|
||
|
/*
|
||
|
* pm8xxx_upl_config - configure UPL I/O settings and UPL enable/disable
|
||
|
*
|
||
|
* @upldev: the UPL device
|
||
|
* @mask: setting mask to configure
|
||
|
* @flags: setting flags
|
||
|
*/
|
||
|
int pm8xxx_upl_config(struct pm8xxx_upl_device *upldev, u32 mask, u32 flags)
|
||
|
{
|
||
|
int rc;
|
||
|
u8 upl_ctrl, m, f;
|
||
|
|
||
|
if (upldev == NULL || IS_ERR(upldev))
|
||
|
return -EINVAL;
|
||
|
|
||
|
mutex_lock(&upldev->upl_mutex);
|
||
|
|
||
|
rc = pm8xxx_readb(upldev->dev->parent, SSBI_REG_UPL_CTRL, &upl_ctrl);
|
||
|
if (rc) {
|
||
|
pr_err("%s: FAIL pm8xxx_readb(0x%X)=0x%02X: rc=%d\n",
|
||
|
__func__, SSBI_REG_UPL_CTRL, upl_ctrl, rc);
|
||
|
goto upl_config_done;
|
||
|
}
|
||
|
|
||
|
m = mask & 0x00ff;
|
||
|
f = flags & 0x00ff;
|
||
|
upl_ctrl &= ~m;
|
||
|
upl_ctrl |= m & f;
|
||
|
|
||
|
rc = pm8xxx_writeb(upldev->dev->parent, SSBI_REG_UPL_CTRL, upl_ctrl);
|
||
|
if (rc)
|
||
|
pr_err("%s: FAIL pm8xxx_writeb(0x%X)=0x%02X: rc=%d\n",
|
||
|
__func__, SSBI_REG_UPL_CTRL, upl_ctrl, rc);
|
||
|
upl_config_done:
|
||
|
mutex_unlock(&upldev->upl_mutex);
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL(pm8xxx_upl_config);
|
||
|
|
||
|
#if defined(CONFIG_DEBUG_FS)
|
||
|
|
||
|
static int truthtable_set(void *data, u64 val)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
rc = pm8xxx_upl_write_truthtable(data, val);
|
||
|
if (rc)
|
||
|
pr_err("%s: pm8xxx_upl_write_truthtable: rc=%d, "
|
||
|
"truthtable=0x%llX\n", __func__, rc, val);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int truthtable_get(void *data, u64 *val)
|
||
|
{
|
||
|
int rc;
|
||
|
u16 truthtable;
|
||
|
|
||
|
rc = pm8xxx_upl_read_truthtable(data, &truthtable);
|
||
|
if (rc)
|
||
|
pr_err("%s: pm8xxx_upl_read_truthtable: rc=%d, "
|
||
|
"truthtable=0x%X\n", __func__, rc, truthtable);
|
||
|
if (val)
|
||
|
*val = truthtable;
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
DEFINE_SIMPLE_ATTRIBUTE(upl_truthtable_fops, truthtable_get,
|
||
|
truthtable_set, "0x%04llX\n");
|
||
|
|
||
|
/* enter values as 0xMMMMFFFF where MMMM is the mask and FFFF is the flags */
|
||
|
static int control_set(void *data, u64 val)
|
||
|
{
|
||
|
u8 mask, flags;
|
||
|
int rc;
|
||
|
|
||
|
flags = val & 0xFFFF;
|
||
|
mask = (val >> 16) & 0xFFFF;
|
||
|
|
||
|
rc = pm8xxx_upl_config(data, mask, flags);
|
||
|
if (rc)
|
||
|
pr_err("%s: pm8xxx_upl_config: rc=%d, mask = 0x%X, "
|
||
|
"flags = 0x%X\n", __func__, rc, mask, flags);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int control_get(void *data, u64 *val)
|
||
|
{
|
||
|
struct pm8xxx_upl_device *upldev;
|
||
|
int rc = 0;
|
||
|
u8 ctrl;
|
||
|
|
||
|
upldev = data;
|
||
|
|
||
|
mutex_lock(&upldev->upl_mutex);
|
||
|
|
||
|
rc = pm8xxx_readb(upldev->dev->parent, SSBI_REG_UPL_CTRL, &ctrl);
|
||
|
if (rc)
|
||
|
pr_err("%s: FAIL pm8xxx_readb(): rc=%d (ctrl=0x%02X)\n",
|
||
|
__func__, rc, ctrl);
|
||
|
|
||
|
mutex_unlock(&upldev->upl_mutex);
|
||
|
|
||
|
*val = ctrl;
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
DEFINE_SIMPLE_ATTRIBUTE(upl_control_fops, control_get,
|
||
|
control_set, "0x%02llX\n");
|
||
|
|
||
|
static int pm8xxx_upl_debug_init(struct pm8xxx_upl_device *upldev)
|
||
|
{
|
||
|
struct dentry *dent;
|
||
|
struct dentry *temp;
|
||
|
|
||
|
dent = debugfs_create_dir("pm8xxx-upl", NULL);
|
||
|
if (dent == NULL || IS_ERR(dent)) {
|
||
|
pr_err("%s: ERR debugfs_create_dir: dent=0x%X\n",
|
||
|
__func__, (unsigned)dent);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
temp = debugfs_create_file("truthtable", S_IRUSR | S_IWUSR, dent,
|
||
|
upldev, &upl_truthtable_fops);
|
||
|
if (temp == NULL || IS_ERR(temp)) {
|
||
|
pr_err("%s: ERR debugfs_create_file: dent=0x%X\n",
|
||
|
__func__, (unsigned)dent);
|
||
|
goto debug_error;
|
||
|
}
|
||
|
|
||
|
temp = debugfs_create_file("control", S_IRUSR | S_IWUSR, dent,
|
||
|
upldev, &upl_control_fops);
|
||
|
if (temp == NULL || IS_ERR(temp)) {
|
||
|
pr_err("%s: ERR debugfs_create_file: dent=0x%X\n",
|
||
|
__func__, (unsigned)dent);
|
||
|
goto debug_error;
|
||
|
}
|
||
|
|
||
|
upldev->dent = dent;
|
||
|
return 0;
|
||
|
|
||
|
debug_error:
|
||
|
debugfs_remove_recursive(dent);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
static int __devexit pm8xxx_upl_debug_remove(struct pm8xxx_upl_device *upldev)
|
||
|
{
|
||
|
debugfs_remove_recursive(upldev->dent);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#endif /* CONFIG_DEBUG_FS */
|
||
|
|
||
|
static int __devinit pm8xxx_upl_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct pm8xxx_upl_device *upldev;
|
||
|
|
||
|
upldev = kzalloc(sizeof *upldev, GFP_KERNEL);
|
||
|
if (upldev == NULL) {
|
||
|
pr_err("%s: kzalloc() failed.\n", __func__);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
mutex_init(&upldev->upl_mutex);
|
||
|
|
||
|
upl_dev = upldev;
|
||
|
upldev->dev = &pdev->dev;
|
||
|
platform_set_drvdata(pdev, upldev);
|
||
|
|
||
|
#if defined(CONFIG_DEBUG_FS)
|
||
|
pm8xxx_upl_debug_init(upl_dev);
|
||
|
#endif
|
||
|
pr_notice("%s: OK\n", __func__);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __devexit pm8xxx_upl_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct pm8xxx_upl_device *upldev = platform_get_drvdata(pdev);
|
||
|
|
||
|
#if defined(CONFIG_DEBUG_FS)
|
||
|
pm8xxx_upl_debug_remove(upldev);
|
||
|
#endif
|
||
|
|
||
|
platform_set_drvdata(pdev, NULL);
|
||
|
kfree(upldev);
|
||
|
pr_notice("%s: OK\n", __func__);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver pm8xxx_upl_driver = {
|
||
|
.probe = pm8xxx_upl_probe,
|
||
|
.remove = __devexit_p(pm8xxx_upl_remove),
|
||
|
.driver = {
|
||
|
.name = PM8XXX_UPL_DEV_NAME,
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init pm8xxx_upl_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&pm8xxx_upl_driver);
|
||
|
}
|
||
|
|
||
|
static void __exit pm8xxx_upl_exit(void)
|
||
|
{
|
||
|
platform_driver_unregister(&pm8xxx_upl_driver);
|
||
|
}
|
||
|
|
||
|
module_init(pm8xxx_upl_init);
|
||
|
module_exit(pm8xxx_upl_exit);
|
||
|
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
MODULE_DESCRIPTION("PM8XXX UPL driver");
|
||
|
MODULE_VERSION("1.0");
|
||
|
MODULE_ALIAS("platform:" PM8XXX_UPL_DEV_NAME);
|