android_kernel_lenovo_1050f/drivers/platform/x86/intel_crystalcove_pwrsrc.c

443 lines
12 KiB
C

/*
* intel_crystalcove_pwrsrc.c - Intel Crystal Cove Power Source Detect Driver
*
* Copyright (C) 2013 Intel Corporation
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 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; version 2 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Author: Kannappan R <r.kannappan@intel.com>
* Ramakrishna Pallala <ramakrishna.pallala@intel.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/usb/phy.h>
#include <linux/notifier.h>
#include <linux/extcon.h>
#include <linux/mfd/intel_mid_pmic.h>
#include <asm/intel_crystalcove_pwrsrc.h>
#define CRYSTALCOVE_PWRSRCIRQ_REG 0x03
#define CRYSTALCOVE_MPWRSRCIRQS0_REG 0x0F
#define CRYSTALCOVE_MPWRSRCIRQSX_REG 0x10
#define CRYSTALCOVE_SPWRSRC_REG 0x1E
#define CRYSTALCOVE_RESETSRC0_REG 0x20
#define CRYSTALCOVE_RESETSRC1_REG 0x21
#define CRYSTALCOVE_WAKESRC_REG 0x22
#define PWRSRC_VBUS_DET (1 << 0)
#define PWRSRC_DCIN_DET (1 << 1)
#define PWRSRC_BAT_DET (1 << 2)
#define CRYSTALCOVE_VBUSCNTL_REG 0x6C
#define VBUSCNTL_EN (1 << 0)
#define VBUSCNTL_SEL (1 << 1)
#define PWRSRC_EXTCON_CABLE_AC "CHARGER_AC"
#define PWRSRC_EXTCON_CABLE_USB "USB"
#define PWRSRC_DRV_NAME "crystal_cove_pwrsrc"
/*
* Crystal Cove PMIC can not do USB type detection.
* So if we find extcon USB_SDP cable then unregister
* the pwrsrc extcon device as BYT platform is not
* supporting AC and USB simultaneously.
*/
#define EXTCON_CABLE_SDP "CHARGER_USB_SDP"
#ifndef DEBUG
#define dev_dbg(dev, format, arg...) \
dev_printk(KERN_DEBUG, dev, format, ##arg)
#endif
static const char *byt_extcon_cable[] = {
PWRSRC_EXTCON_CABLE_AC,
PWRSRC_EXTCON_CABLE_USB,
NULL,
};
struct pwrsrc_info {
struct platform_device *pdev;
int irq;
struct usb_phy *otg;
struct extcon_dev *edev;
struct notifier_block nb;
};
static char *pwrsrc_resetsrc0_info[] = {
/* bit 0 */ "Last shutdown caused by SOC reporting a thermal event",
/* bit 1 */ "Last shutdown caused by critical PMIC temperature",
/* bit 2 */ "Last shutdown caused by critical system temperature",
/* bit 3 */ "Last shutdown caused by critical battery temperature",
/* bit 4 */ "Last shutdown caused by VSYS under voltage",
/* bit 5 */ "Last shutdown caused by VSYS over voltage",
/* bit 6 */ "Last shutdown caused by battery removal",
NULL,
};
static char *pwrsrc_resetsrc1_info[] = {
/* bit 0 */ "Last shutdown caused by VCRIT threshold",
/* bit 1 */ "Last shutdown caused by BATID reporting battery removal",
/* bit 2 */ "Last shutdown caused by user pressing the power button",
NULL,
};
static char *pwrsrc_wakesrc_info[] = {
/* bit 0 */ "Last wake caused by user pressing the power button",
/* bit 1 */ "Last wake caused by a battery insertion",
/* bit 2 */ "Last wake caused by a USB charger insertion",
/* bit 3 */ "Last wake caused by an adapter insertion",
NULL,
};
/* Decode and log the given "reset source indicator" register, then clear it */
static void crystalcove_pwrsrc_log_rsi(struct platform_device *pdev,
char **pwrsrc_rsi_info,
int reg_s)
{
char *rsi_info = pwrsrc_rsi_info[0];
int val, i = 0;
int bit_select, clear_mask = 0x0;
val = intel_mid_pmic_readb(reg_s);
while (rsi_info) {
bit_select = (1 << i);
if (val & bit_select) {
dev_info(&pdev->dev, "%s\n", rsi_info);
clear_mask |= bit_select;
}
rsi_info = pwrsrc_rsi_info[++i];
}
/* Clear the register value for next reboot (write 1 to clear bit) */
intel_mid_pmic_writeb(reg_s, clear_mask);
}
/*
* D1 ensures SW control: D1[0] = HW mode, D1[1] = SW mode
* D0 control the VBUS: D0[0] = disable VBUS, D0[1] = enable VBUS
*/
int crystal_cove_enable_vbus(void)
{
int ret;
ret = intel_mid_pmic_writeb(CRYSTALCOVE_VBUSCNTL_REG, 0x03);
return ret;
}
EXPORT_SYMBOL(crystal_cove_enable_vbus);
int crystal_cove_disable_vbus(void)
{
int ret;
ret = intel_mid_pmic_writeb(CRYSTALCOVE_VBUSCNTL_REG, 0x02);
return ret;
}
EXPORT_SYMBOL(crystal_cove_disable_vbus);
int crystal_cove_vbus_on_status(void)
{
int ret;
ret = intel_mid_pmic_readb(CRYSTALCOVE_SPWRSRC_REG);
if (ret < 0)
return ret;
if (ret & PWRSRC_VBUS_DET)
return 1;
return 0;
}
EXPORT_SYMBOL(crystal_cove_vbus_on_status);
static void handle_pwrsrc_event(struct pwrsrc_info *info, int pwrsrcirq)
{
int spwrsrc, mask;
spwrsrc = intel_mid_pmic_readb(CRYSTALCOVE_SPWRSRC_REG);
if (spwrsrc < 0)
goto pmic_read_fail;
if (pwrsrcirq & PWRSRC_VBUS_DET) {
if (spwrsrc & PWRSRC_VBUS_DET) {
dev_dbg(&info->pdev->dev, "VBUS attach event\n");
mask = 1;
if (info->edev)
extcon_set_cable_state(info->edev,
PWRSRC_EXTCON_CABLE_USB, true);
} else {
dev_dbg(&info->pdev->dev, "VBUS detach event\n");
mask = 0;
if (info->edev)
extcon_set_cable_state(info->edev,
PWRSRC_EXTCON_CABLE_USB, false);
}
/* notify OTG driver */
if (info->otg)
atomic_notifier_call_chain(&info->otg->notifier,
USB_EVENT_VBUS, &mask);
} else if (pwrsrcirq & PWRSRC_DCIN_DET) {
if (spwrsrc & PWRSRC_DCIN_DET) {
dev_dbg(&info->pdev->dev, "ADP attach event\n");
if (info->edev)
extcon_set_cable_state(info->edev,
PWRSRC_EXTCON_CABLE_AC, true);
} else {
dev_dbg(&info->pdev->dev, "ADP detach event\n");
if (info->edev)
extcon_set_cable_state(info->edev,
PWRSRC_EXTCON_CABLE_AC, false);
}
} else if (pwrsrcirq & PWRSRC_BAT_DET) {
if (spwrsrc & PWRSRC_BAT_DET)
dev_dbg(&info->pdev->dev, "Battery attach event\n");
else
dev_dbg(&info->pdev->dev, "Battery detach event\n");
} else {
dev_dbg(&info->pdev->dev, "event none or spurious\n");
}
return;
pmic_read_fail:
dev_err(&info->pdev->dev, "SPWRSRC read failed:%d\n", spwrsrc);
return;
}
static irqreturn_t crystalcove_pwrsrc_isr(int irq, void *data)
{
struct pwrsrc_info *info = data;
int pwrsrcirq;
pwrsrcirq = intel_mid_pmic_readb(CRYSTALCOVE_PWRSRCIRQ_REG);
if (pwrsrcirq < 0) {
dev_err(&info->pdev->dev, "PWRSRCIRQ read failed\n");
goto pmic_irq_fail;
}
dev_dbg(&info->pdev->dev, "pwrsrcirq=%x\n", pwrsrcirq);
handle_pwrsrc_event(info, pwrsrcirq);
pmic_irq_fail:
intel_mid_pmic_writeb(CRYSTALCOVE_PWRSRCIRQ_REG, pwrsrcirq);
return IRQ_HANDLED;
}
static int pwrsrc_extcon_dev_reg_callback(struct notifier_block *nb,
unsigned long event, void *data)
{
struct pwrsrc_info *info = container_of(nb, struct pwrsrc_info, nb);
int mask = 0;
/* check if there is other extcon cables */
if (extcon_num_of_cable_devs(EXTCON_CABLE_SDP)) {
dev_info(&info->pdev->dev, "unregistering otg device\n");
/* Send VBUS disconnect as another cable detection
* driver registered to extcon framework and notifies
* OTG on cable connect */
if (info->otg)
atomic_notifier_call_chain(&info->otg->notifier,
USB_EVENT_VBUS, &mask);
/* Set VBUS supply mode to SW control mode */
intel_mid_pmic_writeb(CRYSTALCOVE_VBUSCNTL_REG, 0x02);
if (info->nb.notifier_call) {
extcon_dev_unregister_notify(&info->nb);
info->nb.notifier_call = NULL;
}
if (info->otg) {
usb_put_phy(info->otg);
info->otg = NULL;
}
}
return NOTIFY_OK;
}
static int pwrsrc_extcon_registration(struct pwrsrc_info *info)
{
int ret = 0;
/* register with extcon */
info->edev = kzalloc(sizeof(struct extcon_dev), GFP_KERNEL);
if (!info->edev) {
dev_err(&info->pdev->dev, "mem alloc failed\n");
ret = -ENOMEM;
goto pwrsrc_extcon_fail;
}
info->edev->name = "BYT-Charger";
info->edev->supported_cable = byt_extcon_cable;
ret = extcon_dev_register(info->edev, &info->pdev->dev);
if (ret) {
dev_err(&info->pdev->dev, "extcon registration failed!!\n");
kfree(info->edev);
goto pwrsrc_extcon_fail;
}
if (extcon_num_of_cable_devs(EXTCON_CABLE_SDP)) {
dev_info(&info->pdev->dev,
"extcon device is already registered\n");
/* Set VBUS supply mode to SW control mode */
intel_mid_pmic_writeb(CRYSTALCOVE_VBUSCNTL_REG, 0x02);
} else {
/* Workaround: Set VBUS supply mode to HW control mode */
intel_mid_pmic_writeb(CRYSTALCOVE_VBUSCNTL_REG, 0x00);
/* OTG notification */
info->otg = usb_get_phy(USB_PHY_TYPE_USB2);
if (IS_ERR_OR_NULL(info->otg)) {
info->otg = NULL;
dev_warn(&info->pdev->dev, "Failed to get otg transceiver!!\n");
extcon_dev_unregister(info->edev);
kfree(info->edev);
ret = -ENODEV;
goto pwrsrc_extcon_fail;
}
info->nb.notifier_call = &pwrsrc_extcon_dev_reg_callback;
extcon_dev_register_notify(&info->nb);
}
pwrsrc_extcon_fail:
return ret;
}
static int crystalcove_pwrsrc_probe(struct platform_device *pdev)
{
struct pwrsrc_info *info;
int ret, pwrsrcirq = 0x0;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info) {
dev_err(&pdev->dev, "mem alloc failed\n");
return -ENOMEM;
}
info->pdev = pdev;
info->irq = platform_get_irq(pdev, 0);
platform_set_drvdata(pdev, info);
/* Log reason for last reset and wake events */
crystalcove_pwrsrc_log_rsi(pdev, pwrsrc_resetsrc0_info,
CRYSTALCOVE_RESETSRC0_REG);
crystalcove_pwrsrc_log_rsi(pdev, pwrsrc_resetsrc1_info,
CRYSTALCOVE_RESETSRC1_REG);
crystalcove_pwrsrc_log_rsi(pdev, pwrsrc_wakesrc_info,
CRYSTALCOVE_WAKESRC_REG);
ret = pwrsrc_extcon_registration(info);
if (ret)
goto extcon_reg_failed;
/* check if device is already connected */
if (info->otg)
pwrsrcirq |= PWRSRC_VBUS_DET;
if (info->edev)
pwrsrcirq |= PWRSRC_DCIN_DET;
if (pwrsrcirq)
handle_pwrsrc_event(info, pwrsrcirq);
ret = request_threaded_irq(info->irq, NULL, crystalcove_pwrsrc_isr,
IRQF_ONESHOT | IRQF_NO_SUSPEND,
PWRSRC_DRV_NAME, info);
if (ret) {
dev_err(&pdev->dev, "unable to register irq %d\n", info->irq);
goto intr_teg_failed;
}
/* unmask the PWRSRC interrupts */
intel_mid_pmic_writeb(CRYSTALCOVE_MPWRSRCIRQS0_REG, 0x00);
intel_mid_pmic_writeb(CRYSTALCOVE_MPWRSRCIRQSX_REG, 0x00);
return 0;
intr_teg_failed:
if (info->otg)
usb_put_phy(info->otg);
if (info->edev) {
extcon_dev_unregister(info->edev);
kfree(info->edev);
}
extcon_reg_failed:
kfree(info);
return ret;
}
static int crystalcove_pwrsrc_remove(struct platform_device *pdev)
{
struct pwrsrc_info *info = platform_get_drvdata(pdev);
free_irq(info->irq, info);
if (info->otg)
usb_put_phy(info->otg);
if (info->edev) {
extcon_dev_unregister(info->edev);
kfree(info->edev);
}
kfree(info);
return 0;
}
#ifdef CONFIG_PM
static int crystalcove_pwrsrc_suspend(struct device *dev)
{
return 0;
}
static int crystalcove_pwrsrc_resume(struct device *dev)
{
return 0;
}
#else
#define crystalcove_pwrsrc_suspend NULL
#define crystalcove_pwrsrc_resume NULL
#endif
static const struct dev_pm_ops crystalcove_pwrsrc_driver_pm_ops = {
.suspend = crystalcove_pwrsrc_suspend,
.resume = crystalcove_pwrsrc_resume,
};
static struct platform_driver crystalcove_pwrsrc_driver = {
.probe = crystalcove_pwrsrc_probe,
.remove = crystalcove_pwrsrc_remove,
.driver = {
.name = PWRSRC_DRV_NAME,
.pm = &crystalcove_pwrsrc_driver_pm_ops,
},
};
static int __init crystalcove_pwrsrc_init(void)
{
return platform_driver_register(&crystalcove_pwrsrc_driver);
}
device_initcall(crystalcove_pwrsrc_init);
static void __exit crystalcove_pwrsrc_exit(void)
{
platform_driver_unregister(&crystalcove_pwrsrc_driver);
}
module_exit(crystalcove_pwrsrc_exit);
MODULE_AUTHOR("Kannappan R <r.kannappan@intel.com>");
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
MODULE_DESCRIPTION("CrystalCove Power Source Detect Driver");
MODULE_LICENSE("GPL");