290 lines
6.1 KiB
C
290 lines
6.1 KiB
C
/* Copyright (c) 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.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/regulator/pm8058-xo.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#define FSM_XO_IOC_MAGIC 0x93
|
|
#define FSM_XO_IOC_CLKBUF _IO(FSM_XO_IOC_MAGIC, 1)
|
|
|
|
#define FSM_XO_DEVICE_READY 0x01
|
|
#define FSM_XO_DEVICE_OFF 0x00
|
|
|
|
/* enum for TCXO clock output buffer definition */
|
|
enum clk_buffer_type {
|
|
XO_BUFFER_A0 = 0,
|
|
XO_BUFFER_A1 = 1,
|
|
XO_BUFFER_LAST
|
|
};
|
|
|
|
/*
|
|
* This user request structure is used to exchange the pmic device data
|
|
* requested to user space applications. The pointer to this structure is
|
|
* passed to the the ioctl function.
|
|
*/
|
|
struct fsm_xo_req {
|
|
enum clk_buffer_type clkBuffer;
|
|
u8 clkBufEnable;
|
|
};
|
|
|
|
struct fsm_xo_priv_t {
|
|
struct mutex lock;
|
|
struct regulator *a0;
|
|
struct regulator *a1;
|
|
u8 a0_enabled;
|
|
u8 a1_enabled;
|
|
};
|
|
|
|
static struct fsm_xo_priv_t *fsm_xo_priv;
|
|
|
|
static int fsm_xo_open(struct inode *inode, struct file *filp)
|
|
{
|
|
if (fsm_xo_priv == NULL)
|
|
return -ENODEV;
|
|
|
|
filp->private_data = fsm_xo_priv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsm_xo_release(struct inode *inode, struct file *filp)
|
|
{
|
|
filp->private_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int fsm_xo_enable_a0(void)
|
|
{
|
|
int err = 0;
|
|
|
|
if (!fsm_xo_priv->a0_enabled) {
|
|
err = regulator_enable(fsm_xo_priv->a0);
|
|
if (err != 0)
|
|
pr_err("Error = %d enabling xo buffer a0\n", err);
|
|
else
|
|
fsm_xo_priv->a0_enabled = 1;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static inline int fsm_xo_disable_a0(void)
|
|
{
|
|
int err = 0;
|
|
|
|
if (fsm_xo_priv->a0_enabled) {
|
|
err = regulator_disable(fsm_xo_priv->a0);
|
|
if (err != 0)
|
|
pr_err("Error = %d disabling xo buffer a0\n", err);
|
|
else
|
|
fsm_xo_priv->a0_enabled = 0;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static inline int fsm_xo_enable_a1(void)
|
|
{
|
|
int err = 0;
|
|
|
|
if (!fsm_xo_priv->a1_enabled) {
|
|
err = regulator_enable(fsm_xo_priv->a1);
|
|
if (err != 0)
|
|
pr_err("Error = %d enabling xo buffer a1\n", err);
|
|
else
|
|
fsm_xo_priv->a1_enabled = 1;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static inline int fsm_xo_disable_a1(void)
|
|
{
|
|
int err = 0;
|
|
|
|
if (fsm_xo_priv->a1_enabled) {
|
|
err = regulator_disable(fsm_xo_priv->a1);
|
|
if (err != 0)
|
|
pr_err("Error = %d disabling xo buffer a1\n", err);
|
|
else
|
|
fsm_xo_priv->a1_enabled = 0;
|
|
}
|
|
return err;
|
|
}
|
|
static long
|
|
fsm_xo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int err = 0;
|
|
struct fsm_xo_req req;
|
|
|
|
/* Verify user arguments. */
|
|
if (_IOC_TYPE(cmd) != FSM_XO_IOC_MAGIC)
|
|
return -ENOTTY;
|
|
|
|
/* Lock for access */
|
|
if (mutex_lock_interruptible(&fsm_xo_priv->lock))
|
|
return -ERESTARTSYS;
|
|
|
|
switch (cmd) {
|
|
case FSM_XO_IOC_CLKBUF:
|
|
if (arg == 0) {
|
|
pr_err("user space arg not supplied\n");
|
|
err = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
if (copy_from_user(&req, (void __user *)arg,
|
|
sizeof(req))) {
|
|
pr_err("Error copying from user space\n");
|
|
err = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
if (req.clkBuffer == XO_BUFFER_A0) {
|
|
if (req.clkBufEnable)
|
|
err = fsm_xo_enable_a0();
|
|
else
|
|
err = fsm_xo_disable_a0();
|
|
} else if (req.clkBuffer == XO_BUFFER_A1) {
|
|
if (req.clkBufEnable)
|
|
err = fsm_xo_enable_a1();
|
|
else
|
|
err = fsm_xo_disable_a1();
|
|
} else {
|
|
pr_err("Invalid ioctl argument.\n");
|
|
err = -ENOTTY;
|
|
}
|
|
break;
|
|
default:
|
|
pr_err("Invalid ioctl command.\n");
|
|
err = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&fsm_xo_priv->lock);
|
|
return err;
|
|
}
|
|
|
|
static const struct file_operations fsm_xo_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = fsm_xo_ioctl,
|
|
.open = fsm_xo_open,
|
|
.release = fsm_xo_release
|
|
};
|
|
|
|
static struct miscdevice fsm_xo_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "fsm_xo",
|
|
.fops = &fsm_xo_fops
|
|
};
|
|
|
|
static int fsm_xo_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Initialize */
|
|
fsm_xo_priv = kzalloc(sizeof(struct fsm_xo_priv_t), GFP_KERNEL);
|
|
|
|
if (fsm_xo_priv == NULL) {
|
|
pr_alert("Not enough memory to initialize device\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fsm_xo_priv->a0 = regulator_get(&pdev->dev, "a0_clk_buffer");
|
|
if (IS_ERR(fsm_xo_priv->a0)) {
|
|
pr_err("Error getting a0_clk_buffer\n");
|
|
ret = PTR_ERR(fsm_xo_priv->a0);
|
|
fsm_xo_priv->a0 = NULL;
|
|
goto err;
|
|
}
|
|
fsm_xo_priv->a1 = regulator_get(&pdev->dev, "a1_clk_buffer");
|
|
if (IS_ERR(fsm_xo_priv->a1)) {
|
|
pr_err("Error getting a1_clk_buffer\n");
|
|
ret = PTR_ERR(fsm_xo_priv->a1);
|
|
fsm_xo_priv->a1 = NULL;
|
|
goto err;
|
|
}
|
|
|
|
fsm_xo_priv->a0_enabled = 0;
|
|
fsm_xo_priv->a1_enabled = 0;
|
|
|
|
/* Enable the clock buffers. AMSS depends on this on the FSM. */
|
|
fsm_xo_enable_a0();
|
|
fsm_xo_enable_a1();
|
|
|
|
mutex_init(&fsm_xo_priv->lock);
|
|
|
|
ret = misc_register(&fsm_xo_dev);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
if (fsm_xo_priv->a0)
|
|
regulator_put(fsm_xo_priv->a0);
|
|
if (fsm_xo_priv->a1)
|
|
regulator_put(fsm_xo_priv->a1);
|
|
|
|
kfree(fsm_xo_priv);
|
|
fsm_xo_priv = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __devexit fsm_xo_remove(struct platform_device *pdev)
|
|
{
|
|
if (fsm_xo_priv && fsm_xo_priv->a0)
|
|
regulator_put(fsm_xo_priv->a0);
|
|
if (fsm_xo_priv && fsm_xo_priv->a1)
|
|
regulator_put(fsm_xo_priv->a1);
|
|
|
|
kfree(fsm_xo_priv);
|
|
fsm_xo_priv = NULL;
|
|
|
|
misc_deregister(&fsm_xo_dev);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver fsm_xo_driver = {
|
|
.probe = fsm_xo_probe,
|
|
.remove = fsm_xo_remove,
|
|
.driver = {
|
|
.name = "fsm_xo_driver",
|
|
}
|
|
};
|
|
|
|
static int __init fsm_xo_init(void)
|
|
{
|
|
return platform_driver_register(&fsm_xo_driver);
|
|
}
|
|
|
|
static void __exit fsm_xo_exit(void)
|
|
{
|
|
platform_driver_unregister(&fsm_xo_driver);
|
|
}
|
|
|
|
module_init(fsm_xo_init);
|
|
module_exit(fsm_xo_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Provide userspace access to XO buffers in PMIC8058.");
|
|
MODULE_VERSION("1.00");
|