cw1200: add driver for the ST-E CW1100 & CW1200 WLAN chipsets
Signed-off-by: Solomon Peachy <pizza@shaftnet.org> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
committed by
John W. Linville
parent
5f07d15a77
commit
a910e4a94f
480
drivers/net/wireless/cw1200/cw1200_spi.c
Normal file
480
drivers/net/wireless/cw1200/cw1200_spi.c
Normal file
@@ -0,0 +1,480 @@
|
||||
/*
|
||||
* Mac80211 SPI driver for ST-Ericsson CW1200 device
|
||||
*
|
||||
* Copyright (c) 2011, Sagrad Inc.
|
||||
* Author: Solomon Peachy <speachy@sagrad.com>
|
||||
*
|
||||
* Based on cw1200_sdio.c
|
||||
* Copyright (c) 2010, ST-Ericsson
|
||||
* Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
|
||||
*
|
||||
* 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/version.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <net/mac80211.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "cw1200.h"
|
||||
#include "sbus.h"
|
||||
#include <linux/cw1200_platform.h>
|
||||
#include "hwio.h"
|
||||
|
||||
MODULE_AUTHOR("Solomon Peachy <speachy@sagrad.com>");
|
||||
MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SPI driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("spi:cw1200_wlan_spi");
|
||||
|
||||
/* #define SPI_DEBUG */
|
||||
|
||||
struct sbus_priv {
|
||||
struct spi_device *func;
|
||||
struct cw1200_common *core;
|
||||
const struct cw1200_platform_data_spi *pdata;
|
||||
spinlock_t lock; /* Serialize all bus operations */
|
||||
int claimed;
|
||||
};
|
||||
|
||||
#define SDIO_TO_SPI_ADDR(addr) ((addr & 0x1f)>>2)
|
||||
#define SET_WRITE 0x7FFF /* usage: and operation */
|
||||
#define SET_READ 0x8000 /* usage: or operation */
|
||||
|
||||
/*
|
||||
Notes on byte ordering:
|
||||
LE: B0 B1 B2 B3
|
||||
BE: B3 B2 B1 B0
|
||||
|
||||
Hardware expects 32-bit data to be written as 16-bit BE words:
|
||||
|
||||
B1 B0 B3 B2
|
||||
|
||||
*/
|
||||
|
||||
static int cw1200_spi_memcpy_fromio(struct sbus_priv *self,
|
||||
unsigned int addr,
|
||||
void *dst, int count)
|
||||
{
|
||||
int ret, i;
|
||||
uint16_t regaddr;
|
||||
struct spi_message m;
|
||||
|
||||
struct spi_transfer t_addr = {
|
||||
.tx_buf = ®addr,
|
||||
.len = sizeof(regaddr),
|
||||
};
|
||||
struct spi_transfer t_msg = {
|
||||
.rx_buf = dst,
|
||||
.len = count,
|
||||
};
|
||||
|
||||
regaddr = (SDIO_TO_SPI_ADDR(addr))<<12;
|
||||
regaddr |= SET_READ;
|
||||
regaddr |= (count>>1);
|
||||
regaddr = cpu_to_le16(regaddr);
|
||||
|
||||
#ifdef SPI_DEBUG
|
||||
pr_info("READ : %04d from 0x%02x (%04x)\n", count, addr,
|
||||
le16_to_cpu(regaddr));
|
||||
#endif
|
||||
|
||||
#if defined(__LITTLE_ENDIAN)
|
||||
/* We have to byteswap if the SPI bus is limited to 8b operation */
|
||||
if (self->func->bits_per_word == 8)
|
||||
#endif
|
||||
regaddr = swab16(regaddr);
|
||||
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t_addr, &m);
|
||||
spi_message_add_tail(&t_msg, &m);
|
||||
ret = spi_sync(self->func, &m);
|
||||
|
||||
#ifdef SPI_DEBUG
|
||||
pr_info("READ : ");
|
||||
for (i = 0; i < t_addr.len; i++)
|
||||
printk("%02x ", ((u8 *)t_addr.tx_buf)[i]);
|
||||
printk(" : ");
|
||||
for (i = 0; i < t_msg.len; i++)
|
||||
printk("%02x ", ((u8 *)t_msg.rx_buf)[i]);
|
||||
printk("\n");
|
||||
#endif
|
||||
|
||||
#if defined(__LITTLE_ENDIAN)
|
||||
/* We have to byteswap if the SPI bus is limited to 8b operation */
|
||||
if (self->func->bits_per_word == 8)
|
||||
#endif
|
||||
{
|
||||
uint16_t *buf = (uint16_t *)dst;
|
||||
for (i = 0; i < ((count + 1) >> 1); i++)
|
||||
buf[i] = swab16(buf[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cw1200_spi_memcpy_toio(struct sbus_priv *self,
|
||||
unsigned int addr,
|
||||
const void *src, int count)
|
||||
{
|
||||
int rval, i;
|
||||
uint16_t regaddr;
|
||||
struct spi_transfer t_addr = {
|
||||
.tx_buf = ®addr,
|
||||
.len = sizeof(regaddr),
|
||||
};
|
||||
struct spi_transfer t_msg = {
|
||||
.tx_buf = src,
|
||||
.len = count,
|
||||
};
|
||||
struct spi_message m;
|
||||
|
||||
regaddr = (SDIO_TO_SPI_ADDR(addr))<<12;
|
||||
regaddr &= SET_WRITE;
|
||||
regaddr |= (count>>1);
|
||||
regaddr = cpu_to_le16(regaddr);
|
||||
|
||||
#ifdef SPI_DEBUG
|
||||
pr_info("WRITE: %04d to 0x%02x (%04x)\n", count, addr,
|
||||
le16_to_cpu(regaddr));
|
||||
#endif
|
||||
|
||||
#if defined(__LITTLE_ENDIAN)
|
||||
/* We have to byteswap if the SPI bus is limited to 8b operation */
|
||||
if (self->func->bits_per_word == 8)
|
||||
#endif
|
||||
{
|
||||
uint16_t *buf = (uint16_t *)src;
|
||||
regaddr = swab16(regaddr);
|
||||
for (i = 0; i < ((count + 1) >> 1); i++)
|
||||
buf[i] = swab16(buf[i]);
|
||||
}
|
||||
|
||||
#ifdef SPI_DEBUG
|
||||
pr_info("WRITE: ");
|
||||
for (i = 0; i < t_addr.len; i++)
|
||||
printk("%02x ", ((u8 *)t_addr.tx_buf)[i]);
|
||||
printk(" : ");
|
||||
for (i = 0; i < t_msg.len; i++)
|
||||
printk("%02x ", ((u8 *)t_msg.tx_buf)[i]);
|
||||
printk("\n");
|
||||
#endif
|
||||
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t_addr, &m);
|
||||
spi_message_add_tail(&t_msg, &m);
|
||||
rval = spi_sync(self->func, &m);
|
||||
|
||||
#ifdef SPI_DEBUG
|
||||
pr_info("WROTE: %d\n", m.actual_length);
|
||||
#endif
|
||||
|
||||
#if defined(__LITTLE_ENDIAN)
|
||||
/* We have to byteswap if the SPI bus is limited to 8b operation */
|
||||
if (self->func->bits_per_word == 8)
|
||||
#endif
|
||||
{
|
||||
uint16_t *buf = (uint16_t *)src;
|
||||
for (i = 0; i < ((count + 1) >> 1); i++)
|
||||
buf[i] = swab16(buf[i]);
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
static void cw1200_spi_lock(struct sbus_priv *self)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
might_sleep();
|
||||
|
||||
spin_lock_irqsave(&self->lock, flags);
|
||||
while (1) {
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
if (!self->claimed)
|
||||
break;
|
||||
spin_unlock_irqrestore(&self->lock, flags);
|
||||
schedule();
|
||||
spin_lock_irqsave(&self->lock, flags);
|
||||
}
|
||||
set_current_state(TASK_RUNNING);
|
||||
self->claimed = 1;
|
||||
spin_unlock_irqrestore(&self->lock, flags);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void cw1200_spi_unlock(struct sbus_priv *self)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&self->lock, flags);
|
||||
self->claimed = 0;
|
||||
spin_unlock_irqrestore(&self->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
static irqreturn_t cw1200_spi_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct sbus_priv *self = dev_id;
|
||||
|
||||
if (self->core) {
|
||||
cw1200_irq_handler(self->core);
|
||||
return IRQ_HANDLED;
|
||||
} else {
|
||||
return IRQ_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static int cw1200_spi_irq_subscribe(struct sbus_priv *self)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pr_debug("SW IRQ subscribe\n");
|
||||
|
||||
ret = request_any_context_irq(self->func->irq, cw1200_spi_irq_handler,
|
||||
IRQF_TRIGGER_HIGH,
|
||||
"cw1200_wlan_irq", self);
|
||||
if (WARN_ON(ret < 0))
|
||||
goto exit;
|
||||
|
||||
ret = enable_irq_wake(self->func->irq);
|
||||
if (WARN_ON(ret))
|
||||
goto free_irq;
|
||||
|
||||
return 0;
|
||||
|
||||
free_irq:
|
||||
free_irq(self->func->irq, self);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cw1200_spi_irq_unsubscribe(struct sbus_priv *self)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("SW IRQ unsubscribe\n");
|
||||
disable_irq_wake(self->func->irq);
|
||||
free_irq(self->func->irq, self);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata)
|
||||
{
|
||||
const struct resource *reset = pdata->reset;
|
||||
|
||||
if (reset) {
|
||||
gpio_set_value(reset->start, 0);
|
||||
msleep(30); /* Min is 2 * CLK32K cycles */
|
||||
gpio_free(reset->start);
|
||||
}
|
||||
|
||||
if (pdata->power_ctrl)
|
||||
pdata->power_ctrl(pdata, false);
|
||||
if (pdata->clk_ctrl)
|
||||
pdata->clk_ctrl(pdata, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cw1200_spi_on(const struct cw1200_platform_data_spi *pdata)
|
||||
{
|
||||
const struct resource *reset = pdata->reset;
|
||||
const struct resource *powerup = pdata->reset;
|
||||
|
||||
/* Ensure I/Os are pulled low */
|
||||
if (reset) {
|
||||
gpio_request(reset->start, reset->name);
|
||||
gpio_direction_output(reset->start, 0);
|
||||
}
|
||||
if (powerup) {
|
||||
gpio_request(powerup->start, powerup->name);
|
||||
gpio_direction_output(powerup->start, 0);
|
||||
}
|
||||
if (reset || powerup)
|
||||
msleep(10); /* Settle time? */
|
||||
|
||||
/* Enable 3v3 and 1v8 to hardware */
|
||||
if (pdata->power_ctrl) {
|
||||
if (pdata->power_ctrl(pdata, true)) {
|
||||
pr_err("power_ctrl() failed!\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable CLK32K */
|
||||
if (pdata->clk_ctrl) {
|
||||
if (pdata->clk_ctrl(pdata, true)) {
|
||||
pr_err("clk_ctrl() failed!\n");
|
||||
return -1;
|
||||
}
|
||||
msleep(10); /* Delay until clock is stable for 2 cycles */
|
||||
}
|
||||
|
||||
/* Enable POWERUP signal */
|
||||
if (powerup) {
|
||||
gpio_set_value(powerup->start, 1);
|
||||
msleep(250); /* or more..? */
|
||||
}
|
||||
/* Enable RSTn signal */
|
||||
if (reset) {
|
||||
gpio_set_value(reset->start, 1);
|
||||
msleep(50); /* Or more..? */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t cw1200_spi_align_size(struct sbus_priv *self, size_t size)
|
||||
{
|
||||
return size & 1 ? size + 1 : size;
|
||||
}
|
||||
|
||||
static int cw1200_spi_pm(struct sbus_priv *self, bool suspend)
|
||||
{
|
||||
return irq_set_irq_wake(self->func->irq, suspend);
|
||||
}
|
||||
|
||||
static struct sbus_ops cw1200_spi_sbus_ops = {
|
||||
.sbus_memcpy_fromio = cw1200_spi_memcpy_fromio,
|
||||
.sbus_memcpy_toio = cw1200_spi_memcpy_toio,
|
||||
.lock = cw1200_spi_lock,
|
||||
.unlock = cw1200_spi_unlock,
|
||||
.align_size = cw1200_spi_align_size,
|
||||
.power_mgmt = cw1200_spi_pm,
|
||||
};
|
||||
|
||||
/* Probe Function to be called by SPI stack when device is discovered */
|
||||
static int cw1200_spi_probe(struct spi_device *func)
|
||||
{
|
||||
const struct cw1200_platform_data_spi *plat_data =
|
||||
func->dev.platform_data;
|
||||
struct sbus_priv *self;
|
||||
int status;
|
||||
|
||||
/* Sanity check speed */
|
||||
if (func->max_speed_hz > 52000000)
|
||||
func->max_speed_hz = 52000000;
|
||||
if (func->max_speed_hz < 1000000)
|
||||
func->max_speed_hz = 1000000;
|
||||
|
||||
/* Fix up transfer size */
|
||||
if (plat_data->spi_bits_per_word)
|
||||
func->bits_per_word = plat_data->spi_bits_per_word;
|
||||
if (!func->bits_per_word)
|
||||
func->bits_per_word = 16;
|
||||
|
||||
/* And finally.. */
|
||||
func->mode = SPI_MODE_0;
|
||||
|
||||
pr_info("cw1200_wlan_spi: Probe called (CS %d M %d BPW %d CLK %d)\n",
|
||||
func->chip_select, func->mode, func->bits_per_word,
|
||||
func->max_speed_hz);
|
||||
|
||||
if (cw1200_spi_on(plat_data)) {
|
||||
pr_err("spi_on() failed!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (spi_setup(func)) {
|
||||
pr_err("spi_setup() failed!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
self = kzalloc(sizeof(*self), GFP_KERNEL);
|
||||
if (!self) {
|
||||
pr_err("Can't allocate SPI sbus_priv.");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
self->pdata = plat_data;
|
||||
self->func = func;
|
||||
spin_lock_init(&self->lock);
|
||||
|
||||
spi_set_drvdata(func, self);
|
||||
|
||||
status = cw1200_spi_irq_subscribe(self);
|
||||
|
||||
status = cw1200_core_probe(&cw1200_spi_sbus_ops,
|
||||
self, &func->dev, &self->core,
|
||||
self->pdata->ref_clk,
|
||||
self->pdata->macaddr,
|
||||
self->pdata->sdd_file,
|
||||
self->pdata->have_5ghz);
|
||||
|
||||
if (status) {
|
||||
cw1200_spi_irq_unsubscribe(self);
|
||||
cw1200_spi_off(plat_data);
|
||||
kfree(self);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Disconnect Function to be called by SPI stack when device is disconnected */
|
||||
static int cw1200_spi_disconnect(struct spi_device *func)
|
||||
{
|
||||
struct sbus_priv *self = spi_get_drvdata(func);
|
||||
|
||||
if (self) {
|
||||
cw1200_spi_irq_unsubscribe(self);
|
||||
if (self->core) {
|
||||
cw1200_core_release(self->core);
|
||||
self->core = NULL;
|
||||
}
|
||||
kfree(self);
|
||||
}
|
||||
cw1200_spi_off(func->dev.platform_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cw1200_spi_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct sbus_priv *self = spi_get_drvdata(to_spi_device(dev));
|
||||
|
||||
if (!cw1200_can_suspend(self->core))
|
||||
return -EAGAIN;
|
||||
|
||||
/* XXX notify host that we have to keep CW1200 powered on? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cw1200_spi_resume(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver spi_driver = {
|
||||
.probe = cw1200_spi_probe,
|
||||
.remove = cw1200_spi_disconnect,
|
||||
.driver = {
|
||||
.name = "cw1200_wlan_spi",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
.suspend = cw1200_spi_suspend,
|
||||
.resume = cw1200_spi_resume,
|
||||
},
|
||||
};
|
||||
|
||||
/* Init Module function -> Called by insmod */
|
||||
static int __init cw1200_spi_init(void)
|
||||
{
|
||||
return spi_register_driver(&spi_driver);
|
||||
}
|
||||
|
||||
/* Called at Driver Unloading */
|
||||
static void __exit cw1200_spi_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&spi_driver);
|
||||
}
|
||||
|
||||
module_init(cw1200_spi_init);
|
||||
module_exit(cw1200_spi_exit);
|
||||
Reference in New Issue
Block a user