SVN-Revision: 10637master
parent
5c58e93209
commit
12b5a779b9
@ -0,0 +1,47 @@ |
||||
#
|
||||
# Copyright (C) 2008 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
# $Id: Makefile 10138 2008-01-06 19:28:26Z nbd $
|
||||
|
||||
#XXX This package will go away once the stuff is merged into the kernel.
|
||||
|
||||
include $(TOPDIR)/rules.mk |
||||
include $(INCLUDE_DIR)/kernel.mk |
||||
|
||||
PKG_NAME:=mmc-over-gpio
|
||||
PKG_RELEASE:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk |
||||
|
||||
|
||||
define KernelPackage/mmc-over-gpio |
||||
SUBMENU:=Other modules
|
||||
DEPENDS:=@LINUX_2_6 +kmod-spi +kmod-spi-bitbang +kmod-mmc +kmod-mmc-spi
|
||||
TITLE:=MMC/SD card over GPIO support
|
||||
FILES:=$(PKG_BUILD_DIR)/spi_gpio.$(LINUX_KMOD_SUFFIX) \
|
||||
$(PKG_BUILD_DIR)/mmc_over_spigpio.$(LINUX_KMOD_SUFFIX)
|
||||
AUTOLOAD:=$(call AutoLoad,90,spi_gpio mmc_over_spigpio)
|
||||
endef |
||||
|
||||
define KernelPackage/mmc-over-gpio/description |
||||
Support for driving an MMC/SD card over GPIO pins via SPI.
|
||||
endef |
||||
|
||||
define Build/Prepare |
||||
mkdir -p $(PKG_BUILD_DIR)
|
||||
$(CP) ./src/* $(PKG_BUILD_DIR)/
|
||||
endef |
||||
|
||||
define Build/Compile |
||||
$(MAKE) -C "$(LINUX_DIR)" \
|
||||
CROSS_COMPILE="$(TARGET_CROSS)" \
|
||||
ARCH="$(LINUX_KARCH)" \
|
||||
SUBDIRS="$(PKG_BUILD_DIR)" \
|
||||
EXTRA_CFLAGS="$(BUILDFLAGS)" \
|
||||
modules
|
||||
endef |
||||
|
||||
$(eval $(call KernelPackage,mmc-over-gpio)) |
@ -0,0 +1,2 @@ |
||||
obj-m += spi_gpio.o
|
||||
obj-m += mmc_over_spigpio.o
|
@ -0,0 +1,53 @@ |
||||
/*
|
||||
* spi_gpio interface to platform code |
||||
* |
||||
* Copyright (c) 2008 Piotr Skamruk |
||||
* |
||||
* 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 _LINUX_SPI_SPI_GPIO |
||||
#define _LINUX_SPI_SPI_GPIO |
||||
|
||||
#include <linux/types.h> |
||||
#include <linux/spi/spi.h> |
||||
|
||||
|
||||
/** struct spi_gpio_platform_data - Data definitions for a SPI-GPIO device.
|
||||
* This structure holds information about a GPIO-based SPI device. |
||||
* |
||||
* @pin_clk: The GPIO pin number of the CLOCK pin. |
||||
* |
||||
* @pin_miso: The GPIO pin number of the MISO pin. |
||||
* |
||||
* @pin_mosi: The GPIO pin number of the MOSI pin. |
||||
* |
||||
* @pin_cs: The GPIO pin number of the CHIPSELECT pin. |
||||
* |
||||
* @cs_activelow: If true, the chip is selected when the CS line is low. |
||||
* |
||||
* @no_spi_delay: If true, no delay is done in the lowlevel bitbanging. |
||||
* Note that doing no delay is not standards compliant, |
||||
* but it might be needed to speed up transfers on some |
||||
* slow embedded machines. |
||||
* |
||||
* @boardinfo_setup: This callback is called after the |
||||
* SPI master device was registered, but before the |
||||
* device is registered. |
||||
* @boardinfo_setup_data: Data argument passed to boardinfo_setup(). |
||||
*/ |
||||
struct spi_gpio_platform_data { |
||||
unsigned int pin_clk; |
||||
unsigned int pin_miso; |
||||
unsigned int pin_mosi; |
||||
unsigned int pin_cs; |
||||
bool cs_activelow; |
||||
bool no_spi_delay; |
||||
int (*boardinfo_setup)(struct spi_board_info *bi, |
||||
struct spi_master *master, |
||||
void *data); |
||||
void *boardinfo_setup_data; |
||||
}; |
||||
|
||||
#endif /* _LINUX_SPI_SPI_GPIO */ |
@ -0,0 +1,339 @@ |
||||
/*
|
||||
* Driver for driving an MMC card over a bitbanging GPIO SPI bus. |
||||
* |
||||
* Copyright 2008 Michael Buesch <mb@bu3sch.de> |
||||
* |
||||
* Licensed under the GNU/GPL. See COPYING for details. |
||||
*/ |
||||
|
||||
#include <linux/platform_device.h> |
||||
#include <linux/list.h> |
||||
#include <linux/mutex.h> |
||||
#include "linux/spi/spi_gpio.h" //XXX |
||||
|
||||
|
||||
/* This is the maximum speed in Hz */ |
||||
#define GPIOMMC_MAXSPEED 5000000 /* Hz */ |
||||
|
||||
|
||||
#define DRIVER_NAME "spi-gpio-mmc" |
||||
#define PFX DRIVER_NAME ": " |
||||
|
||||
|
||||
#define GPIOMMC_MAX_NAMELEN 15 |
||||
#define GPIOMMC_MAX_NAMELEN_STR __stringify(GPIOMMC_MAX_NAMELEN) |
||||
|
||||
struct gpiommc_pins { |
||||
unsigned int gpio_di; /* Card DI pin */ |
||||
unsigned int gpio_do; /* Card DO pin */ |
||||
unsigned int gpio_clk; /* Card CLK pin */ |
||||
unsigned int gpio_cs; /* Card CS pin */ |
||||
}; |
||||
|
||||
struct gpiommc_device { |
||||
char name[GPIOMMC_MAX_NAMELEN + 1]; |
||||
struct platform_device *pdev; |
||||
struct platform_device *spi_pdev; |
||||
struct gpiommc_pins pins; |
||||
u8 mode; /* SPI_MODE_X */ |
||||
struct spi_board_info boardinfo; |
||||
|
||||
struct list_head list; |
||||
}; |
||||
|
||||
|
||||
static LIST_HEAD(gpiommc_devices_list); |
||||
static DEFINE_MUTEX(gpiommc_mutex); |
||||
|
||||
|
||||
MODULE_DESCRIPTION("SPI-GPIO based MMC driver"); |
||||
MODULE_AUTHOR("Michael Buesch"); |
||||
MODULE_LICENSE("GPL"); |
||||
|
||||
|
||||
static int gpiommc_boardinfo_setup(struct spi_board_info *bi, |
||||
struct spi_master *master, |
||||
void *data) |
||||
{ |
||||
struct gpiommc_device *d = data; |
||||
|
||||
/* Bind the SPI master to the MMC-SPI host driver. */ |
||||
strlcpy(bi->modalias, "mmc_spi", sizeof(bi->modalias)); |
||||
|
||||
bi->max_speed_hz = GPIOMMC_MAXSPEED; |
||||
bi->bus_num = master->bus_num; |
||||
bi->mode = d->mode; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int gpiommc_probe(struct platform_device *pdev) |
||||
{ |
||||
static int instance; |
||||
struct gpiommc_device *d = platform_get_drvdata(pdev); |
||||
struct spi_gpio_platform_data pdata; |
||||
int err = -ENOMEM; |
||||
|
||||
d->spi_pdev = platform_device_alloc("spi-gpio", instance++); |
||||
if (!d->spi_pdev) |
||||
goto out; |
||||
|
||||
memset(&pdata, 0, sizeof(pdata)); |
||||
pdata.pin_clk = d->pins.gpio_clk; |
||||
pdata.pin_miso = d->pins.gpio_do; |
||||
pdata.pin_mosi = d->pins.gpio_di; |
||||
pdata.pin_cs = d->pins.gpio_cs; |
||||
pdata.cs_activelow = 1; |
||||
pdata.no_spi_delay = 1; |
||||
pdata.boardinfo_setup = gpiommc_boardinfo_setup; |
||||
pdata.boardinfo_setup_data = d; |
||||
|
||||
err = platform_device_add_data(d->spi_pdev, &pdata, sizeof(pdata)); |
||||
if (err) |
||||
goto err_free_pdev; |
||||
err = platform_device_register(d->spi_pdev); |
||||
if (err) |
||||
goto err_free_pdata; |
||||
|
||||
printk(KERN_INFO PFX "MMC-Card \"%s\" " |
||||
"attached to GPIO pins %u,%u,%u,%u\n", |
||||
d->name, d->pins.gpio_di, d->pins.gpio_do, |
||||
d->pins.gpio_clk, d->pins.gpio_cs); |
||||
out: |
||||
return err; |
||||
|
||||
err_free_pdata: |
||||
kfree(d->spi_pdev->dev.platform_data); |
||||
d->spi_pdev->dev.platform_data = NULL; |
||||
err_free_pdev: |
||||
platform_device_put(d->spi_pdev); |
||||
return err; |
||||
} |
||||
|
||||
static int gpiommc_remove(struct platform_device *pdev) |
||||
{ |
||||
struct gpiommc_device *d = platform_get_drvdata(pdev); |
||||
|
||||
platform_device_unregister(d->spi_pdev); |
||||
printk(KERN_INFO PFX "MMC-Card \"%s\" removed\n", d->name); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void gpiommc_free(struct gpiommc_device *d) |
||||
{ |
||||
kfree(d); |
||||
} |
||||
|
||||
static struct gpiommc_device * gpiommc_alloc(struct platform_device *pdev, |
||||
const char *name, |
||||
const struct gpiommc_pins *pins, |
||||
u8 mode) |
||||
{ |
||||
struct gpiommc_device *d; |
||||
|
||||
d = kmalloc(sizeof(*d), GFP_KERNEL); |
||||
if (!d) |
||||
return NULL; |
||||
|
||||
strcpy(d->name, name); |
||||
memcpy(&d->pins, pins, sizeof(d->pins)); |
||||
d->mode = mode; |
||||
INIT_LIST_HEAD(&d->list); |
||||
|
||||
return d; |
||||
} |
||||
|
||||
/* List must be locked. */ |
||||
static struct gpiommc_device * gpiommc_find_device(const char *name) |
||||
{ |
||||
struct gpiommc_device *d; |
||||
|
||||
list_for_each_entry(d, &gpiommc_devices_list, list) { |
||||
if (strcmp(d->name, name) == 0) |
||||
return d; |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
static void gpiommc_do_destroy_device(struct gpiommc_device *d) |
||||
{ |
||||
list_del(&d->list); |
||||
platform_device_unregister(d->pdev); |
||||
gpiommc_free(d); |
||||
} |
||||
|
||||
static int gpiommc_destroy_device(const char *name) |
||||
{ |
||||
struct gpiommc_device *d; |
||||
int err = -ENODEV; |
||||
|
||||
mutex_lock(&gpiommc_mutex); |
||||
d = gpiommc_find_device(name); |
||||
if (!d) |
||||
goto out_unlock; |
||||
gpiommc_do_destroy_device(d); |
||||
err = 0; |
||||
out_unlock: |
||||
mutex_unlock(&gpiommc_mutex); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
static int gpiommc_create_device(const char *name, |
||||
const struct gpiommc_pins *pins, |
||||
u8 mode) |
||||
{ |
||||
static int instance; |
||||
struct platform_device *pdev; |
||||
struct gpiommc_device *d; |
||||
int err; |
||||
|
||||
mutex_lock(&gpiommc_mutex); |
||||
err = -EEXIST; |
||||
if (gpiommc_find_device(name)) |
||||
goto out_unlock; |
||||
err = -ENOMEM; |
||||
pdev = platform_device_alloc(DRIVER_NAME, instance++); |
||||
if (!pdev) |
||||
goto out_unlock; |
||||
d = gpiommc_alloc(pdev, name, pins, mode); |
||||
if (!d) |
||||
goto err_free_pdev; |
||||
platform_set_drvdata(pdev, d); |
||||
d->pdev = pdev; |
||||
err = platform_device_register(pdev); |
||||
if (err) |
||||
goto err_free_mdev; |
||||
list_add(&d->list, &gpiommc_devices_list); |
||||
|
||||
err = 0; |
||||
out_unlock: |
||||
mutex_unlock(&gpiommc_mutex); |
||||
|
||||
return err; |
||||
|
||||
err_free_mdev: |
||||
gpiommc_free(d); |
||||
err_free_pdev: |
||||
platform_device_put(pdev); |
||||
goto out_unlock; |
||||
} |
||||
|
||||
static ssize_t gpiommc_add_show(struct device_driver *drv, |
||||
char *buf) |
||||
{ |
||||
return snprintf(buf, PAGE_SIZE, "NAME DI_pin,DO_pin,CLK_pin,CS_pin [MODE]\n"); |
||||
} |
||||
|
||||
static ssize_t gpiommc_add_store(struct device_driver *drv, |
||||
const char *buf, size_t count) |
||||
{ |
||||
int res, err; |
||||
char name[GPIOMMC_MAX_NAMELEN + 1]; |
||||
struct gpiommc_pins pins; |
||||
unsigned int mode; |
||||
|
||||
res = sscanf(buf, "%" GPIOMMC_MAX_NAMELEN_STR "s %u,%u,%u,%u %u", |
||||
name, &pins.gpio_di, &pins.gpio_do, |
||||
&pins.gpio_clk, &pins.gpio_cs, &mode); |
||||
if (res == 5) |
||||
mode = 0; |
||||
else if (res != 6) |
||||
return -EINVAL; |
||||
switch (mode) { |
||||
case 0: |
||||
mode = SPI_MODE_0; |
||||
break; |
||||
case 1: |
||||
mode = SPI_MODE_1; |
||||
break; |
||||
case 2: |
||||
mode = SPI_MODE_2; |
||||
break; |
||||
case 3: |
||||
mode = SPI_MODE_3; |
||||
break; |
||||
default: |
||||
return -EINVAL; |
||||
} |
||||
err = gpiommc_create_device(name, &pins, mode); |
||||
|
||||
return err ? err : count; |
||||
} |
||||
|
||||
static ssize_t gpiommc_remove_show(struct device_driver *drv, |
||||
char *buf) |
||||
{ |
||||
return snprintf(buf, PAGE_SIZE, "write device-name to remove the device\n"); |
||||
} |
||||
|
||||
static ssize_t gpiommc_remove_store(struct device_driver *drv, |
||||
const char *buf, size_t count) |
||||
{ |
||||
int err; |
||||
|
||||
err = gpiommc_destroy_device(buf); |
||||
|
||||
return err ? err : count; |
||||
} |
||||
|
||||
static DRIVER_ATTR(add, 0600, |
||||
gpiommc_add_show, gpiommc_add_store); |
||||
static DRIVER_ATTR(remove, 0600, |
||||
gpiommc_remove_show, gpiommc_remove_store); |
||||
|
||||
static struct platform_driver gpiommc_plat_driver = { |
||||
.probe = gpiommc_probe, |
||||
.remove = gpiommc_remove, |
||||
.driver = { |
||||
.name = DRIVER_NAME, |
||||
.owner = THIS_MODULE, |
||||
}, |
||||
}; |
||||
|
||||
static int __init gpiommc_modinit(void) |
||||
{ |
||||
int err; |
||||
|
||||
err = platform_driver_register(&gpiommc_plat_driver); |
||||
if (err) |
||||
return err; |
||||
err = driver_create_file(&gpiommc_plat_driver.driver, |
||||
&driver_attr_add); |
||||
if (err) |
||||
goto err_drv_unreg; |
||||
err = driver_create_file(&gpiommc_plat_driver.driver, |
||||
&driver_attr_remove); |
||||
if (err) |
||||
goto err_remove_add; |
||||
|
||||
return 0; |
||||
|
||||
err_remove_add: |
||||
driver_remove_file(&gpiommc_plat_driver.driver, |
||||
&driver_attr_add); |
||||
err_drv_unreg: |
||||
platform_driver_unregister(&gpiommc_plat_driver); |
||||
return err; |
||||
} |
||||
module_init(gpiommc_modinit); |
||||
|
||||
static void __exit gpiommc_modexit(void) |
||||
{ |
||||
struct gpiommc_device *d, *tmp; |
||||
|
||||
driver_remove_file(&gpiommc_plat_driver.driver, |
||||
&driver_attr_remove); |
||||
driver_remove_file(&gpiommc_plat_driver.driver, |
||||
&driver_attr_add); |
||||
|
||||
mutex_lock(&gpiommc_mutex); |
||||
list_for_each_entry_safe(d, tmp, &gpiommc_devices_list, list) |
||||
gpiommc_do_destroy_device(d); |
||||
mutex_unlock(&gpiommc_mutex); |
||||
|
||||
platform_driver_unregister(&gpiommc_plat_driver); |
||||
} |
||||
module_exit(gpiommc_modexit); |
@ -0,0 +1,242 @@ |
||||
/*
|
||||
* Bitbanging SPI bus driver using GPIO API |
||||
* |
||||
* Copyright (c) 2008 Piotr Skamruk |
||||
* Copyright (c) 2008 Michael Buesch |
||||
* |
||||
* based on spi_s3c2410_gpio.c |
||||
* Copyright (c) 2006 Ben Dooks |
||||
* Copyright (c) 2006 Simtec Electronics |
||||
* and on i2c-gpio.c |
||||
* Copyright (C) 2007 Atmel Corporation |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
#include <linux/kernel.h> |
||||
#include <linux/init.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/spinlock.h> |
||||
#include <linux/workqueue.h> |
||||
#include <linux/module.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/spi/spi.h> |
||||
#include <linux/spi/spi_bitbang.h> |
||||
#include "linux/spi/spi_gpio.h" //XXX |
||||
#include <asm/gpio.h> |
||||
|
||||
|
||||
struct spi_gpio { |
||||
struct spi_bitbang bitbang; |
||||
struct spi_gpio_platform_data *info; |
||||
struct platform_device *pdev; |
||||
struct spi_board_info bi; |
||||
}; |
||||
|
||||
|
||||
static inline struct spi_gpio *spidev_to_sg(struct spi_device *dev) |
||||
{ |
||||
return dev->controller_data; |
||||
} |
||||
|
||||
static inline void setsck(struct spi_device *dev, int val) |
||||
{ |
||||
struct spi_gpio *sp = spidev_to_sg(dev); |
||||
gpio_set_value(sp->info->pin_clk, val ? 1 : 0); |
||||
} |
||||
|
||||
static inline void setmosi(struct spi_device *dev, int val ) |
||||
{ |
||||
struct spi_gpio *sp = spidev_to_sg(dev); |
||||
gpio_set_value(sp->info->pin_mosi, val ? 1 : 0); |
||||
} |
||||
|
||||
static inline u32 getmiso(struct spi_device *dev) |
||||
{ |
||||
struct spi_gpio *sp = spidev_to_sg(dev); |
||||
return gpio_get_value(sp->info->pin_miso) ? 1 : 0; |
||||
} |
||||
|
||||
static inline void do_spidelay(struct spi_device *dev, unsigned nsecs) |
||||
{ |
||||
struct spi_gpio *sp = spidev_to_sg(dev); |
||||
|
||||
if (!sp->info->no_spi_delay) |
||||
ndelay(nsecs); |
||||
} |
||||
|
||||
#define spidelay(nsecs) do { \ |
||||
/* Steal the spi_device pointer from our caller. \
|
||||
* The bitbang-API should probably get fixed here... */ \
|
||||
do_spidelay(spi, nsecs); \
|
||||
} while (0) |
||||
|
||||
#define EXPAND_BITBANG_TXRX |
||||
#include <linux/spi/spi_bitbang.h> |
||||
|
||||
static u32 spi_gpio_txrx_mode0(struct spi_device *spi, |
||||
unsigned nsecs, u32 word, u8 bits) |
||||
{ |
||||
return bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits); |
||||
} |
||||
|
||||
static u32 spi_gpio_txrx_mode1(struct spi_device *spi, |
||||
unsigned nsecs, u32 word, u8 bits) |
||||
{ |
||||
return bitbang_txrx_be_cpha1(spi, nsecs, 0, word, bits); |
||||
} |
||||
|
||||
static u32 spi_gpio_txrx_mode2(struct spi_device *spi, |
||||
unsigned nsecs, u32 word, u8 bits) |
||||
{ |
||||
return bitbang_txrx_be_cpha0(spi, nsecs, 1, word, bits); |
||||
} |
||||
|
||||
static u32 spi_gpio_txrx_mode3(struct spi_device *spi, |
||||
unsigned nsecs, u32 word, u8 bits) |
||||
{ |
||||
return bitbang_txrx_be_cpha1(spi, nsecs, 1, word, bits); |
||||
} |
||||
|
||||
static void spi_gpio_chipselect(struct spi_device *dev, int on) |
||||
{ |
||||
struct spi_gpio *sp = spidev_to_sg(dev); |
||||
|
||||
if (sp->info->cs_activelow) |
||||
on = !on; |
||||
gpio_set_value(sp->info->pin_cs, on ? 1 : 0); |
||||
} |
||||
|
||||
static int spi_gpio_probe(struct platform_device *pdev) |
||||
{ |
||||
struct spi_master *master; |
||||
struct spi_gpio_platform_data *pdata; |
||||
struct spi_gpio *sp; |
||||
struct spi_device *spidev; |
||||
int err; |
||||
|
||||
pdata = pdev->dev.platform_data; |
||||
if (!pdata) |
||||
return -ENXIO; |
||||
|
||||
err = -ENOMEM; |
||||
master = spi_alloc_master(&pdev->dev, sizeof(struct spi_gpio)); |
||||
if (!master) |
||||
goto err_alloc_master; |
||||
|
||||
sp = spi_master_get_devdata(master); |
||||
platform_set_drvdata(pdev, sp); |
||||
sp->info = pdata; |
||||
|
||||
err = gpio_request(pdata->pin_clk, "spi_clock"); |
||||
if (err) |
||||
goto err_request_clk; |
||||
err = gpio_request(pdata->pin_mosi, "spi_mosi"); |
||||
if (err) |
||||
goto err_request_mosi; |
||||
err = gpio_request(pdata->pin_miso, "spi_miso"); |
||||
if (err) |
||||
goto err_request_miso; |
||||
err = gpio_request(pdata->pin_cs, "spi_cs"); |
||||
if (err) |
||||
goto err_request_cs; |
||||
|
||||
sp->bitbang.master = spi_master_get(master); |
||||
sp->bitbang.master->bus_num = -1; |
||||
sp->bitbang.master->num_chipselect = 1; |
||||
sp->bitbang.chipselect = spi_gpio_chipselect; |
||||
sp->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_txrx_mode0; |
||||
sp->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_txrx_mode1; |
||||
sp->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_txrx_mode2; |
||||
sp->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_txrx_mode3; |
||||
|
||||
gpio_direction_output(pdata->pin_clk, 0); |
||||
gpio_direction_output(pdata->pin_mosi, 0); |
||||
gpio_direction_output(pdata->pin_cs, |
||||
pdata->cs_activelow ? 1 : 0); |
||||
gpio_direction_input(pdata->pin_miso); |
||||
|
||||
err = spi_bitbang_start(&sp->bitbang); |
||||
if (err) |
||||
goto err_no_bitbang; |
||||
err = pdata->boardinfo_setup(&sp->bi, master, |
||||
pdata->boardinfo_setup_data); |
||||
if (err) |
||||
goto err_bi_setup; |
||||
sp->bi.controller_data = sp; |
||||
spidev = spi_new_device(master, &sp->bi); |
||||
if (!spidev) |
||||
goto err_new_dev; |
||||
|
||||
return 0; |
||||
|
||||
err_new_dev: |
||||
err_bi_setup: |
||||
spi_bitbang_stop(&sp->bitbang); |
||||
err_no_bitbang: |
||||
spi_master_put(sp->bitbang.master); |
||||
gpio_free(pdata->pin_cs); |
||||
err_request_cs: |
||||
gpio_free(pdata->pin_miso); |
||||
err_request_miso: |
||||
gpio_free(pdata->pin_mosi); |
||||
err_request_mosi: |
||||
gpio_free(pdata->pin_clk); |
||||
err_request_clk: |
||||
kfree(master); |
||||
|
||||
err_alloc_master: |
||||
return err; |
||||
} |
||||
|
||||
static int __devexit spi_gpio_remove(struct platform_device *pdev) |
||||
{ |
||||
struct spi_gpio *sp; |
||||
struct spi_gpio_platform_data *pdata; |
||||
|
||||
pdata = pdev->dev.platform_data; |
||||
sp = platform_get_drvdata(pdev); |
||||
|
||||
gpio_free(pdata->pin_clk); |
||||
gpio_free(pdata->pin_mosi); |
||||
gpio_free(pdata->pin_miso); |
||||
gpio_free(pdata->pin_cs); |
||||
spi_bitbang_stop(&sp->bitbang); |
||||
spi_master_put(sp->bitbang.master); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct platform_driver spi_gpio_driver = { |
||||
.driver = { |
||||
.name = "spi-gpio", |
||||
.owner = THIS_MODULE, |
||||
}, |
||||
.probe = spi_gpio_probe, |
||||
.remove = __devexit_p(spi_gpio_remove), |
||||
}; |
||||
|
||||
static int __init spi_gpio_init(void) |
||||
{ |
||||
int err; |
||||
|
||||
err = platform_driver_register(&spi_gpio_driver); |
||||
if (err) |
||||
printk(KERN_ERR "spi-gpio: register failed: %d\n", err); |
||||
|
||||
return err; |
||||
} |
||||
module_init(spi_gpio_init); |
||||
|
||||
static void __exit spi_gpio_exit(void) |
||||
{ |
||||
platform_driver_unregister(&spi_gpio_driver); |
||||
} |
||||
module_exit(spi_gpio_exit); |
||||
|
||||
MODULE_AUTHOR("Piot Skamruk <piotr.skamruk at gmail.com>"); |
||||
MODULE_AUTHOR("Michael Buesch"); |
||||
MODULE_DESCRIPTION("Platform independent GPIO bitbangling SPI driver"); |
||||
MODULE_LICENSE("GPL v2"); |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue