// SPDX-License-Identifier: GPL-2.0-only
/*
* atusb.c - Driver for the ATUSB IEEE 802.15.4 dongle
*
* Written 2013 by Werner Almesberger <werner@almesberger.net>
*
* Copyright (c) 2015 - 2016 Stefan Schmidt <stefan@datenfreihafen.org>
*
* Based on at86rf230.c and spi_atusb.c.
* at86rf230.c is
* Copyright (C) 2009 Siemens AG
* Written by: Dmitry Eremin-Solenikov <dmitry.baryshkov@siemens.com>
*
* spi_atusb.c is
* Copyright (c) 2011 Richard Sharpe <realrichardsharpe@gmail.com>
* Copyright (c) 2011 Stefan Schmidt <stefan@datenfreihafen.org>
* Copyright (c) 2011 Werner Almesberger <werner@almesberger.net>
*
* USB initialization is
* Copyright (c) 2013 Alexander Aring <alex.aring@gmail.com>
*
* Busware HUL support is
* Copyright (c) 2017 Josef Filzmaier <j.filzmaier@gmx.at>
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/jiffies.h>
#include <linux/usb.h>
#include <linux/skbuff.h>
#include <net/cfg802154.h>
#include <net/mac802154.h>
#include "at86rf230.h"
#include "atusb.h"
#define ATUSB_JEDEC_ATMEL 0x1f /* JEDEC manufacturer ID */
#define ATUSB_NUM_RX_URBS 4 /* allow for a bit of local latency */
#define ATUSB_ALLOC_DELAY_MS 100 /* delay after failed allocation */
#define ATUSB_TX_TIMEOUT_MS 200 /* on the air timeout */
struct atusb {
struct ieee802154_hw *hw;
struct usb_device *usb_dev;
struct atusb_chip_data *data;
int shutdown; /* non-zero if shutting down */
int err; /* set by first error */
/* RX variables */
struct delayed_work work; /* memory allocations */
struct usb_anchor idle_urbs; /* URBs waiting to be submitted */
struct usb_anchor rx_urbs; /* URBs waiting for reception */
/* TX variables */
struct usb_ctrlrequest tx_dr;
struct urb *tx_urb;
struct sk_buff *tx_skb;
u8 tx_ack_seq; /* current TX ACK sequence number */
/* Firmware variable */
unsigned char fw_ver_maj; /* Firmware major version number */
unsigned char fw_ver_min; /* Firmware minor version number */
unsigned char fw_hw_type; /* Firmware hardware type */
};
struct atusb_chip_data {
u16 t_channel_switch;
int rssi_base_val;
int (*set_channel)(struct ieee802154_hw*, u8, u8);
int (*set_txpower)(struct ieee802154_hw*, s32);
};
static int atusb_write_subreg(struct atusb *atusb, u8 reg, u8 mask,
u8 shift, u8 value)
{
struct usb_device *usb_dev = atusb->usb_dev;
u8 orig, tmp;
int ret = 0;
dev_dbg(&usb_dev->dev, "%s: 0x%02x <- 0x%02x\n", __func__, reg, value);
ret = usb_control_msg_recv(usb_dev, 0, ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
0, reg, &orig, 1, 1000, GFP_KERNEL);
if (ret < 0)
return ret;
/* Write the value only into that part of the register which is allowed
* by the mask. All other bits stay as before.
*/
tmp = orig & ~mask;
tmp |= (value << shift) & mask;
if (tmp != orig)
ret = usb_control_msg_send(usb_dev, 0, ATUSB_REG_WRITE, ATUSB_REQ_TO_DEV,
tmp, reg, NULL, 0, 1000, GFP_KERNEL);
return ret;
}
static int atusb_read_subreg(struct atusb *lp,
unsigned int addr, unsigned int mask,
unsigned int shift)
{
int reg, ret;
ret = usb_control_msg_recv(lp->usb_dev, 0, ATUSB_REG_READ, ATUSB_REQ_FROM_DEV,
0, addr, ®, 1, 1000, GFP_KERNEL);
if (ret < 0)
return ret;
reg = (reg & mask) >> shift;
return reg;
}
static int atusb_get_and_clear_error(struct atusb *atusb)
{
int err = atusb->err;
atusb->err = 0;
return err;
}
/* ----- skb allocation ---------------------------------------------------- */
#define MAX_PSDU 127
#define MAX_RX_XFER (1 + MAX_PSDU + 2 + 1) /* PHR+PSDU+CRC+LQI */
#define SKB_ATUSB(skb) (*(struct atusb **)(skb)->cb)
static void atusb_in(struct urb *urb);
static int atusb_submit_rx_urb(struct atusb *atusb, struct urb *urb)
{
struct usb_device *usb_dev = atusb->usb_dev;
struct sk_buff *skb = urb->context;
int ret;
if (!skb) {
skb = alloc_skb(MAX_RX_XFER, GFP_KERNEL);
if (!skb) {
dev_warn_ratelimited(&usb_dev->dev,
"atusb_in: can't allocate skb\n");
return -ENOMEM;
}
skb_put(skb, MAX_RX_XFER);
SKB_ATUSB(skb) = atusb;
}
usb_fill_bulk_urb(urb, usb_dev, usb_rcvbulkpipe(usb_dev, 1),
skb->data, MAX_RX_XFER, atusb_in, skb);
usb_anchor_urb(urb, &atusb->rx_urbs);
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
usb_unanchor_urb(urb);
kfree_skb(skb);
urb->context = NULL;
}
return ret;
}