Commit 36ed0958 authored by Jiri Kosina's avatar Jiri Kosina

Merge branch 'for-5.11/amd-sfh-hid' into for-linus

From Sandeep Singh.

AMD SFH (Sensor Fusion Hub) is HID based driver.SFH FW is part of MP2 processor
(MP2 which is an ARM core connected to x86 for processing sensor data) and it
runs on MP2 where in the driver resides on X86.  The driver functionalities are
divided into three parts:

1: amd-mp2-pcie:- This part of the module will communicate with MP2
                  firmware. MP2 which is exposed as a PCI device to the
                  X86, uses mailboxes to talk to MP2 firmware to
                  send/receive commands.
2: Client Layer:- This part of the driver will use DRAM  data and convert
                  the  data into HID format based on HID reports.
3: Transport layer :- This part of the driver the will communicate with HID
                  core.Communication between devices and HID core is
                  mostly done via HID reports

In terms of architecture, it resembles like ISH (Intel Integrated Sensor Hub).
However the major difference is all the hid reports are generated as part of
the kernel driver.

AMD SFH is integrated as a part of SoC, starting from 17h family of processors.
The solution is working well on several OEM products.  AMD SFH uses HID over
PCIe bus.
parents c870d50c 6e6eae04
.. SPDX-License-Identifier: GPL-2.0
AMD Sensor Fusion Hub
=====================
AMD Sensor Fusion Hub (SFH) is part of an SOC starting from Ryzen based platforms.
The solution is working well on several OEM products. AMD SFH uses HID over PCIe bus.
In terms of architecture it resembles ISH, however the major difference is all
the HID reports are generated as part of the kernel driver.
1. Block Diagram
================
::
---------------------------------
| HID User Space Applications |
- -------------------------------
---------------------------------------------
---------------------------------
| HID Core |
---------------------------------
---------------------------------
| AMD HID Transport |
---------------------------------
--------------------------------
| AMD HID Client |
| with HID Report Generator|
--------------------------------
--------------------------------
| AMD MP2 PCIe Driver |
--------------------------------
OS
---------------------------------------------
Hardware + Firmware
--------------------------------
| SFH MP2 Processor |
--------------------------------
AMD HID Transport Layer
-----------------------
AMD SFH transport is also implemented as a bus. Each client application executing in the AMD MP2 is
registered as a device on this bus. Here: MP2 which is an ARM core connected to x86 for processing
sensor data. The layer, which binds each device (AMD SFH HID driver) identifies the device type and
registers with the hid core. Transport layer attach a constant "struct hid_ll_driver" object with
each device. Once a device is registered with HID core, the callbacks provided via this struct are
used by HID core to communicate with the device. AMD HID Transport layer implements the synchronous calls.
AMD HID Client Layer
--------------------
This layer is responsible to implement HID request and descriptors. As firmware is OS agnostic, HID
client layer fills the HID request structure and descriptors. HID client layer is complex as it is
interface between MP2 PCIe layer and HID. HID client layer initialized the MP2 PCIe layer and holds
the instance of MP2 layer. It identifies the number of sensors connected using MP2-PCIe layer. Base
on that allocates the DRAM address for each and every sensor and pass it to MP2-PCIe driver.On
enumeration of each the sensor, client layer fills the HID Descriptor structure and HID input repor
structure. HID Feature report structure is optional. The report descriptor structure varies from
sensor to sensor.
AMD MP2 PCIe layer
------------------
MP2 PCIe Layer is responsible for making all transactions with the firmware over PCIe.
The connection establishment between firmware and PCIe happens here.
The communication between X86 and MP2 is split into three parts.
1. Command transfer via the C2P mailbox registers.
2. Data transfer via DRAM.
3. Supported sensor info via P2C registers.
Commands are sent to MP2 using C2P Mailbox registers. Writing into C2P Message registers generate
interrupt to MP2. The client layer allocates the physical memory and the same is sent to MP2 via
the PCI layer. MP2 firmware writes the command output to the access DRAM memory which the client
layer has allocated. Firmware always writes minimum of 32 bytes into DRAM. So as a protocol driver
shall allocate minimum of 32 bytes DRAM space.
Enumeration and Probing flow
----------------------------
::
HID AMD AMD AMD -PCIe MP2
Core Transport Client layer layer FW
| | | | |
| | | on Boot Driver Loaded |
| | | | |
| | | MP2-PCIe Int |
| | | | |
| | |---Get Number of sensors-> | |
| | | Read P2C |
| | | Register |
| | | | |
| | | Loop(for No of Sensors) | |
| | |----------------------| | |
| | | Create HID Descriptor| | |
| | | Create Input report | | |
| | | Descriptor Map | | |
| | | the MP2 FW Index to | | |
| | | HID Index | | |
| | | Allocate the DRAM | Enable |
| | | address | Sensors |
| | |----------------------| | |
| | HID transport| | Enable |
| |<--Probe------| |---Sensor CMD--> |
| | Create the | | |
| | HID device | | |
| | (MFD) | | |
| | by Populating| | |
| | the HID | | |
| | ll_driver | | |
| HID | | | |
| add | | | |
|Device | | | |
|<------------- | | | |
Data Flow from Application to the AMD SFH Driver
------------------------------------------------
::
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
|HID_req | | | |
|get_report | | | |
|------------->| | | |
| | HID_get_input| | |
| | report | | |
| |------------->|------------------------| | |
| | | Read the DRAM data for| | |
| | | requested sensor and | | |
| | | create the HID input | | |
| | | report | | |
| | |------------------------| | |
| |Data received | | |
| | in HID report| | |
To |<-------------|<-------------| | |
Applications| | | | |
<-------| | | | |
......@@ -16,3 +16,4 @@ Human Interface Devices (HID)
hid-alps
intel-ish-hid
amd-sfh-hid
......@@ -950,6 +950,14 @@ S: Supported
F: arch/arm64/boot/dts/amd/amd-seattle-xgbe*.dtsi
F: drivers/net/ethernet/amd/xgbe/
AMD SENSOR FUSION HUB DRIVER
M: Nehal Shah <nehal-bakulchandra.shah@amd.com>
M: Sandeep Singh <sandeep.singh@amd.com>
L: linux-input@vger.kernel.org
S: Maintained
F: Documentation/hid/amd-sfh*
F: drivers/hid/amd-sfh-hid/
AMS AS73211 DRIVER
M: Christian Eggers <ceggers@arri.de>
L: linux-iio@vger.kernel.org
......
......@@ -1183,4 +1183,6 @@ source "drivers/hid/i2c-hid/Kconfig"
source "drivers/hid/intel-ish-hid/Kconfig"
source "drivers/hid/amd-sfh-hid/Kconfig"
endmenu
......@@ -142,3 +142,5 @@ obj-$(CONFIG_I2C_HID) += i2c-hid/
obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
# SPDX-License-Identifier: GPL-2.0-or-later
menu "AMD SFH HID Support"
depends on X86_64 || COMPILE_TEST
depends on PCI
depends on HID
config AMD_SFH_HID
tristate "AMD Sensor Fusion Hub"
help
If you say yes to this option, support will be included for the
AMD Sensor Fusion Hub.
This driver will enable sensors functionality on AMD platforms
starting from 17h family of RYZEN parts.
This driver can also be built as a module. If so, the module will
be called amd-sfh.
Say Y or M here if you want to support AMD SFH. If unsure, say N.
endmenu
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Makefile - AMD SFH HID drivers
# Copyright (c) 2019-2020, Advanced Micro Devices, Inc.
#
#
obj-$(CONFIG_AMD_SFH_HID) += amd_sfh.o
amd_sfh-objs := amd_sfh_hid.o
amd_sfh-objs += amd_sfh_client.o
amd_sfh-objs += amd_sfh_pcie.o
amd_sfh-objs += hid_descriptor/amd_sfh_hid_desc.o
ccflags-y += -I $(srctree)/$(src)/
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD SFH Client Layer
* Copyright 2020 Advanced Micro Devices, Inc.
* Authors: Nehal Bakulchandra Shah <Nehal-Bakulchandra.Shah@amd.com>
* Sandeep Singh <Sandeep.singh@amd.com>
*/
#include <linux/dma-mapping.h>
#include <linux/hid.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/errno.h>
#include "hid_descriptor/amd_sfh_hid_desc.h"
#include "amd_sfh_pcie.h"
#include "amd_sfh_hid.h"
#define AMD_SFH_IDLE_LOOP 200
struct request_list {
struct hid_device *hid;
struct list_head list;
u8 report_id;
u8 sensor_idx;
u8 report_type;
u8 current_index;
};
static struct request_list req_list;
void amd_sfh_set_report(struct hid_device *hid, int report_id,
int report_type)
{
struct amdtp_hid_data *hid_data = hid->driver_data;
struct amdtp_cl_data *cli_data = hid_data->cli_data;
int i;
for (i = 0; i < cli_data->num_hid_devices; i++) {
if (cli_data->hid_sensor_hubs[i] == hid) {
cli_data->cur_hid_dev = i;
break;
}
}
amdtp_hid_wakeup(hid);
}
int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type)
{
struct amdtp_hid_data *hid_data = hid->driver_data;
struct amdtp_cl_data *cli_data = hid_data->cli_data;
int i;
for (i = 0; i < cli_data->num_hid_devices; i++) {
if (cli_data->hid_sensor_hubs[i] == hid) {
struct request_list *new = kzalloc(sizeof(*new), GFP_KERNEL);
if (!new)
return -ENOMEM;
new->current_index = i;
new->sensor_idx = cli_data->sensor_idx[i];
new->hid = hid;
new->report_type = report_type;
new->report_id = report_id;
cli_data->report_id[i] = report_id;
cli_data->request_done[i] = false;
list_add(&new->list, &req_list.list);
break;
}
}
schedule_delayed_work(&cli_data->work, 0);
return 0;
}
static void amd_sfh_work(struct work_struct *work)
{
struct amdtp_cl_data *cli_data = container_of(work, struct amdtp_cl_data, work.work);
struct request_list *req_node;
u8 current_index, sensor_index;
u8 report_id, node_type;
u8 report_size = 0;
req_node = list_last_entry(&req_list.list, struct request_list, list);
list_del(&req_node->list);
current_index = req_node->current_index;
sensor_index = req_node->sensor_idx;
report_id = req_node->report_id;
node_type = req_node->report_type;
if (node_type == HID_FEATURE_REPORT) {
report_size = get_feature_report(sensor_index, report_id,
cli_data->feature_report[current_index]);
if (report_size)
hid_input_report(cli_data->hid_sensor_hubs[current_index],
cli_data->report_type[current_index],
cli_data->feature_report[current_index], report_size, 0);
else
pr_err("AMDSFH: Invalid report size\n");
} else if (node_type == HID_INPUT_REPORT) {
report_size = get_input_report(sensor_index, report_id,
cli_data->input_report[current_index],
cli_data->sensor_virt_addr[current_index]);
if (report_size)
hid_input_report(cli_data->hid_sensor_hubs[current_index],
cli_data->report_type[current_index],
cli_data->input_report[current_index], report_size, 0);
else
pr_err("AMDSFH: Invalid report size\n");
}
cli_data->cur_hid_dev = current_index;
cli_data->sensor_requested_cnt[current_index] = 0;
amdtp_hid_wakeup(cli_data->hid_sensor_hubs[current_index]);
}
static void amd_sfh_work_buffer(struct work_struct *work)
{
struct amdtp_cl_data *cli_data = container_of(work, struct amdtp_cl_data, work_buffer.work);
u8 report_size;
int i;
for (i = 0; i < cli_data->num_hid_devices; i++) {
report_size = get_input_report(cli_data->sensor_idx[i], cli_data->report_id[i],
cli_data->input_report[i],
cli_data->sensor_virt_addr[i]);
hid_input_report(cli_data->hid_sensor_hubs[i], HID_INPUT_REPORT,
cli_data->input_report[i], report_size, 0);
}
schedule_delayed_work(&cli_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
}
int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
{
struct amdtp_cl_data *cl_data = privdata->cl_data;
struct amd_mp2_sensor_info info;
struct device *dev;
u32 feature_report_size;
u32 input_report_size;
u8 cl_idx;
int rc, i;
dev = &privdata->pdev->dev;
cl_data = kzalloc(sizeof(*cl_data), GFP_KERNEL);
if (!cl_data)
return -ENOMEM;
cl_data->num_hid_devices = amd_mp2_get_sensor_num(privdata, &cl_data->sensor_idx[0]);
INIT_DELAYED_WORK(&cl_data->work, amd_sfh_work);
INIT_DELAYED_WORK(&cl_data->work_buffer, amd_sfh_work_buffer);
INIT_LIST_HEAD(&req_list.list);
for (i = 0; i < cl_data->num_hid_devices; i++) {
cl_data->sensor_virt_addr[i] = dma_alloc_coherent(dev, sizeof(int) * 8,
&cl_data->sensor_phys_addr[i],
GFP_KERNEL);
cl_data->sensor_sts[i] = 0;
cl_data->sensor_requested_cnt[i] = 0;
cl_data->cur_hid_dev = i;
cl_idx = cl_data->sensor_idx[i];
cl_data->report_descr_sz[i] = get_descr_sz(cl_idx, descr_size);
if (!cl_data->report_descr_sz[i]) {
rc = -EINVAL;
goto cleanup;
}
feature_report_size = get_descr_sz(cl_idx, feature_size);
if (!feature_report_size) {
rc = -EINVAL;
goto cleanup;
}
input_report_size = get_descr_sz(cl_idx, input_size);
if (!input_report_size) {
rc = -EINVAL;
goto cleanup;
}
cl_data->feature_report[i] = kzalloc(feature_report_size, GFP_KERNEL);
if (!cl_data->feature_report[i]) {
rc = -ENOMEM;
goto cleanup;
}
cl_data->input_report[i] = kzalloc(input_report_size, GFP_KERNEL);
if (!cl_data->input_report[i]) {
rc = -ENOMEM;
goto cleanup;
}
info.period = msecs_to_jiffies(AMD_SFH_IDLE_LOOP);
info.sensor_idx = cl_idx;
info.phys_address = cl_data->sensor_phys_addr[i];
cl_data->report_descr[i] = kzalloc(cl_data->report_descr_sz[i], GFP_KERNEL);
if (!cl_data->report_descr[i]) {
rc = -ENOMEM;
goto cleanup;
}
rc = get_report_descriptor(cl_idx, cl_data->report_descr[i]);
if (rc)
return rc;
rc = amdtp_hid_probe(cl_data->cur_hid_dev, cl_data);
if (rc)
return rc;
amd_start_sensor(privdata, info);
cl_data->sensor_sts[i] = 1;
}
privdata->cl_data = cl_data;
schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
return 0;
cleanup:
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_virt_addr[i]) {
dma_free_coherent(&privdata->pdev->dev, 8 * sizeof(int),
cl_data->sensor_virt_addr[i],
cl_data->sensor_phys_addr[i]);
}
kfree(cl_data->feature_report[i]);
kfree(cl_data->input_report[i]);
kfree(cl_data->report_descr[i]);
}
kfree(cl_data);
return rc;
}
int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
{
struct amdtp_cl_data *cl_data = privdata->cl_data;
int i;
for (i = 0; i < cl_data->num_hid_devices; i++)
amd_stop_sensor(privdata, i);
cancel_delayed_work_sync(&cl_data->work);
cancel_delayed_work_sync(&cl_data->work_buffer);
amdtp_hid_remove(cl_data);
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_virt_addr[i]) {
dma_free_coherent(&privdata->pdev->dev, 8 * sizeof(int),
cl_data->sensor_virt_addr[i],
cl_data->sensor_phys_addr[i]);
}
}
kfree(cl_data);
return 0;
}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD MP2 Sensors transport driver
*
* Authors: Nehal Bakulchandra Shah <Nehal-bakulchandra.shah@amd.com>
* Sandeep Singh <sandeep.singh@amd.com>
*/
#include <linux/hid.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include "amd_sfh_hid.h"
#define AMD_SFH_RESPONSE_TIMEOUT 1500
/**
* amdtp_hid_parse() - hid-core .parse() callback
* @hid: hid device instance
*
* This function gets called during call to hid_add_device
*
* Return: 0 on success and non zero on error
*/
static int amdtp_hid_parse(struct hid_device *hid)
{
struct amdtp_hid_data *hid_data = hid->driver_data;
struct amdtp_cl_data *cli_data = hid_data->cli_data;
return hid_parse_report(hid, cli_data->report_descr[hid_data->index],
cli_data->report_descr_sz[hid_data->index]);
}
/* Empty callbacks with success return code */
static int amdtp_hid_start(struct hid_device *hid)
{
return 0;
}
static void amdtp_hid_stop(struct hid_device *hid)
{
}
static int amdtp_hid_open(struct hid_device *hid)
{
return 0;
}
static void amdtp_hid_close(struct hid_device *hid)
{
}
static int amdtp_raw_request(struct hid_device *hdev, u8 reportnum,
u8 *buf, size_t len, u8 rtype, int reqtype)
{
return 0;
}
static void amdtp_hid_request(struct hid_device *hid, struct hid_report *rep, int reqtype)
{
int rc;
switch (reqtype) {
case HID_REQ_GET_REPORT:
rc = amd_sfh_get_report(hid, rep->id, rep->type);
if (rc)
dev_err(&hid->dev, "AMDSFH get report error\n");
break;
case HID_REQ_SET_REPORT:
amd_sfh_set_report(hid, rep->id, reqtype);
break;
default:
break;
}
}
static int amdtp_wait_for_response(struct hid_device *hid)
{
struct amdtp_hid_data *hid_data = hid->driver_data;
struct amdtp_cl_data *cli_data = hid_data->cli_data;
int i, ret = 0;
for (i = 0; i < cli_data->num_hid_devices; i++) {
if (cli_data->hid_sensor_hubs[i] == hid)
break;
}
if (!cli_data->request_done[i])
ret = wait_event_interruptible_timeout(hid_data->hid_wait,
cli_data->request_done[i],
msecs_to_jiffies(AMD_SFH_RESPONSE_TIMEOUT));
if (ret == -ERESTARTSYS)
return -ERESTARTSYS;
else if (ret < 0)
return -ETIMEDOUT;
else
return 0;
}
void amdtp_hid_wakeup(struct hid_device *hid)
{
struct amdtp_hid_data *hid_data = hid->driver_data;
struct amdtp_cl_data *cli_data = hid_data->cli_data;
cli_data->request_done[cli_data->cur_hid_dev] = true;
wake_up_interruptible(&hid_data->hid_wait);
}
static struct hid_ll_driver amdtp_hid_ll_driver = {
.parse = amdtp_hid_parse,
.start = amdtp_hid_start,
.stop = amdtp_hid_stop,
.open = amdtp_hid_open,
.close = amdtp_hid_close,
.request = amdtp_hid_request,
.wait = amdtp_wait_for_response,
.raw_request = amdtp_raw_request,
};
int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data)
{
struct hid_device *hid;
struct amdtp_hid_data *hid_data;
int rc;
hid = hid_allocate_device();
if (IS_ERR(hid))
return PTR_ERR(hid);
hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL);
if (!hid_data) {
rc = -ENOMEM;
goto err_hid_data;
}
hid->ll_driver = &amdtp_hid_ll_driver;
hid_data->index = cur_hid_dev;
hid_data->cli_data = cli_data;
init_waitqueue_head(&hid_data->hid_wait);
hid->driver_data = hid_data;
cli_data->hid_sensor_hubs[cur_hid_dev] = hid;
hid->bus = BUS_AMD_AMDTP;
hid->vendor = AMD_SFH_HID_VENDOR;
hid->product = AMD_SFH_HID_PRODUCT;
snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-amdtp",
hid->vendor, hid->product);
rc = hid_add_device(hid);
if (rc)
goto err_hid_device;
return 0;
err_hid_device:
kfree(hid_data);
err_hid_data:
hid_destroy_device(hid);
return rc;
}
void amdtp_hid_remove(struct amdtp_cl_data *cli_data)
{
int i;
for (i = 0; i < cli_data->num_hid_devices; ++i) {
kfree(cli_data->feature_report[i]);
kfree(cli_data->input_report[i]);
kfree(cli_data->report_descr[i]);
if (cli_data->hid_sensor_hubs[i]) {
kfree(cli_data->hid_sensor_hubs[i]->driver_data);
hid_destroy_device(cli_data->hid_sensor_hubs[i]);
cli_data->hid_sensor_hubs[i] = NULL;
}
}
}
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* AMD MP2 Sensors transport driver
*
* Authors: Nehal Bakulchandra Shah <Nehal-bakulchandra.shah@amd.com>
* Sandeep Singh <sandeep.singh@amd.com>
*/
#ifndef AMDSFH_HID_H
#define AMDSFH_HID_H
#define MAX_HID_DEVICES 4
#define BUS_AMD_AMDTP 0x20
#define AMD_SFH_HID_VENDOR 0x1022
#define AMD_SFH_HID_PRODUCT 0x0001
struct amdtp_cl_data {
u8 init_done;
u32 cur_hid_dev;
u32 hid_dev_count;
u32 num_hid_devices;
struct device_info *hid_devices;
u8 *report_descr[MAX_HID_DEVICES];
int report_descr_sz[MAX_HID_DEVICES];
struct hid_device *hid_sensor_hubs[MAX_HID_DEVICES];
u8 *hid_descr[MAX_HID_DEVICES];
int hid_descr_size[MAX_HID_DEVICES];
phys_addr_t phys_addr_base;
u32 *sensor_virt_addr[MAX_HID_DEVICES];
phys_addr_t sensor_phys_addr[MAX_HID_DEVICES];
u32 sensor_sts[MAX_HID_DEVICES];
u32 sensor_requested_cnt[MAX_HID_DEVICES];
u8 report_type[MAX_HID_DEVICES];
u8 report_id[MAX_HID_DEVICES];
u8 sensor_idx[MAX_HID_DEVICES];
u8 *feature_report[MAX_HID_DEVICES];
u8 *input_report[MAX_HID_DEVICES];
u8 request_done[MAX_HID_DEVICES];
struct delayed_work work;
struct delayed_work work_buffer;
};
/**
* struct amdtp_hid_data - Per instance HID data
* @index: Device index in the order of enumeration
* @request_done: Get Feature/Input report complete flag
* used during get/set request from hid core
* @cli_data: Link to the client instance
* @hid_wait: Completion waitq
*
* Used to tie hid->driver data to driver client instance
*/
struct amdtp_hid_data {
int index;
struct amdtp_cl_data *cli_data;
wait_queue_head_t hid_wait;
};
/* Interface functions between HID LL driver and AMD SFH client */
void hid_amdtp_set_feature(struct hid_device *hid, char *buf, u32 len, int report_id);
void hid_amdtp_get_report(struct hid_device *hid, int report_id, int report_type);
int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data);
void amdtp_hid_remove(struct amdtp_cl_data *cli_data);
int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type);
void amd_sfh_set_report(struct hid_device *hid, int report_id, int report_type);
void amdtp_hid_wakeup(struct hid_device *hid);
#endif
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD MP2 PCIe communication driver
* Copyright 2020 Advanced Micro Devices, Inc.
*
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
* Sandeep Singh <Sandeep.singh@amd.com>
*/
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "amd_sfh_pcie.h"
#define DRIVER_NAME "pcie_mp2_amd"
#define DRIVER_DESC "AMD(R) PCIe MP2 Communication Driver"
#define ACEL_EN BIT(0)
#define GYRO_EN BIT(1)
#define MAGNO_EN BIT(2)
#define ALS_EN BIT(19)
void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info)
{
union sfh_cmd_param cmd_param;
union sfh_cmd_base cmd_base;
/* fill up command register */
memset(&cmd_base, 0, sizeof(cmd_base));
cmd_base.s.cmd_id = ENABLE_SENSOR;
cmd_base.s.period = info.period;
cmd_base.s.sensor_id = info.sensor_idx;
/* fill up command param register */
memset(&cmd_param, 0, sizeof(cmd_param));
cmd_param.s.buf_layout = 1;
cmd_param.s.buf_length = 16;
writeq(info.phys_address, privdata->mmio + AMD_C2P_MSG2);
writel(cmd_param.ul, privdata->mmio + AMD_C2P_MSG1);
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
}
void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
{
union sfh_cmd_base cmd_base;
/* fill up command register */
memset(&cmd_base, 0, sizeof(cmd_base));
cmd_base.s.cmd_id = DISABLE_SENSOR;
cmd_base.s.period = 0;
cmd_base.s.sensor_id = sensor_idx;
writeq(0x0, privdata->mmio + AMD_C2P_MSG2);
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
}
void amd_stop_all_sensors(struct amd_mp2_dev *privdata)
{
union sfh_cmd_base cmd_base;
/* fill up command register */
memset(&cmd_base, 0, sizeof(cmd_base));
cmd_base.s.cmd_id = STOP_ALL_SENSORS;
cmd_base.s.period = 0;
cmd_base.s.sensor_id = 0;
writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
}
int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
{
int activestatus, num_of_sensors = 0;
privdata->activecontrolstatus = readl(privdata->mmio + AMD_P2C_MSG3);
activestatus = privdata->activecontrolstatus >> 4;
if (ACEL_EN & activestatus)
sensor_id[num_of_sensors++] = accel_idx;
if (GYRO_EN & activestatus)
sensor_id[num_of_sensors++] = gyro_idx;
if (MAGNO_EN & activestatus)
sensor_id[num_of_sensors++] = mag_idx;
if (ALS_EN & activestatus)
sensor_id[num_of_sensors++] = als_idx;
return num_of_sensors;
}
static void amd_mp2_pci_remove(void *privdata)
{
amd_sfh_hid_client_deinit(privdata);
amd_stop_all_sensors(privdata);
}
static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct amd_mp2_dev *privdata;
int rc;
privdata = devm_kzalloc(&pdev->dev, sizeof(*privdata), GFP_KERNEL);
if (!privdata)
return -ENOMEM;
privdata->pdev = pdev;
pci_set_drvdata(pdev, privdata);
rc = pcim_enable_device(pdev);
if (rc)
return rc;
rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME);
if (rc)
return rc;
privdata->mmio = pcim_iomap_table(pdev)[2];
pci_set_master(pdev);
rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
if (rc) {
rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
return rc;
}
rc = devm_add_action_or_reset(&pdev->dev, amd_mp2_pci_remove, privdata);
if (rc)
return rc;
return amd_sfh_hid_client_init(privdata);
}
static const struct pci_device_id amd_mp2_pci_tbl[] = {
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) },
{ }
};
MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl);
static struct pci_driver amd_mp2_pci_driver = {
.name = DRIVER_NAME,
.id_table = amd_mp2_pci_tbl,
.probe = amd_mp2_pci_probe,
};
module_pci_driver(amd_mp2_pci_driver);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Shyam Sundar S K <Shyam-sundar.S-k@amd.com>");
MODULE_AUTHOR("Sandeep Singh <Sandeep.singh@amd.com>");
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* AMD MP2 PCIe communication driver
* Copyright 2020 Advanced Micro Devices, Inc.
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
* Sandeep Singh <Sandeep.singh@amd.com>
*/
#ifndef PCIE_MP2_AMD_H
#define PCIE_MP2_AMD_H
#include <linux/pci.h>
#define PCI_DEVICE_ID_AMD_MP2 0x15E4
#define ENABLE_SENSOR 1
#define DISABLE_SENSOR 2
#define STOP_ALL_SENSORS 8
/* MP2 C2P Message Registers */
#define AMD_C2P_MSG0 0x10500
#define AMD_C2P_MSG1 0x10504
#define AMD_C2P_MSG2 0x10508
/* MP2 P2C Message Registers */
#define AMD_P2C_MSG3 0x1068C /* Supported Sensors info */
/* SFH Command register */
union sfh_cmd_base {
u32 ul;
struct {
u32 cmd_id : 8;
u32 sensor_id : 8;
u32 period : 16;
} s;
};
union sfh_cmd_param {
u32 ul;
struct {
u32 buf_layout : 2;
u32 buf_length : 6;
u32 rsvd : 24;
} s;
};
struct sfh_cmd_reg {
union sfh_cmd_base cmd_base;
union sfh_cmd_param cmd_param;
phys_addr_t phys_addr;
};
enum sensor_idx {
accel_idx = 0,
gyro_idx = 1,
mag_idx = 2,
als_idx = 19
};
struct amd_mp2_dev {
struct pci_dev *pdev;
struct amdtp_cl_data *cl_data;
void __iomem *mmio;
u32 activecontrolstatus;
};
struct amd_mp2_sensor_info {
u8 sensor_idx;
u32 period;
phys_addr_t phys_address;
};
void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info);
void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx);
void amd_stop_all_sensors(struct amd_mp2_dev *privdata);
int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id);
int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata);
int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata);
#endif
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD SFH Report Descriptor generator
* Copyright 2020 Advanced Micro Devices, Inc.
* Authors: Nehal Bakulchandra Shah <Nehal-Bakulchandra.Shah@amd.com>
* Sandeep Singh <sandeep.singh@amd.com>
*/
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/slab.h>
#include "amd_sfh_pcie.h"
#include "amd_sfh_hid_desc.h"
#include "amd_sfh_hid_report_desc.h"
#define AMD_SFH_FW_MULTIPLIER (1000)
#define HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM 0x41
#define HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM 0x51
#define HID_DEFAULT_REPORT_INTERVAL 0x50
#define HID_DEFAULT_MIN_VALUE 0X7F
#define HID_DEFAULT_MAX_VALUE 0x80
#define HID_DEFAULT_SENSITIVITY 0x7F
#define HID_USAGE_SENSOR_PROPERTY_CONNECTION_TYPE_PC_INTEGRATED_ENUM 0x01
/* state enums */
#define HID_USAGE_SENSOR_STATE_READY_ENUM 0x02
#define HID_USAGE_SENSOR_STATE_INITIALIZING_ENUM 0x05
#define HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM 0x04
int get_report_descriptor(int sensor_idx, u8 *rep_desc)
{
switch (sensor_idx) {
case accel_idx: /* accel */
memset(rep_desc, 0, sizeof(accel3_report_descriptor));
memcpy(rep_desc, accel3_report_descriptor,
sizeof(accel3_report_descriptor));
break;
case gyro_idx: /* gyro */
memset(rep_desc, 0, sizeof(gyro3_report_descriptor));
memcpy(rep_desc, gyro3_report_descriptor,
sizeof(gyro3_report_descriptor));
break;
case mag_idx: /* Magnetometer */
memset(rep_desc, 0, sizeof(comp3_report_descriptor));
memcpy(rep_desc, comp3_report_descriptor,
sizeof(comp3_report_descriptor));
break;
case als_idx: /* ambient light sensor */
memset(rep_desc, 0, sizeof(als_report_descriptor));
memcpy(rep_desc, als_report_descriptor,
sizeof(als_report_descriptor));
break;
default:
break;
}
return 0;
}
u32 get_descr_sz(int sensor_idx, int descriptor_name)
{
switch (sensor_idx) {
case accel_idx:
switch (descriptor_name) {
case descr_size:
return sizeof(accel3_report_descriptor);
case input_size:
return sizeof(struct accel3_input_report);
case feature_size:
return sizeof(struct accel3_feature_report);
}
break;
case gyro_idx:
switch (descriptor_name) {
case descr_size:
return sizeof(gyro3_report_descriptor);
case input_size:
return sizeof(struct gyro_input_report);
case feature_size:
return sizeof(struct gyro_feature_report);
}
break;
case mag_idx:
switch (descriptor_name) {
case descr_size:
return sizeof(comp3_report_descriptor);
case input_size:
return sizeof(struct magno_input_report);
case feature_size:
return sizeof(struct magno_feature_report);
}
break;
case als_idx:
switch (descriptor_name) {
case descr_size:
return sizeof(als_report_descriptor);
case input_size:
return sizeof(struct als_input_report);
case feature_size:
return sizeof(struct als_feature_report);
}
break;
default:
break;
}
return 0;
}
static void get_common_features(struct common_feature_property *common, int report_id)
{
common->report_id = report_id;
common->connection_type = HID_USAGE_SENSOR_PROPERTY_CONNECTION_TYPE_PC_INTEGRATED_ENUM;
common->report_state = HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM;
common->power_state = HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM;
common->sensor_state = HID_USAGE_SENSOR_STATE_INITIALIZING_ENUM;
common->report_interval = HID_DEFAULT_REPORT_INTERVAL;
}
u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report)
{
struct accel3_feature_report acc_feature;
struct gyro_feature_report gyro_feature;
struct magno_feature_report magno_feature;
struct als_feature_report als_feature;
u8 report_size = 0;
if (!feature_report)
return report_size;
switch (sensor_idx) {
case accel_idx: /* accel */
get_common_features(&acc_feature.common_property, report_id);
acc_feature.accel_change_sesnitivity = HID_DEFAULT_SENSITIVITY;
acc_feature.accel_sensitivity_min = HID_DEFAULT_MIN_VALUE;
acc_feature.accel_sensitivity_max = HID_DEFAULT_MAX_VALUE;
memcpy(feature_report, &acc_feature, sizeof(acc_feature));
report_size = sizeof(acc_feature);
break;
case gyro_idx: /* gyro */
get_common_features(&gyro_feature.common_property, report_id);
gyro_feature.gyro_change_sesnitivity = HID_DEFAULT_SENSITIVITY;
gyro_feature.gyro_sensitivity_min = HID_DEFAULT_MIN_VALUE;
gyro_feature.gyro_sensitivity_max = HID_DEFAULT_MAX_VALUE;
memcpy(feature_report, &gyro_feature, sizeof(gyro_feature));
report_size = sizeof(gyro_feature);
break;
case mag_idx: /* Magnetometer */
get_common_features(&magno_feature.common_property, report_id);
magno_feature.magno_headingchange_sensitivity = HID_DEFAULT_SENSITIVITY;
magno_feature.heading_min = HID_DEFAULT_MIN_VALUE;
magno_feature.heading_max = HID_DEFAULT_MAX_VALUE;
magno_feature.flux_change_sensitivity = HID_DEFAULT_MIN_VALUE;
magno_feature.flux_min = HID_DEFAULT_MIN_VALUE;
magno_feature.flux_max = HID_DEFAULT_MAX_VALUE;
memcpy(feature_report, &magno_feature, sizeof(magno_feature));
report_size = sizeof(magno_feature);
break;
case als_idx: /* ambient light sensor */
get_common_features(&als_feature.common_property, report_id);
als_feature.als_change_sesnitivity = HID_DEFAULT_SENSITIVITY;
als_feature.als_sensitivity_min = HID_DEFAULT_MIN_VALUE;
als_feature.als_sensitivity_max = HID_DEFAULT_MAX_VALUE;
memcpy(feature_report, &als_feature, sizeof(als_feature));
report_size = sizeof(als_feature);
break;
default:
break;
}
return report_size;
}
static void get_common_inputs(struct common_input_property *common, int report_id)
{
common->report_id = report_id;
common->sensor_state = HID_USAGE_SENSOR_STATE_READY_ENUM;
common->event_type = HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM;
}
u8 get_input_report(int sensor_idx, int report_id, u8 *input_report, u32 *sensor_virt_addr)
{
struct accel3_input_report acc_input;
struct gyro_input_report gyro_input;
struct magno_input_report magno_input;
struct als_input_report als_input;
u8 report_size = 0;
if (!sensor_virt_addr || !input_report)
return report_size;
switch (sensor_idx) {
case accel_idx: /* accel */
get_common_inputs(&acc_input.common_property, report_id);
acc_input.in_accel_x_value = (int)sensor_virt_addr[0] / AMD_SFH_FW_MULTIPLIER;
acc_input.in_accel_y_value = (int)sensor_virt_addr[1] / AMD_SFH_FW_MULTIPLIER;
acc_input.in_accel_z_value = (int)sensor_virt_addr[2] / AMD_SFH_FW_MULTIPLIER;
memcpy(input_report, &acc_input, sizeof(acc_input));
report_size = sizeof(acc_input);
break;
case gyro_idx: /* gyro */
get_common_inputs(&gyro_input.common_property, report_id);
gyro_input.in_angel_x_value = (int)sensor_virt_addr[0] / AMD_SFH_FW_MULTIPLIER;
gyro_input.in_angel_y_value = (int)sensor_virt_addr[1] / AMD_SFH_FW_MULTIPLIER;
gyro_input.in_angel_z_value = (int)sensor_virt_addr[2] / AMD_SFH_FW_MULTIPLIER;
memcpy(input_report, &gyro_input, sizeof(gyro_input));
report_size = sizeof(gyro_input);
break;
case mag_idx: /* Magnetometer */
get_common_inputs(&magno_input.common_property, report_id);
magno_input.in_magno_x = (int)sensor_virt_addr[0] / AMD_SFH_FW_MULTIPLIER;
magno_input.in_magno_y = (int)sensor_virt_addr[1] / AMD_SFH_FW_MULTIPLIER;
magno_input.in_magno_z = (int)sensor_virt_addr[2] / AMD_SFH_FW_MULTIPLIER;
magno_input.in_magno_accuracy = (u16)sensor_virt_addr[3] / AMD_SFH_FW_MULTIPLIER;
memcpy(input_report, &magno_input, sizeof(magno_input));
report_size = sizeof(magno_input);
break;
case als_idx: /* Als */
get_common_inputs(&als_input.common_property, report_id);
als_input.illuminance_value = (int)sensor_virt_addr[0] / AMD_SFH_FW_MULTIPLIER;
report_size = sizeof(als_input);
memcpy(input_report, &als_input, sizeof(als_input));
break;
default:
break;
}
return report_size;
}
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* HID report descriptors, structures and routines
* Copyright 2020 Advanced Micro Devices, Inc.
* Authors: Nehal Bakulchandra Shah <Nehal-bakulchandra.shah@amd.com>
* Sandeep Singh <Sandeep.singh@amd.com>
*/
#ifndef AMD_SFH_HID_DESCRIPTOR_H
#define AMD_SFH_HID_DESCRIPTOR_H
enum desc_type {
/* Report descriptor name */
descr_size = 1,
input_size,
feature_size,
};
struct common_feature_property {
/* common properties */
u8 report_id;
u8 connection_type;
u8 report_state;
u8 power_state;
u8 sensor_state;
u32 report_interval;
} __packed;
struct common_input_property {
/* common properties */
u8 report_id;
u8 sensor_state;
u8 event_type;
} __packed;
struct accel3_feature_report {
struct common_feature_property common_property;
/* properties specific to this sensor */
u16 accel_change_sesnitivity;
s16 accel_sensitivity_max;
s16 accel_sensitivity_min;
} __packed;
struct accel3_input_report {
struct common_input_property common_property;
/* values specific to this sensor */
int in_accel_x_value;
int in_accel_y_value;
int in_accel_z_value;
/* include if required to support the "shake" event */
u8 in_accel_shake_detection;
} __packed;
struct gyro_feature_report {
struct common_feature_property common_property;
/* properties specific to this sensor */
u16 gyro_change_sesnitivity;
s16 gyro_sensitivity_max;
s16 gyro_sensitivity_min;
} __packed;
struct gyro_input_report {
struct common_input_property common_property;
/* values specific to this sensor */
int in_angel_x_value;
int in_angel_y_value;
int in_angel_z_value;
} __packed;
struct magno_feature_report {
struct common_feature_property common_property;
/*properties specific to this sensor */
u16 magno_headingchange_sensitivity;
s16 heading_min;
s16 heading_max;
u16 flux_change_sensitivity;
s16 flux_min;
s16 flux_max;
} __packed;
struct magno_input_report {
struct common_input_property common_property;
int in_magno_x;
int in_magno_y;
int in_magno_z;
int in_magno_accuracy;
} __packed;
struct als_feature_report {
struct common_feature_property common_property;
/* properties specific to this sensor */
u16 als_change_sesnitivity;
s16 als_sensitivity_max;
s16 als_sensitivity_min;
} __packed;
struct als_input_report {
struct common_input_property common_property;
/* values specific to this sensor */
int illuminance_value;
} __packed;
int get_report_descriptor(int sensor_idx, u8 rep_desc[]);
u32 get_descr_sz(int sensor_idx, int descriptor_name);
u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report);
u8 get_input_report(int sensor_idx, int report_id, u8 *input_report, u32 *sensor_virt_addr);
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment