396 lines
10 KiB
C
Executable file
396 lines
10 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2014-2019, Samsung Electronics.
|
|
*
|
|
* 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/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/bitops.h>
|
|
|
|
#include "gnss_mbox.h"
|
|
#include "gnss_utils.h"
|
|
|
|
static struct gnss_mbox_drv_data _gnss_mbox[MAX_MBOX_REGION];
|
|
|
|
static inline void gnss_mbox_writel(enum gnss_mbox_region id, u32 val, long reg)
|
|
{
|
|
writel(val, _gnss_mbox[id].base + reg);
|
|
}
|
|
|
|
static inline u32 gnss_mbox_readl(enum gnss_mbox_region id, long reg)
|
|
{
|
|
return readl(_gnss_mbox[id].base + reg);
|
|
}
|
|
|
|
int gnss_mbox_register_irq(enum gnss_mbox_region id, u32 int_num, irq_handler_t handler, void *data)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if ((!handler) || (int_num > 15))
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&_gnss_mbox[id].lock, flags);
|
|
|
|
_gnss_mbox[id].hd[int_num].data = data;
|
|
_gnss_mbox[id].hd[int_num].handler = handler;
|
|
_gnss_mbox[id].registered_irq |= 1 << (int_num + 16);
|
|
set_bit(int_num, &_gnss_mbox[id].unmasked_irq);
|
|
|
|
spin_unlock_irqrestore(&_gnss_mbox[id].lock, flags);
|
|
|
|
gnss_mbox_enable_irq(id, int_num);
|
|
gif_info("id:%d num:%d intmr0:0x%08x\n", id, int_num, gnss_mbox_readl(id, EXYNOS_MBOX_INTMR0));
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_register_irq);
|
|
|
|
int gnss_mbox_unregister_irq(enum gnss_mbox_region id, u32 int_num, irq_handler_t handler)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (!handler || (_gnss_mbox[id].hd[int_num].handler != handler))
|
|
return -EINVAL;
|
|
|
|
gnss_mbox_disable_irq(id, int_num);
|
|
gif_info("id:%d num:%d intmr0:0x%08x\n", id, int_num, gnss_mbox_readl(id, EXYNOS_MBOX_INTMR0));
|
|
|
|
spin_lock_irqsave(&_gnss_mbox[id].lock, flags);
|
|
|
|
_gnss_mbox[id].hd[int_num].data = NULL;
|
|
_gnss_mbox[id].hd[int_num].handler = NULL;
|
|
_gnss_mbox[id].registered_irq &= ~(1 << (int_num + 16));
|
|
clear_bit(int_num, &_gnss_mbox[id].unmasked_irq);
|
|
|
|
spin_unlock_irqrestore(&_gnss_mbox[id].lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_unregister_irq);
|
|
|
|
int gnss_mbox_enable_irq(enum gnss_mbox_region id, u32 int_num)
|
|
{
|
|
unsigned long flags;
|
|
unsigned long tmp;
|
|
|
|
/* The irq should have been registered. */
|
|
if (!(_gnss_mbox[id].registered_irq & BIT(int_num + 16)))
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&_gnss_mbox[id].lock, flags);
|
|
|
|
tmp = gnss_mbox_readl(id, EXYNOS_MBOX_INTMR0);
|
|
|
|
/* Clear the mask if it was set. */
|
|
if (test_and_clear_bit(int_num + 16, &tmp))
|
|
gnss_mbox_writel(id, tmp, EXYNOS_MBOX_INTMR0);
|
|
|
|
/* Mark the irq as unmasked */
|
|
set_bit(int_num, &_gnss_mbox[id].unmasked_irq);
|
|
|
|
spin_unlock_irqrestore(&_gnss_mbox[id].lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_enable_irq);
|
|
|
|
int gnss_mbox_disable_irq(enum gnss_mbox_region id, u32 int_num)
|
|
{
|
|
unsigned long flags;
|
|
unsigned long irq_mask;
|
|
|
|
/* The interrupt must have been registered. */
|
|
if (!(_gnss_mbox[id].registered_irq & BIT(int_num + 16)))
|
|
return -EINVAL;
|
|
|
|
/* Set the mask */
|
|
spin_lock_irqsave(&_gnss_mbox[id].lock, flags);
|
|
|
|
irq_mask = gnss_mbox_readl(id, EXYNOS_MBOX_INTMR0);
|
|
|
|
/* Set the mask if it was not already set */
|
|
if (!test_and_set_bit(int_num + 16, &irq_mask)) {
|
|
gnss_mbox_writel(id, irq_mask, EXYNOS_MBOX_INTMR0);
|
|
|
|
udelay(5);
|
|
|
|
/* Reset the status bit to signal interrupt needs handling */
|
|
gnss_mbox_writel(id, BIT(int_num + 16), EXYNOS_MBOX_INTGR0);
|
|
|
|
udelay(5);
|
|
}
|
|
|
|
/* Remove the irq from the umasked irqs */
|
|
clear_bit(int_num, &_gnss_mbox[id].unmasked_irq);
|
|
|
|
spin_unlock_irqrestore(&_gnss_mbox[id].lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_disable_irq);
|
|
|
|
void gnss_mbox_set_interrupt(enum gnss_mbox_region id, u32 int_num)
|
|
{
|
|
/* generate interrupt */
|
|
if (int_num < 16)
|
|
gnss_mbox_writel(id, 0x1 << int_num, EXYNOS_MBOX_INTGR1);
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_set_interrupt);
|
|
|
|
/* Shared register : Get / Set / Extract / Update / Dump */
|
|
static bool is_valid_sr(enum gnss_mbox_region id, u32 sr_num)
|
|
{
|
|
if (!_gnss_mbox[id].num_shared_reg) {
|
|
gif_err("num_shared_reg is 0\n");
|
|
return false;
|
|
}
|
|
|
|
if (sr_num > _gnss_mbox[id].num_shared_reg) {
|
|
gif_err("num_shared_reg is %d:%d\n",
|
|
sr_num, _gnss_mbox[id].num_shared_reg);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
u32 gnss_mbox_get_sr(enum gnss_mbox_region id, u32 sr_num)
|
|
{
|
|
if (!is_valid_sr(id, sr_num))
|
|
return 0;
|
|
|
|
return gnss_mbox_readl(id,
|
|
_gnss_mbox[id].shared_reg_offset + (4 * sr_num));
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_get_sr);
|
|
|
|
u32 gnss_mbox_extract_sr(enum gnss_mbox_region id,
|
|
u32 sr_num, u32 mask, u32 pos)
|
|
{
|
|
if (!is_valid_sr(id, sr_num))
|
|
return 0;
|
|
|
|
return (gnss_mbox_get_sr(id, sr_num) >> pos) & mask;
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_extract_sr);
|
|
|
|
void gnss_mbox_set_sr(enum gnss_mbox_region id, u32 sr_num, u32 msg)
|
|
{
|
|
if (!is_valid_sr(id, sr_num))
|
|
return;
|
|
|
|
gnss_mbox_writel(id, msg, _gnss_mbox[id].shared_reg_offset + (4 * sr_num));
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_set_sr);
|
|
|
|
void gnss_mbox_update_sr(enum gnss_mbox_region id,
|
|
u32 sr_num, u32 msg, u32 mask, u32 pos)
|
|
{
|
|
u32 val;
|
|
unsigned long flags;
|
|
|
|
if (!is_valid_sr(id, sr_num))
|
|
return;
|
|
|
|
spin_lock_irqsave(&_gnss_mbox[id].lock, flags);
|
|
|
|
val = gnss_mbox_get_sr(id, sr_num);
|
|
val &= ~(mask << pos);
|
|
val |= (msg & mask) << pos;
|
|
gnss_mbox_set_sr(id, sr_num, val);
|
|
|
|
spin_unlock_irqrestore(&_gnss_mbox[id].lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_update_sr);
|
|
|
|
void gnss_mbox_dump_sr(enum gnss_mbox_region id)
|
|
{
|
|
unsigned long flags;
|
|
u32 i, value;
|
|
|
|
spin_lock_irqsave(&_gnss_mbox[id].lock, flags);
|
|
|
|
for (i = 0; i < _gnss_mbox[id].num_shared_reg; i++) {
|
|
value = gnss_mbox_readl(id,
|
|
_gnss_mbox[id].shared_reg_offset + (4 * i));
|
|
gif_info("mbox dump: 0x%02x: 0x%04x\n", i, value);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&_gnss_mbox[id].lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_dump_sr);
|
|
|
|
void gnss_mbox_sw_reset(enum gnss_mbox_region id)
|
|
{
|
|
u32 reg_val;
|
|
|
|
gif_info("Reset GNSS mailbox\n");
|
|
|
|
if (_gnss_mbox[id].use_sw_reset_reg) {
|
|
reg_val = gnss_mbox_readl(id, EXYNOS_MBOX_MCUCTLR);
|
|
reg_val |= (0x1 << MBOX_MCUCTLR_MSWRST);
|
|
|
|
gnss_mbox_writel(id, reg_val, EXYNOS_MBOX_MCUCTLR);
|
|
udelay(5);
|
|
}
|
|
|
|
gnss_mbox_writel(id, ~(_gnss_mbox[id].unmasked_irq) << 16, EXYNOS_MBOX_INTMR0);
|
|
gif_info("id:%d intmr0:0x%08x\n", id, gnss_mbox_readl(id, EXYNOS_MBOX_INTMR0));
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_sw_reset);
|
|
|
|
void gnss_mbox_clear_all_interrupt(enum gnss_mbox_region id)
|
|
{
|
|
gnss_mbox_writel(id, 0xFFFF, EXYNOS_MBOX_INTCR1);
|
|
}
|
|
EXPORT_SYMBOL(gnss_mbox_clear_all_interrupt);
|
|
|
|
/* Interrupt handler */
|
|
static irqreturn_t gnss_mbox_handler(int irq, void *data)
|
|
{
|
|
u32 irq_stat, i;
|
|
u32 id;
|
|
|
|
id = ((struct gnss_mbox_drv_data *)data)->id;
|
|
|
|
spin_lock(&_gnss_mbox[id].lock);
|
|
|
|
/* Check raised interrupts */
|
|
irq_stat = gnss_mbox_readl(id, EXYNOS_MBOX_INTSR0) & 0xFFFF0000;
|
|
|
|
/* Only clear and handle unmasked interrupts */
|
|
irq_stat &= ~(gnss_mbox_readl(id, EXYNOS_MBOX_INTMR0)) & 0xFFFF0000;
|
|
|
|
/* Interrupt Clear */
|
|
gnss_mbox_writel(id, irq_stat, EXYNOS_MBOX_INTCR0);
|
|
spin_unlock(&_gnss_mbox[id].lock);
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
if (irq_stat & (1 << (i + 16))) {
|
|
if ((1 << (i + 16)) & _gnss_mbox[id].registered_irq) {
|
|
_gnss_mbox[id].hd[i].handler(i, _gnss_mbox[id].hd[i].data);
|
|
} else {
|
|
gif_err_limited("Unregistered interrupt:%d %d 0x%08x 0x%08lx 0x%08x\n",
|
|
id, i, irq_stat,
|
|
_gnss_mbox[id].unmasked_irq << 16,
|
|
gnss_mbox_readl(id, EXYNOS_MBOX_INTMR0));
|
|
}
|
|
|
|
irq_stat &= ~(1 << (i + 16));
|
|
}
|
|
|
|
if (!irq_stat)
|
|
break;
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* Probe */
|
|
static int gnss_mbox_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct resource *res = NULL;
|
|
int ret = 0;
|
|
u32 id = 0;
|
|
int irq = 0;
|
|
|
|
gif_info("+++\n");
|
|
|
|
if (!dev->of_node) {
|
|
gif_err("of_node is null\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
gif_dt_read_u32(dev->of_node, "mbox,id", id);
|
|
if (id >= MAX_MBOX_REGION) {
|
|
gif_err("mbox,id is over %d:%d\n", MAX_MBOX_REGION, id);
|
|
return -EINVAL;
|
|
}
|
|
_gnss_mbox[id].id = id;
|
|
gif_dt_read_string(dev->of_node, "mbox,name", _gnss_mbox[id].name);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
_gnss_mbox[id].base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(_gnss_mbox[id].base)) {
|
|
gif_err("devm_ioremap_resource() error\n");
|
|
return PTR_ERR(_gnss_mbox[id].base);
|
|
}
|
|
|
|
/* Shared register */
|
|
gif_dt_read_u32(dev->of_node, "num_shared_reg",
|
|
_gnss_mbox[id].num_shared_reg);
|
|
gif_dt_read_u32(dev->of_node, "shared_reg_offset",
|
|
_gnss_mbox[id].shared_reg_offset);
|
|
gif_info("num_shared_reg:%d shared_reg_offset:0x%x\n",
|
|
_gnss_mbox[id].num_shared_reg, _gnss_mbox[id].shared_reg_offset);
|
|
|
|
/* SW reset reg */
|
|
gif_dt_read_bool(dev->of_node, "use_sw_reset_reg",
|
|
_gnss_mbox[id].use_sw_reset_reg);
|
|
gif_info("use_sw_reset_reg:%d\n", _gnss_mbox[id].use_sw_reset_reg);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
|
|
ret = devm_request_irq(dev, irq, gnss_mbox_handler,
|
|
IRQF_ONESHOT, pdev->name, &_gnss_mbox[id]);
|
|
if (ret) {
|
|
gif_err("Can't request %s interrupt!!!\n", pdev->name);
|
|
return -EIO;
|
|
}
|
|
|
|
spin_lock_init(&_gnss_mbox[id].lock);
|
|
gnss_mbox_clear_all_interrupt(id);
|
|
|
|
gnss_mbox_writel(id, 0xFFFF0000, EXYNOS_MBOX_INTMR0);
|
|
gif_info("id:%d intmr0:0x%08x\n", id, gnss_mbox_readl(id, EXYNOS_MBOX_INTMR0));
|
|
|
|
gif_info("---\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gnss_mbox_remove(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id gnss_mbox_dt_match[] = {
|
|
{ .compatible = "samsung,exynos-gnss-mailbox", },
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, gnss_mbox_dt_match);
|
|
|
|
static struct platform_driver gnss_mbox_driver = {
|
|
.probe = gnss_mbox_probe,
|
|
.remove = gnss_mbox_remove,
|
|
.driver = {
|
|
.name = "gnss_mailbox",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(gnss_mbox_dt_match),
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
};
|
|
module_platform_driver(gnss_mbox_driver);
|
|
|
|
MODULE_DESCRIPTION("Exynos GNSS mailbox driver");
|
|
MODULE_LICENSE("GPL");
|
|
|