Release 4.11 drivers/net/wireless/st/cw1200/cw1200_spi.c
/*
* 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/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 "hwbus.h"
#include <linux/platform_data/net-cw1200.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 hwbus_priv {
struct spi_device *func;
struct cw1200_common *core;
const struct cw1200_platform_data_spi *pdata;
spinlock_t lock; /* Serialize all bus operations */
wait_queue_head_t wq;
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 hwbus_priv *self,
unsigned int addr,
void *dst, int count)
{
int ret, i;
u16 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);
#ifdef SPI_DEBUG
pr_info("READ : %04d from 0x%02x (%04x)\n", count, addr, regaddr);
#endif
/* Header is LE16 */
regaddr = cpu_to_le16(regaddr);
/* We have to byteswap if the SPI bus is limited to 8b operation
or we are running on a Big Endian system
*/
#if defined(__LITTLE_ENDIAN)
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
/* We have to byteswap if the SPI bus is limited to 8b operation
or we are running on a Big Endian system
*/
#if defined(__LITTLE_ENDIAN)
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 333 | 100.00% | 3 | 100.00% |
Total | 333 | 100.00% | 3 | 100.00% |
static int cw1200_spi_memcpy_toio(struct hwbus_priv *self,
unsigned int addr,
const void *src, int count)
{
int rval, i;
u16 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);
#ifdef SPI_DEBUG
pr_info("WRITE: %04d to 0x%02x (%04x)\n", count, addr, regaddr);
#endif
/* Header is LE16 */
regaddr = cpu_to_le16(regaddr);
/* We have to byteswap if the SPI bus is limited to 8b operation
or we are running on a Big Endian system
*/
#if defined(__LITTLE_ENDIAN)
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 394 | 100.00% | 3 | 100.00% |
Total | 394 | 100.00% | 3 | 100.00% |
static void cw1200_spi_lock(struct hwbus_priv *self)
{
unsigned long flags;
DECLARE_WAITQUEUE(wait, current);
might_sleep();
add_wait_queue(&self->wq, &wait);
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);
remove_wait_queue(&self->wq, &wait);
return;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 121 | 100.00% | 3 | 100.00% |
Total | 121 | 100.00% | 3 | 100.00% |
static void cw1200_spi_unlock(struct hwbus_priv *self)
{
unsigned long flags;
spin_lock_irqsave(&self->lock, flags);
self->claimed = 0;
spin_unlock_irqrestore(&self->lock, flags);
wake_up(&self->wq);
return;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 50 | 100.00% | 3 | 100.00% |
Total | 50 | 100.00% | 3 | 100.00% |
static irqreturn_t cw1200_spi_irq_handler(int irq, void *dev_id)
{
struct hwbus_priv *self = dev_id;
if (self->core) {
cw1200_spi_lock(self);
cw1200_irq_handler(self->core);
cw1200_spi_unlock(self);
return IRQ_HANDLED;
} else {
return IRQ_NONE;
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 54 | 100.00% | 3 | 100.00% |
Total | 54 | 100.00% | 3 | 100.00% |
static int cw1200_spi_irq_subscribe(struct hwbus_priv *self)
{
int ret;
pr_debug("SW IRQ subscribe\n");
ret = request_threaded_irq(self->func->irq, NULL,
cw1200_spi_irq_handler,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 96 | 100.00% | 3 | 100.00% |
Total | 96 | 100.00% | 3 | 100.00% |
static int cw1200_spi_irq_unsubscribe(struct hwbus_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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 44 | 100.00% | 3 | 100.00% |
Total | 44 | 100.00% | 3 | 100.00% |
static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata)
{
if (pdata->reset) {
gpio_set_value(pdata->reset, 0);
msleep(30); /* Min is 2 * CLK32K cycles */
gpio_free(pdata->reset);
}
if (pdata->power_ctrl)
pdata->power_ctrl(pdata, false);
if (pdata->clk_ctrl)
pdata->clk_ctrl(pdata, false);
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 75 | 100.00% | 2 | 100.00% |
Total | 75 | 100.00% | 2 | 100.00% |
static int cw1200_spi_on(const struct cw1200_platform_data_spi *pdata)
{
/* Ensure I/Os are pulled low */
if (pdata->reset) {
gpio_request(pdata->reset, "cw1200_wlan_reset");
gpio_direction_output(pdata->reset, 0);
}
if (pdata->powerup) {
gpio_request(pdata->powerup, "cw1200_wlan_powerup");
gpio_direction_output(pdata->powerup, 0);
}
if (pdata->reset || pdata->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 (pdata->powerup) {
gpio_set_value(pdata->powerup, 1);
msleep(250); /* or more..? */
}
/* Enable RSTn signal */
if (pdata->reset) {
gpio_set_value(pdata->reset, 1);
msleep(50); /* Or more..? */
}
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 200 | 100.00% | 2 | 100.00% |
Total | 200 | 100.00% | 2 | 100.00% |
static size_t cw1200_spi_align_size(struct hwbus_priv *self, size_t size)
{
return size & 1 ? size + 1 : size;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 25 | 100.00% | 2 | 100.00% |
Total | 25 | 100.00% | 2 | 100.00% |
static int cw1200_spi_pm(struct hwbus_priv *self, bool suspend)
{
return irq_set_irq_wake(self->func->irq, suspend);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 26 | 100.00% | 2 | 100.00% |
Total | 26 | 100.00% | 2 | 100.00% |
static struct hwbus_ops cw1200_spi_hwbus_ops = {
.hwbus_memcpy_fromio = cw1200_spi_memcpy_fromio,
.hwbus_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 =
dev_get_platdata(&func->dev);
struct hwbus_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 = devm_kzalloc(&func->dev, sizeof(*self), GFP_KERNEL);
if (!self) {
pr_err("Can't allocate SPI hwbus_priv.");
return -ENOMEM;
}
self->pdata = plat_data;
self->func = func;
spin_lock_init(&self->lock);
spi_set_drvdata(func, self);
init_waitqueue_head(&self->wq);
status = cw1200_spi_irq_subscribe(self);
status = cw1200_core_probe(&cw1200_spi_hwbus_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);
}
return status;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 283 | 96.59% | 3 | 60.00% |
Himangi Saraogi | 6 | 2.05% | 1 | 20.00% |
Jingoo Han | 4 | 1.37% | 1 | 20.00% |
Total | 293 | 100.00% | 5 | 100.00% |
/* Disconnect Function to be called by SPI stack when device is disconnected */
static int cw1200_spi_disconnect(struct spi_device *func)
{
struct hwbus_priv *self = spi_get_drvdata(func);
if (self) {
cw1200_spi_irq_unsubscribe(self);
if (self->core) {
cw1200_core_release(self->core);
self->core = NULL;
}
}
cw1200_spi_off(dev_get_platdata(&func->dev));
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 63 | 94.03% | 2 | 66.67% |
Jingoo Han | 4 | 5.97% | 1 | 33.33% |
Total | 67 | 100.00% | 3 | 100.00% |
static int __maybe_unused cw1200_spi_suspend(struct device *dev)
{
struct hwbus_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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 42 | 97.67% | 2 | 66.67% |
Arnd Bergmann | 1 | 2.33% | 1 | 33.33% |
Total | 43 | 100.00% | 3 | 100.00% |
static SIMPLE_DEV_PM_OPS(cw1200_pm_ops, cw1200_spi_suspend, NULL);
static struct spi_driver spi_driver = {
.probe = cw1200_spi_probe,
.remove = cw1200_spi_disconnect,
.driver = {
.name = "cw1200_wlan_spi",
.pm = IS_ENABLED(CONFIG_PM) ? &cw1200_pm_ops : NULL,
},
};
module_spi_driver(spi_driver);
Overall Contributors
Person | Tokens | Prop | Commits | CommitProp |
Solomon Peachy | 1985 | 98.17% | 10 | 66.67% |
Lars-Peter Clausen | 12 | 0.59% | 1 | 6.67% |
Arnd Bergmann | 9 | 0.45% | 1 | 6.67% |
Jingoo Han | 8 | 0.40% | 1 | 6.67% |
Himangi Saraogi | 6 | 0.30% | 1 | 6.67% |
Wei Yongjun | 2 | 0.10% | 1 | 6.67% |
Total | 2022 | 100.00% | 15 | 100.00% |
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.