OpenCloudOS-Kernel/drivers/nfc/fdp/fdp.c

755 lines
18 KiB
C
Raw Normal View History

treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 157 Based on 3 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version 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 this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] 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 this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-27 14:55:06 +08:00
// SPDX-License-Identifier: GPL-2.0-or-later
/* -------------------------------------------------------------------------
* Copyright (C) 2014-2016, Intel Corporation
*
* -------------------------------------------------------------------------
*/
#include <linux/module.h>
#include <linux/nfc.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <net/nfc/nci_core.h>
#include "fdp.h"
#define FDP_OTP_PATCH_NAME "otp.bin"
#define FDP_RAM_PATCH_NAME "ram.bin"
#define FDP_FW_HEADER_SIZE 576
#define FDP_FW_UPDATE_SLEEP 1000
#define NCI_GET_VERSION_TIMEOUT 8000
#define NCI_PATCH_REQUEST_TIMEOUT 8000
#define FDP_PATCH_CONN_DEST 0xC2
#define FDP_PATCH_CONN_PARAM_TYPE 0xA0
#define NCI_PATCH_TYPE_RAM 0x00
#define NCI_PATCH_TYPE_OTP 0x01
#define NCI_PATCH_TYPE_EOT 0xFF
#define NCI_PARAM_ID_FW_RAM_VERSION 0xA0
#define NCI_PARAM_ID_FW_OTP_VERSION 0xA1
#define NCI_PARAM_ID_OTP_LIMITED_VERSION 0xC5
#define NCI_PARAM_ID_KEY_INDEX_ID 0xC6
#define NCI_GID_PROP 0x0F
#define NCI_OP_PROP_PATCH_OID 0x08
#define NCI_OP_PROP_SET_PDATA_OID 0x23
struct fdp_nci_info {
struct nfc_phy_ops *phy_ops;
struct fdp_i2c_phy *phy;
struct nci_dev *ndev;
const struct firmware *otp_patch;
const struct firmware *ram_patch;
u32 otp_patch_version;
u32 ram_patch_version;
u32 otp_version;
u32 ram_version;
u32 limited_otp_version;
u8 key_index;
u8 *fw_vsc_cfg;
u8 clock_type;
u32 clock_freq;
atomic_t data_pkt_counter;
void (*data_pkt_counter_cb)(struct nci_dev *ndev);
u8 setup_patch_sent;
u8 setup_patch_ntf;
u8 setup_patch_status;
u8 setup_reset_ntf;
wait_queue_head_t setup_wq;
};
static u8 nci_core_get_config_otp_ram_version[5] = {
0x04,
NCI_PARAM_ID_FW_RAM_VERSION,
NCI_PARAM_ID_FW_OTP_VERSION,
NCI_PARAM_ID_OTP_LIMITED_VERSION,
NCI_PARAM_ID_KEY_INDEX_ID
};
struct nci_core_get_config_rsp {
u8 status;
u8 count;
u8 data[];
};
static int fdp_nci_create_conn(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct core_conn_create_dest_spec_params param;
int r;
/* proprietary destination specific paramerer without value */
param.type = FDP_PATCH_CONN_PARAM_TYPE;
param.length = 0x00;
r = nci_core_conn_create(info->ndev, FDP_PATCH_CONN_DEST, 1,
sizeof(param), &param);
if (r)
return r;
return nci_get_conn_info_by_dest_type_params(ndev,
FDP_PATCH_CONN_DEST, NULL);
}
static inline int fdp_nci_get_versions(struct nci_dev *ndev)
{
return nci_core_cmd(ndev, NCI_OP_CORE_GET_CONFIG_CMD,
sizeof(nci_core_get_config_otp_ram_version),
(__u8 *) &nci_core_get_config_otp_ram_version);
}
static inline int fdp_nci_patch_cmd(struct nci_dev *ndev, u8 type)
{
return nci_prop_cmd(ndev, NCI_OP_PROP_PATCH_OID, sizeof(type), &type);
}
static inline int fdp_nci_set_production_data(struct nci_dev *ndev, u8 len,
char *data)
{
return nci_prop_cmd(ndev, NCI_OP_PROP_SET_PDATA_OID, len, data);
}
static int fdp_nci_set_clock(struct nci_dev *ndev, u8 clock_type,
u32 clock_freq)
{
u32 fc = 13560;
u32 nd, num, delta;
char data[9];
nd = (24 * fc) / clock_freq;
delta = 24 * fc - nd * clock_freq;
num = (32768 * delta) / clock_freq;
data[0] = 0x00;
data[1] = 0x00;
data[2] = 0x00;
data[3] = 0x10;
data[4] = 0x04;
data[5] = num & 0xFF;
data[6] = (num >> 8) & 0xff;
data[7] = nd;
data[8] = clock_type;
return fdp_nci_set_production_data(ndev, 9, data);
}
static void fdp_nci_send_patch_cb(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
info->setup_patch_sent = 1;
wake_up(&info->setup_wq);
}
/*
* Register a packet sent counter and a callback
*
* We have no other way of knowing when all firmware packets were sent out
* on the i2c bus. We need to know that in order to close the connection and
* send the patch end message.
*/
static void fdp_nci_set_data_pkt_counter(struct nci_dev *ndev,
void (*cb)(struct nci_dev *ndev), int count)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
dev_dbg(dev, "NCI data pkt counter %d\n", count);
atomic_set(&info->data_pkt_counter, count);
info->data_pkt_counter_cb = cb;
}
/*
* The device is expecting a stream of packets. All packets need to
* have the PBF flag set to 0x0 (last packet) even if the firmware
* file is segmented and there are multiple packets. If we give the
* whole firmware to nci_send_data it will segment it and it will set
* the PBF flag to 0x01 so we need to do the segmentation here.
*
* The firmware will be analyzed and applied when we send NCI_OP_PROP_PATCH_CMD
* command with NCI_PATCH_TYPE_EOT parameter. The device will send a
* NFCC_PATCH_NTF packet and a NCI_OP_CORE_RESET_NTF packet.
*/
static int fdp_nci_send_patch(struct nci_dev *ndev, u8 conn_id, u8 type)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
const struct firmware *fw;
struct sk_buff *skb;
unsigned long len;
int max_size, payload_size;
int rc = 0;
if ((type == NCI_PATCH_TYPE_OTP && !info->otp_patch) ||
(type == NCI_PATCH_TYPE_RAM && !info->ram_patch))
return -EINVAL;
if (type == NCI_PATCH_TYPE_OTP)
fw = info->otp_patch;
else
fw = info->ram_patch;
max_size = nci_conn_max_data_pkt_payload_size(ndev, conn_id);
if (max_size <= 0)
return -EINVAL;
len = fw->size;
fdp_nci_set_data_pkt_counter(ndev, fdp_nci_send_patch_cb,
DIV_ROUND_UP(fw->size, max_size));
while (len) {
payload_size = min_t(unsigned long, max_size, len);
skb = nci_skb_alloc(ndev, (NCI_CTRL_HDR_SIZE + payload_size),
GFP_KERNEL);
if (!skb) {
fdp_nci_set_data_pkt_counter(ndev, NULL, 0);
return -ENOMEM;
}
skb_reserve(skb, NCI_CTRL_HDR_SIZE);
skb_put_data(skb, fw->data + (fw->size - len), payload_size);
rc = nci_send_data(ndev, conn_id, skb);
if (rc) {
fdp_nci_set_data_pkt_counter(ndev, NULL, 0);
return rc;
}
len -= payload_size;
}
return rc;
}
static int fdp_nci_open(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
return info->phy_ops->enable(info->phy);
}
static int fdp_nci_close(struct nci_dev *ndev)
{
return 0;
}
static int fdp_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
if (atomic_dec_and_test(&info->data_pkt_counter))
info->data_pkt_counter_cb(ndev);
return info->phy_ops->write(info->phy, skb);
}
static int fdp_nci_request_firmware(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
u8 *data;
int r;
r = request_firmware(&info->ram_patch, FDP_RAM_PATCH_NAME, dev);
if (r < 0) {
nfc_err(dev, "RAM patch request error\n");
return r;
}
data = (u8 *) info->ram_patch->data;
info->ram_patch_version =
data[FDP_FW_HEADER_SIZE] |
(data[FDP_FW_HEADER_SIZE + 1] << 8) |
(data[FDP_FW_HEADER_SIZE + 2] << 16) |
(data[FDP_FW_HEADER_SIZE + 3] << 24);
dev_dbg(dev, "RAM patch version: %d, size: %d\n",
info->ram_patch_version, (int) info->ram_patch->size);
r = request_firmware(&info->otp_patch, FDP_OTP_PATCH_NAME, dev);
if (r < 0) {
nfc_err(dev, "OTP patch request error\n");
return 0;
}
data = (u8 *) info->otp_patch->data;
info->otp_patch_version =
data[FDP_FW_HEADER_SIZE] |
(data[FDP_FW_HEADER_SIZE + 1] << 8) |
(data[FDP_FW_HEADER_SIZE+2] << 16) |
(data[FDP_FW_HEADER_SIZE+3] << 24);
dev_dbg(dev, "OTP patch version: %d, size: %d\n",
info->otp_patch_version, (int) info->otp_patch->size);
return 0;
}
static void fdp_nci_release_firmware(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
if (info->otp_patch) {
release_firmware(info->otp_patch);
info->otp_patch = NULL;
}
if (info->ram_patch) {
release_firmware(info->ram_patch);
info->ram_patch = NULL;
}
}
static int fdp_nci_patch_otp(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
int conn_id;
int r = 0;
if (info->otp_version >= info->otp_patch_version)
return r;
info->setup_patch_sent = 0;
info->setup_reset_ntf = 0;
info->setup_patch_ntf = 0;
/* Patch init request */
r = fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_OTP);
if (r)
return r;
/* Patch data connection creation */
conn_id = fdp_nci_create_conn(ndev);
if (conn_id < 0)
return conn_id;
/* Send the patch over the data connection */
r = fdp_nci_send_patch(ndev, conn_id, NCI_PATCH_TYPE_OTP);
if (r)
return r;
/* Wait for all the packets to be send over i2c */
wait_event_interruptible(info->setup_wq,
info->setup_patch_sent == 1);
/* make sure that the NFCC processed the last data packet */
msleep(FDP_FW_UPDATE_SLEEP);
/* Close the data connection */
r = nci_core_conn_close(info->ndev, conn_id);
if (r)
return r;
/* Patch finish message */
if (fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_EOT)) {
nfc_err(dev, "OTP patch error 0x%x\n", r);
return -EINVAL;
}
/* If the patch notification didn't arrive yet, wait for it */
wait_event_interruptible(info->setup_wq, info->setup_patch_ntf);
/* Check if the patching was successful */
r = info->setup_patch_status;
if (r) {
nfc_err(dev, "OTP patch error 0x%x\n", r);
return -EINVAL;
}
/*
* We need to wait for the reset notification before we
* can continue
*/
wait_event_interruptible(info->setup_wq, info->setup_reset_ntf);
return r;
}
static int fdp_nci_patch_ram(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
int conn_id;
int r = 0;
if (info->ram_version >= info->ram_patch_version)
return r;
info->setup_patch_sent = 0;
info->setup_reset_ntf = 0;
info->setup_patch_ntf = 0;
/* Patch init request */
r = fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_RAM);
if (r)
return r;
/* Patch data connection creation */
conn_id = fdp_nci_create_conn(ndev);
if (conn_id < 0)
return conn_id;
/* Send the patch over the data connection */
r = fdp_nci_send_patch(ndev, conn_id, NCI_PATCH_TYPE_RAM);
if (r)
return r;
/* Wait for all the packets to be send over i2c */
wait_event_interruptible(info->setup_wq,
info->setup_patch_sent == 1);
/* make sure that the NFCC processed the last data packet */
msleep(FDP_FW_UPDATE_SLEEP);
/* Close the data connection */
r = nci_core_conn_close(info->ndev, conn_id);
if (r)
return r;
/* Patch finish message */
if (fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_EOT)) {
nfc_err(dev, "RAM patch error 0x%x\n", r);
return -EINVAL;
}
/* If the patch notification didn't arrive yet, wait for it */
wait_event_interruptible(info->setup_wq, info->setup_patch_ntf);
/* Check if the patching was successful */
r = info->setup_patch_status;
if (r) {
nfc_err(dev, "RAM patch error 0x%x\n", r);
return -EINVAL;
}
/*
* We need to wait for the reset notification before we
* can continue
*/
wait_event_interruptible(info->setup_wq, info->setup_reset_ntf);
return r;
}
static int fdp_nci_setup(struct nci_dev *ndev)
{
/* Format: total length followed by an NCI packet */
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
int r;
u8 patched = 0;
r = nci_core_init(ndev);
if (r)
goto error;
/* Get RAM and OTP version */
r = fdp_nci_get_versions(ndev);
if (r)
goto error;
/* Load firmware from disk */
r = fdp_nci_request_firmware(ndev);
if (r)
goto error;
/* Update OTP */
if (info->otp_version < info->otp_patch_version) {
r = fdp_nci_patch_otp(ndev);
if (r)
goto error;
patched = 1;
}
/* Update RAM */
if (info->ram_version < info->ram_patch_version) {
r = fdp_nci_patch_ram(ndev);
if (r)
goto error;
patched = 1;
}
/* Release the firmware buffers */
fdp_nci_release_firmware(ndev);
/* If a patch was applied the new version is checked */
if (patched) {
r = nci_core_init(ndev);
if (r)
goto error;
r = fdp_nci_get_versions(ndev);
if (r)
goto error;
if (info->otp_version != info->otp_patch_version ||
info->ram_version != info->ram_patch_version) {
nfc_err(dev, "Firmware update failed");
r = -EINVAL;
goto error;
}
}
/*
* We initialized the devices but the NFC subsystem expects
* it to not be initialized.
*/
return nci_core_reset(ndev);
error:
fdp_nci_release_firmware(ndev);
nfc_err(dev, "Setup error %d\n", r);
return r;
}
static int fdp_nci_post_setup(struct nci_dev *ndev)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
int r;
/* Check if the device has VSC */
if (info->fw_vsc_cfg && info->fw_vsc_cfg[0]) {
/* Set the vendor specific configuration */
r = fdp_nci_set_production_data(ndev, info->fw_vsc_cfg[3],
&info->fw_vsc_cfg[4]);
if (r) {
nfc_err(dev, "Vendor specific config set error %d\n",
r);
return r;
}
}
/* Set clock type and frequency */
r = fdp_nci_set_clock(ndev, info->clock_type, info->clock_freq);
if (r) {
nfc_err(dev, "Clock set error %d\n", r);
return r;
}
/*
* In order to apply the VSC FDP needs a reset
*/
r = nci_core_reset(ndev);
if (r)
return r;
/**
* The nci core was initialized when post setup was called
* so we leave it like that
*/
return nci_core_init(ndev);
}
static int fdp_nci_core_reset_ntf_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
info->setup_reset_ntf = 1;
wake_up(&info->setup_wq);
return 0;
}
static int fdp_nci_prop_patch_ntf_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
info->setup_patch_ntf = 1;
info->setup_patch_status = skb->data[0];
wake_up(&info->setup_wq);
return 0;
}
static int fdp_nci_prop_patch_rsp_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
u8 status = skb->data[0];
dev_dbg(dev, "%s: status 0x%x\n", __func__, status);
nci_req_complete(ndev, status);
return 0;
}
static int fdp_nci_prop_set_production_data_rsp_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
u8 status = skb->data[0];
dev_dbg(dev, "%s: status 0x%x\n", __func__, status);
nci_req_complete(ndev, status);
return 0;
}
static int fdp_nci_core_get_config_rsp_packet(struct nci_dev *ndev,
struct sk_buff *skb)
{
struct fdp_nci_info *info = nci_get_drvdata(ndev);
struct device *dev = &info->phy->i2c_dev->dev;
struct nci_core_get_config_rsp *rsp = (void *) skb->data;
u8 i, *p;
if (rsp->status == NCI_STATUS_OK) {
p = rsp->data;
for (i = 0; i < 4; i++) {
switch (*p++) {
case NCI_PARAM_ID_FW_RAM_VERSION:
p++;
info->ram_version = le32_to_cpup((__le32 *) p);
p += 4;
break;
case NCI_PARAM_ID_FW_OTP_VERSION:
p++;
info->otp_version = le32_to_cpup((__le32 *) p);
p += 4;
break;
case NCI_PARAM_ID_OTP_LIMITED_VERSION:
p++;
info->otp_version = le32_to_cpup((__le32 *) p);
p += 4;
break;
case NCI_PARAM_ID_KEY_INDEX_ID:
p++;
info->key_index = *p++;
}
}
}
dev_dbg(dev, "OTP version %d\n", info->otp_version);
dev_dbg(dev, "RAM version %d\n", info->ram_version);
dev_dbg(dev, "key index %d\n", info->key_index);
dev_dbg(dev, "%s: status 0x%x\n", __func__, rsp->status);
nci_req_complete(ndev, rsp->status);
return 0;
}
static struct nci_driver_ops fdp_core_ops[] = {
{
.opcode = NCI_OP_CORE_GET_CONFIG_RSP,
.rsp = fdp_nci_core_get_config_rsp_packet,
},
{
.opcode = NCI_OP_CORE_RESET_NTF,
.ntf = fdp_nci_core_reset_ntf_packet,
},
};
static struct nci_driver_ops fdp_prop_ops[] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROP, NCI_OP_PROP_PATCH_OID),
.rsp = fdp_nci_prop_patch_rsp_packet,
.ntf = fdp_nci_prop_patch_ntf_packet,
},
{
.opcode = nci_opcode_pack(NCI_GID_PROP,
NCI_OP_PROP_SET_PDATA_OID),
.rsp = fdp_nci_prop_set_production_data_rsp_packet,
},
};
static struct nci_ops nci_ops = {
.open = fdp_nci_open,
.close = fdp_nci_close,
.send = fdp_nci_send,
.setup = fdp_nci_setup,
.post_setup = fdp_nci_post_setup,
.prop_ops = fdp_prop_ops,
.n_prop_ops = ARRAY_SIZE(fdp_prop_ops),
.core_ops = fdp_core_ops,
.n_core_ops = ARRAY_SIZE(fdp_core_ops),
};
int fdp_nci_probe(struct fdp_i2c_phy *phy, struct nfc_phy_ops *phy_ops,
struct nci_dev **ndevp, int tx_headroom,
int tx_tailroom, u8 clock_type, u32 clock_freq,
u8 *fw_vsc_cfg)
{
struct device *dev = &phy->i2c_dev->dev;
struct fdp_nci_info *info;
struct nci_dev *ndev;
u32 protocols;
int r;
info = devm_kzalloc(dev, sizeof(struct fdp_nci_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->phy = phy;
info->phy_ops = phy_ops;
info->clock_type = clock_type;
info->clock_freq = clock_freq;
info->fw_vsc_cfg = fw_vsc_cfg;
init_waitqueue_head(&info->setup_wq);
protocols = NFC_PROTO_JEWEL_MASK |
NFC_PROTO_MIFARE_MASK |
NFC_PROTO_FELICA_MASK |
NFC_PROTO_ISO14443_MASK |
NFC_PROTO_ISO14443_B_MASK |
NFC_PROTO_NFC_DEP_MASK |
NFC_PROTO_ISO15693_MASK;
ndev = nci_allocate_device(&nci_ops, protocols, tx_headroom,
tx_tailroom);
if (!ndev) {
nfc_err(dev, "Cannot allocate nfc ndev\n");
return -ENOMEM;
}
r = nci_register_device(ndev);
if (r)
goto err_regdev;
*ndevp = ndev;
info->ndev = ndev;
nci_set_drvdata(ndev, info);
return 0;
err_regdev:
nci_free_device(ndev);
return r;
}
EXPORT_SYMBOL(fdp_nci_probe);
void fdp_nci_remove(struct nci_dev *ndev)
{
nci_unregister_device(ndev);
nci_free_device(ndev);
}
EXPORT_SYMBOL(fdp_nci_remove);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NFC NCI driver for Intel Fields Peak NFC controller");
MODULE_AUTHOR("Robert Dolca <robert.dolca@intel.com>");