diff options
author | David S. Miller | 2013-02-18 15:12:07 -0500 |
---|---|---|
committer | David S. Miller | 2013-02-18 15:12:07 -0500 |
commit | 40d1ae57a0eb2ea8196e15cd2d54ffc186497522 (patch) | |
tree | 4efa8aa9d2c1e8b70272aaea4f472a1c656d0998 /drivers/nfc/microread/i2c.c | |
parent | 6cf1c5fc26c6507bcb0edced6fcda876a79b5a6d (diff) | |
parent | 98d5fac2330779e6eea6431a90b44c7476260dcc (diff) |
Merge branch 'for-davem' of git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-next
John W. Linville says:
====================
This probably is the last big pull request for wireless bits
for 3.9. Of course, I'm sure there will be a few stragglers here
and there...surely a few bug fixes as well... :-) (In fact, I see
that Johannes has already queued-up a few more for me while I was
preparing this...)
Included are a number of pulls...
For mac80211-next, Johannes says:
"The biggest change I have is undoubtedly Marco's mesh powersave
implementation. Beyond that, I have a patch from Emmanuel to modify the
DTIM period API in mac80211, scan improvements and a removal of some
previous workaround code from Stanislaw, dynamic short slot time from
Thomas and 64-bit station byte counters from Vladimir. I also made a
number of changes myself, some related to WoWLAN, some auth/deauth
improvements and most of them BSS list cleanups."
"This time, I have relatively large number of fixes in various areas of
the code (a memory leak in regulatory, an RX race in mac80211, the new
radar checking caused a P2P device problem, some mesh issues with
stations, an older bug in tracing and for kernel-doc) as well as a
number of small new features. The biggest (in the diffstat) is my work
on hidden SSID tracking."
"Please pull to get
* radar detection work from Simon
* mesh improvements from Thomas
* a connection monitoring/powersave fix from Wojciech
* TDLS-related station management work from Jouni
* VLAN crypto fixes from Michael Braun
* CCK support in minstrel_ht from Felix
* an SMPS (not SMSP, oops) related improvement in mac80211 (Emmanuel)
* some WoWLAN work from Amitkumar Karwar: pattern match offset and a
documentation fix
* some WoWLAN work from myself (TCP connection wakeup feature API)
* and a lot of VHT (and some HT) work (also from myself)
And a number of more random cleanups/fixes. I merged mac80211/master to
avoid a merge problem there."
And regarding iwlwifi-next, Johannes says:
"We continue work on our new driver, but I also have a WoWLAN and AP mode
improvement for the previous driver and a change to use threaded
interrupts to prepare us for working with non-PCIe devices."
Regarding wl12xx, Luca says:
"A few more patches intended for 3.9. Mostly some clean-ups I've been
doing to make it easier to support device-tree. Also including one bug
fix for wl12xx where the rates we advertise were wrong and an update in
the wlconf structure to support newer firmwares."
For the nfc-next bits, Samuel says:
"This is the second NFC pull request for 3.9.
We have:
- A few pn533 fixes on top of Waldemar refactorization of the driver, one of
them fixes target mode.
- A new driver for Inside Secure microread chipset. It supports two
physical layers: i2c and MEI. The MEI one depends on a patchset that's
been sent to Greg Kroah-Hartman for inclusion into the 3.9 kernel [1]. The
dependency is a KConfig one which means this code is not buildable as long
as the MEI API is not usptream."
"This 3rd NFC pull request for 3.9 contains a fix for the microread MEI
physical layer support, as the MEI bus API changed.
From the MEI code, we now pass the MEI id back to the driver probe routine,
and we also pass a name and a MEI id table through the mei_bus_driver
structure. A few renames as well like e.g. mei_bus_driver to mei_driver or
mei_bus_client to mei_device in order to be closer to the driver model
practices."
For the ath6kl bits, Kalle says:
"There's not anything special here, most of the patches are just code
cleanup. The only functional changes are using the beacon interval from user
space and fixing a crash which happens when inserting and removing the
module in a loop."
Also, I pulled the wireless tree in order to resolve some pending
merge issues. On top of that, there is a bunch of work on brcmfmac
that leads up to P2P support. Also, mwifiex, rtlwifi, and a variety
of other drivers see some basic cleanups and minor enhancements.
Please let me know if there are problems!
====================
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/nfc/microread/i2c.c')
-rw-r--r-- | drivers/nfc/microread/i2c.c | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/drivers/nfc/microread/i2c.c b/drivers/nfc/microread/i2c.c new file mode 100644 index 000000000000..101089495bf8 --- /dev/null +++ b/drivers/nfc/microread/i2c.c @@ -0,0 +1,340 @@ +/* + * HCI based Driver for Inside Secure microread NFC Chip - i2c layer + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> + +#include <linux/nfc.h> +#include <net/nfc/hci.h> +#include <net/nfc/llc.h> + +#include "microread.h" + +#define MICROREAD_I2C_DRIVER_NAME "microread" + +#define MICROREAD_I2C_FRAME_HEADROOM 1 +#define MICROREAD_I2C_FRAME_TAILROOM 1 + +/* framing in HCI mode */ +#define MICROREAD_I2C_LLC_LEN 1 +#define MICROREAD_I2C_LLC_CRC 1 +#define MICROREAD_I2C_LLC_LEN_CRC (MICROREAD_I2C_LLC_LEN + \ + MICROREAD_I2C_LLC_CRC) +#define MICROREAD_I2C_LLC_MIN_SIZE (1 + MICROREAD_I2C_LLC_LEN_CRC) +#define MICROREAD_I2C_LLC_MAX_PAYLOAD 29 +#define MICROREAD_I2C_LLC_MAX_SIZE (MICROREAD_I2C_LLC_LEN_CRC + 1 + \ + MICROREAD_I2C_LLC_MAX_PAYLOAD) + +struct microread_i2c_phy { + struct i2c_client *i2c_dev; + struct nfc_hci_dev *hdev; + + int irq; + + int hard_fault; /* + * < 0 if hardware error occured (e.g. i2c err) + * and prevents normal operation. + */ +}; + +#define I2C_DUMP_SKB(info, skb) \ +do { \ + pr_debug("%s:\n", info); \ + print_hex_dump(KERN_DEBUG, "i2c: ", DUMP_PREFIX_OFFSET, \ + 16, 1, (skb)->data, (skb)->len, 0); \ +} while (0) + +static void microread_i2c_add_len_crc(struct sk_buff *skb) +{ + int i; + u8 crc = 0; + int len; + + len = skb->len; + *skb_push(skb, 1) = len; + + for (i = 0; i < skb->len; i++) + crc = crc ^ skb->data[i]; + + *skb_put(skb, 1) = crc; +} + +static void microread_i2c_remove_len_crc(struct sk_buff *skb) +{ + skb_pull(skb, MICROREAD_I2C_FRAME_HEADROOM); + skb_trim(skb, MICROREAD_I2C_FRAME_TAILROOM); +} + +static int check_crc(struct sk_buff *skb) +{ + int i; + u8 crc = 0; + + for (i = 0; i < skb->len - 1; i++) + crc = crc ^ skb->data[i]; + + if (crc != skb->data[skb->len-1]) { + pr_err(MICROREAD_I2C_DRIVER_NAME + ": CRC error 0x%x != 0x%x\n", + crc, skb->data[skb->len-1]); + + pr_info(DRIVER_DESC ": %s : BAD CRC\n", __func__); + + return -EPERM; + } + + return 0; +} + +static int microread_i2c_enable(void *phy_id) +{ + return 0; +} + +static void microread_i2c_disable(void *phy_id) +{ + return; +} + +static int microread_i2c_write(void *phy_id, struct sk_buff *skb) +{ + int r; + struct microread_i2c_phy *phy = phy_id; + struct i2c_client *client = phy->i2c_dev; + + if (phy->hard_fault != 0) + return phy->hard_fault; + + usleep_range(3000, 6000); + + microread_i2c_add_len_crc(skb); + + I2C_DUMP_SKB("i2c frame written", skb); + + r = i2c_master_send(client, skb->data, skb->len); + + if (r == -EREMOTEIO) { /* Retry, chip was in standby */ + usleep_range(6000, 10000); + r = i2c_master_send(client, skb->data, skb->len); + } + + if (r >= 0) { + if (r != skb->len) + r = -EREMOTEIO; + else + r = 0; + } + + microread_i2c_remove_len_crc(skb); + + return r; +} + + +static int microread_i2c_read(struct microread_i2c_phy *phy, + struct sk_buff **skb) +{ + int r; + u8 len; + u8 tmp[MICROREAD_I2C_LLC_MAX_SIZE - 1]; + struct i2c_client *client = phy->i2c_dev; + + pr_debug("%s\n", __func__); + + r = i2c_master_recv(client, &len, 1); + if (r != 1) { + dev_err(&client->dev, "cannot read len byte\n"); + return -EREMOTEIO; + } + + if ((len < MICROREAD_I2C_LLC_MIN_SIZE) || + (len > MICROREAD_I2C_LLC_MAX_SIZE)) { + dev_err(&client->dev, "invalid len byte\n"); + pr_err("invalid len byte\n"); + r = -EBADMSG; + goto flush; + } + + *skb = alloc_skb(1 + len, GFP_KERNEL); + if (*skb == NULL) { + r = -ENOMEM; + goto flush; + } + + *skb_put(*skb, 1) = len; + + r = i2c_master_recv(client, skb_put(*skb, len), len); + if (r != len) { + kfree_skb(*skb); + return -EREMOTEIO; + } + + I2C_DUMP_SKB("cc frame read", *skb); + + r = check_crc(*skb); + if (r != 0) { + kfree_skb(*skb); + r = -EBADMSG; + goto flush; + } + + skb_pull(*skb, 1); + skb_trim(*skb, (*skb)->len - MICROREAD_I2C_FRAME_TAILROOM); + + usleep_range(3000, 6000); + + return 0; + +flush: + if (i2c_master_recv(client, tmp, sizeof(tmp)) < 0) + r = -EREMOTEIO; + + usleep_range(3000, 6000); + + return r; +} + +static irqreturn_t microread_i2c_irq_thread_fn(int irq, void *phy_id) +{ + struct microread_i2c_phy *phy = phy_id; + struct i2c_client *client; + struct sk_buff *skb = NULL; + int r; + + if (!phy || irq != phy->i2c_dev->irq) { + WARN_ON_ONCE(1); + return IRQ_NONE; + } + + client = phy->i2c_dev; + dev_dbg(&client->dev, "IRQ\n"); + + if (phy->hard_fault != 0) + return IRQ_HANDLED; + + r = microread_i2c_read(phy, &skb); + if (r == -EREMOTEIO) { + phy->hard_fault = r; + + nfc_hci_recv_frame(phy->hdev, NULL); + + return IRQ_HANDLED; + } else if ((r == -ENOMEM) || (r == -EBADMSG)) { + return IRQ_HANDLED; + } + + nfc_hci_recv_frame(phy->hdev, skb); + + return IRQ_HANDLED; +} + +static struct nfc_phy_ops i2c_phy_ops = { + .write = microread_i2c_write, + .enable = microread_i2c_enable, + .disable = microread_i2c_disable, +}; + +static int microread_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct microread_i2c_phy *phy; + struct microread_nfc_platform_data *pdata = + dev_get_platdata(&client->dev); + int r; + + dev_dbg(&client->dev, "client %p", client); + + if (!pdata) { + dev_err(&client->dev, "client %p: missing platform data", + client); + return -EINVAL; + } + + phy = devm_kzalloc(&client->dev, sizeof(struct microread_i2c_phy), + GFP_KERNEL); + if (!phy) { + dev_err(&client->dev, "Can't allocate microread phy"); + return -ENOMEM; + } + + i2c_set_clientdata(client, phy); + phy->i2c_dev = client; + + r = request_threaded_irq(client->irq, NULL, microread_i2c_irq_thread_fn, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + MICROREAD_I2C_DRIVER_NAME, phy); + if (r) { + dev_err(&client->dev, "Unable to register IRQ handler"); + return r; + } + + r = microread_probe(phy, &i2c_phy_ops, LLC_SHDLC_NAME, + MICROREAD_I2C_FRAME_HEADROOM, + MICROREAD_I2C_FRAME_TAILROOM, + MICROREAD_I2C_LLC_MAX_PAYLOAD, &phy->hdev); + if (r < 0) + goto err_irq; + + dev_info(&client->dev, "Probed"); + + return 0; + +err_irq: + free_irq(client->irq, phy); + + return r; +} + +static int microread_i2c_remove(struct i2c_client *client) +{ + struct microread_i2c_phy *phy = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s\n", __func__); + + microread_remove(phy->hdev); + + free_irq(client->irq, phy); + + return 0; +} + +static struct i2c_device_id microread_i2c_id[] = { + { MICROREAD_I2C_DRIVER_NAME, 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, microread_i2c_id); + +static struct i2c_driver microread_i2c_driver = { + .driver = { + .name = MICROREAD_I2C_DRIVER_NAME, + }, + .probe = microread_i2c_probe, + .remove = microread_i2c_remove, + .id_table = microread_i2c_id, +}; + +module_i2c_driver(microread_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(DRIVER_DESC); |