﻿/******************************************************************************
 * nfc.h
 ******************************************************************************/
... some defines, some struct and functions declarations

struct nfc_sock_list {
	struct hlist_head head;
	rwlock_t lock;
};
static inline void nfc_put_device(struct nfc_dev *dev) {
	put_device(&dev->dev);
}
static inline void nfc_device_iter_init(struct class_dev_iter *iter) {
	class_dev_iter_init(iter, &nfc_class, NULL, NULL);
}
static inline struct nfc_dev *nfc_device_iter_next(struct class_dev_iter *iter) {
	struct device *d = class_dev_iter_next(iter);
	return to_nfc_dev(d);
}
static inline void nfc_device_iter_exit(struct class_dev_iter *iter) {
	class_dev_iter_exit(iter);
}

/******************************************************************************
 * Generic driver for NXP NCI NFC chips: nxp-nci.h
 ******************************************************************************/
... some defines and functions declarations

enum nxp_nci_mode {
	NXP_NCI_MODE_COLD, 	NXP_NCI_MODE_NCI, 	NXP_NCI_MODE_FW
};
struct nxp_nci_phy_ops {
	int (*set_mode)(void *id, enum nxp_nci_mode mode);
	int (*write)(void *id, struct sk_buff *skb);
};
struct nxp_nci_fw_info {
	char name[NFC_FIRMWARE_NAME_MAXSIZE + 1];
	const struct firmware *fw;
	size_t size, written, frame_size;
	const u8 *data;
	struct work_struct work;
	struct completion cmd_completion;
	int cmd_result;
};
struct nxp_nci_info {
	struct nci_dev *ndev;
	void *phy_id;
	struct device *pdev;
	enum nxp_nci_mode mode;
	const struct nxp_nci_phy_ops *phy_ops;
	unsigned int max_payload;
	struct mutex info_lock;
	struct nxp_nci_fw_info fw_info;
};

/******************************************************************************
 * Generic driver for NXP NCI NFC chips: core.c
 ******************************************************************************/
... some include and defines 

static int nxp_nci_open(struct nci_dev *ndev)
{
	struct nxp_nci_info *info = nci_get_drvdata(ndev); int r;
	mutex_lock(&info->info_lock);
	r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_NCI);
	info->mode = NXP_NCI_MODE_NCI;
	mutex_unlock(&info->info_lock);
	return r;
}

static int nxp_nci_close(struct nci_dev *ndev)
{
	struct nxp_nci_info *info = nci_get_drvdata(ndev); int r;
	mutex_lock(&info->info_lock);
	info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
	info->mode = NXP_NCI_MODE_COLD;
	mutex_unlock(&info->info_lock);
	return r;
}

static int nxp_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
{
	struct nxp_nci_info *info = nci_get_drvdata(ndev); int r;
	if (...some check) { ... for some exception }
	r = info->phy_ops->write(info->phy_id, skb);
	return r;
}

static struct nci_ops nxp_nci_ops = {
	.open = nxp_nci_open,
	.close = nxp_nci_close,
	.send = nxp_nci_send,
	.fw_download = nxp_nci_fw_download,
};

int nxp_nci_probe(void *phy_id, struct device *pdev,
		  const struct nxp_nci_phy_ops *phy_ops,
		  unsigned int max_payload,
		  struct nci_dev **ndev)
{
	struct nxp_nci_info *info; int r;

	info = devm_kzalloc(pdev, sizeof(struct nxp_nci_info), GFP_KERNEL);
	if (...some check) { ... for some exception }
	info->phy_id = phy_id;
	info->pdev = pdev;
	info->phy_ops = phy_ops;
	info->max_payload = max_payload;
	INIT_WORK(&info->fw_info.work, nxp_nci_fw_work);
	init_completion(&info->fw_info.cmd_completion);
	mutex_init(&info->info_lock);

	if (info->phy_ops->set_mode) {
		r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
	}
	info->mode = NXP_NCI_MODE_COLD;
	info->ndev = nci_allocate_device(&nxp_nci_ops, NXP_NCI_NFC_PROTOCOLS,
					 NXP_NCI_HDR_LEN, 0);
	if (...some check) { ... for some exception nci_free_device(info->ndev);}
	nci_set_parent_dev(info->ndev, pdev);
	nci_set_drvdata(info->ndev, info);
	r = nci_register_device(info->ndev);
	if (...some check) { ... for some exception like nci_free_device(info->ndev);}
	*ndev = info->ndev;
	return r;
}

