diff options
Diffstat (limited to 'drivers/mailbox')
| -rw-r--r-- | drivers/mailbox/Kconfig | 34 | ||||
| -rw-r--r-- | drivers/mailbox/Makefile | 6 | ||||
| -rw-r--r-- | drivers/mailbox/mailbox-omap1.c | 203 | ||||
| -rw-r--r-- | drivers/mailbox/mailbox-omap2.c | 358 | ||||
| -rw-r--r-- | drivers/mailbox/omap-mailbox.c | 469 | ||||
| -rw-r--r-- | drivers/mailbox/omap-mbox.h | 67 | 
6 files changed, 1137 insertions, 0 deletions
| diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 9545c9f03809..c8b5c13bcd05 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -16,4 +16,38 @@ config PL320_MBOX  	  Management Engine, primarily for cpufreq. Say Y here if you want  	  to use the PL320 IPCM support. +config OMAP_MBOX +	tristate +	help +	  This option is selected by any OMAP architecture specific mailbox +	  driver such as CONFIG_OMAP1_MBOX or CONFIG_OMAP2PLUS_MBOX. This +	  enables the common OMAP mailbox framework code. + +config OMAP1_MBOX +	tristate "OMAP1 Mailbox framework support" +	depends on ARCH_OMAP1 +	select OMAP_MBOX +	help +	  Mailbox implementation for OMAP chips with hardware for +	  interprocessor communication involving DSP in OMAP1. Say Y here +	  if you want to use OMAP1 Mailbox framework support. + +config OMAP2PLUS_MBOX +	tristate "OMAP2+ Mailbox framework support" +	depends on ARCH_OMAP2PLUS +	select OMAP_MBOX +	help +	  Mailbox implementation for OMAP family chips with hardware for +	  interprocessor communication involving DSP, IVA1.0 and IVA2 in +	  OMAP2/3; or IPU, IVA HD and DSP in OMAP4/5. Say Y here if you +	  want to use OMAP2+ Mailbox framework support. + +config OMAP_MBOX_KFIFO_SIZE +	int "Mailbox kfifo default buffer size (bytes)" +	depends on OMAP2PLUS_MBOX || OMAP1_MBOX +	default 256 +	help +	  Specify the default size of mailbox's kfifo buffers (bytes). +	  This can also be changed at runtime (via the mbox_kfifo_size +	  module parameter).  endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index 543ad6a79505..e0facb34084a 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -1 +1,7 @@  obj-$(CONFIG_PL320_MBOX)	+= pl320-ipc.o + +obj-$(CONFIG_OMAP_MBOX)		+= omap-mailbox.o +obj-$(CONFIG_OMAP1_MBOX)	+= mailbox_omap1.o +mailbox_omap1-objs		:= mailbox-omap1.o +obj-$(CONFIG_OMAP2PLUS_MBOX)	+= mailbox_omap2.o +mailbox_omap2-objs		:= mailbox-omap2.o diff --git a/drivers/mailbox/mailbox-omap1.c b/drivers/mailbox/mailbox-omap1.c new file mode 100644 index 000000000000..9001b7633f10 --- /dev/null +++ b/drivers/mailbox/mailbox-omap1.c @@ -0,0 +1,203 @@ +/* + * Mailbox reservation modules for OMAP1 + * + * Copyright (C) 2006-2009 Nokia Corporation + * Written by: Hiroshi DOYU <[email protected]> + * + * This file is subject to the terms and conditions of the GNU General Public + * License.  See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +#include "omap-mbox.h" + +#define MAILBOX_ARM2DSP1		0x00 +#define MAILBOX_ARM2DSP1b		0x04 +#define MAILBOX_DSP2ARM1		0x08 +#define MAILBOX_DSP2ARM1b		0x0c +#define MAILBOX_DSP2ARM2		0x10 +#define MAILBOX_DSP2ARM2b		0x14 +#define MAILBOX_ARM2DSP1_Flag		0x18 +#define MAILBOX_DSP2ARM1_Flag		0x1c +#define MAILBOX_DSP2ARM2_Flag		0x20 + +static void __iomem *mbox_base; + +struct omap_mbox1_fifo { +	unsigned long cmd; +	unsigned long data; +	unsigned long flag; +}; + +struct omap_mbox1_priv { +	struct omap_mbox1_fifo tx_fifo; +	struct omap_mbox1_fifo rx_fifo; +}; + +static inline int mbox_read_reg(size_t ofs) +{ +	return __raw_readw(mbox_base + ofs); +} + +static inline void mbox_write_reg(u32 val, size_t ofs) +{ +	__raw_writew(val, mbox_base + ofs); +} + +/* msg */ +static mbox_msg_t omap1_mbox_fifo_read(struct omap_mbox *mbox) +{ +	struct omap_mbox1_fifo *fifo = +		&((struct omap_mbox1_priv *)mbox->priv)->rx_fifo; +	mbox_msg_t msg; + +	msg = mbox_read_reg(fifo->data); +	msg |= ((mbox_msg_t) mbox_read_reg(fifo->cmd)) << 16; + +	return msg; +} + +static void +omap1_mbox_fifo_write(struct omap_mbox *mbox, mbox_msg_t msg) +{ +	struct omap_mbox1_fifo *fifo = +		&((struct omap_mbox1_priv *)mbox->priv)->tx_fifo; + +	mbox_write_reg(msg & 0xffff, fifo->data); +	mbox_write_reg(msg >> 16, fifo->cmd); +} + +static int omap1_mbox_fifo_empty(struct omap_mbox *mbox) +{ +	return 0; +} + +static int omap1_mbox_fifo_full(struct omap_mbox *mbox) +{ +	struct omap_mbox1_fifo *fifo = +		&((struct omap_mbox1_priv *)mbox->priv)->rx_fifo; + +	return mbox_read_reg(fifo->flag); +} + +/* irq */ +static void +omap1_mbox_enable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	if (irq == IRQ_RX) +		enable_irq(mbox->irq); +} + +static void +omap1_mbox_disable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	if (irq == IRQ_RX) +		disable_irq(mbox->irq); +} + +static int +omap1_mbox_is_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	if (irq == IRQ_TX) +		return 0; +	return 1; +} + +static struct omap_mbox_ops omap1_mbox_ops = { +	.type		= OMAP_MBOX_TYPE1, +	.fifo_read	= omap1_mbox_fifo_read, +	.fifo_write	= omap1_mbox_fifo_write, +	.fifo_empty	= omap1_mbox_fifo_empty, +	.fifo_full	= omap1_mbox_fifo_full, +	.enable_irq	= omap1_mbox_enable_irq, +	.disable_irq	= omap1_mbox_disable_irq, +	.is_irq		= omap1_mbox_is_irq, +}; + +/* FIXME: the following struct should be created automatically by the user id */ + +/* DSP */ +static struct omap_mbox1_priv omap1_mbox_dsp_priv = { +	.tx_fifo = { +		.cmd	= MAILBOX_ARM2DSP1b, +		.data	= MAILBOX_ARM2DSP1, +		.flag	= MAILBOX_ARM2DSP1_Flag, +	}, +	.rx_fifo = { +		.cmd	= MAILBOX_DSP2ARM1b, +		.data	= MAILBOX_DSP2ARM1, +		.flag	= MAILBOX_DSP2ARM1_Flag, +	}, +}; + +static struct omap_mbox mbox_dsp_info = { +	.name	= "dsp", +	.ops	= &omap1_mbox_ops, +	.priv	= &omap1_mbox_dsp_priv, +}; + +static struct omap_mbox *omap1_mboxes[] = { &mbox_dsp_info, NULL }; + +static int omap1_mbox_probe(struct platform_device *pdev) +{ +	struct resource *mem; +	int ret; +	struct omap_mbox **list; + +	list = omap1_mboxes; +	list[0]->irq = platform_get_irq_byname(pdev, "dsp"); + +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!mem) +		return -ENOENT; + +	mbox_base = ioremap(mem->start, resource_size(mem)); +	if (!mbox_base) +		return -ENOMEM; + +	ret = omap_mbox_register(&pdev->dev, list); +	if (ret) { +		iounmap(mbox_base); +		return ret; +	} + +	return 0; +} + +static int omap1_mbox_remove(struct platform_device *pdev) +{ +	omap_mbox_unregister(); +	iounmap(mbox_base); +	return 0; +} + +static struct platform_driver omap1_mbox_driver = { +	.probe	= omap1_mbox_probe, +	.remove	= omap1_mbox_remove, +	.driver	= { +		.name	= "omap-mailbox", +	}, +}; + +static int __init omap1_mbox_init(void) +{ +	return platform_driver_register(&omap1_mbox_driver); +} + +static void __exit omap1_mbox_exit(void) +{ +	platform_driver_unregister(&omap1_mbox_driver); +} + +module_init(omap1_mbox_init); +module_exit(omap1_mbox_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("omap mailbox: omap1 architecture specific functions"); +MODULE_AUTHOR("Hiroshi DOYU <[email protected]>"); +MODULE_ALIAS("platform:omap1-mailbox"); diff --git a/drivers/mailbox/mailbox-omap2.c b/drivers/mailbox/mailbox-omap2.c new file mode 100644 index 000000000000..eba380d7b17f --- /dev/null +++ b/drivers/mailbox/mailbox-omap2.c @@ -0,0 +1,358 @@ +/* + * Mailbox reservation modules for OMAP2/3 + * + * Copyright (C) 2006-2009 Nokia Corporation + * Written by: Hiroshi DOYU <[email protected]> + *        and  Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License.  See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include <linux/platform_data/mailbox-omap.h> + +#include "omap-mbox.h" + +#define MAILBOX_REVISION		0x000 +#define MAILBOX_MESSAGE(m)		(0x040 + 4 * (m)) +#define MAILBOX_FIFOSTATUS(m)		(0x080 + 4 * (m)) +#define MAILBOX_MSGSTATUS(m)		(0x0c0 + 4 * (m)) +#define MAILBOX_IRQSTATUS(u)		(0x100 + 8 * (u)) +#define MAILBOX_IRQENABLE(u)		(0x104 + 8 * (u)) + +#define OMAP4_MAILBOX_IRQSTATUS(u)	(0x104 + 0x10 * (u)) +#define OMAP4_MAILBOX_IRQENABLE(u)	(0x108 + 0x10 * (u)) +#define OMAP4_MAILBOX_IRQENABLE_CLR(u)	(0x10c + 0x10 * (u)) + +#define MAILBOX_IRQ_NEWMSG(m)		(1 << (2 * (m))) +#define MAILBOX_IRQ_NOTFULL(m)		(1 << (2 * (m) + 1)) + +#define MBOX_REG_SIZE			0x120 + +#define OMAP4_MBOX_REG_SIZE		0x130 + +#define MBOX_NR_REGS			(MBOX_REG_SIZE / sizeof(u32)) +#define OMAP4_MBOX_NR_REGS		(OMAP4_MBOX_REG_SIZE / sizeof(u32)) + +static void __iomem *mbox_base; + +struct omap_mbox2_fifo { +	unsigned long msg; +	unsigned long fifo_stat; +	unsigned long msg_stat; +}; + +struct omap_mbox2_priv { +	struct omap_mbox2_fifo tx_fifo; +	struct omap_mbox2_fifo rx_fifo; +	unsigned long irqenable; +	unsigned long irqstatus; +	u32 newmsg_bit; +	u32 notfull_bit; +	u32 ctx[OMAP4_MBOX_NR_REGS]; +	unsigned long irqdisable; +	u32 intr_type; +}; + +static inline unsigned int mbox_read_reg(size_t ofs) +{ +	return __raw_readl(mbox_base + ofs); +} + +static inline void mbox_write_reg(u32 val, size_t ofs) +{ +	__raw_writel(val, mbox_base + ofs); +} + +/* Mailbox H/W preparations */ +static int omap2_mbox_startup(struct omap_mbox *mbox) +{ +	u32 l; + +	pm_runtime_enable(mbox->dev->parent); +	pm_runtime_get_sync(mbox->dev->parent); + +	l = mbox_read_reg(MAILBOX_REVISION); +	pr_debug("omap mailbox rev %d.%d\n", (l & 0xf0) >> 4, (l & 0x0f)); + +	return 0; +} + +static void omap2_mbox_shutdown(struct omap_mbox *mbox) +{ +	pm_runtime_put_sync(mbox->dev->parent); +	pm_runtime_disable(mbox->dev->parent); +} + +/* Mailbox FIFO handle functions */ +static mbox_msg_t omap2_mbox_fifo_read(struct omap_mbox *mbox) +{ +	struct omap_mbox2_fifo *fifo = +		&((struct omap_mbox2_priv *)mbox->priv)->rx_fifo; +	return (mbox_msg_t) mbox_read_reg(fifo->msg); +} + +static void omap2_mbox_fifo_write(struct omap_mbox *mbox, mbox_msg_t msg) +{ +	struct omap_mbox2_fifo *fifo = +		&((struct omap_mbox2_priv *)mbox->priv)->tx_fifo; +	mbox_write_reg(msg, fifo->msg); +} + +static int omap2_mbox_fifo_empty(struct omap_mbox *mbox) +{ +	struct omap_mbox2_fifo *fifo = +		&((struct omap_mbox2_priv *)mbox->priv)->rx_fifo; +	return (mbox_read_reg(fifo->msg_stat) == 0); +} + +static int omap2_mbox_fifo_full(struct omap_mbox *mbox) +{ +	struct omap_mbox2_fifo *fifo = +		&((struct omap_mbox2_priv *)mbox->priv)->tx_fifo; +	return mbox_read_reg(fifo->fifo_stat); +} + +/* Mailbox IRQ handle functions */ +static void omap2_mbox_enable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	struct omap_mbox2_priv *p = mbox->priv; +	u32 l, bit = (irq == IRQ_TX) ? p->notfull_bit : p->newmsg_bit; + +	l = mbox_read_reg(p->irqenable); +	l |= bit; +	mbox_write_reg(l, p->irqenable); +} + +static void omap2_mbox_disable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	struct omap_mbox2_priv *p = mbox->priv; +	u32 bit = (irq == IRQ_TX) ? p->notfull_bit : p->newmsg_bit; + +	/* +	 * Read and update the interrupt configuration register for pre-OMAP4. +	 * OMAP4 and later SoCs have a dedicated interrupt disabling register. +	 */ +	if (!p->intr_type) +		bit = mbox_read_reg(p->irqdisable) & ~bit; + +	mbox_write_reg(bit, p->irqdisable); +} + +static void omap2_mbox_ack_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	struct omap_mbox2_priv *p = mbox->priv; +	u32 bit = (irq == IRQ_TX) ? p->notfull_bit : p->newmsg_bit; + +	mbox_write_reg(bit, p->irqstatus); + +	/* Flush posted write for irq status to avoid spurious interrupts */ +	mbox_read_reg(p->irqstatus); +} + +static int omap2_mbox_is_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	struct omap_mbox2_priv *p = mbox->priv; +	u32 bit = (irq == IRQ_TX) ? p->notfull_bit : p->newmsg_bit; +	u32 enable = mbox_read_reg(p->irqenable); +	u32 status = mbox_read_reg(p->irqstatus); + +	return (int)(enable & status & bit); +} + +static void omap2_mbox_save_ctx(struct omap_mbox *mbox) +{ +	int i; +	struct omap_mbox2_priv *p = mbox->priv; +	int nr_regs; + +	if (p->intr_type) +		nr_regs = OMAP4_MBOX_NR_REGS; +	else +		nr_regs = MBOX_NR_REGS; +	for (i = 0; i < nr_regs; i++) { +		p->ctx[i] = mbox_read_reg(i * sizeof(u32)); + +		dev_dbg(mbox->dev, "%s: [%02x] %08x\n", __func__, +			i, p->ctx[i]); +	} +} + +static void omap2_mbox_restore_ctx(struct omap_mbox *mbox) +{ +	int i; +	struct omap_mbox2_priv *p = mbox->priv; +	int nr_regs; + +	if (p->intr_type) +		nr_regs = OMAP4_MBOX_NR_REGS; +	else +		nr_regs = MBOX_NR_REGS; +	for (i = 0; i < nr_regs; i++) { +		mbox_write_reg(p->ctx[i], i * sizeof(u32)); + +		dev_dbg(mbox->dev, "%s: [%02x] %08x\n", __func__, +			i, p->ctx[i]); +	} +} + +static struct omap_mbox_ops omap2_mbox_ops = { +	.type		= OMAP_MBOX_TYPE2, +	.startup	= omap2_mbox_startup, +	.shutdown	= omap2_mbox_shutdown, +	.fifo_read	= omap2_mbox_fifo_read, +	.fifo_write	= omap2_mbox_fifo_write, +	.fifo_empty	= omap2_mbox_fifo_empty, +	.fifo_full	= omap2_mbox_fifo_full, +	.enable_irq	= omap2_mbox_enable_irq, +	.disable_irq	= omap2_mbox_disable_irq, +	.ack_irq	= omap2_mbox_ack_irq, +	.is_irq		= omap2_mbox_is_irq, +	.save_ctx	= omap2_mbox_save_ctx, +	.restore_ctx	= omap2_mbox_restore_ctx, +}; + +static int omap2_mbox_probe(struct platform_device *pdev) +{ +	struct resource *mem; +	int ret; +	struct omap_mbox **list, *mbox, *mboxblk; +	struct omap_mbox2_priv *priv, *privblk; +	struct omap_mbox_pdata *pdata = pdev->dev.platform_data; +	struct omap_mbox_dev_info *info; +	int i; + +	if (!pdata || !pdata->info_cnt || !pdata->info) { +		pr_err("%s: platform not supported\n", __func__); +		return -ENODEV; +	} + +	/* allocate one extra for marking end of list */ +	list = kzalloc((pdata->info_cnt + 1) * sizeof(*list), GFP_KERNEL); +	if (!list) +		return -ENOMEM; + +	mboxblk = mbox = kzalloc(pdata->info_cnt * sizeof(*mbox), GFP_KERNEL); +	if (!mboxblk) { +		ret = -ENOMEM; +		goto free_list; +	} + +	privblk = priv = kzalloc(pdata->info_cnt * sizeof(*priv), GFP_KERNEL); +	if (!privblk) { +		ret = -ENOMEM; +		goto free_mboxblk; +	} + +	info = pdata->info; +	for (i = 0; i < pdata->info_cnt; i++, info++, priv++) { +		priv->tx_fifo.msg = MAILBOX_MESSAGE(info->tx_id); +		priv->tx_fifo.fifo_stat = MAILBOX_FIFOSTATUS(info->tx_id); +		priv->rx_fifo.msg =  MAILBOX_MESSAGE(info->rx_id); +		priv->rx_fifo.msg_stat =  MAILBOX_MSGSTATUS(info->rx_id); +		priv->notfull_bit = MAILBOX_IRQ_NOTFULL(info->tx_id); +		priv->newmsg_bit = MAILBOX_IRQ_NEWMSG(info->rx_id); +		if (pdata->intr_type) { +			priv->irqenable = OMAP4_MAILBOX_IRQENABLE(info->usr_id); +			priv->irqstatus = OMAP4_MAILBOX_IRQSTATUS(info->usr_id); +			priv->irqdisable = +				OMAP4_MAILBOX_IRQENABLE_CLR(info->usr_id); +		} else { +			priv->irqenable = MAILBOX_IRQENABLE(info->usr_id); +			priv->irqstatus = MAILBOX_IRQSTATUS(info->usr_id); +			priv->irqdisable = MAILBOX_IRQENABLE(info->usr_id); +		} +		priv->intr_type = pdata->intr_type; + +		mbox->priv = priv; +		mbox->name = info->name; +		mbox->ops = &omap2_mbox_ops; +		mbox->irq = platform_get_irq(pdev, info->irq_id); +		if (mbox->irq < 0) { +			ret = mbox->irq; +			goto free_privblk; +		} +		list[i] = mbox++; +	} + +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!mem) { +		ret = -ENOENT; +		goto free_privblk; +	} + +	mbox_base = ioremap(mem->start, resource_size(mem)); +	if (!mbox_base) { +		ret = -ENOMEM; +		goto free_privblk; +	} + +	ret = omap_mbox_register(&pdev->dev, list); +	if (ret) +		goto unmap_mbox; +	platform_set_drvdata(pdev, list); + +	return 0; + +unmap_mbox: +	iounmap(mbox_base); +free_privblk: +	kfree(privblk); +free_mboxblk: +	kfree(mboxblk); +free_list: +	kfree(list); +	return ret; +} + +static int omap2_mbox_remove(struct platform_device *pdev) +{ +	struct omap_mbox2_priv *privblk; +	struct omap_mbox **list = platform_get_drvdata(pdev); +	struct omap_mbox *mboxblk = list[0]; + +	privblk = mboxblk->priv; +	omap_mbox_unregister(); +	iounmap(mbox_base); +	kfree(privblk); +	kfree(mboxblk); +	kfree(list); +	platform_set_drvdata(pdev, NULL); + +	return 0; +} + +static struct platform_driver omap2_mbox_driver = { +	.probe	= omap2_mbox_probe, +	.remove	= omap2_mbox_remove, +	.driver	= { +		.name = "omap-mailbox", +	}, +}; + +static int __init omap2_mbox_init(void) +{ +	return platform_driver_register(&omap2_mbox_driver); +} + +static void __exit omap2_mbox_exit(void) +{ +	platform_driver_unregister(&omap2_mbox_driver); +} + +module_init(omap2_mbox_init); +module_exit(omap2_mbox_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("omap mailbox: omap2/3/4 architecture specific functions"); +MODULE_AUTHOR("Hiroshi DOYU <[email protected]>"); +MODULE_AUTHOR("Paul Mundt"); +MODULE_ALIAS("platform:omap2-mailbox"); diff --git a/drivers/mailbox/omap-mailbox.c b/drivers/mailbox/omap-mailbox.c new file mode 100644 index 000000000000..d79a646b9042 --- /dev/null +++ b/drivers/mailbox/omap-mailbox.c @@ -0,0 +1,469 @@ +/* + * OMAP mailbox driver + * + * Copyright (C) 2006-2009 Nokia Corporation. All rights reserved. + * + * Contact: Hiroshi DOYU <[email protected]> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/kfifo.h> +#include <linux/err.h> +#include <linux/notifier.h> +#include <linux/module.h> + +#include "omap-mbox.h" + +static struct omap_mbox **mboxes; + +static int mbox_configured; +static DEFINE_MUTEX(mbox_configured_lock); + +static unsigned int mbox_kfifo_size = CONFIG_OMAP_MBOX_KFIFO_SIZE; +module_param(mbox_kfifo_size, uint, S_IRUGO); +MODULE_PARM_DESC(mbox_kfifo_size, "Size of omap's mailbox kfifo (bytes)"); + +/* Mailbox FIFO handle functions */ +static inline mbox_msg_t mbox_fifo_read(struct omap_mbox *mbox) +{ +	return mbox->ops->fifo_read(mbox); +} +static inline void mbox_fifo_write(struct omap_mbox *mbox, mbox_msg_t msg) +{ +	mbox->ops->fifo_write(mbox, msg); +} +static inline int mbox_fifo_empty(struct omap_mbox *mbox) +{ +	return mbox->ops->fifo_empty(mbox); +} +static inline int mbox_fifo_full(struct omap_mbox *mbox) +{ +	return mbox->ops->fifo_full(mbox); +} + +/* Mailbox IRQ handle functions */ +static inline void ack_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	if (mbox->ops->ack_irq) +		mbox->ops->ack_irq(mbox, irq); +} +static inline int is_mbox_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	return mbox->ops->is_irq(mbox, irq); +} + +/* + * message sender + */ +static int __mbox_poll_for_space(struct omap_mbox *mbox) +{ +	int ret = 0, i = 1000; + +	while (mbox_fifo_full(mbox)) { +		if (mbox->ops->type == OMAP_MBOX_TYPE2) +			return -1; +		if (--i == 0) +			return -1; +		udelay(1); +	} +	return ret; +} + +int omap_mbox_msg_send(struct omap_mbox *mbox, mbox_msg_t msg) +{ +	struct omap_mbox_queue *mq = mbox->txq; +	int ret = 0, len; + +	spin_lock_bh(&mq->lock); + +	if (kfifo_avail(&mq->fifo) < sizeof(msg)) { +		ret = -ENOMEM; +		goto out; +	} + +	if (kfifo_is_empty(&mq->fifo) && !__mbox_poll_for_space(mbox)) { +		mbox_fifo_write(mbox, msg); +		goto out; +	} + +	len = kfifo_in(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); +	WARN_ON(len != sizeof(msg)); + +	tasklet_schedule(&mbox->txq->tasklet); + +out: +	spin_unlock_bh(&mq->lock); +	return ret; +} +EXPORT_SYMBOL(omap_mbox_msg_send); + +void omap_mbox_save_ctx(struct omap_mbox *mbox) +{ +	if (!mbox->ops->save_ctx) { +		dev_err(mbox->dev, "%s:\tno save\n", __func__); +		return; +	} + +	mbox->ops->save_ctx(mbox); +} +EXPORT_SYMBOL(omap_mbox_save_ctx); + +void omap_mbox_restore_ctx(struct omap_mbox *mbox) +{ +	if (!mbox->ops->restore_ctx) { +		dev_err(mbox->dev, "%s:\tno restore\n", __func__); +		return; +	} + +	mbox->ops->restore_ctx(mbox); +} +EXPORT_SYMBOL(omap_mbox_restore_ctx); + +void omap_mbox_enable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	mbox->ops->enable_irq(mbox, irq); +} +EXPORT_SYMBOL(omap_mbox_enable_irq); + +void omap_mbox_disable_irq(struct omap_mbox *mbox, omap_mbox_irq_t irq) +{ +	mbox->ops->disable_irq(mbox, irq); +} +EXPORT_SYMBOL(omap_mbox_disable_irq); + +static void mbox_tx_tasklet(unsigned long tx_data) +{ +	struct omap_mbox *mbox = (struct omap_mbox *)tx_data; +	struct omap_mbox_queue *mq = mbox->txq; +	mbox_msg_t msg; +	int ret; + +	while (kfifo_len(&mq->fifo)) { +		if (__mbox_poll_for_space(mbox)) { +			omap_mbox_enable_irq(mbox, IRQ_TX); +			break; +		} + +		ret = kfifo_out(&mq->fifo, (unsigned char *)&msg, +								sizeof(msg)); +		WARN_ON(ret != sizeof(msg)); + +		mbox_fifo_write(mbox, msg); +	} +} + +/* + * Message receiver(workqueue) + */ +static void mbox_rx_work(struct work_struct *work) +{ +	struct omap_mbox_queue *mq = +			container_of(work, struct omap_mbox_queue, work); +	mbox_msg_t msg; +	int len; + +	while (kfifo_len(&mq->fifo) >= sizeof(msg)) { +		len = kfifo_out(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); +		WARN_ON(len != sizeof(msg)); + +		blocking_notifier_call_chain(&mq->mbox->notifier, len, +								(void *)msg); +		spin_lock_irq(&mq->lock); +		if (mq->full) { +			mq->full = false; +			omap_mbox_enable_irq(mq->mbox, IRQ_RX); +		} +		spin_unlock_irq(&mq->lock); +	} +} + +/* + * Mailbox interrupt handler + */ +static void __mbox_tx_interrupt(struct omap_mbox *mbox) +{ +	omap_mbox_disable_irq(mbox, IRQ_TX); +	ack_mbox_irq(mbox, IRQ_TX); +	tasklet_schedule(&mbox->txq->tasklet); +} + +static void __mbox_rx_interrupt(struct omap_mbox *mbox) +{ +	struct omap_mbox_queue *mq = mbox->rxq; +	mbox_msg_t msg; +	int len; + +	while (!mbox_fifo_empty(mbox)) { +		if (unlikely(kfifo_avail(&mq->fifo) < sizeof(msg))) { +			omap_mbox_disable_irq(mbox, IRQ_RX); +			mq->full = true; +			goto nomem; +		} + +		msg = mbox_fifo_read(mbox); + +		len = kfifo_in(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); +		WARN_ON(len != sizeof(msg)); + +		if (mbox->ops->type == OMAP_MBOX_TYPE1) +			break; +	} + +	/* no more messages in the fifo. clear IRQ source. */ +	ack_mbox_irq(mbox, IRQ_RX); +nomem: +	schedule_work(&mbox->rxq->work); +} + +static irqreturn_t mbox_interrupt(int irq, void *p) +{ +	struct omap_mbox *mbox = p; + +	if (is_mbox_irq(mbox, IRQ_TX)) +		__mbox_tx_interrupt(mbox); + +	if (is_mbox_irq(mbox, IRQ_RX)) +		__mbox_rx_interrupt(mbox); + +	return IRQ_HANDLED; +} + +static struct omap_mbox_queue *mbox_queue_alloc(struct omap_mbox *mbox, +					void (*work) (struct work_struct *), +					void (*tasklet)(unsigned long)) +{ +	struct omap_mbox_queue *mq; + +	mq = kzalloc(sizeof(struct omap_mbox_queue), GFP_KERNEL); +	if (!mq) +		return NULL; + +	spin_lock_init(&mq->lock); + +	if (kfifo_alloc(&mq->fifo, mbox_kfifo_size, GFP_KERNEL)) +		goto error; + +	if (work) +		INIT_WORK(&mq->work, work); + +	if (tasklet) +		tasklet_init(&mq->tasklet, tasklet, (unsigned long)mbox); +	return mq; +error: +	kfree(mq); +	return NULL; +} + +static void mbox_queue_free(struct omap_mbox_queue *q) +{ +	kfifo_free(&q->fifo); +	kfree(q); +} + +static int omap_mbox_startup(struct omap_mbox *mbox) +{ +	int ret = 0; +	struct omap_mbox_queue *mq; + +	mutex_lock(&mbox_configured_lock); +	if (!mbox_configured++) { +		if (likely(mbox->ops->startup)) { +			ret = mbox->ops->startup(mbox); +			if (unlikely(ret)) +				goto fail_startup; +		} else +			goto fail_startup; +	} + +	if (!mbox->use_count++) { +		mq = mbox_queue_alloc(mbox, NULL, mbox_tx_tasklet); +		if (!mq) { +			ret = -ENOMEM; +			goto fail_alloc_txq; +		} +		mbox->txq = mq; + +		mq = mbox_queue_alloc(mbox, mbox_rx_work, NULL); +		if (!mq) { +			ret = -ENOMEM; +			goto fail_alloc_rxq; +		} +		mbox->rxq = mq; +		mq->mbox = mbox; +		ret = request_irq(mbox->irq, mbox_interrupt, IRQF_SHARED, +							mbox->name, mbox); +		if (unlikely(ret)) { +			pr_err("failed to register mailbox interrupt:%d\n", +									ret); +			goto fail_request_irq; +		} + +		omap_mbox_enable_irq(mbox, IRQ_RX); +	} +	mutex_unlock(&mbox_configured_lock); +	return 0; + +fail_request_irq: +	mbox_queue_free(mbox->rxq); +fail_alloc_rxq: +	mbox_queue_free(mbox->txq); +fail_alloc_txq: +	if (mbox->ops->shutdown) +		mbox->ops->shutdown(mbox); +	mbox->use_count--; +fail_startup: +	mbox_configured--; +	mutex_unlock(&mbox_configured_lock); +	return ret; +} + +static void omap_mbox_fini(struct omap_mbox *mbox) +{ +	mutex_lock(&mbox_configured_lock); + +	if (!--mbox->use_count) { +		omap_mbox_disable_irq(mbox, IRQ_RX); +		free_irq(mbox->irq, mbox); +		tasklet_kill(&mbox->txq->tasklet); +		flush_work(&mbox->rxq->work); +		mbox_queue_free(mbox->txq); +		mbox_queue_free(mbox->rxq); +	} + +	if (likely(mbox->ops->shutdown)) { +		if (!--mbox_configured) +			mbox->ops->shutdown(mbox); +	} + +	mutex_unlock(&mbox_configured_lock); +} + +struct omap_mbox *omap_mbox_get(const char *name, struct notifier_block *nb) +{ +	struct omap_mbox *_mbox, *mbox = NULL; +	int i, ret; + +	if (!mboxes) +		return ERR_PTR(-EINVAL); + +	for (i = 0; (_mbox = mboxes[i]); i++) { +		if (!strcmp(_mbox->name, name)) { +			mbox = _mbox; +			break; +		} +	} + +	if (!mbox) +		return ERR_PTR(-ENOENT); + +	if (nb) +		blocking_notifier_chain_register(&mbox->notifier, nb); + +	ret = omap_mbox_startup(mbox); +	if (ret) { +		blocking_notifier_chain_unregister(&mbox->notifier, nb); +		return ERR_PTR(-ENODEV); +	} + +	return mbox; +} +EXPORT_SYMBOL(omap_mbox_get); + +void omap_mbox_put(struct omap_mbox *mbox, struct notifier_block *nb) +{ +	blocking_notifier_chain_unregister(&mbox->notifier, nb); +	omap_mbox_fini(mbox); +} +EXPORT_SYMBOL(omap_mbox_put); + +static struct class omap_mbox_class = { .name = "mbox", }; + +int omap_mbox_register(struct device *parent, struct omap_mbox **list) +{ +	int ret; +	int i; + +	mboxes = list; +	if (!mboxes) +		return -EINVAL; + +	for (i = 0; mboxes[i]; i++) { +		struct omap_mbox *mbox = mboxes[i]; +		mbox->dev = device_create(&omap_mbox_class, +				parent, 0, mbox, "%s", mbox->name); +		if (IS_ERR(mbox->dev)) { +			ret = PTR_ERR(mbox->dev); +			goto err_out; +		} + +		BLOCKING_INIT_NOTIFIER_HEAD(&mbox->notifier); +	} +	return 0; + +err_out: +	while (i--) +		device_unregister(mboxes[i]->dev); +	return ret; +} +EXPORT_SYMBOL(omap_mbox_register); + +int omap_mbox_unregister(void) +{ +	int i; + +	if (!mboxes) +		return -EINVAL; + +	for (i = 0; mboxes[i]; i++) +		device_unregister(mboxes[i]->dev); +	mboxes = NULL; +	return 0; +} +EXPORT_SYMBOL(omap_mbox_unregister); + +static int __init omap_mbox_init(void) +{ +	int err; + +	err = class_register(&omap_mbox_class); +	if (err) +		return err; + +	/* kfifo size sanity check: alignment and minimal size */ +	mbox_kfifo_size = ALIGN(mbox_kfifo_size, sizeof(mbox_msg_t)); +	mbox_kfifo_size = max_t(unsigned int, mbox_kfifo_size, +							sizeof(mbox_msg_t)); + +	return 0; +} +subsys_initcall(omap_mbox_init); + +static void __exit omap_mbox_exit(void) +{ +	class_unregister(&omap_mbox_class); +} +module_exit(omap_mbox_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("omap mailbox: interrupt driven messaging"); +MODULE_AUTHOR("Toshihiro Kobayashi"); +MODULE_AUTHOR("Hiroshi DOYU"); diff --git a/drivers/mailbox/omap-mbox.h b/drivers/mailbox/omap-mbox.h new file mode 100644 index 000000000000..6cd38fc68599 --- /dev/null +++ b/drivers/mailbox/omap-mbox.h @@ -0,0 +1,67 @@ +/* + * omap-mbox.h: OMAP mailbox internal definitions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef OMAP_MBOX_H +#define OMAP_MBOX_H + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/omap-mailbox.h> + +typedef int __bitwise omap_mbox_type_t; +#define OMAP_MBOX_TYPE1 ((__force omap_mbox_type_t) 1) +#define OMAP_MBOX_TYPE2 ((__force omap_mbox_type_t) 2) + +struct omap_mbox_ops { +	omap_mbox_type_t	type; +	int		(*startup)(struct omap_mbox *mbox); +	void		(*shutdown)(struct omap_mbox *mbox); +	/* fifo */ +	mbox_msg_t	(*fifo_read)(struct omap_mbox *mbox); +	void		(*fifo_write)(struct omap_mbox *mbox, mbox_msg_t msg); +	int		(*fifo_empty)(struct omap_mbox *mbox); +	int		(*fifo_full)(struct omap_mbox *mbox); +	/* irq */ +	void		(*enable_irq)(struct omap_mbox *mbox, +						omap_mbox_irq_t irq); +	void		(*disable_irq)(struct omap_mbox *mbox, +						omap_mbox_irq_t irq); +	void		(*ack_irq)(struct omap_mbox *mbox, omap_mbox_irq_t irq); +	int		(*is_irq)(struct omap_mbox *mbox, omap_mbox_irq_t irq); +	/* ctx */ +	void		(*save_ctx)(struct omap_mbox *mbox); +	void		(*restore_ctx)(struct omap_mbox *mbox); +}; + +struct omap_mbox_queue { +	spinlock_t		lock; +	struct kfifo		fifo; +	struct work_struct	work; +	struct tasklet_struct	tasklet; +	struct omap_mbox	*mbox; +	bool full; +}; + +struct omap_mbox { +	const char		*name; +	unsigned int		irq; +	struct omap_mbox_queue	*txq, *rxq; +	struct omap_mbox_ops	*ops; +	struct device		*dev; +	void			*priv; +	int			use_count; +	struct blocking_notifier_head	notifier; +}; + +int omap_mbox_register(struct device *parent, struct omap_mbox **); +int omap_mbox_unregister(void); + +#endif /* OMAP_MBOX_H */ |