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