void nxp_nci_remove(struct nci_dev *ndev)
{
	struct nxp_nci_info *info = nci_get_drvdata(ndev);

	if (info->mode == NXP_NCI_MODE_FW)
		nxp_nci_fw_work_complete(info, -ESHUTDOWN);
	cancel_work_sync(&info->fw_info.work);
	mutex_lock(&info->info_lock);
	info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
	nci_unregister_device(ndev);
	nci_free_device(ndev);
	mutex_unlock(&info->info_lock);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NXP NCI NFC driver");
MODULE_AUTHOR("Clément Perrochaud <clement.perrochaud@nxp.com>");

/******************************************************************************
 * Generic driver for NXP NCI NFC chips : fw.c
 ******************************************************************************/
... some include and defines 

void nxp_nci_fw_work_complete(struct nxp_nci_info *info, int result)
{
	struct nxp_nci_fw_info *fw_info = &info->fw_info; 	int r;
	result = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_COLD);
	info->mode = NXP_NCI_MODE_COLD;
	nfc_fw_download_done(info->ndev->nfc_dev, fw_info->name, result);
}

static int nxp_nci_fw_send_chunk(struct nxp_nci_info *info)
{
	struct sk_buff *skb; struct nxp_nci_fw_info *fw_info = &info->fw_info;
	size_t chunk_len,remaining_len; u16 header, crc;

	... some code to compute header, chunk_len, remaining_len, crc
	skb = nci_skb_alloc(info->ndev, info->max_payload, GFP_KERNEL);
	skb_put_data(skb, fw_info->data + fw_info->written, chunk_len);
	r = info->phy_ops->write(info->phy_id, skb);
	kfree_skb(skb);
	return r;
}

static int nxp_nci_fw_send(struct nxp_nci_info *info)
{
	struct nxp_nci_fw_info *fw_info = &info->fw_info;
	long completion_rc;

	reinit_completion(&fw_info->cmd_completion);

	if (fw_info->written == 0) {
		fw_info->frame_size = get_unaligned_be16(fw_info->data);
		fw_info->data += NXP_NCI_FW_HDR_LEN;
		fw_info->size -= NXP_NCI_FW_HDR_LEN;
	}
	fw_info->written += nxp_nci_fw_send_chunk(info);

	if (*fw_info->data == NXP_NCI_FW_CMD_RESET) {
		fw_info->cmd_result = 0;
		if (fw_info->fw)
			schedule_work(&fw_info->work);
	} else {
		completion_rc = wait_for_completion_interruptible_timeout(
			&fw_info->cmd_completion, NXP_NCI_FW_ANSWER_TIMEOUT);
		if (completion_rc == 0)
			return -ETIMEDOUT;
	}
	return 0;
}

void nxp_nci_fw_work(struct work_struct *work)
{
	struct nxp_nci_info *info; struct nxp_nci_fw_info *fw_info;

	fw_info = container_of(work, struct nxp_nci_fw_info, work);
	info = container_of(fw_info, struct nxp_nci_info, fw_info);
	mutex_lock(&info->info_lock);
	fw_info->cmd_result;
	if (fw_info->written == fw_info->frame_size) {
		fw_info->data += fw_info->frame_size;
		fw_info->size -= fw_info->frame_size;
		fw_info->written = 0;
	}
	if (fw_info->size > 0)
		r = nxp_nci_fw_send(info);
	else
		nxp_nci_fw_work_complete(info, r);
	mutex_unlock(&info->info_lock);
}

void nxp_nci_fw_download(struct nci_dev *ndev, const char *firmware_name)
{
	struct nxp_nci_info *info = nci_get_drvdata(ndev);
	struct nxp_nci_fw_info *fw_info = &info->fw_info;

	mutex_lock(&info->info_lock);
	strcpy(fw_info->name, firmware_name);
	request_firmware(&fw_info->fw, firmware_name, ndev->nfc_dev->dev.parent);
	r = info->phy_ops->set_mode(info->phy_id, NXP_NCI_MODE_FW);
	info->mode = NXP_NCI_MODE_FW;
	fw_info->data = fw_info->fw->data;
	fw_info->size = fw_info->fw->size;
	fw_info->written = 0;
	fw_info->frame_size = 0;
	fw_info->cmd_result = 0;
	schedule_work(&fw_info->work);
	mutex_unlock(&info->info_lock);
}

void nxp_nci_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb)
{
	struct nxp_nci_info *info = nci_get_drvdata(ndev);
	struct nxp_nci_fw_info *fw_info = &info->fw_info;

	complete(&fw_info->cmd_completion);
	if (skb) {
		fw_info->cmd_result = nxp_nci_fw_read_status(*(u8 *)skb_pull(skb, HDR_LEN));
		kfree_skb(skb);
	} else {
		fw_info->cmd_result = -EIO;
	}
	schedule_work(&fw_info->work);
}
EXPORT_SYMBOL(nxp_nci_fw_recv_frame);
