Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1ba47da5 authored by Inaky Perez-Gonzalez's avatar Inaky Perez-Gonzalez Committed by David Vrabel
Browse files

uwb: add the i1480 DFU driver



Add the driver for downloading the firmware to an Intel i1480 device.

Signed-off-by: default avatarDavid Vrabel <david.vrabel@csr.com>
parent 3b0c5a38
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -64,4 +64,16 @@ config UWB_WLP
	  This is a common library for drivers that implement
	  networking over UWB.

config UWB_I1480U
        tristate "Support for Intel Wireless UWB Link 1480 HWA"
        depends on UWB_HWA
        select FW_LOADER
        help
         This driver enables support for the i1480 when connected via
         USB. It consists of a firmware uploader that will enable it
         to behave as an HWA device.

         To compile this driver select Y (built in) or M (module). It
         is safe to select any even if you do not have the hardware.

endif # UWB
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ obj-$(CONFIG_UWB) += uwb.o
obj-$(CONFIG_UWB_WLP)		+= wlp/
obj-$(CONFIG_UWB_WHCI)		+= umc.o whci.o whc-rc.o
obj-$(CONFIG_UWB_HWA)		+= hwa-rc.o
obj-$(CONFIG_UWB_I1480U)	+= i1480/

uwb-objs :=		\
	address.o	\
+1 −0
Original line number Diff line number Diff line
obj-$(CONFIG_UWB_I1480U)	+= dfu/ i1480-est.o
+9 −0
Original line number Diff line number Diff line
obj-$(CONFIG_UWB_I1480U)	+= i1480-dfu-usb.o

i1480-dfu-usb-objs := \
	dfu.o 	\
	mac.o	\
	phy.o	\
	usb.o

+281 −0
Original line number Diff line number Diff line
/*
 * Intel Wireless UWB Link 1480
 * Main driver
 *
 * Copyright (C) 2005-2006 Intel Corporation
 * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
 *
 * 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.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 *
 * Common code for firmware upload used by the USB and PCI version;
 * i1480_fw_upload() takes a device descriptor and uses the function
 * pointers it provides to upload firmware and prepare the PHY.
 *
 * As well, provides common functions used by the rest of the code.
 */
#include "i1480-dfu.h"
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/uwb.h>
#include <linux/random.h>

#define D_LOCAL 0
#include <linux/uwb/debug.h>

/** @return 0 if If @evt is a valid reply event; otherwise complain */
int i1480_rceb_check(const struct i1480 *i1480, const struct uwb_rceb *rceb,
		     const char *cmd, u8 context,
		     unsigned expected_type, unsigned expected_event)
{
	int result = 0;
	struct device *dev = i1480->dev;
	if (rceb->bEventContext != context) {
		dev_err(dev, "%s: "
			"unexpected context id 0x%02x (expected 0x%02x)\n",
			cmd, rceb->bEventContext, context);
		result = -EINVAL;
	}
	if (rceb->bEventType != expected_type) {
		dev_err(dev, "%s: "
			"unexpected event type 0x%02x (expected 0x%02x)\n",
			cmd, rceb->bEventType, expected_type);
		result = -EINVAL;
	}
	if (le16_to_cpu(rceb->wEvent) != expected_event) {
		dev_err(dev, "%s: "
			"unexpected event 0x%04x (expected 0x%04x)\n",
			cmd, le16_to_cpu(rceb->wEvent), expected_event);
		result = -EINVAL;
	}
	return result;
}
EXPORT_SYMBOL_GPL(i1480_rceb_check);


/**
 * Execute a Radio Control Command
 *
 * Command data has to be in i1480->cmd_buf.
 *
 * @returns size of the reply data filled in i1480->evt_buf or < 0 errno
 *          code on error.
 */
ssize_t i1480_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size,
		  size_t reply_size)
{
	ssize_t result;
	struct uwb_rceb *reply = i1480->evt_buf;
	struct uwb_rccb *cmd = i1480->cmd_buf;
	u16 expected_event = reply->wEvent;
	u8 expected_type = reply->bEventType;
	u8 context;

	d_fnstart(3, i1480->dev, "(%p, %s, %zu)\n", i1480, cmd_name, cmd_size);
	init_completion(&i1480->evt_complete);
	i1480->evt_result = -EINPROGRESS;
	do {
		get_random_bytes(&context, 1);
	} while (context == 0x00 || context == 0xff);
	cmd->bCommandContext = context;
	result = i1480->cmd(i1480, cmd_name, cmd_size);
	if (result < 0)
		goto error;
	/* wait for the callback to report a event was received */
	result = wait_for_completion_interruptible_timeout(
		&i1480->evt_complete, HZ);
	if (result == 0) {
		result = -ETIMEDOUT;
		goto error;
	}
	if (result < 0)
		goto error;
	result = i1480->evt_result;
	if (result < 0) {
		dev_err(i1480->dev, "%s: command reply reception failed: %zd\n",
			cmd_name, result);
		goto error;
	}
	if (result != reply_size) {
		dev_err(i1480->dev, "%s returned only %zu bytes, %zu expected\n",
			cmd_name, result, reply_size);
		result = -EINVAL;
		goto error;
	}
	/* Verify we got the right event in response */
	result = i1480_rceb_check(i1480, i1480->evt_buf, cmd_name, context,
				  expected_type, expected_event);
error:
	d_fnend(3, i1480->dev, "(%p, %s, %zu) = %zd\n",
		i1480, cmd_name, cmd_size, result);
	return result;
}
EXPORT_SYMBOL_GPL(i1480_cmd);


/**
 * Get information about the MAC and PHY
 *
 * @wa:      Wired adaptor
 * @neh:     Notification/event handler
 * @reply:   Pointer to the reply event buffer
 * @returns: 0 if ok, < 0 errno code on error.
 */
static
int i1480_cmd_get_mac_phy_info(struct i1480 *i1480)
{
	int result;
	struct uwb_rccb *cmd = i1480->cmd_buf;
	struct i1480_evt_confirm_GMPI *reply = i1480->evt_buf;

	cmd->bCommandType = i1480_CET_VS1;
	cmd->wCommand = cpu_to_le16(i1480_CMD_GET_MAC_PHY_INFO);
	reply->rceb.bEventType = i1480_CET_VS1;
	reply->rceb.wEvent = i1480_EVT_GET_MAC_PHY_INFO;
	result = i1480_cmd(i1480, "GET_MAC_PHY_INFO", sizeof(*cmd),
			   sizeof(*reply));
	if (result < 0)
		goto out;
	if (le16_to_cpu(reply->status) != 0x00) {
		dev_err(i1480->dev,
			"GET_MAC_PHY_INFO: command execution failed: %d\n",
			reply->status);
		result = -EIO;
	}
out:
	return result;
}


/**
 * Get i1480's info and print it
 *
 * @wa:      Wire Adapter
 * @neh:     Notification/event handler
 * @returns: 0 if ok, < 0 errno code on error.
 */
static
int i1480_check_info(struct i1480 *i1480)
{
	struct i1480_evt_confirm_GMPI *reply = i1480->evt_buf;
	int result;
	unsigned mac_fw_rev;
#if i1480_FW <= 0x00000302
	unsigned phy_fw_rev;
#endif
	if (i1480->quirk_no_check_info) {
		dev_err(i1480->dev, "firmware info check disabled\n");
		return 0;
	}

	result = i1480_cmd_get_mac_phy_info(i1480);
	if (result < 0) {
		dev_err(i1480->dev, "Cannot get MAC & PHY information: %d\n",
			result);
		goto out;
	}
	mac_fw_rev = le16_to_cpu(reply->mac_fw_rev);
#if i1480_FW > 0x00000302
	dev_info(i1480->dev,
		 "HW v%02hx  "
		 "MAC FW v%02hx.%02hx caps %04hx  "
		 "PHY type %02hx v%02hx caps %02hx %02hx %02hx\n",
		 reply->hw_rev, mac_fw_rev >> 8, mac_fw_rev & 0xff,
		 le16_to_cpu(reply->mac_caps),
		 reply->phy_vendor, reply->phy_rev,
		 reply->phy_caps[0], reply->phy_caps[1], reply->phy_caps[2]);
#else
	phy_fw_rev = le16_to_cpu(reply->phy_fw_rev);
	dev_info(i1480->dev, "MAC FW v%02hx.%02hx caps %04hx "
		 " PHY FW v%02hx.%02hx caps %04hx\n",
		 mac_fw_rev >> 8, mac_fw_rev & 0xff,
		 le16_to_cpu(reply->mac_caps),
		 phy_fw_rev >> 8, phy_fw_rev & 0xff,
		 le16_to_cpu(reply->phy_caps));
#endif
	dev_dbg(i1480->dev,
		"key-stores:%hu mcast-addr-stores:%hu sec-modes:%hu\n",
		(unsigned short) reply->key_stores,
		le16_to_cpu(reply->mcast_addr_stores),
		(unsigned short) reply->sec_mode_supported);
	/* FIXME: complain if fw version too low -- pending for
	 * numbering to stabilize */
out:
	return result;
}


static
int i1480_print_state(struct i1480 *i1480)
{
	int result;
	u32 *buf = (u32 *) i1480->cmd_buf;

	result = i1480->read(i1480, 0x80080000, 2 * sizeof(*buf));
	if (result < 0) {
		dev_err(i1480->dev, "cannot read U & L states: %d\n", result);
		goto error;
	}
	dev_info(i1480->dev, "state U 0x%08x, L 0x%08x\n", buf[0], buf[1]);
error:
	return result;
}


/*
 * PCI probe, firmware uploader
 *
 * _mac_fw_upload() will call rc_setup(), which needs an rc_release().
 */
int i1480_fw_upload(struct i1480 *i1480)
{
	int result;

	result = i1480_pre_fw_upload(i1480);	/* PHY pre fw */
	if (result < 0 && result != -ENOENT) {
		i1480_print_state(i1480);
		goto error;
	}
	result = i1480_mac_fw_upload(i1480);	/* MAC fw */
	if (result < 0) {
		if (result == -ENOENT)
			dev_err(i1480->dev, "Cannot locate MAC FW file '%s'\n",
				i1480->mac_fw_name);
		else
			i1480_print_state(i1480);
		goto error;
	}
	result = i1480_phy_fw_upload(i1480);	/* PHY fw */
	if (result < 0 && result != -ENOENT) {
		i1480_print_state(i1480);
		goto error_rc_release;
	}
	result = i1480_check_info(i1480);
	if (result < 0) {
		dev_warn(i1480->dev, "Warning! Cannot check firmware info: %d\n",
			 result);
		result = 0;
	}
	dev_info(i1480->dev, "firmware uploaded successfully\n");
error_rc_release:
	if (i1480->rc_release)
		i1480->rc_release(i1480);
	result = 0;
error:
	return result;
}
EXPORT_SYMBOL_GPL(i1480_fw_upload);
Loading