blob: 719b7dee996d44f8bc101d63284f568fd271bfe4 [file] [log] [blame]
/*
* This file is part of the UWB stack for linux.
*
* Copyright (c) 2020-2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 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 GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo. Please contact Qorvo to inquire about licensing terms.
*/
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/math64.h>
#include <linux/printk.h>
#include <linux/netdevice.h>
#include <net/mcps802154_schedule.h>
#include "net/nfcc_coex_region_nl.h"
#include "mcps802154_i.h"
#include "nfcc_coex_region.h"
#include "nfcc_coex_region_call.h"
#include "nfcc_coex_access.h"
#include "nfcc_coex_session.h"
static struct mcps802154_region_ops nfcc_coex_region_ops;
static struct mcps802154_region *nfcc_coex_open(struct mcps802154_llhw *llhw)
{
struct nfcc_coex_local *local;
local = kmalloc(sizeof(*local), GFP_KERNEL);
if (!local)
return NULL;
local->llhw = llhw;
local->region.ops = &nfcc_coex_region_ops;
local->state = NFCC_COEX_STATE_UNUSED;
return &local->region;
}
static void nfcc_coex_close(struct mcps802154_region *region)
{
struct nfcc_coex_local *local = region_to_local(region);
kfree(local);
}
static void nfcc_coex_notify_stop(struct mcps802154_region *region)
{
struct nfcc_coex_local *local = region_to_local(region);
nfcc_coex_session_control(local, NFCC_COEX_CALL_CCC_SESSION_STOP, NULL,
NULL);
if (local->state != NFCC_COEX_STATE_UNUSED) {
pr_err("device stopped while nfcc coex not stopped state=%d",
local->state);
local->state = NFCC_COEX_STATE_UNUSED;
}
}
static int nfcc_coex_call(struct mcps802154_region *region, u32 call_id,
const struct nlattr *attrs,
const struct genl_info *info)
{
struct nfcc_coex_local *local = region_to_local(region);
switch (call_id) {
case NFCC_COEX_CALL_CCC_SESSION_START:
case NFCC_COEX_CALL_CCC_SESSION_STOP:
return nfcc_coex_session_control(local, call_id, attrs, info);
default:
return -EINVAL;
}
}
static int nfcc_coex_get_demand(struct mcps802154_region *region,
u32 next_timestamp_dtu,
struct mcps802154_region_demand *demand)
{
struct nfcc_coex_local *local = region_to_local(region);
const struct nfcc_coex_session *session = &local->session;
switch (local->state) {
case NFCC_COEX_STATE_UNUSED:
return 0;
default:
if (session->first_access) {
/* region_demand have never been set.
* Duration is set at 12ms, value catched during test. */
demand->timestamp_dtu = next_timestamp_dtu;
demand->duration_dtu = 187200;
} else if (is_before_dtu(session->region_demand.timestamp_dtu,
next_timestamp_dtu)) {
/* Date is late. */
int shift = next_timestamp_dtu -
session->region_demand.timestamp_dtu;
int duration =
session->region_demand.duration_dtu - shift;
demand->timestamp_dtu = next_timestamp_dtu;
demand->duration_dtu = duration > 0 ? duration : 1;
} else {
memcpy(demand, &session->region_demand,
sizeof(*demand));
}
return 1;
}
return 0;
}
void nfcc_coex_report(struct nfcc_coex_local *local)
{
struct nfcc_coex_session *session = &local->session;
const struct dw3000_vendor_cmd_nfcc_coex_get_access_info
*get_access_info = &session->get_access_info;
struct sk_buff *msg;
int r;
msg = mcps802154_region_event_alloc_skb(
local->llhw, &local->region,
NFCC_COEX_CALL_CCC_SESSION_NOTIFICATION, session->event_portid,
NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!msg)
return;
#define P(attr, type, value) \
do { \
if (nla_put_##type(msg, NFCC_COEX_CALL_ATTR_CCC_##attr, \
value)) { \
goto nla_put_failure; \
} \
} while (0)
P(WATCHDOG_TIMEOUT, u8, get_access_info->watchdog_timeout);
P(STOPPED, u8, get_access_info->stop);
#undef P
r = mcps802154_region_event(local->llhw, msg);
if (r == -ECONNREFUSED)
/* TODO stop. */
;
return;
nla_put_failure:
kfree_skb(msg);
}
static struct mcps802154_region_ops nfcc_coex_region_ops = {
/* clang-format off */
.owner = THIS_MODULE,
.name = "nfcc_coex",
.open = nfcc_coex_open,
.close = nfcc_coex_close,
.notify_stop = nfcc_coex_notify_stop,
.call = nfcc_coex_call,
.get_access = nfcc_coex_get_access,
.get_demand = nfcc_coex_get_demand,
/* clang-format on */
};
int __init nfcc_coex_region_init(void)
{
return mcps802154_region_register(&nfcc_coex_region_ops);
}
void __exit nfcc_coex_region_exit(void)
{
mcps802154_region_unregister(&nfcc_coex_region_ops);
}
module_init(nfcc_coex_region_init);
module_exit(nfcc_coex_region_exit);
MODULE_DESCRIPTION("Vendor Region for IEEE 802.15.4 MCPS");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL v2");