blob: 63fc1b0dde9d88718c5ecab289cbf1bd94b16d07 [file] [log] [blame]
/*
* arch/arm/mach-tegra/tegra_bb.c
*
* Copyright (C) 2012-2013 NVIDIA Corporation. All rights reserved.
*
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/types.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
#include <linux/clk.h>
#include <linux/clk/tegra.h>
#include <linux/suspend.h>
#include <linux/pm_runtime.h>
#include <mach/tegra_bb.h>
#include <mach/tegra_bbc_proxy.h>
#include <mach/pm_domains.h>
#include <linux/platform_data/nvshm.h>
#include "clock.h"
#include "iomap.h"
#include "sleep.h"
#include "tegra_emc.h"
#include "pm.h"
/* BB mailbox offset */
#define TEGRA_BB_REG_MAILBOX (0x0)
/* BB mailbox return code (MOSD) offset */
#define TEGRA_BB_REG_RETCODE (0x4)
/* BB status mask */
#define TEGRA_BB_STATUS_MASK (0xffff)
#define TEGRA_BB_IPC_COLDBOOT (0x0001)
#define TEGRA_BB_IPC_READY (0x0005)
#define TEGRA_BB_BOOT_RESTART_FW_REQ (0x0003)
#define BBC_MC_MIN_FREQ 600000000
#define BBC_MC_MAX_FREQ 700000000
#define PMC_EVENT_COUNTER_0 (0x44c)
#define PMC_EVENT_COUNTER_0_EN_MASK (1<<20)
#define PMC_EVENT_COUNTER_0_LP0BB (0<<16)
#define PMC_EVENT_COUNTER_0_LP0ACTIVE (1<<16)
#define PMC_IPC_STS_0 (0x500)
#define AP2BB_RESET_SHIFT (1)
#define AP2BB_RESET_DEFAULT_MASK (1)
#define BB2AP_MEM_REQ_SHIFT (3)
#define BB2AP_MEM_REQ_SOON_SHIFT (4)
#define PMC_IPC_SET_0 (0x504)
#define AP2BB_MEM_STS_SHIFT (5)
#define PMC_IPC_CLEAR_0 (0x508)
#define FLOW_IPC_STS_0 (0x500)
#define AP2BB_MSC_STS_SHIFT (4)
#define BB2AP_INT1_STS_SHIFT (3)
#define BB2AP_INT0_STS_SHIFT (2)
#define AP2BB_INT0_STS_SHIFT (0)
#define FLOW_IPC_SET_0 (0x504)
#define FLOW_IPC_CLR_0 (0x508)
#define MC_BBC_MEM_REGIONS_0_OFFSET (0xF0)
#define MC_BBC_MEM_REGIONS_0_PRIV_SIZE_MASK (0x3)
#define MC_BBC_MEM_REGIONS_0_PRIV_SIZE_SHIFT (0)
#define MC_BBC_MEM_REGIONS_0_PRIV_BASE_MASK (0x1FF)
#define MC_BBC_MEM_REGIONS_0_PRIV_BASE_SHIFT (3)
#define MC_BBC_MEM_REGIONS_0_IPC_SIZE_MASK (0x3)
#define MC_BBC_MEM_REGIONS_0_IPC_SIZE_SHIFT (16)
#define MC_BBC_MEM_REGIONS_0_IPC_BASE_MASK (0x1FF)
#define MC_BBC_MEM_REGIONS_0_IPC_BASE_SHIFT (19)
enum bbc_pm_state {
BBC_REMOVE_FLOOR = 1,
BBC_SET_FLOOR,
BBC_CRASHDUMP_FLOOR,
};
struct tegra_bb {
spinlock_t lock;
int status;
int instance;
char name[16];
char priv_name[16];
char ipc_name[16];
unsigned long priv_phy;
unsigned long ipc_phy;
void *ipc_virt;
void *mb_virt;
unsigned long priv_size;
unsigned long ipc_size;
unsigned long ipc_irq;
unsigned long emc_min_freq;
u32 emc_flags;
char ipc_serial[NVSHM_SERIAL_BYTE_SIZE];
unsigned int irq;
unsigned int mem_req_soon;
enum bbc_pm_state state, prev_state;
struct regulator *vdd_bb_core;
struct regulator *vdd_bb_pll;
void (*ipc_cb)(void *data);
void *ipc_cb_data;
struct miscdevice dev_priv;
struct miscdevice dev_ipc;
struct device *dev;
struct sysfs_dirent *sd;
struct nvshm_platform_data nvshm_pdata;
struct platform_device nvshm_device;
struct workqueue_struct *workqueue;
struct work_struct work;
struct clk *emc_clk;
struct device *proxy_dev;
struct notifier_block pm_notifier;
bool is_suspending;
};
static int tegra_bb_open(struct inode *inode, struct file *filp);
static int tegra_bb_map(struct file *filp, struct vm_area_struct *vma);
static int tegra_bb_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
static const struct file_operations tegra_bb_priv_fops = {
.owner = THIS_MODULE,
.open = tegra_bb_open,
.mmap = tegra_bb_map,
};
static const struct file_operations tegra_bb_ipc_fops = {
.owner = THIS_MODULE,
.open = tegra_bb_open,
.mmap = tegra_bb_map,
};
static const struct vm_operations_struct tegra_bb_vm_ops = {
.fault = tegra_bb_vm_fault,
};
void tegra_bb_register_ipc(struct platform_device *pdev,
void (*cb)(void *data), void *cb_data)
{
struct tegra_bb *bb = platform_get_drvdata(pdev);
if (!bb) {
dev_err(&pdev->dev, "%s tegra_bb not found!\n", __func__);
return;
}
bb->ipc_cb = cb;
bb->ipc_cb_data = cb_data;
}
EXPORT_SYMBOL_GPL(tegra_bb_register_ipc);
void tegra_bb_generate_ipc(struct platform_device *pdev)
{
struct tegra_bb *bb = platform_get_drvdata(pdev);
#ifndef CONFIG_TEGRA_BASEBAND_SIMU
void __iomem *flow = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
#endif
if (!bb) {
dev_err(&pdev->dev, "%s tegra_bb not found!\n", __func__);
return;
}
#ifdef CONFIG_TEGRA_BASEBAND_SIMU
{
int sts;
if (bb->ipc_cb)
bb->ipc_cb(bb->ipc_cb_data);
/* Notify sysfs */
sts = *(unsigned int *)bb->mb_virt & TEGRA_BB_STATUS_MASK;
if ((bb->status != TEGRA_BB_IPC_READY) ||
(bb->status != sts)) {
sysfs_notify(&bb->dev->kobj, NULL, "status");
}
bb->status = sts;
}
#else
{
u32 sts = readl(flow + FLOW_IPC_STS_0);
sts |= 1 << AP2BB_INT0_STS_SHIFT;
writel(sts, flow + FLOW_IPC_SET_0);
}
#endif
}
EXPORT_SYMBOL_GPL(tegra_bb_generate_ipc);
void tegra_bb_clear_ipc(struct platform_device *dev)
{
void __iomem *fctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
/* clear BB2AP INT status */
writel(1 << BB2AP_INT0_STS_SHIFT, fctrl + FLOW_IPC_CLR_0);
}
EXPORT_SYMBOL_GPL(tegra_bb_clear_ipc);
void tegra_bb_abort_ipc(struct platform_device *dev)
{
void __iomem *fctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
/* clear AP2BB INT status */
writel(1 << AP2BB_INT0_STS_SHIFT, fctrl + FLOW_IPC_CLR_0);
}
EXPORT_SYMBOL_GPL(tegra_bb_abort_ipc);
int tegra_bb_check_ipc(struct platform_device *dev)
{
void __iomem *fctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
int sts;
sts = readl(fctrl + FLOW_IPC_STS_0);
if (sts & (1 << AP2BB_INT0_STS_SHIFT))
return 0;
return 1;
}
EXPORT_SYMBOL_GPL(tegra_bb_check_ipc);
int tegra_bb_check_bb2ap_ipc(void)
{
void __iomem *fctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
int sts;
sts = readl(pmc + PMC_IPC_STS_0);
sts = sts >> AP2BB_RESET_SHIFT;
sts &= AP2BB_RESET_DEFAULT_MASK;
if (!sts)
return 0;
sts = readl(fctrl + FLOW_IPC_STS_0);
if (sts & (1 << BB2AP_INT0_STS_SHIFT))
return 1;
return 0;
}
EXPORT_SYMBOL_GPL(tegra_bb_check_bb2ap_ipc);
static int tegra_bb_open(struct inode *inode, struct file *filp)
{
int ret;
ret = nonseekable_open(inode, filp);
return ret;
}
static int tegra_bb_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
pr_warn("%s vma->vm_start=0x%x vma->vm_end=0x%x vma->vm_pgoff=0x%x\n"
, __func__,
(unsigned int)vma->vm_start,
(unsigned int)vma->vm_end,
(unsigned int)vma->vm_pgoff);
vmf = vmf;
return VM_FAULT_NOPAGE;
}
static inline void tegra_bb_enable_mem_req_soon(void)
{
void __iomem *fctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
int val = readl(fctrl + FLOW_IPC_STS_0);
/* AP2BB_MSC_STS[3] is to mask or unmask
* mem_req_soon interrupt to interrupt controller */
val = val | (0x8 << AP2BB_MSC_STS_SHIFT);
writel(val, fctrl + FLOW_IPC_SET_0);
pr_debug("%s: fctrl ipc_sts = %x\n", __func__, val);
}
static int tegra_bb_map(struct file *filp, struct vm_area_struct *vma)
{
struct miscdevice *miscdev = filp->private_data;
struct tegra_bb *bb;
unsigned long phy;
unsigned long off;
unsigned long vsize;
unsigned long psize;
int ret;
off = vma->vm_pgoff << PAGE_SHIFT;
vsize = vma->vm_end - vma->vm_start;
bb = dev_get_drvdata(miscdev->parent);
if (miscdev->fops == &tegra_bb_priv_fops) {
phy = bb->priv_phy + off;
pr_debug("%s map priv section @0x%x\n",
__func__,
(unsigned int)phy);
psize = bb->priv_size - off;
} else {
phy = bb->ipc_phy + off;
pr_debug("%s map ipc section @0x%x\n",
__func__,
(unsigned int)phy);
psize = bb->ipc_size - off;
}
if (vsize > psize) {
pr_err("%s request exceed mapping!\n",
__func__);
return -EINVAL;
}
vma->vm_ops = &tegra_bb_vm_ops;
ret = remap_pfn_range(vma, vma->vm_start, __phys_to_pfn(phy),
vsize, pgprot_noncached(vma->vm_page_prot));
if (ret) {
pr_err("%s remap_pfn_range ret %d\n", __func__, (int)ret);
return -EAGAIN;
}
return ret;
}
static ssize_t show_tegra_bb_retcode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tegra_bb *bb = dev_get_drvdata(dev);
int retcode;
if (!bb) {
dev_err(dev, "%s tegra_bb not found!\n", __func__);
return 0;
}
retcode = *(int *)((unsigned long)bb->mb_virt + TEGRA_BB_REG_RETCODE);
dev_dbg(dev, "%s retcode=%d\n", __func__, (int)retcode);
return sprintf(buf, "%d\n", retcode);
}
static DEVICE_ATTR(retcode, S_IRUSR | S_IRGRP, show_tegra_bb_retcode, NULL);
static ssize_t show_tegra_bb_status(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tegra_bb *bb = dev_get_drvdata(dev);
unsigned int *ptr;
int status;
if (!bb) {
dev_err(dev, "%s tegra_bb not found!\n", __func__);
return 0;
}
ptr = bb->mb_virt + TEGRA_BB_REG_MAILBOX;
status = *ptr & 0xFFFF;
dev_dbg(dev, "%s status=%x\n", __func__, status);
return sprintf(buf, "%d\n", status);
}
static ssize_t store_tegra_bb_status(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret, value;
unsigned int *ptr;
struct tegra_bb *bb = dev_get_drvdata(dev);
struct platform_device *pdev = container_of(dev, struct platform_device,
dev);
if (!bb) {
dev_err(dev, "%s tegra_bb not found!\n", __func__);
return 0;
}
ret = sscanf(buf, "%d", &value);
if (ret != 1)
return -1;
value &= 0xFFFF;
ptr = bb->mb_virt + TEGRA_BB_REG_MAILBOX;
*ptr = (value & 0xFFFF) | ((~value << 16) & 0xFFFF0000);
tegra_bb_generate_ipc(pdev);
dev_dbg(dev, "%s status=0x%x\n", __func__, (unsigned int)value);
return count;
}
static DEVICE_ATTR(status, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
show_tegra_bb_status, store_tegra_bb_status);
static ssize_t show_tegra_bb_priv_size(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tegra_bb *bb = dev_get_drvdata(dev);
if (!bb) {
dev_err(dev, "%s tegra_bb not found!\n", __func__);
return 0;
}
dev_dbg(dev, "%s priv_size=%d\n", __func__,
(unsigned int)bb->priv_size);
return sprintf(buf, "%d\n", (int)bb->priv_size);
}
static DEVICE_ATTR(priv_size, S_IRUSR | S_IRGRP, show_tegra_bb_priv_size, NULL);
static ssize_t show_tegra_bb_ipc_size(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tegra_bb *bb = dev_get_drvdata(dev);
if (!bb) {
dev_err(dev, "%s tegra_bb not found!\n", __func__);
return 0;
}
dev_dbg(dev, "%s ipc_size=%d\n", __func__, (unsigned int)bb->ipc_size);
return sprintf(buf, "%d\n", (int)bb->ipc_size);
}
static DEVICE_ATTR(ipc_size, S_IRUSR | S_IRGRP, show_tegra_bb_ipc_size, NULL);
static ssize_t store_tegra_bb_reset(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ret, value;
static bool regulator_status;
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
struct tegra_bb *bb = dev_get_drvdata(dev);
if (!bb) {
dev_err(dev, "%s tegra_bb not found!\n", __func__);
return 0;
}
ret = sscanf(buf, "%d", &value);
if (ret != 1)
return -1;
if ((value < 0) || (value > 1))
return -EINVAL;
dev_dbg(dev, "%s reset=%d\n", __func__, (unsigned int)value);
/* reset is active low - sysfs interface assume reset is active high */
if (value) {
writel(1 << AP2BB_RESET_SHIFT | 1 << AP2BB_MEM_STS_SHIFT,
pmc + PMC_IPC_CLEAR_0);
if (regulator_status == true) {
pr_debug("%s: disabling bbc regulators\n", __func__);
regulator_disable(bb->vdd_bb_core);
regulator_disable(bb->vdd_bb_pll);
regulator_status = false;
}
bb->state = BBC_REMOVE_FLOOR;
queue_work(bb->workqueue, &bb->work);
} else {
/* power on bbc rails */
if (bb->vdd_bb_core && bb->vdd_bb_pll &&
regulator_status == false) {
pr_debug("%s: enabling bbc regulators\n", __func__);
regulator_set_voltage(bb->vdd_bb_core, 1100000,
1100000);
regulator_enable(bb->vdd_bb_core);
regulator_set_voltage(bb->vdd_bb_pll, 1100000,
1100000);
regulator_enable(bb->vdd_bb_pll);
regulator_status = true;
tegra_bb_enable_mem_req_soon();
}
writel(1 << AP2BB_RESET_SHIFT | 1 << AP2BB_MEM_STS_SHIFT,
pmc + PMC_IPC_SET_0);
}
return ret;
}
static ssize_t show_tegra_bb_reset(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int sts;
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
/* reset is active low - sysfs interface assume reset is active high */
sts = readl(pmc + PMC_IPC_STS_0);
dev_dbg(dev, "%s IPC_STS=0x%x\n", __func__, (unsigned int)sts);
sts = ~sts >> AP2BB_RESET_SHIFT;
sts &= AP2BB_RESET_DEFAULT_MASK;
dev_dbg(dev, "%s reset=%d\n", __func__, (unsigned int)sts);
return sprintf(buf, "%d\n", (int)sts);
}
static DEVICE_ATTR(reset, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
show_tegra_bb_reset, store_tegra_bb_reset);
static ssize_t show_tegra_bb_state(struct device *dev,
struct device_attribute *attr,
char *buf)
{
unsigned int sts, mem_req, mem_req_soon;
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
sts = readl(pmc + PMC_IPC_STS_0);
dev_dbg(dev, "%s IPC_STS=0x%x\n", __func__, (unsigned int)sts);
mem_req = (sts >> BB2AP_MEM_REQ_SHIFT) & 1;
mem_req_soon = (sts >> BB2AP_MEM_REQ_SOON_SHIFT) & 1;
dev_dbg(dev, "%s mem_req=%d mem_req_soon=%d\n", __func__,
mem_req, mem_req_soon);
return sprintf(buf, "%d\n", (unsigned int)mem_req);
}
static DEVICE_ATTR(state, S_IRUSR | S_IRGRP, show_tegra_bb_state, NULL);
static ssize_t show_tegra_bb_serial(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct tegra_bb *bb = dev_get_drvdata(dev);
int idx, ret;
if (!bb) {
dev_err(dev, "%s tegra_bb not found!\n", __func__);
return 0;
}
for (idx = 0; NVSHM_SERIAL_BYTE_SIZE > idx; ++idx) {
ret = sprintf(buf+2*idx, "%02X", bb->ipc_serial[idx]);
if (ret < 0) {
dev_err(dev, "%s sprintf shm serial failure!\n",
__func__);
return 0;
}
}
buf[2*NVSHM_SERIAL_BYTE_SIZE] = '\n';
return (2*NVSHM_SERIAL_BYTE_SIZE+1);
}
static DEVICE_ATTR(serial, S_IRUSR | S_IRGRP, show_tegra_bb_serial, NULL);
void tegra_bb_set_ipc_serial(struct platform_device *pdev, char *serial)
{
struct tegra_bb *bb = platform_get_drvdata(pdev);
if (!bb) {
dev_err(&pdev->dev, "%s tegra_bb not found!\n", __func__);
return;
}
if (serial == NULL) {
/* Remove sysfs entry */
device_remove_file(&pdev->dev, &dev_attr_serial);
return;
}
/* Create sysfs entry */
device_create_file(&pdev->dev, &dev_attr_serial);
/* Locally store serail number for future sysfs access */
memcpy(bb->ipc_serial, serial, sizeof(bb->ipc_serial));
}
EXPORT_SYMBOL_GPL(tegra_bb_set_ipc_serial);
#ifndef CONFIG_TEGRA_BASEBAND_SIMU
static irqreturn_t tegra_bb_isr_handler(int irq, void *data)
{
struct tegra_bb *bb = (struct tegra_bb *)data;
void __iomem *fctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
unsigned long irq_flags;
int sts;
disable_irq_nosync(irq);
#ifdef CONFIG_PM
if (bb->is_suspending)
pm_wakeup_event(bb->dev, 0);
#endif
spin_lock_irqsave(&bb->lock, irq_flags);
/* read/clear INT status */
sts = readl(fctrl + FLOW_IPC_STS_0);
if (sts & (1 << BB2AP_INT0_STS_SHIFT))
writel(1 << BB2AP_INT0_STS_SHIFT, fctrl + FLOW_IPC_CLR_0);
spin_unlock_irqrestore(&bb->lock, irq_flags);
/* wake IPC mechanism */
if (bb->ipc_cb)
bb->ipc_cb(bb->ipc_cb_data);
/* Notify sysfs */
sts = *(unsigned int *)bb->mb_virt & TEGRA_BB_STATUS_MASK;
if ((bb->status != TEGRA_BB_IPC_READY) ||
(bb->status != sts)) {
pr_debug("%s: notify sysfs status %d\n", __func__, sts);
sysfs_notify_dirent(bb->sd);
}
if (sts == TEGRA_BB_BOOT_RESTART_FW_REQ) {
pr_debug("%s: boot_restart_fw_req\n", __func__);
bb->state = BBC_CRASHDUMP_FLOOR;
queue_work(bb->workqueue, &bb->work);
}
bb->status = sts;
return IRQ_HANDLED;
}
static irqreturn_t tegra_bb_mem_req_soon(int irq, void *data)
{
struct tegra_bb *bb = (struct tegra_bb *)data;
void __iomem *fctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
u32 fc_sts;
spin_lock(&bb->lock);
/* clear the interrupt */
fc_sts = readl(fctrl + FLOW_IPC_STS_0);
if (fc_sts & (0x8 << AP2BB_MSC_STS_SHIFT)) {
writel((0x8 << AP2BB_MSC_STS_SHIFT), fctrl + FLOW_IPC_CLR_0);
irq_set_irq_type(bb->mem_req_soon, IRQF_TRIGGER_RISING);
bb->state = BBC_SET_FLOOR;
queue_work(bb->workqueue, &bb->work);
}
spin_unlock(&bb->lock);
return IRQ_HANDLED;
}
static inline void pmc_32kwritel(u32 val, unsigned long offs)
{
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
writel(val, pmc + offs);
udelay(130);
}
static void tegra_bb_enable_pmc_wake(void)
{
/* Set PMC wake interrupt on active low
* please see Bug 1181348 for details of SW WAR
*/
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
u32 reg = readl(pmc + PMC_CTRL2);
reg &= ~(PMC_CTRL2_WAKE_DET_EN);
pmc_32kwritel(reg, PMC_CTRL2);
reg = readl(pmc + PMC_WAKE2_LEVEL);
reg &= ~(PMC_WAKE2_BB_MEM_REQ);
pmc_32kwritel(reg, PMC_WAKE2_LEVEL);
usleep_range(1000, 1100);
pmc_32kwritel(1, PMC_AUTO_WAKE_LVL);
usleep_range(1000, 1100);
reg = readl(pmc + PMC_WAKE2_MASK);
reg |= PMC_WAKE2_BB_MEM_REQ;
pmc_32kwritel(reg, PMC_WAKE2_MASK);
reg = readl(pmc + PMC_CTRL2);
reg |= PMC_CTRL2_WAKE_DET_EN;
pmc_32kwritel(reg, PMC_CTRL2);
pr_debug("%s\n", __func__);
}
static irqreturn_t tegra_pmc_wake_intr(int irq, void *data)
{
struct tegra_bb *bb = (struct tegra_bb *)data;
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
static void __iomem *tert_ictlr = IO_ADDRESS(TEGRA_TERTIARY_ICTLR_BASE);
u32 lic, sts;
int mem_req_soon;
spin_lock(&bb->lock);
sts = readl(pmc + PMC_IPC_STS_0);
mem_req_soon = (sts >> BB2AP_MEM_REQ_SOON_SHIFT) & 1;
/* clear interrupt */
lic = readl(tert_ictlr + TRI_ICTLR_VIRQ_CPU);
if (lic & TRI_ICTLR_PMC_WAKE_INT)
writel(TRI_ICTLR_PMC_WAKE_INT,
tert_ictlr + TRI_ICTLR_CPU_IER_CLR);
irq_set_irq_type(INT_PMC_WAKE_INT, IRQF_TRIGGER_RISING);
if (!mem_req_soon) {
bb->state = BBC_REMOVE_FLOOR;
queue_work(bb->workqueue, &bb->work);
}
spin_unlock(&bb->lock);
return IRQ_HANDLED;
}
static void tegra_bb_emc_dvfs(struct work_struct *work)
{
struct tegra_bb *bb = container_of(work, struct tegra_bb, work);
unsigned long flags;
if (!bb)
return;
spin_lock_irqsave(&bb->lock, flags);
if (bb->prev_state == bb->state) {
spin_unlock_irqrestore(&bb->lock, flags);
return;
}
switch (bb->state) {
case BBC_SET_FLOOR:
bb->prev_state = bb->state;
spin_unlock_irqrestore(&bb->lock, flags);
pm_runtime_get_sync(bb->dev);
/* going from 0 to high */
clk_prepare_enable(bb->emc_clk);
if (bb->emc_flags & EMC_DSR)
tegra_emc_dsr_override(TEGRA_EMC_DSR_OVERRIDE);
clk_set_rate(bb->emc_clk, bb->emc_min_freq);
pr_debug("bbc setting floor to %lu\nMHz",
bb->emc_min_freq/1000000);
/* restore iso bw request*/
tegra_bbc_proxy_restore_iso(bb->proxy_dev);
/* reenable pmc_wake_det irq */
tegra_bb_enable_pmc_wake();
irq_set_irq_type(INT_PMC_WAKE_INT, IRQF_TRIGGER_HIGH);
return;
case BBC_REMOVE_FLOOR:
/* discard erroneous request */
if (bb->prev_state != BBC_SET_FLOOR) {
spin_unlock_irqrestore(&bb->lock, flags);
return;
}
bb->prev_state = bb->state;
spin_unlock_irqrestore(&bb->lock, flags);
/* remove iso bandwitdh request from bbc */
tegra_bbc_proxy_clear_iso(bb->proxy_dev);
/* going from high to 0 */
if (bb->emc_flags & EMC_DSR)
tegra_emc_dsr_override(TEGRA_EMC_DSR_NORMAL);
clk_set_rate(bb->emc_clk, 0);
clk_disable_unprepare(bb->emc_clk);
pr_debug("bbc removing emc floor\n");
/* reenable mem_req_soon irq */
tegra_bb_enable_mem_req_soon();
irq_set_irq_type(bb->mem_req_soon, IRQF_TRIGGER_HIGH);
pm_runtime_put(bb->dev);
return;
case BBC_CRASHDUMP_FLOOR:
/* BBC is crashed and ready to send coredump.
* do not store prev_state */
spin_unlock_irqrestore(&bb->lock, flags);
pr_info("%s: bbc crash detected, set EMC to max\n", __func__);
if (bb->prev_state != BBC_SET_FLOOR) {
pm_runtime_get_sync(bb->dev);
clk_prepare_enable(bb->emc_clk);
}
tegra_emc_dsr_override(TEGRA_EMC_DSR_OVERRIDE);
clk_set_rate(bb->emc_clk, BBC_MC_MAX_FREQ);
return;
default:
spin_unlock_irqrestore(&bb->lock, flags);
break;
}
return;
}
void tegra_bb_set_emc_floor(struct device *dev, unsigned long freq, u32 flags)
{
struct tegra_bb *bb = dev_get_drvdata(dev);
if (!bb) {
dev_err(dev, "%s tegra_bb not found!\n", __func__);
return;
}
if (bb->emc_min_freq != freq) {
bb->emc_min_freq = freq;
clk_set_rate(bb->emc_clk, bb->emc_min_freq);
}
bb->emc_flags = flags;
return;
}
EXPORT_SYMBOL(tegra_bb_set_emc_floor);
#endif
static int tegra_bb_pm_notifier_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
struct tegra_bb *bb = container_of(this, struct tegra_bb, pm_notifier);
int sts, mem_req_soon;
if (!bb) {
pr_err("tegra_bb not found!\n");
return NOTIFY_OK;
}
sts = readl(pmc + PMC_IPC_STS_0);
mem_req_soon = (sts >> BB2AP_MEM_REQ_SOON_SHIFT) & 1;
sts = sts >> AP2BB_RESET_SHIFT;
sts &= AP2BB_RESET_DEFAULT_MASK;
switch (event) {
case PM_SUSPEND_PREPARE:
/* make sure IRQ will send a pm wake event */
bb->is_suspending = true;
/* inform tegra_common_suspend about EMC requirement */
tegra_lp1bb_suspend_emc_rate(bb->emc_min_freq, BBC_MC_MIN_FREQ);
/* prepare for possible LP1BB state */
if (sts)
clk_set_rate(bb->emc_clk, BBC_MC_MIN_FREQ);
return NOTIFY_OK;
case PM_POST_SUSPEND:
/* no need for IRQ to send a pm wake events anymore */
bb->is_suspending = false;
if (sts && !mem_req_soon) {
pr_debug("bbc is inactive, remove floor\n");
clk_set_rate(bb->emc_clk, 0);
}
/* else, wait for IRQs to do the job */
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
static int tegra_bb_probe(struct platform_device *pdev)
{
struct tegra_bb *bb;
int ret;
struct tegra_bb_platform_data *pdata;
void __iomem *tegra_mc = IO_ADDRESS(TEGRA_MC_BASE);
unsigned int size, bbc_mem_regions_0;
struct clk *c;
unsigned int mb_size = SZ_4K + SZ_128K; /* config + stats */
if (!pdev) {
pr_err("%s platform device is NULL!\n", __func__);
return -EINVAL;
}
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "%s tegra_bb not found!\n", __func__);
return -EINVAL;
}
dev_dbg(&pdev->dev, "%s\n", __func__);
bb = kzalloc(sizeof(struct tegra_bb), GFP_KERNEL);
if (bb == NULL) {
kfree(bb);
return -ENOMEM;
}
spin_lock_init(&bb->lock);
/* Private region */
bbc_mem_regions_0 = readl(tegra_mc + MC_BBC_MEM_REGIONS_0_OFFSET);
pr_info("%s MC_BBC_MEM_REGIONS_0=0x%x\n", __func__, bbc_mem_regions_0);
size = (bbc_mem_regions_0 >> MC_BBC_MEM_REGIONS_0_PRIV_SIZE_SHIFT) &
MC_BBC_MEM_REGIONS_0_PRIV_SIZE_MASK;
/* Private */
switch (size) {
case 0:
bb->priv_size = SZ_8M;
break;
case 1:
bb->priv_size = SZ_16M;
break;
case 2:
bb->priv_size = SZ_32M;
break;
case 3:
bb->priv_size = SZ_64M;
break;
case 7:
pr_err("%s no private memory mapped\n", __func__);
break;
default:
pr_err("%s invalid private memory size 0x%x\n", __func__, size);
break;
}
bb->priv_phy =
((bbc_mem_regions_0 >> MC_BBC_MEM_REGIONS_0_PRIV_BASE_SHIFT)
& MC_BBC_MEM_REGIONS_0_PRIV_BASE_MASK) << 23;
/* IPC */
size = (bbc_mem_regions_0 >> MC_BBC_MEM_REGIONS_0_IPC_SIZE_SHIFT) &
MC_BBC_MEM_REGIONS_0_IPC_SIZE_MASK;
switch (size) {
case 0:
bb->ipc_size = SZ_8M;
break;
case 1:
bb->ipc_size = SZ_16M;
break;
case 2:
bb->ipc_size = SZ_32M;
break;
case 3:
bb->ipc_size = SZ_64M;
break;
case 7:
pr_err("%s no IPC memory mapped\n", __func__);
break;
default:
pr_err("%s invalid IPC memory size 0x%x\n", __func__, size);
break;
}
bb->ipc_phy =
((bbc_mem_regions_0 >> MC_BBC_MEM_REGIONS_0_IPC_BASE_SHIFT)
& MC_BBC_MEM_REGIONS_0_IPC_BASE_MASK) << 23;
pr_info("%s priv@0x%lx/0x%lx\n", __func__,
(unsigned long)bb->priv_phy,
bb->priv_size);
pr_info("%s ipc@0x%lx/0x%lx\n", __func__,
(unsigned long)bb->ipc_phy,
bb->ipc_size);
if (!(bb->ipc_size && bb->priv_size)) {
pr_err("%s: invalid tegra_bb regions\n", __func__);
BUG();
}
bb->irq = pdata->bb_irq;
bb->mem_req_soon = pdata->mem_req_soon;
/* Map mb_virt uncached (first 132K of IPC for config + statistics) */
bb->mb_virt = ioremap_nocache(bb->ipc_phy, mb_size);
pr_debug("%s: uncached IPC Virtual=0x%p\n", __func__, bb->mb_virt);
/* IPC memory is cached */
bb->ipc_virt = ioremap_cached(bb->ipc_phy, bb->ipc_size);
pr_debug("%s: IPC Virtual=0x%p\n", __func__, bb->ipc_virt);
/* clear the first 4K of IPC memory */
memset(bb->mb_virt, 0, SZ_1K*4);
/* init value of cold boot */
*(unsigned int *)bb->mb_virt = TEGRA_BB_IPC_COLDBOOT |
((~TEGRA_BB_IPC_COLDBOOT) << 16);
/* Register devs */
bb->dev_priv.minor = MISC_DYNAMIC_MINOR;
snprintf(bb->priv_name, sizeof(bb->priv_name),
"tegra_bb_priv%d", pdev->id);
bb->dev_priv.name = bb->priv_name;
bb->dev_priv.fops = &tegra_bb_priv_fops;
bb->dev_priv.parent = &pdev->dev;
bb->dev_ipc.minor = MISC_DYNAMIC_MINOR;
snprintf(bb->ipc_name, sizeof(bb->ipc_name),
"tegra_bb_ipc%d",
pdev->id);
bb->dev_ipc.name = bb->ipc_name;
bb->dev_ipc.fops = &tegra_bb_ipc_fops;
bb->dev_ipc.parent = &pdev->dev;
ret = misc_register(&bb->dev_priv);
if (ret) {
dev_err(&pdev->dev, "unable to register miscdevice %s\n",
bb->dev_priv.name);
kfree(bb);
return -EAGAIN;
}
ret = misc_register(&bb->dev_ipc);
if (ret) {
dev_err(&pdev->dev, "unable to register miscdevice %s\n",
bb->dev_ipc.name);
kfree(bb);
return -EAGAIN;
}
bb->dev = &pdev->dev;
bb->instance = pdev->id;
bb->status = 0;
ret = device_create_file(&pdev->dev, &dev_attr_status);
ret = device_create_file(&pdev->dev, &dev_attr_retcode);
ret = device_create_file(&pdev->dev, &dev_attr_priv_size);
ret = device_create_file(&pdev->dev, &dev_attr_ipc_size);
ret = device_create_file(&pdev->dev, &dev_attr_reset);
ret = device_create_file(&pdev->dev, &dev_attr_state);
bb->sd = sysfs_get_dirent(pdev->dev.kobj.sd, NULL, "status");
bb->vdd_bb_core = regulator_get(NULL, "vdd_bb");
if (IS_ERR_OR_NULL(bb->vdd_bb_core)) {
pr_err("vdd_bb regulator get failed\n");
bb->vdd_bb_core = NULL;
}
bb->vdd_bb_pll = regulator_get(NULL, "avdd_bb_pll");
if (IS_ERR_OR_NULL(bb->vdd_bb_pll)) {
pr_err("avdd_bb_pll regulator get failed\n");
bb->vdd_bb_pll = NULL;
}
/* clk enable for mc_bbc / pll_p_bbc */
c = tegra_get_clock_by_name("mc_bbc");
if (IS_ERR_OR_NULL(c))
pr_err("mc_bbc get failed\n");
else
clk_enable(c);
c = tegra_get_clock_by_name("pll_p_bbc");
if (IS_ERR_OR_NULL(c))
pr_err("pll_p_bbc get failed\n");
else
clk_enable(c);
bb->nvshm_pdata.ipc_base_virt = bb->ipc_virt;
bb->nvshm_pdata.ipc_size = bb->ipc_size;
bb->nvshm_pdata.mb_base_virt = bb->mb_virt;
bb->nvshm_pdata.mb_size = mb_size;
bb->nvshm_pdata.bb_irq = bb->irq;
bb->nvshm_pdata.tegra_bb = pdev;
bb->nvshm_device.name = "nvshm";
bb->nvshm_device.id = bb->instance;
bb->nvshm_device.dev.platform_data = &bb->nvshm_pdata;
platform_device_register(&bb->nvshm_device);
tegra_pd_add_device(&pdev->dev);
pm_runtime_enable(&pdev->dev);
#ifndef CONFIG_TEGRA_BASEBAND_SIMU
snprintf(bb->name, sizeof(bb->name), "tegra_bb%d", pdev->id);
ret = request_irq(bb->irq, tegra_bb_isr_handler, IRQF_TRIGGER_HIGH,
bb->name, bb);
if (ret) {
dev_err(&pdev->dev, "Could not register irq handler\n");
kfree(bb);
return -EAGAIN;
}
ret = enable_irq_wake(bb->irq);
if (ret) {
dev_err(&pdev->dev, "set enable_irq_wake failed\n");
kfree(bb);
return -EAGAIN;
}
/* setup emc dvfs request framework */
bb->emc_clk = clk_get(&pdev->dev, "emc_fl");
if (IS_ERR(bb->emc_clk)) {
dev_err(&pdev->dev, "can't get emc clock\n");
kfree(bb);
return -ENOENT;
}
bb->workqueue = alloc_workqueue("bbc-pm", WQ_HIGHPRI, 1);
if (!bb->workqueue) {
dev_err(&pdev->dev, "failed to create workqueue\n");
kfree(bb);
return -ENOMEM;
}
INIT_WORK(&bb->work, tegra_bb_emc_dvfs);
bb->emc_min_freq = BBC_MC_MIN_FREQ;
bb->emc_flags = EMC_DSR;
/* get bbc proxy device struct, it should be registered
* before this driver.
*/
bb->proxy_dev = bus_find_device_by_name(&platform_bus_type, NULL,
"tegra_bbc_proxy");
if (!bb->proxy_dev)
dev_warn(&pdev->dev, "%s: bbc proxy device not found!\n",
__func__);
ret = request_irq(bb->mem_req_soon, tegra_bb_mem_req_soon,
IRQF_TRIGGER_RISING, "bb_mem_req_soon", bb);
if (ret) {
dev_err(&pdev->dev, "Could not register mem_req_soon irq\n");
kfree(bb);
return -EAGAIN;
}
tegra_bb_enable_mem_req_soon();
ret = request_irq(INT_PMC_WAKE_INT, tegra_pmc_wake_intr,
IRQF_TRIGGER_RISING, "tegra_pmc_wake_intr", bb);
if (ret) {
dev_err(&pdev->dev,
"Could not register pmc_wake_int irq handler\n");
kfree(bb);
return -EAGAIN;
}
bb->pm_notifier.notifier_call = tegra_bb_pm_notifier_event;
register_pm_notifier(&bb->pm_notifier);
#endif
bb->is_suspending = false;
dev_set_drvdata(&pdev->dev, bb);
return 0;
}
#ifdef CONFIG_PM
static int tegra_bb_suspend(struct device *dev)
{
dev_dbg(dev, "%s\n", __func__);
return 0;
}
static int tegra_bb_resume(struct device *dev)
{
#ifndef CONFIG_TEGRA_BASEBAND_SIMU
struct tegra_bb *bb = dev_get_drvdata(dev);
#endif
dev_dbg(dev, "%s\n", __func__);
#ifndef CONFIG_TEGRA_BASEBAND_SIMU
tegra_bb_enable_mem_req_soon();
irq_set_irq_type(bb->mem_req_soon, IRQF_TRIGGER_HIGH);
tegra_bb_enable_pmc_wake();
irq_set_irq_type(INT_PMC_WAKE_INT, IRQF_TRIGGER_HIGH);
#endif
return 0;
}
static int tegra_bb_suspend_noirq(struct device *dev)
{
dev_dbg(dev, "%s\n", __func__);
/* abort suspend if IPC interrupt is pending*/
if (tegra_bb_check_bb2ap_ipc())
return -EBUSY;
return 0;
}
static int tegra_bb_resume_noirq(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops tegra_bb_pm_ops = {
.suspend_noirq = tegra_bb_suspend_noirq,
.resume_noirq = tegra_bb_resume_noirq,
.suspend = tegra_bb_suspend,
.resume = tegra_bb_resume,
};
#endif
static struct platform_driver tegra_bb_driver = {
.driver = {
.name = "tegra_bb",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &tegra_bb_pm_ops,
#endif
},
.probe = tegra_bb_probe,
};
static int __init tegra_bb_init(void)
{
int ret;
ret = platform_driver_register(&tegra_bb_driver);
pr_debug("%s ret %d\n", __func__, ret);
return ret;
}
static void __exit tegra_bb_exit(void)
{
pr_debug("%s\n", __func__);
platform_driver_unregister(&tegra_bb_driver);
}
fs_initcall(tegra_bb_init);
module_exit(tegra_bb_exit);
#ifdef CONFIG_DEBUG_FS
static struct dentry *bb_debugfs_root;
static enum pmc_event_sel {
PMC_EVENT_NONE,
PMC_EVENT_LP0BB,
PMC_EVENT_LP0ACTIVE,
} pmc_event_sel;
static int lp0bb_transitions_set(void *data, u64 val)
{
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
u32 reg;
if (!val) {
/* disable event counting and clear counter */
reg = 0;
writel(reg, pmc + PMC_EVENT_COUNTER_0);
pmc_event_sel = PMC_EVENT_NONE;
} else if (val == 1) {
reg = PMC_EVENT_COUNTER_0_EN_MASK | PMC_EVENT_COUNTER_0_LP0BB;
/* lp0->lp0bb transitions */
writel(reg, pmc + PMC_EVENT_COUNTER_0);
pmc_event_sel = PMC_EVENT_LP0BB;
}
return 0;
}
static inline unsigned long read_pmc_event_counter(void)
{
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
u32 reg = readl(pmc + PMC_EVENT_COUNTER_0);
/* hw event counter is 16 bit */
reg = reg & 0xffff;
return reg;
}
static int lp0bb_transitions_get(void *data, u64 *val)
{
if (pmc_event_sel == PMC_EVENT_LP0BB)
*val = (u32) read_pmc_event_counter();
else
*val = 0;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(lp0bb_fops, lp0bb_transitions_get,
lp0bb_transitions_set, "%lld\n");
static int lp0active_transitions_set(void *data, u64 val)
{
void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
u32 reg;
if (!val) {
/* disable event counting and clear counter */
reg = 0;
writel(reg, pmc + PMC_EVENT_COUNTER_0);
pmc_event_sel = PMC_EVENT_NONE;
} else if (val == 1) {
reg = PMC_EVENT_COUNTER_0_EN_MASK |
PMC_EVENT_COUNTER_0_LP0ACTIVE;
/* lp0->active transitions */
writel(reg, pmc + PMC_EVENT_COUNTER_0);
pmc_event_sel = PMC_EVENT_LP0ACTIVE;
}
return 0;
}
static int lp0active_transitions_get(void *data, u64 *val)
{
if (pmc_event_sel == PMC_EVENT_LP0ACTIVE)
*val = (u32) read_pmc_event_counter();
else
*val = 0;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(lp0active_fops, lp0active_transitions_get,
lp0active_transitions_set, "%lld\n");
static int __init tegra_bb_debug_init(void)
{
struct dentry *d;
bb_debugfs_root = debugfs_create_dir("tegra_bb", NULL);
if (!bb_debugfs_root)
return -ENOMEM;
d = debugfs_create_file("lp0bb_transitions", S_IWUSR | S_IRUGO,
bb_debugfs_root, NULL, &lp0bb_fops);
if (!d)
goto err;
d = debugfs_create_file("lp0active_transitions", S_IWUSR | S_IRUGO,
bb_debugfs_root, NULL, &lp0active_fops);
if (!d)
goto err;
return 0;
err:
debugfs_remove_recursive(bb_debugfs_root);
return -ENOMEM;
}
late_initcall(tegra_bb_debug_init);
#endif
MODULE_DESCRIPTION("Tegra T148 BB Module");
MODULE_LICENSE("GPL");