326 lines
7.4 KiB
C
326 lines
7.4 KiB
C
/* Copyright (c) 2011-2012, 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/kernel.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/module.h>
|
|
#include <mach/socinfo.h>
|
|
#include <mach/proc_comm.h>
|
|
#include "footswitch.h"
|
|
|
|
/* PCOM power rail IDs */
|
|
#define PCOM_FS_GRP 8
|
|
#define PCOM_FS_GRP_2D 58
|
|
#define PCOM_FS_MDP 14
|
|
#define PCOM_FS_MFC 68
|
|
#define PCOM_FS_ROTATOR 90
|
|
#define PCOM_FS_VFE 41
|
|
#define PCOM_FS_VPE 76
|
|
|
|
#define PCOM_RAIL_MODE_AUTO 0
|
|
#define PCOM_RAIL_MODE_MANUAL 1
|
|
|
|
/**
|
|
* struct footswitch - Per-footswitch data and state
|
|
* @rdev: Regulator framework device
|
|
* @desc: Regulator descriptor
|
|
* @init_data: Regulator platform data
|
|
* @pcom_id: Proc-comm ID of the footswitch
|
|
* @is_enabled: Flag set when footswitch is enabled
|
|
* @has_ahb_clk: Flag set if footswitched core has an ahb_clk
|
|
* @has_src_clk: Flag set if footswitched core has a src_clk
|
|
* @src_clk: Controls the core clock's rate
|
|
* @core_clk: Clocks the core
|
|
* @ahb_clk: Clocks the core's register interface
|
|
* @src_clk_init_rate: Rate to use for src_clk if it has not been set yet
|
|
* @is_rate_set: Flag set if core_clk's rate has been set
|
|
*/
|
|
struct footswitch {
|
|
struct regulator_dev *rdev;
|
|
struct regulator_desc desc;
|
|
struct regulator_init_data init_data;
|
|
unsigned pcom_id;
|
|
bool is_enabled;
|
|
struct clk *src_clk;
|
|
struct clk *core_clk;
|
|
struct clk *ahb_clk;
|
|
const bool has_ahb_clk;
|
|
const bool has_src_clk;
|
|
const int src_clk_init_rate;
|
|
bool is_rate_set;
|
|
};
|
|
|
|
static inline int set_rail_mode(int pcom_id, int mode)
|
|
{
|
|
int rc;
|
|
|
|
rc = msm_proc_comm(PCOM_CLKCTL_RPC_RAIL_CONTROL, &pcom_id, &mode);
|
|
if (!rc && pcom_id)
|
|
rc = -EINVAL;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static inline int set_rail_state(int pcom_id, int state)
|
|
{
|
|
int rc;
|
|
|
|
rc = msm_proc_comm(state, &pcom_id, NULL);
|
|
if (!rc && pcom_id)
|
|
rc = -EINVAL;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int enable_clocks(struct footswitch *fs)
|
|
{
|
|
fs->is_rate_set = !!(clk_get_rate(fs->src_clk));
|
|
if (!fs->is_rate_set)
|
|
clk_set_rate(fs->src_clk, fs->src_clk_init_rate);
|
|
clk_prepare_enable(fs->core_clk);
|
|
|
|
if (fs->ahb_clk)
|
|
clk_prepare_enable(fs->ahb_clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void disable_clocks(struct footswitch *fs)
|
|
{
|
|
if (fs->ahb_clk)
|
|
clk_disable_unprepare(fs->ahb_clk);
|
|
clk_disable_unprepare(fs->core_clk);
|
|
}
|
|
|
|
static int footswitch_is_enabled(struct regulator_dev *rdev)
|
|
{
|
|
struct footswitch *fs = rdev_get_drvdata(rdev);
|
|
|
|
return fs->is_enabled;
|
|
}
|
|
|
|
static int footswitch_enable(struct regulator_dev *rdev)
|
|
{
|
|
struct footswitch *fs = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
|
|
rc = enable_clocks(fs);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE);
|
|
if (!rc)
|
|
fs->is_enabled = true;
|
|
|
|
disable_clocks(fs);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int footswitch_disable(struct regulator_dev *rdev)
|
|
{
|
|
struct footswitch *fs = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
|
|
rc = enable_clocks(fs);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_DISABLE);
|
|
if (!rc)
|
|
fs->is_enabled = false;
|
|
|
|
disable_clocks(fs);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct regulator_ops footswitch_ops = {
|
|
.is_enabled = footswitch_is_enabled,
|
|
.enable = footswitch_enable,
|
|
.disable = footswitch_disable,
|
|
};
|
|
|
|
#define FOOTSWITCH(_id, _pcom_id, _name, _src_clk, _rate, _ahb_clk) \
|
|
[_id] = { \
|
|
.desc = { \
|
|
.id = _id, \
|
|
.name = _name, \
|
|
.ops = &footswitch_ops, \
|
|
.type = REGULATOR_VOLTAGE, \
|
|
.owner = THIS_MODULE, \
|
|
}, \
|
|
.pcom_id = _pcom_id, \
|
|
.has_src_clk = _src_clk, \
|
|
.src_clk_init_rate = _rate, \
|
|
.has_ahb_clk = _ahb_clk, \
|
|
}
|
|
static struct footswitch footswitches[] = {
|
|
FOOTSWITCH(FS_GFX3D, PCOM_FS_GRP,
|
|
"fs_gfx3d", true, 24576000, true),
|
|
FOOTSWITCH(FS_GFX2D0, PCOM_FS_GRP_2D,
|
|
"fs_gfx2d0", false, 24576000, true),
|
|
FOOTSWITCH(FS_MDP, PCOM_FS_MDP,
|
|
"fs_mdp", false, 24576000, true),
|
|
FOOTSWITCH(FS_MFC, PCOM_FS_MFC,
|
|
"fs_mfc", false, 24576000, true),
|
|
FOOTSWITCH(FS_ROT, PCOM_FS_ROTATOR,
|
|
"fs_rot", false, 0, true),
|
|
FOOTSWITCH(FS_VFE, PCOM_FS_VFE,
|
|
"fs_vfe", false, 24576000, true),
|
|
FOOTSWITCH(FS_VPE, PCOM_FS_VPE,
|
|
"fs_vpe", false, 24576000, false),
|
|
};
|
|
|
|
static int get_clocks(struct device *dev, struct footswitch *fs)
|
|
{
|
|
int rc;
|
|
|
|
/*
|
|
* Some SoCs may not have a separate rate-settable clock.
|
|
* If one can't be found, try to use the core clock for
|
|
* rate-setting instead.
|
|
*/
|
|
if (fs->has_src_clk) {
|
|
fs->src_clk = clk_get(dev, "src_clk");
|
|
if (IS_ERR(fs->src_clk))
|
|
fs->src_clk = clk_get(dev, "core_clk");
|
|
} else {
|
|
fs->src_clk = clk_get(dev, "core_clk");
|
|
}
|
|
if (IS_ERR(fs->src_clk)) {
|
|
pr_err("%s clk_get(src_clk) failed\n", fs->desc.name);
|
|
rc = PTR_ERR(fs->src_clk);
|
|
goto err_src_clk;
|
|
}
|
|
|
|
fs->core_clk = clk_get(dev, "core_clk");
|
|
if (IS_ERR(fs->core_clk)) {
|
|
pr_err("%s clk_get(core_clk) failed\n", fs->desc.name);
|
|
rc = PTR_ERR(fs->core_clk);
|
|
goto err_core_clk;
|
|
}
|
|
|
|
if (fs->has_ahb_clk) {
|
|
fs->ahb_clk = clk_get(dev, "iface_clk");
|
|
if (IS_ERR(fs->ahb_clk)) {
|
|
pr_err("%s clk_get(iface_clk) failed\n", fs->desc.name);
|
|
rc = PTR_ERR(fs->ahb_clk);
|
|
goto err_ahb_clk;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_ahb_clk:
|
|
clk_put(fs->core_clk);
|
|
err_core_clk:
|
|
clk_put(fs->src_clk);
|
|
err_src_clk:
|
|
return rc;
|
|
}
|
|
|
|
static void put_clocks(struct footswitch *fs)
|
|
{
|
|
clk_put(fs->src_clk);
|
|
clk_put(fs->core_clk);
|
|
clk_put(fs->ahb_clk);
|
|
}
|
|
|
|
static int footswitch_probe(struct platform_device *pdev)
|
|
{
|
|
struct footswitch *fs;
|
|
struct regulator_init_data *init_data;
|
|
int rc;
|
|
|
|
if (pdev == NULL)
|
|
return -EINVAL;
|
|
|
|
if (pdev->id >= MAX_FS)
|
|
return -ENODEV;
|
|
|
|
init_data = pdev->dev.platform_data;
|
|
fs = &footswitches[pdev->id];
|
|
|
|
/*
|
|
* Enable footswitch in manual mode (ie. not controlled along
|
|
* with pcom clocks).
|
|
*/
|
|
rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE);
|
|
if (rc)
|
|
return rc;
|
|
rc = set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_MANUAL);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = get_clocks(&pdev->dev, fs);
|
|
if (rc)
|
|
return rc;
|
|
|
|
fs->rdev = regulator_register(&fs->desc, &pdev->dev,
|
|
init_data, fs, NULL);
|
|
if (IS_ERR(fs->rdev)) {
|
|
pr_err("regulator_register(%s) failed\n", fs->desc.name);
|
|
rc = PTR_ERR(fs->rdev);
|
|
goto err_register;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_register:
|
|
put_clocks(fs);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __devexit footswitch_remove(struct platform_device *pdev)
|
|
{
|
|
struct footswitch *fs = &footswitches[pdev->id];
|
|
|
|
regulator_unregister(fs->rdev);
|
|
set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_AUTO);
|
|
put_clocks(fs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver footswitch_driver = {
|
|
.probe = footswitch_probe,
|
|
.remove = __devexit_p(footswitch_remove),
|
|
.driver = {
|
|
.name = "footswitch-pcom",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init footswitch_init(void)
|
|
{
|
|
return platform_driver_register(&footswitch_driver);
|
|
}
|
|
subsys_initcall(footswitch_init);
|
|
|
|
static void __exit footswitch_exit(void)
|
|
{
|
|
platform_driver_unregister(&footswitch_driver);
|
|
}
|
|
module_exit(footswitch_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("proc_comm rail footswitch");
|
|
MODULE_ALIAS("platform:footswitch-pcom");
|