* [PATCH] spi: Add SuperH HSPI prototype driver @ 2011-12-27 8:35 Kuninori Morimoto [not found] ` <87vcp2tnst.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 0 siblings, 1 reply; 8+ messages in thread From: Kuninori Morimoto @ 2011-12-27 8:35 UTC (permalink / raw) To: Grant Likely Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Magnus, Kuninori Morimoto This patch adds SuperH HSPI driver. It is still prototype driver, but has enough function at this point. Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> --- drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi-sh-hspi.c | 375 +++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/sh_hspi.h | 34 ++++ 4 files changed, 416 insertions(+), 0 deletions(-) create mode 100644 drivers/spi/spi-sh-hspi.c create mode 100644 include/linux/spi/sh_hspi.h diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 52e2900..c5e5fe6 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -325,6 +325,12 @@ config SPI_SH_SCI help SPI driver for SuperH SCI blocks. +config SPI_SH_HSPI + tristate "SuperH HSPI controller" + depends on ARCH_SHMOBILE + help + SPI driver for SuperH HSPI blocks. + config SPI_STMP3XXX tristate "Freescale STMP37xx/378x SPI/SSP controller" depends on ARCH_STMP3XXX && SPI_MASTER diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 61c3261..d65e059 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o obj-$(CONFIG_SPI_SH) += spi-sh.o obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o +obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o obj-$(CONFIG_SPI_STMP3XXX) += spi-stmp.o obj-$(CONFIG_SPI_TEGRA) += spi-tegra.o obj-$(CONFIG_SPI_TI_SSP) += spi-ti-ssp.o diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c new file mode 100644 index 0000000..ee4c8e6 --- /dev/null +++ b/drivers/spi/spi-sh-hspi.c @@ -0,0 +1,375 @@ +/* + * SuperH HSPI bus driver + * + * Copyright (C) 2011 Kuninori Morimoto + * + * Based on spi-sh.c: + * Based on pxa2xx_spi.c: + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/io.h> +#include <linux/spi/spi.h> +#include <linux/spi/sh_hspi.h> + +#define SPCR 0x00 +#define SPSR 0x04 +#define SPSCR 0x08 +#define SPTBR 0x0C +#define SPRBR 0x10 +#define SPCR2 0x14 + +/* SPSR */ +#define RXFL (1 << 2) + +#define hspi2info(h) (h->dev->platform_data) + +struct hspi_priv { + void __iomem *addr; + struct spi_master *master; + struct list_head queue; + struct workqueue_struct *workqueue; + struct work_struct ws; + struct device *dev; + spinlock_t lock; +}; + +/* + * basic function + */ +static void hspi_write(struct hspi_priv *hspi, int reg, u32 val) +{ + iowrite32(val, hspi->addr + reg); +} + +static u8 hspi_read(struct hspi_priv *hspi, int reg) +{ + return (u8)ioread32(hspi->addr + reg); +} + +/* +static void hspi_bset(struct hspi_priv *hspi, int reg, u32 mask, u32 val) +{ + u32 tmp; + + tmp = (u32)hspi_read(hspi, reg); + tmp &= ~mask; + tmp |= (val & mask); + hspi_write(hspi, tmp, reg); +} +*/ +/* + * transfer function + */ +static int hspi_status_check_timeout(struct hspi_priv *hspi, u32 mask, u32 val) +{ + int t = 256; + + while (t--) { + if ((mask & hspi_read(hspi, SPSR)) == val) + return 0; + + mdelay(10); + } + + dev_err(hspi->dev, "timeout\n"); + return -ETIMEDOUT; +} + +static int hspi_push(struct hspi_priv *hspi, struct spi_message *msg, + struct spi_transfer *t) +{ + int i, ret; + u8 *data = (u8 *)t->tx_buf; + + /* + * FIXME + * very simple, but polling transfer + */ + for (i = 0; i < t->len; i++) { + /* wait remains */ + ret = hspi_status_check_timeout(hspi, 0x1, 0x0); + if (ret < 0) + return ret; + + hspi_write(hspi, SPTBR, (u32)data[i]); + + /* wait recive */ + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); + if (ret < 0) + return ret; + + /* dummy read */ + hspi_read(hspi, SPRBR); + } + + return 0; +} + +static int hspi_pop(struct hspi_priv *hspi, struct spi_message *msg, + struct spi_transfer *t) +{ + int i; + u8 *data = (u8 *)t->rx_buf; + + /* + * FIXME + * very simple, but polling receive + */ + for (i = 0; i < t->len; i++) { + /* wait remains */ + while ((0x1 & hspi_read(hspi, SPSR))) + mdelay(10); + + /* dummy write */ + hspi_write(hspi, SPTBR, 0x0); + + /* wait recive */ + while (!(0x4 & hspi_read(hspi, SPSR))) + mdelay(10); + + data[i] = (u8)hspi_read(hspi, SPRBR); + } + + return 0; +} + +static void hspi_work(struct work_struct *work) +{ + struct hspi_priv *hspi = container_of(work, struct hspi_priv, ws); + struct sh_hspi_info *info = hspi2info(hspi); + struct spi_message *msg; + struct spi_transfer *t; + unsigned long flags; + u32 data; + int ret; + + dev_dbg(hspi->dev, "%s\n", __func__); + + /************************ pm enable ************************/ + pm_runtime_get_sync(hspi->dev); + + /* setup first of all in under pm_runtime */ + data = SH_HSPI_CLK_DIVC(info->flags); + + if (info->flags & SH_HSPI_FBS) + data |= 1 << 7; + if (info->flags & SH_HSPI_CLKP_HIGH) + data |= 1 << 6; + if (info->flags & SH_HSPI_IDIV_DIV128) + data |= 1 << 5; + + hspi_write(hspi, SPCR, data); + hspi_write(hspi, SPSR, 0x0); + hspi_write(hspi, SPSCR, 0x1); /* master mode */ + + while (1) { + msg = NULL; + + /************************ spin lock ************************/ + spin_lock_irqsave(&hspi->lock, flags); + if (!list_empty(&hspi->queue)) { + msg = list_entry(hspi->queue.next, + struct spi_message, queue); + list_del_init(&msg->queue); + } + spin_unlock_irqrestore(&hspi->lock, flags); + /************************ spin unlock ************************/ + if (!msg) + break; + + ret = 0; + list_for_each_entry(t, &msg->transfers, transfer_list) { + if (t->tx_buf) { + ret = hspi_push(hspi, msg, t); + if (ret < 0) + goto error; + } + if (t->rx_buf) { + ret = hspi_pop(hspi, msg, t); + if (ret < 0) + goto error; + } + msg->actual_length += t->len; + } +error: + msg->status = ret; + msg->complete(msg->context); + } + + pm_runtime_put_sync(hspi->dev); + /************************ pm disable ************************/ + + return; +} + +/* + * spi master function + */ +static int hspi_setup(struct spi_device *spi) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + struct device *dev = hspi->dev; + + if (8 != spi->bits_per_word) { + dev_err(dev, "bits_per_word shluld be 8\n"); + return -EIO; + } + + dev_dbg(dev, "%s setup\n", spi->modalias); + + return 0; +} + +static void hspi_cleanup(struct spi_device *spi) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + struct device *dev = hspi->dev; + + dev_dbg(dev, "%s cleanup\n", spi->modalias); +} + +static int hspi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + unsigned long flags; + + /************************ spin lock ************************/ + spin_lock_irqsave(&hspi->lock, flags); + + msg->actual_length = 0; + msg->status = -EINPROGRESS; + list_add_tail(&msg->queue, &hspi->queue); + + spin_unlock_irqrestore(&hspi->lock, flags); + /************************ spin unlock ************************/ + + queue_work(hspi->workqueue, &hspi->ws); + + return 0; +} + +static int __devinit hspi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct spi_master *master; + struct hspi_priv *hspi; + const char *devname; + int ret; + + /* get base addr */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "invalid resource\n"); + return -EINVAL; + } + + master = spi_alloc_master(&pdev->dev, sizeof(struct hspi_priv)); + if (!master) { + dev_err(&pdev->dev, "spi_alloc_master error.\n"); + return -ENOMEM; + } + + hspi = spi_master_get_devdata(master); + dev_set_drvdata(&pdev->dev, hspi); + + devname = dev_name(&pdev->dev); + + /* init hspi */ + hspi->master = master; + hspi->dev = &pdev->dev; + hspi->addr = ioremap(res->start, resource_size(res)); + if (!hspi->addr) { + dev_err(&pdev->dev, "ioremap error.\n"); + ret = -ENOMEM; + goto error1; + } + hspi->workqueue = create_singlethread_workqueue(devname); + if (!hspi->workqueue) { + dev_err(&pdev->dev, "create workqueue error\n"); + ret = -EBUSY; + goto error2; + } + + spin_lock_init(&hspi->lock); + INIT_LIST_HEAD(&hspi->queue); + INIT_WORK(&hspi->ws, hspi_work); + + master->num_chipselect = 1; + master->bus_num = pdev->id; + master->setup = hspi_setup; + master->transfer = hspi_transfer; + master->cleanup = hspi_cleanup; + master->mode_bits = SPI_CPOL | SPI_CPHA; + ret = spi_register_master(master); + if (ret < 0) { + printk(KERN_ERR "spi_register_master error.\n"); + goto error3; + } + + pm_runtime_enable(&pdev->dev); + + dev_info(&pdev->dev, "%s probed\n", devname); + + return 0; + + error3: + destroy_workqueue(hspi->workqueue); + error2: + iounmap(hspi->addr); + error1: + spi_master_put(master); + + return ret; +} + +static int __devexit hspi_remove(struct platform_device *pdev) +{ + struct hspi_priv *hspi = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + destroy_workqueue(hspi->workqueue); + iounmap(hspi->addr); + spi_unregister_master(hspi->master); + + return 0; +} + +static struct platform_driver hspi_driver = { + .probe = hspi_probe, + .remove = __devexit_p(hspi_remove), + .driver = { + .name = "sh-hspi", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(hspi_driver); + +MODULE_DESCRIPTION("SuperH HSPI bus driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>"); +MODULE_ALIAS("platform:sh_spi"); diff --git a/include/linux/spi/sh_hspi.h b/include/linux/spi/sh_hspi.h new file mode 100644 index 0000000..956d112 --- /dev/null +++ b/include/linux/spi/sh_hspi.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011 Kuninori Morimoto + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef SH_HSPI_H +#define SH_HSPI_H + +/* + * flags + * + * + */ +#define SH_HSPI_CLK_DIVC(d) (d & 0xFF) + +#define SH_HSPI_FBS (1 << 8) +#define SH_HSPI_CLKP_HIGH (1 << 9) /* default LOW */ +#define SH_HSPI_IDIV_DIV128 (1 << 10) /* default div16 */ +struct sh_hspi_info { + u32 flags; +}; + +#endif -- 1.7.5.4 ------------------------------------------------------------------------------ Write once. Port to many. Get the SDK and tools to simplify cross-platform app development. Create new or port existing apps to sell to consumers worldwide. Explore the Intel AppUpSM program developer opportunity. appdeveloper.intel.com/join http://p.sf.net/sfu/intel-appdev ^ permalink raw reply related [flat|nested] 8+ messages in thread
[parent not found: <87vcp2tnst.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH] spi: Add SuperH HSPI prototype driver [not found] ` <87vcp2tnst.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> @ 2012-01-04 20:05 ` Grant Likely [not found] ` <20120104200548.GH15503-e0URQFbLeQY2iJbIjFUEsiwD8/FfD2ys@public.gmane.org> 0 siblings, 1 reply; 8+ messages in thread From: Grant Likely @ 2012-01-04 20:05 UTC (permalink / raw) To: Kuninori Morimoto Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Magnus, Kuninori Morimoto On Tue, Dec 27, 2011 at 12:35:18AM -0800, Kuninori Morimoto wrote: > This patch adds SuperH HSPI driver. > It is still prototype driver, but has enough function at this point. > > Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> Some brief comments below... > --- > +static int hspi_pop(struct hspi_priv *hspi, struct spi_message *msg, > + struct spi_transfer *t) > +{ > + int i; > + u8 *data = (u8 *)t->rx_buf; > + > + /* > + * FIXME > + * very simple, but polling receive > + */ > + for (i = 0; i < t->len; i++) { > + /* wait remains */ > + while ((0x1 & hspi_read(hspi, SPSR))) > + mdelay(10); > + > + /* dummy write */ > + hspi_write(hspi, SPTBR, 0x0); > + > + /* wait recive */ > + while (!(0x4 & hspi_read(hspi, SPSR))) > + mdelay(10); Those mdelays are really expensive. Can the driver sleep instead? > + > + data[i] = (u8)hspi_read(hspi, SPRBR); > + } > + > + return 0; > +} > + > +static void hspi_work(struct work_struct *work) > +{ > + struct hspi_priv *hspi = container_of(work, struct hspi_priv, ws); > + struct sh_hspi_info *info = hspi2info(hspi); > + struct spi_message *msg; > + struct spi_transfer *t; > + unsigned long flags; > + u32 data; > + int ret; > + > + dev_dbg(hspi->dev, "%s\n", __func__); > + > + /************************ pm enable ************************/ > + pm_runtime_get_sync(hspi->dev); > + > + /* setup first of all in under pm_runtime */ > + data = SH_HSPI_CLK_DIVC(info->flags); > + > + if (info->flags & SH_HSPI_FBS) > + data |= 1 << 7; > + if (info->flags & SH_HSPI_CLKP_HIGH) > + data |= 1 << 6; > + if (info->flags & SH_HSPI_IDIV_DIV128) > + data |= 1 << 5; > + > + hspi_write(hspi, SPCR, data); > + hspi_write(hspi, SPSR, 0x0); > + hspi_write(hspi, SPSCR, 0x1); /* master mode */ > + > + while (1) { > + msg = NULL; > + > + /************************ spin lock ************************/ > + spin_lock_irqsave(&hspi->lock, flags); > + if (!list_empty(&hspi->queue)) { > + msg = list_entry(hspi->queue.next, > + struct spi_message, queue); > + list_del_init(&msg->queue); > + } > + spin_unlock_irqrestore(&hspi->lock, flags); > + /************************ spin unlock ************************/ > + if (!msg) > + break; > + > + ret = 0; > + list_for_each_entry(t, &msg->transfers, transfer_list) { > + if (t->tx_buf) { > + ret = hspi_push(hspi, msg, t); > + if (ret < 0) > + goto error; > + } > + if (t->rx_buf) { > + ret = hspi_pop(hspi, msg, t); > + if (ret < 0) > + goto error; > + } > + msg->actual_length += t->len; > + } > +error: > + msg->status = ret; > + msg->complete(msg->context); > + } > + > + pm_runtime_put_sync(hspi->dev); > + /************************ pm disable ************************/ > + > + return; > +} > + > +/* > + * spi master function > + */ > +static int hspi_setup(struct spi_device *spi) > +{ > + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); > + struct device *dev = hspi->dev; > + > + if (8 != spi->bits_per_word) { > + dev_err(dev, "bits_per_word shluld be 8\n"); typo > + return -EIO; > + } > + > + dev_dbg(dev, "%s setup\n", spi->modalias); > + > + return 0; > +} > + > +static void hspi_cleanup(struct spi_device *spi) > +{ > + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); > + struct device *dev = hspi->dev; > + > + dev_dbg(dev, "%s cleanup\n", spi->modalias); > +} > + > +static int hspi_transfer(struct spi_device *spi, struct spi_message *msg) > +{ > + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); > + unsigned long flags; > + > + /************************ spin lock ************************/ > + spin_lock_irqsave(&hspi->lock, flags); > + > + msg->actual_length = 0; > + msg->status = -EINPROGRESS; > + list_add_tail(&msg->queue, &hspi->queue); > + > + spin_unlock_irqrestore(&hspi->lock, flags); > + /************************ spin unlock ************************/ > + > + queue_work(hspi->workqueue, &hspi->ws); > + > + return 0; > +} > + > +static int __devinit hspi_probe(struct platform_device *pdev) > +{ > + struct resource *res; > + struct spi_master *master; > + struct hspi_priv *hspi; > + const char *devname; > + int ret; > + > + /* get base addr */ > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(&pdev->dev, "invalid resource\n"); > + return -EINVAL; > + } > + > + master = spi_alloc_master(&pdev->dev, sizeof(struct hspi_priv)); sizeof(*hspi) > + if (!master) { > + dev_err(&pdev->dev, "spi_alloc_master error.\n"); > + return -ENOMEM; > + } > + > + hspi = spi_master_get_devdata(master); > + dev_set_drvdata(&pdev->dev, hspi); > + > + devname = dev_name(&pdev->dev); > + > + /* init hspi */ > + hspi->master = master; > + hspi->dev = &pdev->dev; > + hspi->addr = ioremap(res->start, resource_size(res)); > + if (!hspi->addr) { > + dev_err(&pdev->dev, "ioremap error.\n"); > + ret = -ENOMEM; > + goto error1; > + } > + hspi->workqueue = create_singlethread_workqueue(devname); nit: After addressing my comment below, devname is referenced exactly once after being set. create_singlethread_workqueue(dev_name(&pdev->dev)) is sufficient here. > + if (!hspi->workqueue) { > + dev_err(&pdev->dev, "create workqueue error\n"); > + ret = -EBUSY; > + goto error2; > + } > + > + spin_lock_init(&hspi->lock); > + INIT_LIST_HEAD(&hspi->queue); > + INIT_WORK(&hspi->ws, hspi_work); > + > + master->num_chipselect = 1; > + master->bus_num = pdev->id; > + master->setup = hspi_setup; > + master->transfer = hspi_transfer; > + master->cleanup = hspi_cleanup; > + master->mode_bits = SPI_CPOL | SPI_CPHA; > + ret = spi_register_master(master); > + if (ret < 0) { > + printk(KERN_ERR "spi_register_master error.\n"); dev_err() > + goto error3; > + } > + > + pm_runtime_enable(&pdev->dev); > + > + dev_info(&pdev->dev, "%s probed\n", devname); dev_info() prints the devname already. This line prints it twice. > + > + return 0; > + > + error3: > + destroy_workqueue(hspi->workqueue); > + error2: > + iounmap(hspi->addr); > + error1: > + spi_master_put(master); > + > + return ret; > +} > + > +static int __devexit hspi_remove(struct platform_device *pdev) > +{ > + struct hspi_priv *hspi = dev_get_drvdata(&pdev->dev); > + > + pm_runtime_disable(&pdev->dev); > + > + destroy_workqueue(hspi->workqueue); > + iounmap(hspi->addr); > + spi_unregister_master(hspi->master); Must unregister the master *before* unmapping it and destroying the workqueue. > + > + return 0; > +} ------------------------------------------------------------------------------ Ridiculously easy VDI. With Citrix VDI-in-a-Box, you don't need a complex infrastructure or vast IT resources to deliver seamless, secure access to virtual desktops. With this all-in-one solution, easily deploy virtual desktops for less than the cost of PCs and save 60% on VDI infrastructure costs. Try it free! http://p.sf.net/sfu/Citrix-VDIinabox ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <20120104200548.GH15503-e0URQFbLeQY2iJbIjFUEsiwD8/FfD2ys@public.gmane.org>]
* [PATCH v2] spi: Add SuperH HSPI prototype driver [not found] ` <20120104200548.GH15503-e0URQFbLeQY2iJbIjFUEsiwD8/FfD2ys@public.gmane.org> @ 2012-01-06 6:00 ` Kuninori Morimoto [not found] ` <871urd9xq0.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 0 siblings, 1 reply; 8+ messages in thread From: Kuninori Morimoto @ 2012-01-06 6:00 UTC (permalink / raw) To: Grant Likely Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Magnus, Kuninori Morimoto This patch adds SuperH HSPI driver. It is still prototype driver, but has enough function at this point. Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> --- v1 -> v2 - mdelay() -> msleep() - typo fix - sizeof(struct hspi_priv) -> sizeof(*hspi) - remove "devname" - use dev_err() instead of printk(KERN_ERR, ) - modiry spi_unregister_master() timing drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi-sh-hspi.c | 363 +++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/sh_hspi.h | 34 ++++ 4 files changed, 404 insertions(+), 0 deletions(-) create mode 100644 drivers/spi/spi-sh-hspi.c create mode 100644 include/linux/spi/sh_hspi.h diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index a1fd73d..33bb33e 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -325,6 +325,12 @@ config SPI_SH_SCI help SPI driver for SuperH SCI blocks. +config SPI_SH_HSPI + tristate "SuperH HSPI controller" + depends on ARCH_SHMOBILE + help + SPI driver for SuperH HSPI blocks. + config SPI_STMP3XXX tristate "Freescale STMP37xx/378x SPI/SSP controller" depends on ARCH_STMP3XXX && SPI_MASTER diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 61c3261..d65e059 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o obj-$(CONFIG_SPI_SH) += spi-sh.o obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o +obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o obj-$(CONFIG_SPI_STMP3XXX) += spi-stmp.o obj-$(CONFIG_SPI_TEGRA) += spi-tegra.o obj-$(CONFIG_SPI_TI_SSP) += spi-ti-ssp.o diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c new file mode 100644 index 0000000..f527d24 --- /dev/null +++ b/drivers/spi/spi-sh-hspi.c @@ -0,0 +1,363 @@ +/* + * SuperH HSPI bus driver + * + * Copyright (C) 2011 Kuninori Morimoto + * + * Based on spi-sh.c: + * Based on pxa2xx_spi.c: + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/io.h> +#include <linux/spi/spi.h> +#include <linux/spi/sh_hspi.h> + +#define SPCR 0x00 +#define SPSR 0x04 +#define SPSCR 0x08 +#define SPTBR 0x0C +#define SPRBR 0x10 +#define SPCR2 0x14 + +/* SPSR */ +#define RXFL (1 << 2) + +#define hspi2info(h) (h->dev->platform_data) + +struct hspi_priv { + void __iomem *addr; + struct spi_master *master; + struct list_head queue; + struct workqueue_struct *workqueue; + struct work_struct ws; + struct device *dev; + spinlock_t lock; +}; + +/* + * basic function + */ +static void hspi_write(struct hspi_priv *hspi, int reg, u32 val) +{ + iowrite32(val, hspi->addr + reg); +} + +static u8 hspi_read(struct hspi_priv *hspi, int reg) +{ + return (u8)ioread32(hspi->addr + reg); +} + +/* + * transfer function + */ +static int hspi_status_check_timeout(struct hspi_priv *hspi, u32 mask, u32 val) +{ + int t = 256; + + while (t--) { + if ((mask & hspi_read(hspi, SPSR)) == val) + return 0; + + msleep(10); + } + + dev_err(hspi->dev, "timeout\n"); + return -ETIMEDOUT; +} + +static int hspi_push(struct hspi_priv *hspi, struct spi_message *msg, + struct spi_transfer *t) +{ + int i, ret; + u8 *data = (u8 *)t->tx_buf; + + /* + * FIXME + * very simple, but polling transfer + */ + for (i = 0; i < t->len; i++) { + /* wait remains */ + ret = hspi_status_check_timeout(hspi, 0x1, 0x0); + if (ret < 0) + return ret; + + hspi_write(hspi, SPTBR, (u32)data[i]); + + /* wait recive */ + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); + if (ret < 0) + return ret; + + /* dummy read */ + hspi_read(hspi, SPRBR); + } + + return 0; +} + +static int hspi_pop(struct hspi_priv *hspi, struct spi_message *msg, + struct spi_transfer *t) +{ + int i, ret; + u8 *data = (u8 *)t->rx_buf; + + /* + * FIXME + * very simple, but polling receive + */ + for (i = 0; i < t->len; i++) { + /* wait remains */ + ret = hspi_status_check_timeout(hspi, 0x1, 0); + if (ret < 0) + return ret; + + /* dummy write */ + hspi_write(hspi, SPTBR, 0x0); + + /* wait recive */ + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); + if (ret < 0) + return ret; + + data[i] = (u8)hspi_read(hspi, SPRBR); + } + + return 0; +} + +static void hspi_work(struct work_struct *work) +{ + struct hspi_priv *hspi = container_of(work, struct hspi_priv, ws); + struct sh_hspi_info *info = hspi2info(hspi); + struct spi_message *msg; + struct spi_transfer *t; + unsigned long flags; + u32 data; + int ret; + + dev_dbg(hspi->dev, "%s\n", __func__); + + /************************ pm enable ************************/ + pm_runtime_get_sync(hspi->dev); + + /* setup first of all in under pm_runtime */ + data = SH_HSPI_CLK_DIVC(info->flags); + + if (info->flags & SH_HSPI_FBS) + data |= 1 << 7; + if (info->flags & SH_HSPI_CLKP_HIGH) + data |= 1 << 6; + if (info->flags & SH_HSPI_IDIV_DIV128) + data |= 1 << 5; + + hspi_write(hspi, SPCR, data); + hspi_write(hspi, SPSR, 0x0); + hspi_write(hspi, SPSCR, 0x1); /* master mode */ + + while (1) { + msg = NULL; + + /************************ spin lock ************************/ + spin_lock_irqsave(&hspi->lock, flags); + if (!list_empty(&hspi->queue)) { + msg = list_entry(hspi->queue.next, + struct spi_message, queue); + list_del_init(&msg->queue); + } + spin_unlock_irqrestore(&hspi->lock, flags); + /************************ spin unlock ************************/ + if (!msg) + break; + + ret = 0; + list_for_each_entry(t, &msg->transfers, transfer_list) { + if (t->tx_buf) { + ret = hspi_push(hspi, msg, t); + if (ret < 0) + goto error; + } + if (t->rx_buf) { + ret = hspi_pop(hspi, msg, t); + if (ret < 0) + goto error; + } + msg->actual_length += t->len; + } +error: + msg->status = ret; + msg->complete(msg->context); + } + + pm_runtime_put_sync(hspi->dev); + /************************ pm disable ************************/ + + return; +} + +/* + * spi master function + */ +static int hspi_setup(struct spi_device *spi) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + struct device *dev = hspi->dev; + + if (8 != spi->bits_per_word) { + dev_err(dev, "bits_per_word should be 8\n"); + return -EIO; + } + + dev_dbg(dev, "%s setup\n", spi->modalias); + + return 0; +} + +static void hspi_cleanup(struct spi_device *spi) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + struct device *dev = hspi->dev; + + dev_dbg(dev, "%s cleanup\n", spi->modalias); +} + +static int hspi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + unsigned long flags; + + /************************ spin lock ************************/ + spin_lock_irqsave(&hspi->lock, flags); + + msg->actual_length = 0; + msg->status = -EINPROGRESS; + list_add_tail(&msg->queue, &hspi->queue); + + spin_unlock_irqrestore(&hspi->lock, flags); + /************************ spin unlock ************************/ + + queue_work(hspi->workqueue, &hspi->ws); + + return 0; +} + +static int __devinit hspi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct spi_master *master; + struct hspi_priv *hspi; + int ret; + + /* get base addr */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "invalid resource\n"); + return -EINVAL; + } + + master = spi_alloc_master(&pdev->dev, sizeof(*hspi)); + if (!master) { + dev_err(&pdev->dev, "spi_alloc_master error.\n"); + return -ENOMEM; + } + + hspi = spi_master_get_devdata(master); + dev_set_drvdata(&pdev->dev, hspi); + + /* init hspi */ + hspi->master = master; + hspi->dev = &pdev->dev; + hspi->addr = ioremap(res->start, resource_size(res)); + if (!hspi->addr) { + dev_err(&pdev->dev, "ioremap error.\n"); + ret = -ENOMEM; + goto error1; + } + hspi->workqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!hspi->workqueue) { + dev_err(&pdev->dev, "create workqueue error\n"); + ret = -EBUSY; + goto error2; + } + + spin_lock_init(&hspi->lock); + INIT_LIST_HEAD(&hspi->queue); + INIT_WORK(&hspi->ws, hspi_work); + + master->num_chipselect = 1; + master->bus_num = pdev->id; + master->setup = hspi_setup; + master->transfer = hspi_transfer; + master->cleanup = hspi_cleanup; + master->mode_bits = SPI_CPOL | SPI_CPHA; + ret = spi_register_master(master); + if (ret < 0) { + dev_err(&pdev->dev, "spi_register_master error.\n"); + goto error3; + } + + pm_runtime_enable(&pdev->dev); + + dev_info(&pdev->dev, "probed\n"); + + return 0; + + error3: + destroy_workqueue(hspi->workqueue); + error2: + iounmap(hspi->addr); + error1: + spi_master_put(master); + + return ret; +} + +static int __devexit hspi_remove(struct platform_device *pdev) +{ + struct hspi_priv *hspi = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + spi_unregister_master(hspi->master); + destroy_workqueue(hspi->workqueue); + iounmap(hspi->addr); + + return 0; +} + +static struct platform_driver hspi_driver = { + .probe = hspi_probe, + .remove = __devexit_p(hspi_remove), + .driver = { + .name = "sh-hspi", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(hspi_driver); + +MODULE_DESCRIPTION("SuperH HSPI bus driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>"); +MODULE_ALIAS("platform:sh_spi"); diff --git a/include/linux/spi/sh_hspi.h b/include/linux/spi/sh_hspi.h new file mode 100644 index 0000000..956d112 --- /dev/null +++ b/include/linux/spi/sh_hspi.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011 Kuninori Morimoto + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef SH_HSPI_H +#define SH_HSPI_H + +/* + * flags + * + * + */ +#define SH_HSPI_CLK_DIVC(d) (d & 0xFF) + +#define SH_HSPI_FBS (1 << 8) +#define SH_HSPI_CLKP_HIGH (1 << 9) /* default LOW */ +#define SH_HSPI_IDIV_DIV128 (1 << 10) /* default div16 */ +struct sh_hspi_info { + u32 flags; +}; + +#endif -- 1.7.5.4 ------------------------------------------------------------------------------ Ridiculously easy VDI. With Citrix VDI-in-a-Box, you don't need a complex infrastructure or vast IT resources to deliver seamless, secure access to virtual desktops. With this all-in-one solution, easily deploy virtual desktops for less than the cost of PCs and save 60% on VDI infrastructure costs. Try it free! http://p.sf.net/sfu/Citrix-VDIinabox ^ permalink raw reply related [flat|nested] 8+ messages in thread
[parent not found: <871urd9xq0.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH v2] spi: Add SuperH HSPI prototype driver [not found] ` <871urd9xq0.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> @ 2012-02-29 8:03 ` Kuninori Morimoto 2012-03-01 9:26 ` Shubhrajyoti Datta 1 sibling, 0 replies; 8+ messages in thread From: Kuninori Morimoto @ 2012-02-29 8:03 UTC (permalink / raw) To: Grant Likely Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Magnus, Kuninori Morimoto Hi Grant > This patch adds SuperH HSPI driver. > It is still prototype driver, but has enough function at this point. > > Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> > --- > v1 -> v2 > > - mdelay() -> msleep() > - typo fix > - sizeof(struct hspi_priv) -> sizeof(*hspi) > - remove "devname" > - use dev_err() instead of printk(KERN_ERR, ) > - modiry spi_unregister_master() timing Which branch can I find this patch on your git-tree ? or was it rejected ? Best regards --- Kuninori Morimoto ------------------------------------------------------------------------------ Virtualization & Cloud Management Using Capacity Planning Cloud computing makes use of virtualization - but cloud computing also focuses on allowing computing to be delivered as a service. http://www.accelacomm.com/jaw/sfnl/114/51521223/ ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] spi: Add SuperH HSPI prototype driver [not found] ` <871urd9xq0.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 2012-02-29 8:03 ` Kuninori Morimoto @ 2012-03-01 9:26 ` Shubhrajyoti Datta [not found] ` <CAM=Q2cv3QBLEWp+rQ9f5onBS1j=wZOes5m-r9fs6Uvfu6bnTkg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> 1 sibling, 1 reply; 8+ messages in thread From: Shubhrajyoti Datta @ 2012-03-01 9:26 UTC (permalink / raw) To: Kuninori Morimoto Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Magnus, Kuninori Morimoto Hi Kuninori, On Fri, Jan 6, 2012 at 11:30 AM, Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> wrote: > This patch adds SuperH HSPI driver. > It is still prototype driver, but has enough function at this point. > > Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> > --- > v1 -> v2 > > - mdelay() -> msleep() > - typo fix > - sizeof(struct hspi_priv) -> sizeof(*hspi) > - remove "devname" > - use dev_err() instead of printk(KERN_ERR, ) > - modiry spi_unregister_master() timing > > drivers/spi/Kconfig | 6 + > drivers/spi/Makefile | 1 + > drivers/spi/spi-sh-hspi.c | 363 +++++++++++++++++++++++++++++++++++++++++++ > include/linux/spi/sh_hspi.h | 34 ++++ > 4 files changed, 404 insertions(+), 0 deletions(-) > create mode 100644 drivers/spi/spi-sh-hspi.c > create mode 100644 include/linux/spi/sh_hspi.h > > diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig > index a1fd73d..33bb33e 100644 > --- a/drivers/spi/Kconfig > +++ b/drivers/spi/Kconfig > @@ -325,6 +325,12 @@ config SPI_SH_SCI > help > SPI driver for SuperH SCI blocks. > > +config SPI_SH_HSPI > + tristate "SuperH HSPI controller" > + depends on ARCH_SHMOBILE > + help > + SPI driver for SuperH HSPI blocks. > + > config SPI_STMP3XXX > tristate "Freescale STMP37xx/378x SPI/SSP controller" > depends on ARCH_STMP3XXX && SPI_MASTER > diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile > index 61c3261..d65e059 100644 > --- a/drivers/spi/Makefile > +++ b/drivers/spi/Makefile > @@ -51,6 +51,7 @@ obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o > obj-$(CONFIG_SPI_SH) += spi-sh.o > obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o > obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o > +obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o > obj-$(CONFIG_SPI_STMP3XXX) += spi-stmp.o > obj-$(CONFIG_SPI_TEGRA) += spi-tegra.o > obj-$(CONFIG_SPI_TI_SSP) += spi-ti-ssp.o > diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c > new file mode 100644 > index 0000000..f527d24 > --- /dev/null > +++ b/drivers/spi/spi-sh-hspi.c > @@ -0,0 +1,363 @@ > +/* > + * SuperH HSPI bus driver > + * > + * Copyright (C) 2011 Kuninori Morimoto > + * > + * Based on spi-sh.c: > + * Based on pxa2xx_spi.c: > + * Copyright (C) 2011 Renesas Solutions Corp. > + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs > + * > + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA > + * > + */ > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/timer.h> > +#include <linux/delay.h> > +#include <linux/list.h> > +#include <linux/workqueue.h> > +#include <linux/interrupt.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/io.h> > +#include <linux/spi/spi.h> > +#include <linux/spi/sh_hspi.h> > + > +#define SPCR 0x00 > +#define SPSR 0x04 > +#define SPSCR 0x08 > +#define SPTBR 0x0C > +#define SPRBR 0x10 > +#define SPCR2 0x14 > + > +/* SPSR */ > +#define RXFL (1 << 2) > + > +#define hspi2info(h) (h->dev->platform_data) > + > +struct hspi_priv { > + void __iomem *addr; > + struct spi_master *master; > + struct list_head queue; > + struct workqueue_struct *workqueue; > + struct work_struct ws; > + struct device *dev; > + spinlock_t lock; > +}; > + > +/* > + * basic function > + */ > +static void hspi_write(struct hspi_priv *hspi, int reg, u32 val) > +{ > + iowrite32(val, hspi->addr + reg); > +} > + > +static u8 hspi_read(struct hspi_priv *hspi, int reg) > +{ > + return (u8)ioread32(hspi->addr + reg); You do a ioread32 and then typecast to u8 Didnt undwerstand? > +} > + > +/* > + * transfer function > + */ > +static int hspi_status_check_timeout(struct hspi_priv *hspi, u32 mask, u32 val) > +{ > + int t = 256; > + > + while (t--) { > + if ((mask & hspi_read(hspi, SPSR)) == val) > + return 0; > + > + msleep(10); > + } > + > + dev_err(hspi->dev, "timeout\n"); > + return -ETIMEDOUT; > +} > + > +static int hspi_push(struct hspi_priv *hspi, struct spi_message *msg, > + struct spi_transfer *t) > +{ > + int i, ret; > + u8 *data = (u8 *)t->tx_buf; > + > + /* > + * FIXME > + * very simple, but polling transfer > + */ > + for (i = 0; i < t->len; i++) { > + /* wait remains */ > + ret = hspi_status_check_timeout(hspi, 0x1, 0x0); > + if (ret < 0) > + return ret; > + > + hspi_write(hspi, SPTBR, (u32)data[i]); > + > + /* wait recive */ > + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); > + if (ret < 0) > + return ret; > + > + /* dummy read */ > + hspi_read(hspi, SPRBR); > + } > + > + return 0; > +} > + > +static int hspi_pop(struct hspi_priv *hspi, struct spi_message *msg, > + struct spi_transfer *t) > +{ > + int i, ret; > + u8 *data = (u8 *)t->rx_buf; > + > + /* > + * FIXME > + * very simple, but polling receive > + */ > + for (i = 0; i < t->len; i++) { > + /* wait remains */ > + ret = hspi_status_check_timeout(hspi, 0x1, 0); > + if (ret < 0) > + return ret; > + > + /* dummy write */ > + hspi_write(hspi, SPTBR, 0x0); > + > + /* wait recive */ > + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); > + if (ret < 0) > + return ret; > + > + data[i] = (u8)hspi_read(hspi, SPRBR); > + } > + > + return 0; > +} > + > +static void hspi_work(struct work_struct *work) > +{ > + struct hspi_priv *hspi = container_of(work, struct hspi_priv, ws); > + struct sh_hspi_info *info = hspi2info(hspi); > + struct spi_message *msg; > + struct spi_transfer *t; > + unsigned long flags; > + u32 data; > + int ret; > + > + dev_dbg(hspi->dev, "%s\n", __func__); > + > + /************************ pm enable ************************/ > + pm_runtime_get_sync(hspi->dev); > + > + /* setup first of all in under pm_runtime */ > + data = SH_HSPI_CLK_DIVC(info->flags); > + > + if (info->flags & SH_HSPI_FBS) > + data |= 1 << 7; > + if (info->flags & SH_HSPI_CLKP_HIGH) > + data |= 1 << 6; > + if (info->flags & SH_HSPI_IDIV_DIV128) > + data |= 1 << 5; > + > + hspi_write(hspi, SPCR, data); > + hspi_write(hspi, SPSR, 0x0); > + hspi_write(hspi, SPSCR, 0x1); /* master mode */ > + > + while (1) { > + msg = NULL; > + > + /************************ spin lock ************************/ > + spin_lock_irqsave(&hspi->lock, flags); > + if (!list_empty(&hspi->queue)) { > + msg = list_entry(hspi->queue.next, > + struct spi_message, queue); > + list_del_init(&msg->queue); > + } > + spin_unlock_irqrestore(&hspi->lock, flags); > + /************************ spin unlock ************************/ > + if (!msg) > + break; > + > + ret = 0; > + list_for_each_entry(t, &msg->transfers, transfer_list) { > + if (t->tx_buf) { > + ret = hspi_push(hspi, msg, t); > + if (ret < 0) > + goto error; > + } > + if (t->rx_buf) { > + ret = hspi_pop(hspi, msg, t); > + if (ret < 0) > + goto error; > + } > + msg->actual_length += t->len; > + } > +error: > + msg->status = ret; > + msg->complete(msg->context); > + } > + > + pm_runtime_put_sync(hspi->dev); > + /************************ pm disable ************************/ > + > + return; > +} > + > +/* > + * spi master function > + */ > +static int hspi_setup(struct spi_device *spi) > +{ > + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); > + struct device *dev = hspi->dev; > + > + if (8 != spi->bits_per_word) { > + dev_err(dev, "bits_per_word should be 8\n"); > + return -EIO; > + } > + > + dev_dbg(dev, "%s setup\n", spi->modalias); > + > + return 0; > +} > + > +static void hspi_cleanup(struct spi_device *spi) > +{ > + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); > + struct device *dev = hspi->dev; > + > + dev_dbg(dev, "%s cleanup\n", spi->modalias); > +} > + > +static int hspi_transfer(struct spi_device *spi, struct spi_message *msg) > +{ > + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); > + unsigned long flags; > + > + /************************ spin lock ************************/ > + spin_lock_irqsave(&hspi->lock, flags); > + > + msg->actual_length = 0; > + msg->status = -EINPROGRESS; > + list_add_tail(&msg->queue, &hspi->queue); > + > + spin_unlock_irqrestore(&hspi->lock, flags); > + /************************ spin unlock ************************/ > + > + queue_work(hspi->workqueue, &hspi->ws); > + > + return 0; > +} > + > +static int __devinit hspi_probe(struct platform_device *pdev) > +{ > + struct resource *res; > + struct spi_master *master; > + struct hspi_priv *hspi; > + int ret; > + > + /* get base addr */ > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(&pdev->dev, "invalid resource\n"); > + return -EINVAL; > + } > + > + master = spi_alloc_master(&pdev->dev, sizeof(*hspi)); > + if (!master) { > + dev_err(&pdev->dev, "spi_alloc_master error.\n"); > + return -ENOMEM; > + } > + > + hspi = spi_master_get_devdata(master); > + dev_set_drvdata(&pdev->dev, hspi); > + > + /* init hspi */ > + hspi->master = master; > + hspi->dev = &pdev->dev; > + hspi->addr = ioremap(res->start, resource_size(res)); Could we use devm_* functions here > + if (!hspi->addr) { <snip> ------------------------------------------------------------------------------ Virtualization & Cloud Management Using Capacity Planning Cloud computing makes use of virtualization - but cloud computing also focuses on allowing computing to be delivered as a service. http://www.accelacomm.com/jaw/sfnl/114/51521223/ ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <CAM=Q2cv3QBLEWp+rQ9f5onBS1j=wZOes5m-r9fs6Uvfu6bnTkg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* [PATCH v3] spi: Add SuperH HSPI prototype driver [not found] ` <CAM=Q2cv3QBLEWp+rQ9f5onBS1j=wZOes5m-r9fs6Uvfu6bnTkg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> @ 2012-03-02 1:10 ` Kuninori Morimoto [not found] ` <878vjjbyfk.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 0 siblings, 1 reply; 8+ messages in thread From: Kuninori Morimoto @ 2012-03-02 1:10 UTC (permalink / raw) To: Shubhrajyoti Datta, Grant Likely Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Magnus, Kuninori Morimoto This patch adds SuperH HSPI driver. It is still prototype driver, but has enough function at this point. Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> --- v2 -> v3 - modify wrong hspi_read() return cast - use devm_xx() drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi-sh-hspi.c | 364 +++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/sh_hspi.h | 34 ++++ 4 files changed, 405 insertions(+), 0 deletions(-) create mode 100644 drivers/spi/spi-sh-hspi.c create mode 100644 include/linux/spi/sh_hspi.h diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 8293658..6f544e6 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -324,6 +324,12 @@ config SPI_SH_SCI help SPI driver for SuperH SCI blocks. +config SPI_SH_HSPI + tristate "SuperH HSPI controller" + depends on ARCH_SHMOBILE + help + SPI driver for SuperH HSPI blocks. + config SPI_STMP3XXX tristate "Freescale STMP37xx/378x SPI/SSP controller" depends on ARCH_STMP3XXX && SPI_MASTER diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 61c3261..d65e059 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_SPI_S3C64XX) += spi-s3c64xx.o obj-$(CONFIG_SPI_SH) += spi-sh.o obj-$(CONFIG_SPI_SH_MSIOF) += spi-sh-msiof.o obj-$(CONFIG_SPI_SH_SCI) += spi-sh-sci.o +obj-$(CONFIG_SPI_SH_HSPI) += spi-sh-hspi.o obj-$(CONFIG_SPI_STMP3XXX) += spi-stmp.o obj-$(CONFIG_SPI_TEGRA) += spi-tegra.o obj-$(CONFIG_SPI_TI_SSP) += spi-ti-ssp.o diff --git a/drivers/spi/spi-sh-hspi.c b/drivers/spi/spi-sh-hspi.c new file mode 100644 index 0000000..8356ec8 --- /dev/null +++ b/drivers/spi/spi-sh-hspi.c @@ -0,0 +1,364 @@ +/* + * SuperH HSPI bus driver + * + * Copyright (C) 2011 Kuninori Morimoto + * + * Based on spi-sh.c: + * Based on pxa2xx_spi.c: + * Copyright (C) 2011 Renesas Solutions Corp. + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/io.h> +#include <linux/spi/spi.h> +#include <linux/spi/sh_hspi.h> + +#define SPCR 0x00 +#define SPSR 0x04 +#define SPSCR 0x08 +#define SPTBR 0x0C +#define SPRBR 0x10 +#define SPCR2 0x14 + +/* SPSR */ +#define RXFL (1 << 2) + +#define hspi2info(h) (h->dev->platform_data) + +struct hspi_priv { + void __iomem *addr; + struct spi_master *master; + struct list_head queue; + struct workqueue_struct *workqueue; + struct work_struct ws; + struct device *dev; + spinlock_t lock; +}; + +/* + * basic function + */ +static void hspi_write(struct hspi_priv *hspi, int reg, u32 val) +{ + iowrite32(val, hspi->addr + reg); +} + +static u32 hspi_read(struct hspi_priv *hspi, int reg) +{ + return ioread32(hspi->addr + reg); +} + +/* + * transfer function + */ +static int hspi_status_check_timeout(struct hspi_priv *hspi, u32 mask, u32 val) +{ + int t = 256; + + while (t--) { + if ((mask & hspi_read(hspi, SPSR)) == val) + return 0; + + msleep(20); + } + + dev_err(hspi->dev, "timeout\n"); + return -ETIMEDOUT; +} + +static int hspi_push(struct hspi_priv *hspi, struct spi_message *msg, + struct spi_transfer *t) +{ + int i, ret; + u8 *data = (u8 *)t->tx_buf; + + /* + * FIXME + * very simple, but polling transfer + */ + for (i = 0; i < t->len; i++) { + /* wait remains */ + ret = hspi_status_check_timeout(hspi, 0x1, 0x0); + if (ret < 0) + return ret; + + hspi_write(hspi, SPTBR, (u32)data[i]); + + /* wait recive */ + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); + if (ret < 0) + return ret; + + /* dummy read */ + hspi_read(hspi, SPRBR); + } + + return 0; +} + +static int hspi_pop(struct hspi_priv *hspi, struct spi_message *msg, + struct spi_transfer *t) +{ + int i, ret; + u8 *data = (u8 *)t->rx_buf; + + /* + * FIXME + * very simple, but polling receive + */ + for (i = 0; i < t->len; i++) { + /* wait remains */ + ret = hspi_status_check_timeout(hspi, 0x1, 0); + if (ret < 0) + return ret; + + /* dummy write */ + hspi_write(hspi, SPTBR, 0x0); + + /* wait recive */ + ret = hspi_status_check_timeout(hspi, 0x4, 0x4); + if (ret < 0) + return ret; + + data[i] = (u8)hspi_read(hspi, SPRBR); + } + + return 0; +} + +static void hspi_work(struct work_struct *work) +{ + struct hspi_priv *hspi = container_of(work, struct hspi_priv, ws); + struct sh_hspi_info *info = hspi2info(hspi); + struct spi_message *msg; + struct spi_transfer *t; + unsigned long flags; + u32 data; + int ret; + + dev_dbg(hspi->dev, "%s\n", __func__); + + /************************ pm enable ************************/ + pm_runtime_get_sync(hspi->dev); + + /* setup first of all in under pm_runtime */ + data = SH_HSPI_CLK_DIVC(info->flags); + + if (info->flags & SH_HSPI_FBS) + data |= 1 << 7; + if (info->flags & SH_HSPI_CLKP_HIGH) + data |= 1 << 6; + if (info->flags & SH_HSPI_IDIV_DIV128) + data |= 1 << 5; + + hspi_write(hspi, SPCR, data); + hspi_write(hspi, SPSR, 0x0); + hspi_write(hspi, SPSCR, 0x1); /* master mode */ + + while (1) { + msg = NULL; + + /************************ spin lock ************************/ + spin_lock_irqsave(&hspi->lock, flags); + if (!list_empty(&hspi->queue)) { + msg = list_entry(hspi->queue.next, + struct spi_message, queue); + list_del_init(&msg->queue); + } + spin_unlock_irqrestore(&hspi->lock, flags); + /************************ spin unlock ************************/ + if (!msg) + break; + + ret = 0; + list_for_each_entry(t, &msg->transfers, transfer_list) { + if (t->tx_buf) { + ret = hspi_push(hspi, msg, t); + if (ret < 0) + goto error; + } + if (t->rx_buf) { + ret = hspi_pop(hspi, msg, t); + if (ret < 0) + goto error; + } + msg->actual_length += t->len; + } +error: + msg->status = ret; + msg->complete(msg->context); + } + + pm_runtime_put_sync(hspi->dev); + /************************ pm disable ************************/ + + return; +} + +/* + * spi master function + */ +static int hspi_setup(struct spi_device *spi) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + struct device *dev = hspi->dev; + + if (8 != spi->bits_per_word) { + dev_err(dev, "bits_per_word should be 8\n"); + return -EIO; + } + + dev_dbg(dev, "%s setup\n", spi->modalias); + + return 0; +} + +static void hspi_cleanup(struct spi_device *spi) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + struct device *dev = hspi->dev; + + dev_dbg(dev, "%s cleanup\n", spi->modalias); +} + +static int hspi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct hspi_priv *hspi = spi_master_get_devdata(spi->master); + unsigned long flags; + + /************************ spin lock ************************/ + spin_lock_irqsave(&hspi->lock, flags); + + msg->actual_length = 0; + msg->status = -EINPROGRESS; + list_add_tail(&msg->queue, &hspi->queue); + + spin_unlock_irqrestore(&hspi->lock, flags); + /************************ spin unlock ************************/ + + queue_work(hspi->workqueue, &hspi->ws); + + return 0; +} + +static int __devinit hspi_probe(struct platform_device *pdev) +{ + struct resource *res; + struct spi_master *master; + struct hspi_priv *hspi; + int ret; + + /* get base addr */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "invalid resource\n"); + return -EINVAL; + } + + master = spi_alloc_master(&pdev->dev, sizeof(*hspi)); + if (!master) { + dev_err(&pdev->dev, "spi_alloc_master error.\n"); + return -ENOMEM; + } + + hspi = spi_master_get_devdata(master); + dev_set_drvdata(&pdev->dev, hspi); + + /* init hspi */ + hspi->master = master; + hspi->dev = &pdev->dev; + hspi->addr = devm_ioremap(hspi->dev, + res->start, resource_size(res)); + if (!hspi->addr) { + dev_err(&pdev->dev, "ioremap error.\n"); + ret = -ENOMEM; + goto error1; + } + hspi->workqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!hspi->workqueue) { + dev_err(&pdev->dev, "create workqueue error\n"); + ret = -EBUSY; + goto error2; + } + + spin_lock_init(&hspi->lock); + INIT_LIST_HEAD(&hspi->queue); + INIT_WORK(&hspi->ws, hspi_work); + + master->num_chipselect = 1; + master->bus_num = pdev->id; + master->setup = hspi_setup; + master->transfer = hspi_transfer; + master->cleanup = hspi_cleanup; + master->mode_bits = SPI_CPOL | SPI_CPHA; + ret = spi_register_master(master); + if (ret < 0) { + dev_err(&pdev->dev, "spi_register_master error.\n"); + goto error3; + } + + pm_runtime_enable(&pdev->dev); + + dev_info(&pdev->dev, "probed\n"); + + return 0; + + error3: + destroy_workqueue(hspi->workqueue); + error2: + devm_iounmap(hspi->dev, hspi->addr); + error1: + spi_master_put(master); + + return ret; +} + +static int __devexit hspi_remove(struct platform_device *pdev) +{ + struct hspi_priv *hspi = dev_get_drvdata(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + spi_unregister_master(hspi->master); + destroy_workqueue(hspi->workqueue); + devm_iounmap(hspi->dev, hspi->addr); + + return 0; +} + +static struct platform_driver hspi_driver = { + .probe = hspi_probe, + .remove = __devexit_p(hspi_remove), + .driver = { + .name = "sh-hspi", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(hspi_driver); + +MODULE_DESCRIPTION("SuperH HSPI bus driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>"); +MODULE_ALIAS("platform:sh_spi"); diff --git a/include/linux/spi/sh_hspi.h b/include/linux/spi/sh_hspi.h new file mode 100644 index 0000000..956d112 --- /dev/null +++ b/include/linux/spi/sh_hspi.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011 Kuninori Morimoto + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef SH_HSPI_H +#define SH_HSPI_H + +/* + * flags + * + * + */ +#define SH_HSPI_CLK_DIVC(d) (d & 0xFF) + +#define SH_HSPI_FBS (1 << 8) +#define SH_HSPI_CLKP_HIGH (1 << 9) /* default LOW */ +#define SH_HSPI_IDIV_DIV128 (1 << 10) /* default div16 */ +struct sh_hspi_info { + u32 flags; +}; + +#endif -- 1.7.5.4 ------------------------------------------------------------------------------ Virtualization & Cloud Management Using Capacity Planning Cloud computing makes use of virtualization - but cloud computing also focuses on allowing computing to be delivered as a service. http://www.accelacomm.com/jaw/sfnl/114/51521223/ ^ permalink raw reply related [flat|nested] 8+ messages in thread
[parent not found: <878vjjbyfk.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH v3] spi: Add SuperH HSPI prototype driver [not found] ` <878vjjbyfk.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> @ 2012-03-09 17:54 ` Grant Likely [not found] ` <CACxGe6scA9M4byU1vGSBaZaMjuB8z8q7t2m+qdDkGoYK4=+1mw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> 0 siblings, 1 reply; 8+ messages in thread From: Grant Likely @ 2012-03-09 17:54 UTC (permalink / raw) To: Kuninori Morimoto Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Shubhrajyoti Datta, Magnus, Kuninori Morimoto On Thu, Mar 1, 2012 at 6:10 PM, Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> wrote: > This patch adds SuperH HSPI driver. > It is still prototype driver, but has enough function at this point. > > Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> Applied, thanks. However, the spi subsystem is getting new support for core-queuing in v3.4. You should craft a patch before v3.5 to use the new infrastructure. g. ------------------------------------------------------------------------------ Virtualization & Cloud Management Using Capacity Planning Cloud computing makes use of virtualization - but cloud computing also focuses on allowing computing to be delivered as a service. http://www.accelacomm.com/jaw/sfnl/114/51521223/ ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <CACxGe6scA9M4byU1vGSBaZaMjuB8z8q7t2m+qdDkGoYK4=+1mw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [PATCH v3] spi: Add SuperH HSPI prototype driver [not found] ` <CACxGe6scA9M4byU1vGSBaZaMjuB8z8q7t2m+qdDkGoYK4=+1mw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> @ 2012-03-13 0:23 ` Kuninori Morimoto 0 siblings, 0 replies; 8+ messages in thread From: Kuninori Morimoto @ 2012-03-13 0:23 UTC (permalink / raw) To: Grant Likely Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f, Shubhrajyoti Datta, Magnus, Kuninori Morimoto Hi Grant > On Thu, Mar 1, 2012 at 6:10 PM, Kuninori Morimoto > <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> wrote: > > This patch adds SuperH HSPI driver. > > It is still prototype driver, but has enough function at this point. > > > > Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> > > Applied, thanks. > > However, the spi subsystem is getting new support for core-queuing in > v3.4. You should craft a patch before v3.5 to use the new > infrastructure. I understand, Thank you. I keep updating this driver Best regards --- Kuninori Morimoto ------------------------------------------------------------------------------ Keep Your Developer Skills Current with LearnDevNow! The most comprehensive online learning library for Microsoft developers is just $99.99! Visual Studio, SharePoint, SQL - plus HTML5, CSS3, MVC3, Metro Style Apps, more. Free future releases when you subscribe now! http://p.sf.net/sfu/learndevnow-d2d ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2012-03-13 0:23 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2011-12-27 8:35 [PATCH] spi: Add SuperH HSPI prototype driver Kuninori Morimoto [not found] ` <87vcp2tnst.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 2012-01-04 20:05 ` Grant Likely [not found] ` <20120104200548.GH15503-e0URQFbLeQY2iJbIjFUEsiwD8/FfD2ys@public.gmane.org> 2012-01-06 6:00 ` [PATCH v2] " Kuninori Morimoto [not found] ` <871urd9xq0.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 2012-02-29 8:03 ` Kuninori Morimoto 2012-03-01 9:26 ` Shubhrajyoti Datta [not found] ` <CAM=Q2cv3QBLEWp+rQ9f5onBS1j=wZOes5m-r9fs6Uvfu6bnTkg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> 2012-03-02 1:10 ` [PATCH v3] " Kuninori Morimoto [not found] ` <878vjjbyfk.wl%kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> 2012-03-09 17:54 ` Grant Likely [not found] ` <CACxGe6scA9M4byU1vGSBaZaMjuB8z8q7t2m+qdDkGoYK4=+1mw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> 2012-03-13 0:23 ` Kuninori Morimoto
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).