Commit 78961a5740374a8143f8fe120300f2ed160dd276

Authored by Kaiwan N Billimoria
Committed by Linus Torvalds
1 parent 4917d92780

spi_lm70llp parport adapter driver

This adds a driver for the LM70-LLP parport adapter, which is an eval board
for the LM70 temperature sensor.  For those without that board, it may be a
simpler example of a parport-to-SPI adapter then spi_butterfly.

Signed-off-by: Kaiwan N Billimoria <kaiwan@designergraphix.com>

Doc, coding style, and interface updates; build fixes.  Minor rename.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Showing 4 changed files with 440 additions and 0 deletions Side-by-side Diff

Documentation/spi/spi-lm70llp
  1 +spi_lm70llp : LM70-LLP parport-to-SPI adapter
  2 +==============================================
  3 +
  4 +Supported board/chip:
  5 + * National Semiconductor LM70 LLP evaluation board
  6 + Datasheet: http://www.national.com/pf/LM/LM70.html
  7 +
  8 +Author:
  9 + Kaiwan N Billimoria <kaiwan@designergraphix.com>
  10 +
  11 +Description
  12 +-----------
  13 +This driver provides glue code connecting a National Semiconductor LM70 LLP
  14 +temperature sensor evaluation board to the kernel's SPI core subsystem.
  15 +
  16 +In effect, this driver turns the parallel port interface on the eval board
  17 +into a SPI bus with a single device, which will be driven by the generic
  18 +LM70 driver (drivers/hwmon/lm70.c).
  19 +
  20 +The hardware interfacing on the LM70 LLP eval board is as follows:
  21 +
  22 + Parallel LM70 LLP
  23 + Port Direction JP2 Header
  24 + ----------- --------- ----------------
  25 + D0 2 - -
  26 + D1 3 --> V+ 5
  27 + D2 4 --> V+ 5
  28 + D3 5 --> V+ 5
  29 + D4 6 --> V+ 5
  30 + D5 7 --> nCS 8
  31 + D6 8 --> SCLK 3
  32 + D7 9 --> SI/O 5
  33 + GND 25 - GND 7
  34 + Select 13 <-- SI/O 1
  35 + ----------- --------- ----------------
  36 +
  37 +Note that since the LM70 uses a "3-wire" variant of SPI, the SI/SO pin
  38 +is connected to both pin D7 (as Master Out) and Select (as Master In)
  39 +using an arrangment that lets either the parport or the LM70 pull the
  40 +pin low. This can't be shared with true SPI devices, but other 3-wire
  41 +devices might share the same SI/SO pin.
  42 +
  43 +The bitbanger routine in this driver (lm70_txrx) is called back from
  44 +the bound "hwmon/lm70" protocol driver through its sysfs hook, using a
  45 +spi_write_then_read() call. It performs Mode 0 (SPI/Microwire) bitbanging.
  46 +The lm70 driver then inteprets the resulting digital temperature value
  47 +and exports it through sysfs.
  48 +
  49 +A "gotcha": National Semiconductor's LM70 LLP eval board circuit schematic
  50 +shows that the SI/O line from the LM70 chip is connected to the base of a
  51 +transistor Q1 (and also a pullup, and a zener diode to D7); while the
  52 +collector is tied to VCC.
  53 +
  54 +Interpreting this circuit, when the LM70 SI/O line is High (or tristate
  55 +and not grounded by the host via D7), the transistor conducts and switches
  56 +the collector to zero, which is reflected on pin 13 of the DB25 parport
  57 +connector. When SI/O is Low (driven by the LM70 or the host) on the other
  58 +hand, the transistor is cut off and the voltage tied to it's collector is
  59 +reflected on pin 13 as a High level.
  60 +
  61 +So: the getmiso inline routine in this driver takes this fact into account,
  62 +inverting the value read at pin 13.
  63 +
  64 +
  65 +Thanks to
  66 +---------
  67 +o David Brownell for mentoring the SPI-side driver development.
  68 +o Dr.Craig Hollabaugh for the (early) "manual" bitbanging driver version.
  69 +o Nadir Billimoria for help interpreting the circuit schematic.
... ... @@ -107,6 +107,15 @@
107 107 This enables using the Freescale iMX SPI controller in master
108 108 mode.
109 109  
  110 +config SPI_LM70_LLP
  111 + tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)"
  112 + depends on SPI_MASTER && PARPORT && EXPERIMENTAL
  113 + select SPI_BITBANG
  114 + help
  115 + This driver supports the NS LM70 LLP Evaluation Board,
  116 + which interfaces to an LM70 temperature sensor using
  117 + a parallel port.
  118 +
110 119 config SPI_MPC52xx_PSC
111 120 tristate "Freescale MPC52xx PSC SPI controller"
112 121 depends on SPI_MASTER && PPC_MPC52xx && EXPERIMENTAL
drivers/spi/Makefile
... ... @@ -17,6 +17,7 @@
17 17 obj-$(CONFIG_SPI_AU1550) += au1550_spi.o
18 18 obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o
19 19 obj-$(CONFIG_SPI_IMX) += spi_imx.o
  20 +obj-$(CONFIG_SPI_LM70_LLP) += spi_lm70llp.o
20 21 obj-$(CONFIG_SPI_PXA2XX) += pxa2xx_spi.o
21 22 obj-$(CONFIG_SPI_OMAP_UWIRE) += omap_uwire.o
22 23 obj-$(CONFIG_SPI_MPC52xx_PSC) += mpc52xx_psc_spi.o
drivers/spi/spi_lm70llp.c
  1 +/*
  2 + * spi_lm70llp.c - driver for lm70llp eval board for the LM70 sensor
  3 + *
  4 + * Copyright (C) 2006 Kaiwan N Billimoria <kaiwan@designergraphix.com>
  5 + *
  6 + * This program is free software; you can redistribute it and/or modify
  7 + * it under the terms of the GNU General Public License as published by
  8 + * the Free Software Foundation; either version 2 of the License, or
  9 + * (at your option) any later version.
  10 + *
  11 + * This program is distributed in the hope that it will be useful,
  12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14 + * GNU General Public License for more details.
  15 + *
  16 + * You should have received a copy of the GNU General Public License
  17 + * along with this program; if not, write to the Free Software
  18 + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  19 + */
  20 +
  21 +#include <linux/init.h>
  22 +#include <linux/module.h>
  23 +#include <linux/kernel.h>
  24 +#include <linux/delay.h>
  25 +#include <linux/device.h>
  26 +#include <linux/parport.h>
  27 +#include <linux/sysfs.h>
  28 +#include <linux/workqueue.h>
  29 +
  30 +
  31 +#include <linux/spi/spi.h>
  32 +#include <linux/spi/spi_bitbang.h>
  33 +
  34 +
  35 +/*
  36 + * The LM70 communicates with a host processor using a 3-wire variant of
  37 + * the SPI/Microwire bus interface. This driver specifically supports an
  38 + * NS LM70 LLP Evaluation Board, interfacing to a PC using its parallel
  39 + * port to bitbang an SPI-parport bridge. Accordingly, this is an SPI
  40 + * master controller driver. The hwmon/lm70 driver is a "SPI protocol
  41 + * driver", layered on top of this one and usable without the lm70llp.
  42 + *
  43 + * The LM70 is a temperature sensor chip from National Semiconductor; its
  44 + * datasheet is available at http://www.national.com/pf/LM/LM70.html
  45 + *
  46 + * Also see Documentation/spi/spi-lm70llp. The SPI<->parport code here is
  47 + * (heavily) based on spi-butterfly by David Brownell.
  48 + *
  49 + * The LM70 LLP connects to the PC parallel port in the following manner:
  50 + *
  51 + * Parallel LM70 LLP
  52 + * Port Direction JP2 Header
  53 + * ----------- --------- ------------
  54 + * D0 2 - -
  55 + * D1 3 --> V+ 5
  56 + * D2 4 --> V+ 5
  57 + * D3 5 --> V+ 5
  58 + * D4 6 --> V+ 5
  59 + * D5 7 --> nCS 8
  60 + * D6 8 --> SCLK 3
  61 + * D7 9 --> SI/O 5
  62 + * GND 25 - GND 7
  63 + * Select 13 <-- SI/O 1
  64 + *
  65 + * Note that parport pin 13 actually gets inverted by the transistor
  66 + * arrangement which lets either the parport or the LM70 drive the
  67 + * SI/SO signal.
  68 + */
  69 +
  70 +#define DRVNAME "spi-lm70llp"
  71 +
  72 +#define lm70_INIT 0xBE
  73 +#define SIO 0x10
  74 +#define nCS 0x20
  75 +#define SCLK 0x40
  76 +
  77 +/*-------------------------------------------------------------------------*/
  78 +
  79 +struct spi_lm70llp {
  80 + struct spi_bitbang bitbang;
  81 + struct parport *port;
  82 + struct pardevice *pd;
  83 + struct spi_device *spidev_lm70;
  84 + struct spi_board_info info;
  85 + struct class_device *cdev;
  86 +};
  87 +
  88 +/* REVISIT : ugly global ; provides "exclusive open" facility */
  89 +static struct spi_lm70llp *lm70llp;
  90 +
  91 +
  92 +/*-------------------------------------------------------------------*/
  93 +
  94 +static inline struct spi_lm70llp *spidev_to_pp(struct spi_device *spi)
  95 +{
  96 + return spi->controller_data;
  97 +}
  98 +
  99 +/*---------------------- LM70 LLP eval board-specific inlines follow */
  100 +
  101 +/* NOTE: we don't actually need to reread the output values, since they'll
  102 + * still be what we wrote before. Plus, going through parport builds in
  103 + * a ~1ms/operation delay; these SPI transfers could easily be faster.
  104 + */
  105 +
  106 +static inline void deassertCS(struct spi_lm70llp *pp)
  107 +{
  108 + u8 data = parport_read_data(pp->port);
  109 + parport_write_data(pp->port, data | nCS);
  110 +}
  111 +
  112 +static inline void assertCS(struct spi_lm70llp *pp)
  113 +{
  114 + u8 data = parport_read_data(pp->port);
  115 + parport_write_data(pp->port, data & ~nCS);
  116 +}
  117 +
  118 +static inline void clkHigh(struct spi_lm70llp *pp)
  119 +{
  120 + u8 data = parport_read_data(pp->port);
  121 + parport_write_data(pp->port, data | SCLK);
  122 +}
  123 +
  124 +static inline void clkLow(struct spi_lm70llp *pp)
  125 +{
  126 + u8 data = parport_read_data(pp->port);
  127 + parport_write_data(pp->port, data & ~SCLK);
  128 +}
  129 +
  130 +/*------------------------- SPI-LM70-specific inlines ----------------------*/
  131 +
  132 +static inline void spidelay(unsigned d)
  133 +{
  134 + udelay(d);
  135 +}
  136 +
  137 +static inline void setsck(struct spi_device *s, int is_on)
  138 +{
  139 + struct spi_lm70llp *pp = spidev_to_pp(s);
  140 +
  141 + if (is_on)
  142 + clkHigh(pp);
  143 + else
  144 + clkLow(pp);
  145 +}
  146 +
  147 +static inline void setmosi(struct spi_device *s, int is_on)
  148 +{
  149 + /* FIXME update D7 ... this way we can put the chip
  150 + * into shutdown mode and read the manufacturer ID,
  151 + * but we can't put it back into operational mode.
  152 + */
  153 +}
  154 +
  155 +/*
  156 + * getmiso:
  157 + * Why do we return 0 when the SIO line is high and vice-versa?
  158 + * The fact is, the lm70 eval board from NS (which this driver drives),
  159 + * is wired in just such a way : when the lm70's SIO goes high, a transistor
  160 + * switches it to low reflecting this on the parport (pin 13), and vice-versa.
  161 + */
  162 +static inline int getmiso(struct spi_device *s)
  163 +{
  164 + struct spi_lm70llp *pp = spidev_to_pp(s);
  165 + return ((SIO == (parport_read_status(pp->port) & SIO)) ? 0 : 1 );
  166 +}
  167 +/*--------------------------------------------------------------------*/
  168 +
  169 +#define EXPAND_BITBANG_TXRX 1
  170 +#include <linux/spi/spi_bitbang.h>
  171 +
  172 +static void lm70_chipselect(struct spi_device *spi, int value)
  173 +{
  174 + struct spi_lm70llp *pp = spidev_to_pp(spi);
  175 +
  176 + if (value)
  177 + assertCS(pp);
  178 + else
  179 + deassertCS(pp);
  180 +}
  181 +
  182 +/*
  183 + * Our actual bitbanger routine.
  184 + */
  185 +static u32 lm70_txrx(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits)
  186 +{
  187 + static u32 sio=0;
  188 + static int first_time=1;
  189 +
  190 + /* First time: perform SPI bitbang and return the LSB of
  191 + * the result of the SPI call.
  192 + */
  193 + if (first_time) {
  194 + sio = bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits);
  195 + first_time=0;
  196 + return (sio & 0x00ff);
  197 + }
  198 + /* Return the MSB of the result of the SPI call */
  199 + else {
  200 + first_time=1;
  201 + return (sio >> 8);
  202 + }
  203 +}
  204 +
  205 +static void spi_lm70llp_attach(struct parport *p)
  206 +{
  207 + struct pardevice *pd;
  208 + struct spi_lm70llp *pp;
  209 + struct spi_master *master;
  210 + int status;
  211 +
  212 + if (lm70llp) {
  213 + printk(KERN_WARNING
  214 + "%s: spi_lm70llp instance already loaded. Aborting.\n",
  215 + DRVNAME);
  216 + return;
  217 + }
  218 +
  219 + /* TODO: this just _assumes_ a lm70 is there ... no probe;
  220 + * the lm70 driver could verify it, reading the manf ID.
  221 + */
  222 +
  223 + master = spi_alloc_master(p->physport->dev, sizeof *pp);
  224 + if (!master) {
  225 + status = -ENOMEM;
  226 + goto out_fail;
  227 + }
  228 + pp = spi_master_get_devdata(master);
  229 +
  230 + master->bus_num = -1; /* dynamic alloc of a bus number */
  231 + master->num_chipselect = 1;
  232 +
  233 + /*
  234 + * SPI and bitbang hookup.
  235 + */
  236 + pp->bitbang.master = spi_master_get(master);
  237 + pp->bitbang.chipselect = lm70_chipselect;
  238 + pp->bitbang.txrx_word[SPI_MODE_0] = lm70_txrx;
  239 + pp->bitbang.flags = SPI_3WIRE;
  240 +
  241 + /*
  242 + * Parport hookup
  243 + */
  244 + pp->port = p;
  245 + pd = parport_register_device(p, DRVNAME,
  246 + NULL, NULL, NULL,
  247 + PARPORT_FLAG_EXCL, pp);
  248 + if (!pd) {
  249 + status = -ENOMEM;
  250 + goto out_free_master;
  251 + }
  252 + pp->pd = pd;
  253 +
  254 + status = parport_claim(pd);
  255 + if (status < 0)
  256 + goto out_parport_unreg;
  257 +
  258 + /*
  259 + * Start SPI ...
  260 + */
  261 + status = spi_bitbang_start(&pp->bitbang);
  262 + if (status < 0) {
  263 + printk(KERN_WARNING
  264 + "%s: spi_bitbang_start failed with status %d\n",
  265 + DRVNAME, status);
  266 + goto out_off_and_release;
  267 + }
  268 +
  269 + /*
  270 + * The modalias name MUST match the device_driver name
  271 + * for the bus glue code to match and subsequently bind them.
  272 + * We are binding to the generic drivers/hwmon/lm70.c device
  273 + * driver.
  274 + */
  275 + strcpy(pp->info.modalias, "lm70");
  276 + pp->info.max_speed_hz = 6 * 1000 * 1000;
  277 + pp->info.chip_select = 0;
  278 + pp->info.mode = SPI_3WIRE | SPI_MODE_0;
  279 +
  280 + /* power up the chip, and let the LM70 control SI/SO */
  281 + parport_write_data(pp->port, lm70_INIT);
  282 +
  283 + /* Enable access to our primary data structure via
  284 + * the board info's (void *)controller_data.
  285 + */
  286 + pp->info.controller_data = pp;
  287 + pp->spidev_lm70 = spi_new_device(pp->bitbang.master, &pp->info);
  288 + if (pp->spidev_lm70)
  289 + dev_dbg(&pp->spidev_lm70->dev, "spidev_lm70 at %s\n",
  290 + pp->spidev_lm70->dev.bus_id);
  291 + else {
  292 + printk(KERN_WARNING "%s: spi_new_device failed\n", DRVNAME);
  293 + status = -ENODEV;
  294 + goto out_bitbang_stop;
  295 + }
  296 + pp->spidev_lm70->bits_per_word = 16;
  297 +
  298 + lm70llp = pp;
  299 +
  300 + return;
  301 +
  302 +out_bitbang_stop:
  303 + spi_bitbang_stop(&pp->bitbang);
  304 +out_off_and_release:
  305 + /* power down */
  306 + parport_write_data(pp->port, 0);
  307 + mdelay(10);
  308 + parport_release(pp->pd);
  309 +out_parport_unreg:
  310 + parport_unregister_device(pd);
  311 +out_free_master:
  312 + (void) spi_master_put(master);
  313 +out_fail:
  314 + pr_info("%s: spi_lm70llp probe fail, status %d\n", DRVNAME, status);
  315 +}
  316 +
  317 +static void spi_lm70llp_detach(struct parport *p)
  318 +{
  319 + struct spi_lm70llp *pp;
  320 +
  321 + if (!lm70llp || lm70llp->port != p)
  322 + return;
  323 +
  324 + pp = lm70llp;
  325 + spi_bitbang_stop(&pp->bitbang);
  326 +
  327 + /* power down */
  328 + parport_write_data(pp->port, 0);
  329 + msleep(10);
  330 +
  331 + parport_release(pp->pd);
  332 + parport_unregister_device(pp->pd);
  333 +
  334 + (void) spi_master_put(pp->bitbang.master);
  335 +
  336 + lm70llp = NULL;
  337 +}
  338 +
  339 +
  340 +static struct parport_driver spi_lm70llp_drv = {
  341 + .name = DRVNAME,
  342 + .attach = spi_lm70llp_attach,
  343 + .detach = spi_lm70llp_detach,
  344 +};
  345 +
  346 +static int __init init_spi_lm70llp(void)
  347 +{
  348 + return parport_register_driver(&spi_lm70llp_drv);
  349 +}
  350 +module_init(init_spi_lm70llp);
  351 +
  352 +static void __exit cleanup_spi_lm70llp(void)
  353 +{
  354 + parport_unregister_driver(&spi_lm70llp_drv);
  355 +}
  356 +module_exit(cleanup_spi_lm70llp);
  357 +
  358 +MODULE_AUTHOR("Kaiwan N Billimoria <kaiwan@designergraphix.com>");
  359 +MODULE_DESCRIPTION(
  360 + "Parport adapter for the National Semiconductor LM70 LLP eval board");
  361 +MODULE_LICENSE("GPL");