summaryrefslogtreecommitdiffstats
path: root/drivers/staging/greybus
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/staging/greybus
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/staging/greybus')
-rw-r--r--drivers/staging/greybus/Documentation/firmware/authenticate.c94
-rw-r--r--drivers/staging/greybus/Documentation/firmware/firmware-management333
-rw-r--r--drivers/staging/greybus/Documentation/firmware/firmware.c218
-rw-r--r--drivers/staging/greybus/Documentation/sysfs-bus-greybus275
-rw-r--r--drivers/staging/greybus/Kconfig216
-rw-r--r--drivers/staging/greybus/Makefile73
-rw-r--r--drivers/staging/greybus/TODO5
-rw-r--r--drivers/staging/greybus/arche-apb-ctrl.c489
-rw-r--r--drivers/staging/greybus/arche-platform.c667
-rw-r--r--drivers/staging/greybus/arche_platform.h28
-rw-r--r--drivers/staging/greybus/audio_apbridgea.c205
-rw-r--r--drivers/staging/greybus/audio_apbridgea.h133
-rw-r--r--drivers/staging/greybus/audio_codec.c1094
-rw-r--r--drivers/staging/greybus/audio_codec.h243
-rw-r--r--drivers/staging/greybus/audio_gb.c225
-rw-r--r--drivers/staging/greybus/audio_helper.c179
-rw-r--r--drivers/staging/greybus/audio_helper.h17
-rw-r--r--drivers/staging/greybus/audio_manager.c186
-rw-r--r--drivers/staging/greybus/audio_manager.h81
-rw-r--r--drivers/staging/greybus/audio_manager_module.c241
-rw-r--r--drivers/staging/greybus/audio_manager_private.h27
-rw-r--r--drivers/staging/greybus/audio_manager_sysfs.c101
-rw-r--r--drivers/staging/greybus/audio_module.c479
-rw-r--r--drivers/staging/greybus/audio_topology.c1452
-rw-r--r--drivers/staging/greybus/authentication.c426
-rw-r--r--drivers/staging/greybus/bootrom.c527
-rw-r--r--drivers/staging/greybus/camera.c1382
-rw-r--r--drivers/staging/greybus/firmware.h41
-rw-r--r--drivers/staging/greybus/fw-core.c311
-rw-r--r--drivers/staging/greybus/fw-download.c466
-rw-r--r--drivers/staging/greybus/fw-management.c720
-rw-r--r--drivers/staging/greybus/gb-camera.h126
-rw-r--r--drivers/staging/greybus/gbphy.c357
-rw-r--r--drivers/staging/greybus/gbphy.h109
-rw-r--r--drivers/staging/greybus/gpio.c634
-rw-r--r--drivers/staging/greybus/greybus_authentication.h74
-rw-r--r--drivers/staging/greybus/greybus_firmware.h75
-rw-r--r--drivers/staging/greybus/hid.c519
-rw-r--r--drivers/staging/greybus/i2c.c321
-rw-r--r--drivers/staging/greybus/light.c1357
-rw-r--r--drivers/staging/greybus/log.c132
-rw-r--r--drivers/staging/greybus/loopback.c1177
-rw-r--r--drivers/staging/greybus/power_supply.c1139
-rw-r--r--drivers/staging/greybus/pwm.c352
-rw-r--r--drivers/staging/greybus/raw.c379
-rw-r--r--drivers/staging/greybus/sdio.c883
-rw-r--r--drivers/staging/greybus/spi.c78
-rw-r--r--drivers/staging/greybus/spilib.c570
-rw-r--r--drivers/staging/greybus/spilib.h26
-rw-r--r--drivers/staging/greybus/uart.c1027
-rw-r--r--drivers/staging/greybus/usb.c245
-rw-r--r--drivers/staging/greybus/vibrator.c248
52 files changed, 20762 insertions, 0 deletions
diff --git a/drivers/staging/greybus/Documentation/firmware/authenticate.c b/drivers/staging/greybus/Documentation/firmware/authenticate.c
new file mode 100644
index 0000000000..3d2c6f88a1
--- /dev/null
+++ b/drivers/staging/greybus/Documentation/firmware/authenticate.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Sample code to test CAP protocol
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "../../greybus_authentication.h"
+
+struct cap_ioc_get_endpoint_uid uid;
+struct cap_ioc_get_ims_certificate cert = {
+ .certificate_class = 0,
+ .certificate_id = 0,
+};
+
+struct cap_ioc_authenticate authenticate = {
+ .auth_type = 0,
+ .challenge = {0},
+};
+
+int main(int argc, char *argv[])
+{
+ unsigned int timeout = 10000;
+ char *capdev;
+ int fd, ret;
+
+ /* Make sure arguments are correct */
+ if (argc != 2) {
+ printf("\nUsage: ./firmware <Path of the gb-cap-X dev>\n");
+ return 0;
+ }
+
+ capdev = argv[1];
+
+ printf("Opening %s authentication device\n", capdev);
+
+ fd = open(capdev, O_RDWR);
+ if (fd < 0) {
+ printf("Failed to open: %s\n", capdev);
+ return -1;
+ }
+
+ /* Get UID */
+ printf("Get UID\n");
+
+ ret = ioctl(fd, CAP_IOC_GET_ENDPOINT_UID, &uid);
+ if (ret < 0) {
+ printf("Failed to get UID: %s (%d)\n", capdev, ret);
+ ret = -1;
+ goto close_fd;
+ }
+
+ printf("UID received: 0x%llx\n", *(unsigned long long int *)(uid.uid));
+
+ /* Get certificate */
+ printf("Get IMS certificate\n");
+
+ ret = ioctl(fd, CAP_IOC_GET_IMS_CERTIFICATE, &cert);
+ if (ret < 0) {
+ printf("Failed to get IMS certificate: %s (%d)\n", capdev, ret);
+ ret = -1;
+ goto close_fd;
+ }
+
+ printf("IMS Certificate size: %d\n", cert.cert_size);
+
+ /* Authenticate */
+ printf("Authenticate module\n");
+
+ memcpy(authenticate.uid, uid.uid, 8);
+
+ ret = ioctl(fd, CAP_IOC_AUTHENTICATE, &authenticate);
+ if (ret < 0) {
+ printf("Failed to authenticate module: %s (%d)\n", capdev, ret);
+ ret = -1;
+ goto close_fd;
+ }
+
+ printf("Authenticated, result (%02x), sig-size (%02x)\n",
+ authenticate.result_code, authenticate.signature_size);
+
+close_fd:
+ close(fd);
+
+ return ret;
+}
diff --git a/drivers/staging/greybus/Documentation/firmware/firmware-management b/drivers/staging/greybus/Documentation/firmware/firmware-management
new file mode 100644
index 0000000000..7918257e5b
--- /dev/null
+++ b/drivers/staging/greybus/Documentation/firmware/firmware-management
@@ -0,0 +1,333 @@
+
+Firmware Management
+-------------------
+ Copyright 2016 Google Inc.
+ Copyright 2016 Linaro Ltd.
+
+Interface-Manifest
+------------------
+
+All firmware packages on the Modules or Interfaces are managed by a special
+Firmware Management Protocol. To support Firmware Management by the AP, the
+Interface Manifest shall at least contain the Firmware Management Bundle and a
+Firmware Management Protocol CPort within it.
+
+The bundle may contain additional CPorts based on the extra functionality
+required to manage firmware packages.
+
+For example, this is how the Firmware Management part of the Interface Manifest
+may look like:
+
+ ; Firmware Management Bundle (Bundle 1):
+ [bundle-descriptor 1]
+ class = 0x16
+
+ ; (Mandatory) Firmware Management Protocol on CPort 1
+ [cport-descriptor 2]
+ bundle = 1
+ protocol = 0x18
+
+ ; (Optional) Firmware Download Protocol on CPort 2
+ [cport-descriptor 1]
+ bundle = 1
+ protocol = 0x17
+
+ ; (Optional) SPI protocol on CPort 3
+ [cport-descriptor 3]
+ bundle = 1
+ protocol = 0x0b
+
+ ; (Optional) Component Authentication Protocol (CAP) on CPort 4
+ [cport-descriptor 4]
+ bundle = 1
+ protocol = 0x19
+
+
+Sysfs Interfaces - Firmware Management
+--------------------------------------
+
+The Firmware Management Protocol interacts with Userspace using the character
+device interface. The character device will be present in /dev/ directory
+and will be named gb-fw-mgmt-<N>. The number <N> is assigned at runtime.
+
+Identifying the Character Device
+================================
+
+There can be multiple devices present in /dev/ directory with name gb-fw-mgmt-N
+and user first needs to identify the character device used for
+firmware-management for a particular interface.
+
+The Firmware Management core creates a device of class 'gb_fw_mgmt', which shall
+be used by the user to identify the right character device for it. The class
+device is created within the Bundle directory for a particular Interface.
+
+For example this is how the class-device can be present:
+
+/sys/bus/greybus/devices/1-1/1-1.1/1-1.1.1/gb_fw_mgmt/gb-fw-mgmt-0
+
+The last name in this path: gb-fw-mgmt-0 is precisely the name of the char
+device and so the device in this case will be:
+
+/dev/gb-fw-mgmt-0.
+
+Operations on the Char device
+=============================
+
+The Character device (gb-fw-mgmt-0 in example) can be opened by the userspace
+application and it can perform various 'ioctl' operations on the device. The
+device doesn't support any read/write operations.
+
+Following are the IOCTLs and their data structures available to the user:
+
+/* IOCTL support */
+#define GB_FW_LOAD_METHOD_UNIPRO 0x01
+#define GB_FW_LOAD_METHOD_INTERNAL 0x02
+
+#define GB_FW_LOAD_STATUS_FAILED 0x00
+#define GB_FW_LOAD_STATUS_UNVALIDATED 0x01
+#define GB_FW_LOAD_STATUS_VALIDATED 0x02
+#define GB_FW_LOAD_STATUS_VALIDATION_FAILED 0x03
+
+#define GB_FW_BACKEND_FW_STATUS_SUCCESS 0x01
+#define GB_FW_BACKEND_FW_STATUS_FAIL_FIND 0x02
+#define GB_FW_BACKEND_FW_STATUS_FAIL_FETCH 0x03
+#define GB_FW_BACKEND_FW_STATUS_FAIL_WRITE 0x04
+#define GB_FW_BACKEND_FW_STATUS_INT 0x05
+#define GB_FW_BACKEND_FW_STATUS_RETRY 0x06
+#define GB_FW_BACKEND_FW_STATUS_NOT_SUPPORTED 0x07
+
+#define GB_FW_BACKEND_VERSION_STATUS_SUCCESS 0x01
+#define GB_FW_BACKEND_VERSION_STATUS_NOT_AVAILABLE 0x02
+#define GB_FW_BACKEND_VERSION_STATUS_NOT_SUPPORTED 0x03
+#define GB_FW_BACKEND_VERSION_STATUS_RETRY 0x04
+#define GB_FW_BACKEND_VERSION_STATUS_FAIL_INT 0x05
+
+
+struct fw_mgmt_ioc_get_intf_version {
+ __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+ __u16 major;
+ __u16 minor;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_get_backend_version {
+ __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+ __u16 major;
+ __u16 minor;
+ __u8 status;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_intf_load_and_validate {
+ __u8 firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE];
+ __u8 load_method;
+ __u8 status;
+ __u16 major;
+ __u16 minor;
+} __packed;
+
+struct fw_mgmt_ioc_backend_fw_update {
+ __u8 firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE];
+ __u8 status;
+} __packed;
+
+#define FW_MGMT_IOCTL_BASE 'S'
+#define FW_MGMT_IOC_GET_INTF_FW _IOR(FW_MGMT_IOCTL_BASE, 0, struct fw_mgmt_ioc_get_intf_version)
+#define FW_MGMT_IOC_GET_BACKEND_FW _IOWR(FW_MGMT_IOCTL_BASE, 1, struct fw_mgmt_ioc_get_backend_version)
+#define FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE _IOWR(FW_MGMT_IOCTL_BASE, 2, struct fw_mgmt_ioc_intf_load_and_validate)
+#define FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE _IOWR(FW_MGMT_IOCTL_BASE, 3, struct fw_mgmt_ioc_backend_fw_update)
+#define FW_MGMT_IOC_SET_TIMEOUT_MS _IOW(FW_MGMT_IOCTL_BASE, 4, unsigned int)
+#define FW_MGMT_IOC_MODE_SWITCH _IO(FW_MGMT_IOCTL_BASE, 5)
+
+1. FW_MGMT_IOC_GET_INTF_FW:
+
+ This ioctl shall be used by the user to get the version and firmware-tag of
+ the currently running Interface Firmware. All the fields of the 'struct
+ fw_mgmt_ioc_get_fw' are filled by the kernel.
+
+2. FW_MGMT_IOC_GET_BACKEND_FW:
+
+ This ioctl shall be used by the user to get the version of a currently
+ running Backend Interface Firmware identified by a firmware-tag. The user is
+ required to fill the 'firmware_tag' field of the 'struct fw_mgmt_ioc_get_fw'
+ in this case. The 'major' and 'minor' fields are set by the kernel in
+ response.
+
+3. FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE:
+
+ This ioctl shall be used by the user to load an Interface Firmware package on
+ an Interface. The user needs to fill the 'firmware_tag' and 'load_method'
+ fields of the 'struct fw_mgmt_ioc_intf_load_and_validate'. The 'status',
+ 'major' and 'minor' fields are set by the kernel in response.
+
+4. FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE:
+
+ This ioctl shall be used by the user to request an Interface to update a
+ Backend Interface Firmware. The user is required to fill the 'firmware_tag'
+ field of the 'struct fw_mgmt_ioc_get_fw' in this case. The 'status' field is
+ set by the kernel in response.
+
+5. FW_MGMT_IOC_SET_TIMEOUT_MS:
+
+ This ioctl shall be used by the user to increase the timeout interval within
+ which the firmware must get loaded by the Module. The default timeout is 1
+ second. The user needs to pass the timeout in milliseconds.
+
+6. FW_MGMT_IOC_MODE_SWITCH:
+
+ This ioctl shall be used by the user to mode-switch the module to the
+ previously loaded interface firmware. If the interface firmware isn't loaded
+ previously, or if another unsuccessful FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE
+ operation is started after loading interface firmware, then the firmware core
+ wouldn't allow mode-switch.
+
+
+Sysfs Interfaces - Authentication
+---------------------------------
+
+The Component Authentication Protocol interacts with Userspace using the
+character device interface. The character device will be present in /dev/
+directory and will be named gb-authenticate-<N>. The number <N> is assigned at
+runtime.
+
+Identifying the Character Device
+================================
+
+There can be multiple devices present in /dev/ directory with name
+gb-authenticate-N and user first needs to identify the character device used for
+authentication a of particular interface.
+
+The Authentication core creates a device of class 'gb_authenticate', which shall
+be used by the user to identify the right character device for it. The class
+device is created within the Bundle directory for a particular Interface.
+
+For example this is how the class-device can be present:
+
+/sys/bus/greybus/devices/1-1/1-1.1/1-1.1.1/gb_authenticate/gb-authenticate-0
+
+The last name in this path: gb-authenticate-0 is precisely the name of the char
+device and so the device in this case will be:
+
+/dev/gb-authenticate-0.
+
+Operations on the Char device
+=============================
+
+The Character device (/dev/gb-authenticate-0 in above example) can be opened by
+the userspace application and it can perform various 'ioctl' operations on the
+device. The device doesn't support any read/write operations.
+
+Following are the IOCTLs and their data structures available to the user:
+
+#define CAP_CERTIFICATE_MAX_SIZE 1600
+#define CAP_SIGNATURE_MAX_SIZE 320
+
+/* Certificate class types */
+#define CAP_CERT_IMS_EAPC 0x00000001
+#define CAP_CERT_IMS_EASC 0x00000002
+#define CAP_CERT_IMS_EARC 0x00000003
+#define CAP_CERT_IMS_IAPC 0x00000004
+#define CAP_CERT_IMS_IASC 0x00000005
+#define CAP_CERT_IMS_IARC 0x00000006
+
+/* IMS Certificate response result codes */
+#define CAP_IMS_RESULT_CERT_FOUND 0x00
+#define CAP_IMS_RESULT_CERT_CLASS_INVAL 0x01
+#define CAP_IMS_RESULT_CERT_CORRUPT 0x02
+#define CAP_IMS_RESULT_CERT_NOT_FOUND 0x03
+
+/* Authentication types */
+#define CAP_AUTH_IMS_PRI 0x00000001
+#define CAP_AUTH_IMS_SEC 0x00000002
+#define CAP_AUTH_IMS_RSA 0x00000003
+
+/* Authenticate response result codes */
+#define CAP_AUTH_RESULT_CR_SUCCESS 0x00
+#define CAP_AUTH_RESULT_CR_BAD_TYPE 0x01
+#define CAP_AUTH_RESULT_CR_WRONG_EP 0x02
+#define CAP_AUTH_RESULT_CR_NO_KEY 0x03
+#define CAP_AUTH_RESULT_CR_SIG_FAIL 0x04
+
+
+/* IOCTL support */
+struct cap_ioc_get_endpoint_uid {
+ __u8 uid[8];
+} __attribute__ ((__packed__));
+
+struct cap_ioc_get_ims_certificate {
+ __u32 certificate_class;
+ __u32 certificate_id;
+
+ __u8 result_code;
+ __u32 cert_size;
+ __u8 certificate[CAP_CERTIFICATE_MAX_SIZE];
+} __attribute__ ((__packed__));
+
+struct cap_ioc_authenticate {
+ __u32 auth_type;
+ __u8 uid[8];
+ __u8 challenge[32];
+
+ __u8 result_code;
+ __u8 response[64];
+ __u32 signature_size;
+ __u8 signature[CAP_SIGNATURE_MAX_SIZE];
+} __attribute__ ((__packed__));
+
+#define CAP_IOCTL_BASE 'C'
+#define CAP_IOC_GET_ENDPOINT_UID _IOR(CAP_IOCTL_BASE, 0, struct cap_ioc_get_endpoint_uid)
+#define CAP_IOC_GET_IMS_CERTIFICATE _IOWR(CAP_IOCTL_BASE, 1, struct cap_ioc_get_ims_certificate)
+#define CAP_IOC_AUTHENTICATE _IOWR(CAP_IOCTL_BASE, 2, struct cap_ioc_authenticate)
+
+
+1. CAP_IOC_GET_ENDPOINT_UID:
+
+ This ioctl shall be used by the user to get the endpoint UID associated with
+ the Interface. All the fields of the 'struct cap_ioc_get_endpoint_uid' are
+ filled by the kernel.
+
+2. CAP_IOC_GET_IMS_CERTIFICATE:
+
+ This ioctl shall be used by the user to retrieve one of the available
+ cryptographic certificates held by the Interface for use in Component
+ Authentication. The user is required to fill the 'certificate_class' and
+ 'certificate_id' field of the 'struct cap_ioc_get_ims_certificate' in this
+ case. The other fields will be set by the kernel in response. The first
+ 'cert_size' bytes of the 'certificate' shall be read by the user and others
+ must be discarded.
+
+3. CAP_IOC_AUTHENTICATE:
+
+ This ioctl shall be used by the user to authenticate the Module attached to
+ an Interface. The user needs to fill the 'auth_type', 'uid', and 'challenge'
+ fields of the 'struct cap_ioc_authenticate'. The other fields will be set by
+ the kernel in response. The first 'signature_size' bytes of the 'signature'
+ shall be read by the user and others must be discarded.
+
+
+Sysfs Interfaces - Firmware Download
+------------------------------------
+
+The Firmware Download Protocol uses the existing Linux Kernel's Firmware class
+and the interface provided to userspace are described in:
+Documentation/firmware_class/.
+
+
+Sysfs Interfaces - SPI Flash
+----------------------------
+
+The SPI flash is exposed in userspace as a MTD device and is created
+within the Bundle directory. For example, this is how the path may look like:
+
+$ ls /sys/bus/greybus/devices/1-1/1-1.1/1-1.1.1/spi_master/spi32766/spi32766.0/mtd
+mtd0 mtd0ro
+
+
+Sample Applications
+-------------------
+
+The current directory also provides a firmware.c test application, which can be
+referenced while developing userspace application to talk to firmware-management
+protocol.
+
+The current directory also provides a authenticate.c test application, which can
+be referenced while developing userspace application to talk to
+component authentication protocol.
diff --git a/drivers/staging/greybus/Documentation/firmware/firmware.c b/drivers/staging/greybus/Documentation/firmware/firmware.c
new file mode 100644
index 0000000000..765d69faa9
--- /dev/null
+++ b/drivers/staging/greybus/Documentation/firmware/firmware.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Sample code to test firmware-management protocol
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "../../greybus_firmware.h"
+
+#define FW_DEV_DEFAULT "/dev/gb-fw-mgmt-0"
+#define FW_TAG_INT_DEFAULT "s3f"
+#define FW_TAG_BCND_DEFAULT "bf_01"
+#define FW_UPDATE_TYPE_DEFAULT 0
+#define FW_TIMEOUT_DEFAULT 10000
+
+static const char *firmware_tag;
+static const char *fwdev = FW_DEV_DEFAULT;
+static unsigned int fw_update_type = FW_UPDATE_TYPE_DEFAULT;
+static unsigned int fw_timeout = FW_TIMEOUT_DEFAULT;
+
+static struct fw_mgmt_ioc_get_intf_version intf_fw_info;
+static struct fw_mgmt_ioc_get_backend_version backend_fw_info;
+static struct fw_mgmt_ioc_intf_load_and_validate intf_load;
+static struct fw_mgmt_ioc_backend_fw_update backend_update;
+
+static void usage(void)
+{
+ printf("\nUsage: ./firmware <gb-fw-mgmt-X (default: gb-fw-mgmt-0)> <interface: 0, backend: 1 (default: 0)> <firmware-tag> (default: \"s3f\"/\"bf_01\") <timeout (default: 10000 ms)>\n");
+}
+
+static int update_intf_firmware(int fd)
+{
+ int ret;
+
+ /* Get Interface Firmware Version */
+ printf("Get Interface Firmware Version\n");
+
+ ret = ioctl(fd, FW_MGMT_IOC_GET_INTF_FW, &intf_fw_info);
+ if (ret < 0) {
+ printf("Failed to get interface firmware version: %s (%d)\n",
+ fwdev, ret);
+ return -1;
+ }
+
+ printf("Interface Firmware tag (%s), major (%d), minor (%d)\n",
+ intf_fw_info.firmware_tag, intf_fw_info.major,
+ intf_fw_info.minor);
+
+ /* Try Interface Firmware load over Unipro */
+ printf("Loading Interface Firmware\n");
+
+ intf_load.load_method = GB_FW_U_LOAD_METHOD_UNIPRO;
+ intf_load.status = 0;
+ intf_load.major = 0;
+ intf_load.minor = 0;
+
+ strncpy((char *)&intf_load.firmware_tag, firmware_tag,
+ GB_FIRMWARE_U_TAG_MAX_SIZE);
+
+ ret = ioctl(fd, FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE, &intf_load);
+ if (ret < 0) {
+ printf("Failed to load interface firmware: %s (%d)\n", fwdev,
+ ret);
+ return -1;
+ }
+
+ if (intf_load.status != GB_FW_U_LOAD_STATUS_VALIDATED &&
+ intf_load.status != GB_FW_U_LOAD_STATUS_UNVALIDATED) {
+ printf("Load status says loading failed: %d\n",
+ intf_load.status);
+ return -1;
+ }
+
+ printf("Interface Firmware (%s) Load done: major: %d, minor: %d, status: %d\n",
+ firmware_tag, intf_load.major, intf_load.minor,
+ intf_load.status);
+
+ /* Initiate Mode-switch to the newly loaded firmware */
+ printf("Initiate Mode switch\n");
+
+ ret = ioctl(fd, FW_MGMT_IOC_MODE_SWITCH);
+ if (ret < 0)
+ printf("Failed to initiate mode-switch (%d)\n", ret);
+
+ return ret;
+}
+
+static int update_backend_firmware(int fd)
+{
+ int ret;
+
+ /* Get Backend Firmware Version */
+ printf("Getting Backend Firmware Version\n");
+
+ strncpy((char *)&backend_fw_info.firmware_tag, firmware_tag,
+ GB_FIRMWARE_U_TAG_MAX_SIZE);
+
+retry_fw_version:
+ ret = ioctl(fd, FW_MGMT_IOC_GET_BACKEND_FW, &backend_fw_info);
+ if (ret < 0) {
+ printf("Failed to get backend firmware version: %s (%d)\n",
+ fwdev, ret);
+ return -1;
+ }
+
+ printf("Backend Firmware tag (%s), major (%d), minor (%d), status (%d)\n",
+ backend_fw_info.firmware_tag, backend_fw_info.major,
+ backend_fw_info.minor, backend_fw_info.status);
+
+ if (backend_fw_info.status == GB_FW_U_BACKEND_VERSION_STATUS_RETRY)
+ goto retry_fw_version;
+
+ if ((backend_fw_info.status != GB_FW_U_BACKEND_VERSION_STATUS_SUCCESS)
+ && (backend_fw_info.status != GB_FW_U_BACKEND_VERSION_STATUS_NOT_AVAILABLE)) {
+ printf("Failed to get backend firmware version: %s (%d)\n",
+ fwdev, backend_fw_info.status);
+ return -1;
+ }
+
+ /* Try Backend Firmware Update over Unipro */
+ printf("Updating Backend Firmware\n");
+
+ strncpy((char *)&backend_update.firmware_tag, firmware_tag,
+ GB_FIRMWARE_U_TAG_MAX_SIZE);
+
+retry_fw_update:
+ backend_update.status = 0;
+
+ ret = ioctl(fd, FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE, &backend_update);
+ if (ret < 0) {
+ printf("Failed to load backend firmware: %s (%d)\n", fwdev, ret);
+ return -1;
+ }
+
+ if (backend_update.status == GB_FW_U_BACKEND_FW_STATUS_RETRY) {
+ printf("Retrying firmware update: %d\n", backend_update.status);
+ goto retry_fw_update;
+ }
+
+ if (backend_update.status != GB_FW_U_BACKEND_FW_STATUS_SUCCESS) {
+ printf("Load status says loading failed: %d\n",
+ backend_update.status);
+ } else {
+ printf("Backend Firmware (%s) Load done: status: %d\n",
+ firmware_tag, backend_update.status);
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int fd, ret;
+ char *endptr;
+
+ if (argc > 1 &&
+ (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
+ usage();
+ return -1;
+ }
+
+ if (argc > 1)
+ fwdev = argv[1];
+
+ if (argc > 2)
+ fw_update_type = strtoul(argv[2], &endptr, 10);
+
+ if (argc > 3)
+ firmware_tag = argv[3];
+ else if (!fw_update_type)
+ firmware_tag = FW_TAG_INT_DEFAULT;
+ else
+ firmware_tag = FW_TAG_BCND_DEFAULT;
+
+ if (argc > 4)
+ fw_timeout = strtoul(argv[4], &endptr, 10);
+
+ printf("Trying Firmware update: fwdev: %s, type: %s, tag: %s, timeout: %u\n",
+ fwdev, fw_update_type == 0 ? "interface" : "backend",
+ firmware_tag, fw_timeout);
+
+ printf("Opening %s firmware management device\n", fwdev);
+
+ fd = open(fwdev, O_RDWR);
+ if (fd < 0) {
+ printf("Failed to open: %s\n", fwdev);
+ return -1;
+ }
+
+ /* Set Timeout */
+ printf("Setting timeout to %u ms\n", fw_timeout);
+
+ ret = ioctl(fd, FW_MGMT_IOC_SET_TIMEOUT_MS, &fw_timeout);
+ if (ret < 0) {
+ printf("Failed to set timeout: %s (%d)\n", fwdev, ret);
+ ret = -1;
+ goto close_fd;
+ }
+
+ if (!fw_update_type)
+ ret = update_intf_firmware(fd);
+ else
+ ret = update_backend_firmware(fd);
+
+close_fd:
+ close(fd);
+
+ return ret;
+}
diff --git a/drivers/staging/greybus/Documentation/sysfs-bus-greybus b/drivers/staging/greybus/Documentation/sysfs-bus-greybus
new file mode 100644
index 0000000000..2e998966cb
--- /dev/null
+++ b/drivers/staging/greybus/Documentation/sysfs-bus-greybus
@@ -0,0 +1,275 @@
+What: /sys/bus/greybus/devices/greybusN
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The "root" greybus device for the Greybus device tree, or bus,
+ where N is a dynamically assigned 1-based id.
+
+What: /sys/bus/greybus/devices/greybusN/bus_id
+Date: April 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The ID of the "root" greybus device, or bus.
+
+What: /sys/bus/greybus/devices/N-M
+Date: March 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ A Module M on the bus N, where M is the 1-byte interface
+ ID of the module's primary interface.
+
+What: /sys/bus/greybus/devices/N-M/eject
+Date: March 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Writing a non-zero argument to this attibute disables the
+ module's interfaces before physically ejecting it.
+
+What: /sys/bus/greybus/devices/N-M/module_id
+Date: March 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The ID of a Greybus module, corresponding to the ID of its
+ primary interface.
+
+What: /sys/bus/greybus/devices/N-M/num_interfaces
+Date: March 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The number of interfaces of a module.
+
+What: /sys/bus/greybus/devices/N-M.I
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ An Interface I on the bus N and module N-M, where I is the
+ 1-byte interface ID.
+
+What: /sys/bus/greybus/devices/N-M.I/current_now
+Date: March 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Current measurement of the interface in microamps (uA)
+
+What: /sys/bus/greybus/devices/N-M.I/ddbl1_manufacturer_id
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Unipro Device Descriptor Block Level 1 manufacturer ID for the
+ greybus Interface.
+
+What: /sys/bus/greybus/devices/N-M.I/ddbl1_product_id
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Unipro Device Descriptor Block Level 1 product ID for the
+ greybus Interface.
+
+What: /sys/bus/greybus/devices/N-M.I/interface_id
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The ID of a Greybus interface.
+
+What: /sys/bus/greybus/devices/N-M.I/interface_type
+Date: June 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The type of a Greybus interface; "dummy", "unipro", "greybus",
+ or "unknown".
+
+What: /sys/bus/greybus/devices/N-M.I/power_now
+Date: March 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Power measurement of the interface in microwatts (uW)
+
+What: /sys/bus/greybus/devices/N-M.I/power_state
+Date: March 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ This file reflects the power state of a Greybus interface. If
+ the value read from it is "on", then power is currently
+ supplied to the interface. Otherwise it will read "off" and
+ power is currently not supplied to the interface.
+
+ If the value read is "off", then writing "on" (or '1', 'y',
+ 'Y') to this file will enable power to the interface and an
+ attempt to boot and possibly enumerate it will be made. Note
+ that on errors, the interface will again be powered down.
+
+ If the value read is "on", then writing "off" (or '0', 'n',
+ 'N') to this file will power down the interface.
+
+What: /sys/bus/greybus/devices/N-M.I/product_id
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Product ID of a Greybus interface.
+
+What: /sys/bus/greybus/devices/N-M.I/serial_number
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Serial Number of the Greybus interface, represented by a 64 bit
+ hexadecimal number.
+
+What: /sys/bus/greybus/devices/N-M.I/vendor_id
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Vendor ID of a Greybus interface.
+
+What: /sys/bus/greybus/devices/N-M.I/voltage_now
+Date: March 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Voltage measurement of the interface in microvolts (uV)
+
+What: /sys/bus/greybus/devices/N-M.I.ctrl
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Abstract control device for interface I that represents the
+ current mode of an enumerated Greybus interface.
+
+What: /sys/bus/greybus/devices/N-M.I.ctrl/product_string
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Product ID string of a Greybus interface.
+
+What: /sys/bus/greybus/devices/N-M.I.ctrl/vendor_string
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Vendor ID string of a Greybus interface.
+
+What: /sys/bus/greybus/devices/N-M.I.B
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ A bundle B on the Interface I, B is replaced by a 1-byte
+ number representing the bundle.
+
+What: /sys/bus/greybus/devices/N-M.I.B/bundle_class
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The greybus class of the bundle B.
+
+What: /sys/bus/greybus/devices/N-M.I.B/bundle_id
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The interface-unique id of the bundle B.
+
+What: /sys/bus/greybus/devices/N-M.I.B/gpbX
+Date: April 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The General Purpose Bridged PHY device of the bundle B,
+ where X is a dynamically assigned 0-based id.
+
+What: /sys/bus/greybus/devices/N-M.I.B/state
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ A bundle has a state that is managed by the userspace
+ Endo process. This file allows that Endo to signal
+ other Android HALs that the state of the bundle has
+ changed to a specific value. When written to, any
+ process watching the file will be woken up, and the new
+ value can be read. It's a "poor-man's IPC", yes, but
+ simplifies the Android userspace code immensely.
+
+What: /sys/bus/greybus/devices/N-svc
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The singleton SVC device of bus N.
+
+What: /sys/bus/greybus/devices/N-svc/ap_intf_id
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The AP interface ID, a 1-byte non-zero integer which
+ defines the position of the AP module on the frame.
+ The interface positions are defined in the GMP
+ Module Developer Kit.
+
+What: /sys/bus/greybus/devices/N-svc/endo_id
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The Endo ID, which is a 2-byte hexadecimal value
+ defined by the Endo layout scheme, documented in
+ the GMP Module Developer Kit.
+
+What: /sys/bus/greybus/devices/N-svc/intf_eject
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ Write the number of the interface that you wish to
+ forcibly eject from the system.
+
+What: /sys/bus/greybus/devices/N-svc/version
+Date: October 2015
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ The version number of the firmware in the SVC device.
+
+What: /sys/bus/greybus/devices/N-svc/watchdog
+Date: October 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ If the SVC watchdog is enabled or not. Writing 0 to this
+ file will disable the watchdog, writing 1 will enable it.
+
+What: /sys/bus/greybus/devices/N-svc/watchdog_action
+Date: July 2016
+KernelVersion: 4.XX
+Contact: Greg Kroah-Hartman <greg@kroah.com>
+Description:
+ This attribute indicates the action to be performed upon SVC
+ watchdog bite.
+
+ The action can be one of the "reset" or "panic". Writing either
+ one of the "reset" or "panic" will change the behavior of SVC
+ watchdog bite. Default value is "reset".
+
+ "reset" means the UniPro subsystem is to be reset.
+
+ "panic" means SVC watchdog bite will cause kernel to panic.
diff --git a/drivers/staging/greybus/Kconfig b/drivers/staging/greybus/Kconfig
new file mode 100644
index 0000000000..927cfa4bc9
--- /dev/null
+++ b/drivers/staging/greybus/Kconfig
@@ -0,0 +1,216 @@
+# SPDX-License-Identifier: GPL-2.0
+if GREYBUS
+
+config GREYBUS_AUDIO
+ tristate "Greybus Audio Class driver"
+ depends on SOUND && SND_SOC
+ help
+ Select this option if you have a device that follows the
+ Greybus Audio Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-audio.ko
+
+config GREYBUS_AUDIO_APB_CODEC
+ tristate "Greybus APBridge Audio codec driver"
+ depends on SND_SOC && GREYBUS_AUDIO
+ help
+ Select this option if you have a Toshiba APB device that has I2S
+ ports and acts as a Greybus "Dummy codec". This device is a
+ bridge from an APB-I2S port to a Unipro network.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-audio-codec.ko
+
+
+config GREYBUS_BOOTROM
+ tristate "Greybus Bootrom Class driver"
+ help
+ Select this option if you have a device that follows the
+ Greybus Bootrom Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-bootrom.ko
+
+config GREYBUS_CAMERA
+ tristate "Greybus Camera Class driver"
+ depends on MEDIA_SUPPORT && LEDS_CLASS_FLASH && BROKEN
+ help
+ Select this option if you have a device that follows the
+ Greybus Camera Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-camera.ko
+
+config GREYBUS_FIRMWARE
+ tristate "Greybus Firmware Download Class driver"
+ depends on SPI
+ help
+ Select this option if you have a device that follows the
+ Greybus Firmware Download Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-firmware.ko
+
+config GREYBUS_HID
+ tristate "Greybus HID Class driver"
+ depends on HID && INPUT
+ help
+ Select this option if you have a device that follows the
+ Greybus HID Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-hid.ko
+
+config GREYBUS_LIGHT
+ tristate "Greybus LED Class driver"
+ depends on LEDS_CLASS
+ help
+ Select this option if you have a device that follows the
+ Greybus LED Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-light.ko
+
+config GREYBUS_LOG
+ tristate "Greybus Debug Log Class driver"
+ help
+ Select this option if you have a device that follows the
+ Greybus Debug Log Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-log.ko
+
+config GREYBUS_LOOPBACK
+ tristate "Greybus Loopback Class driver"
+ help
+ Select this option if you have a device that follows the
+ Greybus Debug Log Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-log.ko
+
+config GREYBUS_POWER
+ tristate "Greybus Powersupply Class driver"
+ depends on POWER_SUPPLY
+ help
+ Select this option if you have a device that follows the
+ Greybus Powersupply Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-power-supply.ko
+
+config GREYBUS_RAW
+ tristate "Greybus Raw Class driver"
+ help
+ Select this option if you have a device that follows the
+ Greybus Raw Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-raw.ko
+
+config GREYBUS_VIBRATOR
+ tristate "Greybus Vibrator Motor Class driver"
+ help
+ Select this option if you have a device that follows the
+ Greybus Vibrator Motor Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-vibrator.ko
+
+menuconfig GREYBUS_BRIDGED_PHY
+ tristate "Greybus Bridged PHY Class drivers"
+ help
+ Select this option to pick from a variety of Greybus Bridged
+ PHY class drivers. These drivers emulate a number of
+ different "traditional" busses by tunneling them over Greybus.
+ Examples of this include serial, SPI, USB, and others.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-phy.ko
+
+if GREYBUS_BRIDGED_PHY
+
+config GREYBUS_GPIO
+ tristate "Greybus GPIO Bridged PHY driver"
+ depends on GPIOLIB
+ select GPIOLIB_IRQCHIP
+ help
+ Select this option if you have a device that follows the
+ Greybus GPIO Bridged PHY Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-gpio.ko
+
+config GREYBUS_I2C
+ tristate "Greybus I2C Bridged PHY driver"
+ depends on I2C
+ help
+ Select this option if you have a device that follows the
+ Greybus I2C Bridged PHY Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-i2c.ko
+
+config GREYBUS_PWM
+ tristate "Greybus PWM Bridged PHY driver"
+ depends on PWM
+ help
+ Select this option if you have a device that follows the
+ Greybus PWM Bridged PHY Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-pwm.ko
+
+config GREYBUS_SDIO
+ tristate "Greybus SDIO Bridged PHY driver"
+ depends on MMC
+ help
+ Select this option if you have a device that follows the
+ Greybus SDIO Bridged PHY Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-sdio.ko
+
+config GREYBUS_SPI
+ tristate "Greybus SPI Bridged PHY driver"
+ depends on SPI
+ help
+ Select this option if you have a device that follows the
+ Greybus SPI Bridged PHY Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-spi.ko
+
+config GREYBUS_UART
+ tristate "Greybus UART Bridged PHY driver"
+ depends on TTY
+ help
+ Select this option if you have a device that follows the
+ Greybus UART Bridged PHY Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-uart.ko
+
+config GREYBUS_USB
+ tristate "Greybus USB Host Bridged PHY driver"
+ depends on USB
+ help
+ Select this option if you have a device that follows the
+ Greybus USB Host Bridged PHY Class specification.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-usb.ko
+
+endif # GREYBUS_BRIDGED_PHY
+
+config GREYBUS_ARCHE
+ tristate "Greybus Arche Platform driver"
+ depends on USB_HSIC_USB3613 || COMPILE_TEST
+ help
+ Select this option if you have an Arche device.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-arche.ko
+
+endif # GREYBUS
diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile
new file mode 100644
index 0000000000..7c5e896223
--- /dev/null
+++ b/drivers/staging/greybus/Makefile
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: GPL-2.0
+# needed for trace events
+ccflags-y += -I$(src)
+
+# Greybus class drivers
+gb-bootrom-y := bootrom.o
+gb-camera-y := camera.o
+gb-firmware-y := fw-core.o fw-download.o fw-management.o authentication.o
+gb-spilib-y := spilib.o
+gb-hid-y := hid.o
+gb-light-y := light.o
+gb-log-y := log.o
+gb-loopback-y := loopback.o
+gb-power-supply-y := power_supply.o
+gb-raw-y := raw.o
+gb-vibrator-y := vibrator.o
+
+obj-$(CONFIG_GREYBUS_BOOTROM) += gb-bootrom.o
+obj-$(CONFIG_GREYBUS_CAMERA) += gb-camera.o
+obj-$(CONFIG_GREYBUS_FIRMWARE) += gb-firmware.o gb-spilib.o
+obj-$(CONFIG_GREYBUS_HID) += gb-hid.o
+obj-$(CONFIG_GREYBUS_LIGHT) += gb-light.o
+obj-$(CONFIG_GREYBUS_LOG) += gb-log.o
+obj-$(CONFIG_GREYBUS_LOOPBACK) += gb-loopback.o
+obj-$(CONFIG_GREYBUS_POWER) += gb-power-supply.o
+obj-$(CONFIG_GREYBUS_RAW) += gb-raw.o
+obj-$(CONFIG_GREYBUS_VIBRATOR) += gb-vibrator.o
+
+# Greybus Audio is a bunch of modules
+gb-audio-module-y := audio_module.o audio_topology.o
+gb-audio-codec-y := audio_codec.o audio_helper.o
+gb-audio-gb-y := audio_gb.o
+gb-audio-apbridgea-y := audio_apbridgea.o
+gb-audio-manager-y := audio_manager.o audio_manager_module.o
+
+# Greybus Audio sysfs helpers can be useful when debugging
+#GB_AUDIO_MANAGER_SYSFS ?= true
+#ifeq ($(GB_AUDIO_MANAGER_SYSFS),true)
+#gb-audio-manager-y += audio_manager_sysfs.o
+#ccflags-y += -DGB_AUDIO_MANAGER_SYSFS
+#endif
+
+obj-$(CONFIG_GREYBUS_AUDIO_APB_CODEC) += gb-audio-codec.o
+obj-$(CONFIG_GREYBUS_AUDIO_APB_CODEC) += gb-audio-module.o
+obj-$(CONFIG_GREYBUS_AUDIO) += gb-audio-gb.o
+obj-$(CONFIG_GREYBUS_AUDIO) += gb-audio-apbridgea.o
+obj-$(CONFIG_GREYBUS_AUDIO) += gb-audio-manager.o
+
+
+# Greybus Bridged PHY drivers
+gb-gbphy-y := gbphy.o
+gb-gpio-y := gpio.o
+gb-i2c-y := i2c.o
+gb-pwm-y := pwm.o
+gb-sdio-y := sdio.o
+gb-spi-y := spi.o
+gb-uart-y := uart.o
+gb-usb-y := usb.o
+
+obj-$(CONFIG_GREYBUS_BRIDGED_PHY) += gb-gbphy.o
+obj-$(CONFIG_GREYBUS_GPIO) += gb-gpio.o
+obj-$(CONFIG_GREYBUS_I2C) += gb-i2c.o
+obj-$(CONFIG_GREYBUS_PWM) += gb-pwm.o
+obj-$(CONFIG_GREYBUS_SDIO) += gb-sdio.o
+obj-$(CONFIG_GREYBUS_SPI) += gb-spi.o gb-spilib.o
+obj-$(CONFIG_GREYBUS_UART) += gb-uart.o
+obj-$(CONFIG_GREYBUS_USB) += gb-usb.o
+
+
+# Greybus Platform driver
+gb-arche-y := arche-platform.o arche-apb-ctrl.o
+
+obj-$(CONFIG_GREYBUS_ARCHE) += gb-arche.o
diff --git a/drivers/staging/greybus/TODO b/drivers/staging/greybus/TODO
new file mode 100644
index 0000000000..6461e0132f
--- /dev/null
+++ b/drivers/staging/greybus/TODO
@@ -0,0 +1,5 @@
+* Convert all uses of the old GPIO API from <linux/gpio.h> to the
+ GPIO descriptor API in <linux/gpio/consumer.h> and look up GPIO
+ lines from device tree or ACPI.
+* Make pwm.c use the struct pwm_ops::apply instead of ::config, ::set_polarity,
+ ::enable and ::disable.
diff --git a/drivers/staging/greybus/arche-apb-ctrl.c b/drivers/staging/greybus/arche-apb-ctrl.c
new file mode 100644
index 0000000000..8541995008
--- /dev/null
+++ b/drivers/staging/greybus/arche-apb-ctrl.c
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arche Platform driver to control APB.
+ *
+ * Copyright 2014-2015 Google Inc.
+ * Copyright 2014-2015 Linaro Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/of_irq.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spinlock.h>
+#include "arche_platform.h"
+
+static void apb_bootret_deassert(struct device *dev);
+
+struct arche_apb_ctrl_drvdata {
+ /* Control GPIO signals to and from AP <=> AP Bridges */
+ struct gpio_desc *resetn;
+ struct gpio_desc *boot_ret;
+ struct gpio_desc *pwroff;
+ struct gpio_desc *wake_in;
+ struct gpio_desc *wake_out;
+ struct gpio_desc *pwrdn;
+
+ enum arche_platform_state state;
+ bool init_disabled;
+
+ struct regulator *vcore;
+ struct regulator *vio;
+
+ struct gpio_desc *clk_en;
+ struct clk *clk;
+
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pin_default;
+
+ /* V2: SPI Bus control */
+ struct gpio_desc *spi_en;
+ bool spi_en_polarity_high;
+};
+
+/*
+ * Note that these low level api's are active high
+ */
+static inline void deassert_reset(struct gpio_desc *gpio)
+{
+ gpiod_set_raw_value(gpio, 1);
+}
+
+static inline void assert_reset(struct gpio_desc *gpio)
+{
+ gpiod_set_raw_value(gpio, 0);
+}
+
+/*
+ * Note: Please do not modify the below sequence, as it is as per the spec
+ */
+static int coldboot_seq(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
+ int ret;
+
+ if (apb->init_disabled ||
+ apb->state == ARCHE_PLATFORM_STATE_ACTIVE)
+ return 0;
+
+ /* Hold APB in reset state */
+ assert_reset(apb->resetn);
+
+ if (apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING && apb->spi_en)
+ devm_gpiod_put(dev, apb->spi_en);
+
+ /* Enable power to APB */
+ if (!IS_ERR(apb->vcore)) {
+ ret = regulator_enable(apb->vcore);
+ if (ret) {
+ dev_err(dev, "failed to enable core regulator\n");
+ return ret;
+ }
+ }
+
+ if (!IS_ERR(apb->vio)) {
+ ret = regulator_enable(apb->vio);
+ if (ret) {
+ dev_err(dev, "failed to enable IO regulator\n");
+ return ret;
+ }
+ }
+
+ apb_bootret_deassert(dev);
+
+ /* On DB3 clock was not mandatory */
+ if (apb->clk_en)
+ gpiod_set_value(apb->clk_en, 1);
+
+ usleep_range(100, 200);
+
+ /* deassert reset to APB : Active-low signal */
+ deassert_reset(apb->resetn);
+
+ apb->state = ARCHE_PLATFORM_STATE_ACTIVE;
+
+ return 0;
+}
+
+static int fw_flashing_seq(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
+ int ret;
+
+ if (apb->init_disabled ||
+ apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
+ return 0;
+
+ ret = regulator_enable(apb->vcore);
+ if (ret) {
+ dev_err(dev, "failed to enable core regulator\n");
+ return ret;
+ }
+
+ ret = regulator_enable(apb->vio);
+ if (ret) {
+ dev_err(dev, "failed to enable IO regulator\n");
+ return ret;
+ }
+
+ if (apb->spi_en) {
+ unsigned long flags;
+
+ if (apb->spi_en_polarity_high)
+ flags = GPIOD_OUT_HIGH;
+ else
+ flags = GPIOD_OUT_LOW;
+
+ apb->spi_en = devm_gpiod_get(dev, "spi-en", flags);
+ if (IS_ERR(apb->spi_en)) {
+ ret = PTR_ERR(apb->spi_en);
+ dev_err(dev, "Failed requesting SPI bus en GPIO: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /* for flashing device should be in reset state */
+ assert_reset(apb->resetn);
+ apb->state = ARCHE_PLATFORM_STATE_FW_FLASHING;
+
+ return 0;
+}
+
+static int standby_boot_seq(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
+
+ if (apb->init_disabled)
+ return 0;
+
+ /*
+ * Even if it is in OFF state,
+ * then we do not want to change the state
+ */
+ if (apb->state == ARCHE_PLATFORM_STATE_STANDBY ||
+ apb->state == ARCHE_PLATFORM_STATE_OFF)
+ return 0;
+
+ if (apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING && apb->spi_en)
+ devm_gpiod_put(dev, apb->spi_en);
+
+ /*
+ * As per WDM spec, do nothing
+ *
+ * Pasted from WDM spec,
+ * - A falling edge on POWEROFF_L is detected (a)
+ * - WDM enters standby mode, but no output signals are changed
+ */
+
+ /* TODO: POWEROFF_L is input to WDM module */
+ apb->state = ARCHE_PLATFORM_STATE_STANDBY;
+ return 0;
+}
+
+static void poweroff_seq(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
+
+ if (apb->init_disabled || apb->state == ARCHE_PLATFORM_STATE_OFF)
+ return;
+
+ if (apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING && apb->spi_en)
+ devm_gpiod_put(dev, apb->spi_en);
+
+ /* disable the clock */
+ if (apb->clk_en)
+ gpiod_set_value(apb->clk_en, 0);
+
+ if (!IS_ERR(apb->vcore) && regulator_is_enabled(apb->vcore) > 0)
+ regulator_disable(apb->vcore);
+
+ if (!IS_ERR(apb->vio) && regulator_is_enabled(apb->vio) > 0)
+ regulator_disable(apb->vio);
+
+ /* As part of exit, put APB back in reset state */
+ assert_reset(apb->resetn);
+ apb->state = ARCHE_PLATFORM_STATE_OFF;
+
+ /* TODO: May have to send an event to SVC about this exit */
+}
+
+static void apb_bootret_deassert(struct device *dev)
+{
+ struct arche_apb_ctrl_drvdata *apb = dev_get_drvdata(dev);
+
+ gpiod_set_value(apb->boot_ret, 0);
+}
+
+int apb_ctrl_coldboot(struct device *dev)
+{
+ return coldboot_seq(to_platform_device(dev));
+}
+
+int apb_ctrl_fw_flashing(struct device *dev)
+{
+ return fw_flashing_seq(to_platform_device(dev));
+}
+
+int apb_ctrl_standby_boot(struct device *dev)
+{
+ return standby_boot_seq(to_platform_device(dev));
+}
+
+void apb_ctrl_poweroff(struct device *dev)
+{
+ poweroff_seq(to_platform_device(dev));
+}
+
+static ssize_t state_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct arche_apb_ctrl_drvdata *apb = platform_get_drvdata(pdev);
+ int ret = 0;
+ bool is_disabled;
+
+ if (sysfs_streq(buf, "off")) {
+ if (apb->state == ARCHE_PLATFORM_STATE_OFF)
+ return count;
+
+ poweroff_seq(pdev);
+ } else if (sysfs_streq(buf, "active")) {
+ if (apb->state == ARCHE_PLATFORM_STATE_ACTIVE)
+ return count;
+
+ poweroff_seq(pdev);
+ is_disabled = apb->init_disabled;
+ apb->init_disabled = false;
+ ret = coldboot_seq(pdev);
+ if (ret)
+ apb->init_disabled = is_disabled;
+ } else if (sysfs_streq(buf, "standby")) {
+ if (apb->state == ARCHE_PLATFORM_STATE_STANDBY)
+ return count;
+
+ ret = standby_boot_seq(pdev);
+ } else if (sysfs_streq(buf, "fw_flashing")) {
+ if (apb->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
+ return count;
+
+ /*
+ * First we want to make sure we power off everything
+ * and then enter FW flashing state
+ */
+ poweroff_seq(pdev);
+ ret = fw_flashing_seq(pdev);
+ } else {
+ dev_err(dev, "unknown state\n");
+ ret = -EINVAL;
+ }
+
+ return ret ? ret : count;
+}
+
+static ssize_t state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arche_apb_ctrl_drvdata *apb = dev_get_drvdata(dev);
+
+ switch (apb->state) {
+ case ARCHE_PLATFORM_STATE_OFF:
+ return sprintf(buf, "off%s\n",
+ apb->init_disabled ? ",disabled" : "");
+ case ARCHE_PLATFORM_STATE_ACTIVE:
+ return sprintf(buf, "active\n");
+ case ARCHE_PLATFORM_STATE_STANDBY:
+ return sprintf(buf, "standby\n");
+ case ARCHE_PLATFORM_STATE_FW_FLASHING:
+ return sprintf(buf, "fw_flashing\n");
+ default:
+ return sprintf(buf, "unknown state\n");
+ }
+}
+
+static DEVICE_ATTR_RW(state);
+
+static int apb_ctrl_get_devtree_data(struct platform_device *pdev,
+ struct arche_apb_ctrl_drvdata *apb)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ apb->resetn = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(apb->resetn)) {
+ ret = PTR_ERR(apb->resetn);
+ dev_err(dev, "Failed requesting reset GPIO: %d\n", ret);
+ return ret;
+ }
+
+ apb->boot_ret = devm_gpiod_get(dev, "boot-ret", GPIOD_OUT_LOW);
+ if (IS_ERR(apb->boot_ret)) {
+ ret = PTR_ERR(apb->boot_ret);
+ dev_err(dev, "Failed requesting bootret GPIO: %d\n", ret);
+ return ret;
+ }
+
+ /* It's not mandatory to support power management interface */
+ apb->pwroff = devm_gpiod_get_optional(dev, "pwr-off", GPIOD_IN);
+ if (IS_ERR(apb->pwroff)) {
+ ret = PTR_ERR(apb->pwroff);
+ dev_err(dev, "Failed requesting pwroff_n GPIO: %d\n", ret);
+ return ret;
+ }
+
+ /* Do not make clock mandatory as of now (for DB3) */
+ apb->clk_en = devm_gpiod_get_optional(dev, "clock-en", GPIOD_OUT_LOW);
+ if (IS_ERR(apb->clk_en)) {
+ ret = PTR_ERR(apb->clk_en);
+ dev_err(dev, "Failed requesting APB clock en GPIO: %d\n", ret);
+ return ret;
+ }
+
+ apb->pwrdn = devm_gpiod_get(dev, "pwr-down", GPIOD_OUT_LOW);
+ if (IS_ERR(apb->pwrdn)) {
+ ret = PTR_ERR(apb->pwrdn);
+ dev_warn(dev, "Failed requesting power down GPIO: %d\n", ret);
+ return ret;
+ }
+
+ /* Regulators are optional, as we may have fixed supply coming in */
+ apb->vcore = devm_regulator_get(dev, "vcore");
+ if (IS_ERR(apb->vcore))
+ dev_warn(dev, "no core regulator found\n");
+
+ apb->vio = devm_regulator_get(dev, "vio");
+ if (IS_ERR(apb->vio))
+ dev_warn(dev, "no IO regulator found\n");
+
+ apb->pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (IS_ERR(apb->pinctrl)) {
+ dev_err(&pdev->dev, "could not get pinctrl handle\n");
+ return PTR_ERR(apb->pinctrl);
+ }
+ apb->pin_default = pinctrl_lookup_state(apb->pinctrl, "default");
+ if (IS_ERR(apb->pin_default)) {
+ dev_err(&pdev->dev, "could not get default pin state\n");
+ return PTR_ERR(apb->pin_default);
+ }
+
+ /* Only applicable for platform >= V2 */
+ if (of_property_read_bool(pdev->dev.of_node, "gb,spi-en-active-high"))
+ apb->spi_en_polarity_high = true;
+
+ return 0;
+}
+
+static int arche_apb_ctrl_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct arche_apb_ctrl_drvdata *apb;
+ struct device *dev = &pdev->dev;
+
+ apb = devm_kzalloc(&pdev->dev, sizeof(*apb), GFP_KERNEL);
+ if (!apb)
+ return -ENOMEM;
+
+ ret = apb_ctrl_get_devtree_data(pdev, apb);
+ if (ret) {
+ dev_err(dev, "failed to get apb devicetree data %d\n", ret);
+ return ret;
+ }
+
+ /* Initially set APB to OFF state */
+ apb->state = ARCHE_PLATFORM_STATE_OFF;
+ /* Check whether device needs to be enabled on boot */
+ if (of_property_read_bool(pdev->dev.of_node, "arche,init-disable"))
+ apb->init_disabled = true;
+
+ platform_set_drvdata(pdev, apb);
+
+ /* Create sysfs interface to allow user to change state dynamically */
+ ret = device_create_file(dev, &dev_attr_state);
+ if (ret) {
+ dev_err(dev, "failed to create state file in sysfs\n");
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "Device registered successfully\n");
+ return 0;
+}
+
+static void arche_apb_ctrl_remove(struct platform_device *pdev)
+{
+ device_remove_file(&pdev->dev, &dev_attr_state);
+ poweroff_seq(pdev);
+ platform_set_drvdata(pdev, NULL);
+}
+
+static int __maybe_unused arche_apb_ctrl_suspend(struct device *dev)
+{
+ /*
+ * If timing profile permits, we may shutdown bridge
+ * completely
+ *
+ * TODO: sequence ??
+ *
+ * Also, need to make sure we meet precondition for unipro suspend
+ * Precondition: Definition ???
+ */
+ return 0;
+}
+
+static int __maybe_unused arche_apb_ctrl_resume(struct device *dev)
+{
+ /*
+ * At least for ES2 we have to meet the delay requirement between
+ * unipro switch and AP bridge init, depending on whether bridge is in
+ * OFF state or standby state.
+ *
+ * Based on whether bridge is in standby or OFF state we may have to
+ * assert multiple signals. Please refer to WDM spec, for more info.
+ *
+ */
+ return 0;
+}
+
+static void arche_apb_ctrl_shutdown(struct platform_device *pdev)
+{
+ apb_ctrl_poweroff(&pdev->dev);
+}
+
+static SIMPLE_DEV_PM_OPS(arche_apb_ctrl_pm_ops, arche_apb_ctrl_suspend,
+ arche_apb_ctrl_resume);
+
+static const struct of_device_id arche_apb_ctrl_of_match[] = {
+ { .compatible = "usbffff,2", },
+ { },
+};
+
+static struct platform_driver arche_apb_ctrl_device_driver = {
+ .probe = arche_apb_ctrl_probe,
+ .remove_new = arche_apb_ctrl_remove,
+ .shutdown = arche_apb_ctrl_shutdown,
+ .driver = {
+ .name = "arche-apb-ctrl",
+ .pm = &arche_apb_ctrl_pm_ops,
+ .of_match_table = arche_apb_ctrl_of_match,
+ }
+};
+
+int __init arche_apb_init(void)
+{
+ return platform_driver_register(&arche_apb_ctrl_device_driver);
+}
+
+void __exit arche_apb_exit(void)
+{
+ platform_driver_unregister(&arche_apb_ctrl_device_driver);
+}
diff --git a/drivers/staging/greybus/arche-platform.c b/drivers/staging/greybus/arche-platform.c
new file mode 100644
index 0000000000..891b75327d
--- /dev/null
+++ b/drivers/staging/greybus/arche-platform.c
@@ -0,0 +1,667 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Arche Platform driver to enable Unipro link.
+ *
+ * Copyright 2014-2015 Google Inc.
+ * Copyright 2014-2015 Linaro Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/suspend.h>
+#include <linux/time.h>
+#include <linux/greybus.h>
+#include <linux/of.h>
+#include "arche_platform.h"
+
+#if IS_ENABLED(CONFIG_USB_HSIC_USB3613)
+#include <linux/usb/usb3613.h>
+#else
+static inline int usb3613_hub_mode_ctrl(bool unused)
+{
+ return 0;
+}
+#endif
+
+#define WD_COLDBOOT_PULSE_WIDTH_MS 30
+
+enum svc_wakedetect_state {
+ WD_STATE_IDLE, /* Default state = pulled high/low */
+ WD_STATE_BOOT_INIT, /* WD = falling edge (low) */
+ WD_STATE_COLDBOOT_TRIG, /* WD = rising edge (high), > 30msec */
+ WD_STATE_STANDBYBOOT_TRIG, /* As of now not used ?? */
+ WD_STATE_COLDBOOT_START, /* Cold boot process started */
+ WD_STATE_STANDBYBOOT_START, /* Not used */
+};
+
+struct arche_platform_drvdata {
+ /* Control GPIO signals to and from AP <=> SVC */
+ struct gpio_desc *svc_reset;
+ bool is_reset_act_hi;
+ struct gpio_desc *svc_sysboot;
+ struct gpio_desc *wake_detect; /* bi-dir,maps to WAKE_MOD & WAKE_FRAME signals */
+
+ enum arche_platform_state state;
+
+ struct gpio_desc *svc_refclk_req;
+ struct clk *svc_ref_clk;
+
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pin_default;
+
+ int num_apbs;
+
+ enum svc_wakedetect_state wake_detect_state;
+ int wake_detect_irq;
+ spinlock_t wake_lock; /* Protect wake_detect_state */
+ struct mutex platform_state_mutex; /* Protect state */
+ unsigned long wake_detect_start;
+ struct notifier_block pm_notifier;
+
+ struct device *dev;
+};
+
+/* Requires calling context to hold arche_pdata->platform_state_mutex */
+static void arche_platform_set_state(struct arche_platform_drvdata *arche_pdata,
+ enum arche_platform_state state)
+{
+ arche_pdata->state = state;
+}
+
+/* Requires arche_pdata->wake_lock is held by calling context */
+static void arche_platform_set_wake_detect_state(struct arche_platform_drvdata *arche_pdata,
+ enum svc_wakedetect_state state)
+{
+ arche_pdata->wake_detect_state = state;
+}
+
+static inline void svc_reset_onoff(struct gpio_desc *gpio, bool onoff)
+{
+ gpiod_set_raw_value(gpio, onoff);
+}
+
+static int apb_cold_boot(struct device *dev, void *data)
+{
+ int ret;
+
+ ret = apb_ctrl_coldboot(dev);
+ if (ret)
+ dev_warn(dev, "failed to coldboot\n");
+
+ /*Child nodes are independent, so do not exit coldboot operation */
+ return 0;
+}
+
+static int apb_poweroff(struct device *dev, void *data)
+{
+ apb_ctrl_poweroff(dev);
+
+ /* Enable HUB3613 into HUB mode. */
+ if (usb3613_hub_mode_ctrl(false))
+ dev_warn(dev, "failed to control hub device\n");
+
+ return 0;
+}
+
+static void arche_platform_wd_irq_en(struct arche_platform_drvdata *arche_pdata)
+{
+ /* Enable interrupt here, to read event back from SVC */
+ enable_irq(arche_pdata->wake_detect_irq);
+}
+
+static irqreturn_t arche_platform_wd_irq_thread(int irq, void *devid)
+{
+ struct arche_platform_drvdata *arche_pdata = devid;
+ unsigned long flags;
+
+ spin_lock_irqsave(&arche_pdata->wake_lock, flags);
+ if (arche_pdata->wake_detect_state != WD_STATE_COLDBOOT_TRIG) {
+ /* Something is wrong */
+ spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
+ return IRQ_HANDLED;
+ }
+
+ arche_platform_set_wake_detect_state(arche_pdata,
+ WD_STATE_COLDBOOT_START);
+ spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
+
+ /* It should complete power cycle, so first make sure it is poweroff */
+ device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
+
+ /* Bring APB out of reset: cold boot sequence */
+ device_for_each_child(arche_pdata->dev, NULL, apb_cold_boot);
+
+ /* Enable HUB3613 into HUB mode. */
+ if (usb3613_hub_mode_ctrl(true))
+ dev_warn(arche_pdata->dev, "failed to control hub device\n");
+
+ spin_lock_irqsave(&arche_pdata->wake_lock, flags);
+ arche_platform_set_wake_detect_state(arche_pdata, WD_STATE_IDLE);
+ spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t arche_platform_wd_irq(int irq, void *devid)
+{
+ struct arche_platform_drvdata *arche_pdata = devid;
+ unsigned long flags;
+
+ spin_lock_irqsave(&arche_pdata->wake_lock, flags);
+
+ if (gpiod_get_value(arche_pdata->wake_detect)) {
+ /* wake/detect rising */
+
+ /*
+ * If wake/detect line goes high after low, within less than
+ * 30msec, then standby boot sequence is initiated, which is not
+ * supported/implemented as of now. So ignore it.
+ */
+ if (arche_pdata->wake_detect_state == WD_STATE_BOOT_INIT) {
+ if (time_before(jiffies,
+ arche_pdata->wake_detect_start +
+ msecs_to_jiffies(WD_COLDBOOT_PULSE_WIDTH_MS))) {
+ arche_platform_set_wake_detect_state(arche_pdata,
+ WD_STATE_IDLE);
+ } else {
+ /*
+ * Check we are not in middle of irq thread
+ * already
+ */
+ if (arche_pdata->wake_detect_state !=
+ WD_STATE_COLDBOOT_START) {
+ arche_platform_set_wake_detect_state(arche_pdata,
+ WD_STATE_COLDBOOT_TRIG);
+ spin_unlock_irqrestore(&arche_pdata->wake_lock,
+ flags);
+ return IRQ_WAKE_THREAD;
+ }
+ }
+ }
+ } else {
+ /* wake/detect falling */
+ if (arche_pdata->wake_detect_state == WD_STATE_IDLE) {
+ arche_pdata->wake_detect_start = jiffies;
+ /*
+ * In the beginning, when wake/detect goes low
+ * (first time), we assume it is meant for coldboot
+ * and set the flag. If wake/detect line stays low
+ * beyond 30msec, then it is coldboot else fallback
+ * to standby boot.
+ */
+ arche_platform_set_wake_detect_state(arche_pdata,
+ WD_STATE_BOOT_INIT);
+ }
+ }
+
+ spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Requires arche_pdata->platform_state_mutex to be held
+ */
+static int
+arche_platform_coldboot_seq(struct arche_platform_drvdata *arche_pdata)
+{
+ int ret;
+
+ if (arche_pdata->state == ARCHE_PLATFORM_STATE_ACTIVE)
+ return 0;
+
+ dev_info(arche_pdata->dev, "Booting from cold boot state\n");
+
+ svc_reset_onoff(arche_pdata->svc_reset, arche_pdata->is_reset_act_hi);
+
+ gpiod_set_value(arche_pdata->svc_sysboot, 0);
+ usleep_range(100, 200);
+
+ ret = clk_prepare_enable(arche_pdata->svc_ref_clk);
+ if (ret) {
+ dev_err(arche_pdata->dev, "failed to enable svc_ref_clk: %d\n",
+ ret);
+ return ret;
+ }
+
+ /* bring SVC out of reset */
+ svc_reset_onoff(arche_pdata->svc_reset, !arche_pdata->is_reset_act_hi);
+
+ arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_ACTIVE);
+
+ return 0;
+}
+
+/*
+ * Requires arche_pdata->platform_state_mutex to be held
+ */
+static int
+arche_platform_fw_flashing_seq(struct arche_platform_drvdata *arche_pdata)
+{
+ int ret;
+
+ if (arche_pdata->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
+ return 0;
+
+ dev_info(arche_pdata->dev, "Switching to FW flashing state\n");
+
+ svc_reset_onoff(arche_pdata->svc_reset, arche_pdata->is_reset_act_hi);
+
+ gpiod_set_value(arche_pdata->svc_sysboot, 1);
+
+ usleep_range(100, 200);
+
+ ret = clk_prepare_enable(arche_pdata->svc_ref_clk);
+ if (ret) {
+ dev_err(arche_pdata->dev, "failed to enable svc_ref_clk: %d\n",
+ ret);
+ return ret;
+ }
+
+ svc_reset_onoff(arche_pdata->svc_reset, !arche_pdata->is_reset_act_hi);
+
+ arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_FW_FLASHING);
+
+ return 0;
+}
+
+/*
+ * Requires arche_pdata->platform_state_mutex to be held
+ */
+static void
+arche_platform_poweroff_seq(struct arche_platform_drvdata *arche_pdata)
+{
+ unsigned long flags;
+
+ if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
+ return;
+
+ /* If in fw_flashing mode, then no need to repeate things again */
+ if (arche_pdata->state != ARCHE_PLATFORM_STATE_FW_FLASHING) {
+ disable_irq(arche_pdata->wake_detect_irq);
+
+ spin_lock_irqsave(&arche_pdata->wake_lock, flags);
+ arche_platform_set_wake_detect_state(arche_pdata,
+ WD_STATE_IDLE);
+ spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
+ }
+
+ clk_disable_unprepare(arche_pdata->svc_ref_clk);
+
+ /* As part of exit, put APB back in reset state */
+ svc_reset_onoff(arche_pdata->svc_reset, arche_pdata->is_reset_act_hi);
+
+ arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_OFF);
+}
+
+static ssize_t state_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct arche_platform_drvdata *arche_pdata = dev_get_drvdata(dev);
+ int ret = 0;
+
+ mutex_lock(&arche_pdata->platform_state_mutex);
+
+ if (sysfs_streq(buf, "off")) {
+ if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
+ goto exit;
+
+ /* If SVC goes down, bring down APB's as well */
+ device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
+
+ arche_platform_poweroff_seq(arche_pdata);
+
+ } else if (sysfs_streq(buf, "active")) {
+ if (arche_pdata->state == ARCHE_PLATFORM_STATE_ACTIVE)
+ goto exit;
+
+ /* First we want to make sure we power off everything
+ * and then activate back again
+ */
+ device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
+ arche_platform_poweroff_seq(arche_pdata);
+
+ arche_platform_wd_irq_en(arche_pdata);
+ ret = arche_platform_coldboot_seq(arche_pdata);
+ if (ret)
+ goto exit;
+
+ } else if (sysfs_streq(buf, "standby")) {
+ if (arche_pdata->state == ARCHE_PLATFORM_STATE_STANDBY)
+ goto exit;
+
+ dev_warn(arche_pdata->dev, "standby state not supported\n");
+ } else if (sysfs_streq(buf, "fw_flashing")) {
+ if (arche_pdata->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
+ goto exit;
+
+ /*
+ * Here we only control SVC.
+ *
+ * In case of FW_FLASHING mode we do not want to control
+ * APBs, as in case of V2, SPI bus is shared between both
+ * the APBs. So let user chose which APB he wants to flash.
+ */
+ arche_platform_poweroff_seq(arche_pdata);
+
+ ret = arche_platform_fw_flashing_seq(arche_pdata);
+ if (ret)
+ goto exit;
+ } else {
+ dev_err(arche_pdata->dev, "unknown state\n");
+ ret = -EINVAL;
+ }
+
+exit:
+ mutex_unlock(&arche_pdata->platform_state_mutex);
+ return ret ? ret : count;
+}
+
+static ssize_t state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arche_platform_drvdata *arche_pdata = dev_get_drvdata(dev);
+
+ switch (arche_pdata->state) {
+ case ARCHE_PLATFORM_STATE_OFF:
+ return sprintf(buf, "off\n");
+ case ARCHE_PLATFORM_STATE_ACTIVE:
+ return sprintf(buf, "active\n");
+ case ARCHE_PLATFORM_STATE_STANDBY:
+ return sprintf(buf, "standby\n");
+ case ARCHE_PLATFORM_STATE_FW_FLASHING:
+ return sprintf(buf, "fw_flashing\n");
+ default:
+ return sprintf(buf, "unknown state\n");
+ }
+}
+
+static DEVICE_ATTR_RW(state);
+
+static int arche_platform_pm_notifier(struct notifier_block *notifier,
+ unsigned long pm_event, void *unused)
+{
+ struct arche_platform_drvdata *arche_pdata =
+ container_of(notifier, struct arche_platform_drvdata,
+ pm_notifier);
+ int ret = NOTIFY_DONE;
+
+ mutex_lock(&arche_pdata->platform_state_mutex);
+ switch (pm_event) {
+ case PM_SUSPEND_PREPARE:
+ if (arche_pdata->state != ARCHE_PLATFORM_STATE_ACTIVE) {
+ ret = NOTIFY_STOP;
+ break;
+ }
+ device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
+ arche_platform_poweroff_seq(arche_pdata);
+ break;
+ case PM_POST_SUSPEND:
+ if (arche_pdata->state != ARCHE_PLATFORM_STATE_OFF)
+ break;
+
+ arche_platform_wd_irq_en(arche_pdata);
+ arche_platform_coldboot_seq(arche_pdata);
+ break;
+ default:
+ break;
+ }
+ mutex_unlock(&arche_pdata->platform_state_mutex);
+
+ return ret;
+}
+
+static int arche_platform_probe(struct platform_device *pdev)
+{
+ struct arche_platform_drvdata *arche_pdata;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+ unsigned int flags;
+
+ arche_pdata = devm_kzalloc(&pdev->dev, sizeof(*arche_pdata),
+ GFP_KERNEL);
+ if (!arche_pdata)
+ return -ENOMEM;
+
+ /* setup svc reset gpio */
+ arche_pdata->is_reset_act_hi = of_property_read_bool(np,
+ "svc,reset-active-high");
+ if (arche_pdata->is_reset_act_hi)
+ flags = GPIOD_OUT_HIGH;
+ else
+ flags = GPIOD_OUT_LOW;
+
+ arche_pdata->svc_reset = devm_gpiod_get(dev, "svc,reset", flags);
+ if (IS_ERR(arche_pdata->svc_reset)) {
+ ret = PTR_ERR(arche_pdata->svc_reset);
+ dev_err(dev, "failed to request svc-reset GPIO: %d\n", ret);
+ return ret;
+ }
+ arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_OFF);
+
+ arche_pdata->svc_sysboot = devm_gpiod_get(dev, "svc,sysboot",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(arche_pdata->svc_sysboot)) {
+ ret = PTR_ERR(arche_pdata->svc_sysboot);
+ dev_err(dev, "failed to request sysboot0 GPIO: %d\n", ret);
+ return ret;
+ }
+
+ /* setup the clock request gpio first */
+ arche_pdata->svc_refclk_req = devm_gpiod_get(dev, "svc,refclk-req",
+ GPIOD_IN);
+ if (IS_ERR(arche_pdata->svc_refclk_req)) {
+ ret = PTR_ERR(arche_pdata->svc_refclk_req);
+ dev_err(dev, "failed to request svc-clk-req GPIO: %d\n", ret);
+ return ret;
+ }
+
+ /* setup refclk2 to follow the pin */
+ arche_pdata->svc_ref_clk = devm_clk_get(dev, "svc_ref_clk");
+ if (IS_ERR(arche_pdata->svc_ref_clk)) {
+ ret = PTR_ERR(arche_pdata->svc_ref_clk);
+ dev_err(dev, "failed to get svc_ref_clk: %d\n", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, arche_pdata);
+
+ arche_pdata->num_apbs = of_get_child_count(np);
+ dev_dbg(dev, "Number of APB's available - %d\n", arche_pdata->num_apbs);
+
+ arche_pdata->wake_detect = devm_gpiod_get(dev, "svc,wake-detect",
+ GPIOD_IN);
+ if (IS_ERR(arche_pdata->wake_detect)) {
+ ret = PTR_ERR(arche_pdata->wake_detect);
+ dev_err(dev, "Failed requesting wake_detect GPIO: %d\n", ret);
+ return ret;
+ }
+
+ arche_platform_set_wake_detect_state(arche_pdata, WD_STATE_IDLE);
+
+ arche_pdata->dev = &pdev->dev;
+
+ spin_lock_init(&arche_pdata->wake_lock);
+ mutex_init(&arche_pdata->platform_state_mutex);
+ arche_pdata->wake_detect_irq =
+ gpiod_to_irq(arche_pdata->wake_detect);
+
+ ret = devm_request_threaded_irq(dev, arche_pdata->wake_detect_irq,
+ arche_platform_wd_irq,
+ arche_platform_wd_irq_thread,
+ IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ dev_name(dev), arche_pdata);
+ if (ret) {
+ dev_err(dev, "failed to request wake detect IRQ %d\n", ret);
+ return ret;
+ }
+ disable_irq(arche_pdata->wake_detect_irq);
+
+ ret = device_create_file(dev, &dev_attr_state);
+ if (ret) {
+ dev_err(dev, "failed to create state file in sysfs\n");
+ return ret;
+ }
+
+ ret = of_platform_populate(np, NULL, NULL, dev);
+ if (ret) {
+ dev_err(dev, "failed to populate child nodes %d\n", ret);
+ goto err_device_remove;
+ }
+
+ arche_pdata->pm_notifier.notifier_call = arche_platform_pm_notifier;
+ ret = register_pm_notifier(&arche_pdata->pm_notifier);
+
+ if (ret) {
+ dev_err(dev, "failed to register pm notifier %d\n", ret);
+ goto err_device_remove;
+ }
+
+ /* Explicitly power off if requested */
+ if (!of_property_read_bool(pdev->dev.of_node, "arche,init-off")) {
+ mutex_lock(&arche_pdata->platform_state_mutex);
+ ret = arche_platform_coldboot_seq(arche_pdata);
+ if (ret) {
+ dev_err(dev, "Failed to cold boot svc %d\n", ret);
+ goto err_coldboot;
+ }
+ arche_platform_wd_irq_en(arche_pdata);
+ mutex_unlock(&arche_pdata->platform_state_mutex);
+ }
+
+ dev_info(dev, "Device registered successfully\n");
+ return 0;
+
+err_coldboot:
+ mutex_unlock(&arche_pdata->platform_state_mutex);
+err_device_remove:
+ device_remove_file(&pdev->dev, &dev_attr_state);
+ return ret;
+}
+
+static int arche_remove_child(struct device *dev, void *unused)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ platform_device_unregister(pdev);
+
+ return 0;
+}
+
+static void arche_platform_remove(struct platform_device *pdev)
+{
+ struct arche_platform_drvdata *arche_pdata = platform_get_drvdata(pdev);
+
+ unregister_pm_notifier(&arche_pdata->pm_notifier);
+ device_remove_file(&pdev->dev, &dev_attr_state);
+ device_for_each_child(&pdev->dev, NULL, arche_remove_child);
+ arche_platform_poweroff_seq(arche_pdata);
+
+ if (usb3613_hub_mode_ctrl(false))
+ dev_warn(arche_pdata->dev, "failed to control hub device\n");
+}
+
+static __maybe_unused int arche_platform_suspend(struct device *dev)
+{
+ /*
+ * If timing profile premits, we may shutdown bridge
+ * completely
+ *
+ * TODO: sequence ??
+ *
+ * Also, need to make sure we meet precondition for unipro suspend
+ * Precondition: Definition ???
+ */
+ return 0;
+}
+
+static __maybe_unused int arche_platform_resume(struct device *dev)
+{
+ /*
+ * At least for ES2 we have to meet the delay requirement between
+ * unipro switch and AP bridge init, depending on whether bridge is in
+ * OFF state or standby state.
+ *
+ * Based on whether bridge is in standby or OFF state we may have to
+ * assert multiple signals. Please refer to WDM spec, for more info.
+ *
+ */
+ return 0;
+}
+
+static void arche_platform_shutdown(struct platform_device *pdev)
+{
+ struct arche_platform_drvdata *arche_pdata = platform_get_drvdata(pdev);
+
+ arche_platform_poweroff_seq(arche_pdata);
+
+ usb3613_hub_mode_ctrl(false);
+}
+
+static SIMPLE_DEV_PM_OPS(arche_platform_pm_ops,
+ arche_platform_suspend,
+ arche_platform_resume);
+
+static const struct of_device_id arche_platform_of_match[] = {
+ /* Use PID/VID of SVC device */
+ { .compatible = "google,arche-platform", },
+ { },
+};
+
+static const struct of_device_id arche_combined_id[] = {
+ /* Use PID/VID of SVC device */
+ { .compatible = "google,arche-platform", },
+ { .compatible = "usbffff,2", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, arche_combined_id);
+
+static struct platform_driver arche_platform_device_driver = {
+ .probe = arche_platform_probe,
+ .remove_new = arche_platform_remove,
+ .shutdown = arche_platform_shutdown,
+ .driver = {
+ .name = "arche-platform-ctrl",
+ .pm = &arche_platform_pm_ops,
+ .of_match_table = arche_platform_of_match,
+ }
+};
+
+static int __init arche_init(void)
+{
+ int retval;
+
+ retval = platform_driver_register(&arche_platform_device_driver);
+ if (retval)
+ return retval;
+
+ retval = arche_apb_init();
+ if (retval)
+ platform_driver_unregister(&arche_platform_device_driver);
+
+ return retval;
+}
+module_init(arche_init);
+
+static void __exit arche_exit(void)
+{
+ arche_apb_exit();
+ platform_driver_unregister(&arche_platform_device_driver);
+}
+module_exit(arche_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Vaibhav Hiremath <vaibhav.hiremath@linaro.org>");
+MODULE_DESCRIPTION("Arche Platform Driver");
diff --git a/drivers/staging/greybus/arche_platform.h b/drivers/staging/greybus/arche_platform.h
new file mode 100644
index 0000000000..9d997f2d65
--- /dev/null
+++ b/drivers/staging/greybus/arche_platform.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Arche Platform driver to enable Unipro link.
+ *
+ * Copyright 2015-2016 Google Inc.
+ * Copyright 2015-2016 Linaro Ltd.
+ */
+
+#ifndef __ARCHE_PLATFORM_H
+#define __ARCHE_PLATFORM_H
+
+enum arche_platform_state {
+ ARCHE_PLATFORM_STATE_OFF,
+ ARCHE_PLATFORM_STATE_ACTIVE,
+ ARCHE_PLATFORM_STATE_STANDBY,
+ ARCHE_PLATFORM_STATE_FW_FLASHING,
+};
+
+int __init arche_apb_init(void);
+void __exit arche_apb_exit(void);
+
+/* Operational states for the APB device */
+int apb_ctrl_coldboot(struct device *dev);
+int apb_ctrl_fw_flashing(struct device *dev);
+int apb_ctrl_standby_boot(struct device *dev);
+void apb_ctrl_poweroff(struct device *dev);
+
+#endif /* __ARCHE_PLATFORM_H */
diff --git a/drivers/staging/greybus/audio_apbridgea.c b/drivers/staging/greybus/audio_apbridgea.c
new file mode 100644
index 0000000000..26117e390d
--- /dev/null
+++ b/drivers/staging/greybus/audio_apbridgea.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Audio Device Class Protocol helpers
+ *
+ * Copyright 2015-2016 Google Inc.
+ */
+
+#include <linux/greybus.h>
+#include "audio_apbridgea.h"
+#include "audio_codec.h"
+
+int gb_audio_apbridgea_set_config(struct gb_connection *connection,
+ __u16 i2s_port, __u32 format, __u32 rate,
+ __u32 mclk_freq)
+{
+ struct audio_apbridgea_set_config_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_CONFIG;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+ req.format = cpu_to_le32(format);
+ req.rate = cpu_to_le32(rate);
+ req.mclk_freq = cpu_to_le32(mclk_freq);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_config);
+
+int gb_audio_apbridgea_register_cport(struct gb_connection *connection,
+ __u16 i2s_port, __u16 cportid,
+ __u8 direction)
+{
+ struct audio_apbridgea_register_cport_request req;
+ int ret;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_REGISTER_CPORT;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+ req.cport = cpu_to_le16(cportid);
+ req.direction = direction;
+
+ ret = gb_pm_runtime_get_sync(connection->bundle);
+ if (ret)
+ return ret;
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_register_cport);
+
+int gb_audio_apbridgea_unregister_cport(struct gb_connection *connection,
+ __u16 i2s_port, __u16 cportid,
+ __u8 direction)
+{
+ struct audio_apbridgea_unregister_cport_request req;
+ int ret;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_UNREGISTER_CPORT;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+ req.cport = cpu_to_le16(cportid);
+ req.direction = direction;
+
+ ret = gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+
+ gb_pm_runtime_put_autosuspend(connection->bundle);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_unregister_cport);
+
+int gb_audio_apbridgea_set_tx_data_size(struct gb_connection *connection,
+ __u16 i2s_port, __u16 size)
+{
+ struct audio_apbridgea_set_tx_data_size_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_TX_DATA_SIZE;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+ req.size = cpu_to_le16(size);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_tx_data_size);
+
+int gb_audio_apbridgea_prepare_tx(struct gb_connection *connection,
+ __u16 i2s_port)
+{
+ struct audio_apbridgea_prepare_tx_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_PREPARE_TX;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_prepare_tx);
+
+int gb_audio_apbridgea_start_tx(struct gb_connection *connection,
+ __u16 i2s_port, __u64 timestamp)
+{
+ struct audio_apbridgea_start_tx_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_START_TX;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+ req.timestamp = cpu_to_le64(timestamp);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_start_tx);
+
+int gb_audio_apbridgea_stop_tx(struct gb_connection *connection, __u16 i2s_port)
+{
+ struct audio_apbridgea_stop_tx_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_STOP_TX;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_stop_tx);
+
+int gb_audio_apbridgea_shutdown_tx(struct gb_connection *connection,
+ __u16 i2s_port)
+{
+ struct audio_apbridgea_shutdown_tx_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_SHUTDOWN_TX;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_shutdown_tx);
+
+int gb_audio_apbridgea_set_rx_data_size(struct gb_connection *connection,
+ __u16 i2s_port, __u16 size)
+{
+ struct audio_apbridgea_set_rx_data_size_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_RX_DATA_SIZE;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+ req.size = cpu_to_le16(size);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_rx_data_size);
+
+int gb_audio_apbridgea_prepare_rx(struct gb_connection *connection,
+ __u16 i2s_port)
+{
+ struct audio_apbridgea_prepare_rx_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_PREPARE_RX;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_prepare_rx);
+
+int gb_audio_apbridgea_start_rx(struct gb_connection *connection,
+ __u16 i2s_port)
+{
+ struct audio_apbridgea_start_rx_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_START_RX;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_start_rx);
+
+int gb_audio_apbridgea_stop_rx(struct gb_connection *connection, __u16 i2s_port)
+{
+ struct audio_apbridgea_stop_rx_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_STOP_RX;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_stop_rx);
+
+int gb_audio_apbridgea_shutdown_rx(struct gb_connection *connection,
+ __u16 i2s_port)
+{
+ struct audio_apbridgea_shutdown_rx_request req;
+
+ req.hdr.type = AUDIO_APBRIDGEA_TYPE_SHUTDOWN_RX;
+ req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+ return gb_hd_output(connection->hd, &req, sizeof(req),
+ GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_shutdown_rx);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("greybus:audio-apbridgea");
+MODULE_DESCRIPTION("Greybus Special APBridgeA Audio Protocol library");
+MODULE_AUTHOR("Mark Greer <mgreer@animalcreek.com>");
diff --git a/drivers/staging/greybus/audio_apbridgea.h b/drivers/staging/greybus/audio_apbridgea.h
new file mode 100644
index 0000000000..efec0f815e
--- /dev/null
+++ b/drivers/staging/greybus/audio_apbridgea.h
@@ -0,0 +1,133 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (c) 2015-2016 Google Inc.
+ */
+/*
+ * This is a special protocol for configuring communication over the
+ * I2S bus between the DSP on the MSM8994 and APBridgeA. Therefore,
+ * we can predefine several low-level attributes of the communication
+ * because we know that they are supported. In particular, the following
+ * assumptions are made:
+ * - there are two channels (i.e., stereo)
+ * - the low-level protocol is I2S as defined by Philips/NXP
+ * - the DSP on the MSM8994 is the clock master for MCLK, BCLK, and WCLK
+ * - WCLK changes on the falling edge of BCLK
+ * - WCLK low for left channel; high for right channel
+ * - TX data is sent on the falling edge of BCLK
+ * - RX data is received/latched on the rising edge of BCLK
+ */
+
+#ifndef __AUDIO_APBRIDGEA_H
+#define __AUDIO_APBRIDGEA_H
+
+#define AUDIO_APBRIDGEA_TYPE_SET_CONFIG 0x01
+#define AUDIO_APBRIDGEA_TYPE_REGISTER_CPORT 0x02
+#define AUDIO_APBRIDGEA_TYPE_UNREGISTER_CPORT 0x03
+#define AUDIO_APBRIDGEA_TYPE_SET_TX_DATA_SIZE 0x04
+ /* 0x05 unused */
+#define AUDIO_APBRIDGEA_TYPE_PREPARE_TX 0x06
+#define AUDIO_APBRIDGEA_TYPE_START_TX 0x07
+#define AUDIO_APBRIDGEA_TYPE_STOP_TX 0x08
+#define AUDIO_APBRIDGEA_TYPE_SHUTDOWN_TX 0x09
+#define AUDIO_APBRIDGEA_TYPE_SET_RX_DATA_SIZE 0x0a
+ /* 0x0b unused */
+#define AUDIO_APBRIDGEA_TYPE_PREPARE_RX 0x0c
+#define AUDIO_APBRIDGEA_TYPE_START_RX 0x0d
+#define AUDIO_APBRIDGEA_TYPE_STOP_RX 0x0e
+#define AUDIO_APBRIDGEA_TYPE_SHUTDOWN_RX 0x0f
+
+#define AUDIO_APBRIDGEA_PCM_FMT_8 BIT(0)
+#define AUDIO_APBRIDGEA_PCM_FMT_16 BIT(1)
+#define AUDIO_APBRIDGEA_PCM_FMT_24 BIT(2)
+#define AUDIO_APBRIDGEA_PCM_FMT_32 BIT(3)
+#define AUDIO_APBRIDGEA_PCM_FMT_64 BIT(4)
+
+#define AUDIO_APBRIDGEA_PCM_RATE_5512 BIT(0)
+#define AUDIO_APBRIDGEA_PCM_RATE_8000 BIT(1)
+#define AUDIO_APBRIDGEA_PCM_RATE_11025 BIT(2)
+#define AUDIO_APBRIDGEA_PCM_RATE_16000 BIT(3)
+#define AUDIO_APBRIDGEA_PCM_RATE_22050 BIT(4)
+#define AUDIO_APBRIDGEA_PCM_RATE_32000 BIT(5)
+#define AUDIO_APBRIDGEA_PCM_RATE_44100 BIT(6)
+#define AUDIO_APBRIDGEA_PCM_RATE_48000 BIT(7)
+#define AUDIO_APBRIDGEA_PCM_RATE_64000 BIT(8)
+#define AUDIO_APBRIDGEA_PCM_RATE_88200 BIT(9)
+#define AUDIO_APBRIDGEA_PCM_RATE_96000 BIT(10)
+#define AUDIO_APBRIDGEA_PCM_RATE_176400 BIT(11)
+#define AUDIO_APBRIDGEA_PCM_RATE_192000 BIT(12)
+
+#define AUDIO_APBRIDGEA_DIRECTION_TX BIT(0)
+#define AUDIO_APBRIDGEA_DIRECTION_RX BIT(1)
+
+/* The I2S port is passed in the 'index' parameter of the USB request */
+/* The CPort is passed in the 'value' parameter of the USB request */
+
+struct audio_apbridgea_hdr {
+ __u8 type;
+ __le16 i2s_port;
+ __u8 data[];
+} __packed;
+
+struct audio_apbridgea_set_config_request {
+ struct audio_apbridgea_hdr hdr;
+ __le32 format; /* AUDIO_APBRIDGEA_PCM_FMT_* */
+ __le32 rate; /* AUDIO_APBRIDGEA_PCM_RATE_* */
+ __le32 mclk_freq; /* XXX Remove? */
+} __packed;
+
+struct audio_apbridgea_register_cport_request {
+ struct audio_apbridgea_hdr hdr;
+ __le16 cport;
+ __u8 direction;
+} __packed;
+
+struct audio_apbridgea_unregister_cport_request {
+ struct audio_apbridgea_hdr hdr;
+ __le16 cport;
+ __u8 direction;
+} __packed;
+
+struct audio_apbridgea_set_tx_data_size_request {
+ struct audio_apbridgea_hdr hdr;
+ __le16 size;
+} __packed;
+
+struct audio_apbridgea_prepare_tx_request {
+ struct audio_apbridgea_hdr hdr;
+} __packed;
+
+struct audio_apbridgea_start_tx_request {
+ struct audio_apbridgea_hdr hdr;
+ __le64 timestamp;
+} __packed;
+
+struct audio_apbridgea_stop_tx_request {
+ struct audio_apbridgea_hdr hdr;
+} __packed;
+
+struct audio_apbridgea_shutdown_tx_request {
+ struct audio_apbridgea_hdr hdr;
+} __packed;
+
+struct audio_apbridgea_set_rx_data_size_request {
+ struct audio_apbridgea_hdr hdr;
+ __le16 size;
+} __packed;
+
+struct audio_apbridgea_prepare_rx_request {
+ struct audio_apbridgea_hdr hdr;
+} __packed;
+
+struct audio_apbridgea_start_rx_request {
+ struct audio_apbridgea_hdr hdr;
+} __packed;
+
+struct audio_apbridgea_stop_rx_request {
+ struct audio_apbridgea_hdr hdr;
+} __packed;
+
+struct audio_apbridgea_shutdown_rx_request {
+ struct audio_apbridgea_hdr hdr;
+} __packed;
+
+#endif /*__AUDIO_APBRIDGEA_H */
diff --git a/drivers/staging/greybus/audio_codec.c b/drivers/staging/greybus/audio_codec.c
new file mode 100644
index 0000000000..2f05e761fb
--- /dev/null
+++ b/drivers/staging/greybus/audio_codec.c
@@ -0,0 +1,1094 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * APBridge ALSA SoC dummy codec driver
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <uapi/linux/input.h>
+
+#include "audio_codec.h"
+#include "audio_apbridgea.h"
+#include "audio_manager.h"
+#include "audio_helper.h"
+
+static struct gbaudio_codec_info *gbcodec;
+
+static struct gbaudio_data_connection *
+find_data(struct gbaudio_module_info *module, int id)
+{
+ struct gbaudio_data_connection *data;
+
+ list_for_each_entry(data, &module->data_list, list) {
+ if (id == data->id)
+ return data;
+ }
+ return NULL;
+}
+
+static struct gbaudio_stream_params *
+find_dai_stream_params(struct gbaudio_codec_info *codec, int id, int stream)
+{
+ struct gbaudio_codec_dai *dai;
+
+ list_for_each_entry(dai, &codec->dai_list, list) {
+ if (dai->id == id)
+ return &dai->params[stream];
+ }
+ return NULL;
+}
+
+static int gbaudio_module_enable_tx(struct gbaudio_codec_info *codec,
+ struct gbaudio_module_info *module, int id)
+{
+ int module_state, ret = 0;
+ u16 data_cport, i2s_port, cportid;
+ u8 sig_bits, channels;
+ u32 format, rate;
+ struct gbaudio_data_connection *data;
+ struct gbaudio_stream_params *params;
+
+ /* find the dai */
+ data = find_data(module, id);
+ if (!data) {
+ dev_err(module->dev, "%d:DATA connection missing\n", id);
+ return -ENODEV;
+ }
+ module_state = data->state[SNDRV_PCM_STREAM_PLAYBACK];
+
+ params = find_dai_stream_params(codec, id, SNDRV_PCM_STREAM_PLAYBACK);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ return -EINVAL;
+ }
+
+ /* register cport */
+ if (module_state < GBAUDIO_CODEC_STARTUP) {
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_register_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_TX);
+ if (ret) {
+ dev_err_ratelimited(module->dev, "reg_cport failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_STARTUP;
+ dev_dbg(module->dev, "Dynamic Register %d DAI\n", cportid);
+ }
+
+ /* hw_params */
+ if (module_state < GBAUDIO_CODEC_HWPARAMS) {
+ format = params->format;
+ channels = params->channels;
+ rate = params->rate;
+ sig_bits = params->sig_bits;
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_set_pcm(module->mgmt_connection, data_cport,
+ format, rate, channels, sig_bits);
+ if (ret) {
+ dev_err_ratelimited(module->dev, "set_pcm failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_HWPARAMS;
+ dev_dbg(module->dev, "Dynamic hw_params %d DAI\n", data_cport);
+ }
+
+ /* prepare */
+ if (module_state < GBAUDIO_CODEC_PREPARE) {
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_set_tx_data_size(module->mgmt_connection,
+ data_cport, 192);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "set_tx_data_size failed:%d\n",
+ ret);
+ return ret;
+ }
+ ret = gb_audio_gb_activate_tx(module->mgmt_connection, data_cport);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "activate_tx failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_PREPARE;
+ dev_dbg(module->dev, "Dynamic prepare %d DAI\n", data_cport);
+ }
+
+ return 0;
+}
+
+static int gbaudio_module_disable_tx(struct gbaudio_module_info *module, int id)
+{
+ int ret;
+ u16 data_cport, cportid, i2s_port;
+ int module_state;
+ struct gbaudio_data_connection *data;
+
+ /* find the dai */
+ data = find_data(module, id);
+ if (!data) {
+ dev_err(module->dev, "%d:DATA connection missing\n", id);
+ return -ENODEV;
+ }
+ module_state = data->state[SNDRV_PCM_STREAM_PLAYBACK];
+
+ if (module_state > GBAUDIO_CODEC_HWPARAMS) {
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_deactivate_tx(module->mgmt_connection,
+ data_cport);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "deactivate_tx failed:%d\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Dynamic deactivate %d DAI\n", data_cport);
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_HWPARAMS;
+ }
+
+ if (module_state > GBAUDIO_CODEC_SHUTDOWN) {
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_unregister_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_TX);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "unregister_cport failed:%d\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Dynamic Unregister %d DAI\n", cportid);
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_SHUTDOWN;
+ }
+
+ return 0;
+}
+
+static int gbaudio_module_enable_rx(struct gbaudio_codec_info *codec,
+ struct gbaudio_module_info *module, int id)
+{
+ int module_state, ret = 0;
+ u16 data_cport, i2s_port, cportid;
+ u8 sig_bits, channels;
+ u32 format, rate;
+ struct gbaudio_data_connection *data;
+ struct gbaudio_stream_params *params;
+
+ /* find the dai */
+ data = find_data(module, id);
+ if (!data) {
+ dev_err(module->dev, "%d:DATA connection missing\n", id);
+ return -ENODEV;
+ }
+ module_state = data->state[SNDRV_PCM_STREAM_CAPTURE];
+
+ params = find_dai_stream_params(codec, id, SNDRV_PCM_STREAM_CAPTURE);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ return -EINVAL;
+ }
+
+ /* register cport */
+ if (module_state < GBAUDIO_CODEC_STARTUP) {
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_register_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_RX);
+ if (ret) {
+ dev_err_ratelimited(module->dev, "reg_cport failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_STARTUP;
+ dev_dbg(module->dev, "Dynamic Register %d DAI\n", cportid);
+ }
+
+ /* hw_params */
+ if (module_state < GBAUDIO_CODEC_HWPARAMS) {
+ format = params->format;
+ channels = params->channels;
+ rate = params->rate;
+ sig_bits = params->sig_bits;
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_set_pcm(module->mgmt_connection, data_cport,
+ format, rate, channels, sig_bits);
+ if (ret) {
+ dev_err_ratelimited(module->dev, "set_pcm failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_HWPARAMS;
+ dev_dbg(module->dev, "Dynamic hw_params %d DAI\n", data_cport);
+ }
+
+ /* prepare */
+ if (module_state < GBAUDIO_CODEC_PREPARE) {
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_set_rx_data_size(module->mgmt_connection,
+ data_cport, 192);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "set_rx_data_size failed:%d\n",
+ ret);
+ return ret;
+ }
+ ret = gb_audio_gb_activate_rx(module->mgmt_connection,
+ data_cport);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "activate_rx failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_PREPARE;
+ dev_dbg(module->dev, "Dynamic prepare %d DAI\n", data_cport);
+ }
+
+ return 0;
+}
+
+static int gbaudio_module_disable_rx(struct gbaudio_module_info *module, int id)
+{
+ int ret;
+ u16 data_cport, cportid, i2s_port;
+ int module_state;
+ struct gbaudio_data_connection *data;
+
+ /* find the dai */
+ data = find_data(module, id);
+ if (!data) {
+ dev_err(module->dev, "%d:DATA connection missing\n", id);
+ return -ENODEV;
+ }
+ module_state = data->state[SNDRV_PCM_STREAM_CAPTURE];
+
+ if (module_state > GBAUDIO_CODEC_HWPARAMS) {
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_deactivate_rx(module->mgmt_connection,
+ data_cport);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "deactivate_rx failed:%d\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Dynamic deactivate %d DAI\n", data_cport);
+ data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_HWPARAMS;
+ }
+
+ if (module_state > GBAUDIO_CODEC_SHUTDOWN) {
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_unregister_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_RX);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "unregister_cport failed:%d\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Dynamic Unregister %d DAI\n", cportid);
+ data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_SHUTDOWN;
+ }
+
+ return 0;
+}
+
+int gbaudio_module_update(struct gbaudio_codec_info *codec,
+ struct snd_soc_dapm_widget *w,
+ struct gbaudio_module_info *module, int enable)
+{
+ int dai_id, ret;
+ char intf_name[NAME_SIZE], dir[NAME_SIZE];
+
+ dev_dbg(module->dev, "%s:Module update %s sequence\n", w->name,
+ enable ? "Enable" : "Disable");
+
+ if ((w->id != snd_soc_dapm_aif_in) && (w->id != snd_soc_dapm_aif_out)) {
+ dev_dbg(codec->dev, "No action required for %s\n", w->name);
+ return 0;
+ }
+
+ /* parse dai_id from AIF widget's stream_name */
+ ret = sscanf(w->sname, "%s %d %s", intf_name, &dai_id, dir);
+ if (ret < 3) {
+ dev_err(codec->dev, "Error while parsing dai_id for %s\n", w->name);
+ return -EINVAL;
+ }
+
+ mutex_lock(&codec->lock);
+ if (w->id == snd_soc_dapm_aif_in) {
+ if (enable)
+ ret = gbaudio_module_enable_tx(codec, module, dai_id);
+ else
+ ret = gbaudio_module_disable_tx(module, dai_id);
+ } else if (w->id == snd_soc_dapm_aif_out) {
+ if (enable)
+ ret = gbaudio_module_enable_rx(codec, module, dai_id);
+ else
+ ret = gbaudio_module_disable_rx(module, dai_id);
+ }
+
+ mutex_unlock(&codec->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(gbaudio_module_update);
+
+/*
+ * codec DAI ops
+ */
+static int gbcodec_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ mutex_lock(&codec->lock);
+
+ if (list_empty(&codec->module_list)) {
+ dev_err(codec->dev, "No codec module available\n");
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ params = find_dai_stream_params(codec, dai->id, substream->stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+ params->state = GBAUDIO_CODEC_STARTUP;
+ mutex_unlock(&codec->lock);
+ /* to prevent suspend in case of active audio */
+ pm_stay_awake(dai->dev);
+
+ return 0;
+}
+
+static void gbcodec_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ mutex_lock(&codec->lock);
+
+ if (list_empty(&codec->module_list))
+ dev_info(codec->dev, "No codec module available during shutdown\n");
+
+ params = find_dai_stream_params(codec, dai->id, substream->stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return;
+ }
+ params->state = GBAUDIO_CODEC_SHUTDOWN;
+ mutex_unlock(&codec->lock);
+ pm_relax(dai->dev);
+}
+
+static int gbcodec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hwparams,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ u8 sig_bits, channels;
+ u32 format, rate;
+ struct gbaudio_module_info *module;
+ struct gbaudio_data_connection *data;
+ struct gb_bundle *bundle;
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ mutex_lock(&codec->lock);
+
+ if (list_empty(&codec->module_list)) {
+ dev_err(codec->dev, "No codec module available\n");
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ /*
+ * assuming, currently only 48000 Hz, 16BIT_LE, stereo
+ * is supported, validate params before configuring codec
+ */
+ if (params_channels(hwparams) != 2) {
+ dev_err(dai->dev, "Invalid channel count:%d\n",
+ params_channels(hwparams));
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+ channels = params_channels(hwparams);
+
+ if (params_rate(hwparams) != 48000) {
+ dev_err(dai->dev, "Invalid sampling rate:%d\n",
+ params_rate(hwparams));
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+ rate = GB_AUDIO_PCM_RATE_48000;
+
+ if (params_format(hwparams) != SNDRV_PCM_FORMAT_S16_LE) {
+ dev_err(dai->dev, "Invalid format:%d\n", params_format(hwparams));
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+ format = GB_AUDIO_PCM_FMT_S16_LE;
+
+ /* find the data connection */
+ list_for_each_entry(module, &codec->module_list, list) {
+ data = find_data(module, dai->id);
+ if (data)
+ break;
+ }
+
+ if (!data) {
+ dev_err(dai->dev, "DATA connection missing\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+
+ params = find_dai_stream_params(codec, dai->id, substream->stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+
+ bundle = to_gb_bundle(module->dev);
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret) {
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ ret = gb_audio_apbridgea_set_config(data->connection, 0,
+ AUDIO_APBRIDGEA_PCM_FMT_16,
+ AUDIO_APBRIDGEA_PCM_RATE_48000,
+ 6144000);
+ if (ret) {
+ dev_err_ratelimited(dai->dev, "%d: Error during set_config\n",
+ ret);
+ gb_pm_runtime_put_noidle(bundle);
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ gb_pm_runtime_put_noidle(bundle);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sig_bits = dai->driver->playback.sig_bits;
+ else
+ sig_bits = dai->driver->capture.sig_bits;
+
+ params->state = GBAUDIO_CODEC_HWPARAMS;
+ params->format = format;
+ params->rate = rate;
+ params->channels = channels;
+ params->sig_bits = sig_bits;
+
+ mutex_unlock(&codec->lock);
+ return 0;
+}
+
+static int gbcodec_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ struct gbaudio_module_info *module = NULL, *iter;
+ struct gbaudio_data_connection *data;
+ struct gb_bundle *bundle;
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ mutex_lock(&codec->lock);
+
+ if (list_empty(&codec->module_list)) {
+ dev_err(codec->dev, "No codec module available\n");
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ list_for_each_entry(iter, &codec->module_list, list) {
+ /* find the dai */
+ data = find_data(iter, dai->id);
+ if (data) {
+ module = iter;
+ break;
+ }
+ }
+ if (!data) {
+ dev_err(dai->dev, "DATA connection missing\n");
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ params = find_dai_stream_params(codec, dai->id, substream->stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+
+ bundle = to_gb_bundle(module->dev);
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret) {
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ switch (substream->stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ ret = gb_audio_apbridgea_set_tx_data_size(data->connection, 0, 192);
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ ret = gb_audio_apbridgea_set_rx_data_size(data->connection, 0, 192);
+ break;
+ }
+ if (ret) {
+ gb_pm_runtime_put_noidle(bundle);
+ mutex_unlock(&codec->lock);
+ dev_err_ratelimited(dai->dev, "set_data_size failed:%d\n", ret);
+ return ret;
+ }
+
+ gb_pm_runtime_put_noidle(bundle);
+
+ params->state = GBAUDIO_CODEC_PREPARE;
+ mutex_unlock(&codec->lock);
+ return 0;
+}
+
+static int gbcodec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+ int ret;
+ struct gbaudio_data_connection *data;
+ struct gbaudio_module_info *module = NULL, *iter;
+ struct gb_bundle *bundle;
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ dev_dbg(dai->dev, "Mute:%d, Direction:%s\n", mute,
+ stream ? "CAPTURE" : "PLAYBACK");
+
+ mutex_lock(&codec->lock);
+
+ params = find_dai_stream_params(codec, dai->id, stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+
+ if (list_empty(&codec->module_list)) {
+ dev_err(codec->dev, "No codec module available\n");
+ if (mute) {
+ params->state = GBAUDIO_CODEC_STOP;
+ ret = 0;
+ } else {
+ ret = -ENODEV;
+ }
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ list_for_each_entry(iter, &codec->module_list, list) {
+ /* find the dai */
+ data = find_data(iter, dai->id);
+ if (data) {
+ module = iter;
+ break;
+ }
+ }
+ if (!data) {
+ dev_err(dai->dev, "%s DATA connection missing\n",
+ dai->name);
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ bundle = to_gb_bundle(module->dev);
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret) {
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ if (!mute && !stream) {/* start playback */
+ ret = gb_audio_apbridgea_prepare_tx(data->connection, 0);
+ if (!ret)
+ ret = gb_audio_apbridgea_start_tx(data->connection, 0, 0);
+ params->state = GBAUDIO_CODEC_START;
+ } else if (!mute && stream) {/* start capture */
+ ret = gb_audio_apbridgea_prepare_rx(data->connection, 0);
+ if (!ret)
+ ret = gb_audio_apbridgea_start_rx(data->connection, 0);
+ params->state = GBAUDIO_CODEC_START;
+ } else if (mute && !stream) {/* stop playback */
+ ret = gb_audio_apbridgea_stop_tx(data->connection, 0);
+ if (!ret)
+ ret = gb_audio_apbridgea_shutdown_tx(data->connection, 0);
+ params->state = GBAUDIO_CODEC_STOP;
+ } else if (mute && stream) {/* stop capture */
+ ret = gb_audio_apbridgea_stop_rx(data->connection, 0);
+ if (!ret)
+ ret = gb_audio_apbridgea_shutdown_rx(data->connection, 0);
+ params->state = GBAUDIO_CODEC_STOP;
+ } else {
+ ret = -EINVAL;
+ }
+
+ if (ret)
+ dev_err_ratelimited(dai->dev,
+ "%s:Error during %s %s stream:%d\n",
+ module->name, mute ? "Mute" : "Unmute",
+ stream ? "Capture" : "Playback", ret);
+
+ gb_pm_runtime_put_noidle(bundle);
+ mutex_unlock(&codec->lock);
+ return ret;
+}
+
+static const struct snd_soc_dai_ops gbcodec_dai_ops = {
+ .startup = gbcodec_startup,
+ .shutdown = gbcodec_shutdown,
+ .hw_params = gbcodec_hw_params,
+ .prepare = gbcodec_prepare,
+ .mute_stream = gbcodec_mute_stream,
+};
+
+static struct snd_soc_dai_driver gbaudio_dai[] = {
+ {
+ .name = "apb-i2s0",
+ .id = 0,
+ .playback = {
+ .stream_name = "I2S 0 Playback",
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_max = 48000,
+ .rate_min = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .sig_bits = 16,
+ },
+ .capture = {
+ .stream_name = "I2S 0 Capture",
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_max = 48000,
+ .rate_min = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .sig_bits = 16,
+ },
+ .ops = &gbcodec_dai_ops,
+ },
+};
+
+static int gbaudio_init_jack(struct gbaudio_module_info *module,
+ struct snd_soc_card *card)
+{
+ int ret;
+ struct gbaudio_jack *jack, *n;
+ struct snd_soc_jack_pin *headset, *button;
+
+ if (!module->jack_mask)
+ return 0;
+
+ snprintf(module->jack_name, NAME_SIZE, "GB %d Headset Jack",
+ module->dev_id);
+
+ headset = devm_kzalloc(module->dev, sizeof(*headset), GFP_KERNEL);
+ if (!headset)
+ return -ENOMEM;
+
+ headset->pin = module->jack_name;
+ headset->mask = module->jack_mask;
+ ret = snd_soc_card_jack_new_pins(card, module->jack_name,
+ module->jack_mask,
+ &module->headset.jack, headset, 1);
+ if (ret) {
+ dev_err(module->dev, "Failed to create new jack\n");
+ return ret;
+ }
+
+ /* Add to module's jack list */
+ list_add(&module->headset.list, &module->jack_list);
+
+ if (!module->button_mask)
+ return 0;
+
+ snprintf(module->button_name, NAME_SIZE, "GB %d Button Jack",
+ module->dev_id);
+ button = devm_kzalloc(module->dev, sizeof(*button), GFP_KERNEL);
+ if (!button) {
+ ret = -ENOMEM;
+ goto free_jacks;
+ }
+
+ button->pin = module->button_name;
+ button->mask = module->button_mask;
+ ret = snd_soc_card_jack_new_pins(card, module->button_name,
+ module->button_mask,
+ &module->button.jack,
+ button, 1);
+ if (ret) {
+ dev_err(module->dev, "Failed to create button jack\n");
+ goto free_jacks;
+ }
+
+ /* Add to module's jack list */
+ list_add(&module->button.list, &module->jack_list);
+
+ /*
+ * Currently, max 4 buttons are supported with following key mapping
+ * BTN_0 = KEY_MEDIA
+ * BTN_1 = KEY_VOICECOMMAND
+ * BTN_2 = KEY_VOLUMEUP
+ * BTN_3 = KEY_VOLUMEDOWN
+ */
+
+ if (module->button_mask & SND_JACK_BTN_0) {
+ ret = snd_jack_set_key(module->button.jack.jack, SND_JACK_BTN_0,
+ KEY_MEDIA);
+ if (ret) {
+ dev_err(module->dev, "Failed to set BTN_0\n");
+ goto free_jacks;
+ }
+ }
+
+ if (module->button_mask & SND_JACK_BTN_1) {
+ ret = snd_jack_set_key(module->button.jack.jack, SND_JACK_BTN_1,
+ KEY_VOICECOMMAND);
+ if (ret) {
+ dev_err(module->dev, "Failed to set BTN_1\n");
+ goto free_jacks;
+ }
+ }
+
+ if (module->button_mask & SND_JACK_BTN_2) {
+ ret = snd_jack_set_key(module->button.jack.jack, SND_JACK_BTN_2,
+ KEY_VOLUMEUP);
+ if (ret) {
+ dev_err(module->dev, "Failed to set BTN_2\n");
+ goto free_jacks;
+ }
+ }
+
+ if (module->button_mask & SND_JACK_BTN_3) {
+ ret = snd_jack_set_key(module->button.jack.jack, SND_JACK_BTN_3,
+ KEY_VOLUMEDOWN);
+ if (ret) {
+ dev_err(module->dev, "Failed to set BTN_0\n");
+ goto free_jacks;
+ }
+ }
+
+ /* FIXME
+ * verify if this is really required
+ set_bit(INPUT_PROP_NO_DUMMY_RELEASE,
+ module->button.jack.jack->input_dev->propbit);
+ */
+
+ return 0;
+
+free_jacks:
+ list_for_each_entry_safe(jack, n, &module->jack_list, list) {
+ snd_device_free(card->snd_card, jack->jack.jack);
+ list_del(&jack->list);
+ }
+
+ return ret;
+}
+
+int gbaudio_register_module(struct gbaudio_module_info *module)
+{
+ int ret;
+ struct snd_soc_component *comp;
+ struct gbaudio_jack *jack = NULL;
+
+ if (!gbcodec) {
+ dev_err(module->dev, "GB Codec not yet probed\n");
+ return -EAGAIN;
+ }
+
+ comp = gbcodec->component;
+
+ mutex_lock(&gbcodec->register_mutex);
+
+ if (module->num_dais) {
+ dev_err(gbcodec->dev,
+ "%d:DAIs not supported via gbcodec driver\n",
+ module->num_dais);
+ mutex_unlock(&gbcodec->register_mutex);
+ return -EINVAL;
+ }
+
+ ret = gbaudio_init_jack(module, comp->card);
+ if (ret) {
+ mutex_unlock(&gbcodec->register_mutex);
+ return ret;
+ }
+
+ if (module->dapm_widgets)
+ snd_soc_dapm_new_controls(&comp->dapm, module->dapm_widgets,
+ module->num_dapm_widgets);
+ if (module->controls)
+ snd_soc_add_component_controls(comp, module->controls,
+ module->num_controls);
+ if (module->dapm_routes)
+ snd_soc_dapm_add_routes(&comp->dapm, module->dapm_routes,
+ module->num_dapm_routes);
+
+ /* card already instantiated, create widgets here only */
+ if (comp->card->instantiated) {
+ gbaudio_dapm_link_component_dai_widgets(comp->card, &comp->dapm);
+#ifdef CONFIG_SND_JACK
+ /*
+ * register jack devices for this module
+ * from codec->jack_list
+ */
+ list_for_each_entry(jack, &module->jack_list, list) {
+ snd_device_register(comp->card->snd_card,
+ jack->jack.jack);
+ }
+#endif
+ }
+
+ mutex_lock(&gbcodec->lock);
+ list_add(&module->list, &gbcodec->module_list);
+ mutex_unlock(&gbcodec->lock);
+
+ if (comp->card->instantiated)
+ ret = snd_soc_dapm_new_widgets(comp->card);
+ dev_dbg(comp->dev, "Registered %s module\n", module->name);
+
+ mutex_unlock(&gbcodec->register_mutex);
+ return ret;
+}
+EXPORT_SYMBOL(gbaudio_register_module);
+
+static void gbaudio_codec_clean_data_tx(struct gbaudio_data_connection *data)
+{
+ u16 i2s_port, cportid;
+ int ret;
+
+ if (list_is_singular(&gbcodec->module_list)) {
+ ret = gb_audio_apbridgea_stop_tx(data->connection, 0);
+ if (ret)
+ return;
+ ret = gb_audio_apbridgea_shutdown_tx(data->connection, 0);
+ if (ret)
+ return;
+ }
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_unregister_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_TX);
+ data->state[0] = GBAUDIO_CODEC_SHUTDOWN;
+}
+
+static void gbaudio_codec_clean_data_rx(struct gbaudio_data_connection *data)
+{
+ u16 i2s_port, cportid;
+ int ret;
+
+ if (list_is_singular(&gbcodec->module_list)) {
+ ret = gb_audio_apbridgea_stop_rx(data->connection, 0);
+ if (ret)
+ return;
+ ret = gb_audio_apbridgea_shutdown_rx(data->connection, 0);
+ if (ret)
+ return;
+ }
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_unregister_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_RX);
+ data->state[1] = GBAUDIO_CODEC_SHUTDOWN;
+}
+
+static void gbaudio_codec_cleanup(struct gbaudio_module_info *module)
+{
+ struct gbaudio_data_connection *data;
+ int pb_state, cap_state;
+
+ dev_dbg(gbcodec->dev, "%s: removed, cleanup APBridge\n", module->name);
+ list_for_each_entry(data, &module->data_list, list) {
+ pb_state = data->state[0];
+ cap_state = data->state[1];
+
+ if (pb_state > GBAUDIO_CODEC_SHUTDOWN)
+ gbaudio_codec_clean_data_tx(data);
+
+ if (cap_state > GBAUDIO_CODEC_SHUTDOWN)
+ gbaudio_codec_clean_data_rx(data);
+ }
+}
+
+void gbaudio_unregister_module(struct gbaudio_module_info *module)
+{
+ struct snd_soc_component *comp = gbcodec->component;
+ struct gbaudio_jack *jack, *n;
+ int mask;
+
+ dev_dbg(comp->dev, "Unregister %s module\n", module->name);
+
+ mutex_lock(&gbcodec->register_mutex);
+ mutex_lock(&gbcodec->lock);
+ gbaudio_codec_cleanup(module);
+ list_del(&module->list);
+ dev_dbg(comp->dev, "Process Unregister %s module\n", module->name);
+ mutex_unlock(&gbcodec->lock);
+
+#ifdef CONFIG_SND_JACK
+ /* free jack devices for this module jack_list */
+ list_for_each_entry_safe(jack, n, &module->jack_list, list) {
+ if (jack == &module->headset)
+ mask = GBCODEC_JACK_MASK;
+ else if (jack == &module->button)
+ mask = GBCODEC_JACK_BUTTON_MASK;
+ else
+ mask = 0;
+ if (mask) {
+ dev_dbg(module->dev, "Report %s removal\n",
+ jack->jack.jack->id);
+ snd_soc_jack_report(&jack->jack, 0, mask);
+ snd_device_free(comp->card->snd_card,
+ jack->jack.jack);
+ list_del(&jack->list);
+ }
+ }
+#endif
+
+ if (module->dapm_routes) {
+ dev_dbg(comp->dev, "Removing %d routes\n",
+ module->num_dapm_routes);
+ snd_soc_dapm_del_routes(&comp->dapm, module->dapm_routes,
+ module->num_dapm_routes);
+ }
+ if (module->controls) {
+ dev_dbg(comp->dev, "Removing %d controls\n",
+ module->num_controls);
+ /* release control semaphore */
+ gbaudio_remove_component_controls(comp, module->controls,
+ module->num_controls);
+ }
+ if (module->dapm_widgets) {
+ dev_dbg(comp->dev, "Removing %d widgets\n",
+ module->num_dapm_widgets);
+ gbaudio_dapm_free_controls(&comp->dapm, module->dapm_widgets,
+ module->num_dapm_widgets);
+ }
+
+ dev_dbg(comp->dev, "Unregistered %s module\n", module->name);
+
+ mutex_unlock(&gbcodec->register_mutex);
+}
+EXPORT_SYMBOL(gbaudio_unregister_module);
+
+/*
+ * component driver ops
+ */
+static int gbcodec_probe(struct snd_soc_component *comp)
+{
+ int i;
+ struct gbaudio_codec_info *info;
+ struct gbaudio_codec_dai *dai;
+
+ info = devm_kzalloc(comp->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = comp->dev;
+ INIT_LIST_HEAD(&info->module_list);
+ mutex_init(&info->lock);
+ mutex_init(&info->register_mutex);
+ INIT_LIST_HEAD(&info->dai_list);
+
+ /* init dai_list used to maintain runtime stream info */
+ for (i = 0; i < ARRAY_SIZE(gbaudio_dai); i++) {
+ dai = devm_kzalloc(comp->dev, sizeof(*dai), GFP_KERNEL);
+ if (!dai)
+ return -ENOMEM;
+ dai->id = gbaudio_dai[i].id;
+ list_add(&dai->list, &info->dai_list);
+ }
+
+ info->component = comp;
+ snd_soc_component_set_drvdata(comp, info);
+ gbcodec = info;
+
+ device_init_wakeup(comp->dev, 1);
+ return 0;
+}
+
+static int gbcodec_write(struct snd_soc_component *comp, unsigned int reg,
+ unsigned int value)
+{
+ return 0;
+}
+
+static unsigned int gbcodec_read(struct snd_soc_component *comp,
+ unsigned int reg)
+{
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_codec_dev_gbaudio = {
+ .probe = gbcodec_probe,
+ .read = gbcodec_read,
+ .write = gbcodec_write,
+};
+
+#ifdef CONFIG_PM
+static int gbaudio_codec_suspend(struct device *dev)
+{
+ dev_dbg(dev, "%s: suspend\n", __func__);
+ return 0;
+}
+
+static int gbaudio_codec_resume(struct device *dev)
+{
+ dev_dbg(dev, "%s: resume\n", __func__);
+ return 0;
+}
+
+static const struct dev_pm_ops gbaudio_codec_pm_ops = {
+ .suspend = gbaudio_codec_suspend,
+ .resume = gbaudio_codec_resume,
+};
+#endif
+
+static int gbaudio_codec_probe(struct platform_device *pdev)
+{
+ return devm_snd_soc_register_component(&pdev->dev,
+ &soc_codec_dev_gbaudio,
+ gbaudio_dai, ARRAY_SIZE(gbaudio_dai));
+}
+
+static const struct of_device_id greybus_asoc_machine_of_match[] = {
+ { .compatible = "toshiba,apb-dummy-codec", },
+ {},
+};
+
+static struct platform_driver gbaudio_codec_driver = {
+ .driver = {
+ .name = "apb-dummy-codec",
+#ifdef CONFIG_PM
+ .pm = &gbaudio_codec_pm_ops,
+#endif
+ .of_match_table = greybus_asoc_machine_of_match,
+ },
+ .probe = gbaudio_codec_probe,
+};
+module_platform_driver(gbaudio_codec_driver);
+
+MODULE_DESCRIPTION("APBridge ALSA SoC dummy codec driver");
+MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@linaro.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:apb-dummy-codec");
diff --git a/drivers/staging/greybus/audio_codec.h b/drivers/staging/greybus/audio_codec.h
new file mode 100644
index 0000000000..f3f7a7ec6b
--- /dev/null
+++ b/drivers/staging/greybus/audio_codec.h
@@ -0,0 +1,243 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Greybus audio driver
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ */
+
+#ifndef __LINUX_GBAUDIO_CODEC_H
+#define __LINUX_GBAUDIO_CODEC_H
+
+#include <linux/greybus.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#define NAME_SIZE 32
+#define MAX_DAIS 2 /* APB1, APB2 */
+
+enum {
+ APB1_PCM = 0,
+ APB2_PCM,
+ NUM_CODEC_DAIS,
+};
+
+/*
+ * device_type should be same as defined in audio.h
+ * (Android media layer)
+ */
+enum {
+ GBAUDIO_DEVICE_NONE = 0x0,
+ /* reserved bits */
+ GBAUDIO_DEVICE_BIT_IN = 0x80000000,
+ GBAUDIO_DEVICE_BIT_DEFAULT = 0x40000000,
+ /* output devices */
+ GBAUDIO_DEVICE_OUT_SPEAKER = 0x2,
+ GBAUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4,
+ GBAUDIO_DEVICE_OUT_WIRED_HEADPHONE = 0x8,
+ /* input devices */
+ GBAUDIO_DEVICE_IN_BUILTIN_MIC = GBAUDIO_DEVICE_BIT_IN | 0x4,
+ GBAUDIO_DEVICE_IN_WIRED_HEADSET = GBAUDIO_DEVICE_BIT_IN | 0x10,
+};
+
+#define GBCODEC_JACK_MASK 0x0000FFFF
+#define GBCODEC_JACK_BUTTON_MASK 0xFFFF0000
+
+enum gbaudio_codec_state {
+ GBAUDIO_CODEC_SHUTDOWN = 0,
+ GBAUDIO_CODEC_STARTUP,
+ GBAUDIO_CODEC_HWPARAMS,
+ GBAUDIO_CODEC_PREPARE,
+ GBAUDIO_CODEC_START,
+ GBAUDIO_CODEC_STOP,
+};
+
+struct gbaudio_stream_params {
+ int state;
+ u8 sig_bits, channels;
+ u32 format, rate;
+};
+
+struct gbaudio_codec_dai {
+ int id;
+ /* runtime params for playback/capture streams */
+ struct gbaudio_stream_params params[2];
+ struct list_head list;
+};
+
+struct gbaudio_codec_info {
+ struct device *dev;
+ struct snd_soc_component *component;
+ struct list_head module_list;
+ /* to maintain runtime stream params for each DAI */
+ struct list_head dai_list;
+ struct mutex lock;
+ struct mutex register_mutex;
+};
+
+struct gbaudio_widget {
+ __u8 id;
+ const char *name;
+ struct list_head list;
+};
+
+struct gbaudio_control {
+ __u8 id;
+ char *name;
+ char *wname;
+ const char * const *texts;
+ int items;
+ struct list_head list;
+};
+
+struct gbaudio_data_connection {
+ int id;
+ __le16 data_cport;
+ struct gb_connection *connection;
+ struct list_head list;
+ /* maintain runtime state for playback/capture stream */
+ int state[2];
+};
+
+/* stream direction */
+#define GB_PLAYBACK BIT(0)
+#define GB_CAPTURE BIT(1)
+
+enum gbaudio_module_state {
+ GBAUDIO_MODULE_OFF = 0,
+ GBAUDIO_MODULE_ON,
+};
+
+struct gbaudio_jack {
+ struct snd_soc_jack jack;
+ struct list_head list;
+};
+
+struct gbaudio_module_info {
+ /* module info */
+ struct device *dev;
+ int dev_id; /* check if it should be bundle_id/hd_cport_id */
+ int vid;
+ int pid;
+ int type;
+ int set_uevent;
+ char vstr[NAME_SIZE];
+ char pstr[NAME_SIZE];
+ struct list_head list;
+ /* need to share this info to above user space */
+ int manager_id;
+ char name[NAME_SIZE];
+ unsigned int ip_devices;
+ unsigned int op_devices;
+
+ /* jack related */
+ char jack_name[NAME_SIZE];
+ char button_name[NAME_SIZE];
+ int jack_type;
+ int jack_mask;
+ int button_mask;
+ int button_status;
+ struct gbaudio_jack headset;
+ struct gbaudio_jack button;
+
+ /* connection info */
+ struct gb_connection *mgmt_connection;
+ size_t num_data_connections;
+ struct list_head data_list;
+
+ /* topology related */
+ int num_dais;
+ int num_controls;
+ int num_dapm_widgets;
+ int num_dapm_routes;
+ unsigned long dai_offset;
+ unsigned long widget_offset;
+ unsigned long control_offset;
+ unsigned long route_offset;
+ struct snd_kcontrol_new *controls;
+ struct snd_soc_dapm_widget *dapm_widgets;
+ struct snd_soc_dapm_route *dapm_routes;
+ struct snd_soc_dai_driver *dais;
+
+ struct list_head widget_list;
+ struct list_head ctl_list;
+ struct list_head widget_ctl_list;
+ struct list_head jack_list;
+
+ struct gb_audio_topology *topology;
+};
+
+int gbaudio_tplg_parse_data(struct gbaudio_module_info *module,
+ struct gb_audio_topology *tplg_data);
+void gbaudio_tplg_release(struct gbaudio_module_info *module);
+
+int gbaudio_module_update(struct gbaudio_codec_info *codec,
+ struct snd_soc_dapm_widget *w,
+ struct gbaudio_module_info *module,
+ int enable);
+int gbaudio_register_module(struct gbaudio_module_info *module);
+void gbaudio_unregister_module(struct gbaudio_module_info *module);
+
+/* protocol related */
+int gb_audio_gb_get_topology(struct gb_connection *connection,
+ struct gb_audio_topology **topology);
+int gb_audio_gb_get_control(struct gb_connection *connection,
+ u8 control_id, u8 index,
+ struct gb_audio_ctl_elem_value *value);
+int gb_audio_gb_set_control(struct gb_connection *connection,
+ u8 control_id, u8 index,
+ struct gb_audio_ctl_elem_value *value);
+int gb_audio_gb_enable_widget(struct gb_connection *connection,
+ u8 widget_id);
+int gb_audio_gb_disable_widget(struct gb_connection *connection,
+ u8 widget_id);
+int gb_audio_gb_get_pcm(struct gb_connection *connection,
+ u16 data_cport, u32 *format,
+ u32 *rate, u8 *channels,
+ u8 *sig_bits);
+int gb_audio_gb_set_pcm(struct gb_connection *connection,
+ u16 data_cport, u32 format,
+ u32 rate, u8 channels,
+ u8 sig_bits);
+int gb_audio_gb_set_tx_data_size(struct gb_connection *connection,
+ u16 data_cport, u16 size);
+int gb_audio_gb_activate_tx(struct gb_connection *connection,
+ u16 data_cport);
+int gb_audio_gb_deactivate_tx(struct gb_connection *connection,
+ u16 data_cport);
+int gb_audio_gb_set_rx_data_size(struct gb_connection *connection,
+ u16 data_cport, u16 size);
+int gb_audio_gb_activate_rx(struct gb_connection *connection,
+ u16 data_cport);
+int gb_audio_gb_deactivate_rx(struct gb_connection *connection,
+ u16 data_cport);
+int gb_audio_apbridgea_set_config(struct gb_connection *connection,
+ __u16 i2s_port, __u32 format,
+ __u32 rate, __u32 mclk_freq);
+int gb_audio_apbridgea_register_cport(struct gb_connection *connection,
+ __u16 i2s_port, __u16 cportid,
+ __u8 direction);
+int gb_audio_apbridgea_unregister_cport(struct gb_connection *connection,
+ __u16 i2s_port, __u16 cportid,
+ __u8 direction);
+int gb_audio_apbridgea_set_tx_data_size(struct gb_connection *connection,
+ __u16 i2s_port, __u16 size);
+int gb_audio_apbridgea_prepare_tx(struct gb_connection *connection,
+ __u16 i2s_port);
+int gb_audio_apbridgea_start_tx(struct gb_connection *connection,
+ __u16 i2s_port, __u64 timestamp);
+int gb_audio_apbridgea_stop_tx(struct gb_connection *connection,
+ __u16 i2s_port);
+int gb_audio_apbridgea_shutdown_tx(struct gb_connection *connection,
+ __u16 i2s_port);
+int gb_audio_apbridgea_set_rx_data_size(struct gb_connection *connection,
+ __u16 i2s_port, __u16 size);
+int gb_audio_apbridgea_prepare_rx(struct gb_connection *connection,
+ __u16 i2s_port);
+int gb_audio_apbridgea_start_rx(struct gb_connection *connection,
+ __u16 i2s_port);
+int gb_audio_apbridgea_stop_rx(struct gb_connection *connection,
+ __u16 i2s_port);
+int gb_audio_apbridgea_shutdown_rx(struct gb_connection *connection,
+ __u16 i2s_port);
+
+#endif /* __LINUX_GBAUDIO_CODEC_H */
diff --git a/drivers/staging/greybus/audio_gb.c b/drivers/staging/greybus/audio_gb.c
new file mode 100644
index 0000000000..9d8994fdb4
--- /dev/null
+++ b/drivers/staging/greybus/audio_gb.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Audio Device Class Protocol helpers
+ *
+ * Copyright 2015-2016 Google Inc.
+ */
+
+#include <linux/greybus.h>
+#include "audio_codec.h"
+
+/* TODO: Split into separate calls */
+int gb_audio_gb_get_topology(struct gb_connection *connection,
+ struct gb_audio_topology **topology)
+{
+ struct gb_audio_get_topology_size_response size_resp;
+ struct gb_audio_topology *topo;
+ u16 size;
+ int ret;
+
+ ret = gb_operation_sync(connection, GB_AUDIO_TYPE_GET_TOPOLOGY_SIZE,
+ NULL, 0, &size_resp, sizeof(size_resp));
+ if (ret)
+ return ret;
+
+ size = le16_to_cpu(size_resp.size);
+ if (size < sizeof(*topo))
+ return -ENODATA;
+
+ topo = kzalloc(size, GFP_KERNEL);
+ if (!topo)
+ return -ENOMEM;
+
+ ret = gb_operation_sync(connection, GB_AUDIO_TYPE_GET_TOPOLOGY, NULL, 0,
+ topo, size);
+ if (ret) {
+ kfree(topo);
+ return ret;
+ }
+
+ *topology = topo;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_get_topology);
+
+int gb_audio_gb_get_control(struct gb_connection *connection,
+ u8 control_id, u8 index,
+ struct gb_audio_ctl_elem_value *value)
+{
+ struct gb_audio_get_control_request req;
+ struct gb_audio_get_control_response resp;
+ int ret;
+
+ req.control_id = control_id;
+ req.index = index;
+
+ ret = gb_operation_sync(connection, GB_AUDIO_TYPE_GET_CONTROL,
+ &req, sizeof(req), &resp, sizeof(resp));
+ if (ret)
+ return ret;
+
+ memcpy(value, &resp.value, sizeof(*value));
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_get_control);
+
+int gb_audio_gb_set_control(struct gb_connection *connection,
+ u8 control_id, u8 index,
+ struct gb_audio_ctl_elem_value *value)
+{
+ struct gb_audio_set_control_request req;
+
+ req.control_id = control_id;
+ req.index = index;
+ memcpy(&req.value, value, sizeof(req.value));
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_SET_CONTROL,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_set_control);
+
+int gb_audio_gb_enable_widget(struct gb_connection *connection,
+ u8 widget_id)
+{
+ struct gb_audio_enable_widget_request req;
+
+ req.widget_id = widget_id;
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_ENABLE_WIDGET,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_enable_widget);
+
+int gb_audio_gb_disable_widget(struct gb_connection *connection,
+ u8 widget_id)
+{
+ struct gb_audio_disable_widget_request req;
+
+ req.widget_id = widget_id;
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_DISABLE_WIDGET,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_disable_widget);
+
+int gb_audio_gb_get_pcm(struct gb_connection *connection, u16 data_cport,
+ u32 *format, u32 *rate, u8 *channels,
+ u8 *sig_bits)
+{
+ struct gb_audio_get_pcm_request req;
+ struct gb_audio_get_pcm_response resp;
+ int ret;
+
+ req.data_cport = cpu_to_le16(data_cport);
+
+ ret = gb_operation_sync(connection, GB_AUDIO_TYPE_GET_PCM,
+ &req, sizeof(req), &resp, sizeof(resp));
+ if (ret)
+ return ret;
+
+ *format = le32_to_cpu(resp.format);
+ *rate = le32_to_cpu(resp.rate);
+ *channels = resp.channels;
+ *sig_bits = resp.sig_bits;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_get_pcm);
+
+int gb_audio_gb_set_pcm(struct gb_connection *connection, u16 data_cport,
+ u32 format, u32 rate, u8 channels,
+ u8 sig_bits)
+{
+ struct gb_audio_set_pcm_request req;
+
+ req.data_cport = cpu_to_le16(data_cport);
+ req.format = cpu_to_le32(format);
+ req.rate = cpu_to_le32(rate);
+ req.channels = channels;
+ req.sig_bits = sig_bits;
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_SET_PCM,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_set_pcm);
+
+int gb_audio_gb_set_tx_data_size(struct gb_connection *connection,
+ u16 data_cport, u16 size)
+{
+ struct gb_audio_set_tx_data_size_request req;
+
+ req.data_cport = cpu_to_le16(data_cport);
+ req.size = cpu_to_le16(size);
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_SET_TX_DATA_SIZE,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_set_tx_data_size);
+
+int gb_audio_gb_activate_tx(struct gb_connection *connection,
+ u16 data_cport)
+{
+ struct gb_audio_activate_tx_request req;
+
+ req.data_cport = cpu_to_le16(data_cport);
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_ACTIVATE_TX,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_activate_tx);
+
+int gb_audio_gb_deactivate_tx(struct gb_connection *connection,
+ u16 data_cport)
+{
+ struct gb_audio_deactivate_tx_request req;
+
+ req.data_cport = cpu_to_le16(data_cport);
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_DEACTIVATE_TX,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_deactivate_tx);
+
+int gb_audio_gb_set_rx_data_size(struct gb_connection *connection,
+ u16 data_cport, u16 size)
+{
+ struct gb_audio_set_rx_data_size_request req;
+
+ req.data_cport = cpu_to_le16(data_cport);
+ req.size = cpu_to_le16(size);
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_SET_RX_DATA_SIZE,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_set_rx_data_size);
+
+int gb_audio_gb_activate_rx(struct gb_connection *connection,
+ u16 data_cport)
+{
+ struct gb_audio_activate_rx_request req;
+
+ req.data_cport = cpu_to_le16(data_cport);
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_ACTIVATE_RX,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_activate_rx);
+
+int gb_audio_gb_deactivate_rx(struct gb_connection *connection,
+ u16 data_cport)
+{
+ struct gb_audio_deactivate_rx_request req;
+
+ req.data_cport = cpu_to_le16(data_cport);
+
+ return gb_operation_sync(connection, GB_AUDIO_TYPE_DEACTIVATE_RX,
+ &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_deactivate_rx);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("greybus:audio-gb");
+MODULE_DESCRIPTION("Greybus Audio Device Class Protocol library");
+MODULE_AUTHOR("Mark Greer <mgreer@animalcreek.com>");
diff --git a/drivers/staging/greybus/audio_helper.c b/drivers/staging/greybus/audio_helper.c
new file mode 100644
index 0000000000..97ce5b9ad7
--- /dev/null
+++ b/drivers/staging/greybus/audio_helper.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Audio Sound SoC helper APIs
+ */
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include "audio_helper.h"
+
+#define gbaudio_dapm_for_each_direction(dir) \
+ for ((dir) = SND_SOC_DAPM_DIR_IN; (dir) <= SND_SOC_DAPM_DIR_OUT; \
+ (dir)++)
+
+static void gbaudio_dapm_link_dai_widget(struct snd_soc_dapm_widget *dai_w,
+ struct snd_soc_card *card)
+{
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dapm_widget *src, *sink;
+ struct snd_soc_dai *dai = dai_w->priv;
+
+ /* ...find all widgets with the same stream and link them */
+ list_for_each_entry(w, &card->widgets, list) {
+ if (w->dapm != dai_w->dapm)
+ continue;
+
+ switch (w->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ continue;
+ default:
+ break;
+ }
+
+ if (!w->sname || !strstr(w->sname, dai_w->sname))
+ continue;
+
+ /*
+ * check if widget is already linked,
+ * if (w->linked)
+ * return;
+ */
+
+ if (dai_w->id == snd_soc_dapm_dai_in) {
+ src = dai_w;
+ sink = w;
+ } else {
+ src = w;
+ sink = dai_w;
+ }
+ dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
+ /* Add the DAPM path and set widget's linked status
+ * snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);
+ * w->linked = 1;
+ */
+ }
+}
+
+int gbaudio_dapm_link_component_dai_widgets(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm)
+{
+ struct snd_soc_dapm_widget *dai_w;
+
+ /* For each DAI widget... */
+ list_for_each_entry(dai_w, &card->widgets, list) {
+ if (dai_w->dapm != dapm)
+ continue;
+ switch (dai_w->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ break;
+ default:
+ continue;
+ }
+ gbaudio_dapm_link_dai_widget(dai_w, card);
+ }
+
+ return 0;
+}
+
+static void gbaudio_dapm_free_path(struct snd_soc_dapm_path *path)
+{
+ list_del(&path->list_node[SND_SOC_DAPM_DIR_IN]);
+ list_del(&path->list_node[SND_SOC_DAPM_DIR_OUT]);
+ list_del(&path->list_kcontrol);
+ list_del(&path->list);
+ kfree(path);
+}
+
+static void gbaudio_dapm_free_widget(struct snd_soc_dapm_widget *w)
+{
+ struct snd_soc_dapm_path *p, *next_p;
+ enum snd_soc_dapm_direction dir;
+
+ list_del(&w->list);
+ /*
+ * remove source and sink paths associated to this widget.
+ * While removing the path, remove reference to it from both
+ * source and sink widgets so that path is removed only once.
+ */
+ gbaudio_dapm_for_each_direction(dir) {
+ snd_soc_dapm_widget_for_each_path_safe(w, dir, p, next_p)
+ gbaudio_dapm_free_path(p);
+ }
+
+ kfree(w->kcontrols);
+ kfree_const(w->name);
+ kfree_const(w->sname);
+ kfree(w);
+}
+
+int gbaudio_dapm_free_controls(struct snd_soc_dapm_context *dapm,
+ const struct snd_soc_dapm_widget *widget,
+ int num)
+{
+ int i;
+ struct snd_soc_dapm_widget *w, *tmp_w;
+
+ mutex_lock(&dapm->card->dapm_mutex);
+ for (i = 0; i < num; i++) {
+ /* below logic can be optimized to identify widget pointer */
+ w = NULL;
+ list_for_each_entry(tmp_w, &dapm->card->widgets, list) {
+ if (tmp_w->dapm == dapm &&
+ !strcmp(tmp_w->name, widget->name)) {
+ w = tmp_w;
+ break;
+ }
+ }
+ if (!w) {
+ dev_err(dapm->dev, "%s: widget not found\n",
+ widget->name);
+ widget++;
+ continue;
+ }
+ widget++;
+ gbaudio_dapm_free_widget(w);
+ }
+ mutex_unlock(&dapm->card->dapm_mutex);
+ return 0;
+}
+
+static int gbaudio_remove_controls(struct snd_card *card, struct device *dev,
+ const struct snd_kcontrol_new *controls,
+ int num_controls, const char *prefix)
+{
+ int i, err;
+
+ for (i = 0; i < num_controls; i++) {
+ const struct snd_kcontrol_new *control = &controls[i];
+ struct snd_ctl_elem_id id;
+
+ if (prefix)
+ snprintf(id.name, sizeof(id.name), "%s %s", prefix,
+ control->name);
+ else
+ strscpy(id.name, control->name, sizeof(id.name));
+ id.numid = 0;
+ id.iface = control->iface;
+ id.device = control->device;
+ id.subdevice = control->subdevice;
+ id.index = control->index;
+ err = snd_ctl_remove_id(card, &id);
+ if (err < 0)
+ dev_err(dev, "%d: Failed to remove %s\n", err,
+ control->name);
+ }
+ return 0;
+}
+
+int gbaudio_remove_component_controls(struct snd_soc_component *component,
+ const struct snd_kcontrol_new *controls,
+ unsigned int num_controls)
+{
+ struct snd_card *card = component->card->snd_card;
+
+ return gbaudio_remove_controls(card, component->dev, controls,
+ num_controls, component->name_prefix);
+}
diff --git a/drivers/staging/greybus/audio_helper.h b/drivers/staging/greybus/audio_helper.h
new file mode 100644
index 0000000000..5cf1c6d7d3
--- /dev/null
+++ b/drivers/staging/greybus/audio_helper.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Greybus Audio Sound SoC helper APIs
+ */
+
+#ifndef __LINUX_GBAUDIO_HELPER_H
+#define __LINUX_GBAUDIO_HELPER_H
+
+int gbaudio_dapm_link_component_dai_widgets(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm);
+int gbaudio_dapm_free_controls(struct snd_soc_dapm_context *dapm,
+ const struct snd_soc_dapm_widget *widget,
+ int num);
+int gbaudio_remove_component_controls(struct snd_soc_component *component,
+ const struct snd_kcontrol_new *controls,
+ unsigned int num_controls);
+#endif
diff --git a/drivers/staging/greybus/audio_manager.c b/drivers/staging/greybus/audio_manager.c
new file mode 100644
index 0000000000..9a3f7c034a
--- /dev/null
+++ b/drivers/staging/greybus/audio_manager.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ */
+
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/idr.h>
+
+#include "audio_manager.h"
+#include "audio_manager_private.h"
+
+static struct kset *manager_kset;
+
+static LIST_HEAD(modules_list);
+static DECLARE_RWSEM(modules_rwsem);
+static DEFINE_IDA(module_id);
+
+/* helpers */
+static struct gb_audio_manager_module *gb_audio_manager_get_locked(int id)
+{
+ struct gb_audio_manager_module *module;
+
+ if (id < 0)
+ return NULL;
+
+ list_for_each_entry(module, &modules_list, list) {
+ if (module->id == id)
+ return module;
+ }
+
+ return NULL;
+}
+
+/* public API */
+int gb_audio_manager_add(struct gb_audio_manager_module_descriptor *desc)
+{
+ struct gb_audio_manager_module *module;
+ int id;
+ int err;
+
+ id = ida_simple_get(&module_id, 0, 0, GFP_KERNEL);
+ if (id < 0)
+ return id;
+
+ err = gb_audio_manager_module_create(&module, manager_kset,
+ id, desc);
+ if (err) {
+ ida_simple_remove(&module_id, id);
+ return err;
+ }
+
+ /* Add it to the list */
+ down_write(&modules_rwsem);
+ list_add_tail(&module->list, &modules_list);
+ up_write(&modules_rwsem);
+
+ return module->id;
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_add);
+
+int gb_audio_manager_remove(int id)
+{
+ struct gb_audio_manager_module *module;
+
+ down_write(&modules_rwsem);
+
+ module = gb_audio_manager_get_locked(id);
+ if (!module) {
+ up_write(&modules_rwsem);
+ return -EINVAL;
+ }
+ list_del(&module->list);
+ kobject_put(&module->kobj);
+ up_write(&modules_rwsem);
+ ida_simple_remove(&module_id, id);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_remove);
+
+void gb_audio_manager_remove_all(void)
+{
+ struct gb_audio_manager_module *module, *next;
+ int is_empty;
+
+ down_write(&modules_rwsem);
+
+ list_for_each_entry_safe(module, next, &modules_list, list) {
+ list_del(&module->list);
+ ida_simple_remove(&module_id, module->id);
+ kobject_put(&module->kobj);
+ }
+
+ is_empty = list_empty(&modules_list);
+
+ up_write(&modules_rwsem);
+
+ if (!is_empty)
+ pr_warn("Not all nodes were deleted\n");
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_remove_all);
+
+struct gb_audio_manager_module *gb_audio_manager_get_module(int id)
+{
+ struct gb_audio_manager_module *module;
+
+ down_read(&modules_rwsem);
+ module = gb_audio_manager_get_locked(id);
+ kobject_get(&module->kobj);
+ up_read(&modules_rwsem);
+ return module;
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_get_module);
+
+void gb_audio_manager_put_module(struct gb_audio_manager_module *module)
+{
+ kobject_put(&module->kobj);
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_put_module);
+
+int gb_audio_manager_dump_module(int id)
+{
+ struct gb_audio_manager_module *module;
+
+ down_read(&modules_rwsem);
+ module = gb_audio_manager_get_locked(id);
+ up_read(&modules_rwsem);
+
+ if (!module)
+ return -EINVAL;
+
+ gb_audio_manager_module_dump(module);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_dump_module);
+
+void gb_audio_manager_dump_all(void)
+{
+ struct gb_audio_manager_module *module;
+ int count = 0;
+
+ down_read(&modules_rwsem);
+ list_for_each_entry(module, &modules_list, list) {
+ gb_audio_manager_module_dump(module);
+ count++;
+ }
+ up_read(&modules_rwsem);
+
+ pr_info("Number of connected modules: %d\n", count);
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_dump_all);
+
+/*
+ * module init/deinit
+ */
+static int __init manager_init(void)
+{
+ manager_kset = kset_create_and_add(GB_AUDIO_MANAGER_NAME, NULL,
+ kernel_kobj);
+ if (!manager_kset)
+ return -ENOMEM;
+
+#ifdef GB_AUDIO_MANAGER_SYSFS
+ gb_audio_manager_sysfs_init(&manager_kset->kobj);
+#endif
+
+ return 0;
+}
+
+static void __exit manager_exit(void)
+{
+ gb_audio_manager_remove_all();
+ kset_unregister(manager_kset);
+ ida_destroy(&module_id);
+}
+
+module_init(manager_init);
+module_exit(manager_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Svetlin Ankov <ankov_svetlin@projectara.com>");
diff --git a/drivers/staging/greybus/audio_manager.h b/drivers/staging/greybus/audio_manager.h
new file mode 100644
index 0000000000..be605485a8
--- /dev/null
+++ b/drivers/staging/greybus/audio_manager.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ */
+
+#ifndef _GB_AUDIO_MANAGER_H_
+#define _GB_AUDIO_MANAGER_H_
+
+#include <linux/kobject.h>
+#include <linux/list.h>
+
+#define GB_AUDIO_MANAGER_NAME "gb_audio_manager"
+#define GB_AUDIO_MANAGER_MODULE_NAME_LEN 64
+#define GB_AUDIO_MANAGER_MODULE_NAME_LEN_SSCANF "63"
+
+struct gb_audio_manager_module_descriptor {
+ char name[GB_AUDIO_MANAGER_MODULE_NAME_LEN];
+ int vid;
+ int pid;
+ int intf_id;
+ unsigned int ip_devices;
+ unsigned int op_devices;
+};
+
+struct gb_audio_manager_module {
+ struct kobject kobj;
+ struct list_head list;
+ int id;
+ struct gb_audio_manager_module_descriptor desc;
+};
+
+/*
+ * Creates a new gb_audio_manager_module_descriptor, using the specified
+ * descriptor.
+ *
+ * Returns a negative result on error, or the id of the newly created module.
+ *
+ */
+int gb_audio_manager_add(struct gb_audio_manager_module_descriptor *desc);
+
+/*
+ * Removes a connected gb_audio_manager_module_descriptor for the specified ID.
+ *
+ * Returns zero on success, or a negative value on error.
+ */
+int gb_audio_manager_remove(int id);
+
+/*
+ * Removes all connected gb_audio_modules
+ *
+ * Returns zero on success, or a negative value on error.
+ */
+void gb_audio_manager_remove_all(void);
+
+/*
+ * Retrieves a gb_audio_manager_module_descriptor for the specified id.
+ * Returns the gb_audio_manager_module_descriptor structure,
+ * or NULL if there is no module with the specified ID.
+ */
+struct gb_audio_manager_module *gb_audio_manager_get_module(int id);
+
+/*
+ * Decreases the refcount of the module, obtained by the get function.
+ * Modules are removed via gb_audio_manager_remove
+ */
+void gb_audio_manager_put_module(struct gb_audio_manager_module *module);
+
+/*
+ * Dumps the module for the specified id
+ * Return 0 on success
+ */
+int gb_audio_manager_dump_module(int id);
+
+/*
+ * Dumps all connected modules
+ */
+void gb_audio_manager_dump_all(void);
+
+#endif /* _GB_AUDIO_MANAGER_H_ */
diff --git a/drivers/staging/greybus/audio_manager_module.c b/drivers/staging/greybus/audio_manager_module.c
new file mode 100644
index 0000000000..5f9dcbdbc1
--- /dev/null
+++ b/drivers/staging/greybus/audio_manager_module.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ */
+
+#include <linux/slab.h>
+
+#include "audio_manager.h"
+#include "audio_manager_private.h"
+
+#define to_gb_audio_module_attr(x) \
+ container_of(x, struct gb_audio_manager_module_attribute, attr)
+
+static inline struct gb_audio_manager_module *to_gb_audio_module(struct kobject *kobj)
+{
+ return container_of(kobj, struct gb_audio_manager_module, kobj);
+}
+
+struct gb_audio_manager_module_attribute {
+ struct attribute attr;
+ ssize_t (*show)(struct gb_audio_manager_module *module,
+ struct gb_audio_manager_module_attribute *attr,
+ char *buf);
+ ssize_t (*store)(struct gb_audio_manager_module *module,
+ struct gb_audio_manager_module_attribute *attr,
+ const char *buf, size_t count);
+};
+
+static ssize_t gb_audio_module_attr_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct gb_audio_manager_module_attribute *attribute;
+ struct gb_audio_manager_module *module;
+
+ attribute = to_gb_audio_module_attr(attr);
+ module = to_gb_audio_module(kobj);
+
+ if (!attribute->show)
+ return -EIO;
+
+ return attribute->show(module, attribute, buf);
+}
+
+static ssize_t gb_audio_module_attr_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buf, size_t len)
+{
+ struct gb_audio_manager_module_attribute *attribute;
+ struct gb_audio_manager_module *module;
+
+ attribute = to_gb_audio_module_attr(attr);
+ module = to_gb_audio_module(kobj);
+
+ if (!attribute->store)
+ return -EIO;
+
+ return attribute->store(module, attribute, buf, len);
+}
+
+static const struct sysfs_ops gb_audio_module_sysfs_ops = {
+ .show = gb_audio_module_attr_show,
+ .store = gb_audio_module_attr_store,
+};
+
+static void gb_audio_module_release(struct kobject *kobj)
+{
+ struct gb_audio_manager_module *module = to_gb_audio_module(kobj);
+
+ pr_info("Destroying audio module #%d\n", module->id);
+ /* TODO -> delete from list */
+ kfree(module);
+}
+
+static ssize_t gb_audio_module_name_show(struct gb_audio_manager_module *module,
+ struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%s", module->desc.name);
+}
+
+static struct gb_audio_manager_module_attribute gb_audio_module_name_attribute =
+ __ATTR(name, 0664, gb_audio_module_name_show, NULL);
+
+static ssize_t gb_audio_module_vid_show(struct gb_audio_manager_module *module,
+ struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d", module->desc.vid);
+}
+
+static struct gb_audio_manager_module_attribute gb_audio_module_vid_attribute =
+ __ATTR(vid, 0664, gb_audio_module_vid_show, NULL);
+
+static ssize_t gb_audio_module_pid_show(struct gb_audio_manager_module *module,
+ struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d", module->desc.pid);
+}
+
+static struct gb_audio_manager_module_attribute gb_audio_module_pid_attribute =
+ __ATTR(pid, 0664, gb_audio_module_pid_show, NULL);
+
+static ssize_t gb_audio_module_intf_id_show(struct gb_audio_manager_module *module,
+ struct gb_audio_manager_module_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d", module->desc.intf_id);
+}
+
+static struct gb_audio_manager_module_attribute
+ gb_audio_module_intf_id_attribute =
+ __ATTR(intf_id, 0664, gb_audio_module_intf_id_show, NULL);
+
+static ssize_t gb_audio_module_ip_devices_show(struct gb_audio_manager_module *module,
+ struct gb_audio_manager_module_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "0x%X", module->desc.ip_devices);
+}
+
+static struct gb_audio_manager_module_attribute
+ gb_audio_module_ip_devices_attribute =
+ __ATTR(ip_devices, 0664, gb_audio_module_ip_devices_show, NULL);
+
+static ssize_t gb_audio_module_op_devices_show(struct gb_audio_manager_module *module,
+ struct gb_audio_manager_module_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "0x%X", module->desc.op_devices);
+}
+
+static struct gb_audio_manager_module_attribute
+ gb_audio_module_op_devices_attribute =
+ __ATTR(op_devices, 0664, gb_audio_module_op_devices_show, NULL);
+
+static struct attribute *gb_audio_module_default_attrs[] = {
+ &gb_audio_module_name_attribute.attr,
+ &gb_audio_module_vid_attribute.attr,
+ &gb_audio_module_pid_attribute.attr,
+ &gb_audio_module_intf_id_attribute.attr,
+ &gb_audio_module_ip_devices_attribute.attr,
+ &gb_audio_module_op_devices_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+ATTRIBUTE_GROUPS(gb_audio_module_default);
+
+static struct kobj_type gb_audio_module_type = {
+ .sysfs_ops = &gb_audio_module_sysfs_ops,
+ .release = gb_audio_module_release,
+ .default_groups = gb_audio_module_default_groups,
+};
+
+static void send_add_uevent(struct gb_audio_manager_module *module)
+{
+ char name_string[128];
+ char vid_string[64];
+ char pid_string[64];
+ char intf_id_string[64];
+ char ip_devices_string[64];
+ char op_devices_string[64];
+
+ char *envp[] = {
+ name_string,
+ vid_string,
+ pid_string,
+ intf_id_string,
+ ip_devices_string,
+ op_devices_string,
+ NULL
+ };
+
+ snprintf(name_string, 128, "NAME=%s", module->desc.name);
+ snprintf(vid_string, 64, "VID=%d", module->desc.vid);
+ snprintf(pid_string, 64, "PID=%d", module->desc.pid);
+ snprintf(intf_id_string, 64, "INTF_ID=%d", module->desc.intf_id);
+ snprintf(ip_devices_string, 64, "I/P DEVICES=0x%X",
+ module->desc.ip_devices);
+ snprintf(op_devices_string, 64, "O/P DEVICES=0x%X",
+ module->desc.op_devices);
+
+ kobject_uevent_env(&module->kobj, KOBJ_ADD, envp);
+}
+
+int gb_audio_manager_module_create(struct gb_audio_manager_module **module,
+ struct kset *manager_kset,
+ int id, struct gb_audio_manager_module_descriptor *desc)
+{
+ int err;
+ struct gb_audio_manager_module *m;
+
+ m = kzalloc(sizeof(*m), GFP_ATOMIC);
+ if (!m)
+ return -ENOMEM;
+
+ /* Initialize the node */
+ INIT_LIST_HEAD(&m->list);
+
+ /* Set the module id */
+ m->id = id;
+
+ /* Copy the provided descriptor */
+ memcpy(&m->desc, desc, sizeof(*desc));
+
+ /* set the kset */
+ m->kobj.kset = manager_kset;
+
+ /*
+ * Initialize and add the kobject to the kernel. All the default files
+ * will be created here. As we have already specified a kset for this
+ * kobject, we don't have to set a parent for the kobject, the kobject
+ * will be placed beneath that kset automatically.
+ */
+ err = kobject_init_and_add(&m->kobj, &gb_audio_module_type, NULL, "%d",
+ id);
+ if (err) {
+ pr_err("failed initializing kobject for audio module #%d\n", id);
+ kobject_put(&m->kobj);
+ return err;
+ }
+
+ /*
+ * Notify the object was created
+ */
+ send_add_uevent(m);
+
+ *module = m;
+ pr_info("Created audio module #%d\n", id);
+ return 0;
+}
+
+void gb_audio_manager_module_dump(struct gb_audio_manager_module *module)
+{
+ pr_info("audio module #%d name=%s vid=%d pid=%d intf_id=%d i/p devices=0x%X o/p devices=0x%X\n",
+ module->id,
+ module->desc.name,
+ module->desc.vid,
+ module->desc.pid,
+ module->desc.intf_id,
+ module->desc.ip_devices,
+ module->desc.op_devices);
+}
diff --git a/drivers/staging/greybus/audio_manager_private.h b/drivers/staging/greybus/audio_manager_private.h
new file mode 100644
index 0000000000..2b3a766c7d
--- /dev/null
+++ b/drivers/staging/greybus/audio_manager_private.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ */
+
+#ifndef _GB_AUDIO_MANAGER_PRIVATE_H_
+#define _GB_AUDIO_MANAGER_PRIVATE_H_
+
+#include <linux/kobject.h>
+
+#include "audio_manager.h"
+
+int gb_audio_manager_module_create(
+ struct gb_audio_manager_module **module,
+ struct kset *manager_kset,
+ int id, struct gb_audio_manager_module_descriptor *desc);
+
+/* module destroyed via kobject_put */
+
+void gb_audio_manager_module_dump(struct gb_audio_manager_module *module);
+
+/* sysfs control */
+void gb_audio_manager_sysfs_init(struct kobject *kobj);
+
+#endif /* _GB_AUDIO_MANAGER_PRIVATE_H_ */
diff --git a/drivers/staging/greybus/audio_manager_sysfs.c b/drivers/staging/greybus/audio_manager_sysfs.c
new file mode 100644
index 0000000000..fcd518f954
--- /dev/null
+++ b/drivers/staging/greybus/audio_manager_sysfs.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ */
+
+#include <linux/string.h>
+#include <linux/sysfs.h>
+
+#include "audio_manager.h"
+#include "audio_manager_private.h"
+
+static ssize_t manager_sysfs_add_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct gb_audio_manager_module_descriptor desc = { {0} };
+
+ int num = sscanf(buf,
+ "name=%" GB_AUDIO_MANAGER_MODULE_NAME_LEN_SSCANF
+ "s vid=%d pid=%d intf_id=%d i/p devices=0x%X o/p devices=0x%X",
+ desc.name, &desc.vid, &desc.pid, &desc.intf_id,
+ &desc.ip_devices, &desc.op_devices);
+
+ if (num != 7)
+ return -EINVAL;
+
+ num = gb_audio_manager_add(&desc);
+ if (num < 0)
+ return -EINVAL;
+
+ return count;
+}
+
+static struct kobj_attribute manager_add_attribute =
+ __ATTR(add, 0664, NULL, manager_sysfs_add_store);
+
+static ssize_t manager_sysfs_remove_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int id;
+
+ int num = kstrtoint(buf, 10, &id);
+
+ if (num != 1)
+ return -EINVAL;
+
+ num = gb_audio_manager_remove(id);
+ if (num)
+ return num;
+
+ return count;
+}
+
+static struct kobj_attribute manager_remove_attribute =
+ __ATTR(remove, 0664, NULL, manager_sysfs_remove_store);
+
+static ssize_t manager_sysfs_dump_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int id;
+
+ int num = kstrtoint(buf, 10, &id);
+
+ if (num == 1) {
+ num = gb_audio_manager_dump_module(id);
+ if (num)
+ return num;
+ } else if (!strncmp("all", buf, 3)) {
+ gb_audio_manager_dump_all();
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static struct kobj_attribute manager_dump_attribute =
+ __ATTR(dump, 0664, NULL, manager_sysfs_dump_store);
+
+static void manager_sysfs_init_attribute(struct kobject *kobj,
+ struct kobj_attribute *kattr)
+{
+ int err;
+
+ err = sysfs_create_file(kobj, &kattr->attr);
+ if (err) {
+ pr_warn("creating the sysfs entry for %s failed: %d\n",
+ kattr->attr.name, err);
+ }
+}
+
+void gb_audio_manager_sysfs_init(struct kobject *kobj)
+{
+ manager_sysfs_init_attribute(kobj, &manager_add_attribute);
+ manager_sysfs_init_attribute(kobj, &manager_remove_attribute);
+ manager_sysfs_init_attribute(kobj, &manager_dump_attribute);
+}
diff --git a/drivers/staging/greybus/audio_module.c b/drivers/staging/greybus/audio_module.c
new file mode 100644
index 0000000000..12c376c477
--- /dev/null
+++ b/drivers/staging/greybus/audio_module.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus audio driver
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "audio_codec.h"
+#include "audio_apbridgea.h"
+#include "audio_manager.h"
+
+/*
+ * gb_snd management functions
+ */
+
+static int gbaudio_request_jack(struct gbaudio_module_info *module,
+ struct gb_audio_jack_event_request *req)
+{
+ int report;
+ struct snd_jack *jack = module->headset.jack.jack;
+ struct snd_jack *btn_jack = module->button.jack.jack;
+
+ if (!jack) {
+ dev_err_ratelimited(module->dev,
+ "Invalid jack event received:type: %u, event: %u\n",
+ req->jack_attribute, req->event);
+ return -EINVAL;
+ }
+
+ dev_warn_ratelimited(module->dev,
+ "Jack Event received: type: %u, event: %u\n",
+ req->jack_attribute, req->event);
+
+ if (req->event == GB_AUDIO_JACK_EVENT_REMOVAL) {
+ module->jack_type = 0;
+ if (btn_jack && module->button_status) {
+ snd_soc_jack_report(&module->button.jack, 0,
+ module->button_mask);
+ module->button_status = 0;
+ }
+ snd_soc_jack_report(&module->headset.jack, 0,
+ module->jack_mask);
+ return 0;
+ }
+
+ report = req->jack_attribute & module->jack_mask;
+ if (!report) {
+ dev_err_ratelimited(module->dev,
+ "Invalid jack event received:type: %u, event: %u\n",
+ req->jack_attribute, req->event);
+ return -EINVAL;
+ }
+
+ if (module->jack_type)
+ dev_warn_ratelimited(module->dev,
+ "Modifying jack from %d to %d\n",
+ module->jack_type, report);
+
+ module->jack_type = report;
+ snd_soc_jack_report(&module->headset.jack, report, module->jack_mask);
+
+ return 0;
+}
+
+static int gbaudio_request_button(struct gbaudio_module_info *module,
+ struct gb_audio_button_event_request *req)
+{
+ int soc_button_id, report;
+ struct snd_jack *btn_jack = module->button.jack.jack;
+
+ if (!btn_jack) {
+ dev_err_ratelimited(module->dev,
+ "Invalid button event received:type: %u, event: %u\n",
+ req->button_id, req->event);
+ return -EINVAL;
+ }
+
+ dev_warn_ratelimited(module->dev,
+ "Button Event received: id: %u, event: %u\n",
+ req->button_id, req->event);
+
+ /* currently supports 4 buttons only */
+ if (!module->jack_type) {
+ dev_err_ratelimited(module->dev,
+ "Jack not present. Bogus event!!\n");
+ return -EINVAL;
+ }
+
+ report = module->button_status & module->button_mask;
+ soc_button_id = 0;
+
+ switch (req->button_id) {
+ case 1:
+ soc_button_id = SND_JACK_BTN_0 & module->button_mask;
+ break;
+
+ case 2:
+ soc_button_id = SND_JACK_BTN_1 & module->button_mask;
+ break;
+
+ case 3:
+ soc_button_id = SND_JACK_BTN_2 & module->button_mask;
+ break;
+
+ case 4:
+ soc_button_id = SND_JACK_BTN_3 & module->button_mask;
+ break;
+ }
+
+ if (!soc_button_id) {
+ dev_err_ratelimited(module->dev,
+ "Invalid button request received\n");
+ return -EINVAL;
+ }
+
+ if (req->event == GB_AUDIO_BUTTON_EVENT_PRESS)
+ report = report | soc_button_id;
+ else
+ report = report & ~soc_button_id;
+
+ module->button_status = report;
+
+ snd_soc_jack_report(&module->button.jack, report, module->button_mask);
+
+ return 0;
+}
+
+static int gbaudio_request_stream(struct gbaudio_module_info *module,
+ struct gb_audio_streaming_event_request *req)
+{
+ dev_warn(module->dev, "Audio Event received: cport: %u, event: %u\n",
+ le16_to_cpu(req->data_cport), req->event);
+
+ return 0;
+}
+
+static int gbaudio_codec_request_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gbaudio_module_info *module =
+ greybus_get_drvdata(connection->bundle);
+ struct gb_operation_msg_hdr *header = op->request->header;
+ struct gb_audio_streaming_event_request *stream_req;
+ struct gb_audio_jack_event_request *jack_req;
+ struct gb_audio_button_event_request *button_req;
+ int ret;
+
+ switch (header->type) {
+ case GB_AUDIO_TYPE_STREAMING_EVENT:
+ stream_req = op->request->payload;
+ ret = gbaudio_request_stream(module, stream_req);
+ break;
+
+ case GB_AUDIO_TYPE_JACK_EVENT:
+ jack_req = op->request->payload;
+ ret = gbaudio_request_jack(module, jack_req);
+ break;
+
+ case GB_AUDIO_TYPE_BUTTON_EVENT:
+ button_req = op->request->payload;
+ ret = gbaudio_request_button(module, button_req);
+ break;
+
+ default:
+ dev_err_ratelimited(&connection->bundle->dev,
+ "Invalid Audio Event received\n");
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int gb_audio_add_mgmt_connection(struct gbaudio_module_info *gbmodule,
+ struct greybus_descriptor_cport *cport_desc,
+ struct gb_bundle *bundle)
+{
+ struct gb_connection *connection;
+
+ /* Management Cport */
+ if (gbmodule->mgmt_connection) {
+ dev_err(&bundle->dev,
+ "Can't have multiple Management connections\n");
+ return -ENODEV;
+ }
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ gbaudio_codec_request_handler);
+ if (IS_ERR(connection))
+ return PTR_ERR(connection);
+
+ greybus_set_drvdata(bundle, gbmodule);
+ gbmodule->mgmt_connection = connection;
+
+ return 0;
+}
+
+static int gb_audio_add_data_connection(struct gbaudio_module_info *gbmodule,
+ struct greybus_descriptor_cport *cport_desc,
+ struct gb_bundle *bundle)
+{
+ struct gb_connection *connection;
+ struct gbaudio_data_connection *dai;
+
+ dai = devm_kzalloc(gbmodule->dev, sizeof(*dai), GFP_KERNEL);
+ if (!dai)
+ return -ENOMEM;
+
+ connection = gb_connection_create_offloaded(bundle,
+ le16_to_cpu(cport_desc->id),
+ GB_CONNECTION_FLAG_CSD);
+ if (IS_ERR(connection)) {
+ devm_kfree(gbmodule->dev, dai);
+ return PTR_ERR(connection);
+ }
+
+ greybus_set_drvdata(bundle, gbmodule);
+ dai->id = 0;
+ dai->data_cport = cpu_to_le16(connection->intf_cport_id);
+ dai->connection = connection;
+ list_add(&dai->list, &gbmodule->data_list);
+
+ return 0;
+}
+
+/*
+ * This is the basic hook get things initialized and registered w/ gb
+ */
+
+static int gb_audio_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct device *dev = &bundle->dev;
+ struct gbaudio_module_info *gbmodule;
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_audio_manager_module_descriptor desc;
+ struct gbaudio_data_connection *dai, *_dai;
+ int ret, i;
+ struct gb_audio_topology *topology;
+
+ /* There should be at least one Management and one Data cport */
+ if (bundle->num_cports < 2)
+ return -ENODEV;
+
+ /*
+ * There can be only one Management connection and any number of data
+ * connections.
+ */
+ gbmodule = devm_kzalloc(dev, sizeof(*gbmodule), GFP_KERNEL);
+ if (!gbmodule)
+ return -ENOMEM;
+
+ gbmodule->num_data_connections = bundle->num_cports - 1;
+ INIT_LIST_HEAD(&gbmodule->data_list);
+ INIT_LIST_HEAD(&gbmodule->widget_list);
+ INIT_LIST_HEAD(&gbmodule->ctl_list);
+ INIT_LIST_HEAD(&gbmodule->widget_ctl_list);
+ INIT_LIST_HEAD(&gbmodule->jack_list);
+ gbmodule->dev = dev;
+ snprintf(gbmodule->name, sizeof(gbmodule->name), "%s.%s", dev->driver->name,
+ dev_name(dev));
+ greybus_set_drvdata(bundle, gbmodule);
+
+ /* Create all connections */
+ for (i = 0; i < bundle->num_cports; i++) {
+ cport_desc = &bundle->cport_desc[i];
+
+ switch (cport_desc->protocol_id) {
+ case GREYBUS_PROTOCOL_AUDIO_MGMT:
+ ret = gb_audio_add_mgmt_connection(gbmodule, cport_desc,
+ bundle);
+ if (ret)
+ goto destroy_connections;
+ break;
+ case GREYBUS_PROTOCOL_AUDIO_DATA:
+ ret = gb_audio_add_data_connection(gbmodule, cport_desc,
+ bundle);
+ if (ret)
+ goto destroy_connections;
+ break;
+ default:
+ dev_err(dev, "Unsupported protocol: 0x%02x\n",
+ cport_desc->protocol_id);
+ ret = -ENODEV;
+ goto destroy_connections;
+ }
+ }
+
+ /* There must be a management cport */
+ if (!gbmodule->mgmt_connection) {
+ ret = -EINVAL;
+ dev_err(dev, "Missing management connection\n");
+ goto destroy_connections;
+ }
+
+ /* Initialize management connection */
+ ret = gb_connection_enable(gbmodule->mgmt_connection);
+ if (ret) {
+ dev_err(dev, "%d: Error while enabling mgmt connection\n", ret);
+ goto destroy_connections;
+ }
+ gbmodule->dev_id = gbmodule->mgmt_connection->intf->interface_id;
+
+ /*
+ * FIXME: malloc for topology happens via audio_gb driver
+ * should be done within codec driver itself
+ */
+ ret = gb_audio_gb_get_topology(gbmodule->mgmt_connection, &topology);
+ if (ret) {
+ dev_err(dev, "%d:Error while fetching topology\n", ret);
+ goto disable_connection;
+ }
+
+ /* process topology data */
+ ret = gbaudio_tplg_parse_data(gbmodule, topology);
+ if (ret) {
+ dev_err(dev, "%d:Error while parsing topology data\n",
+ ret);
+ goto free_topology;
+ }
+ gbmodule->topology = topology;
+
+ /* Initialize data connections */
+ list_for_each_entry(dai, &gbmodule->data_list, list) {
+ ret = gb_connection_enable(dai->connection);
+ if (ret) {
+ dev_err(dev,
+ "%d:Error while enabling %d:data connection\n",
+ ret, le16_to_cpu(dai->data_cport));
+ goto disable_data_connection;
+ }
+ }
+
+ /* register module with gbcodec */
+ ret = gbaudio_register_module(gbmodule);
+ if (ret)
+ goto disable_data_connection;
+
+ /* inform above layer for uevent */
+ dev_dbg(dev, "Inform set_event:%d to above layer\n", 1);
+ /* prepare for the audio manager */
+ strscpy(desc.name, gbmodule->name, sizeof(desc.name));
+ desc.vid = 2; /* todo */
+ desc.pid = 3; /* todo */
+ desc.intf_id = gbmodule->dev_id;
+ desc.op_devices = gbmodule->op_devices;
+ desc.ip_devices = gbmodule->ip_devices;
+ gbmodule->manager_id = gb_audio_manager_add(&desc);
+
+ dev_dbg(dev, "Add GB Audio device:%s\n", gbmodule->name);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+
+disable_data_connection:
+ list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list)
+ gb_connection_disable(dai->connection);
+ gbaudio_tplg_release(gbmodule);
+ gbmodule->topology = NULL;
+
+free_topology:
+ kfree(topology);
+
+disable_connection:
+ gb_connection_disable(gbmodule->mgmt_connection);
+
+destroy_connections:
+ list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) {
+ gb_connection_destroy(dai->connection);
+ list_del(&dai->list);
+ devm_kfree(dev, dai);
+ }
+
+ if (gbmodule->mgmt_connection)
+ gb_connection_destroy(gbmodule->mgmt_connection);
+
+ devm_kfree(dev, gbmodule);
+
+ return ret;
+}
+
+static void gb_audio_disconnect(struct gb_bundle *bundle)
+{
+ struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle);
+ struct gbaudio_data_connection *dai, *_dai;
+
+ gb_pm_runtime_get_sync(bundle);
+
+ /* cleanup module related resources first */
+ gbaudio_unregister_module(gbmodule);
+
+ /* inform uevent to above layers */
+ gb_audio_manager_remove(gbmodule->manager_id);
+
+ gbaudio_tplg_release(gbmodule);
+ kfree(gbmodule->topology);
+ gbmodule->topology = NULL;
+ gb_connection_disable(gbmodule->mgmt_connection);
+ list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) {
+ gb_connection_disable(dai->connection);
+ gb_connection_destroy(dai->connection);
+ list_del(&dai->list);
+ devm_kfree(gbmodule->dev, dai);
+ }
+ gb_connection_destroy(gbmodule->mgmt_connection);
+ gbmodule->mgmt_connection = NULL;
+
+ devm_kfree(&bundle->dev, gbmodule);
+}
+
+static const struct greybus_bundle_id gb_audio_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_AUDIO) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_audio_id_table);
+
+#ifdef CONFIG_PM
+static int gb_audio_suspend(struct device *dev)
+{
+ struct gb_bundle *bundle = to_gb_bundle(dev);
+ struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle);
+ struct gbaudio_data_connection *dai;
+
+ list_for_each_entry(dai, &gbmodule->data_list, list)
+ gb_connection_disable(dai->connection);
+
+ gb_connection_disable(gbmodule->mgmt_connection);
+
+ return 0;
+}
+
+static int gb_audio_resume(struct device *dev)
+{
+ struct gb_bundle *bundle = to_gb_bundle(dev);
+ struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle);
+ struct gbaudio_data_connection *dai;
+ int ret;
+
+ ret = gb_connection_enable(gbmodule->mgmt_connection);
+ if (ret) {
+ dev_err(dev, "%d:Error while enabling mgmt connection\n", ret);
+ return ret;
+ }
+
+ list_for_each_entry(dai, &gbmodule->data_list, list) {
+ ret = gb_connection_enable(dai->connection);
+ if (ret) {
+ dev_err(dev,
+ "%d:Error while enabling %d:data connection\n",
+ ret, le16_to_cpu(dai->data_cport));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_audio_pm_ops = {
+ SET_RUNTIME_PM_OPS(gb_audio_suspend, gb_audio_resume, NULL)
+};
+
+static struct greybus_driver gb_audio_driver = {
+ .name = "gb-audio",
+ .probe = gb_audio_probe,
+ .disconnect = gb_audio_disconnect,
+ .id_table = gb_audio_id_table,
+ .driver.pm = &gb_audio_pm_ops,
+};
+module_greybus_driver(gb_audio_driver);
+
+MODULE_DESCRIPTION("Greybus Audio module driver");
+MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@linaro.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:gbaudio-module");
diff --git a/drivers/staging/greybus/audio_topology.c b/drivers/staging/greybus/audio_topology.c
new file mode 100644
index 0000000000..08e6a807c1
--- /dev/null
+++ b/drivers/staging/greybus/audio_topology.c
@@ -0,0 +1,1452 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus audio driver
+ * Copyright 2015-2016 Google Inc.
+ * Copyright 2015-2016 Linaro Ltd.
+ */
+
+#include <linux/greybus.h>
+#include "audio_codec.h"
+
+#define GBAUDIO_INVALID_ID 0xFF
+
+/* mixer control */
+struct gb_mixer_control {
+ int min, max;
+ unsigned int reg, rreg, shift, rshift, invert;
+};
+
+struct gbaudio_ctl_pvt {
+ unsigned int ctl_id;
+ unsigned int data_cport;
+ unsigned int access;
+ unsigned int vcount;
+ struct gb_audio_ctl_elem_info *info;
+};
+
+static struct gbaudio_module_info *find_gb_module(struct gbaudio_codec_info *codec,
+ char const *name)
+{
+ int dev_id;
+ char begin[NAME_SIZE];
+ struct gbaudio_module_info *module;
+
+ if (!name)
+ return NULL;
+
+ if (sscanf(name, "%s %d", begin, &dev_id) != 2)
+ return NULL;
+
+ dev_dbg(codec->dev, "%s:Find module#%d\n", __func__, dev_id);
+
+ mutex_lock(&codec->lock);
+ list_for_each_entry(module, &codec->module_list, list) {
+ if (module->dev_id == dev_id) {
+ mutex_unlock(&codec->lock);
+ return module;
+ }
+ }
+ mutex_unlock(&codec->lock);
+ dev_warn(codec->dev, "%s: module#%d missing in codec list\n", name,
+ dev_id);
+ return NULL;
+}
+
+static const char *gbaudio_map_controlid(struct gbaudio_module_info *module,
+ __u8 control_id, __u8 index)
+{
+ struct gbaudio_control *control;
+
+ if (control_id == GBAUDIO_INVALID_ID)
+ return NULL;
+
+ list_for_each_entry(control, &module->ctl_list, list) {
+ if (control->id == control_id) {
+ if (index == GBAUDIO_INVALID_ID)
+ return control->name;
+ if (index >= control->items)
+ return NULL;
+ return control->texts[index];
+ }
+ }
+ list_for_each_entry(control, &module->widget_ctl_list, list) {
+ if (control->id == control_id) {
+ if (index == GBAUDIO_INVALID_ID)
+ return control->name;
+ if (index >= control->items)
+ return NULL;
+ return control->texts[index];
+ }
+ }
+ return NULL;
+}
+
+static int gbaudio_map_controlname(struct gbaudio_module_info *module,
+ const char *name)
+{
+ struct gbaudio_control *control;
+
+ list_for_each_entry(control, &module->ctl_list, list) {
+ if (!strncmp(control->name, name, NAME_SIZE))
+ return control->id;
+ }
+
+ dev_warn(module->dev, "%s: missing in modules controls list\n", name);
+
+ return -EINVAL;
+}
+
+static int gbaudio_map_wcontrolname(struct gbaudio_module_info *module,
+ const char *name)
+{
+ struct gbaudio_control *control;
+
+ list_for_each_entry(control, &module->widget_ctl_list, list) {
+ if (!strncmp(control->wname, name, NAME_SIZE))
+ return control->id;
+ }
+ dev_warn(module->dev, "%s: missing in modules controls list\n", name);
+
+ return -EINVAL;
+}
+
+static int gbaudio_map_widgetname(struct gbaudio_module_info *module,
+ const char *name)
+{
+ struct gbaudio_widget *widget;
+
+ list_for_each_entry(widget, &module->widget_list, list) {
+ if (!strncmp(widget->name, name, NAME_SIZE))
+ return widget->id;
+ }
+ dev_warn(module->dev, "%s: missing in modules widgets list\n", name);
+
+ return -EINVAL;
+}
+
+static const char *gbaudio_map_widgetid(struct gbaudio_module_info *module,
+ __u8 widget_id)
+{
+ struct gbaudio_widget *widget;
+
+ list_for_each_entry(widget, &module->widget_list, list) {
+ if (widget->id == widget_id)
+ return widget->name;
+ }
+ return NULL;
+}
+
+static const char **gb_generate_enum_strings(struct gbaudio_module_info *gb,
+ struct gb_audio_enumerated *gbenum)
+{
+ const char **strings;
+ int i;
+ unsigned int items;
+ __u8 *data;
+
+ items = le32_to_cpu(gbenum->items);
+ strings = devm_kcalloc(gb->dev, items, sizeof(char *), GFP_KERNEL);
+ if (!strings)
+ return NULL;
+
+ data = gbenum->names;
+
+ for (i = 0; i < items; i++) {
+ strings[i] = (const char *)data;
+ while (*data != '\0')
+ data++;
+ data++;
+ }
+
+ return strings;
+}
+
+static int gbcodec_mixer_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ unsigned int max;
+ const char *name;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_module_info *module;
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct gbaudio_codec_info *gbcodec = snd_soc_component_get_drvdata(comp);
+
+ dev_dbg(comp->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ if (!info) {
+ dev_err(comp->dev, "NULL info for %s\n", uinfo->id.name);
+ return -EINVAL;
+ }
+
+ /* update uinfo */
+ uinfo->access = data->access;
+ uinfo->count = data->vcount;
+ uinfo->type = (__force snd_ctl_elem_type_t)info->type;
+
+ switch (info->type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+ case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+ uinfo->value.integer.min = le32_to_cpu(info->value.integer.min);
+ uinfo->value.integer.max = le32_to_cpu(info->value.integer.max);
+ break;
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ max = le32_to_cpu(info->value.enumerated.items);
+ uinfo->value.enumerated.items = max;
+ if (uinfo->value.enumerated.item > max - 1)
+ uinfo->value.enumerated.item = max - 1;
+ module = find_gb_module(gbcodec, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+ name = gbaudio_map_controlid(module, data->ctl_id,
+ uinfo->value.enumerated.item);
+ strscpy(uinfo->value.enumerated.name, name, sizeof(uinfo->value.enumerated.name));
+ break;
+ default:
+ dev_err(comp->dev, "Invalid type: %d for %s:kcontrol\n",
+ info->type, kcontrol->id.name);
+ break;
+ }
+ return 0;
+}
+
+static int gbcodec_mixer_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct gbaudio_codec_info *gb = snd_soc_component_get_drvdata(comp);
+ struct gb_bundle *bundle;
+
+ dev_dbg(comp->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(comp->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+
+ /* update ucontrol */
+ switch (info->type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+ case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+ ucontrol->value.integer.value[0] =
+ le32_to_cpu(gbvalue.value.integer_value[0]);
+ if (data->vcount == 2)
+ ucontrol->value.integer.value[1] =
+ le32_to_cpu(gbvalue.value.integer_value[1]);
+ break;
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ ucontrol->value.enumerated.item[0] =
+ le32_to_cpu(gbvalue.value.enumerated_item[0]);
+ if (data->vcount == 2)
+ ucontrol->value.enumerated.item[1] =
+ le32_to_cpu(gbvalue.value.enumerated_item[1]);
+ break;
+ default:
+ dev_err(comp->dev, "Invalid type: %d for %s:kcontrol\n",
+ info->type, kcontrol->id.name);
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int gbcodec_mixer_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret = 0;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct gbaudio_codec_info *gb = snd_soc_component_get_drvdata(comp);
+ struct gb_bundle *bundle;
+
+ dev_dbg(comp->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+ bundle = to_gb_bundle(module->dev);
+
+ /* update ucontrol */
+ switch (info->type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+ case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+ gbvalue.value.integer_value[0] =
+ cpu_to_le32(ucontrol->value.integer.value[0]);
+ if (data->vcount == 2)
+ gbvalue.value.integer_value[1] =
+ cpu_to_le32(ucontrol->value.integer.value[1]);
+ break;
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ gbvalue.value.enumerated_item[0] =
+ cpu_to_le32(ucontrol->value.enumerated.item[0]);
+ if (data->vcount == 2)
+ gbvalue.value.enumerated_item[1] =
+ cpu_to_le32(ucontrol->value.enumerated.item[1]);
+ break;
+ default:
+ dev_err(comp->dev, "Invalid type: %d for %s:kcontrol\n",
+ info->type, kcontrol->id.name);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ return ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_set_control(module->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(comp->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ }
+
+ return ret;
+}
+
+#define SOC_MIXER_GB(xname, kcount, data) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .count = kcount, .info = gbcodec_mixer_ctl_info, \
+ .get = gbcodec_mixer_ctl_get, .put = gbcodec_mixer_ctl_put, \
+ .private_value = (unsigned long)data }
+
+/*
+ * although below callback functions seems redundant to above functions.
+ * same are kept to allow provision for different handling in case
+ * of DAPM related sequencing, etc.
+ */
+static int gbcodec_mixer_dapm_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int platform_max, platform_min;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_info *info;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+
+ /* update uinfo */
+ platform_max = le32_to_cpu(info->value.integer.max);
+ platform_min = le32_to_cpu(info->value.integer.min);
+
+ if (platform_max == 1 &&
+ !strnstr(kcontrol->id.name, " Volume", sizeof(kcontrol->id.name)))
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = data->vcount;
+ uinfo->value.integer.min = platform_min;
+ uinfo->value.integer.max = platform_max;
+
+ return 0;
+}
+
+static int gbcodec_mixer_dapm_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct device *codec_dev = widget->dapm->dev;
+ struct gbaudio_codec_info *gb = dev_get_drvdata(codec_dev);
+ struct gb_bundle *bundle;
+
+ dev_dbg(codec_dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ bundle = to_gb_bundle(module->dev);
+
+ if (data->vcount == 2)
+ dev_warn(widget->dapm->dev,
+ "GB: Control '%s' is stereo, which is not supported\n",
+ kcontrol->id.name);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+ /* update ucontrol */
+ ucontrol->value.integer.value[0] =
+ le32_to_cpu(gbvalue.value.integer_value[0]);
+
+ return ret;
+}
+
+static int gbcodec_mixer_dapm_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, wi, max, connect;
+ unsigned int mask, val;
+ struct gb_audio_ctl_elem_info *info;
+ struct gbaudio_ctl_pvt *data;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct device *codec_dev = widget->dapm->dev;
+ struct gbaudio_codec_info *gb = dev_get_drvdata(codec_dev);
+ struct gb_bundle *bundle;
+
+ dev_dbg(codec_dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+ info = (struct gb_audio_ctl_elem_info *)data->info;
+ bundle = to_gb_bundle(module->dev);
+
+ if (data->vcount == 2)
+ dev_warn(widget->dapm->dev,
+ "GB: Control '%s' is stereo, which is not supported\n",
+ kcontrol->id.name);
+
+ max = le32_to_cpu(info->value.integer.max);
+ mask = (1 << fls(max)) - 1;
+ val = ucontrol->value.integer.value[0] & mask;
+ connect = !!val;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+ if (ret)
+ goto exit;
+
+ /* update ucontrol */
+ if (le32_to_cpu(gbvalue.value.integer_value[0]) != val) {
+ for (wi = 0; wi < wlist->num_widgets; wi++) {
+ widget = wlist->widgets[wi];
+ snd_soc_dapm_mixer_update_power(widget->dapm, kcontrol,
+ connect, NULL);
+ }
+ gbvalue.value.integer_value[0] =
+ cpu_to_le32(ucontrol->value.integer.value[0]);
+
+ ret = gb_audio_gb_set_control(module->mgmt_connection,
+ data->ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+ }
+
+exit:
+ gb_pm_runtime_put_autosuspend(bundle);
+ if (ret)
+ dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+}
+
+#define SOC_DAPM_MIXER_GB(xname, kcount, data) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .count = kcount, .info = gbcodec_mixer_dapm_ctl_info, \
+ .get = gbcodec_mixer_dapm_ctl_get, .put = gbcodec_mixer_dapm_ctl_put, \
+ .private_value = (unsigned long)data}
+
+static int gbcodec_event_spk(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ /* Ensure GB speaker is connected */
+
+ return 0;
+}
+
+static int gbcodec_event_hp(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ /* Ensure GB module supports jack slot */
+
+ return 0;
+}
+
+static int gbcodec_event_int_mic(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ /* Ensure GB module supports jack slot */
+
+ return 0;
+}
+
+static int gbaudio_validate_kcontrol_count(struct gb_audio_widget *w)
+{
+ int ret = 0;
+
+ switch (w->type) {
+ case snd_soc_dapm_spk:
+ case snd_soc_dapm_hp:
+ case snd_soc_dapm_mic:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_input:
+ if (w->ncontrols)
+ ret = -EINVAL;
+ break;
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_mux:
+ if (w->ncontrols != 1)
+ ret = -EINVAL;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gbcodec_enum_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, ctl_id;
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct gbaudio_codec_info *gb = snd_soc_component_get_drvdata(comp);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct gb_bundle *bundle;
+
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ ctl_id = gbaudio_map_controlname(module, kcontrol->id.name);
+ if (ctl_id < 0)
+ return -EINVAL;
+
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(comp->dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+
+ ucontrol->value.enumerated.item[0] =
+ le32_to_cpu(gbvalue.value.enumerated_item[0]);
+ if (e->shift_l != e->shift_r)
+ ucontrol->value.enumerated.item[1] =
+ le32_to_cpu(gbvalue.value.enumerated_item[1]);
+
+ return 0;
+}
+
+static int gbcodec_enum_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, ctl_id;
+ struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol);
+ struct gbaudio_codec_info *gb = snd_soc_component_get_drvdata(comp);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct gb_bundle *bundle;
+
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ ctl_id = gbaudio_map_controlname(module, kcontrol->id.name);
+ if (ctl_id < 0)
+ return -EINVAL;
+
+ if (ucontrol->value.enumerated.item[0] > e->items - 1)
+ return -EINVAL;
+ gbvalue.value.enumerated_item[0] =
+ cpu_to_le32(ucontrol->value.enumerated.item[0]);
+
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->items - 1)
+ return -EINVAL;
+ gbvalue.value.enumerated_item[1] =
+ cpu_to_le32(ucontrol->value.enumerated.item[1]);
+ }
+
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(comp->dev, "%d:Error in %s for %s\n",
+ ret, __func__, kcontrol->id.name);
+ }
+
+ return ret;
+}
+
+static int gbaudio_tplg_create_enum_kctl(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ struct soc_enum *gbe;
+ struct gb_audio_enumerated *gb_enum;
+ int i;
+
+ gbe = devm_kzalloc(gb->dev, sizeof(*gbe), GFP_KERNEL);
+ if (!gbe)
+ return -ENOMEM;
+
+ gb_enum = &ctl->info.value.enumerated;
+
+ /* since count=1, and reg is dummy */
+ gbe->items = le32_to_cpu(gb_enum->items);
+ gbe->texts = gb_generate_enum_strings(gb, gb_enum);
+ if (!gbe->texts)
+ return -ENOMEM;
+
+ /* debug enum info */
+ dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gbe->items,
+ le16_to_cpu(gb_enum->names_length));
+ for (i = 0; i < gbe->items; i++)
+ dev_dbg(gb->dev, "src[%d]: %s\n", i, gbe->texts[i]);
+
+ *kctl = (struct snd_kcontrol_new)
+ SOC_ENUM_EXT(ctl->name, *gbe, gbcodec_enum_ctl_get,
+ gbcodec_enum_ctl_put);
+ return 0;
+}
+
+static int gbaudio_tplg_create_kcontrol(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ int ret = 0;
+ struct gbaudio_ctl_pvt *ctldata;
+
+ switch (ctl->iface) {
+ case (__force int)SNDRV_CTL_ELEM_IFACE_MIXER:
+ switch (ctl->info.type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ ret = gbaudio_tplg_create_enum_kctl(gb, kctl, ctl);
+ break;
+ default:
+ ctldata = devm_kzalloc(gb->dev,
+ sizeof(struct gbaudio_ctl_pvt),
+ GFP_KERNEL);
+ if (!ctldata)
+ return -ENOMEM;
+ ctldata->ctl_id = ctl->id;
+ ctldata->data_cport = le16_to_cpu(ctl->data_cport);
+ ctldata->access = le32_to_cpu(ctl->access);
+ ctldata->vcount = ctl->count_values;
+ ctldata->info = &ctl->info;
+ *kctl = (struct snd_kcontrol_new)
+ SOC_MIXER_GB(ctl->name, ctl->count, ctldata);
+ ctldata = NULL;
+ break;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(gb->dev, "%s:%d control created\n", ctl->name, ctl->id);
+ return ret;
+}
+
+static int gbcodec_enum_dapm_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, ctl_id;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct gbaudio_module_info *module;
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct device *codec_dev = widget->dapm->dev;
+ struct gbaudio_codec_info *gb = dev_get_drvdata(codec_dev);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct gb_bundle *bundle;
+
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ ctl_id = gbaudio_map_wcontrolname(module, kcontrol->id.name);
+ if (ctl_id < 0)
+ return -EINVAL;
+
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+
+ ucontrol->value.enumerated.item[0] = le32_to_cpu(gbvalue.value.enumerated_item[0]);
+ if (e->shift_l != e->shift_r)
+ ucontrol->value.enumerated.item[1] =
+ le32_to_cpu(gbvalue.value.enumerated_item[1]);
+
+ return 0;
+}
+
+static int gbcodec_enum_dapm_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int ret, wi, ctl_id;
+ unsigned int val, mux, change;
+ unsigned int mask;
+ struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+ struct gb_audio_ctl_elem_value gbvalue;
+ struct gbaudio_module_info *module;
+ struct device *codec_dev = widget->dapm->dev;
+ struct gbaudio_codec_info *gb = dev_get_drvdata(codec_dev);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct gb_bundle *bundle;
+
+ if (ucontrol->value.enumerated.item[0] > e->items - 1)
+ return -EINVAL;
+
+ module = find_gb_module(gb, kcontrol->id.name);
+ if (!module)
+ return -EINVAL;
+
+ ctl_id = gbaudio_map_wcontrolname(module, kcontrol->id.name);
+ if (ctl_id < 0)
+ return -EINVAL;
+
+ change = 0;
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec_dev, "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ return ret;
+ }
+
+ mux = ucontrol->value.enumerated.item[0];
+ val = mux << e->shift_l;
+ mask = e->mask << e->shift_l;
+
+ if (le32_to_cpu(gbvalue.value.enumerated_item[0]) !=
+ ucontrol->value.enumerated.item[0]) {
+ change = 1;
+ gbvalue.value.enumerated_item[0] =
+ cpu_to_le32(ucontrol->value.enumerated.item[0]);
+ }
+
+ if (e->shift_l != e->shift_r) {
+ if (ucontrol->value.enumerated.item[1] > e->items - 1)
+ return -EINVAL;
+ val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+ mask |= e->mask << e->shift_r;
+ if (le32_to_cpu(gbvalue.value.enumerated_item[1]) !=
+ ucontrol->value.enumerated.item[1]) {
+ change = 1;
+ gbvalue.value.enumerated_item[1] =
+ cpu_to_le32(ucontrol->value.enumerated.item[1]);
+ }
+ }
+
+ if (change) {
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id,
+ GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ if (ret) {
+ dev_err_ratelimited(codec_dev,
+ "%d:Error in %s for %s\n", ret,
+ __func__, kcontrol->id.name);
+ }
+ for (wi = 0; wi < wlist->num_widgets; wi++) {
+ widget = wlist->widgets[wi];
+ snd_soc_dapm_mux_update_power(widget->dapm, kcontrol,
+ val, e, NULL);
+ }
+ }
+
+ return change;
+}
+
+static int gbaudio_tplg_create_enum_ctl(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ struct soc_enum *gbe;
+ struct gb_audio_enumerated *gb_enum;
+ int i;
+
+ gbe = devm_kzalloc(gb->dev, sizeof(*gbe), GFP_KERNEL);
+ if (!gbe)
+ return -ENOMEM;
+
+ gb_enum = &ctl->info.value.enumerated;
+
+ /* since count=1, and reg is dummy */
+ gbe->items = le32_to_cpu(gb_enum->items);
+ gbe->texts = gb_generate_enum_strings(gb, gb_enum);
+ if (!gbe->texts)
+ return -ENOMEM;
+
+ /* debug enum info */
+ dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gbe->items,
+ le16_to_cpu(gb_enum->names_length));
+ for (i = 0; i < gbe->items; i++)
+ dev_dbg(gb->dev, "src[%d]: %s\n", i, gbe->texts[i]);
+
+ *kctl = (struct snd_kcontrol_new)
+ SOC_DAPM_ENUM_EXT(ctl->name, *gbe, gbcodec_enum_dapm_ctl_get,
+ gbcodec_enum_dapm_ctl_put);
+ return 0;
+}
+
+static int gbaudio_tplg_create_mixer_ctl(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ struct gbaudio_ctl_pvt *ctldata;
+
+ ctldata = devm_kzalloc(gb->dev, sizeof(struct gbaudio_ctl_pvt),
+ GFP_KERNEL);
+ if (!ctldata)
+ return -ENOMEM;
+ ctldata->ctl_id = ctl->id;
+ ctldata->data_cport = le16_to_cpu(ctl->data_cport);
+ ctldata->access = le32_to_cpu(ctl->access);
+ ctldata->vcount = ctl->count_values;
+ ctldata->info = &ctl->info;
+ *kctl = (struct snd_kcontrol_new)
+ SOC_DAPM_MIXER_GB(ctl->name, ctl->count, ctldata);
+
+ return 0;
+}
+
+static int gbaudio_tplg_create_wcontrol(struct gbaudio_module_info *gb,
+ struct snd_kcontrol_new *kctl,
+ struct gb_audio_control *ctl)
+{
+ int ret;
+
+ switch (ctl->iface) {
+ case (__force int)SNDRV_CTL_ELEM_IFACE_MIXER:
+ switch (ctl->info.type) {
+ case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+ ret = gbaudio_tplg_create_enum_ctl(gb, kctl, ctl);
+ break;
+ default:
+ ret = gbaudio_tplg_create_mixer_ctl(gb, kctl, ctl);
+ break;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(gb->dev, "%s:%d DAPM control created, ret:%d\n", ctl->name,
+ ctl->id, ret);
+ return ret;
+}
+
+static int gbaudio_widget_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ int wid;
+ int ret;
+ struct device *codec_dev = w->dapm->dev;
+ struct gbaudio_codec_info *gbcodec = dev_get_drvdata(codec_dev);
+ struct gbaudio_module_info *module;
+ struct gb_bundle *bundle;
+
+ dev_dbg(codec_dev, "%s %s %d\n", __func__, w->name, event);
+
+ /* Find relevant module */
+ module = find_gb_module(gbcodec, w->name);
+ if (!module)
+ return -EINVAL;
+
+ /* map name to widget id */
+ wid = gbaudio_map_widgetname(module, w->name);
+ if (wid < 0) {
+ dev_err(codec_dev, "Invalid widget name:%s\n", w->name);
+ return -EINVAL;
+ }
+
+ bundle = to_gb_bundle(module->dev);
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = gb_audio_gb_enable_widget(module->mgmt_connection, wid);
+ if (!ret)
+ ret = gbaudio_module_update(gbcodec, w, module, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ret = gb_audio_gb_disable_widget(module->mgmt_connection, wid);
+ if (!ret)
+ ret = gbaudio_module_update(gbcodec, w, module, 0);
+ break;
+ }
+ if (ret)
+ dev_err_ratelimited(codec_dev,
+ "%d: widget, event:%d failed:%d\n", wid,
+ event, ret);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget gbaudio_widgets[] = {
+ [snd_soc_dapm_spk] = SND_SOC_DAPM_SPK(NULL, gbcodec_event_spk),
+ [snd_soc_dapm_hp] = SND_SOC_DAPM_HP(NULL, gbcodec_event_hp),
+ [snd_soc_dapm_mic] = SND_SOC_DAPM_MIC(NULL, gbcodec_event_int_mic),
+ [snd_soc_dapm_output] = SND_SOC_DAPM_OUTPUT(NULL),
+ [snd_soc_dapm_input] = SND_SOC_DAPM_INPUT(NULL),
+ [snd_soc_dapm_switch] = SND_SOC_DAPM_SWITCH_E(NULL, SND_SOC_NOPM,
+ 0, 0, NULL,
+ gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ [snd_soc_dapm_pga] = SND_SOC_DAPM_PGA_E(NULL, SND_SOC_NOPM,
+ 0, 0, NULL, 0,
+ gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ [snd_soc_dapm_mixer] = SND_SOC_DAPM_MIXER_E(NULL, SND_SOC_NOPM,
+ 0, 0, NULL, 0,
+ gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ [snd_soc_dapm_mux] = SND_SOC_DAPM_MUX_E(NULL, SND_SOC_NOPM,
+ 0, 0, NULL,
+ gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ [snd_soc_dapm_aif_in] = SND_SOC_DAPM_AIF_IN_E(NULL, NULL, 0,
+ SND_SOC_NOPM, 0, 0,
+ gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+ [snd_soc_dapm_aif_out] = SND_SOC_DAPM_AIF_OUT_E(NULL, NULL, 0,
+ SND_SOC_NOPM, 0, 0,
+ gbaudio_widget_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_POST_PMD),
+};
+
+static int gbaudio_tplg_create_widget(struct gbaudio_module_info *module,
+ struct snd_soc_dapm_widget *dw,
+ struct gb_audio_widget *w, int *w_size)
+{
+ int i, ret, csize;
+ struct snd_kcontrol_new *widget_kctls;
+ struct gb_audio_control *curr;
+ struct gbaudio_control *control, *_control;
+ size_t size;
+ char temp_name[NAME_SIZE];
+
+ ret = gbaudio_validate_kcontrol_count(w);
+ if (ret) {
+ dev_err(module->dev, "Invalid kcontrol count=%d for %s\n",
+ w->ncontrols, w->name);
+ return ret;
+ }
+
+ /* allocate memory for kcontrol */
+ if (w->ncontrols) {
+ size = sizeof(struct snd_kcontrol_new) * w->ncontrols;
+ widget_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL);
+ if (!widget_kctls)
+ return -ENOMEM;
+ }
+
+ *w_size = sizeof(struct gb_audio_widget);
+
+ /* create relevant kcontrols */
+ curr = w->ctl;
+ for (i = 0; i < w->ncontrols; i++) {
+ ret = gbaudio_tplg_create_wcontrol(module, &widget_kctls[i],
+ curr);
+ if (ret) {
+ dev_err(module->dev,
+ "%s:%d type widget_ctl not supported\n",
+ curr->name, curr->iface);
+ goto error;
+ }
+ control = devm_kzalloc(module->dev,
+ sizeof(struct gbaudio_control),
+ GFP_KERNEL);
+ if (!control) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ control->id = curr->id;
+ control->name = curr->name;
+ control->wname = w->name;
+
+ if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) {
+ struct gb_audio_enumerated *gbenum =
+ &curr->info.value.enumerated;
+
+ csize = offsetof(struct gb_audio_control, info);
+ csize += offsetof(struct gb_audio_ctl_elem_info, value);
+ csize += offsetof(struct gb_audio_enumerated, names);
+ csize += le16_to_cpu(gbenum->names_length);
+ control->texts = (const char * const *)
+ gb_generate_enum_strings(module, gbenum);
+ if (!control->texts) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ control->items = le32_to_cpu(gbenum->items);
+ } else {
+ csize = sizeof(struct gb_audio_control);
+ }
+
+ *w_size += csize;
+ curr = (void *)curr + csize;
+ list_add(&control->list, &module->widget_ctl_list);
+ dev_dbg(module->dev, "%s: control of type %d created\n",
+ widget_kctls[i].name, widget_kctls[i].iface);
+ }
+
+ /* Prefix dev_id to widget control_name */
+ strscpy(temp_name, w->name, sizeof(temp_name));
+ snprintf(w->name, sizeof(w->name), "GB %d %s", module->dev_id, temp_name);
+
+ switch (w->type) {
+ case snd_soc_dapm_spk:
+ *dw = gbaudio_widgets[w->type];
+ module->op_devices |= GBAUDIO_DEVICE_OUT_SPEAKER;
+ break;
+ case snd_soc_dapm_hp:
+ *dw = gbaudio_widgets[w->type];
+ module->op_devices |= (GBAUDIO_DEVICE_OUT_WIRED_HEADSET
+ | GBAUDIO_DEVICE_OUT_WIRED_HEADPHONE);
+ module->ip_devices |= GBAUDIO_DEVICE_IN_WIRED_HEADSET;
+ break;
+ case snd_soc_dapm_mic:
+ *dw = gbaudio_widgets[w->type];
+ module->ip_devices |= GBAUDIO_DEVICE_IN_BUILTIN_MIC;
+ break;
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_input:
+ case snd_soc_dapm_switch:
+ case snd_soc_dapm_pga:
+ case snd_soc_dapm_mixer:
+ case snd_soc_dapm_mux:
+ *dw = gbaudio_widgets[w->type];
+ break;
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ *dw = gbaudio_widgets[w->type];
+ dw->sname = w->sname;
+ break;
+ default:
+ ret = -EINVAL;
+ goto error;
+ }
+ dw->name = w->name;
+
+ dev_dbg(module->dev, "%s: widget of type %d created\n", dw->name,
+ dw->id);
+ return 0;
+error:
+ list_for_each_entry_safe(control, _control, &module->widget_ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(module->dev, control);
+ }
+ return ret;
+}
+
+static int gbaudio_tplg_process_kcontrols(struct gbaudio_module_info *module,
+ struct gb_audio_control *controls)
+{
+ int i, csize, ret;
+ struct snd_kcontrol_new *dapm_kctls;
+ struct gb_audio_control *curr;
+ struct gbaudio_control *control, *_control;
+ size_t size;
+ char temp_name[NAME_SIZE];
+
+ size = sizeof(struct snd_kcontrol_new) * module->num_controls;
+ dapm_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL);
+ if (!dapm_kctls)
+ return -ENOMEM;
+
+ curr = controls;
+ for (i = 0; i < module->num_controls; i++) {
+ ret = gbaudio_tplg_create_kcontrol(module, &dapm_kctls[i],
+ curr);
+ if (ret) {
+ dev_err(module->dev, "%s:%d type not supported\n",
+ curr->name, curr->iface);
+ goto error;
+ }
+ control = devm_kzalloc(module->dev, sizeof(struct
+ gbaudio_control),
+ GFP_KERNEL);
+ if (!control) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ control->id = curr->id;
+ /* Prefix dev_id to widget_name */
+ strscpy(temp_name, curr->name, sizeof(temp_name));
+ snprintf(curr->name, sizeof(curr->name), "GB %d %s", module->dev_id,
+ temp_name);
+ control->name = curr->name;
+ if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) {
+ struct gb_audio_enumerated *gbenum =
+ &curr->info.value.enumerated;
+
+ csize = offsetof(struct gb_audio_control, info);
+ csize += offsetof(struct gb_audio_ctl_elem_info, value);
+ csize += offsetof(struct gb_audio_enumerated, names);
+ csize += le16_to_cpu(gbenum->names_length);
+ control->texts = (const char * const *)
+ gb_generate_enum_strings(module, gbenum);
+ if (!control->texts) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ control->items = le32_to_cpu(gbenum->items);
+ } else {
+ csize = sizeof(struct gb_audio_control);
+ }
+
+ list_add(&control->list, &module->ctl_list);
+ dev_dbg(module->dev, "%d:%s created of type %d\n", curr->id,
+ curr->name, curr->info.type);
+ curr = (void *)curr + csize;
+ }
+ module->controls = dapm_kctls;
+
+ return 0;
+error:
+ list_for_each_entry_safe(control, _control, &module->ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(module->dev, control);
+ }
+ devm_kfree(module->dev, dapm_kctls);
+ return ret;
+}
+
+static int gbaudio_tplg_process_widgets(struct gbaudio_module_info *module,
+ struct gb_audio_widget *widgets)
+{
+ int i, ret, w_size;
+ struct snd_soc_dapm_widget *dapm_widgets;
+ struct gb_audio_widget *curr;
+ struct gbaudio_widget *widget, *_widget;
+ size_t size;
+
+ size = sizeof(struct snd_soc_dapm_widget) * module->num_dapm_widgets;
+ dapm_widgets = devm_kzalloc(module->dev, size, GFP_KERNEL);
+ if (!dapm_widgets)
+ return -ENOMEM;
+
+ curr = widgets;
+ for (i = 0; i < module->num_dapm_widgets; i++) {
+ ret = gbaudio_tplg_create_widget(module, &dapm_widgets[i],
+ curr, &w_size);
+ if (ret) {
+ dev_err(module->dev, "%s:%d type not supported\n",
+ curr->name, curr->type);
+ goto error;
+ }
+ widget = devm_kzalloc(module->dev, sizeof(struct
+ gbaudio_widget),
+ GFP_KERNEL);
+ if (!widget) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ widget->id = curr->id;
+ widget->name = curr->name;
+ list_add(&widget->list, &module->widget_list);
+ curr = (void *)curr + w_size;
+ }
+ module->dapm_widgets = dapm_widgets;
+
+ return 0;
+
+error:
+ list_for_each_entry_safe(widget, _widget, &module->widget_list,
+ list) {
+ list_del(&widget->list);
+ devm_kfree(module->dev, widget);
+ }
+ devm_kfree(module->dev, dapm_widgets);
+ return ret;
+}
+
+static int gbaudio_tplg_process_routes(struct gbaudio_module_info *module,
+ struct gb_audio_route *routes)
+{
+ int i, ret;
+ struct snd_soc_dapm_route *dapm_routes;
+ struct gb_audio_route *curr;
+ size_t size;
+
+ size = sizeof(struct snd_soc_dapm_route) * module->num_dapm_routes;
+ dapm_routes = devm_kzalloc(module->dev, size, GFP_KERNEL);
+ if (!dapm_routes)
+ return -ENOMEM;
+
+ module->dapm_routes = dapm_routes;
+ curr = routes;
+
+ for (i = 0; i < module->num_dapm_routes; i++) {
+ dapm_routes->sink =
+ gbaudio_map_widgetid(module, curr->destination_id);
+ if (!dapm_routes->sink) {
+ dev_err(module->dev, "%d:%d:%d:%d - Invalid sink\n",
+ curr->source_id, curr->destination_id,
+ curr->control_id, curr->index);
+ ret = -EINVAL;
+ goto error;
+ }
+ dapm_routes->source =
+ gbaudio_map_widgetid(module, curr->source_id);
+ if (!dapm_routes->source) {
+ dev_err(module->dev, "%d:%d:%d:%d - Invalid source\n",
+ curr->source_id, curr->destination_id,
+ curr->control_id, curr->index);
+ ret = -EINVAL;
+ goto error;
+ }
+ dapm_routes->control =
+ gbaudio_map_controlid(module,
+ curr->control_id,
+ curr->index);
+ if ((curr->control_id != GBAUDIO_INVALID_ID) &&
+ !dapm_routes->control) {
+ dev_err(module->dev, "%d:%d:%d:%d - Invalid control\n",
+ curr->source_id, curr->destination_id,
+ curr->control_id, curr->index);
+ ret = -EINVAL;
+ goto error;
+ }
+ dev_dbg(module->dev, "Route {%s, %s, %s}\n", dapm_routes->sink,
+ (dapm_routes->control) ? dapm_routes->control : "NULL",
+ dapm_routes->source);
+ dapm_routes++;
+ curr++;
+ }
+
+ return 0;
+
+error:
+ devm_kfree(module->dev, module->dapm_routes);
+ return ret;
+}
+
+static int gbaudio_tplg_process_header(struct gbaudio_module_info *module,
+ struct gb_audio_topology *tplg_data)
+{
+ /* fetch no. of kcontrols, widgets & routes */
+ module->num_controls = tplg_data->num_controls;
+ module->num_dapm_widgets = tplg_data->num_widgets;
+ module->num_dapm_routes = tplg_data->num_routes;
+
+ /* update block offset */
+ module->dai_offset = (unsigned long)&tplg_data->data;
+ module->control_offset = module->dai_offset +
+ le32_to_cpu(tplg_data->size_dais);
+ module->widget_offset = module->control_offset +
+ le32_to_cpu(tplg_data->size_controls);
+ module->route_offset = module->widget_offset +
+ le32_to_cpu(tplg_data->size_widgets);
+
+ dev_dbg(module->dev, "DAI offset is 0x%lx\n", module->dai_offset);
+ dev_dbg(module->dev, "control offset is %lx\n",
+ module->control_offset);
+ dev_dbg(module->dev, "widget offset is %lx\n", module->widget_offset);
+ dev_dbg(module->dev, "route offset is %lx\n", module->route_offset);
+
+ return 0;
+}
+
+int gbaudio_tplg_parse_data(struct gbaudio_module_info *module,
+ struct gb_audio_topology *tplg_data)
+{
+ int ret;
+ struct gb_audio_control *controls;
+ struct gb_audio_widget *widgets;
+ struct gb_audio_route *routes;
+ unsigned int jack_type;
+
+ if (!tplg_data)
+ return -EINVAL;
+
+ ret = gbaudio_tplg_process_header(module, tplg_data);
+ if (ret) {
+ dev_err(module->dev, "%d: Error in parsing topology header\n",
+ ret);
+ return ret;
+ }
+
+ /* process control */
+ controls = (struct gb_audio_control *)module->control_offset;
+ ret = gbaudio_tplg_process_kcontrols(module, controls);
+ if (ret) {
+ dev_err(module->dev,
+ "%d: Error in parsing controls data\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Control parsing finished\n");
+
+ /* process widgets */
+ widgets = (struct gb_audio_widget *)module->widget_offset;
+ ret = gbaudio_tplg_process_widgets(module, widgets);
+ if (ret) {
+ dev_err(module->dev,
+ "%d: Error in parsing widgets data\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Widget parsing finished\n");
+
+ /* process route */
+ routes = (struct gb_audio_route *)module->route_offset;
+ ret = gbaudio_tplg_process_routes(module, routes);
+ if (ret) {
+ dev_err(module->dev,
+ "%d: Error in parsing routes data\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Route parsing finished\n");
+
+ /* parse jack capabilities */
+ jack_type = le32_to_cpu(tplg_data->jack_type);
+ if (jack_type) {
+ module->jack_mask = jack_type & GBCODEC_JACK_MASK;
+ module->button_mask = jack_type & GBCODEC_JACK_BUTTON_MASK;
+ }
+
+ return ret;
+}
+
+void gbaudio_tplg_release(struct gbaudio_module_info *module)
+{
+ struct gbaudio_control *control, *_control;
+ struct gbaudio_widget *widget, *_widget;
+
+ if (!module->topology)
+ return;
+
+ /* release kcontrols */
+ list_for_each_entry_safe(control, _control, &module->ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(module->dev, control);
+ }
+ if (module->controls)
+ devm_kfree(module->dev, module->controls);
+
+ /* release widget controls */
+ list_for_each_entry_safe(control, _control, &module->widget_ctl_list,
+ list) {
+ list_del(&control->list);
+ devm_kfree(module->dev, control);
+ }
+
+ /* release widgets */
+ list_for_each_entry_safe(widget, _widget, &module->widget_list,
+ list) {
+ list_del(&widget->list);
+ devm_kfree(module->dev, widget);
+ }
+ if (module->dapm_widgets)
+ devm_kfree(module->dev, module->dapm_widgets);
+
+ /* release routes */
+ if (module->dapm_routes)
+ devm_kfree(module->dev, module->dapm_routes);
+}
diff --git a/drivers/staging/greybus/authentication.c b/drivers/staging/greybus/authentication.c
new file mode 100644
index 0000000000..7e01790a46
--- /dev/null
+++ b/drivers/staging/greybus/authentication.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Component Authentication Protocol (CAP) Driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ */
+
+#include <linux/greybus.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+
+#include "greybus_authentication.h"
+#include "firmware.h"
+
+#define CAP_TIMEOUT_MS 1000
+
+/*
+ * Number of minor devices this driver supports.
+ * There will be exactly one required per Interface.
+ */
+#define NUM_MINORS U8_MAX
+
+struct gb_cap {
+ struct device *parent;
+ struct gb_connection *connection;
+ struct kref kref;
+ struct list_head node;
+ bool disabled; /* connection getting disabled */
+
+ struct mutex mutex;
+ struct cdev cdev;
+ struct device *class_device;
+ dev_t dev_num;
+};
+
+static struct class *cap_class;
+static dev_t cap_dev_num;
+static DEFINE_IDA(cap_minors_map);
+static LIST_HEAD(cap_list);
+static DEFINE_MUTEX(list_mutex);
+
+static void cap_kref_release(struct kref *kref)
+{
+ struct gb_cap *cap = container_of(kref, struct gb_cap, kref);
+
+ kfree(cap);
+}
+
+/*
+ * All users of cap take a reference (from within list_mutex lock), before
+ * they get a pointer to play with. And the structure will be freed only after
+ * the last user has put the reference to it.
+ */
+static void put_cap(struct gb_cap *cap)
+{
+ kref_put(&cap->kref, cap_kref_release);
+}
+
+/* Caller must call put_cap() after using struct gb_cap */
+static struct gb_cap *get_cap(struct cdev *cdev)
+{
+ struct gb_cap *cap;
+
+ mutex_lock(&list_mutex);
+
+ list_for_each_entry(cap, &cap_list, node) {
+ if (&cap->cdev == cdev) {
+ kref_get(&cap->kref);
+ goto unlock;
+ }
+ }
+
+ cap = NULL;
+
+unlock:
+ mutex_unlock(&list_mutex);
+
+ return cap;
+}
+
+static int cap_get_endpoint_uid(struct gb_cap *cap, u8 *euid)
+{
+ struct gb_connection *connection = cap->connection;
+ struct gb_cap_get_endpoint_uid_response response;
+ int ret;
+
+ ret = gb_operation_sync(connection, GB_CAP_TYPE_GET_ENDPOINT_UID, NULL,
+ 0, &response, sizeof(response));
+ if (ret) {
+ dev_err(cap->parent, "failed to get endpoint uid (%d)\n", ret);
+ return ret;
+ }
+
+ memcpy(euid, response.uid, sizeof(response.uid));
+
+ return 0;
+}
+
+static int cap_get_ims_certificate(struct gb_cap *cap, u32 class, u32 id,
+ u8 *certificate, u32 *size, u8 *result)
+{
+ struct gb_connection *connection = cap->connection;
+ struct gb_cap_get_ims_certificate_request *request;
+ struct gb_cap_get_ims_certificate_response *response;
+ size_t max_size = gb_operation_get_payload_size_max(connection);
+ struct gb_operation *op;
+ int ret;
+
+ op = gb_operation_create_flags(connection,
+ GB_CAP_TYPE_GET_IMS_CERTIFICATE,
+ sizeof(*request), max_size,
+ GB_OPERATION_FLAG_SHORT_RESPONSE,
+ GFP_KERNEL);
+ if (!op)
+ return -ENOMEM;
+
+ request = op->request->payload;
+ request->certificate_class = cpu_to_le32(class);
+ request->certificate_id = cpu_to_le32(id);
+
+ ret = gb_operation_request_send_sync(op);
+ if (ret) {
+ dev_err(cap->parent, "failed to get certificate (%d)\n", ret);
+ goto done;
+ }
+
+ response = op->response->payload;
+ *result = response->result_code;
+ *size = op->response->payload_size - sizeof(*response);
+ memcpy(certificate, response->certificate, *size);
+
+done:
+ gb_operation_put(op);
+ return ret;
+}
+
+static int cap_authenticate(struct gb_cap *cap, u32 auth_type, u8 *uid,
+ u8 *challenge, u8 *result, u8 *auth_response,
+ u32 *signature_size, u8 *signature)
+{
+ struct gb_connection *connection = cap->connection;
+ struct gb_cap_authenticate_request *request;
+ struct gb_cap_authenticate_response *response;
+ size_t max_size = gb_operation_get_payload_size_max(connection);
+ struct gb_operation *op;
+ int ret;
+
+ op = gb_operation_create_flags(connection, GB_CAP_TYPE_AUTHENTICATE,
+ sizeof(*request), max_size,
+ GB_OPERATION_FLAG_SHORT_RESPONSE,
+ GFP_KERNEL);
+ if (!op)
+ return -ENOMEM;
+
+ request = op->request->payload;
+ request->auth_type = cpu_to_le32(auth_type);
+ memcpy(request->uid, uid, sizeof(request->uid));
+ memcpy(request->challenge, challenge, sizeof(request->challenge));
+
+ ret = gb_operation_request_send_sync(op);
+ if (ret) {
+ dev_err(cap->parent, "failed to authenticate (%d)\n", ret);
+ goto done;
+ }
+
+ response = op->response->payload;
+ *result = response->result_code;
+ *signature_size = op->response->payload_size - sizeof(*response);
+ memcpy(auth_response, response->response, sizeof(response->response));
+ memcpy(signature, response->signature, *signature_size);
+
+done:
+ gb_operation_put(op);
+ return ret;
+}
+
+/* Char device fops */
+
+static int cap_open(struct inode *inode, struct file *file)
+{
+ struct gb_cap *cap = get_cap(inode->i_cdev);
+
+ /* cap structure can't get freed until file descriptor is closed */
+ if (cap) {
+ file->private_data = cap;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+static int cap_release(struct inode *inode, struct file *file)
+{
+ struct gb_cap *cap = file->private_data;
+
+ put_cap(cap);
+ return 0;
+}
+
+static int cap_ioctl(struct gb_cap *cap, unsigned int cmd,
+ void __user *buf)
+{
+ struct cap_ioc_get_endpoint_uid endpoint_uid;
+ struct cap_ioc_get_ims_certificate *ims_cert;
+ struct cap_ioc_authenticate *authenticate;
+ size_t size;
+ int ret;
+
+ switch (cmd) {
+ case CAP_IOC_GET_ENDPOINT_UID:
+ ret = cap_get_endpoint_uid(cap, endpoint_uid.uid);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(buf, &endpoint_uid, sizeof(endpoint_uid)))
+ return -EFAULT;
+
+ return 0;
+ case CAP_IOC_GET_IMS_CERTIFICATE:
+ size = sizeof(*ims_cert);
+ ims_cert = memdup_user(buf, size);
+ if (IS_ERR(ims_cert))
+ return PTR_ERR(ims_cert);
+
+ ret = cap_get_ims_certificate(cap, ims_cert->certificate_class,
+ ims_cert->certificate_id,
+ ims_cert->certificate,
+ &ims_cert->cert_size,
+ &ims_cert->result_code);
+ if (!ret && copy_to_user(buf, ims_cert, size))
+ ret = -EFAULT;
+ kfree(ims_cert);
+
+ return ret;
+ case CAP_IOC_AUTHENTICATE:
+ size = sizeof(*authenticate);
+ authenticate = memdup_user(buf, size);
+ if (IS_ERR(authenticate))
+ return PTR_ERR(authenticate);
+
+ ret = cap_authenticate(cap, authenticate->auth_type,
+ authenticate->uid,
+ authenticate->challenge,
+ &authenticate->result_code,
+ authenticate->response,
+ &authenticate->signature_size,
+ authenticate->signature);
+ if (!ret && copy_to_user(buf, authenticate, size))
+ ret = -EFAULT;
+ kfree(authenticate);
+
+ return ret;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static long cap_ioctl_unlocked(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct gb_cap *cap = file->private_data;
+ struct gb_bundle *bundle = cap->connection->bundle;
+ int ret = -ENODEV;
+
+ /*
+ * Serialize ioctls.
+ *
+ * We don't want the user to do multiple authentication operations in
+ * parallel.
+ *
+ * This is also used to protect ->disabled, which is used to check if
+ * the connection is getting disconnected, so that we don't start any
+ * new operations.
+ */
+ mutex_lock(&cap->mutex);
+ if (!cap->disabled) {
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (!ret) {
+ ret = cap_ioctl(cap, cmd, (void __user *)arg);
+ gb_pm_runtime_put_autosuspend(bundle);
+ }
+ }
+ mutex_unlock(&cap->mutex);
+
+ return ret;
+}
+
+static const struct file_operations cap_fops = {
+ .owner = THIS_MODULE,
+ .open = cap_open,
+ .release = cap_release,
+ .unlocked_ioctl = cap_ioctl_unlocked,
+};
+
+int gb_cap_connection_init(struct gb_connection *connection)
+{
+ struct gb_cap *cap;
+ int ret, minor;
+
+ if (!connection)
+ return 0;
+
+ cap = kzalloc(sizeof(*cap), GFP_KERNEL);
+ if (!cap)
+ return -ENOMEM;
+
+ cap->parent = &connection->bundle->dev;
+ cap->connection = connection;
+ mutex_init(&cap->mutex);
+ gb_connection_set_data(connection, cap);
+ kref_init(&cap->kref);
+
+ mutex_lock(&list_mutex);
+ list_add(&cap->node, &cap_list);
+ mutex_unlock(&list_mutex);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto err_list_del;
+
+ minor = ida_simple_get(&cap_minors_map, 0, NUM_MINORS, GFP_KERNEL);
+ if (minor < 0) {
+ ret = minor;
+ goto err_connection_disable;
+ }
+
+ /* Add a char device to allow userspace to interact with cap */
+ cap->dev_num = MKDEV(MAJOR(cap_dev_num), minor);
+ cdev_init(&cap->cdev, &cap_fops);
+
+ ret = cdev_add(&cap->cdev, cap->dev_num, 1);
+ if (ret)
+ goto err_remove_ida;
+
+ /* Add a soft link to the previously added char-dev within the bundle */
+ cap->class_device = device_create(cap_class, cap->parent, cap->dev_num,
+ NULL, "gb-authenticate-%d", minor);
+ if (IS_ERR(cap->class_device)) {
+ ret = PTR_ERR(cap->class_device);
+ goto err_del_cdev;
+ }
+
+ return 0;
+
+err_del_cdev:
+ cdev_del(&cap->cdev);
+err_remove_ida:
+ ida_simple_remove(&cap_minors_map, minor);
+err_connection_disable:
+ gb_connection_disable(connection);
+err_list_del:
+ mutex_lock(&list_mutex);
+ list_del(&cap->node);
+ mutex_unlock(&list_mutex);
+
+ put_cap(cap);
+
+ return ret;
+}
+
+void gb_cap_connection_exit(struct gb_connection *connection)
+{
+ struct gb_cap *cap;
+
+ if (!connection)
+ return;
+
+ cap = gb_connection_get_data(connection);
+
+ device_destroy(cap_class, cap->dev_num);
+ cdev_del(&cap->cdev);
+ ida_simple_remove(&cap_minors_map, MINOR(cap->dev_num));
+
+ /*
+ * Disallow any new ioctl operations on the char device and wait for
+ * existing ones to finish.
+ */
+ mutex_lock(&cap->mutex);
+ cap->disabled = true;
+ mutex_unlock(&cap->mutex);
+
+ /* All pending greybus operations should have finished by now */
+ gb_connection_disable(cap->connection);
+
+ /* Disallow new users to get access to the cap structure */
+ mutex_lock(&list_mutex);
+ list_del(&cap->node);
+ mutex_unlock(&list_mutex);
+
+ /*
+ * All current users of cap would have taken a reference to it by
+ * now, we can drop our reference and wait the last user will get
+ * cap freed.
+ */
+ put_cap(cap);
+}
+
+int cap_init(void)
+{
+ int ret;
+
+ cap_class = class_create("gb_authenticate");
+ if (IS_ERR(cap_class))
+ return PTR_ERR(cap_class);
+
+ ret = alloc_chrdev_region(&cap_dev_num, 0, NUM_MINORS,
+ "gb_authenticate");
+ if (ret)
+ goto err_remove_class;
+
+ return 0;
+
+err_remove_class:
+ class_destroy(cap_class);
+ return ret;
+}
+
+void cap_exit(void)
+{
+ unregister_chrdev_region(cap_dev_num, NUM_MINORS);
+ class_destroy(cap_class);
+ ida_destroy(&cap_minors_map);
+}
diff --git a/drivers/staging/greybus/bootrom.c b/drivers/staging/greybus/bootrom.c
new file mode 100644
index 0000000000..a8efb86de1
--- /dev/null
+++ b/drivers/staging/greybus/bootrom.c
@@ -0,0 +1,527 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BOOTROM Greybus driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ */
+
+#include <linux/firmware.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/greybus.h>
+
+#include "firmware.h"
+
+/* Timeout, in jiffies, within which the next request must be received */
+#define NEXT_REQ_TIMEOUT_MS 1000
+
+/*
+ * FIXME: Reduce this timeout once svc core handles parallel processing of
+ * events from the SVC, which are handled sequentially today.
+ */
+#define MODE_SWITCH_TIMEOUT_MS 10000
+
+enum next_request_type {
+ NEXT_REQ_FIRMWARE_SIZE,
+ NEXT_REQ_GET_FIRMWARE,
+ NEXT_REQ_READY_TO_BOOT,
+ NEXT_REQ_MODE_SWITCH,
+};
+
+struct gb_bootrom {
+ struct gb_connection *connection;
+ const struct firmware *fw;
+ u8 protocol_major;
+ u8 protocol_minor;
+ enum next_request_type next_request;
+ struct delayed_work dwork;
+ struct mutex mutex; /* Protects bootrom->fw */
+};
+
+static void free_firmware(struct gb_bootrom *bootrom)
+{
+ if (!bootrom->fw)
+ return;
+
+ release_firmware(bootrom->fw);
+ bootrom->fw = NULL;
+}
+
+static void gb_bootrom_timedout(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct gb_bootrom *bootrom = container_of(dwork,
+ struct gb_bootrom, dwork);
+ struct device *dev = &bootrom->connection->bundle->dev;
+ const char *reason;
+
+ switch (bootrom->next_request) {
+ case NEXT_REQ_FIRMWARE_SIZE:
+ reason = "Firmware Size Request";
+ break;
+ case NEXT_REQ_GET_FIRMWARE:
+ reason = "Get Firmware Request";
+ break;
+ case NEXT_REQ_READY_TO_BOOT:
+ reason = "Ready to Boot Request";
+ break;
+ case NEXT_REQ_MODE_SWITCH:
+ reason = "Interface Mode Switch";
+ break;
+ default:
+ reason = NULL;
+ dev_err(dev, "Invalid next-request: %u", bootrom->next_request);
+ break;
+ }
+
+ dev_err(dev, "Timed out waiting for %s from the Module\n", reason);
+
+ mutex_lock(&bootrom->mutex);
+ free_firmware(bootrom);
+ mutex_unlock(&bootrom->mutex);
+
+ /* TODO: Power-off Module ? */
+}
+
+static void gb_bootrom_set_timeout(struct gb_bootrom *bootrom,
+ enum next_request_type next,
+ unsigned long timeout)
+{
+ bootrom->next_request = next;
+ schedule_delayed_work(&bootrom->dwork, msecs_to_jiffies(timeout));
+}
+
+static void gb_bootrom_cancel_timeout(struct gb_bootrom *bootrom)
+{
+ cancel_delayed_work_sync(&bootrom->dwork);
+}
+
+/*
+ * The es2 chip doesn't have VID/PID programmed into the hardware and we need to
+ * hack that up to distinguish different modules and their firmware blobs.
+ *
+ * This fetches VID/PID (over bootrom protocol) for es2 chip only, when VID/PID
+ * already sent during hotplug are 0.
+ *
+ * Otherwise, we keep intf->vendor_id/product_id same as what's passed
+ * during hotplug.
+ */
+static void bootrom_es2_fixup_vid_pid(struct gb_bootrom *bootrom)
+{
+ struct gb_bootrom_get_vid_pid_response response;
+ struct gb_connection *connection = bootrom->connection;
+ struct gb_interface *intf = connection->bundle->intf;
+ int ret;
+
+ if (!(intf->quirks & GB_INTERFACE_QUIRK_NO_GMP_IDS))
+ return;
+
+ ret = gb_operation_sync(connection, GB_BOOTROM_TYPE_GET_VID_PID,
+ NULL, 0, &response, sizeof(response));
+ if (ret) {
+ dev_err(&connection->bundle->dev,
+ "Bootrom get vid/pid operation failed (%d)\n", ret);
+ return;
+ }
+
+ /*
+ * NOTE: This is hacked, so that the same values of VID/PID can be used
+ * by next firmware level as well. The uevent for bootrom will still
+ * have VID/PID as 0, though after this point the sysfs files will start
+ * showing the updated values. But yeah, that's a bit racy as the same
+ * sysfs files would be showing 0 before this point.
+ */
+ intf->vendor_id = le32_to_cpu(response.vendor_id);
+ intf->product_id = le32_to_cpu(response.product_id);
+
+ dev_dbg(&connection->bundle->dev, "Bootrom got vid (0x%x)/pid (0x%x)\n",
+ intf->vendor_id, intf->product_id);
+}
+
+/* This returns path of the firmware blob on the disk */
+static int find_firmware(struct gb_bootrom *bootrom, u8 stage)
+{
+ struct gb_connection *connection = bootrom->connection;
+ struct gb_interface *intf = connection->bundle->intf;
+ char firmware_name[49];
+ int rc;
+
+ /* Already have a firmware, free it */
+ free_firmware(bootrom);
+
+ /* Bootrom protocol is only supported for loading Stage 2 firmware */
+ if (stage != 2) {
+ dev_err(&connection->bundle->dev, "Invalid boot stage: %u\n",
+ stage);
+ return -EINVAL;
+ }
+
+ /*
+ * Create firmware name
+ *
+ * XXX Name it properly..
+ */
+ snprintf(firmware_name, sizeof(firmware_name),
+ FW_NAME_PREFIX "%08x_%08x_%08x_%08x_s2l.tftf",
+ intf->ddbl1_manufacturer_id, intf->ddbl1_product_id,
+ intf->vendor_id, intf->product_id);
+
+ // FIXME:
+ // Turn to dev_dbg later after everyone has valid bootloaders with good
+ // ids, but leave this as dev_info for now to make it easier to track
+ // down "empty" vid/pid modules.
+ dev_info(&connection->bundle->dev, "Firmware file '%s' requested\n",
+ firmware_name);
+
+ rc = request_firmware(&bootrom->fw, firmware_name,
+ &connection->bundle->dev);
+ if (rc) {
+ dev_err(&connection->bundle->dev,
+ "failed to find %s firmware (%d)\n", firmware_name, rc);
+ }
+
+ return rc;
+}
+
+static int gb_bootrom_firmware_size_request(struct gb_operation *op)
+{
+ struct gb_bootrom *bootrom = gb_connection_get_data(op->connection);
+ struct gb_bootrom_firmware_size_request *size_request =
+ op->request->payload;
+ struct gb_bootrom_firmware_size_response *size_response;
+ struct device *dev = &op->connection->bundle->dev;
+ int ret;
+
+ /* Disable timeouts */
+ gb_bootrom_cancel_timeout(bootrom);
+
+ if (op->request->payload_size != sizeof(*size_request)) {
+ dev_err(dev, "%s: illegal size of firmware size request (%zu != %zu)\n",
+ __func__, op->request->payload_size,
+ sizeof(*size_request));
+ ret = -EINVAL;
+ goto queue_work;
+ }
+
+ mutex_lock(&bootrom->mutex);
+
+ ret = find_firmware(bootrom, size_request->stage);
+ if (ret)
+ goto unlock;
+
+ if (!gb_operation_response_alloc(op, sizeof(*size_response),
+ GFP_KERNEL)) {
+ dev_err(dev, "%s: error allocating response\n", __func__);
+ free_firmware(bootrom);
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ size_response = op->response->payload;
+ size_response->size = cpu_to_le32(bootrom->fw->size);
+
+ dev_dbg(dev, "%s: firmware size %d bytes\n",
+ __func__, size_response->size);
+
+unlock:
+ mutex_unlock(&bootrom->mutex);
+
+queue_work:
+ if (!ret) {
+ /* Refresh timeout */
+ gb_bootrom_set_timeout(bootrom, NEXT_REQ_GET_FIRMWARE,
+ NEXT_REQ_TIMEOUT_MS);
+ }
+
+ return ret;
+}
+
+static int gb_bootrom_get_firmware(struct gb_operation *op)
+{
+ struct gb_bootrom *bootrom = gb_connection_get_data(op->connection);
+ const struct firmware *fw;
+ struct gb_bootrom_get_firmware_request *firmware_request;
+ struct gb_bootrom_get_firmware_response *firmware_response;
+ struct device *dev = &op->connection->bundle->dev;
+ unsigned int offset, size;
+ enum next_request_type next_request;
+ int ret = 0;
+
+ /* Disable timeouts */
+ gb_bootrom_cancel_timeout(bootrom);
+
+ if (op->request->payload_size != sizeof(*firmware_request)) {
+ dev_err(dev, "%s: Illegal size of get firmware request (%zu %zu)\n",
+ __func__, op->request->payload_size,
+ sizeof(*firmware_request));
+ ret = -EINVAL;
+ goto queue_work;
+ }
+
+ mutex_lock(&bootrom->mutex);
+
+ fw = bootrom->fw;
+ if (!fw) {
+ dev_err(dev, "%s: firmware not available\n", __func__);
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ firmware_request = op->request->payload;
+ offset = le32_to_cpu(firmware_request->offset);
+ size = le32_to_cpu(firmware_request->size);
+
+ if (offset >= fw->size || size > fw->size - offset) {
+ dev_warn(dev, "bad firmware request (offs = %u, size = %u)\n",
+ offset, size);
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ if (!gb_operation_response_alloc(op, sizeof(*firmware_response) + size,
+ GFP_KERNEL)) {
+ dev_err(dev, "%s: error allocating response\n", __func__);
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ firmware_response = op->response->payload;
+ memcpy(firmware_response->data, fw->data + offset, size);
+
+ dev_dbg(dev, "responding with firmware (offs = %u, size = %u)\n",
+ offset, size);
+
+unlock:
+ mutex_unlock(&bootrom->mutex);
+
+queue_work:
+ /* Refresh timeout */
+ if (!ret && (offset + size == fw->size))
+ next_request = NEXT_REQ_READY_TO_BOOT;
+ else
+ next_request = NEXT_REQ_GET_FIRMWARE;
+
+ gb_bootrom_set_timeout(bootrom, next_request, NEXT_REQ_TIMEOUT_MS);
+
+ return ret;
+}
+
+static int gb_bootrom_ready_to_boot(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_bootrom *bootrom = gb_connection_get_data(connection);
+ struct gb_bootrom_ready_to_boot_request *rtb_request;
+ struct device *dev = &connection->bundle->dev;
+ u8 status;
+ int ret = 0;
+
+ /* Disable timeouts */
+ gb_bootrom_cancel_timeout(bootrom);
+
+ if (op->request->payload_size != sizeof(*rtb_request)) {
+ dev_err(dev, "%s: Illegal size of ready to boot request (%zu %zu)\n",
+ __func__, op->request->payload_size,
+ sizeof(*rtb_request));
+ ret = -EINVAL;
+ goto queue_work;
+ }
+
+ rtb_request = op->request->payload;
+ status = rtb_request->status;
+
+ /* Return error if the blob was invalid */
+ if (status == GB_BOOTROM_BOOT_STATUS_INVALID) {
+ ret = -EINVAL;
+ goto queue_work;
+ }
+
+ /*
+ * XXX Should we return error for insecure firmware?
+ */
+ dev_dbg(dev, "ready to boot: 0x%x, 0\n", status);
+
+queue_work:
+ /*
+ * Refresh timeout, the Interface shall load the new personality and
+ * send a new hotplug request, which shall get rid of the bootrom
+ * connection. As that can take some time, increase the timeout a bit.
+ */
+ gb_bootrom_set_timeout(bootrom, NEXT_REQ_MODE_SWITCH,
+ MODE_SWITCH_TIMEOUT_MS);
+
+ return ret;
+}
+
+static int gb_bootrom_request_handler(struct gb_operation *op)
+{
+ u8 type = op->type;
+
+ switch (type) {
+ case GB_BOOTROM_TYPE_FIRMWARE_SIZE:
+ return gb_bootrom_firmware_size_request(op);
+ case GB_BOOTROM_TYPE_GET_FIRMWARE:
+ return gb_bootrom_get_firmware(op);
+ case GB_BOOTROM_TYPE_READY_TO_BOOT:
+ return gb_bootrom_ready_to_boot(op);
+ default:
+ dev_err(&op->connection->bundle->dev,
+ "unsupported request: %u\n", type);
+ return -EINVAL;
+ }
+}
+
+static int gb_bootrom_get_version(struct gb_bootrom *bootrom)
+{
+ struct gb_bundle *bundle = bootrom->connection->bundle;
+ struct gb_bootrom_version_request request;
+ struct gb_bootrom_version_response response;
+ int ret;
+
+ request.major = GB_BOOTROM_VERSION_MAJOR;
+ request.minor = GB_BOOTROM_VERSION_MINOR;
+
+ ret = gb_operation_sync(bootrom->connection,
+ GB_BOOTROM_TYPE_VERSION,
+ &request, sizeof(request), &response,
+ sizeof(response));
+ if (ret) {
+ dev_err(&bundle->dev,
+ "failed to get protocol version: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (response.major > request.major) {
+ dev_err(&bundle->dev,
+ "unsupported major protocol version (%u > %u)\n",
+ response.major, request.major);
+ return -ENOTSUPP;
+ }
+
+ bootrom->protocol_major = response.major;
+ bootrom->protocol_minor = response.minor;
+
+ dev_dbg(&bundle->dev, "%s - %u.%u\n", __func__, response.major,
+ response.minor);
+
+ return 0;
+}
+
+static int gb_bootrom_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_bootrom *bootrom;
+ int ret;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_BOOTROM)
+ return -ENODEV;
+
+ bootrom = kzalloc(sizeof(*bootrom), GFP_KERNEL);
+ if (!bootrom)
+ return -ENOMEM;
+
+ connection = gb_connection_create(bundle,
+ le16_to_cpu(cport_desc->id),
+ gb_bootrom_request_handler);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ goto err_free_bootrom;
+ }
+
+ gb_connection_set_data(connection, bootrom);
+
+ bootrom->connection = connection;
+
+ mutex_init(&bootrom->mutex);
+ INIT_DELAYED_WORK(&bootrom->dwork, gb_bootrom_timedout);
+ greybus_set_drvdata(bundle, bootrom);
+
+ ret = gb_connection_enable_tx(connection);
+ if (ret)
+ goto err_connection_destroy;
+
+ ret = gb_bootrom_get_version(bootrom);
+ if (ret)
+ goto err_connection_disable;
+
+ bootrom_es2_fixup_vid_pid(bootrom);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto err_connection_disable;
+
+ /* Refresh timeout */
+ gb_bootrom_set_timeout(bootrom, NEXT_REQ_FIRMWARE_SIZE,
+ NEXT_REQ_TIMEOUT_MS);
+
+ /* Tell bootrom we're ready. */
+ ret = gb_operation_sync(connection, GB_BOOTROM_TYPE_AP_READY, NULL, 0,
+ NULL, 0);
+ if (ret) {
+ dev_err(&connection->bundle->dev,
+ "failed to send AP READY: %d\n", ret);
+ goto err_cancel_timeout;
+ }
+
+ dev_dbg(&bundle->dev, "AP_READY sent\n");
+
+ return 0;
+
+err_cancel_timeout:
+ gb_bootrom_cancel_timeout(bootrom);
+err_connection_disable:
+ gb_connection_disable(connection);
+err_connection_destroy:
+ gb_connection_destroy(connection);
+err_free_bootrom:
+ kfree(bootrom);
+
+ return ret;
+}
+
+static void gb_bootrom_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_bootrom *bootrom = greybus_get_drvdata(bundle);
+
+ dev_dbg(&bundle->dev, "%s\n", __func__);
+
+ gb_connection_disable(bootrom->connection);
+
+ /* Disable timeouts */
+ gb_bootrom_cancel_timeout(bootrom);
+
+ /*
+ * Release firmware:
+ *
+ * As the connection and the delayed work are already disabled, we don't
+ * need to lock access to bootrom->fw here.
+ */
+ free_firmware(bootrom);
+
+ gb_connection_destroy(bootrom->connection);
+ kfree(bootrom);
+}
+
+static const struct greybus_bundle_id gb_bootrom_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_BOOTROM) },
+ { }
+};
+
+static struct greybus_driver gb_bootrom_driver = {
+ .name = "bootrom",
+ .probe = gb_bootrom_probe,
+ .disconnect = gb_bootrom_disconnect,
+ .id_table = gb_bootrom_id_table,
+};
+
+module_greybus_driver(gb_bootrom_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/camera.c b/drivers/staging/greybus/camera.c
new file mode 100644
index 0000000000..cdbb42cd41
--- /dev/null
+++ b/drivers/staging/greybus/camera.c
@@ -0,0 +1,1382 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Camera protocol driver.
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/greybus.h>
+
+#include "gb-camera.h"
+#include "greybus_protocols.h"
+
+enum gb_camera_debugs_buffer_id {
+ GB_CAMERA_DEBUGFS_BUFFER_CAPABILITIES,
+ GB_CAMERA_DEBUGFS_BUFFER_STREAMS,
+ GB_CAMERA_DEBUGFS_BUFFER_CAPTURE,
+ GB_CAMERA_DEBUGFS_BUFFER_FLUSH,
+ GB_CAMERA_DEBUGFS_BUFFER_MAX,
+};
+
+struct gb_camera_debugfs_buffer {
+ char data[PAGE_SIZE];
+ size_t length;
+};
+
+enum gb_camera_state {
+ GB_CAMERA_STATE_UNCONFIGURED,
+ GB_CAMERA_STATE_CONFIGURED,
+};
+
+/**
+ * struct gb_camera - A Greybus Camera Device
+ * @connection: the greybus connection for camera management
+ * @data_connection: the greybus connection for camera data
+ * @data_cport_id: the data CPort ID on the module side
+ * @mutex: protects the connection and state fields
+ * @state: the current module state
+ * @debugfs: debugfs entries for camera protocol operations testing
+ * @module: Greybus camera module registered to HOST processor.
+ */
+struct gb_camera {
+ struct gb_bundle *bundle;
+ struct gb_connection *connection;
+ struct gb_connection *data_connection;
+ u16 data_cport_id;
+
+ struct mutex mutex;
+ enum gb_camera_state state;
+
+ struct {
+ struct dentry *root;
+ struct gb_camera_debugfs_buffer *buffers;
+ } debugfs;
+
+ struct gb_camera_module module;
+};
+
+struct gb_camera_stream_config {
+ unsigned int width;
+ unsigned int height;
+ unsigned int format;
+ unsigned int vc;
+ unsigned int dt[2];
+ unsigned int max_size;
+};
+
+struct gb_camera_fmt_info {
+ enum v4l2_mbus_pixelcode mbus_code;
+ unsigned int gb_format;
+ unsigned int bpp;
+};
+
+/* GB format to media code map */
+static const struct gb_camera_fmt_info gb_fmt_info[] = {
+ {
+ .mbus_code = V4L2_MBUS_FMT_UYVY8_1X16,
+ .gb_format = 0x01,
+ .bpp = 16,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_NV12_1x8,
+ .gb_format = 0x12,
+ .bpp = 12,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_NV21_1x8,
+ .gb_format = 0x13,
+ .bpp = 12,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_YU12_1x8,
+ .gb_format = 0x16,
+ .bpp = 12,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_YV12_1x8,
+ .gb_format = 0x17,
+ .bpp = 12,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_JPEG_1X8,
+ .gb_format = 0x40,
+ .bpp = 0,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_GB_CAM_METADATA_1X8,
+ .gb_format = 0x41,
+ .bpp = 0,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_GB_CAM_DEBUG_DATA_1X8,
+ .gb_format = 0x42,
+ .bpp = 0,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_SBGGR10_1X10,
+ .gb_format = 0x80,
+ .bpp = 10,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_SGBRG10_1X10,
+ .gb_format = 0x81,
+ .bpp = 10,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_SGRBG10_1X10,
+ .gb_format = 0x82,
+ .bpp = 10,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_SRGGB10_1X10,
+ .gb_format = 0x83,
+ .bpp = 10,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_SBGGR12_1X12,
+ .gb_format = 0x84,
+ .bpp = 12,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_SGBRG12_1X12,
+ .gb_format = 0x85,
+ .bpp = 12,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_SGRBG12_1X12,
+ .gb_format = 0x86,
+ .bpp = 12,
+ },
+ {
+ .mbus_code = V4L2_MBUS_FMT_SRGGB12_1X12,
+ .gb_format = 0x87,
+ .bpp = 12,
+ },
+};
+
+static const struct gb_camera_fmt_info *gb_camera_get_format_info(u16 gb_fmt)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(gb_fmt_info); i++) {
+ if (gb_fmt_info[i].gb_format == gb_fmt)
+ return &gb_fmt_info[i];
+ }
+
+ return NULL;
+}
+
+#define ES2_APB_CDSI0_CPORT 16
+#define ES2_APB_CDSI1_CPORT 17
+
+#define GB_CAMERA_MAX_SETTINGS_SIZE 8192
+
+#define gcam_dbg(gcam, format...) dev_dbg(&gcam->bundle->dev, format)
+#define gcam_info(gcam, format...) dev_info(&gcam->bundle->dev, format)
+#define gcam_err(gcam, format...) dev_err(&gcam->bundle->dev, format)
+
+static int gb_camera_operation_sync_flags(struct gb_connection *connection,
+ int type, unsigned int flags,
+ void *request, size_t request_size,
+ void *response, size_t *response_size)
+{
+ struct gb_operation *operation;
+ int ret;
+
+ operation = gb_operation_create_flags(connection, type, request_size,
+ *response_size, flags,
+ GFP_KERNEL);
+ if (!operation)
+ return -ENOMEM;
+
+ if (request_size)
+ memcpy(operation->request->payload, request, request_size);
+
+ ret = gb_operation_request_send_sync(operation);
+ if (ret) {
+ dev_err(&connection->hd->dev,
+ "%s: synchronous operation of type 0x%02x failed: %d\n",
+ connection->name, type, ret);
+ } else {
+ *response_size = operation->response->payload_size;
+
+ if (operation->response->payload_size)
+ memcpy(response, operation->response->payload,
+ operation->response->payload_size);
+ }
+
+ gb_operation_put(operation);
+
+ return ret;
+}
+
+static int gb_camera_get_max_pkt_size(struct gb_camera *gcam,
+ struct gb_camera_configure_streams_response *resp)
+{
+ unsigned int max_pkt_size = 0;
+ unsigned int i;
+
+ for (i = 0; i < resp->num_streams; i++) {
+ struct gb_camera_stream_config_response *cfg = &resp->config[i];
+ const struct gb_camera_fmt_info *fmt_info;
+ unsigned int pkt_size;
+
+ fmt_info = gb_camera_get_format_info(cfg->format);
+ if (!fmt_info) {
+ gcam_err(gcam, "unsupported greybus image format: %d\n",
+ cfg->format);
+ return -EIO;
+ }
+
+ if (fmt_info->bpp == 0) {
+ pkt_size = le32_to_cpu(cfg->max_pkt_size);
+
+ if (pkt_size == 0) {
+ gcam_err(gcam,
+ "Stream %u: invalid zero maximum packet size\n",
+ i);
+ return -EIO;
+ }
+ } else {
+ pkt_size = le16_to_cpu(cfg->width) * fmt_info->bpp / 8;
+
+ if (pkt_size != le32_to_cpu(cfg->max_pkt_size)) {
+ gcam_err(gcam,
+ "Stream %u: maximum packet size mismatch (%u/%u)\n",
+ i, pkt_size, cfg->max_pkt_size);
+ return -EIO;
+ }
+ }
+
+ max_pkt_size = max(pkt_size, max_pkt_size);
+ }
+
+ return max_pkt_size;
+}
+
+/*
+ * Validate the stream configuration response verifying padding is correctly
+ * set and the returned number of streams is supported
+ */
+static const int gb_camera_configure_streams_validate_response(
+ struct gb_camera *gcam,
+ struct gb_camera_configure_streams_response *resp,
+ unsigned int nstreams)
+{
+ unsigned int i;
+
+ /* Validate the returned response structure */
+ if (resp->padding[0] || resp->padding[1]) {
+ gcam_err(gcam, "response padding != 0\n");
+ return -EIO;
+ }
+
+ if (resp->num_streams > nstreams) {
+ gcam_err(gcam, "got #streams %u > request %u\n",
+ resp->num_streams, nstreams);
+ return -EIO;
+ }
+
+ for (i = 0; i < resp->num_streams; i++) {
+ struct gb_camera_stream_config_response *cfg = &resp->config[i];
+
+ if (cfg->padding) {
+ gcam_err(gcam, "stream #%u padding != 0\n", i);
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware Configuration
+ */
+
+static int gb_camera_set_intf_power_mode(struct gb_camera *gcam, u8 intf_id,
+ bool hs)
+{
+ struct gb_svc *svc = gcam->connection->hd->svc;
+ int ret;
+
+ if (hs)
+ ret = gb_svc_intf_set_power_mode(svc, intf_id,
+ GB_SVC_UNIPRO_HS_SERIES_A,
+ GB_SVC_UNIPRO_FAST_MODE, 2, 2,
+ GB_SVC_SMALL_AMPLITUDE,
+ GB_SVC_NO_DE_EMPHASIS,
+ GB_SVC_UNIPRO_FAST_MODE, 2, 2,
+ GB_SVC_PWRM_RXTERMINATION |
+ GB_SVC_PWRM_TXTERMINATION, 0,
+ NULL, NULL);
+ else
+ ret = gb_svc_intf_set_power_mode(svc, intf_id,
+ GB_SVC_UNIPRO_HS_SERIES_A,
+ GB_SVC_UNIPRO_SLOW_AUTO_MODE,
+ 2, 1,
+ GB_SVC_SMALL_AMPLITUDE,
+ GB_SVC_NO_DE_EMPHASIS,
+ GB_SVC_UNIPRO_SLOW_AUTO_MODE,
+ 2, 1,
+ 0, 0,
+ NULL, NULL);
+
+ return ret;
+}
+
+static int gb_camera_set_power_mode(struct gb_camera *gcam, bool hs)
+{
+ struct gb_interface *intf = gcam->connection->intf;
+ struct gb_svc *svc = gcam->connection->hd->svc;
+ int ret;
+
+ ret = gb_camera_set_intf_power_mode(gcam, intf->interface_id, hs);
+ if (ret < 0) {
+ gcam_err(gcam, "failed to set module interface to %s (%d)\n",
+ hs ? "HS" : "PWM", ret);
+ return ret;
+ }
+
+ ret = gb_camera_set_intf_power_mode(gcam, svc->ap_intf_id, hs);
+ if (ret < 0) {
+ gb_camera_set_intf_power_mode(gcam, intf->interface_id, !hs);
+ gcam_err(gcam, "failed to set AP interface to %s (%d)\n",
+ hs ? "HS" : "PWM", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct ap_csi_config_request {
+ __u8 csi_id;
+ __u8 flags;
+#define GB_CAMERA_CSI_FLAG_CLOCK_CONTINUOUS 0x01
+ __u8 num_lanes;
+ __u8 padding;
+ __le32 csi_clk_freq;
+ __le32 max_pkt_size;
+} __packed;
+
+/*
+ * TODO: Compute the number of lanes dynamically based on bandwidth
+ * requirements.
+ */
+#define GB_CAMERA_CSI_NUM_DATA_LANES 4
+
+#define GB_CAMERA_CSI_CLK_FREQ_MAX 999000000U
+#define GB_CAMERA_CSI_CLK_FREQ_MIN 100000000U
+#define GB_CAMERA_CSI_CLK_FREQ_MARGIN 150000000U
+
+static int gb_camera_setup_data_connection(struct gb_camera *gcam,
+ struct gb_camera_configure_streams_response *resp,
+ struct gb_camera_csi_params *csi_params)
+{
+ struct ap_csi_config_request csi_cfg;
+ struct gb_connection *conn;
+ unsigned int clk_freq;
+ int ret;
+
+ /*
+ * Create the data connection between the camera module data CPort and
+ * APB CDSI1. The CDSI1 CPort ID is hardcoded by the ES2 bridge.
+ */
+ conn = gb_connection_create_offloaded(gcam->bundle, gcam->data_cport_id,
+ GB_CONNECTION_FLAG_NO_FLOWCTRL |
+ GB_CONNECTION_FLAG_CDSI1);
+ if (IS_ERR(conn))
+ return PTR_ERR(conn);
+
+ gcam->data_connection = conn;
+ gb_connection_set_data(conn, gcam);
+
+ ret = gb_connection_enable(conn);
+ if (ret)
+ goto error_conn_destroy;
+
+ /* Set the UniPro link to high speed mode. */
+ ret = gb_camera_set_power_mode(gcam, true);
+ if (ret < 0)
+ goto error_conn_disable;
+
+ /*
+ * Configure the APB-A CSI-2 transmitter.
+ *
+ * Hardcode the number of lanes to 4 and compute the bus clock frequency
+ * based on the module bandwidth requirements with a safety margin.
+ */
+ memset(&csi_cfg, 0, sizeof(csi_cfg));
+ csi_cfg.csi_id = 1;
+ csi_cfg.flags = 0;
+ csi_cfg.num_lanes = GB_CAMERA_CSI_NUM_DATA_LANES;
+
+ clk_freq = resp->data_rate / 2 / GB_CAMERA_CSI_NUM_DATA_LANES;
+ clk_freq = clamp(clk_freq + GB_CAMERA_CSI_CLK_FREQ_MARGIN,
+ GB_CAMERA_CSI_CLK_FREQ_MIN,
+ GB_CAMERA_CSI_CLK_FREQ_MAX);
+ csi_cfg.csi_clk_freq = clk_freq;
+
+ ret = gb_camera_get_max_pkt_size(gcam, resp);
+ if (ret < 0) {
+ ret = -EIO;
+ goto error_power;
+ }
+ csi_cfg.max_pkt_size = ret;
+
+ ret = gb_hd_output(gcam->connection->hd, &csi_cfg,
+ sizeof(csi_cfg),
+ GB_APB_REQUEST_CSI_TX_CONTROL, false);
+ if (ret < 0) {
+ gcam_err(gcam, "failed to start the CSI transmitter\n");
+ goto error_power;
+ }
+
+ if (csi_params) {
+ csi_params->clk_freq = csi_cfg.csi_clk_freq;
+ csi_params->num_lanes = csi_cfg.num_lanes;
+ }
+
+ return 0;
+
+error_power:
+ gb_camera_set_power_mode(gcam, false);
+error_conn_disable:
+ gb_connection_disable(gcam->data_connection);
+error_conn_destroy:
+ gb_connection_destroy(gcam->data_connection);
+ gcam->data_connection = NULL;
+ return ret;
+}
+
+static void gb_camera_teardown_data_connection(struct gb_camera *gcam)
+{
+ struct ap_csi_config_request csi_cfg;
+ int ret;
+
+ /* Stop the APB1 CSI transmitter. */
+ memset(&csi_cfg, 0, sizeof(csi_cfg));
+ csi_cfg.csi_id = 1;
+
+ ret = gb_hd_output(gcam->connection->hd, &csi_cfg,
+ sizeof(csi_cfg),
+ GB_APB_REQUEST_CSI_TX_CONTROL, false);
+
+ if (ret < 0)
+ gcam_err(gcam, "failed to stop the CSI transmitter\n");
+
+ /* Set the UniPro link to low speed mode. */
+ gb_camera_set_power_mode(gcam, false);
+
+ /* Destroy the data connection. */
+ gb_connection_disable(gcam->data_connection);
+ gb_connection_destroy(gcam->data_connection);
+ gcam->data_connection = NULL;
+}
+
+/* -----------------------------------------------------------------------------
+ * Camera Protocol Operations
+ */
+
+static int gb_camera_capabilities(struct gb_camera *gcam,
+ u8 *capabilities, size_t *size)
+{
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(gcam->bundle);
+ if (ret)
+ return ret;
+
+ mutex_lock(&gcam->mutex);
+
+ if (!gcam->connection) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = gb_camera_operation_sync_flags(gcam->connection,
+ GB_CAMERA_TYPE_CAPABILITIES,
+ GB_OPERATION_FLAG_SHORT_RESPONSE,
+ NULL, 0,
+ (void *)capabilities, size);
+ if (ret)
+ gcam_err(gcam, "failed to retrieve capabilities: %d\n", ret);
+
+done:
+ mutex_unlock(&gcam->mutex);
+
+ gb_pm_runtime_put_autosuspend(gcam->bundle);
+
+ return ret;
+}
+
+static int gb_camera_configure_streams(struct gb_camera *gcam,
+ unsigned int *num_streams,
+ unsigned int *flags,
+ struct gb_camera_stream_config *streams,
+ struct gb_camera_csi_params *csi_params)
+{
+ struct gb_camera_configure_streams_request *req;
+ struct gb_camera_configure_streams_response *resp;
+ unsigned int nstreams = *num_streams;
+ unsigned int i;
+ size_t req_size;
+ size_t resp_size;
+ int ret;
+
+ if (nstreams > GB_CAMERA_MAX_STREAMS)
+ return -EINVAL;
+
+ req_size = sizeof(*req) + nstreams * sizeof(req->config[0]);
+ resp_size = sizeof(*resp) + nstreams * sizeof(resp->config[0]);
+
+ req = kmalloc(req_size, GFP_KERNEL);
+ resp = kmalloc(resp_size, GFP_KERNEL);
+ if (!req || !resp) {
+ kfree(req);
+ kfree(resp);
+ return -ENOMEM;
+ }
+
+ req->num_streams = nstreams;
+ req->flags = *flags;
+ req->padding = 0;
+
+ for (i = 0; i < nstreams; ++i) {
+ struct gb_camera_stream_config_request *cfg = &req->config[i];
+
+ cfg->width = cpu_to_le16(streams[i].width);
+ cfg->height = cpu_to_le16(streams[i].height);
+ cfg->format = cpu_to_le16(streams[i].format);
+ cfg->padding = 0;
+ }
+
+ mutex_lock(&gcam->mutex);
+
+ ret = gb_pm_runtime_get_sync(gcam->bundle);
+ if (ret)
+ goto done_skip_pm_put;
+
+ if (!gcam->connection) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = gb_camera_operation_sync_flags(gcam->connection,
+ GB_CAMERA_TYPE_CONFIGURE_STREAMS,
+ GB_OPERATION_FLAG_SHORT_RESPONSE,
+ req, req_size,
+ resp, &resp_size);
+ if (ret < 0)
+ goto done;
+
+ ret = gb_camera_configure_streams_validate_response(gcam, resp,
+ nstreams);
+ if (ret < 0)
+ goto done;
+
+ *flags = resp->flags;
+ *num_streams = resp->num_streams;
+
+ for (i = 0; i < resp->num_streams; ++i) {
+ struct gb_camera_stream_config_response *cfg = &resp->config[i];
+
+ streams[i].width = le16_to_cpu(cfg->width);
+ streams[i].height = le16_to_cpu(cfg->height);
+ streams[i].format = le16_to_cpu(cfg->format);
+ streams[i].vc = cfg->virtual_channel;
+ streams[i].dt[0] = cfg->data_type[0];
+ streams[i].dt[1] = cfg->data_type[1];
+ streams[i].max_size = le32_to_cpu(cfg->max_size);
+ }
+
+ if ((resp->flags & GB_CAMERA_CONFIGURE_STREAMS_ADJUSTED) ||
+ (req->flags & GB_CAMERA_CONFIGURE_STREAMS_TEST_ONLY))
+ goto done;
+
+ if (gcam->state == GB_CAMERA_STATE_CONFIGURED) {
+ gb_camera_teardown_data_connection(gcam);
+ gcam->state = GB_CAMERA_STATE_UNCONFIGURED;
+
+ /*
+ * When unconfiguring streams release the PM runtime reference
+ * that was acquired when streams were configured. The bundle
+ * won't be suspended until the PM runtime reference acquired at
+ * the beginning of this function gets released right before
+ * returning.
+ */
+ gb_pm_runtime_put_noidle(gcam->bundle);
+ }
+
+ if (resp->num_streams == 0)
+ goto done;
+
+ /*
+ * Make sure the bundle won't be suspended until streams get
+ * unconfigured after the stream is configured successfully
+ */
+ gb_pm_runtime_get_noresume(gcam->bundle);
+
+ /* Setup CSI-2 connection from APB-A to AP */
+ ret = gb_camera_setup_data_connection(gcam, resp, csi_params);
+ if (ret < 0) {
+ memset(req, 0, sizeof(*req));
+ gb_operation_sync(gcam->connection,
+ GB_CAMERA_TYPE_CONFIGURE_STREAMS,
+ req, sizeof(*req),
+ resp, sizeof(*resp));
+ *flags = 0;
+ *num_streams = 0;
+ gb_pm_runtime_put_noidle(gcam->bundle);
+ goto done;
+ }
+
+ gcam->state = GB_CAMERA_STATE_CONFIGURED;
+
+done:
+ gb_pm_runtime_put_autosuspend(gcam->bundle);
+
+done_skip_pm_put:
+ mutex_unlock(&gcam->mutex);
+ kfree(req);
+ kfree(resp);
+ return ret;
+}
+
+static int gb_camera_capture(struct gb_camera *gcam, u32 request_id,
+ unsigned int streams, unsigned int num_frames,
+ size_t settings_size, const void *settings)
+{
+ struct gb_camera_capture_request *req;
+ size_t req_size;
+ int ret;
+
+ if (settings_size > GB_CAMERA_MAX_SETTINGS_SIZE)
+ return -EINVAL;
+
+ req_size = sizeof(*req) + settings_size;
+ req = kmalloc(req_size, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req->request_id = cpu_to_le32(request_id);
+ req->streams = streams;
+ req->padding = 0;
+ req->num_frames = cpu_to_le16(num_frames);
+ memcpy(req->settings, settings, settings_size);
+
+ mutex_lock(&gcam->mutex);
+
+ if (!gcam->connection) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = gb_operation_sync(gcam->connection, GB_CAMERA_TYPE_CAPTURE,
+ req, req_size, NULL, 0);
+done:
+ mutex_unlock(&gcam->mutex);
+
+ kfree(req);
+
+ return ret;
+}
+
+static int gb_camera_flush(struct gb_camera *gcam, u32 *request_id)
+{
+ struct gb_camera_flush_response resp;
+ int ret;
+
+ mutex_lock(&gcam->mutex);
+
+ if (!gcam->connection) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = gb_operation_sync(gcam->connection, GB_CAMERA_TYPE_FLUSH, NULL, 0,
+ &resp, sizeof(resp));
+
+ if (ret < 0)
+ goto done;
+
+ if (request_id)
+ *request_id = le32_to_cpu(resp.request_id);
+
+done:
+ mutex_unlock(&gcam->mutex);
+
+ return ret;
+}
+
+static int gb_camera_request_handler(struct gb_operation *op)
+{
+ struct gb_camera *gcam = gb_connection_get_data(op->connection);
+ struct gb_camera_metadata_request *payload;
+ struct gb_message *request;
+
+ if (op->type != GB_CAMERA_TYPE_METADATA) {
+ gcam_err(gcam, "Unsupported unsolicited event: %u\n", op->type);
+ return -EINVAL;
+ }
+
+ request = op->request;
+
+ if (request->payload_size < sizeof(*payload)) {
+ gcam_err(gcam, "Wrong event size received (%zu < %zu)\n",
+ request->payload_size, sizeof(*payload));
+ return -EINVAL;
+ }
+
+ payload = request->payload;
+
+ gcam_dbg(gcam, "received metadata for request %u, frame %u, stream %u\n",
+ payload->request_id, payload->frame_number, payload->stream);
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Interface with HOST gmp camera.
+ */
+static unsigned int gb_camera_mbus_to_gb(enum v4l2_mbus_pixelcode mbus_code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(gb_fmt_info); i++) {
+ if (gb_fmt_info[i].mbus_code == mbus_code)
+ return gb_fmt_info[i].gb_format;
+ }
+ return gb_fmt_info[0].gb_format;
+}
+
+static enum v4l2_mbus_pixelcode gb_camera_gb_to_mbus(u16 gb_fmt)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(gb_fmt_info); i++) {
+ if (gb_fmt_info[i].gb_format == gb_fmt)
+ return gb_fmt_info[i].mbus_code;
+ }
+ return gb_fmt_info[0].mbus_code;
+}
+
+static ssize_t gb_camera_op_capabilities(void *priv, char *data, size_t len)
+{
+ struct gb_camera *gcam = priv;
+ size_t capabilities_len = len;
+ int ret;
+
+ ret = gb_camera_capabilities(gcam, data, &capabilities_len);
+ if (ret)
+ return ret;
+
+ return capabilities_len;
+}
+
+static int gb_camera_op_configure_streams(void *priv, unsigned int *nstreams,
+ unsigned int *flags, struct gb_camera_stream *streams,
+ struct gb_camera_csi_params *csi_params)
+{
+ struct gb_camera *gcam = priv;
+ struct gb_camera_stream_config *gb_streams;
+ unsigned int gb_flags = 0;
+ unsigned int gb_nstreams = *nstreams;
+ unsigned int i;
+ int ret;
+
+ if (gb_nstreams > GB_CAMERA_MAX_STREAMS)
+ return -EINVAL;
+
+ gb_streams = kcalloc(gb_nstreams, sizeof(*gb_streams), GFP_KERNEL);
+ if (!gb_streams)
+ return -ENOMEM;
+
+ for (i = 0; i < gb_nstreams; i++) {
+ gb_streams[i].width = streams[i].width;
+ gb_streams[i].height = streams[i].height;
+ gb_streams[i].format =
+ gb_camera_mbus_to_gb(streams[i].pixel_code);
+ }
+
+ if (*flags & GB_CAMERA_IN_FLAG_TEST)
+ gb_flags |= GB_CAMERA_CONFIGURE_STREAMS_TEST_ONLY;
+
+ ret = gb_camera_configure_streams(gcam, &gb_nstreams,
+ &gb_flags, gb_streams, csi_params);
+ if (ret < 0)
+ goto done;
+ if (gb_nstreams > *nstreams) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ *flags = 0;
+ if (gb_flags & GB_CAMERA_CONFIGURE_STREAMS_ADJUSTED)
+ *flags |= GB_CAMERA_OUT_FLAG_ADJUSTED;
+
+ for (i = 0; i < gb_nstreams; i++) {
+ streams[i].width = gb_streams[i].width;
+ streams[i].height = gb_streams[i].height;
+ streams[i].vc = gb_streams[i].vc;
+ streams[i].dt[0] = gb_streams[i].dt[0];
+ streams[i].dt[1] = gb_streams[i].dt[1];
+ streams[i].max_size = gb_streams[i].max_size;
+ streams[i].pixel_code =
+ gb_camera_gb_to_mbus(gb_streams[i].format);
+ }
+ *nstreams = gb_nstreams;
+
+done:
+ kfree(gb_streams);
+ return ret;
+}
+
+static int gb_camera_op_capture(void *priv, u32 request_id,
+ unsigned int streams, unsigned int num_frames,
+ size_t settings_size, const void *settings)
+{
+ struct gb_camera *gcam = priv;
+
+ return gb_camera_capture(gcam, request_id, streams, num_frames,
+ settings_size, settings);
+}
+
+static int gb_camera_op_flush(void *priv, u32 *request_id)
+{
+ struct gb_camera *gcam = priv;
+
+ return gb_camera_flush(gcam, request_id);
+}
+
+static const struct gb_camera_ops gb_cam_ops = {
+ .capabilities = gb_camera_op_capabilities,
+ .configure_streams = gb_camera_op_configure_streams,
+ .capture = gb_camera_op_capture,
+ .flush = gb_camera_op_flush,
+};
+
+/* -----------------------------------------------------------------------------
+ * DebugFS
+ */
+
+static ssize_t gb_camera_debugfs_capabilities(struct gb_camera *gcam,
+ char *buf, size_t len)
+{
+ struct gb_camera_debugfs_buffer *buffer =
+ &gcam->debugfs.buffers[GB_CAMERA_DEBUGFS_BUFFER_CAPABILITIES];
+ size_t size = 1024;
+ unsigned int i;
+ u8 *caps;
+ int ret;
+
+ caps = kmalloc(size, GFP_KERNEL);
+ if (!caps)
+ return -ENOMEM;
+
+ ret = gb_camera_capabilities(gcam, caps, &size);
+ if (ret < 0)
+ goto done;
+
+ /*
+ * hex_dump_to_buffer() doesn't return the number of bytes dumped prior
+ * to v4.0, we need our own implementation :-(
+ */
+ buffer->length = 0;
+
+ for (i = 0; i < size; i += 16) {
+ unsigned int nbytes = min_t(unsigned int, size - i, 16);
+
+ buffer->length += sprintf(buffer->data + buffer->length,
+ "%*ph\n", nbytes, caps + i);
+ }
+
+done:
+ kfree(caps);
+ return ret;
+}
+
+static ssize_t gb_camera_debugfs_configure_streams(struct gb_camera *gcam,
+ char *buf, size_t len)
+{
+ struct gb_camera_debugfs_buffer *buffer =
+ &gcam->debugfs.buffers[GB_CAMERA_DEBUGFS_BUFFER_STREAMS];
+ struct gb_camera_stream_config *streams;
+ unsigned int nstreams;
+ unsigned int flags;
+ unsigned int i;
+ char *token;
+ int ret;
+
+ /* Retrieve number of streams to configure */
+ token = strsep(&buf, ";");
+ if (!token)
+ return -EINVAL;
+
+ ret = kstrtouint(token, 10, &nstreams);
+ if (ret < 0)
+ return ret;
+
+ if (nstreams > GB_CAMERA_MAX_STREAMS)
+ return -EINVAL;
+
+ token = strsep(&buf, ";");
+ if (!token)
+ return -EINVAL;
+
+ ret = kstrtouint(token, 10, &flags);
+ if (ret < 0)
+ return ret;
+
+ /* For each stream to configure parse width, height and format */
+ streams = kcalloc(nstreams, sizeof(*streams), GFP_KERNEL);
+ if (!streams)
+ return -ENOMEM;
+
+ for (i = 0; i < nstreams; ++i) {
+ struct gb_camera_stream_config *stream = &streams[i];
+
+ /* width */
+ token = strsep(&buf, ";");
+ if (!token) {
+ ret = -EINVAL;
+ goto done;
+ }
+ ret = kstrtouint(token, 10, &stream->width);
+ if (ret < 0)
+ goto done;
+
+ /* height */
+ token = strsep(&buf, ";");
+ if (!token)
+ goto done;
+
+ ret = kstrtouint(token, 10, &stream->height);
+ if (ret < 0)
+ goto done;
+
+ /* Image format code */
+ token = strsep(&buf, ";");
+ if (!token)
+ goto done;
+
+ ret = kstrtouint(token, 16, &stream->format);
+ if (ret < 0)
+ goto done;
+ }
+
+ ret = gb_camera_configure_streams(gcam, &nstreams, &flags, streams,
+ NULL);
+ if (ret < 0)
+ goto done;
+
+ buffer->length = sprintf(buffer->data, "%u;%u;", nstreams, flags);
+
+ for (i = 0; i < nstreams; ++i) {
+ struct gb_camera_stream_config *stream = &streams[i];
+
+ buffer->length += sprintf(buffer->data + buffer->length,
+ "%u;%u;%u;%u;%u;%u;%u;",
+ stream->width, stream->height,
+ stream->format, stream->vc,
+ stream->dt[0], stream->dt[1],
+ stream->max_size);
+ }
+
+ ret = len;
+
+done:
+ kfree(streams);
+ return ret;
+};
+
+static ssize_t gb_camera_debugfs_capture(struct gb_camera *gcam,
+ char *buf, size_t len)
+{
+ unsigned int request_id;
+ unsigned int streams_mask;
+ unsigned int num_frames;
+ char *token;
+ int ret;
+
+ /* Request id */
+ token = strsep(&buf, ";");
+ if (!token)
+ return -EINVAL;
+ ret = kstrtouint(token, 10, &request_id);
+ if (ret < 0)
+ return ret;
+
+ /* Stream mask */
+ token = strsep(&buf, ";");
+ if (!token)
+ return -EINVAL;
+ ret = kstrtouint(token, 16, &streams_mask);
+ if (ret < 0)
+ return ret;
+
+ /* number of frames */
+ token = strsep(&buf, ";");
+ if (!token)
+ return -EINVAL;
+ ret = kstrtouint(token, 10, &num_frames);
+ if (ret < 0)
+ return ret;
+
+ ret = gb_camera_capture(gcam, request_id, streams_mask, num_frames, 0,
+ NULL);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+
+static ssize_t gb_camera_debugfs_flush(struct gb_camera *gcam,
+ char *buf, size_t len)
+{
+ struct gb_camera_debugfs_buffer *buffer =
+ &gcam->debugfs.buffers[GB_CAMERA_DEBUGFS_BUFFER_FLUSH];
+ unsigned int req_id;
+ int ret;
+
+ ret = gb_camera_flush(gcam, &req_id);
+ if (ret < 0)
+ return ret;
+
+ buffer->length = sprintf(buffer->data, "%u", req_id);
+
+ return len;
+}
+
+struct gb_camera_debugfs_entry {
+ const char *name;
+ unsigned int mask;
+ unsigned int buffer;
+ ssize_t (*execute)(struct gb_camera *gcam, char *buf, size_t len);
+};
+
+static const struct gb_camera_debugfs_entry gb_camera_debugfs_entries[] = {
+ {
+ .name = "capabilities",
+ .mask = S_IFREG | 0444,
+ .buffer = GB_CAMERA_DEBUGFS_BUFFER_CAPABILITIES,
+ .execute = gb_camera_debugfs_capabilities,
+ }, {
+ .name = "configure_streams",
+ .mask = S_IFREG | 0666,
+ .buffer = GB_CAMERA_DEBUGFS_BUFFER_STREAMS,
+ .execute = gb_camera_debugfs_configure_streams,
+ }, {
+ .name = "capture",
+ .mask = S_IFREG | 0666,
+ .buffer = GB_CAMERA_DEBUGFS_BUFFER_CAPTURE,
+ .execute = gb_camera_debugfs_capture,
+ }, {
+ .name = "flush",
+ .mask = S_IFREG | 0666,
+ .buffer = GB_CAMERA_DEBUGFS_BUFFER_FLUSH,
+ .execute = gb_camera_debugfs_flush,
+ },
+};
+
+static ssize_t gb_camera_debugfs_read(struct file *file, char __user *buf,
+ size_t len, loff_t *offset)
+{
+ const struct gb_camera_debugfs_entry *op = file->private_data;
+ struct gb_camera *gcam = file_inode(file)->i_private;
+ struct gb_camera_debugfs_buffer *buffer;
+ ssize_t ret;
+
+ /* For read-only entries the operation is triggered by a read. */
+ if (!(op->mask & 0222)) {
+ ret = op->execute(gcam, NULL, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ buffer = &gcam->debugfs.buffers[op->buffer];
+
+ return simple_read_from_buffer(buf, len, offset, buffer->data,
+ buffer->length);
+}
+
+static ssize_t gb_camera_debugfs_write(struct file *file,
+ const char __user *buf, size_t len,
+ loff_t *offset)
+{
+ const struct gb_camera_debugfs_entry *op = file->private_data;
+ struct gb_camera *gcam = file_inode(file)->i_private;
+ ssize_t ret;
+ char *kbuf;
+
+ if (len > 1024)
+ return -EINVAL;
+
+ kbuf = memdup_user_nul(buf, len);
+ if (IS_ERR(kbuf))
+ return PTR_ERR(kbuf);
+
+ ret = op->execute(gcam, kbuf, len);
+
+done:
+ kfree(kbuf);
+ return ret;
+}
+
+static int gb_camera_debugfs_open(struct inode *inode, struct file *file)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(gb_camera_debugfs_entries); ++i) {
+ const struct gb_camera_debugfs_entry *entry =
+ &gb_camera_debugfs_entries[i];
+
+ if (!strcmp(file->f_path.dentry->d_iname, entry->name)) {
+ file->private_data = (void *)entry;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static const struct file_operations gb_camera_debugfs_ops = {
+ .open = gb_camera_debugfs_open,
+ .read = gb_camera_debugfs_read,
+ .write = gb_camera_debugfs_write,
+};
+
+static int gb_camera_debugfs_init(struct gb_camera *gcam)
+{
+ struct gb_connection *connection = gcam->connection;
+ char dirname[27];
+ unsigned int i;
+
+ /*
+ * Create root debugfs entry and a file entry for each camera operation.
+ */
+ snprintf(dirname, 27, "camera-%u.%u", connection->intf->interface_id,
+ gcam->bundle->id);
+
+ gcam->debugfs.root = debugfs_create_dir(dirname, gb_debugfs_get());
+
+ gcam->debugfs.buffers =
+ vmalloc(array_size(GB_CAMERA_DEBUGFS_BUFFER_MAX,
+ sizeof(*gcam->debugfs.buffers)));
+ if (!gcam->debugfs.buffers)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(gb_camera_debugfs_entries); ++i) {
+ const struct gb_camera_debugfs_entry *entry =
+ &gb_camera_debugfs_entries[i];
+
+ gcam->debugfs.buffers[i].length = 0;
+
+ debugfs_create_file(entry->name, entry->mask,
+ gcam->debugfs.root, gcam,
+ &gb_camera_debugfs_ops);
+ }
+
+ return 0;
+}
+
+static void gb_camera_debugfs_cleanup(struct gb_camera *gcam)
+{
+ debugfs_remove_recursive(gcam->debugfs.root);
+
+ vfree(gcam->debugfs.buffers);
+}
+
+/* -----------------------------------------------------------------------------
+ * Init & Cleanup
+ */
+
+static void gb_camera_cleanup(struct gb_camera *gcam)
+{
+ gb_camera_debugfs_cleanup(gcam);
+
+ mutex_lock(&gcam->mutex);
+ if (gcam->data_connection) {
+ gb_connection_disable(gcam->data_connection);
+ gb_connection_destroy(gcam->data_connection);
+ gcam->data_connection = NULL;
+ }
+
+ if (gcam->connection) {
+ gb_connection_disable(gcam->connection);
+ gb_connection_destroy(gcam->connection);
+ gcam->connection = NULL;
+ }
+ mutex_unlock(&gcam->mutex);
+}
+
+static void gb_camera_release_module(struct kref *ref)
+{
+ struct gb_camera_module *cam_mod =
+ container_of(ref, struct gb_camera_module, refcount);
+ kfree(cam_mod->priv);
+}
+
+static int gb_camera_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct gb_connection *conn;
+ struct gb_camera *gcam;
+ u16 mgmt_cport_id = 0;
+ u16 data_cport_id = 0;
+ unsigned int i;
+ int ret;
+
+ /*
+ * The camera bundle must contain exactly two CPorts, one for the
+ * camera management protocol and one for the camera data protocol.
+ */
+ if (bundle->num_cports != 2)
+ return -ENODEV;
+
+ for (i = 0; i < bundle->num_cports; ++i) {
+ struct greybus_descriptor_cport *desc = &bundle->cport_desc[i];
+
+ switch (desc->protocol_id) {
+ case GREYBUS_PROTOCOL_CAMERA_MGMT:
+ mgmt_cport_id = le16_to_cpu(desc->id);
+ break;
+ case GREYBUS_PROTOCOL_CAMERA_DATA:
+ data_cport_id = le16_to_cpu(desc->id);
+ break;
+ default:
+ return -ENODEV;
+ }
+ }
+
+ if (!mgmt_cport_id || !data_cport_id)
+ return -ENODEV;
+
+ gcam = kzalloc(sizeof(*gcam), GFP_KERNEL);
+ if (!gcam)
+ return -ENOMEM;
+
+ mutex_init(&gcam->mutex);
+
+ gcam->bundle = bundle;
+ gcam->state = GB_CAMERA_STATE_UNCONFIGURED;
+ gcam->data_cport_id = data_cport_id;
+
+ conn = gb_connection_create(bundle, mgmt_cport_id,
+ gb_camera_request_handler);
+ if (IS_ERR(conn)) {
+ ret = PTR_ERR(conn);
+ goto error;
+ }
+
+ gcam->connection = conn;
+ gb_connection_set_data(conn, gcam);
+
+ ret = gb_connection_enable(conn);
+ if (ret)
+ goto error;
+
+ ret = gb_camera_debugfs_init(gcam);
+ if (ret < 0)
+ goto error;
+
+ gcam->module.priv = gcam;
+ gcam->module.ops = &gb_cam_ops;
+ gcam->module.interface_id = gcam->connection->intf->interface_id;
+ gcam->module.release = gb_camera_release_module;
+ ret = gb_camera_register(&gcam->module);
+ if (ret < 0)
+ goto error;
+
+ greybus_set_drvdata(bundle, gcam);
+
+ gb_pm_runtime_put_autosuspend(gcam->bundle);
+
+ return 0;
+
+error:
+ gb_camera_cleanup(gcam);
+ kfree(gcam);
+ return ret;
+}
+
+static void gb_camera_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_camera *gcam = greybus_get_drvdata(bundle);
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ gb_pm_runtime_get_noresume(bundle);
+
+ gb_camera_cleanup(gcam);
+ gb_camera_unregister(&gcam->module);
+}
+
+static const struct greybus_bundle_id gb_camera_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_CAMERA) },
+ { },
+};
+
+#ifdef CONFIG_PM
+static int gb_camera_suspend(struct device *dev)
+{
+ struct gb_bundle *bundle = to_gb_bundle(dev);
+ struct gb_camera *gcam = greybus_get_drvdata(bundle);
+
+ if (gcam->data_connection)
+ gb_connection_disable(gcam->data_connection);
+
+ gb_connection_disable(gcam->connection);
+
+ return 0;
+}
+
+static int gb_camera_resume(struct device *dev)
+{
+ struct gb_bundle *bundle = to_gb_bundle(dev);
+ struct gb_camera *gcam = greybus_get_drvdata(bundle);
+ int ret;
+
+ ret = gb_connection_enable(gcam->connection);
+ if (ret) {
+ gcam_err(gcam, "failed to enable connection: %d\n", ret);
+ return ret;
+ }
+
+ if (gcam->data_connection) {
+ ret = gb_connection_enable(gcam->data_connection);
+ if (ret) {
+ gcam_err(gcam,
+ "failed to enable data connection: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_camera_pm_ops = {
+ SET_RUNTIME_PM_OPS(gb_camera_suspend, gb_camera_resume, NULL)
+};
+
+static struct greybus_driver gb_camera_driver = {
+ .name = "camera",
+ .probe = gb_camera_probe,
+ .disconnect = gb_camera_disconnect,
+ .id_table = gb_camera_id_table,
+ .driver.pm = &gb_camera_pm_ops,
+};
+
+module_greybus_driver(gb_camera_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/firmware.h b/drivers/staging/greybus/firmware.h
new file mode 100644
index 0000000000..5d2564462f
--- /dev/null
+++ b/drivers/staging/greybus/firmware.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Greybus Firmware Management Header
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ */
+
+#ifndef __FIRMWARE_H
+#define __FIRMWARE_H
+
+#include <linux/greybus.h>
+
+#define FW_NAME_PREFIX "gmp_"
+
+/*
+ * Length of the string in format: "FW_NAME_PREFIX""%08x_%08x_%08x_%08x_%s.tftf"
+ * (3 + 1 + 4 * (8 + 1) + 10 + 1 + 4 + 1)
+ */
+#define FW_NAME_SIZE 56
+
+/* Firmware Management Protocol specific functions */
+int fw_mgmt_init(void);
+void fw_mgmt_exit(void);
+struct gb_connection *to_fw_mgmt_connection(struct device *dev);
+int gb_fw_mgmt_request_handler(struct gb_operation *op);
+int gb_fw_mgmt_connection_init(struct gb_connection *connection);
+void gb_fw_mgmt_connection_exit(struct gb_connection *connection);
+
+/* Firmware Download Protocol specific functions */
+int gb_fw_download_request_handler(struct gb_operation *op);
+int gb_fw_download_connection_init(struct gb_connection *connection);
+void gb_fw_download_connection_exit(struct gb_connection *connection);
+
+/* CAP Protocol specific functions */
+int cap_init(void);
+void cap_exit(void);
+int gb_cap_connection_init(struct gb_connection *connection);
+void gb_cap_connection_exit(struct gb_connection *connection);
+
+#endif /* __FIRMWARE_H */
diff --git a/drivers/staging/greybus/fw-core.c b/drivers/staging/greybus/fw-core.c
new file mode 100644
index 0000000000..0fb15a6041
--- /dev/null
+++ b/drivers/staging/greybus/fw-core.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Firmware Core Bundle Driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/firmware.h>
+#include <linux/greybus.h>
+#include "firmware.h"
+#include "spilib.h"
+
+struct gb_fw_core {
+ struct gb_connection *download_connection;
+ struct gb_connection *mgmt_connection;
+ struct gb_connection *spi_connection;
+ struct gb_connection *cap_connection;
+};
+
+static struct spilib_ops *spilib_ops;
+
+struct gb_connection *to_fw_mgmt_connection(struct device *dev)
+{
+ struct gb_fw_core *fw_core = dev_get_drvdata(dev);
+
+ return fw_core->mgmt_connection;
+}
+
+static int gb_fw_spi_connection_init(struct gb_connection *connection)
+{
+ int ret;
+
+ if (!connection)
+ return 0;
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ return ret;
+
+ ret = gb_spilib_master_init(connection, &connection->bundle->dev,
+ spilib_ops);
+ if (ret) {
+ gb_connection_disable(connection);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void gb_fw_spi_connection_exit(struct gb_connection *connection)
+{
+ if (!connection)
+ return;
+
+ gb_spilib_master_exit(connection);
+ gb_connection_disable(connection);
+}
+
+static int gb_fw_core_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_fw_core *fw_core;
+ int ret, i;
+ u16 cport_id;
+ u8 protocol_id;
+
+ fw_core = kzalloc(sizeof(*fw_core), GFP_KERNEL);
+ if (!fw_core)
+ return -ENOMEM;
+
+ /* Parse CPorts and create connections */
+ for (i = 0; i < bundle->num_cports; i++) {
+ cport_desc = &bundle->cport_desc[i];
+ cport_id = le16_to_cpu(cport_desc->id);
+ protocol_id = cport_desc->protocol_id;
+
+ switch (protocol_id) {
+ case GREYBUS_PROTOCOL_FW_MANAGEMENT:
+ /* Disallow multiple Firmware Management CPorts */
+ if (fw_core->mgmt_connection) {
+ dev_err(&bundle->dev,
+ "multiple management CPorts found\n");
+ ret = -EINVAL;
+ goto err_destroy_connections;
+ }
+
+ connection = gb_connection_create(bundle, cport_id,
+ gb_fw_mgmt_request_handler);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ dev_err(&bundle->dev,
+ "failed to create management connection (%d)\n",
+ ret);
+ goto err_destroy_connections;
+ }
+
+ fw_core->mgmt_connection = connection;
+ break;
+ case GREYBUS_PROTOCOL_FW_DOWNLOAD:
+ /* Disallow multiple Firmware Download CPorts */
+ if (fw_core->download_connection) {
+ dev_err(&bundle->dev,
+ "multiple download CPorts found\n");
+ ret = -EINVAL;
+ goto err_destroy_connections;
+ }
+
+ connection = gb_connection_create(bundle, cport_id,
+ gb_fw_download_request_handler);
+ if (IS_ERR(connection)) {
+ dev_err(&bundle->dev, "failed to create download connection (%ld)\n",
+ PTR_ERR(connection));
+ } else {
+ fw_core->download_connection = connection;
+ }
+
+ break;
+ case GREYBUS_PROTOCOL_SPI:
+ /* Disallow multiple SPI CPorts */
+ if (fw_core->spi_connection) {
+ dev_err(&bundle->dev,
+ "multiple SPI CPorts found\n");
+ ret = -EINVAL;
+ goto err_destroy_connections;
+ }
+
+ connection = gb_connection_create(bundle, cport_id,
+ NULL);
+ if (IS_ERR(connection)) {
+ dev_err(&bundle->dev, "failed to create SPI connection (%ld)\n",
+ PTR_ERR(connection));
+ } else {
+ fw_core->spi_connection = connection;
+ }
+
+ break;
+ case GREYBUS_PROTOCOL_AUTHENTICATION:
+ /* Disallow multiple CAP CPorts */
+ if (fw_core->cap_connection) {
+ dev_err(&bundle->dev, "multiple Authentication CPorts found\n");
+ ret = -EINVAL;
+ goto err_destroy_connections;
+ }
+
+ connection = gb_connection_create(bundle, cport_id,
+ NULL);
+ if (IS_ERR(connection)) {
+ dev_err(&bundle->dev, "failed to create Authentication connection (%ld)\n",
+ PTR_ERR(connection));
+ } else {
+ fw_core->cap_connection = connection;
+ }
+
+ break;
+ default:
+ dev_err(&bundle->dev, "invalid protocol id (0x%02x)\n",
+ protocol_id);
+ ret = -EINVAL;
+ goto err_destroy_connections;
+ }
+ }
+
+ /* Firmware Management connection is mandatory */
+ if (!fw_core->mgmt_connection) {
+ dev_err(&bundle->dev, "missing management connection\n");
+ ret = -ENODEV;
+ goto err_destroy_connections;
+ }
+
+ ret = gb_fw_download_connection_init(fw_core->download_connection);
+ if (ret) {
+ /* We may still be able to work with the Interface */
+ dev_err(&bundle->dev, "failed to initialize firmware download connection, disable it (%d)\n",
+ ret);
+ gb_connection_destroy(fw_core->download_connection);
+ fw_core->download_connection = NULL;
+ }
+
+ ret = gb_fw_spi_connection_init(fw_core->spi_connection);
+ if (ret) {
+ /* We may still be able to work with the Interface */
+ dev_err(&bundle->dev, "failed to initialize SPI connection, disable it (%d)\n",
+ ret);
+ gb_connection_destroy(fw_core->spi_connection);
+ fw_core->spi_connection = NULL;
+ }
+
+ ret = gb_cap_connection_init(fw_core->cap_connection);
+ if (ret) {
+ /* We may still be able to work with the Interface */
+ dev_err(&bundle->dev, "failed to initialize CAP connection, disable it (%d)\n",
+ ret);
+ gb_connection_destroy(fw_core->cap_connection);
+ fw_core->cap_connection = NULL;
+ }
+
+ ret = gb_fw_mgmt_connection_init(fw_core->mgmt_connection);
+ if (ret) {
+ /* We may still be able to work with the Interface */
+ dev_err(&bundle->dev, "failed to initialize firmware management connection, disable it (%d)\n",
+ ret);
+ goto err_exit_connections;
+ }
+
+ greybus_set_drvdata(bundle, fw_core);
+
+ /* FIXME: Remove this after S2 Loader gets runtime PM support */
+ if (!(bundle->intf->quirks & GB_INTERFACE_QUIRK_NO_PM))
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+
+err_exit_connections:
+ gb_cap_connection_exit(fw_core->cap_connection);
+ gb_fw_spi_connection_exit(fw_core->spi_connection);
+ gb_fw_download_connection_exit(fw_core->download_connection);
+err_destroy_connections:
+ gb_connection_destroy(fw_core->mgmt_connection);
+ gb_connection_destroy(fw_core->cap_connection);
+ gb_connection_destroy(fw_core->spi_connection);
+ gb_connection_destroy(fw_core->download_connection);
+ kfree(fw_core);
+
+ return ret;
+}
+
+static void gb_fw_core_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_fw_core *fw_core = greybus_get_drvdata(bundle);
+ int ret;
+
+ /* FIXME: Remove this after S2 Loader gets runtime PM support */
+ if (!(bundle->intf->quirks & GB_INTERFACE_QUIRK_NO_PM)) {
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ gb_pm_runtime_get_noresume(bundle);
+ }
+
+ gb_fw_mgmt_connection_exit(fw_core->mgmt_connection);
+ gb_cap_connection_exit(fw_core->cap_connection);
+ gb_fw_spi_connection_exit(fw_core->spi_connection);
+ gb_fw_download_connection_exit(fw_core->download_connection);
+
+ gb_connection_destroy(fw_core->mgmt_connection);
+ gb_connection_destroy(fw_core->cap_connection);
+ gb_connection_destroy(fw_core->spi_connection);
+ gb_connection_destroy(fw_core->download_connection);
+
+ kfree(fw_core);
+}
+
+static const struct greybus_bundle_id gb_fw_core_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_FW_MANAGEMENT) },
+ { }
+};
+
+static struct greybus_driver gb_fw_core_driver = {
+ .name = "gb-firmware",
+ .probe = gb_fw_core_probe,
+ .disconnect = gb_fw_core_disconnect,
+ .id_table = gb_fw_core_id_table,
+};
+
+static int fw_core_init(void)
+{
+ int ret;
+
+ ret = fw_mgmt_init();
+ if (ret) {
+ pr_err("Failed to initialize fw-mgmt core (%d)\n", ret);
+ return ret;
+ }
+
+ ret = cap_init();
+ if (ret) {
+ pr_err("Failed to initialize component authentication core (%d)\n",
+ ret);
+ goto fw_mgmt_exit;
+ }
+
+ ret = greybus_register(&gb_fw_core_driver);
+ if (ret)
+ goto cap_exit;
+
+ return 0;
+
+cap_exit:
+ cap_exit();
+fw_mgmt_exit:
+ fw_mgmt_exit();
+
+ return ret;
+}
+module_init(fw_core_init);
+
+static void __exit fw_core_exit(void)
+{
+ greybus_deregister(&gb_fw_core_driver);
+ cap_exit();
+ fw_mgmt_exit();
+}
+module_exit(fw_core_exit);
+
+MODULE_ALIAS("greybus:firmware");
+MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>");
+MODULE_DESCRIPTION("Greybus Firmware Bundle Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/fw-download.c b/drivers/staging/greybus/fw-download.c
new file mode 100644
index 0000000000..543692c567
--- /dev/null
+++ b/drivers/staging/greybus/fw-download.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Firmware Download Protocol Driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ */
+
+#include <linux/firmware.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/greybus.h>
+#include "firmware.h"
+
+/* Estimated minimum buffer size, actual size can be smaller than this */
+#define MIN_FETCH_SIZE 512
+/* Timeout, in jiffies, within which fetch or release firmware must be called */
+#define NEXT_REQ_TIMEOUT_J msecs_to_jiffies(1000)
+
+struct fw_request {
+ u8 firmware_id;
+ bool disabled;
+ bool timedout;
+ char name[FW_NAME_SIZE];
+ const struct firmware *fw;
+ struct list_head node;
+
+ struct delayed_work dwork;
+ /* Timeout, in jiffies, within which the firmware shall download */
+ unsigned long release_timeout_j;
+ struct kref kref;
+ struct fw_download *fw_download;
+};
+
+struct fw_download {
+ struct device *parent;
+ struct gb_connection *connection;
+ struct list_head fw_requests;
+ struct ida id_map;
+ struct mutex mutex;
+};
+
+static void fw_req_release(struct kref *kref)
+{
+ struct fw_request *fw_req = container_of(kref, struct fw_request, kref);
+
+ dev_dbg(fw_req->fw_download->parent, "firmware %s released\n",
+ fw_req->name);
+
+ release_firmware(fw_req->fw);
+
+ /*
+ * The request timed out and the module may send a fetch-fw or
+ * release-fw request later. Lets block the id we allocated for this
+ * request, so that the AP doesn't refer to a later fw-request (with
+ * same firmware_id) for the old timedout fw-request.
+ *
+ * NOTE:
+ *
+ * This also means that after 255 timeouts we will fail to service new
+ * firmware downloads. But what else can we do in that case anyway? Lets
+ * just hope that it never happens.
+ */
+ if (!fw_req->timedout)
+ ida_simple_remove(&fw_req->fw_download->id_map,
+ fw_req->firmware_id);
+
+ kfree(fw_req);
+}
+
+/*
+ * Incoming requests are serialized for a connection, and the only race possible
+ * is between the timeout handler freeing this and an incoming request.
+ *
+ * The operations on the fw-request list are protected by the mutex and
+ * get_fw_req() increments the reference count before returning a fw_req pointer
+ * to the users.
+ *
+ * free_firmware() also takes the mutex while removing an entry from the list,
+ * it guarantees that every user of fw_req has taken a kref-reference by now and
+ * we wouldn't have any new users.
+ *
+ * Once the last user drops the reference, the fw_req structure is freed.
+ */
+static void put_fw_req(struct fw_request *fw_req)
+{
+ kref_put(&fw_req->kref, fw_req_release);
+}
+
+/* Caller must call put_fw_req() after using struct fw_request */
+static struct fw_request *get_fw_req(struct fw_download *fw_download,
+ u8 firmware_id)
+{
+ struct fw_request *fw_req;
+
+ mutex_lock(&fw_download->mutex);
+
+ list_for_each_entry(fw_req, &fw_download->fw_requests, node) {
+ if (fw_req->firmware_id == firmware_id) {
+ kref_get(&fw_req->kref);
+ goto unlock;
+ }
+ }
+
+ fw_req = NULL;
+
+unlock:
+ mutex_unlock(&fw_download->mutex);
+
+ return fw_req;
+}
+
+static void free_firmware(struct fw_download *fw_download,
+ struct fw_request *fw_req)
+{
+ /* Already disabled from timeout handlers */
+ if (fw_req->disabled)
+ return;
+
+ mutex_lock(&fw_download->mutex);
+ list_del(&fw_req->node);
+ mutex_unlock(&fw_download->mutex);
+
+ fw_req->disabled = true;
+ put_fw_req(fw_req);
+}
+
+static void fw_request_timedout(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct fw_request *fw_req = container_of(dwork,
+ struct fw_request, dwork);
+ struct fw_download *fw_download = fw_req->fw_download;
+
+ dev_err(fw_download->parent,
+ "Timed out waiting for fetch / release firmware requests: %u\n",
+ fw_req->firmware_id);
+
+ fw_req->timedout = true;
+ free_firmware(fw_download, fw_req);
+}
+
+static int exceeds_release_timeout(struct fw_request *fw_req)
+{
+ struct fw_download *fw_download = fw_req->fw_download;
+
+ if (time_before(jiffies, fw_req->release_timeout_j))
+ return 0;
+
+ dev_err(fw_download->parent,
+ "Firmware download didn't finish in time, abort: %d\n",
+ fw_req->firmware_id);
+
+ fw_req->timedout = true;
+ free_firmware(fw_download, fw_req);
+
+ return -ETIMEDOUT;
+}
+
+/* This returns path of the firmware blob on the disk */
+static struct fw_request *find_firmware(struct fw_download *fw_download,
+ const char *tag)
+{
+ struct gb_interface *intf = fw_download->connection->bundle->intf;
+ struct fw_request *fw_req;
+ int ret, req_count;
+
+ fw_req = kzalloc(sizeof(*fw_req), GFP_KERNEL);
+ if (!fw_req)
+ return ERR_PTR(-ENOMEM);
+
+ /* Allocate ids from 1 to 255 (u8-max), 0 is an invalid id */
+ ret = ida_simple_get(&fw_download->id_map, 1, 256, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(fw_download->parent,
+ "failed to allocate firmware id (%d)\n", ret);
+ goto err_free_req;
+ }
+ fw_req->firmware_id = ret;
+
+ snprintf(fw_req->name, sizeof(fw_req->name),
+ FW_NAME_PREFIX "%08x_%08x_%08x_%08x_%s.tftf",
+ intf->ddbl1_manufacturer_id, intf->ddbl1_product_id,
+ intf->vendor_id, intf->product_id, tag);
+
+ dev_info(fw_download->parent, "Requested firmware package '%s'\n",
+ fw_req->name);
+
+ ret = request_firmware(&fw_req->fw, fw_req->name, fw_download->parent);
+ if (ret) {
+ dev_err(fw_download->parent,
+ "firmware request failed for %s (%d)\n", fw_req->name,
+ ret);
+ goto err_free_id;
+ }
+
+ fw_req->fw_download = fw_download;
+ kref_init(&fw_req->kref);
+
+ mutex_lock(&fw_download->mutex);
+ list_add(&fw_req->node, &fw_download->fw_requests);
+ mutex_unlock(&fw_download->mutex);
+
+ /* Timeout, in jiffies, within which firmware should get loaded */
+ req_count = DIV_ROUND_UP(fw_req->fw->size, MIN_FETCH_SIZE);
+ fw_req->release_timeout_j = jiffies + req_count * NEXT_REQ_TIMEOUT_J;
+
+ INIT_DELAYED_WORK(&fw_req->dwork, fw_request_timedout);
+ schedule_delayed_work(&fw_req->dwork, NEXT_REQ_TIMEOUT_J);
+
+ return fw_req;
+
+err_free_id:
+ ida_simple_remove(&fw_download->id_map, fw_req->firmware_id);
+err_free_req:
+ kfree(fw_req);
+
+ return ERR_PTR(ret);
+}
+
+static int fw_download_find_firmware(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct fw_download *fw_download = gb_connection_get_data(connection);
+ struct gb_fw_download_find_firmware_request *request;
+ struct gb_fw_download_find_firmware_response *response;
+ struct fw_request *fw_req;
+ const char *tag;
+
+ if (op->request->payload_size != sizeof(*request)) {
+ dev_err(fw_download->parent,
+ "illegal size of find firmware request (%zu != %zu)\n",
+ op->request->payload_size, sizeof(*request));
+ return -EINVAL;
+ }
+
+ request = op->request->payload;
+ tag = (const char *)request->firmware_tag;
+
+ /* firmware_tag must be null-terminated */
+ if (strnlen(tag, GB_FIRMWARE_TAG_MAX_SIZE) ==
+ GB_FIRMWARE_TAG_MAX_SIZE) {
+ dev_err(fw_download->parent,
+ "firmware-tag is not null-terminated\n");
+ return -EINVAL;
+ }
+
+ fw_req = find_firmware(fw_download, tag);
+ if (IS_ERR(fw_req))
+ return PTR_ERR(fw_req);
+
+ if (!gb_operation_response_alloc(op, sizeof(*response), GFP_KERNEL)) {
+ dev_err(fw_download->parent, "error allocating response\n");
+ free_firmware(fw_download, fw_req);
+ return -ENOMEM;
+ }
+
+ response = op->response->payload;
+ response->firmware_id = fw_req->firmware_id;
+ response->size = cpu_to_le32(fw_req->fw->size);
+
+ dev_dbg(fw_download->parent,
+ "firmware size is %zu bytes\n", fw_req->fw->size);
+
+ return 0;
+}
+
+static int fw_download_fetch_firmware(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct fw_download *fw_download = gb_connection_get_data(connection);
+ struct gb_fw_download_fetch_firmware_request *request;
+ struct gb_fw_download_fetch_firmware_response *response;
+ struct fw_request *fw_req;
+ const struct firmware *fw;
+ unsigned int offset, size;
+ u8 firmware_id;
+ int ret = 0;
+
+ if (op->request->payload_size != sizeof(*request)) {
+ dev_err(fw_download->parent,
+ "Illegal size of fetch firmware request (%zu %zu)\n",
+ op->request->payload_size, sizeof(*request));
+ return -EINVAL;
+ }
+
+ request = op->request->payload;
+ offset = le32_to_cpu(request->offset);
+ size = le32_to_cpu(request->size);
+ firmware_id = request->firmware_id;
+
+ fw_req = get_fw_req(fw_download, firmware_id);
+ if (!fw_req) {
+ dev_err(fw_download->parent,
+ "firmware not available for id: %02u\n", firmware_id);
+ return -EINVAL;
+ }
+
+ /* Make sure work handler isn't running in parallel */
+ cancel_delayed_work_sync(&fw_req->dwork);
+
+ /* We timed-out before reaching here ? */
+ if (fw_req->disabled) {
+ ret = -ETIMEDOUT;
+ goto put_fw;
+ }
+
+ /*
+ * Firmware download must finish within a limited time interval. If it
+ * doesn't, then we might have a buggy Module on the other side. Abort
+ * download.
+ */
+ ret = exceeds_release_timeout(fw_req);
+ if (ret)
+ goto put_fw;
+
+ fw = fw_req->fw;
+
+ if (offset >= fw->size || size > fw->size - offset) {
+ dev_err(fw_download->parent,
+ "bad fetch firmware request (offs = %u, size = %u)\n",
+ offset, size);
+ ret = -EINVAL;
+ goto put_fw;
+ }
+
+ if (!gb_operation_response_alloc(op, sizeof(*response) + size,
+ GFP_KERNEL)) {
+ dev_err(fw_download->parent,
+ "error allocating fetch firmware response\n");
+ ret = -ENOMEM;
+ goto put_fw;
+ }
+
+ response = op->response->payload;
+ memcpy(response->data, fw->data + offset, size);
+
+ dev_dbg(fw_download->parent,
+ "responding with firmware (offs = %u, size = %u)\n", offset,
+ size);
+
+ /* Refresh timeout */
+ schedule_delayed_work(&fw_req->dwork, NEXT_REQ_TIMEOUT_J);
+
+put_fw:
+ put_fw_req(fw_req);
+
+ return ret;
+}
+
+static int fw_download_release_firmware(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct fw_download *fw_download = gb_connection_get_data(connection);
+ struct gb_fw_download_release_firmware_request *request;
+ struct fw_request *fw_req;
+ u8 firmware_id;
+
+ if (op->request->payload_size != sizeof(*request)) {
+ dev_err(fw_download->parent,
+ "Illegal size of release firmware request (%zu %zu)\n",
+ op->request->payload_size, sizeof(*request));
+ return -EINVAL;
+ }
+
+ request = op->request->payload;
+ firmware_id = request->firmware_id;
+
+ fw_req = get_fw_req(fw_download, firmware_id);
+ if (!fw_req) {
+ dev_err(fw_download->parent,
+ "firmware not available for id: %02u\n", firmware_id);
+ return -EINVAL;
+ }
+
+ cancel_delayed_work_sync(&fw_req->dwork);
+
+ free_firmware(fw_download, fw_req);
+ put_fw_req(fw_req);
+
+ dev_dbg(fw_download->parent, "release firmware\n");
+
+ return 0;
+}
+
+int gb_fw_download_request_handler(struct gb_operation *op)
+{
+ u8 type = op->type;
+
+ switch (type) {
+ case GB_FW_DOWNLOAD_TYPE_FIND_FIRMWARE:
+ return fw_download_find_firmware(op);
+ case GB_FW_DOWNLOAD_TYPE_FETCH_FIRMWARE:
+ return fw_download_fetch_firmware(op);
+ case GB_FW_DOWNLOAD_TYPE_RELEASE_FIRMWARE:
+ return fw_download_release_firmware(op);
+ default:
+ dev_err(&op->connection->bundle->dev,
+ "unsupported request: %u\n", type);
+ return -EINVAL;
+ }
+}
+
+int gb_fw_download_connection_init(struct gb_connection *connection)
+{
+ struct fw_download *fw_download;
+ int ret;
+
+ if (!connection)
+ return 0;
+
+ fw_download = kzalloc(sizeof(*fw_download), GFP_KERNEL);
+ if (!fw_download)
+ return -ENOMEM;
+
+ fw_download->parent = &connection->bundle->dev;
+ INIT_LIST_HEAD(&fw_download->fw_requests);
+ ida_init(&fw_download->id_map);
+ gb_connection_set_data(connection, fw_download);
+ fw_download->connection = connection;
+ mutex_init(&fw_download->mutex);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto err_destroy_id_map;
+
+ return 0;
+
+err_destroy_id_map:
+ ida_destroy(&fw_download->id_map);
+ kfree(fw_download);
+
+ return ret;
+}
+
+void gb_fw_download_connection_exit(struct gb_connection *connection)
+{
+ struct fw_download *fw_download;
+ struct fw_request *fw_req, *tmp;
+
+ if (!connection)
+ return;
+
+ fw_download = gb_connection_get_data(connection);
+ gb_connection_disable(fw_download->connection);
+
+ /*
+ * Make sure we have a reference to the pending requests, before they
+ * are freed from the timeout handler.
+ */
+ mutex_lock(&fw_download->mutex);
+ list_for_each_entry(fw_req, &fw_download->fw_requests, node)
+ kref_get(&fw_req->kref);
+ mutex_unlock(&fw_download->mutex);
+
+ /* Release pending firmware packages */
+ list_for_each_entry_safe(fw_req, tmp, &fw_download->fw_requests, node) {
+ cancel_delayed_work_sync(&fw_req->dwork);
+ free_firmware(fw_download, fw_req);
+ put_fw_req(fw_req);
+ }
+
+ ida_destroy(&fw_download->id_map);
+ kfree(fw_download);
+}
diff --git a/drivers/staging/greybus/fw-management.c b/drivers/staging/greybus/fw-management.c
new file mode 100644
index 0000000000..cd9141e4b7
--- /dev/null
+++ b/drivers/staging/greybus/fw-management.c
@@ -0,0 +1,720 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Firmware Management Protocol Driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ */
+
+#include <linux/cdev.h>
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/greybus.h>
+
+#include "firmware.h"
+#include "greybus_firmware.h"
+
+#define FW_MGMT_TIMEOUT_MS 1000
+
+struct fw_mgmt {
+ struct device *parent;
+ struct gb_connection *connection;
+ struct kref kref;
+ struct list_head node;
+
+ /* Common id-map for interface and backend firmware requests */
+ struct ida id_map;
+ struct mutex mutex;
+ struct completion completion;
+ struct cdev cdev;
+ struct device *class_device;
+ dev_t dev_num;
+ unsigned int timeout_jiffies;
+ bool disabled; /* connection getting disabled */
+
+ /* Interface Firmware specific fields */
+ bool mode_switch_started;
+ bool intf_fw_loaded;
+ u8 intf_fw_request_id;
+ u8 intf_fw_status;
+ u16 intf_fw_major;
+ u16 intf_fw_minor;
+
+ /* Backend Firmware specific fields */
+ u8 backend_fw_request_id;
+ u8 backend_fw_status;
+};
+
+/*
+ * Number of minor devices this driver supports.
+ * There will be exactly one required per Interface.
+ */
+#define NUM_MINORS U8_MAX
+
+static struct class *fw_mgmt_class;
+static dev_t fw_mgmt_dev_num;
+static DEFINE_IDA(fw_mgmt_minors_map);
+static LIST_HEAD(fw_mgmt_list);
+static DEFINE_MUTEX(list_mutex);
+
+static void fw_mgmt_kref_release(struct kref *kref)
+{
+ struct fw_mgmt *fw_mgmt = container_of(kref, struct fw_mgmt, kref);
+
+ ida_destroy(&fw_mgmt->id_map);
+ kfree(fw_mgmt);
+}
+
+/*
+ * All users of fw_mgmt take a reference (from within list_mutex lock), before
+ * they get a pointer to play with. And the structure will be freed only after
+ * the last user has put the reference to it.
+ */
+static void put_fw_mgmt(struct fw_mgmt *fw_mgmt)
+{
+ kref_put(&fw_mgmt->kref, fw_mgmt_kref_release);
+}
+
+/* Caller must call put_fw_mgmt() after using struct fw_mgmt */
+static struct fw_mgmt *get_fw_mgmt(struct cdev *cdev)
+{
+ struct fw_mgmt *fw_mgmt;
+
+ mutex_lock(&list_mutex);
+
+ list_for_each_entry(fw_mgmt, &fw_mgmt_list, node) {
+ if (&fw_mgmt->cdev == cdev) {
+ kref_get(&fw_mgmt->kref);
+ goto unlock;
+ }
+ }
+
+ fw_mgmt = NULL;
+
+unlock:
+ mutex_unlock(&list_mutex);
+
+ return fw_mgmt;
+}
+
+static int fw_mgmt_interface_fw_version_operation(struct fw_mgmt *fw_mgmt,
+ struct fw_mgmt_ioc_get_intf_version *fw_info)
+{
+ struct gb_connection *connection = fw_mgmt->connection;
+ struct gb_fw_mgmt_interface_fw_version_response response;
+ int ret;
+
+ ret = gb_operation_sync(connection,
+ GB_FW_MGMT_TYPE_INTERFACE_FW_VERSION, NULL, 0,
+ &response, sizeof(response));
+ if (ret) {
+ dev_err(fw_mgmt->parent,
+ "failed to get interface firmware version (%d)\n", ret);
+ return ret;
+ }
+
+ fw_info->major = le16_to_cpu(response.major);
+ fw_info->minor = le16_to_cpu(response.minor);
+
+ strncpy(fw_info->firmware_tag, response.firmware_tag,
+ GB_FIRMWARE_TAG_MAX_SIZE);
+
+ /*
+ * The firmware-tag should be NULL terminated, otherwise throw error but
+ * don't fail.
+ */
+ if (fw_info->firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] != '\0') {
+ dev_err(fw_mgmt->parent,
+ "fw-version: firmware-tag is not NULL terminated\n");
+ fw_info->firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] = '\0';
+ }
+
+ return 0;
+}
+
+static int fw_mgmt_load_and_validate_operation(struct fw_mgmt *fw_mgmt,
+ u8 load_method, const char *tag)
+{
+ struct gb_fw_mgmt_load_and_validate_fw_request request;
+ int ret;
+
+ if (load_method != GB_FW_LOAD_METHOD_UNIPRO &&
+ load_method != GB_FW_LOAD_METHOD_INTERNAL) {
+ dev_err(fw_mgmt->parent,
+ "invalid load-method (%d)\n", load_method);
+ return -EINVAL;
+ }
+
+ request.load_method = load_method;
+ strncpy(request.firmware_tag, tag, GB_FIRMWARE_TAG_MAX_SIZE);
+
+ /*
+ * The firmware-tag should be NULL terminated, otherwise throw error and
+ * fail.
+ */
+ if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] != '\0') {
+ dev_err(fw_mgmt->parent, "load-and-validate: firmware-tag is not NULL terminated\n");
+ return -EINVAL;
+ }
+
+ /* Allocate ids from 1 to 255 (u8-max), 0 is an invalid id */
+ ret = ida_simple_get(&fw_mgmt->id_map, 1, 256, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(fw_mgmt->parent, "failed to allocate request id (%d)\n",
+ ret);
+ return ret;
+ }
+
+ fw_mgmt->intf_fw_request_id = ret;
+ fw_mgmt->intf_fw_loaded = false;
+ request.request_id = ret;
+
+ ret = gb_operation_sync(fw_mgmt->connection,
+ GB_FW_MGMT_TYPE_LOAD_AND_VALIDATE_FW, &request,
+ sizeof(request), NULL, 0);
+ if (ret) {
+ ida_simple_remove(&fw_mgmt->id_map,
+ fw_mgmt->intf_fw_request_id);
+ fw_mgmt->intf_fw_request_id = 0;
+ dev_err(fw_mgmt->parent,
+ "load and validate firmware request failed (%d)\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fw_mgmt_interface_fw_loaded_operation(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct fw_mgmt *fw_mgmt = gb_connection_get_data(connection);
+ struct gb_fw_mgmt_loaded_fw_request *request;
+
+ /* No pending load and validate request ? */
+ if (!fw_mgmt->intf_fw_request_id) {
+ dev_err(fw_mgmt->parent,
+ "unexpected firmware loaded request received\n");
+ return -ENODEV;
+ }
+
+ if (op->request->payload_size != sizeof(*request)) {
+ dev_err(fw_mgmt->parent, "illegal size of firmware loaded request (%zu != %zu)\n",
+ op->request->payload_size, sizeof(*request));
+ return -EINVAL;
+ }
+
+ request = op->request->payload;
+
+ /* Invalid request-id ? */
+ if (request->request_id != fw_mgmt->intf_fw_request_id) {
+ dev_err(fw_mgmt->parent, "invalid request id for firmware loaded request (%02u != %02u)\n",
+ fw_mgmt->intf_fw_request_id, request->request_id);
+ return -ENODEV;
+ }
+
+ ida_simple_remove(&fw_mgmt->id_map, fw_mgmt->intf_fw_request_id);
+ fw_mgmt->intf_fw_request_id = 0;
+ fw_mgmt->intf_fw_status = request->status;
+ fw_mgmt->intf_fw_major = le16_to_cpu(request->major);
+ fw_mgmt->intf_fw_minor = le16_to_cpu(request->minor);
+
+ if (fw_mgmt->intf_fw_status == GB_FW_LOAD_STATUS_FAILED)
+ dev_err(fw_mgmt->parent,
+ "failed to load interface firmware, status:%02x\n",
+ fw_mgmt->intf_fw_status);
+ else if (fw_mgmt->intf_fw_status == GB_FW_LOAD_STATUS_VALIDATION_FAILED)
+ dev_err(fw_mgmt->parent,
+ "failed to validate interface firmware, status:%02x\n",
+ fw_mgmt->intf_fw_status);
+ else
+ fw_mgmt->intf_fw_loaded = true;
+
+ complete(&fw_mgmt->completion);
+
+ return 0;
+}
+
+static int fw_mgmt_backend_fw_version_operation(struct fw_mgmt *fw_mgmt,
+ struct fw_mgmt_ioc_get_backend_version *fw_info)
+{
+ struct gb_connection *connection = fw_mgmt->connection;
+ struct gb_fw_mgmt_backend_fw_version_request request;
+ struct gb_fw_mgmt_backend_fw_version_response response;
+ int ret;
+
+ strncpy(request.firmware_tag, fw_info->firmware_tag,
+ GB_FIRMWARE_TAG_MAX_SIZE);
+
+ /*
+ * The firmware-tag should be NULL terminated, otherwise throw error and
+ * fail.
+ */
+ if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] != '\0') {
+ dev_err(fw_mgmt->parent, "backend-version: firmware-tag is not NULL terminated\n");
+ return -EINVAL;
+ }
+
+ ret = gb_operation_sync(connection,
+ GB_FW_MGMT_TYPE_BACKEND_FW_VERSION, &request,
+ sizeof(request), &response, sizeof(response));
+ if (ret) {
+ dev_err(fw_mgmt->parent, "failed to get version of %s backend firmware (%d)\n",
+ fw_info->firmware_tag, ret);
+ return ret;
+ }
+
+ fw_info->status = response.status;
+
+ /* Reset version as that should be non-zero only for success case */
+ fw_info->major = 0;
+ fw_info->minor = 0;
+
+ switch (fw_info->status) {
+ case GB_FW_BACKEND_VERSION_STATUS_SUCCESS:
+ fw_info->major = le16_to_cpu(response.major);
+ fw_info->minor = le16_to_cpu(response.minor);
+ break;
+ case GB_FW_BACKEND_VERSION_STATUS_NOT_AVAILABLE:
+ case GB_FW_BACKEND_VERSION_STATUS_RETRY:
+ break;
+ case GB_FW_BACKEND_VERSION_STATUS_NOT_SUPPORTED:
+ dev_err(fw_mgmt->parent,
+ "Firmware with tag %s is not supported by Interface\n",
+ fw_info->firmware_tag);
+ break;
+ default:
+ dev_err(fw_mgmt->parent, "Invalid status received: %u\n",
+ fw_info->status);
+ }
+
+ return 0;
+}
+
+static int fw_mgmt_backend_fw_update_operation(struct fw_mgmt *fw_mgmt,
+ char *tag)
+{
+ struct gb_fw_mgmt_backend_fw_update_request request;
+ int ret;
+
+ strncpy(request.firmware_tag, tag, GB_FIRMWARE_TAG_MAX_SIZE);
+
+ /*
+ * The firmware-tag should be NULL terminated, otherwise throw error and
+ * fail.
+ */
+ if (request.firmware_tag[GB_FIRMWARE_TAG_MAX_SIZE - 1] != '\0') {
+ dev_err(fw_mgmt->parent, "backend-update: firmware-tag is not NULL terminated\n");
+ return -EINVAL;
+ }
+
+ /* Allocate ids from 1 to 255 (u8-max), 0 is an invalid id */
+ ret = ida_simple_get(&fw_mgmt->id_map, 1, 256, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(fw_mgmt->parent, "failed to allocate request id (%d)\n",
+ ret);
+ return ret;
+ }
+
+ fw_mgmt->backend_fw_request_id = ret;
+ request.request_id = ret;
+
+ ret = gb_operation_sync(fw_mgmt->connection,
+ GB_FW_MGMT_TYPE_BACKEND_FW_UPDATE, &request,
+ sizeof(request), NULL, 0);
+ if (ret) {
+ ida_simple_remove(&fw_mgmt->id_map,
+ fw_mgmt->backend_fw_request_id);
+ fw_mgmt->backend_fw_request_id = 0;
+ dev_err(fw_mgmt->parent,
+ "backend %s firmware update request failed (%d)\n", tag,
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fw_mgmt_backend_fw_updated_operation(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct fw_mgmt *fw_mgmt = gb_connection_get_data(connection);
+ struct gb_fw_mgmt_backend_fw_updated_request *request;
+
+ /* No pending load and validate request ? */
+ if (!fw_mgmt->backend_fw_request_id) {
+ dev_err(fw_mgmt->parent, "unexpected backend firmware updated request received\n");
+ return -ENODEV;
+ }
+
+ if (op->request->payload_size != sizeof(*request)) {
+ dev_err(fw_mgmt->parent, "illegal size of backend firmware updated request (%zu != %zu)\n",
+ op->request->payload_size, sizeof(*request));
+ return -EINVAL;
+ }
+
+ request = op->request->payload;
+
+ /* Invalid request-id ? */
+ if (request->request_id != fw_mgmt->backend_fw_request_id) {
+ dev_err(fw_mgmt->parent, "invalid request id for backend firmware updated request (%02u != %02u)\n",
+ fw_mgmt->backend_fw_request_id, request->request_id);
+ return -ENODEV;
+ }
+
+ ida_simple_remove(&fw_mgmt->id_map, fw_mgmt->backend_fw_request_id);
+ fw_mgmt->backend_fw_request_id = 0;
+ fw_mgmt->backend_fw_status = request->status;
+
+ if ((fw_mgmt->backend_fw_status != GB_FW_BACKEND_FW_STATUS_SUCCESS) &&
+ (fw_mgmt->backend_fw_status != GB_FW_BACKEND_FW_STATUS_RETRY))
+ dev_err(fw_mgmt->parent,
+ "failed to load backend firmware: %02x\n",
+ fw_mgmt->backend_fw_status);
+
+ complete(&fw_mgmt->completion);
+
+ return 0;
+}
+
+/* Char device fops */
+
+static int fw_mgmt_open(struct inode *inode, struct file *file)
+{
+ struct fw_mgmt *fw_mgmt = get_fw_mgmt(inode->i_cdev);
+
+ /* fw_mgmt structure can't get freed until file descriptor is closed */
+ if (fw_mgmt) {
+ file->private_data = fw_mgmt;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+static int fw_mgmt_release(struct inode *inode, struct file *file)
+{
+ struct fw_mgmt *fw_mgmt = file->private_data;
+
+ put_fw_mgmt(fw_mgmt);
+ return 0;
+}
+
+static int fw_mgmt_ioctl(struct fw_mgmt *fw_mgmt, unsigned int cmd,
+ void __user *buf)
+{
+ struct fw_mgmt_ioc_get_intf_version intf_fw_info;
+ struct fw_mgmt_ioc_get_backend_version backend_fw_info;
+ struct fw_mgmt_ioc_intf_load_and_validate intf_load;
+ struct fw_mgmt_ioc_backend_fw_update backend_update;
+ unsigned int timeout;
+ int ret;
+
+ /* Reject any operations after mode-switch has started */
+ if (fw_mgmt->mode_switch_started)
+ return -EBUSY;
+
+ switch (cmd) {
+ case FW_MGMT_IOC_GET_INTF_FW:
+ ret = fw_mgmt_interface_fw_version_operation(fw_mgmt,
+ &intf_fw_info);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(buf, &intf_fw_info, sizeof(intf_fw_info)))
+ return -EFAULT;
+
+ return 0;
+ case FW_MGMT_IOC_GET_BACKEND_FW:
+ if (copy_from_user(&backend_fw_info, buf,
+ sizeof(backend_fw_info)))
+ return -EFAULT;
+
+ ret = fw_mgmt_backend_fw_version_operation(fw_mgmt,
+ &backend_fw_info);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(buf, &backend_fw_info,
+ sizeof(backend_fw_info)))
+ return -EFAULT;
+
+ return 0;
+ case FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE:
+ if (copy_from_user(&intf_load, buf, sizeof(intf_load)))
+ return -EFAULT;
+
+ ret = fw_mgmt_load_and_validate_operation(fw_mgmt,
+ intf_load.load_method, intf_load.firmware_tag);
+ if (ret)
+ return ret;
+
+ if (!wait_for_completion_timeout(&fw_mgmt->completion,
+ fw_mgmt->timeout_jiffies)) {
+ dev_err(fw_mgmt->parent, "timed out waiting for firmware load and validation to finish\n");
+ return -ETIMEDOUT;
+ }
+
+ intf_load.status = fw_mgmt->intf_fw_status;
+ intf_load.major = fw_mgmt->intf_fw_major;
+ intf_load.minor = fw_mgmt->intf_fw_minor;
+
+ if (copy_to_user(buf, &intf_load, sizeof(intf_load)))
+ return -EFAULT;
+
+ return 0;
+ case FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE:
+ if (copy_from_user(&backend_update, buf,
+ sizeof(backend_update)))
+ return -EFAULT;
+
+ ret = fw_mgmt_backend_fw_update_operation(fw_mgmt,
+ backend_update.firmware_tag);
+ if (ret)
+ return ret;
+
+ if (!wait_for_completion_timeout(&fw_mgmt->completion,
+ fw_mgmt->timeout_jiffies)) {
+ dev_err(fw_mgmt->parent, "timed out waiting for backend firmware update to finish\n");
+ return -ETIMEDOUT;
+ }
+
+ backend_update.status = fw_mgmt->backend_fw_status;
+
+ if (copy_to_user(buf, &backend_update, sizeof(backend_update)))
+ return -EFAULT;
+
+ return 0;
+ case FW_MGMT_IOC_SET_TIMEOUT_MS:
+ if (get_user(timeout, (unsigned int __user *)buf))
+ return -EFAULT;
+
+ if (!timeout) {
+ dev_err(fw_mgmt->parent, "timeout can't be zero\n");
+ return -EINVAL;
+ }
+
+ fw_mgmt->timeout_jiffies = msecs_to_jiffies(timeout);
+
+ return 0;
+ case FW_MGMT_IOC_MODE_SWITCH:
+ if (!fw_mgmt->intf_fw_loaded) {
+ dev_err(fw_mgmt->parent,
+ "Firmware not loaded for mode-switch\n");
+ return -EPERM;
+ }
+
+ /*
+ * Disallow new ioctls as the fw-core bundle driver is going to
+ * get disconnected soon and the character device will get
+ * removed.
+ */
+ fw_mgmt->mode_switch_started = true;
+
+ ret = gb_interface_request_mode_switch(fw_mgmt->connection->intf);
+ if (ret) {
+ dev_err(fw_mgmt->parent, "Mode-switch failed: %d\n",
+ ret);
+ fw_mgmt->mode_switch_started = false;
+ return ret;
+ }
+
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static long fw_mgmt_ioctl_unlocked(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct fw_mgmt *fw_mgmt = file->private_data;
+ struct gb_bundle *bundle = fw_mgmt->connection->bundle;
+ int ret = -ENODEV;
+
+ /*
+ * Serialize ioctls.
+ *
+ * We don't want the user to do few operations in parallel. For example,
+ * updating Interface firmware in parallel for the same Interface. There
+ * is no need to do things in parallel for speed and we can avoid having
+ * complicated code for now.
+ *
+ * This is also used to protect ->disabled, which is used to check if
+ * the connection is getting disconnected, so that we don't start any
+ * new operations.
+ */
+ mutex_lock(&fw_mgmt->mutex);
+ if (!fw_mgmt->disabled) {
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (!ret) {
+ ret = fw_mgmt_ioctl(fw_mgmt, cmd, (void __user *)arg);
+ gb_pm_runtime_put_autosuspend(bundle);
+ }
+ }
+ mutex_unlock(&fw_mgmt->mutex);
+
+ return ret;
+}
+
+static const struct file_operations fw_mgmt_fops = {
+ .owner = THIS_MODULE,
+ .open = fw_mgmt_open,
+ .release = fw_mgmt_release,
+ .unlocked_ioctl = fw_mgmt_ioctl_unlocked,
+};
+
+int gb_fw_mgmt_request_handler(struct gb_operation *op)
+{
+ u8 type = op->type;
+
+ switch (type) {
+ case GB_FW_MGMT_TYPE_LOADED_FW:
+ return fw_mgmt_interface_fw_loaded_operation(op);
+ case GB_FW_MGMT_TYPE_BACKEND_FW_UPDATED:
+ return fw_mgmt_backend_fw_updated_operation(op);
+ default:
+ dev_err(&op->connection->bundle->dev,
+ "unsupported request: %u\n", type);
+ return -EINVAL;
+ }
+}
+
+int gb_fw_mgmt_connection_init(struct gb_connection *connection)
+{
+ struct fw_mgmt *fw_mgmt;
+ int ret, minor;
+
+ if (!connection)
+ return 0;
+
+ fw_mgmt = kzalloc(sizeof(*fw_mgmt), GFP_KERNEL);
+ if (!fw_mgmt)
+ return -ENOMEM;
+
+ fw_mgmt->parent = &connection->bundle->dev;
+ fw_mgmt->timeout_jiffies = msecs_to_jiffies(FW_MGMT_TIMEOUT_MS);
+ fw_mgmt->connection = connection;
+
+ gb_connection_set_data(connection, fw_mgmt);
+ init_completion(&fw_mgmt->completion);
+ ida_init(&fw_mgmt->id_map);
+ mutex_init(&fw_mgmt->mutex);
+ kref_init(&fw_mgmt->kref);
+
+ mutex_lock(&list_mutex);
+ list_add(&fw_mgmt->node, &fw_mgmt_list);
+ mutex_unlock(&list_mutex);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto err_list_del;
+
+ minor = ida_simple_get(&fw_mgmt_minors_map, 0, NUM_MINORS, GFP_KERNEL);
+ if (minor < 0) {
+ ret = minor;
+ goto err_connection_disable;
+ }
+
+ /* Add a char device to allow userspace to interact with fw-mgmt */
+ fw_mgmt->dev_num = MKDEV(MAJOR(fw_mgmt_dev_num), minor);
+ cdev_init(&fw_mgmt->cdev, &fw_mgmt_fops);
+
+ ret = cdev_add(&fw_mgmt->cdev, fw_mgmt->dev_num, 1);
+ if (ret)
+ goto err_remove_ida;
+
+ /* Add a soft link to the previously added char-dev within the bundle */
+ fw_mgmt->class_device = device_create(fw_mgmt_class, fw_mgmt->parent,
+ fw_mgmt->dev_num, NULL,
+ "gb-fw-mgmt-%d", minor);
+ if (IS_ERR(fw_mgmt->class_device)) {
+ ret = PTR_ERR(fw_mgmt->class_device);
+ goto err_del_cdev;
+ }
+
+ return 0;
+
+err_del_cdev:
+ cdev_del(&fw_mgmt->cdev);
+err_remove_ida:
+ ida_simple_remove(&fw_mgmt_minors_map, minor);
+err_connection_disable:
+ gb_connection_disable(connection);
+err_list_del:
+ mutex_lock(&list_mutex);
+ list_del(&fw_mgmt->node);
+ mutex_unlock(&list_mutex);
+
+ put_fw_mgmt(fw_mgmt);
+
+ return ret;
+}
+
+void gb_fw_mgmt_connection_exit(struct gb_connection *connection)
+{
+ struct fw_mgmt *fw_mgmt;
+
+ if (!connection)
+ return;
+
+ fw_mgmt = gb_connection_get_data(connection);
+
+ device_destroy(fw_mgmt_class, fw_mgmt->dev_num);
+ cdev_del(&fw_mgmt->cdev);
+ ida_simple_remove(&fw_mgmt_minors_map, MINOR(fw_mgmt->dev_num));
+
+ /*
+ * Disallow any new ioctl operations on the char device and wait for
+ * existing ones to finish.
+ */
+ mutex_lock(&fw_mgmt->mutex);
+ fw_mgmt->disabled = true;
+ mutex_unlock(&fw_mgmt->mutex);
+
+ /* All pending greybus operations should have finished by now */
+ gb_connection_disable(fw_mgmt->connection);
+
+ /* Disallow new users to get access to the fw_mgmt structure */
+ mutex_lock(&list_mutex);
+ list_del(&fw_mgmt->node);
+ mutex_unlock(&list_mutex);
+
+ /*
+ * All current users of fw_mgmt would have taken a reference to it by
+ * now, we can drop our reference and wait the last user will get
+ * fw_mgmt freed.
+ */
+ put_fw_mgmt(fw_mgmt);
+}
+
+int fw_mgmt_init(void)
+{
+ int ret;
+
+ fw_mgmt_class = class_create("gb_fw_mgmt");
+ if (IS_ERR(fw_mgmt_class))
+ return PTR_ERR(fw_mgmt_class);
+
+ ret = alloc_chrdev_region(&fw_mgmt_dev_num, 0, NUM_MINORS,
+ "gb_fw_mgmt");
+ if (ret)
+ goto err_remove_class;
+
+ return 0;
+
+err_remove_class:
+ class_destroy(fw_mgmt_class);
+ return ret;
+}
+
+void fw_mgmt_exit(void)
+{
+ unregister_chrdev_region(fw_mgmt_dev_num, NUM_MINORS);
+ class_destroy(fw_mgmt_class);
+ ida_destroy(&fw_mgmt_minors_map);
+}
diff --git a/drivers/staging/greybus/gb-camera.h b/drivers/staging/greybus/gb-camera.h
new file mode 100644
index 0000000000..5fc469101f
--- /dev/null
+++ b/drivers/staging/greybus/gb-camera.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Greybus Camera protocol driver.
+ *
+ * Copyright 2015 Google Inc.
+ */
+#ifndef __GB_CAMERA_H
+#define __GB_CAMERA_H
+
+#include <linux/v4l2-mediabus.h>
+
+/* Input flags need to be set from the caller */
+#define GB_CAMERA_IN_FLAG_TEST (1 << 0)
+/* Output flags returned */
+#define GB_CAMERA_OUT_FLAG_ADJUSTED (1 << 0)
+
+/**
+ * struct gb_camera_stream - Represents greybus camera stream.
+ * @width: Stream width in pixels.
+ * @height: Stream height in pixels.
+ * @pixel_code: Media bus pixel code.
+ * @vc: MIPI CSI virtual channel.
+ * @dt: MIPI CSI data types. Most formats use a single data type, in which case
+ * the second element will be ignored.
+ * @max_size: Maximum size of a frame in bytes. The camera module guarantees
+ * that all data between the Frame Start and Frame End packet for
+ * the associated virtual channel and data type(s) will not exceed
+ * this size.
+ */
+struct gb_camera_stream {
+ unsigned int width;
+ unsigned int height;
+ enum v4l2_mbus_pixelcode pixel_code;
+ unsigned int vc;
+ unsigned int dt[2];
+ unsigned int max_size;
+};
+
+/**
+ * struct gb_camera_csi_params - CSI configuration parameters
+ * @num_lanes: number of CSI data lanes
+ * @clk_freq: CSI clock frequency in Hz
+ */
+struct gb_camera_csi_params {
+ unsigned int num_lanes;
+ unsigned int clk_freq;
+};
+
+/**
+ * struct gb_camera_ops - Greybus camera operations, used by the Greybus camera
+ * driver to expose operations to the host camera driver.
+ * @capabilities: Retrieve camera capabilities and store them in the buffer
+ * 'buf' capabilities. The buffer maximum size is specified by
+ * the caller in the 'size' parameter, and the effective
+ * capabilities size is returned from the function. If the buffer
+ * size is too small to hold the capabilities an error is
+ * returned and the buffer is left untouched.
+ *
+ * @configure_streams: Negotiate configuration and prepare the module for video
+ * capture. The caller specifies the number of streams it
+ * requests in the 'nstreams' argument and the associated
+ * streams configurations in the 'streams' argument. The
+ * GB_CAMERA_IN_FLAG_TEST 'flag' can be set to test a
+ * configuration without applying it, otherwise the
+ * configuration is applied by the module. The module can
+ * decide to modify the requested configuration, including
+ * using a different number of streams. In that case the
+ * modified configuration won't be applied, the
+ * GB_CAMERA_OUT_FLAG_ADJUSTED 'flag' will be set upon
+ * return, and the modified configuration and number of
+ * streams stored in 'streams' and 'array'. The module
+ * returns its CSI-2 bus parameters in the 'csi_params'
+ * structure in all cases.
+ *
+ * @capture: Submit a capture request. The supplied 'request_id' must be unique
+ * and higher than the IDs of all the previously submitted requests.
+ * The 'streams' argument specifies which streams are affected by the
+ * request in the form of a bitmask, with bits corresponding to the
+ * configured streams indexes. If the request contains settings, the
+ * 'settings' argument points to the settings buffer and its size is
+ * specified by the 'settings_size' argument. Otherwise the 'settings'
+ * argument should be set to NULL and 'settings_size' to 0.
+ *
+ * @flush: Flush the capture requests queue. Return the ID of the last request
+ * that will processed by the device before it stops transmitting video
+ * frames. All queued capture requests with IDs higher than the returned
+ * ID will be dropped without being processed.
+ */
+struct gb_camera_ops {
+ ssize_t (*capabilities)(void *priv, char *buf, size_t len);
+ int (*configure_streams)(void *priv, unsigned int *nstreams,
+ unsigned int *flags, struct gb_camera_stream *streams,
+ struct gb_camera_csi_params *csi_params);
+ int (*capture)(void *priv, u32 request_id,
+ unsigned int streams, unsigned int num_frames,
+ size_t settings_size, const void *settings);
+ int (*flush)(void *priv, u32 *request_id);
+};
+
+/**
+ * struct gb_camera_module - Represents greybus camera module.
+ * @priv: Module private data, passed to all camera operations.
+ * @ops: Greybus camera operation callbacks.
+ * @interface_id: Interface id of the module.
+ * @refcount: Reference counting object.
+ * @release: Module release function.
+ * @list: List entry in the camera modules list.
+ */
+struct gb_camera_module {
+ void *priv;
+ const struct gb_camera_ops *ops;
+
+ unsigned int interface_id;
+ struct kref refcount;
+ void (*release)(struct kref *kref);
+ struct list_head list; /* Global list */
+};
+
+#define gb_camera_call(f, op, args...) \
+ (!(f) ? -ENODEV : (((f)->ops->op) ? \
+ (f)->ops->op((f)->priv, ##args) : -ENOIOCTLCMD))
+
+int gb_camera_register(struct gb_camera_module *module);
+int gb_camera_unregister(struct gb_camera_module *module);
+
+#endif /* __GB_CAMERA_H */
diff --git a/drivers/staging/greybus/gbphy.c b/drivers/staging/greybus/gbphy.c
new file mode 100644
index 0000000000..6a7d8cf2a1
--- /dev/null
+++ b/drivers/staging/greybus/gbphy.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Bridged-Phy Bus driver
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/greybus.h>
+
+#include "gbphy.h"
+
+#define GB_GBPHY_AUTOSUSPEND_MS 3000
+
+struct gbphy_host {
+ struct gb_bundle *bundle;
+ struct list_head devices;
+};
+
+static DEFINE_IDA(gbphy_id);
+
+static ssize_t protocol_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+
+ return sprintf(buf, "0x%02x\n", gbphy_dev->cport_desc->protocol_id);
+}
+static DEVICE_ATTR_RO(protocol_id);
+
+static struct attribute *gbphy_dev_attrs[] = {
+ &dev_attr_protocol_id.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(gbphy_dev);
+
+static void gbphy_dev_release(struct device *dev)
+{
+ struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+
+ ida_simple_remove(&gbphy_id, gbphy_dev->id);
+ kfree(gbphy_dev);
+}
+
+#ifdef CONFIG_PM
+static int gb_gbphy_idle(struct device *dev)
+{
+ pm_runtime_mark_last_busy(dev);
+ pm_request_autosuspend(dev);
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_gbphy_pm_ops = {
+ SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend,
+ pm_generic_runtime_resume,
+ gb_gbphy_idle)
+};
+
+static const struct device_type greybus_gbphy_dev_type = {
+ .name = "gbphy_device",
+ .release = gbphy_dev_release,
+ .pm = &gb_gbphy_pm_ops,
+};
+
+static int gbphy_dev_uevent(const struct device *dev, struct kobj_uevent_env *env)
+{
+ const struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+ const struct greybus_descriptor_cport *cport_desc = gbphy_dev->cport_desc;
+ const struct gb_bundle *bundle = gbphy_dev->bundle;
+ const struct gb_interface *intf = bundle->intf;
+ const struct gb_module *module = intf->module;
+ const struct gb_host_device *hd = intf->hd;
+
+ if (add_uevent_var(env, "BUS=%u", hd->bus_id))
+ return -ENOMEM;
+ if (add_uevent_var(env, "MODULE=%u", module->module_id))
+ return -ENOMEM;
+ if (add_uevent_var(env, "INTERFACE=%u", intf->interface_id))
+ return -ENOMEM;
+ if (add_uevent_var(env, "GREYBUS_ID=%08x/%08x",
+ intf->vendor_id, intf->product_id))
+ return -ENOMEM;
+ if (add_uevent_var(env, "BUNDLE=%u", gbphy_dev->bundle->id))
+ return -ENOMEM;
+ if (add_uevent_var(env, "BUNDLE_CLASS=%02x", bundle->class))
+ return -ENOMEM;
+ if (add_uevent_var(env, "GBPHY=%u", gbphy_dev->id))
+ return -ENOMEM;
+ if (add_uevent_var(env, "PROTOCOL_ID=%02x", cport_desc->protocol_id))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static const struct gbphy_device_id *
+gbphy_dev_match_id(struct gbphy_device *gbphy_dev,
+ struct gbphy_driver *gbphy_drv)
+{
+ const struct gbphy_device_id *id = gbphy_drv->id_table;
+
+ if (!id)
+ return NULL;
+
+ for (; id->protocol_id; id++)
+ if (id->protocol_id == gbphy_dev->cport_desc->protocol_id)
+ return id;
+
+ return NULL;
+}
+
+static int gbphy_dev_match(struct device *dev, struct device_driver *drv)
+{
+ struct gbphy_driver *gbphy_drv = to_gbphy_driver(drv);
+ struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+ const struct gbphy_device_id *id;
+
+ id = gbphy_dev_match_id(gbphy_dev, gbphy_drv);
+ if (id)
+ return 1;
+
+ return 0;
+}
+
+static int gbphy_dev_probe(struct device *dev)
+{
+ struct gbphy_driver *gbphy_drv = to_gbphy_driver(dev->driver);
+ struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+ const struct gbphy_device_id *id;
+ int ret;
+
+ id = gbphy_dev_match_id(gbphy_dev, gbphy_drv);
+ if (!id)
+ return -ENODEV;
+
+ /* for old kernels we need get_sync to resume parent devices */
+ ret = gb_pm_runtime_get_sync(gbphy_dev->bundle);
+ if (ret < 0)
+ return ret;
+
+ pm_runtime_set_autosuspend_delay(dev, GB_GBPHY_AUTOSUSPEND_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ /*
+ * Drivers should call put on the gbphy dev before returning
+ * from probe if they support runtime pm.
+ */
+ ret = gbphy_drv->probe(gbphy_dev, id);
+ if (ret) {
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_dont_use_autosuspend(dev);
+ }
+
+ gb_pm_runtime_put_autosuspend(gbphy_dev->bundle);
+
+ return ret;
+}
+
+static void gbphy_dev_remove(struct device *dev)
+{
+ struct gbphy_driver *gbphy_drv = to_gbphy_driver(dev->driver);
+ struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
+
+ gbphy_drv->remove(gbphy_dev);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_dont_use_autosuspend(dev);
+}
+
+static struct bus_type gbphy_bus_type = {
+ .name = "gbphy",
+ .match = gbphy_dev_match,
+ .probe = gbphy_dev_probe,
+ .remove = gbphy_dev_remove,
+ .uevent = gbphy_dev_uevent,
+};
+
+int gb_gbphy_register_driver(struct gbphy_driver *driver,
+ struct module *owner, const char *mod_name)
+{
+ int retval;
+
+ if (greybus_disabled())
+ return -ENODEV;
+
+ driver->driver.bus = &gbphy_bus_type;
+ driver->driver.name = driver->name;
+ driver->driver.owner = owner;
+ driver->driver.mod_name = mod_name;
+
+ retval = driver_register(&driver->driver);
+ if (retval)
+ return retval;
+
+ pr_info("registered new driver %s\n", driver->name);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gb_gbphy_register_driver);
+
+void gb_gbphy_deregister_driver(struct gbphy_driver *driver)
+{
+ driver_unregister(&driver->driver);
+}
+EXPORT_SYMBOL_GPL(gb_gbphy_deregister_driver);
+
+static struct gbphy_device *gb_gbphy_create_dev(struct gb_bundle *bundle,
+ struct greybus_descriptor_cport *cport_desc)
+{
+ struct gbphy_device *gbphy_dev;
+ int retval;
+ int id;
+
+ id = ida_simple_get(&gbphy_id, 1, 0, GFP_KERNEL);
+ if (id < 0)
+ return ERR_PTR(id);
+
+ gbphy_dev = kzalloc(sizeof(*gbphy_dev), GFP_KERNEL);
+ if (!gbphy_dev) {
+ ida_simple_remove(&gbphy_id, id);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ gbphy_dev->id = id;
+ gbphy_dev->bundle = bundle;
+ gbphy_dev->cport_desc = cport_desc;
+ gbphy_dev->dev.parent = &bundle->dev;
+ gbphy_dev->dev.bus = &gbphy_bus_type;
+ gbphy_dev->dev.type = &greybus_gbphy_dev_type;
+ gbphy_dev->dev.groups = gbphy_dev_groups;
+ gbphy_dev->dev.dma_mask = bundle->dev.dma_mask;
+ dev_set_name(&gbphy_dev->dev, "gbphy%d", id);
+
+ retval = device_register(&gbphy_dev->dev);
+ if (retval) {
+ put_device(&gbphy_dev->dev);
+ return ERR_PTR(retval);
+ }
+
+ return gbphy_dev;
+}
+
+static void gb_gbphy_disconnect(struct gb_bundle *bundle)
+{
+ struct gbphy_host *gbphy_host = greybus_get_drvdata(bundle);
+ struct gbphy_device *gbphy_dev, *temp;
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ gb_pm_runtime_get_noresume(bundle);
+
+ list_for_each_entry_safe(gbphy_dev, temp, &gbphy_host->devices, list) {
+ list_del(&gbphy_dev->list);
+ device_unregister(&gbphy_dev->dev);
+ }
+
+ kfree(gbphy_host);
+}
+
+static int gb_gbphy_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct gbphy_host *gbphy_host;
+ struct gbphy_device *gbphy_dev;
+ int i;
+
+ if (bundle->num_cports == 0)
+ return -ENODEV;
+
+ gbphy_host = kzalloc(sizeof(*gbphy_host), GFP_KERNEL);
+ if (!gbphy_host)
+ return -ENOMEM;
+
+ gbphy_host->bundle = bundle;
+ INIT_LIST_HEAD(&gbphy_host->devices);
+ greybus_set_drvdata(bundle, gbphy_host);
+
+ /*
+ * Create a bunch of children devices, one per cport, and bind the
+ * bridged phy drivers to them.
+ */
+ for (i = 0; i < bundle->num_cports; ++i) {
+ gbphy_dev = gb_gbphy_create_dev(bundle, &bundle->cport_desc[i]);
+ if (IS_ERR(gbphy_dev)) {
+ gb_gbphy_disconnect(bundle);
+ return PTR_ERR(gbphy_dev);
+ }
+ list_add(&gbphy_dev->list, &gbphy_host->devices);
+ }
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+}
+
+static const struct greybus_bundle_id gb_gbphy_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_BRIDGED_PHY) },
+ { },
+};
+MODULE_DEVICE_TABLE(greybus, gb_gbphy_id_table);
+
+static struct greybus_driver gb_gbphy_driver = {
+ .name = "gbphy",
+ .probe = gb_gbphy_probe,
+ .disconnect = gb_gbphy_disconnect,
+ .id_table = gb_gbphy_id_table,
+};
+
+static int __init gbphy_init(void)
+{
+ int retval;
+
+ retval = bus_register(&gbphy_bus_type);
+ if (retval) {
+ pr_err("gbphy bus register failed (%d)\n", retval);
+ return retval;
+ }
+
+ retval = greybus_register(&gb_gbphy_driver);
+ if (retval) {
+ pr_err("error registering greybus driver\n");
+ goto error_gbphy;
+ }
+
+ return 0;
+
+error_gbphy:
+ bus_unregister(&gbphy_bus_type);
+ ida_destroy(&gbphy_id);
+ return retval;
+}
+module_init(gbphy_init);
+
+static void __exit gbphy_exit(void)
+{
+ greybus_deregister(&gb_gbphy_driver);
+ bus_unregister(&gbphy_bus_type);
+ ida_destroy(&gbphy_id);
+}
+module_exit(gbphy_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/gbphy.h b/drivers/staging/greybus/gbphy.h
new file mode 100644
index 0000000000..d4a225b763
--- /dev/null
+++ b/drivers/staging/greybus/gbphy.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Greybus Bridged-Phy Bus driver
+ *
+ * Copyright 2016 Google Inc.
+ */
+
+#ifndef __GBPHY_H
+#define __GBPHY_H
+
+struct gbphy_device {
+ u32 id;
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_bundle *bundle;
+ struct list_head list;
+ struct device dev;
+};
+#define to_gbphy_dev(d) container_of(d, struct gbphy_device, dev)
+
+static inline void *gb_gbphy_get_data(struct gbphy_device *gdev)
+{
+ return dev_get_drvdata(&gdev->dev);
+}
+
+static inline void gb_gbphy_set_data(struct gbphy_device *gdev, void *data)
+{
+ dev_set_drvdata(&gdev->dev, data);
+}
+
+struct gbphy_device_id {
+ __u8 protocol_id;
+};
+
+#define GBPHY_PROTOCOL(p) \
+ .protocol_id = (p),
+
+struct gbphy_driver {
+ const char *name;
+ int (*probe)(struct gbphy_device *device,
+ const struct gbphy_device_id *id);
+ void (*remove)(struct gbphy_device *device);
+ const struct gbphy_device_id *id_table;
+
+ struct device_driver driver;
+};
+#define to_gbphy_driver(d) container_of(d, struct gbphy_driver, driver)
+
+int gb_gbphy_register_driver(struct gbphy_driver *driver,
+ struct module *owner, const char *mod_name);
+void gb_gbphy_deregister_driver(struct gbphy_driver *driver);
+
+#define gb_gbphy_register(driver) \
+ gb_gbphy_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
+#define gb_gbphy_deregister(driver) \
+ gb_gbphy_deregister_driver(driver)
+
+/**
+ * module_gbphy_driver() - Helper macro for registering a gbphy driver
+ * @__gbphy_driver: gbphy_driver structure
+ *
+ * Helper macro for gbphy drivers to set up proper module init / exit
+ * functions. Replaces module_init() and module_exit() and keeps people from
+ * printing pointless things to the kernel log when their driver is loaded.
+ */
+#define module_gbphy_driver(__gbphy_driver) \
+ module_driver(__gbphy_driver, gb_gbphy_register, gb_gbphy_deregister)
+
+#ifdef CONFIG_PM
+static inline int gbphy_runtime_get_sync(struct gbphy_device *gbphy_dev)
+{
+ struct device *dev = &gbphy_dev->dev;
+ int ret;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ dev_err(dev, "pm_runtime_get_sync failed: %d\n", ret);
+ pm_runtime_put_noidle(dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static inline void gbphy_runtime_put_autosuspend(struct gbphy_device *gbphy_dev)
+{
+ struct device *dev = &gbphy_dev->dev;
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+}
+
+static inline void gbphy_runtime_get_noresume(struct gbphy_device *gbphy_dev)
+{
+ pm_runtime_get_noresume(&gbphy_dev->dev);
+}
+
+static inline void gbphy_runtime_put_noidle(struct gbphy_device *gbphy_dev)
+{
+ pm_runtime_put_noidle(&gbphy_dev->dev);
+}
+#else
+static inline int gbphy_runtime_get_sync(struct gbphy_device *gbphy_dev) { return 0; }
+static inline void gbphy_runtime_put_autosuspend(struct gbphy_device *gbphy_dev) {}
+static inline void gbphy_runtime_get_noresume(struct gbphy_device *gbphy_dev) {}
+static inline void gbphy_runtime_put_noidle(struct gbphy_device *gbphy_dev) {}
+#endif
+
+#endif /* __GBPHY_H */
+
diff --git a/drivers/staging/greybus/gpio.c b/drivers/staging/greybus/gpio.c
new file mode 100644
index 0000000000..2a115a8fc2
--- /dev/null
+++ b/drivers/staging/greybus/gpio.c
@@ -0,0 +1,634 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO Greybus driver.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/gpio/driver.h>
+#include <linux/mutex.h>
+#include <linux/greybus.h>
+
+#include "gbphy.h"
+
+struct gb_gpio_line {
+ /* The following has to be an array of line_max entries */
+ /* --> make them just a flags field */
+ u8 active: 1,
+ direction: 1, /* 0 = output, 1 = input */
+ value: 1; /* 0 = low, 1 = high */
+ u16 debounce_usec;
+
+ u8 irq_type;
+ bool irq_type_pending;
+ bool masked;
+ bool masked_pending;
+};
+
+struct gb_gpio_controller {
+ struct gbphy_device *gbphy_dev;
+ struct gb_connection *connection;
+ u8 line_max; /* max line number */
+ struct gb_gpio_line *lines;
+
+ struct gpio_chip chip;
+ struct irq_chip irqc;
+ struct mutex irq_lock;
+};
+
+static inline struct gb_gpio_controller *gpio_chip_to_gb_gpio_controller(struct gpio_chip *chip)
+{
+ return container_of(chip, struct gb_gpio_controller, chip);
+}
+
+static struct gpio_chip *irq_data_to_gpio_chip(struct irq_data *d)
+{
+ return d->domain->host_data;
+}
+
+static int gb_gpio_line_count_operation(struct gb_gpio_controller *ggc)
+{
+ struct gb_gpio_line_count_response response;
+ int ret;
+
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_LINE_COUNT,
+ NULL, 0, &response, sizeof(response));
+ if (!ret)
+ ggc->line_max = response.count;
+ return ret;
+}
+
+static int gb_gpio_activate_operation(struct gb_gpio_controller *ggc, u8 which)
+{
+ struct gb_gpio_activate_request request;
+ struct gbphy_device *gbphy_dev = ggc->gbphy_dev;
+ int ret;
+
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ return ret;
+
+ request.which = which;
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_ACTIVATE,
+ &request, sizeof(request), NULL, 0);
+ if (ret) {
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+ return ret;
+ }
+
+ ggc->lines[which].active = true;
+
+ return 0;
+}
+
+static void gb_gpio_deactivate_operation(struct gb_gpio_controller *ggc,
+ u8 which)
+{
+ struct gbphy_device *gbphy_dev = ggc->gbphy_dev;
+ struct device *dev = &gbphy_dev->dev;
+ struct gb_gpio_deactivate_request request;
+ int ret;
+
+ request.which = which;
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_DEACTIVATE,
+ &request, sizeof(request), NULL, 0);
+ if (ret) {
+ dev_err(dev, "failed to deactivate gpio %u\n", which);
+ goto out_pm_put;
+ }
+
+ ggc->lines[which].active = false;
+
+out_pm_put:
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+}
+
+static int gb_gpio_get_direction_operation(struct gb_gpio_controller *ggc,
+ u8 which)
+{
+ struct device *dev = &ggc->gbphy_dev->dev;
+ struct gb_gpio_get_direction_request request;
+ struct gb_gpio_get_direction_response response;
+ int ret;
+ u8 direction;
+
+ request.which = which;
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_GET_DIRECTION,
+ &request, sizeof(request),
+ &response, sizeof(response));
+ if (ret)
+ return ret;
+
+ direction = response.direction;
+ if (direction && direction != 1) {
+ dev_warn(dev, "gpio %u direction was %u (should be 0 or 1)\n",
+ which, direction);
+ }
+ ggc->lines[which].direction = direction ? 1 : 0;
+ return 0;
+}
+
+static int gb_gpio_direction_in_operation(struct gb_gpio_controller *ggc,
+ u8 which)
+{
+ struct gb_gpio_direction_in_request request;
+ int ret;
+
+ request.which = which;
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_DIRECTION_IN,
+ &request, sizeof(request), NULL, 0);
+ if (!ret)
+ ggc->lines[which].direction = 1;
+ return ret;
+}
+
+static int gb_gpio_direction_out_operation(struct gb_gpio_controller *ggc,
+ u8 which, bool value_high)
+{
+ struct gb_gpio_direction_out_request request;
+ int ret;
+
+ request.which = which;
+ request.value = value_high ? 1 : 0;
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_DIRECTION_OUT,
+ &request, sizeof(request), NULL, 0);
+ if (!ret)
+ ggc->lines[which].direction = 0;
+ return ret;
+}
+
+static int gb_gpio_get_value_operation(struct gb_gpio_controller *ggc,
+ u8 which)
+{
+ struct device *dev = &ggc->gbphy_dev->dev;
+ struct gb_gpio_get_value_request request;
+ struct gb_gpio_get_value_response response;
+ int ret;
+ u8 value;
+
+ request.which = which;
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_GET_VALUE,
+ &request, sizeof(request),
+ &response, sizeof(response));
+ if (ret) {
+ dev_err(dev, "failed to get value of gpio %u\n", which);
+ return ret;
+ }
+
+ value = response.value;
+ if (value && value != 1) {
+ dev_warn(dev, "gpio %u value was %u (should be 0 or 1)\n",
+ which, value);
+ }
+ ggc->lines[which].value = value ? 1 : 0;
+ return 0;
+}
+
+static void gb_gpio_set_value_operation(struct gb_gpio_controller *ggc,
+ u8 which, bool value_high)
+{
+ struct device *dev = &ggc->gbphy_dev->dev;
+ struct gb_gpio_set_value_request request;
+ int ret;
+
+ if (ggc->lines[which].direction == 1) {
+ dev_warn(dev, "refusing to set value of input gpio %u\n",
+ which);
+ return;
+ }
+
+ request.which = which;
+ request.value = value_high ? 1 : 0;
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_SET_VALUE,
+ &request, sizeof(request), NULL, 0);
+ if (ret) {
+ dev_err(dev, "failed to set value of gpio %u\n", which);
+ return;
+ }
+
+ ggc->lines[which].value = request.value;
+}
+
+static int gb_gpio_set_debounce_operation(struct gb_gpio_controller *ggc,
+ u8 which, u16 debounce_usec)
+{
+ struct gb_gpio_set_debounce_request request;
+ int ret;
+
+ request.which = which;
+ request.usec = cpu_to_le16(debounce_usec);
+ ret = gb_operation_sync(ggc->connection, GB_GPIO_TYPE_SET_DEBOUNCE,
+ &request, sizeof(request), NULL, 0);
+ if (!ret)
+ ggc->lines[which].debounce_usec = debounce_usec;
+ return ret;
+}
+
+static void _gb_gpio_irq_mask(struct gb_gpio_controller *ggc, u8 hwirq)
+{
+ struct device *dev = &ggc->gbphy_dev->dev;
+ struct gb_gpio_irq_mask_request request;
+ int ret;
+
+ request.which = hwirq;
+ ret = gb_operation_sync(ggc->connection,
+ GB_GPIO_TYPE_IRQ_MASK,
+ &request, sizeof(request), NULL, 0);
+ if (ret)
+ dev_err(dev, "failed to mask irq: %d\n", ret);
+}
+
+static void _gb_gpio_irq_unmask(struct gb_gpio_controller *ggc, u8 hwirq)
+{
+ struct device *dev = &ggc->gbphy_dev->dev;
+ struct gb_gpio_irq_unmask_request request;
+ int ret;
+
+ request.which = hwirq;
+ ret = gb_operation_sync(ggc->connection,
+ GB_GPIO_TYPE_IRQ_UNMASK,
+ &request, sizeof(request), NULL, 0);
+ if (ret)
+ dev_err(dev, "failed to unmask irq: %d\n", ret);
+}
+
+static void _gb_gpio_irq_set_type(struct gb_gpio_controller *ggc,
+ u8 hwirq, u8 type)
+{
+ struct device *dev = &ggc->gbphy_dev->dev;
+ struct gb_gpio_irq_type_request request;
+ int ret;
+
+ request.which = hwirq;
+ request.type = type;
+
+ ret = gb_operation_sync(ggc->connection,
+ GB_GPIO_TYPE_IRQ_TYPE,
+ &request, sizeof(request), NULL, 0);
+ if (ret)
+ dev_err(dev, "failed to set irq type: %d\n", ret);
+}
+
+static void gb_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_to_gpio_chip(d);
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+ struct gb_gpio_line *line = &ggc->lines[d->hwirq];
+
+ line->masked = true;
+ line->masked_pending = true;
+}
+
+static void gb_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_to_gpio_chip(d);
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+ struct gb_gpio_line *line = &ggc->lines[d->hwirq];
+
+ line->masked = false;
+ line->masked_pending = true;
+}
+
+static int gb_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *chip = irq_data_to_gpio_chip(d);
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+ struct gb_gpio_line *line = &ggc->lines[d->hwirq];
+ struct device *dev = &ggc->gbphy_dev->dev;
+ u8 irq_type;
+
+ switch (type) {
+ case IRQ_TYPE_NONE:
+ irq_type = GB_GPIO_IRQ_TYPE_NONE;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ irq_type = GB_GPIO_IRQ_TYPE_EDGE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ irq_type = GB_GPIO_IRQ_TYPE_EDGE_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ irq_type = GB_GPIO_IRQ_TYPE_EDGE_BOTH;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ irq_type = GB_GPIO_IRQ_TYPE_LEVEL_LOW;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ irq_type = GB_GPIO_IRQ_TYPE_LEVEL_HIGH;
+ break;
+ default:
+ dev_err(dev, "unsupported irq type: %u\n", type);
+ return -EINVAL;
+ }
+
+ line->irq_type = irq_type;
+ line->irq_type_pending = true;
+
+ return 0;
+}
+
+static void gb_gpio_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_to_gpio_chip(d);
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+
+ mutex_lock(&ggc->irq_lock);
+}
+
+static void gb_gpio_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_to_gpio_chip(d);
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+ struct gb_gpio_line *line = &ggc->lines[d->hwirq];
+
+ if (line->irq_type_pending) {
+ _gb_gpio_irq_set_type(ggc, d->hwirq, line->irq_type);
+ line->irq_type_pending = false;
+ }
+
+ if (line->masked_pending) {
+ if (line->masked)
+ _gb_gpio_irq_mask(ggc, d->hwirq);
+ else
+ _gb_gpio_irq_unmask(ggc, d->hwirq);
+ line->masked_pending = false;
+ }
+
+ mutex_unlock(&ggc->irq_lock);
+}
+
+static int gb_gpio_request_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_gpio_controller *ggc = gb_connection_get_data(connection);
+ struct device *dev = &ggc->gbphy_dev->dev;
+ struct gb_message *request;
+ struct gb_gpio_irq_event_request *event;
+ u8 type = op->type;
+ int irq, ret;
+
+ if (type != GB_GPIO_TYPE_IRQ_EVENT) {
+ dev_err(dev, "unsupported unsolicited request: %u\n", type);
+ return -EINVAL;
+ }
+
+ request = op->request;
+
+ if (request->payload_size < sizeof(*event)) {
+ dev_err(dev, "short event received (%zu < %zu)\n",
+ request->payload_size, sizeof(*event));
+ return -EINVAL;
+ }
+
+ event = request->payload;
+ if (event->which > ggc->line_max) {
+ dev_err(dev, "invalid hw irq: %d\n", event->which);
+ return -EINVAL;
+ }
+
+ irq = irq_find_mapping(ggc->chip.irq.domain, event->which);
+ if (!irq) {
+ dev_err(dev, "failed to find IRQ\n");
+ return -EINVAL;
+ }
+
+ ret = generic_handle_irq_safe(irq);
+ if (ret)
+ dev_err(dev, "failed to invoke irq handler\n");
+
+ return ret;
+}
+
+static int gb_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+
+ return gb_gpio_activate_operation(ggc, (u8)offset);
+}
+
+static void gb_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+
+ gb_gpio_deactivate_operation(ggc, (u8)offset);
+}
+
+static int gb_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+ u8 which;
+ int ret;
+
+ which = (u8)offset;
+ ret = gb_gpio_get_direction_operation(ggc, which);
+ if (ret)
+ return ret;
+
+ return ggc->lines[which].direction ? 1 : 0;
+}
+
+static int gb_gpio_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+
+ return gb_gpio_direction_in_operation(ggc, (u8)offset);
+}
+
+static int gb_gpio_direction_output(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+
+ return gb_gpio_direction_out_operation(ggc, (u8)offset, !!value);
+}
+
+static int gb_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+ u8 which;
+ int ret;
+
+ which = (u8)offset;
+ ret = gb_gpio_get_value_operation(ggc, which);
+ if (ret)
+ return ret;
+
+ return ggc->lines[which].value;
+}
+
+static void gb_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+
+ gb_gpio_set_value_operation(ggc, (u8)offset, !!value);
+}
+
+static int gb_gpio_set_config(struct gpio_chip *chip, unsigned int offset,
+ unsigned long config)
+{
+ struct gb_gpio_controller *ggc = gpio_chip_to_gb_gpio_controller(chip);
+ u32 debounce;
+
+ if (pinconf_to_config_param(config) != PIN_CONFIG_INPUT_DEBOUNCE)
+ return -ENOTSUPP;
+
+ debounce = pinconf_to_config_argument(config);
+ if (debounce > U16_MAX)
+ return -EINVAL;
+
+ return gb_gpio_set_debounce_operation(ggc, (u8)offset, (u16)debounce);
+}
+
+static int gb_gpio_controller_setup(struct gb_gpio_controller *ggc)
+{
+ int ret;
+
+ /* Now find out how many lines there are */
+ ret = gb_gpio_line_count_operation(ggc);
+ if (ret)
+ return ret;
+
+ ggc->lines = kcalloc(ggc->line_max + 1, sizeof(*ggc->lines),
+ GFP_KERNEL);
+ if (!ggc->lines)
+ return -ENOMEM;
+
+ return ret;
+}
+
+static int gb_gpio_probe(struct gbphy_device *gbphy_dev,
+ const struct gbphy_device_id *id)
+{
+ struct gb_connection *connection;
+ struct gb_gpio_controller *ggc;
+ struct gpio_chip *gpio;
+ struct gpio_irq_chip *girq;
+ struct irq_chip *irqc;
+ int ret;
+
+ ggc = kzalloc(sizeof(*ggc), GFP_KERNEL);
+ if (!ggc)
+ return -ENOMEM;
+
+ connection =
+ gb_connection_create(gbphy_dev->bundle,
+ le16_to_cpu(gbphy_dev->cport_desc->id),
+ gb_gpio_request_handler);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ goto exit_ggc_free;
+ }
+
+ ggc->connection = connection;
+ gb_connection_set_data(connection, ggc);
+ ggc->gbphy_dev = gbphy_dev;
+ gb_gbphy_set_data(gbphy_dev, ggc);
+
+ ret = gb_connection_enable_tx(connection);
+ if (ret)
+ goto exit_connection_destroy;
+
+ ret = gb_gpio_controller_setup(ggc);
+ if (ret)
+ goto exit_connection_disable;
+
+ irqc = &ggc->irqc;
+ irqc->irq_mask = gb_gpio_irq_mask;
+ irqc->irq_unmask = gb_gpio_irq_unmask;
+ irqc->irq_set_type = gb_gpio_irq_set_type;
+ irqc->irq_bus_lock = gb_gpio_irq_bus_lock;
+ irqc->irq_bus_sync_unlock = gb_gpio_irq_bus_sync_unlock;
+ irqc->name = "greybus_gpio";
+
+ mutex_init(&ggc->irq_lock);
+
+ gpio = &ggc->chip;
+
+ gpio->label = "greybus_gpio";
+ gpio->parent = &gbphy_dev->dev;
+ gpio->owner = THIS_MODULE;
+
+ gpio->request = gb_gpio_request;
+ gpio->free = gb_gpio_free;
+ gpio->get_direction = gb_gpio_get_direction;
+ gpio->direction_input = gb_gpio_direction_input;
+ gpio->direction_output = gb_gpio_direction_output;
+ gpio->get = gb_gpio_get;
+ gpio->set = gb_gpio_set;
+ gpio->set_config = gb_gpio_set_config;
+ gpio->base = -1; /* Allocate base dynamically */
+ gpio->ngpio = ggc->line_max + 1;
+ gpio->can_sleep = true;
+
+ girq = &gpio->irq;
+ girq->chip = irqc;
+ /* The event comes from the outside so no parent handler */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_level_irq;
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto exit_line_free;
+
+ ret = gpiochip_add(gpio);
+ if (ret) {
+ dev_err(&gbphy_dev->dev, "failed to add gpio chip: %d\n", ret);
+ goto exit_line_free;
+ }
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+ return 0;
+
+exit_line_free:
+ kfree(ggc->lines);
+exit_connection_disable:
+ gb_connection_disable(connection);
+exit_connection_destroy:
+ gb_connection_destroy(connection);
+exit_ggc_free:
+ kfree(ggc);
+ return ret;
+}
+
+static void gb_gpio_remove(struct gbphy_device *gbphy_dev)
+{
+ struct gb_gpio_controller *ggc = gb_gbphy_get_data(gbphy_dev);
+ struct gb_connection *connection = ggc->connection;
+ int ret;
+
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ gbphy_runtime_get_noresume(gbphy_dev);
+
+ gb_connection_disable_rx(connection);
+ gpiochip_remove(&ggc->chip);
+ gb_connection_disable(connection);
+ gb_connection_destroy(connection);
+ kfree(ggc->lines);
+ kfree(ggc);
+}
+
+static const struct gbphy_device_id gb_gpio_id_table[] = {
+ { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_GPIO) },
+ { },
+};
+MODULE_DEVICE_TABLE(gbphy, gb_gpio_id_table);
+
+static struct gbphy_driver gpio_driver = {
+ .name = "gpio",
+ .probe = gb_gpio_probe,
+ .remove = gb_gpio_remove,
+ .id_table = gb_gpio_id_table,
+};
+
+module_gbphy_driver(gpio_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/greybus_authentication.h b/drivers/staging/greybus/greybus_authentication.h
new file mode 100644
index 0000000000..48b4a9794d
--- /dev/null
+++ b/drivers/staging/greybus/greybus_authentication.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Greybus Component Authentication User Header
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ */
+
+#ifndef __GREYBUS_AUTHENTICATION_USER_H
+#define __GREYBUS_AUTHENTICATION_USER_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define CAP_CERTIFICATE_MAX_SIZE 1600
+#define CAP_SIGNATURE_MAX_SIZE 320
+
+/* Certificate class types */
+#define CAP_CERT_IMS_EAPC 0x00000001
+#define CAP_CERT_IMS_EASC 0x00000002
+#define CAP_CERT_IMS_EARC 0x00000003
+#define CAP_CERT_IMS_IAPC 0x00000004
+#define CAP_CERT_IMS_IASC 0x00000005
+#define CAP_CERT_IMS_IARC 0x00000006
+
+/* IMS Certificate response result codes */
+#define CAP_IMS_RESULT_CERT_FOUND 0x00
+#define CAP_IMS_RESULT_CERT_CLASS_INVAL 0x01
+#define CAP_IMS_RESULT_CERT_CORRUPT 0x02
+#define CAP_IMS_RESULT_CERT_NOT_FOUND 0x03
+
+/* Authentication types */
+#define CAP_AUTH_IMS_PRI 0x00000001
+#define CAP_AUTH_IMS_SEC 0x00000002
+#define CAP_AUTH_IMS_RSA 0x00000003
+
+/* Authenticate response result codes */
+#define CAP_AUTH_RESULT_CR_SUCCESS 0x00
+#define CAP_AUTH_RESULT_CR_BAD_TYPE 0x01
+#define CAP_AUTH_RESULT_CR_WRONG_EP 0x02
+#define CAP_AUTH_RESULT_CR_NO_KEY 0x03
+#define CAP_AUTH_RESULT_CR_SIG_FAIL 0x04
+
+/* IOCTL support */
+struct cap_ioc_get_endpoint_uid {
+ __u8 uid[8];
+} __attribute__ ((__packed__));
+
+struct cap_ioc_get_ims_certificate {
+ __u32 certificate_class;
+ __u32 certificate_id;
+
+ __u8 result_code;
+ __u32 cert_size;
+ __u8 certificate[CAP_CERTIFICATE_MAX_SIZE];
+} __attribute__ ((__packed__));
+
+struct cap_ioc_authenticate {
+ __u32 auth_type;
+ __u8 uid[8];
+ __u8 challenge[32];
+
+ __u8 result_code;
+ __u8 response[64];
+ __u32 signature_size;
+ __u8 signature[CAP_SIGNATURE_MAX_SIZE];
+} __attribute__ ((__packed__));
+
+#define CAP_IOCTL_BASE 'C'
+#define CAP_IOC_GET_ENDPOINT_UID _IOR(CAP_IOCTL_BASE, 0, struct cap_ioc_get_endpoint_uid)
+#define CAP_IOC_GET_IMS_CERTIFICATE _IOWR(CAP_IOCTL_BASE, 1, struct cap_ioc_get_ims_certificate)
+#define CAP_IOC_AUTHENTICATE _IOWR(CAP_IOCTL_BASE, 2, struct cap_ioc_authenticate)
+
+#endif /* __GREYBUS_AUTHENTICATION_USER_H */
diff --git a/drivers/staging/greybus/greybus_firmware.h b/drivers/staging/greybus/greybus_firmware.h
new file mode 100644
index 0000000000..f68fd5e253
--- /dev/null
+++ b/drivers/staging/greybus/greybus_firmware.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Greybus Firmware Management User Header
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ */
+
+#ifndef __GREYBUS_FIRMWARE_USER_H
+#define __GREYBUS_FIRMWARE_USER_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define GB_FIRMWARE_U_TAG_MAX_SIZE 10
+
+#define GB_FW_U_LOAD_METHOD_UNIPRO 0x01
+#define GB_FW_U_LOAD_METHOD_INTERNAL 0x02
+
+#define GB_FW_U_LOAD_STATUS_FAILED 0x00
+#define GB_FW_U_LOAD_STATUS_UNVALIDATED 0x01
+#define GB_FW_U_LOAD_STATUS_VALIDATED 0x02
+#define GB_FW_U_LOAD_STATUS_VALIDATION_FAILED 0x03
+
+#define GB_FW_U_BACKEND_FW_STATUS_SUCCESS 0x01
+#define GB_FW_U_BACKEND_FW_STATUS_FAIL_FIND 0x02
+#define GB_FW_U_BACKEND_FW_STATUS_FAIL_FETCH 0x03
+#define GB_FW_U_BACKEND_FW_STATUS_FAIL_WRITE 0x04
+#define GB_FW_U_BACKEND_FW_STATUS_INT 0x05
+#define GB_FW_U_BACKEND_FW_STATUS_RETRY 0x06
+#define GB_FW_U_BACKEND_FW_STATUS_NOT_SUPPORTED 0x07
+
+#define GB_FW_U_BACKEND_VERSION_STATUS_SUCCESS 0x01
+#define GB_FW_U_BACKEND_VERSION_STATUS_NOT_AVAILABLE 0x02
+#define GB_FW_U_BACKEND_VERSION_STATUS_NOT_SUPPORTED 0x03
+#define GB_FW_U_BACKEND_VERSION_STATUS_RETRY 0x04
+#define GB_FW_U_BACKEND_VERSION_STATUS_FAIL_INT 0x05
+
+/* IOCTL support */
+struct fw_mgmt_ioc_get_intf_version {
+ __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+ __u16 major;
+ __u16 minor;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_get_backend_version {
+ __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+ __u16 major;
+ __u16 minor;
+ __u8 status;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_intf_load_and_validate {
+ __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+ __u8 load_method;
+ __u8 status;
+ __u16 major;
+ __u16 minor;
+} __attribute__ ((__packed__));
+
+struct fw_mgmt_ioc_backend_fw_update {
+ __u8 firmware_tag[GB_FIRMWARE_U_TAG_MAX_SIZE];
+ __u8 status;
+} __attribute__ ((__packed__));
+
+#define FW_MGMT_IOCTL_BASE 'F'
+#define FW_MGMT_IOC_GET_INTF_FW _IOR(FW_MGMT_IOCTL_BASE, 0, struct fw_mgmt_ioc_get_intf_version)
+#define FW_MGMT_IOC_GET_BACKEND_FW _IOWR(FW_MGMT_IOCTL_BASE, 1, struct fw_mgmt_ioc_get_backend_version)
+#define FW_MGMT_IOC_INTF_LOAD_AND_VALIDATE _IOWR(FW_MGMT_IOCTL_BASE, 2, struct fw_mgmt_ioc_intf_load_and_validate)
+#define FW_MGMT_IOC_INTF_BACKEND_FW_UPDATE _IOWR(FW_MGMT_IOCTL_BASE, 3, struct fw_mgmt_ioc_backend_fw_update)
+#define FW_MGMT_IOC_SET_TIMEOUT_MS _IOW(FW_MGMT_IOCTL_BASE, 4, unsigned int)
+#define FW_MGMT_IOC_MODE_SWITCH _IO(FW_MGMT_IOCTL_BASE, 5)
+
+#endif /* __GREYBUS_FIRMWARE_USER_H */
+
diff --git a/drivers/staging/greybus/hid.c b/drivers/staging/greybus/hid.c
new file mode 100644
index 0000000000..15335c38cb
--- /dev/null
+++ b/drivers/staging/greybus/hid.c
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID class driver for the Greybus.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ */
+
+#include <linux/bitops.h>
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/greybus.h>
+
+/* Greybus HID device's structure */
+struct gb_hid {
+ struct gb_bundle *bundle;
+ struct gb_connection *connection;
+
+ struct hid_device *hid;
+ struct gb_hid_desc_response hdesc;
+
+ unsigned long flags;
+#define GB_HID_STARTED 0x01
+#define GB_HID_READ_PENDING 0x04
+
+ unsigned int bufsize;
+ char *inbuf;
+};
+
+/* Routines to get controller's information over greybus */
+
+/* Operations performed on greybus */
+static int gb_hid_get_desc(struct gb_hid *ghid)
+{
+ return gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_DESC, NULL,
+ 0, &ghid->hdesc, sizeof(ghid->hdesc));
+}
+
+static int gb_hid_get_report_desc(struct gb_hid *ghid, char *rdesc)
+{
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(ghid->bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_REPORT_DESC,
+ NULL, 0, rdesc,
+ le16_to_cpu(ghid->hdesc.wReportDescLength));
+
+ gb_pm_runtime_put_autosuspend(ghid->bundle);
+
+ return ret;
+}
+
+static int gb_hid_set_power(struct gb_hid *ghid, int type)
+{
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(ghid->bundle);
+ if (ret)
+ return ret;
+
+ ret = gb_operation_sync(ghid->connection, type, NULL, 0, NULL, 0);
+
+ gb_pm_runtime_put_autosuspend(ghid->bundle);
+
+ return ret;
+}
+
+static int gb_hid_get_report(struct gb_hid *ghid, u8 report_type, u8 report_id,
+ unsigned char *buf, int len)
+{
+ struct gb_hid_get_report_request request;
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(ghid->bundle);
+ if (ret)
+ return ret;
+
+ request.report_type = report_type;
+ request.report_id = report_id;
+
+ ret = gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_REPORT,
+ &request, sizeof(request), buf, len);
+
+ gb_pm_runtime_put_autosuspend(ghid->bundle);
+
+ return ret;
+}
+
+static int gb_hid_set_report(struct gb_hid *ghid, u8 report_type, u8 report_id,
+ unsigned char *buf, int len)
+{
+ struct gb_hid_set_report_request *request;
+ struct gb_operation *operation;
+ int ret, size = sizeof(*request) + len - 1;
+
+ ret = gb_pm_runtime_get_sync(ghid->bundle);
+ if (ret)
+ return ret;
+
+ operation = gb_operation_create(ghid->connection,
+ GB_HID_TYPE_SET_REPORT, size, 0,
+ GFP_KERNEL);
+ if (!operation) {
+ gb_pm_runtime_put_autosuspend(ghid->bundle);
+ return -ENOMEM;
+ }
+
+ request = operation->request->payload;
+ request->report_type = report_type;
+ request->report_id = report_id;
+ memcpy(request->report, buf, len);
+
+ ret = gb_operation_request_send_sync(operation);
+ if (ret) {
+ dev_err(&operation->connection->bundle->dev,
+ "failed to set report: %d\n", ret);
+ } else {
+ ret = len;
+ }
+
+ gb_operation_put(operation);
+ gb_pm_runtime_put_autosuspend(ghid->bundle);
+
+ return ret;
+}
+
+static int gb_hid_request_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_hid *ghid = gb_connection_get_data(connection);
+ struct gb_hid_input_report_request *request = op->request->payload;
+
+ if (op->type != GB_HID_TYPE_IRQ_EVENT) {
+ dev_err(&connection->bundle->dev,
+ "unsupported unsolicited request\n");
+ return -EINVAL;
+ }
+
+ if (test_bit(GB_HID_STARTED, &ghid->flags))
+ hid_input_report(ghid->hid, HID_INPUT_REPORT,
+ request->report, op->request->payload_size, 1);
+
+ return 0;
+}
+
+static int gb_hid_report_len(struct hid_report *report)
+{
+ return ((report->size - 1) >> 3) + 1 +
+ report->device->report_enum[report->type].numbered;
+}
+
+static void gb_hid_find_max_report(struct hid_device *hid, unsigned int type,
+ unsigned int *max)
+{
+ struct hid_report *report;
+ unsigned int size;
+
+ list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
+ size = gb_hid_report_len(report);
+ if (*max < size)
+ *max = size;
+ }
+}
+
+static void gb_hid_free_buffers(struct gb_hid *ghid)
+{
+ kfree(ghid->inbuf);
+ ghid->inbuf = NULL;
+ ghid->bufsize = 0;
+}
+
+static int gb_hid_alloc_buffers(struct gb_hid *ghid, size_t bufsize)
+{
+ ghid->inbuf = kzalloc(bufsize, GFP_KERNEL);
+ if (!ghid->inbuf)
+ return -ENOMEM;
+
+ ghid->bufsize = bufsize;
+
+ return 0;
+}
+
+/* Routines dealing with reports */
+static void gb_hid_init_report(struct gb_hid *ghid, struct hid_report *report)
+{
+ unsigned int size;
+
+ size = gb_hid_report_len(report);
+ if (gb_hid_get_report(ghid, report->type, report->id, ghid->inbuf,
+ size))
+ return;
+
+ /*
+ * hid->driver_lock is held as we are in probe function,
+ * we just need to setup the input fields, so using
+ * hid_report_raw_event is safe.
+ */
+ hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, size, 1);
+}
+
+static void gb_hid_init_reports(struct gb_hid *ghid)
+{
+ struct hid_device *hid = ghid->hid;
+ struct hid_report *report;
+
+ list_for_each_entry(report,
+ &hid->report_enum[HID_INPUT_REPORT].report_list,
+ list)
+ gb_hid_init_report(ghid, report);
+
+ list_for_each_entry(report,
+ &hid->report_enum[HID_FEATURE_REPORT].report_list,
+ list)
+ gb_hid_init_report(ghid, report);
+}
+
+static int __gb_hid_get_raw_report(struct hid_device *hid,
+ unsigned char report_number, __u8 *buf, size_t count,
+ unsigned char report_type)
+{
+ struct gb_hid *ghid = hid->driver_data;
+ int ret;
+
+ if (report_type == HID_OUTPUT_REPORT)
+ return -EINVAL;
+
+ ret = gb_hid_get_report(ghid, report_type, report_number, buf, count);
+ if (!ret)
+ ret = count;
+
+ return ret;
+}
+
+static int __gb_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
+ size_t len, unsigned char report_type)
+{
+ struct gb_hid *ghid = hid->driver_data;
+ int report_id = buf[0];
+ int ret;
+
+ if (report_type == HID_INPUT_REPORT)
+ return -EINVAL;
+
+ if (report_id) {
+ buf++;
+ len--;
+ }
+
+ ret = gb_hid_set_report(ghid, report_type, report_id, buf, len);
+ if (report_id && ret >= 0)
+ ret++; /* add report_id to the number of transferred bytes */
+
+ return 0;
+}
+
+static int gb_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
+ __u8 *buf, size_t len, unsigned char rtype,
+ int reqtype)
+{
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ return __gb_hid_get_raw_report(hid, reportnum, buf, len, rtype);
+ case HID_REQ_SET_REPORT:
+ if (buf[0] != reportnum)
+ return -EINVAL;
+ return __gb_hid_output_raw_report(hid, buf, len, rtype);
+ default:
+ return -EIO;
+ }
+}
+
+/* HID Callbacks */
+static int gb_hid_parse(struct hid_device *hid)
+{
+ struct gb_hid *ghid = hid->driver_data;
+ unsigned int rsize;
+ char *rdesc;
+ int ret;
+
+ rsize = le16_to_cpu(ghid->hdesc.wReportDescLength);
+ if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
+ dbg_hid("weird size of report descriptor (%u)\n", rsize);
+ return -EINVAL;
+ }
+
+ rdesc = kzalloc(rsize, GFP_KERNEL);
+ if (!rdesc)
+ return -ENOMEM;
+
+ ret = gb_hid_get_report_desc(ghid, rdesc);
+ if (ret) {
+ hid_err(hid, "reading report descriptor failed\n");
+ goto free_rdesc;
+ }
+
+ ret = hid_parse_report(hid, rdesc, rsize);
+ if (ret)
+ dbg_hid("parsing report descriptor failed\n");
+
+free_rdesc:
+ kfree(rdesc);
+
+ return ret;
+}
+
+static int gb_hid_start(struct hid_device *hid)
+{
+ struct gb_hid *ghid = hid->driver_data;
+ unsigned int bufsize = HID_MIN_BUFFER_SIZE;
+ int ret;
+
+ gb_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize);
+ gb_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize);
+ gb_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize);
+
+ if (bufsize > HID_MAX_BUFFER_SIZE)
+ bufsize = HID_MAX_BUFFER_SIZE;
+
+ ret = gb_hid_alloc_buffers(ghid, bufsize);
+ if (ret)
+ return ret;
+
+ if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS))
+ gb_hid_init_reports(ghid);
+
+ return 0;
+}
+
+static void gb_hid_stop(struct hid_device *hid)
+{
+ struct gb_hid *ghid = hid->driver_data;
+
+ gb_hid_free_buffers(ghid);
+}
+
+static int gb_hid_open(struct hid_device *hid)
+{
+ struct gb_hid *ghid = hid->driver_data;
+ int ret;
+
+ ret = gb_hid_set_power(ghid, GB_HID_TYPE_PWR_ON);
+ if (ret < 0)
+ return ret;
+
+ set_bit(GB_HID_STARTED, &ghid->flags);
+ return 0;
+}
+
+static void gb_hid_close(struct hid_device *hid)
+{
+ struct gb_hid *ghid = hid->driver_data;
+ int ret;
+
+ clear_bit(GB_HID_STARTED, &ghid->flags);
+
+ /* Save some power */
+ ret = gb_hid_set_power(ghid, GB_HID_TYPE_PWR_OFF);
+ if (ret)
+ dev_err(&ghid->connection->bundle->dev,
+ "failed to power off (%d)\n", ret);
+}
+
+static int gb_hid_power(struct hid_device *hid, int lvl)
+{
+ struct gb_hid *ghid = hid->driver_data;
+
+ switch (lvl) {
+ case PM_HINT_FULLON:
+ return gb_hid_set_power(ghid, GB_HID_TYPE_PWR_ON);
+ case PM_HINT_NORMAL:
+ return gb_hid_set_power(ghid, GB_HID_TYPE_PWR_OFF);
+ }
+
+ return 0;
+}
+
+/* HID structure to pass callbacks */
+static const struct hid_ll_driver gb_hid_ll_driver = {
+ .parse = gb_hid_parse,
+ .start = gb_hid_start,
+ .stop = gb_hid_stop,
+ .open = gb_hid_open,
+ .close = gb_hid_close,
+ .power = gb_hid_power,
+ .raw_request = gb_hid_raw_request,
+};
+
+static int gb_hid_init(struct gb_hid *ghid)
+{
+ struct hid_device *hid = ghid->hid;
+ int ret;
+
+ ret = gb_hid_get_desc(ghid);
+ if (ret)
+ return ret;
+
+ hid->version = le16_to_cpu(ghid->hdesc.bcdHID);
+ hid->vendor = le16_to_cpu(ghid->hdesc.wVendorID);
+ hid->product = le16_to_cpu(ghid->hdesc.wProductID);
+ hid->country = ghid->hdesc.bCountryCode;
+
+ hid->driver_data = ghid;
+ hid->ll_driver = &gb_hid_ll_driver;
+ hid->dev.parent = &ghid->connection->bundle->dev;
+// hid->bus = BUS_GREYBUS; /* Need a bustype for GREYBUS in <linux/input.h> */
+
+ /* Set HID device's name */
+ snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X",
+ dev_name(&ghid->connection->bundle->dev),
+ hid->vendor, hid->product);
+
+ return 0;
+}
+
+static int gb_hid_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct hid_device *hid;
+ struct gb_hid *ghid;
+ int ret;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_HID)
+ return -ENODEV;
+
+ ghid = kzalloc(sizeof(*ghid), GFP_KERNEL);
+ if (!ghid)
+ return -ENOMEM;
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ gb_hid_request_handler);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ goto err_free_ghid;
+ }
+
+ gb_connection_set_data(connection, ghid);
+ ghid->connection = connection;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid)) {
+ ret = PTR_ERR(hid);
+ goto err_connection_destroy;
+ }
+
+ ghid->hid = hid;
+ ghid->bundle = bundle;
+
+ greybus_set_drvdata(bundle, ghid);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto err_destroy_hid;
+
+ ret = gb_hid_init(ghid);
+ if (ret)
+ goto err_connection_disable;
+
+ ret = hid_add_device(hid);
+ if (ret) {
+ hid_err(hid, "can't add hid device: %d\n", ret);
+ goto err_connection_disable;
+ }
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+
+err_connection_disable:
+ gb_connection_disable(connection);
+err_destroy_hid:
+ hid_destroy_device(hid);
+err_connection_destroy:
+ gb_connection_destroy(connection);
+err_free_ghid:
+ kfree(ghid);
+
+ return ret;
+}
+
+static void gb_hid_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_hid *ghid = greybus_get_drvdata(bundle);
+
+ if (gb_pm_runtime_get_sync(bundle))
+ gb_pm_runtime_get_noresume(bundle);
+
+ hid_destroy_device(ghid->hid);
+ gb_connection_disable(ghid->connection);
+ gb_connection_destroy(ghid->connection);
+ kfree(ghid);
+}
+
+static const struct greybus_bundle_id gb_hid_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_HID) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_hid_id_table);
+
+static struct greybus_driver gb_hid_driver = {
+ .name = "hid",
+ .probe = gb_hid_probe,
+ .disconnect = gb_hid_disconnect,
+ .id_table = gb_hid_id_table,
+};
+module_greybus_driver(gb_hid_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/i2c.c b/drivers/staging/greybus/i2c.c
new file mode 100644
index 0000000000..de2f6516da
--- /dev/null
+++ b/drivers/staging/greybus/i2c.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * I2C bridge driver for the Greybus "generic" I2C module.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/greybus.h>
+
+#include "gbphy.h"
+
+struct gb_i2c_device {
+ struct gb_connection *connection;
+ struct gbphy_device *gbphy_dev;
+
+ u32 functionality;
+
+ struct i2c_adapter adapter;
+};
+
+/*
+ * Map Greybus i2c functionality bits into Linux ones
+ */
+static u32 gb_i2c_functionality_map(u32 gb_i2c_functionality)
+{
+ return gb_i2c_functionality; /* All bits the same for now */
+}
+
+/*
+ * Do initial setup of the i2c device. This includes verifying we
+ * can support it (based on the protocol version it advertises).
+ * If that's OK, we get and cached its functionality bits.
+ *
+ * Note: gb_i2c_dev->connection is assumed to have been valid.
+ */
+static int gb_i2c_device_setup(struct gb_i2c_device *gb_i2c_dev)
+{
+ struct gb_i2c_functionality_response response;
+ u32 functionality;
+ int ret;
+
+ ret = gb_operation_sync(gb_i2c_dev->connection,
+ GB_I2C_TYPE_FUNCTIONALITY,
+ NULL, 0, &response, sizeof(response));
+ if (ret)
+ return ret;
+
+ functionality = le32_to_cpu(response.functionality);
+ gb_i2c_dev->functionality = gb_i2c_functionality_map(functionality);
+
+ return 0;
+}
+
+/*
+ * Map Linux i2c_msg flags into Greybus i2c transfer op flags.
+ */
+static u16 gb_i2c_transfer_op_flags_map(u16 flags)
+{
+ return flags; /* All flags the same for now */
+}
+
+static void
+gb_i2c_fill_transfer_op(struct gb_i2c_transfer_op *op, struct i2c_msg *msg)
+{
+ u16 flags = gb_i2c_transfer_op_flags_map(msg->flags);
+
+ op->addr = cpu_to_le16(msg->addr);
+ op->flags = cpu_to_le16(flags);
+ op->size = cpu_to_le16(msg->len);
+}
+
+static struct gb_operation *
+gb_i2c_operation_create(struct gb_connection *connection,
+ struct i2c_msg *msgs, u32 msg_count)
+{
+ struct gb_i2c_device *gb_i2c_dev = gb_connection_get_data(connection);
+ struct gb_i2c_transfer_request *request;
+ struct gb_operation *operation;
+ struct gb_i2c_transfer_op *op;
+ struct i2c_msg *msg;
+ u32 data_out_size = 0;
+ u32 data_in_size = 0;
+ size_t request_size;
+ void *data;
+ u16 op_count;
+ u32 i;
+
+ if (msg_count > (u32)U16_MAX) {
+ dev_err(&gb_i2c_dev->gbphy_dev->dev, "msg_count (%u) too big\n",
+ msg_count);
+ return NULL;
+ }
+ op_count = (u16)msg_count;
+
+ /*
+ * In addition to space for all message descriptors we need
+ * to have enough to hold all outbound message data.
+ */
+ msg = msgs;
+ for (i = 0; i < msg_count; i++, msg++)
+ if (msg->flags & I2C_M_RD)
+ data_in_size += (u32)msg->len;
+ else
+ data_out_size += (u32)msg->len;
+
+ request_size = sizeof(*request);
+ request_size += msg_count * sizeof(*op);
+ request_size += data_out_size;
+
+ /* Response consists only of incoming data */
+ operation = gb_operation_create(connection, GB_I2C_TYPE_TRANSFER,
+ request_size, data_in_size, GFP_KERNEL);
+ if (!operation)
+ return NULL;
+
+ request = operation->request->payload;
+ request->op_count = cpu_to_le16(op_count);
+ /* Fill in the ops array */
+ op = &request->ops[0];
+ msg = msgs;
+ for (i = 0; i < msg_count; i++)
+ gb_i2c_fill_transfer_op(op++, msg++);
+
+ if (!data_out_size)
+ return operation;
+
+ /* Copy over the outgoing data; it starts after the last op */
+ data = op;
+ msg = msgs;
+ for (i = 0; i < msg_count; i++) {
+ if (!(msg->flags & I2C_M_RD)) {
+ memcpy(data, msg->buf, msg->len);
+ data += msg->len;
+ }
+ msg++;
+ }
+
+ return operation;
+}
+
+static void gb_i2c_decode_response(struct i2c_msg *msgs, u32 msg_count,
+ struct gb_i2c_transfer_response *response)
+{
+ struct i2c_msg *msg = msgs;
+ u8 *data;
+ u32 i;
+
+ if (!response)
+ return;
+ data = response->data;
+ for (i = 0; i < msg_count; i++) {
+ if (msg->flags & I2C_M_RD) {
+ memcpy(msg->buf, data, msg->len);
+ data += msg->len;
+ }
+ msg++;
+ }
+}
+
+/*
+ * Some i2c transfer operations return results that are expected.
+ */
+static bool gb_i2c_expected_transfer_error(int errno)
+{
+ return errno == -EAGAIN || errno == -ENODEV;
+}
+
+static int gb_i2c_transfer_operation(struct gb_i2c_device *gb_i2c_dev,
+ struct i2c_msg *msgs, u32 msg_count)
+{
+ struct gb_connection *connection = gb_i2c_dev->connection;
+ struct device *dev = &gb_i2c_dev->gbphy_dev->dev;
+ struct gb_operation *operation;
+ int ret;
+
+ operation = gb_i2c_operation_create(connection, msgs, msg_count);
+ if (!operation)
+ return -ENOMEM;
+
+ ret = gbphy_runtime_get_sync(gb_i2c_dev->gbphy_dev);
+ if (ret)
+ goto exit_operation_put;
+
+ ret = gb_operation_request_send_sync(operation);
+ if (!ret) {
+ struct gb_i2c_transfer_response *response;
+
+ response = operation->response->payload;
+ gb_i2c_decode_response(msgs, msg_count, response);
+ ret = msg_count;
+ } else if (!gb_i2c_expected_transfer_error(ret)) {
+ dev_err(dev, "transfer operation failed (%d)\n", ret);
+ }
+
+ gbphy_runtime_put_autosuspend(gb_i2c_dev->gbphy_dev);
+
+exit_operation_put:
+ gb_operation_put(operation);
+
+ return ret;
+}
+
+static int gb_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+ int msg_count)
+{
+ struct gb_i2c_device *gb_i2c_dev;
+
+ gb_i2c_dev = i2c_get_adapdata(adap);
+
+ return gb_i2c_transfer_operation(gb_i2c_dev, msgs, msg_count);
+}
+
+static u32 gb_i2c_functionality(struct i2c_adapter *adap)
+{
+ struct gb_i2c_device *gb_i2c_dev = i2c_get_adapdata(adap);
+
+ return gb_i2c_dev->functionality;
+}
+
+static const struct i2c_algorithm gb_i2c_algorithm = {
+ .master_xfer = gb_i2c_master_xfer,
+ .functionality = gb_i2c_functionality,
+};
+
+static int gb_i2c_probe(struct gbphy_device *gbphy_dev,
+ const struct gbphy_device_id *id)
+{
+ struct gb_connection *connection;
+ struct gb_i2c_device *gb_i2c_dev;
+ struct i2c_adapter *adapter;
+ int ret;
+
+ gb_i2c_dev = kzalloc(sizeof(*gb_i2c_dev), GFP_KERNEL);
+ if (!gb_i2c_dev)
+ return -ENOMEM;
+
+ connection =
+ gb_connection_create(gbphy_dev->bundle,
+ le16_to_cpu(gbphy_dev->cport_desc->id),
+ NULL);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ goto exit_i2cdev_free;
+ }
+
+ gb_i2c_dev->connection = connection;
+ gb_connection_set_data(connection, gb_i2c_dev);
+ gb_i2c_dev->gbphy_dev = gbphy_dev;
+ gb_gbphy_set_data(gbphy_dev, gb_i2c_dev);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto exit_connection_destroy;
+
+ ret = gb_i2c_device_setup(gb_i2c_dev);
+ if (ret)
+ goto exit_connection_disable;
+
+ /* Looks good; up our i2c adapter */
+ adapter = &gb_i2c_dev->adapter;
+ adapter->owner = THIS_MODULE;
+ adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+ adapter->algo = &gb_i2c_algorithm;
+
+ adapter->dev.parent = &gbphy_dev->dev;
+ snprintf(adapter->name, sizeof(adapter->name), "Greybus i2c adapter");
+ i2c_set_adapdata(adapter, gb_i2c_dev);
+
+ ret = i2c_add_adapter(adapter);
+ if (ret)
+ goto exit_connection_disable;
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+ return 0;
+
+exit_connection_disable:
+ gb_connection_disable(connection);
+exit_connection_destroy:
+ gb_connection_destroy(connection);
+exit_i2cdev_free:
+ kfree(gb_i2c_dev);
+
+ return ret;
+}
+
+static void gb_i2c_remove(struct gbphy_device *gbphy_dev)
+{
+ struct gb_i2c_device *gb_i2c_dev = gb_gbphy_get_data(gbphy_dev);
+ struct gb_connection *connection = gb_i2c_dev->connection;
+ int ret;
+
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ gbphy_runtime_get_noresume(gbphy_dev);
+
+ i2c_del_adapter(&gb_i2c_dev->adapter);
+ gb_connection_disable(connection);
+ gb_connection_destroy(connection);
+ kfree(gb_i2c_dev);
+}
+
+static const struct gbphy_device_id gb_i2c_id_table[] = {
+ { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_I2C) },
+ { },
+};
+MODULE_DEVICE_TABLE(gbphy, gb_i2c_id_table);
+
+static struct gbphy_driver i2c_driver = {
+ .name = "i2c",
+ .probe = gb_i2c_probe,
+ .remove = gb_i2c_remove,
+ .id_table = gb_i2c_id_table,
+};
+
+module_gbphy_driver(i2c_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/light.c b/drivers/staging/greybus/light.c
new file mode 100644
index 0000000000..87d36948c6
--- /dev/null
+++ b/drivers/staging/greybus/light.c
@@ -0,0 +1,1357 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Lights protocol driver.
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/greybus.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define NAMES_MAX 32
+
+struct gb_channel {
+ u8 id;
+ u32 flags;
+ u32 color;
+ char *color_name;
+ u8 fade_in;
+ u8 fade_out;
+ u32 mode;
+ char *mode_name;
+ struct attribute **attrs;
+ struct attribute_group *attr_group;
+ const struct attribute_group **attr_groups;
+ struct led_classdev *led;
+#if IS_REACHABLE(CONFIG_LEDS_CLASS_FLASH)
+ struct led_classdev_flash fled;
+ struct led_flash_setting intensity_uA;
+ struct led_flash_setting timeout_us;
+#else
+ struct led_classdev cled;
+#endif
+ struct gb_light *light;
+ bool is_registered;
+ bool releasing;
+ bool strobe_state;
+ bool active;
+ struct mutex lock;
+};
+
+struct gb_light {
+ u8 id;
+ char *name;
+ struct gb_lights *glights;
+ u32 flags;
+ u8 channels_count;
+ struct gb_channel *channels;
+ bool has_flash;
+ bool ready;
+#if IS_REACHABLE(CONFIG_V4L2_FLASH_LED_CLASS)
+ struct v4l2_flash *v4l2_flash;
+ struct v4l2_flash *v4l2_flash_ind;
+#endif
+};
+
+struct gb_lights {
+ struct gb_connection *connection;
+ u8 lights_count;
+ struct gb_light *lights;
+ struct mutex lights_lock;
+};
+
+static void gb_lights_channel_free(struct gb_channel *channel);
+
+static struct gb_connection *get_conn_from_channel(struct gb_channel *channel)
+{
+ return channel->light->glights->connection;
+}
+
+static struct gb_connection *get_conn_from_light(struct gb_light *light)
+{
+ return light->glights->connection;
+}
+
+static bool is_channel_flash(struct gb_channel *channel)
+{
+ return !!(channel->mode & (GB_CHANNEL_MODE_FLASH | GB_CHANNEL_MODE_TORCH
+ | GB_CHANNEL_MODE_INDICATOR));
+}
+
+#if IS_REACHABLE(CONFIG_LEDS_CLASS_FLASH)
+static struct gb_channel *get_channel_from_cdev(struct led_classdev *cdev)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev);
+
+ return container_of(fled_cdev, struct gb_channel, fled);
+}
+
+static struct led_classdev *get_channel_cdev(struct gb_channel *channel)
+{
+ return &channel->fled.led_cdev;
+}
+
+static struct gb_channel *get_channel_from_mode(struct gb_light *light,
+ u32 mode)
+{
+ struct gb_channel *channel = NULL;
+ int i;
+
+ for (i = 0; i < light->channels_count; i++) {
+ channel = &light->channels[i];
+ if (channel && channel->mode == mode)
+ break;
+ }
+ return channel;
+}
+
+static int __gb_lights_flash_intensity_set(struct gb_channel *channel,
+ u32 intensity)
+{
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_bundle *bundle = connection->bundle;
+ struct gb_lights_set_flash_intensity_request req;
+ int ret;
+
+ if (channel->releasing)
+ return -ESHUTDOWN;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ return ret;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+ req.intensity_uA = cpu_to_le32(intensity);
+
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_FLASH_INTENSITY,
+ &req, sizeof(req), NULL, 0);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static int __gb_lights_flash_brightness_set(struct gb_channel *channel)
+{
+ u32 intensity;
+
+ /* If the channel is flash we need to get the attached torch channel */
+ if (channel->mode & GB_CHANNEL_MODE_FLASH)
+ channel = get_channel_from_mode(channel->light,
+ GB_CHANNEL_MODE_TORCH);
+
+ /* For not flash we need to convert brightness to intensity */
+ intensity = channel->intensity_uA.min +
+ (channel->intensity_uA.step * channel->led->brightness);
+
+ return __gb_lights_flash_intensity_set(channel, intensity);
+}
+#else
+static struct gb_channel *get_channel_from_cdev(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct gb_channel, cled);
+}
+
+static struct led_classdev *get_channel_cdev(struct gb_channel *channel)
+{
+ return &channel->cled;
+}
+
+static int __gb_lights_flash_brightness_set(struct gb_channel *channel)
+{
+ return 0;
+}
+#endif
+
+static int gb_lights_color_set(struct gb_channel *channel, u32 color);
+static int gb_lights_fade_set(struct gb_channel *channel);
+
+static void led_lock(struct led_classdev *cdev)
+{
+ mutex_lock(&cdev->led_access);
+}
+
+static void led_unlock(struct led_classdev *cdev)
+{
+ mutex_unlock(&cdev->led_access);
+}
+
+#define gb_lights_fade_attr(__dir) \
+static ssize_t fade_##__dir##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct led_classdev *cdev = dev_get_drvdata(dev); \
+ struct gb_channel *channel = get_channel_from_cdev(cdev); \
+ \
+ return sprintf(buf, "%u\n", channel->fade_##__dir); \
+} \
+ \
+static ssize_t fade_##__dir##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t size) \
+{ \
+ struct led_classdev *cdev = dev_get_drvdata(dev); \
+ struct gb_channel *channel = get_channel_from_cdev(cdev); \
+ u8 fade; \
+ int ret; \
+ \
+ led_lock(cdev); \
+ if (led_sysfs_is_disabled(cdev)) { \
+ ret = -EBUSY; \
+ goto unlock; \
+ } \
+ \
+ ret = kstrtou8(buf, 0, &fade); \
+ if (ret < 0) { \
+ dev_err(dev, "could not parse fade value %d\n", ret); \
+ goto unlock; \
+ } \
+ if (channel->fade_##__dir == fade) \
+ goto unlock; \
+ channel->fade_##__dir = fade; \
+ \
+ ret = gb_lights_fade_set(channel); \
+ if (ret < 0) \
+ goto unlock; \
+ \
+ ret = size; \
+unlock: \
+ led_unlock(cdev); \
+ return ret; \
+} \
+static DEVICE_ATTR_RW(fade_##__dir)
+
+gb_lights_fade_attr(in);
+gb_lights_fade_attr(out);
+
+static ssize_t color_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct gb_channel *channel = get_channel_from_cdev(cdev);
+
+ return sprintf(buf, "0x%08x\n", channel->color);
+}
+
+static ssize_t color_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct gb_channel *channel = get_channel_from_cdev(cdev);
+ u32 color;
+ int ret;
+
+ led_lock(cdev);
+ if (led_sysfs_is_disabled(cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+ ret = kstrtou32(buf, 0, &color);
+ if (ret < 0) {
+ dev_err(dev, "could not parse color value %d\n", ret);
+ goto unlock;
+ }
+
+ ret = gb_lights_color_set(channel, color);
+ if (ret < 0)
+ goto unlock;
+
+ channel->color = color;
+ ret = size;
+unlock:
+ led_unlock(cdev);
+ return ret;
+}
+static DEVICE_ATTR_RW(color);
+
+static int channel_attr_groups_set(struct gb_channel *channel,
+ struct led_classdev *cdev)
+{
+ int attr = 0;
+ int size = 0;
+
+ if (channel->flags & GB_LIGHT_CHANNEL_MULTICOLOR)
+ size++;
+ if (channel->flags & GB_LIGHT_CHANNEL_FADER)
+ size += 2;
+
+ if (!size)
+ return 0;
+
+ /* Set attributes based in the channel flags */
+ channel->attrs = kcalloc(size + 1, sizeof(*channel->attrs), GFP_KERNEL);
+ if (!channel->attrs)
+ return -ENOMEM;
+ channel->attr_group = kzalloc(sizeof(*channel->attr_group), GFP_KERNEL);
+ if (!channel->attr_group)
+ return -ENOMEM;
+ channel->attr_groups = kcalloc(2, sizeof(*channel->attr_groups),
+ GFP_KERNEL);
+ if (!channel->attr_groups)
+ return -ENOMEM;
+
+ if (channel->flags & GB_LIGHT_CHANNEL_MULTICOLOR)
+ channel->attrs[attr++] = &dev_attr_color.attr;
+ if (channel->flags & GB_LIGHT_CHANNEL_FADER) {
+ channel->attrs[attr++] = &dev_attr_fade_in.attr;
+ channel->attrs[attr++] = &dev_attr_fade_out.attr;
+ }
+
+ channel->attr_group->attrs = channel->attrs;
+
+ channel->attr_groups[0] = channel->attr_group;
+
+ cdev->groups = channel->attr_groups;
+
+ return 0;
+}
+
+static int gb_lights_fade_set(struct gb_channel *channel)
+{
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_bundle *bundle = connection->bundle;
+ struct gb_lights_set_fade_request req;
+ int ret;
+
+ if (channel->releasing)
+ return -ESHUTDOWN;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ return ret;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+ req.fade_in = channel->fade_in;
+ req.fade_out = channel->fade_out;
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_FADE,
+ &req, sizeof(req), NULL, 0);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static int gb_lights_color_set(struct gb_channel *channel, u32 color)
+{
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_bundle *bundle = connection->bundle;
+ struct gb_lights_set_color_request req;
+ int ret;
+
+ if (channel->releasing)
+ return -ESHUTDOWN;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ return ret;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+ req.color = cpu_to_le32(color);
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_COLOR,
+ &req, sizeof(req), NULL, 0);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static int __gb_lights_led_brightness_set(struct gb_channel *channel)
+{
+ struct gb_lights_set_brightness_request req;
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_bundle *bundle = connection->bundle;
+ bool old_active;
+ int ret;
+
+ mutex_lock(&channel->lock);
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ goto out_unlock;
+
+ old_active = channel->active;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+ req.brightness = (u8)channel->led->brightness;
+
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_BRIGHTNESS,
+ &req, sizeof(req), NULL, 0);
+ if (ret < 0)
+ goto out_pm_put;
+
+ if (channel->led->brightness)
+ channel->active = true;
+ else
+ channel->active = false;
+
+ /* we need to keep module alive when turning to active state */
+ if (!old_active && channel->active)
+ goto out_unlock;
+
+ /*
+ * on the other hand if going to inactive we still hold a reference and
+ * need to put it, so we could go to suspend.
+ */
+ if (old_active && !channel->active)
+ gb_pm_runtime_put_autosuspend(bundle);
+
+out_pm_put:
+ gb_pm_runtime_put_autosuspend(bundle);
+out_unlock:
+ mutex_unlock(&channel->lock);
+
+ return ret;
+}
+
+static int __gb_lights_brightness_set(struct gb_channel *channel)
+{
+ int ret;
+
+ if (channel->releasing)
+ return 0;
+
+ if (is_channel_flash(channel))
+ ret = __gb_lights_flash_brightness_set(channel);
+ else
+ ret = __gb_lights_led_brightness_set(channel);
+
+ return ret;
+}
+
+static int gb_brightness_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct gb_channel *channel = get_channel_from_cdev(cdev);
+
+ channel->led->brightness = value;
+
+ return __gb_lights_brightness_set(channel);
+}
+
+static enum led_brightness gb_brightness_get(struct led_classdev *cdev)
+
+{
+ struct gb_channel *channel = get_channel_from_cdev(cdev);
+
+ return channel->led->brightness;
+}
+
+static int gb_blink_set(struct led_classdev *cdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct gb_channel *channel = get_channel_from_cdev(cdev);
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_bundle *bundle = connection->bundle;
+ struct gb_lights_blink_request req;
+ bool old_active;
+ int ret;
+
+ if (channel->releasing)
+ return -ESHUTDOWN;
+
+ if (!delay_on || !delay_off)
+ return -EINVAL;
+
+ mutex_lock(&channel->lock);
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ goto out_unlock;
+
+ old_active = channel->active;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+ req.time_on_ms = cpu_to_le16(*delay_on);
+ req.time_off_ms = cpu_to_le16(*delay_off);
+
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_BLINK, &req,
+ sizeof(req), NULL, 0);
+ if (ret < 0)
+ goto out_pm_put;
+
+ if (*delay_on)
+ channel->active = true;
+ else
+ channel->active = false;
+
+ /* we need to keep module alive when turning to active state */
+ if (!old_active && channel->active)
+ goto out_unlock;
+
+ /*
+ * on the other hand if going to inactive we still hold a reference and
+ * need to put it, so we could go to suspend.
+ */
+ if (old_active && !channel->active)
+ gb_pm_runtime_put_autosuspend(bundle);
+
+out_pm_put:
+ gb_pm_runtime_put_autosuspend(bundle);
+out_unlock:
+ mutex_unlock(&channel->lock);
+
+ return ret;
+}
+
+static void gb_lights_led_operations_set(struct gb_channel *channel,
+ struct led_classdev *cdev)
+{
+ cdev->brightness_get = gb_brightness_get;
+ cdev->brightness_set_blocking = gb_brightness_set;
+
+ if (channel->flags & GB_LIGHT_CHANNEL_BLINK)
+ cdev->blink_set = gb_blink_set;
+}
+
+#if IS_REACHABLE(CONFIG_V4L2_FLASH_LED_CLASS)
+/* V4L2 specific helpers */
+static const struct v4l2_flash_ops v4l2_flash_ops;
+
+static void __gb_lights_channel_v4l2_config(struct led_flash_setting *channel_s,
+ struct led_flash_setting *v4l2_s)
+{
+ v4l2_s->min = channel_s->min;
+ v4l2_s->max = channel_s->max;
+ v4l2_s->step = channel_s->step;
+ /* For v4l2 val is the default value */
+ v4l2_s->val = channel_s->max;
+}
+
+static int gb_lights_light_v4l2_register(struct gb_light *light)
+{
+ struct gb_connection *connection = get_conn_from_light(light);
+ struct device *dev = &connection->bundle->dev;
+ struct v4l2_flash_config sd_cfg = { {0} }, sd_cfg_ind = { {0} };
+ struct led_classdev_flash *fled;
+ struct led_classdev *iled = NULL;
+ struct gb_channel *channel_torch, *channel_ind, *channel_flash;
+
+ channel_torch = get_channel_from_mode(light, GB_CHANNEL_MODE_TORCH);
+ if (channel_torch)
+ __gb_lights_channel_v4l2_config(&channel_torch->intensity_uA,
+ &sd_cfg.intensity);
+
+ channel_ind = get_channel_from_mode(light, GB_CHANNEL_MODE_INDICATOR);
+ if (channel_ind) {
+ __gb_lights_channel_v4l2_config(&channel_ind->intensity_uA,
+ &sd_cfg_ind.intensity);
+ iled = &channel_ind->fled.led_cdev;
+ }
+
+ channel_flash = get_channel_from_mode(light, GB_CHANNEL_MODE_FLASH);
+ WARN_ON(!channel_flash);
+
+ fled = &channel_flash->fled;
+
+ snprintf(sd_cfg.dev_name, sizeof(sd_cfg.dev_name), "%s", light->name);
+ snprintf(sd_cfg_ind.dev_name, sizeof(sd_cfg_ind.dev_name),
+ "%s indicator", light->name);
+
+ /* Set the possible values to faults, in our case all faults */
+ sd_cfg.flash_faults = LED_FAULT_OVER_VOLTAGE | LED_FAULT_TIMEOUT |
+ LED_FAULT_OVER_TEMPERATURE | LED_FAULT_SHORT_CIRCUIT |
+ LED_FAULT_OVER_CURRENT | LED_FAULT_INDICATOR |
+ LED_FAULT_UNDER_VOLTAGE | LED_FAULT_INPUT_VOLTAGE |
+ LED_FAULT_LED_OVER_TEMPERATURE;
+
+ light->v4l2_flash = v4l2_flash_init(dev, NULL, fled, &v4l2_flash_ops,
+ &sd_cfg);
+ if (IS_ERR(light->v4l2_flash))
+ return PTR_ERR(light->v4l2_flash);
+
+ if (channel_ind) {
+ light->v4l2_flash_ind =
+ v4l2_flash_indicator_init(dev, NULL, iled, &sd_cfg_ind);
+ if (IS_ERR(light->v4l2_flash_ind)) {
+ v4l2_flash_release(light->v4l2_flash);
+ return PTR_ERR(light->v4l2_flash_ind);
+ }
+ }
+
+ return 0;
+}
+
+static void gb_lights_light_v4l2_unregister(struct gb_light *light)
+{
+ v4l2_flash_release(light->v4l2_flash_ind);
+ v4l2_flash_release(light->v4l2_flash);
+}
+#else
+static int gb_lights_light_v4l2_register(struct gb_light *light)
+{
+ struct gb_connection *connection = get_conn_from_light(light);
+
+ dev_err(&connection->bundle->dev, "no support for v4l2 subdevices\n");
+ return 0;
+}
+
+static void gb_lights_light_v4l2_unregister(struct gb_light *light)
+{
+}
+#endif
+
+#if IS_REACHABLE(CONFIG_LEDS_CLASS_FLASH)
+/* Flash specific operations */
+static int gb_lights_flash_intensity_set(struct led_classdev_flash *fcdev,
+ u32 brightness)
+{
+ struct gb_channel *channel = container_of(fcdev, struct gb_channel,
+ fled);
+ int ret;
+
+ ret = __gb_lights_flash_intensity_set(channel, brightness);
+ if (ret < 0)
+ return ret;
+
+ fcdev->brightness.val = brightness;
+
+ return 0;
+}
+
+static int gb_lights_flash_intensity_get(struct led_classdev_flash *fcdev,
+ u32 *brightness)
+{
+ *brightness = fcdev->brightness.val;
+
+ return 0;
+}
+
+static int gb_lights_flash_strobe_set(struct led_classdev_flash *fcdev,
+ bool state)
+{
+ struct gb_channel *channel = container_of(fcdev, struct gb_channel,
+ fled);
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_bundle *bundle = connection->bundle;
+ struct gb_lights_set_flash_strobe_request req;
+ int ret;
+
+ if (channel->releasing)
+ return -ESHUTDOWN;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ return ret;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+ req.state = state ? 1 : 0;
+
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_FLASH_STROBE,
+ &req, sizeof(req), NULL, 0);
+ if (!ret)
+ channel->strobe_state = state;
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static int gb_lights_flash_strobe_get(struct led_classdev_flash *fcdev,
+ bool *state)
+{
+ struct gb_channel *channel = container_of(fcdev, struct gb_channel,
+ fled);
+
+ *state = channel->strobe_state;
+ return 0;
+}
+
+static int gb_lights_flash_timeout_set(struct led_classdev_flash *fcdev,
+ u32 timeout)
+{
+ struct gb_channel *channel = container_of(fcdev, struct gb_channel,
+ fled);
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_bundle *bundle = connection->bundle;
+ struct gb_lights_set_flash_timeout_request req;
+ int ret;
+
+ if (channel->releasing)
+ return -ESHUTDOWN;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ return ret;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+ req.timeout_us = cpu_to_le32(timeout);
+
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_SET_FLASH_TIMEOUT,
+ &req, sizeof(req), NULL, 0);
+ if (!ret)
+ fcdev->timeout.val = timeout;
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static int gb_lights_flash_fault_get(struct led_classdev_flash *fcdev,
+ u32 *fault)
+{
+ struct gb_channel *channel = container_of(fcdev, struct gb_channel,
+ fled);
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_bundle *bundle = connection->bundle;
+ struct gb_lights_get_flash_fault_request req;
+ struct gb_lights_get_flash_fault_response resp;
+ int ret;
+
+ if (channel->releasing)
+ return -ESHUTDOWN;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret < 0)
+ return ret;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_GET_FLASH_FAULT,
+ &req, sizeof(req), &resp, sizeof(resp));
+ if (!ret)
+ *fault = le32_to_cpu(resp.fault);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static const struct led_flash_ops gb_lights_flash_ops = {
+ .flash_brightness_set = gb_lights_flash_intensity_set,
+ .flash_brightness_get = gb_lights_flash_intensity_get,
+ .strobe_set = gb_lights_flash_strobe_set,
+ .strobe_get = gb_lights_flash_strobe_get,
+ .timeout_set = gb_lights_flash_timeout_set,
+ .fault_get = gb_lights_flash_fault_get,
+};
+
+static int __gb_lights_channel_torch_attach(struct gb_channel *channel,
+ struct gb_channel *channel_torch)
+{
+ char *name;
+
+ /* we can only attach torch to a flash channel */
+ if (!(channel->mode & GB_CHANNEL_MODE_FLASH))
+ return 0;
+
+ /* Move torch brightness to the destination */
+ channel->led->max_brightness = channel_torch->led->max_brightness;
+
+ /* append mode name to flash name */
+ name = kasprintf(GFP_KERNEL, "%s_%s", channel->led->name,
+ channel_torch->mode_name);
+ if (!name)
+ return -ENOMEM;
+ kfree(channel->led->name);
+ channel->led->name = name;
+
+ channel_torch->led = channel->led;
+
+ return 0;
+}
+
+static int __gb_lights_flash_led_register(struct gb_channel *channel)
+{
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct led_classdev_flash *fled = &channel->fled;
+ struct led_flash_setting *fset;
+ struct gb_channel *channel_torch;
+ int ret;
+
+ fled->ops = &gb_lights_flash_ops;
+
+ fled->led_cdev.flags |= LED_DEV_CAP_FLASH;
+
+ fset = &fled->brightness;
+ fset->min = channel->intensity_uA.min;
+ fset->max = channel->intensity_uA.max;
+ fset->step = channel->intensity_uA.step;
+ fset->val = channel->intensity_uA.max;
+
+ /* Only the flash mode have the timeout constraints settings */
+ if (channel->mode & GB_CHANNEL_MODE_FLASH) {
+ fset = &fled->timeout;
+ fset->min = channel->timeout_us.min;
+ fset->max = channel->timeout_us.max;
+ fset->step = channel->timeout_us.step;
+ fset->val = channel->timeout_us.max;
+ }
+
+ /*
+ * If light have torch mode channel, this channel will be the led
+ * classdev of the registered above flash classdev
+ */
+ channel_torch = get_channel_from_mode(channel->light,
+ GB_CHANNEL_MODE_TORCH);
+ if (channel_torch) {
+ ret = __gb_lights_channel_torch_attach(channel, channel_torch);
+ if (ret < 0)
+ goto fail;
+ }
+
+ ret = led_classdev_flash_register(&connection->bundle->dev, fled);
+ if (ret < 0)
+ goto fail;
+
+ channel->is_registered = true;
+ return 0;
+fail:
+ channel->led = NULL;
+ return ret;
+}
+
+static void __gb_lights_flash_led_unregister(struct gb_channel *channel)
+{
+ if (!channel->is_registered)
+ return;
+
+ led_classdev_flash_unregister(&channel->fled);
+}
+
+static int gb_lights_channel_flash_config(struct gb_channel *channel)
+{
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct gb_lights_get_channel_flash_config_request req;
+ struct gb_lights_get_channel_flash_config_response conf;
+ struct led_flash_setting *fset;
+ int ret;
+
+ req.light_id = channel->light->id;
+ req.channel_id = channel->id;
+
+ ret = gb_operation_sync(connection,
+ GB_LIGHTS_TYPE_GET_CHANNEL_FLASH_CONFIG,
+ &req, sizeof(req), &conf, sizeof(conf));
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Intensity constraints for flash related modes: flash, torch,
+ * indicator. They will be needed for v4l2 registration.
+ */
+ fset = &channel->intensity_uA;
+ fset->min = le32_to_cpu(conf.intensity_min_uA);
+ fset->max = le32_to_cpu(conf.intensity_max_uA);
+ fset->step = le32_to_cpu(conf.intensity_step_uA);
+
+ /*
+ * On flash type, max brightness is set as the number of intensity steps
+ * available.
+ */
+ channel->led->max_brightness = (fset->max - fset->min) / fset->step;
+
+ /* Only the flash mode have the timeout constraints settings */
+ if (channel->mode & GB_CHANNEL_MODE_FLASH) {
+ fset = &channel->timeout_us;
+ fset->min = le32_to_cpu(conf.timeout_min_us);
+ fset->max = le32_to_cpu(conf.timeout_max_us);
+ fset->step = le32_to_cpu(conf.timeout_step_us);
+ }
+
+ return 0;
+}
+#else
+static int gb_lights_channel_flash_config(struct gb_channel *channel)
+{
+ struct gb_connection *connection = get_conn_from_channel(channel);
+
+ dev_err(&connection->bundle->dev, "no support for flash devices\n");
+ return 0;
+}
+
+static int __gb_lights_flash_led_register(struct gb_channel *channel)
+{
+ return 0;
+}
+
+static void __gb_lights_flash_led_unregister(struct gb_channel *channel)
+{
+}
+
+#endif
+
+static int __gb_lights_led_register(struct gb_channel *channel)
+{
+ struct gb_connection *connection = get_conn_from_channel(channel);
+ struct led_classdev *cdev = get_channel_cdev(channel);
+ int ret;
+
+ ret = led_classdev_register(&connection->bundle->dev, cdev);
+ if (ret < 0)
+ channel->led = NULL;
+ else
+ channel->is_registered = true;
+ return ret;
+}
+
+static int gb_lights_channel_register(struct gb_channel *channel)
+{
+ /* Normal LED channel, just register in led classdev and we are done */
+ if (!is_channel_flash(channel))
+ return __gb_lights_led_register(channel);
+
+ /*
+ * Flash Type need more work, register flash classdev, indicator as
+ * flash classdev, torch will be led classdev of the flash classdev.
+ */
+ if (!(channel->mode & GB_CHANNEL_MODE_TORCH))
+ return __gb_lights_flash_led_register(channel);
+
+ return 0;
+}
+
+static void __gb_lights_led_unregister(struct gb_channel *channel)
+{
+ struct led_classdev *cdev = get_channel_cdev(channel);
+
+ if (!channel->is_registered)
+ return;
+
+ led_classdev_unregister(cdev);
+ kfree(cdev->name);
+ cdev->name = NULL;
+ channel->led = NULL;
+}
+
+static void gb_lights_channel_unregister(struct gb_channel *channel)
+{
+ /* The same as register, handle channels differently */
+ if (!is_channel_flash(channel)) {
+ __gb_lights_led_unregister(channel);
+ return;
+ }
+
+ if (channel->mode & GB_CHANNEL_MODE_TORCH)
+ __gb_lights_led_unregister(channel);
+ else
+ __gb_lights_flash_led_unregister(channel);
+}
+
+static int gb_lights_channel_config(struct gb_light *light,
+ struct gb_channel *channel)
+{
+ struct gb_lights_get_channel_config_response conf;
+ struct gb_lights_get_channel_config_request req;
+ struct gb_connection *connection = get_conn_from_light(light);
+ struct led_classdev *cdev = get_channel_cdev(channel);
+ char *name;
+ int ret;
+
+ req.light_id = light->id;
+ req.channel_id = channel->id;
+
+ ret = gb_operation_sync(connection, GB_LIGHTS_TYPE_GET_CHANNEL_CONFIG,
+ &req, sizeof(req), &conf, sizeof(conf));
+ if (ret < 0)
+ return ret;
+
+ channel->light = light;
+ channel->mode = le32_to_cpu(conf.mode);
+ channel->flags = le32_to_cpu(conf.flags);
+ channel->color = le32_to_cpu(conf.color);
+ channel->color_name = kstrndup(conf.color_name, NAMES_MAX, GFP_KERNEL);
+ if (!channel->color_name)
+ return -ENOMEM;
+ channel->mode_name = kstrndup(conf.mode_name, NAMES_MAX, GFP_KERNEL);
+ if (!channel->mode_name)
+ return -ENOMEM;
+
+ channel->led = cdev;
+
+ name = kasprintf(GFP_KERNEL, "%s:%s:%s", light->name,
+ channel->color_name, channel->mode_name);
+ if (!name)
+ return -ENOMEM;
+
+ cdev->name = name;
+
+ cdev->max_brightness = conf.max_brightness;
+
+ ret = channel_attr_groups_set(channel, cdev);
+ if (ret < 0)
+ return ret;
+
+ gb_lights_led_operations_set(channel, cdev);
+
+ /*
+ * If it is not a flash related channel (flash, torch or indicator) we
+ * are done here. If not, continue and fetch flash related
+ * configurations.
+ */
+ if (!is_channel_flash(channel))
+ return ret;
+
+ light->has_flash = true;
+
+ return gb_lights_channel_flash_config(channel);
+}
+
+static int gb_lights_light_config(struct gb_lights *glights, u8 id)
+{
+ struct gb_light *light = &glights->lights[id];
+ struct gb_lights_get_light_config_request req;
+ struct gb_lights_get_light_config_response conf;
+ int ret;
+ int i;
+
+ light->glights = glights;
+ light->id = id;
+
+ req.id = id;
+
+ ret = gb_operation_sync(glights->connection,
+ GB_LIGHTS_TYPE_GET_LIGHT_CONFIG,
+ &req, sizeof(req), &conf, sizeof(conf));
+ if (ret < 0)
+ return ret;
+
+ if (!conf.channel_count)
+ return -EINVAL;
+ if (!strlen(conf.name))
+ return -EINVAL;
+
+ light->channels_count = conf.channel_count;
+ light->name = kstrndup(conf.name, NAMES_MAX, GFP_KERNEL);
+ if (!light->name)
+ return -ENOMEM;
+ light->channels = kcalloc(light->channels_count,
+ sizeof(struct gb_channel), GFP_KERNEL);
+ if (!light->channels)
+ return -ENOMEM;
+
+ /* First we collect all the configurations for all channels */
+ for (i = 0; i < light->channels_count; i++) {
+ light->channels[i].id = i;
+ ret = gb_lights_channel_config(light, &light->channels[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int gb_lights_light_register(struct gb_light *light)
+{
+ int ret;
+ int i;
+
+ /*
+ * Then, if everything went ok in getting configurations, we register
+ * the classdev, flash classdev and v4l2 subsystem, if a flash device is
+ * found.
+ */
+ for (i = 0; i < light->channels_count; i++) {
+ ret = gb_lights_channel_register(&light->channels[i]);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&light->channels[i].lock);
+ }
+
+ light->ready = true;
+
+ if (light->has_flash) {
+ ret = gb_lights_light_v4l2_register(light);
+ if (ret < 0) {
+ light->has_flash = false;
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void gb_lights_channel_free(struct gb_channel *channel)
+{
+ kfree(channel->attrs);
+ kfree(channel->attr_group);
+ kfree(channel->attr_groups);
+ kfree(channel->color_name);
+ kfree(channel->mode_name);
+ mutex_destroy(&channel->lock);
+}
+
+static void gb_lights_channel_release(struct gb_channel *channel)
+{
+ channel->releasing = true;
+
+ gb_lights_channel_unregister(channel);
+
+ gb_lights_channel_free(channel);
+}
+
+static void gb_lights_light_release(struct gb_light *light)
+{
+ int i;
+
+ light->ready = false;
+
+ if (light->has_flash)
+ gb_lights_light_v4l2_unregister(light);
+ light->has_flash = false;
+
+ for (i = 0; i < light->channels_count; i++)
+ gb_lights_channel_release(&light->channels[i]);
+ light->channels_count = 0;
+
+ kfree(light->channels);
+ light->channels = NULL;
+ kfree(light->name);
+ light->name = NULL;
+}
+
+static void gb_lights_release(struct gb_lights *glights)
+{
+ int i;
+
+ if (!glights)
+ return;
+
+ mutex_lock(&glights->lights_lock);
+ if (!glights->lights)
+ goto free_glights;
+
+ for (i = 0; i < glights->lights_count; i++)
+ gb_lights_light_release(&glights->lights[i]);
+
+ kfree(glights->lights);
+
+free_glights:
+ mutex_unlock(&glights->lights_lock);
+ mutex_destroy(&glights->lights_lock);
+ kfree(glights);
+}
+
+static int gb_lights_get_count(struct gb_lights *glights)
+{
+ struct gb_lights_get_lights_response resp;
+ int ret;
+
+ ret = gb_operation_sync(glights->connection, GB_LIGHTS_TYPE_GET_LIGHTS,
+ NULL, 0, &resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
+
+ if (!resp.lights_count)
+ return -EINVAL;
+
+ glights->lights_count = resp.lights_count;
+
+ return 0;
+}
+
+static int gb_lights_create_all(struct gb_lights *glights)
+{
+ struct gb_connection *connection = glights->connection;
+ int ret;
+ int i;
+
+ mutex_lock(&glights->lights_lock);
+ ret = gb_lights_get_count(glights);
+ if (ret < 0)
+ goto out;
+
+ glights->lights = kcalloc(glights->lights_count,
+ sizeof(struct gb_light), GFP_KERNEL);
+ if (!glights->lights) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < glights->lights_count; i++) {
+ ret = gb_lights_light_config(glights, i);
+ if (ret < 0) {
+ dev_err(&connection->bundle->dev,
+ "Fail to configure lights device\n");
+ goto out;
+ }
+ }
+
+out:
+ mutex_unlock(&glights->lights_lock);
+ return ret;
+}
+
+static int gb_lights_register_all(struct gb_lights *glights)
+{
+ struct gb_connection *connection = glights->connection;
+ int ret = 0;
+ int i;
+
+ mutex_lock(&glights->lights_lock);
+ for (i = 0; i < glights->lights_count; i++) {
+ ret = gb_lights_light_register(&glights->lights[i]);
+ if (ret < 0) {
+ dev_err(&connection->bundle->dev,
+ "Fail to enable lights device\n");
+ break;
+ }
+ }
+
+ mutex_unlock(&glights->lights_lock);
+ return ret;
+}
+
+static int gb_lights_request_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct device *dev = &connection->bundle->dev;
+ struct gb_lights *glights = gb_connection_get_data(connection);
+ struct gb_light *light;
+ struct gb_message *request;
+ struct gb_lights_event_request *payload;
+ int ret = 0;
+ u8 light_id;
+ u8 event;
+
+ if (op->type != GB_LIGHTS_TYPE_EVENT) {
+ dev_err(dev, "Unsupported unsolicited event: %u\n", op->type);
+ return -EINVAL;
+ }
+
+ request = op->request;
+
+ if (request->payload_size < sizeof(*payload)) {
+ dev_err(dev, "Wrong event size received (%zu < %zu)\n",
+ request->payload_size, sizeof(*payload));
+ return -EINVAL;
+ }
+
+ payload = request->payload;
+ light_id = payload->light_id;
+
+ if (light_id >= glights->lights_count ||
+ !glights->lights[light_id].ready) {
+ dev_err(dev, "Event received for unconfigured light id: %d\n",
+ light_id);
+ return -EINVAL;
+ }
+
+ event = payload->event;
+
+ if (event & GB_LIGHTS_LIGHT_CONFIG) {
+ light = &glights->lights[light_id];
+
+ mutex_lock(&glights->lights_lock);
+ gb_lights_light_release(light);
+ ret = gb_lights_light_config(glights, light_id);
+ if (!ret)
+ ret = gb_lights_light_register(light);
+ if (ret < 0)
+ gb_lights_light_release(light);
+ mutex_unlock(&glights->lights_lock);
+ }
+
+ return ret;
+}
+
+static int gb_lights_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_lights *glights;
+ int ret;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_LIGHTS)
+ return -ENODEV;
+
+ glights = kzalloc(sizeof(*glights), GFP_KERNEL);
+ if (!glights)
+ return -ENOMEM;
+
+ mutex_init(&glights->lights_lock);
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ gb_lights_request_handler);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ goto out;
+ }
+
+ glights->connection = connection;
+ gb_connection_set_data(connection, glights);
+
+ greybus_set_drvdata(bundle, glights);
+
+ /* We aren't ready to receive an incoming request yet */
+ ret = gb_connection_enable_tx(connection);
+ if (ret)
+ goto error_connection_destroy;
+
+ /*
+ * Setup all the lights devices over this connection, if anything goes
+ * wrong tear down all lights
+ */
+ ret = gb_lights_create_all(glights);
+ if (ret < 0)
+ goto error_connection_disable;
+
+ /* We are ready to receive an incoming request now, enable RX as well */
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto error_connection_disable;
+
+ /* Enable & register lights */
+ ret = gb_lights_register_all(glights);
+ if (ret < 0)
+ goto error_connection_disable;
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+
+error_connection_disable:
+ gb_connection_disable(connection);
+error_connection_destroy:
+ gb_connection_destroy(connection);
+out:
+ gb_lights_release(glights);
+ return ret;
+}
+
+static void gb_lights_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_lights *glights = greybus_get_drvdata(bundle);
+
+ if (gb_pm_runtime_get_sync(bundle))
+ gb_pm_runtime_get_noresume(bundle);
+
+ gb_connection_disable(glights->connection);
+ gb_connection_destroy(glights->connection);
+
+ gb_lights_release(glights);
+}
+
+static const struct greybus_bundle_id gb_lights_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_LIGHTS) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_lights_id_table);
+
+static struct greybus_driver gb_lights_driver = {
+ .name = "lights",
+ .probe = gb_lights_probe,
+ .disconnect = gb_lights_disconnect,
+ .id_table = gb_lights_id_table,
+};
+module_greybus_driver(gb_lights_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/log.c b/drivers/staging/greybus/log.c
new file mode 100644
index 0000000000..971f36dcca
--- /dev/null
+++ b/drivers/staging/greybus/log.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus driver for the log protocol
+ *
+ * Copyright 2016 Google Inc.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sizes.h>
+#include <linux/uaccess.h>
+#include <linux/greybus.h>
+
+struct gb_log {
+ struct gb_connection *connection;
+};
+
+static int gb_log_request_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct device *dev = &connection->bundle->dev;
+ struct gb_log_send_log_request *receive;
+ u16 len;
+
+ if (op->type != GB_LOG_TYPE_SEND_LOG) {
+ dev_err(dev, "unknown request type 0x%02x\n", op->type);
+ return -EINVAL;
+ }
+
+ /* Verify size of payload */
+ if (op->request->payload_size < sizeof(*receive)) {
+ dev_err(dev, "log request too small (%zu < %zu)\n",
+ op->request->payload_size, sizeof(*receive));
+ return -EINVAL;
+ }
+ receive = op->request->payload;
+ len = le16_to_cpu(receive->len);
+ if (len != (op->request->payload_size - sizeof(*receive))) {
+ dev_err(dev, "log request wrong size %d vs %zu\n", len,
+ (op->request->payload_size - sizeof(*receive)));
+ return -EINVAL;
+ }
+ if (len == 0) {
+ dev_err(dev, "log request of 0 bytes?\n");
+ return -EINVAL;
+ }
+
+ if (len > GB_LOG_MAX_LEN) {
+ dev_err(dev, "log request too big: %d\n", len);
+ return -EINVAL;
+ }
+
+ /* Ensure the buffer is 0 terminated */
+ receive->msg[len - 1] = '\0';
+
+ /*
+ * Print with dev_dbg() so that it can be easily turned off using
+ * dynamic debugging (and prevent any DoS)
+ */
+ dev_dbg(dev, "%s", receive->msg);
+
+ return 0;
+}
+
+static int gb_log_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_log *log;
+ int retval;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_LOG)
+ return -ENODEV;
+
+ log = kzalloc(sizeof(*log), GFP_KERNEL);
+ if (!log)
+ return -ENOMEM;
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ gb_log_request_handler);
+ if (IS_ERR(connection)) {
+ retval = PTR_ERR(connection);
+ goto error_free;
+ }
+
+ log->connection = connection;
+ greybus_set_drvdata(bundle, log);
+
+ retval = gb_connection_enable(connection);
+ if (retval)
+ goto error_connection_destroy;
+
+ return 0;
+
+error_connection_destroy:
+ gb_connection_destroy(connection);
+error_free:
+ kfree(log);
+ return retval;
+}
+
+static void gb_log_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_log *log = greybus_get_drvdata(bundle);
+ struct gb_connection *connection = log->connection;
+
+ gb_connection_disable(connection);
+ gb_connection_destroy(connection);
+
+ kfree(log);
+}
+
+static const struct greybus_bundle_id gb_log_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_LOG) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_log_id_table);
+
+static struct greybus_driver gb_log_driver = {
+ .name = "log",
+ .probe = gb_log_probe,
+ .disconnect = gb_log_disconnect,
+ .id_table = gb_log_id_table,
+};
+module_greybus_driver(gb_log_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/loopback.c b/drivers/staging/greybus/loopback.c
new file mode 100644
index 0000000000..d7b39f3bb6
--- /dev/null
+++ b/drivers/staging/greybus/loopback.c
@@ -0,0 +1,1177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Loopback bridge driver for the Greybus loopback module.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/sizes.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/kfifo.h>
+#include <linux/debugfs.h>
+#include <linux/list_sort.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/atomic.h>
+#include <linux/pm_runtime.h>
+#include <linux/greybus.h>
+#include <asm/div64.h>
+
+#define NSEC_PER_DAY 86400000000000ULL
+
+struct gb_loopback_stats {
+ u32 min;
+ u32 max;
+ u64 sum;
+ u32 count;
+};
+
+struct gb_loopback_device {
+ struct dentry *root;
+ u32 count;
+ size_t size_max;
+
+ /* We need to take a lock in atomic context */
+ spinlock_t lock;
+ wait_queue_head_t wq;
+};
+
+static struct gb_loopback_device gb_dev;
+
+struct gb_loopback_async_operation {
+ struct gb_loopback *gb;
+ struct gb_operation *operation;
+ ktime_t ts;
+ int (*completion)(struct gb_loopback_async_operation *op_async);
+};
+
+struct gb_loopback {
+ struct gb_connection *connection;
+
+ struct dentry *file;
+ struct kfifo kfifo_lat;
+ struct mutex mutex;
+ struct task_struct *task;
+ struct device *dev;
+ wait_queue_head_t wq;
+ wait_queue_head_t wq_completion;
+ atomic_t outstanding_operations;
+
+ /* Per connection stats */
+ ktime_t ts;
+ struct gb_loopback_stats latency;
+ struct gb_loopback_stats throughput;
+ struct gb_loopback_stats requests_per_second;
+ struct gb_loopback_stats apbridge_unipro_latency;
+ struct gb_loopback_stats gbphy_firmware_latency;
+
+ int type;
+ int async;
+ int id;
+ u32 size;
+ u32 iteration_max;
+ u32 iteration_count;
+ int us_wait;
+ u32 error;
+ u32 requests_completed;
+ u32 requests_timedout;
+ u32 timeout;
+ u32 jiffy_timeout;
+ u32 timeout_min;
+ u32 timeout_max;
+ u32 outstanding_operations_max;
+ u64 elapsed_nsecs;
+ u32 apbridge_latency_ts;
+ u32 gbphy_latency_ts;
+
+ u32 send_count;
+};
+
+static struct class loopback_class = {
+ .name = "gb_loopback",
+};
+static DEFINE_IDA(loopback_ida);
+
+/* Min/max values in jiffies */
+#define GB_LOOPBACK_TIMEOUT_MIN 1
+#define GB_LOOPBACK_TIMEOUT_MAX 10000
+
+#define GB_LOOPBACK_FIFO_DEFAULT 8192
+
+static unsigned int kfifo_depth = GB_LOOPBACK_FIFO_DEFAULT;
+module_param(kfifo_depth, uint, 0444);
+
+/* Maximum size of any one send data buffer we support */
+#define MAX_PACKET_SIZE (PAGE_SIZE * 2)
+
+#define GB_LOOPBACK_US_WAIT_MAX 1000000
+
+/* interface sysfs attributes */
+#define gb_loopback_ro_attr(field) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct gb_loopback *gb = dev_get_drvdata(dev); \
+ return sprintf(buf, "%u\n", gb->field); \
+} \
+static DEVICE_ATTR_RO(field)
+
+#define gb_loopback_ro_stats_attr(name, field, type) \
+static ssize_t name##_##field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct gb_loopback *gb = dev_get_drvdata(dev); \
+ /* Report 0 for min and max if no transfer succeeded */ \
+ if (!gb->requests_completed) \
+ return sprintf(buf, "0\n"); \
+ return sprintf(buf, "%" #type "\n", gb->name.field); \
+} \
+static DEVICE_ATTR_RO(name##_##field)
+
+#define gb_loopback_ro_avg_attr(name) \
+static ssize_t name##_avg_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct gb_loopback_stats *stats; \
+ struct gb_loopback *gb; \
+ u64 avg, rem; \
+ u32 count; \
+ gb = dev_get_drvdata(dev); \
+ stats = &gb->name; \
+ count = stats->count ? stats->count : 1; \
+ avg = stats->sum + count / 2000000; /* round closest */ \
+ rem = do_div(avg, count); \
+ rem *= 1000000; \
+ do_div(rem, count); \
+ return sprintf(buf, "%llu.%06u\n", avg, (u32)rem); \
+} \
+static DEVICE_ATTR_RO(name##_avg)
+
+#define gb_loopback_stats_attrs(field) \
+ gb_loopback_ro_stats_attr(field, min, u); \
+ gb_loopback_ro_stats_attr(field, max, u); \
+ gb_loopback_ro_avg_attr(field)
+
+#define gb_loopback_attr(field, type) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct gb_loopback *gb = dev_get_drvdata(dev); \
+ return sprintf(buf, "%" #type "\n", gb->field); \
+} \
+static ssize_t field##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, \
+ size_t len) \
+{ \
+ int ret; \
+ struct gb_loopback *gb = dev_get_drvdata(dev); \
+ mutex_lock(&gb->mutex); \
+ ret = sscanf(buf, "%"#type, &gb->field); \
+ if (ret != 1) \
+ len = -EINVAL; \
+ else \
+ gb_loopback_check_attr(gb, bundle); \
+ mutex_unlock(&gb->mutex); \
+ return len; \
+} \
+static DEVICE_ATTR_RW(field)
+
+#define gb_dev_loopback_ro_attr(field, conn) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct gb_loopback *gb = dev_get_drvdata(dev); \
+ return sprintf(buf, "%u\n", gb->field); \
+} \
+static DEVICE_ATTR_RO(field)
+
+#define gb_dev_loopback_rw_attr(field, type) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct gb_loopback *gb = dev_get_drvdata(dev); \
+ return sprintf(buf, "%" #type "\n", gb->field); \
+} \
+static ssize_t field##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, \
+ size_t len) \
+{ \
+ int ret; \
+ struct gb_loopback *gb = dev_get_drvdata(dev); \
+ mutex_lock(&gb->mutex); \
+ ret = sscanf(buf, "%"#type, &gb->field); \
+ if (ret != 1) \
+ len = -EINVAL; \
+ else \
+ gb_loopback_check_attr(gb); \
+ mutex_unlock(&gb->mutex); \
+ return len; \
+} \
+static DEVICE_ATTR_RW(field)
+
+static void gb_loopback_reset_stats(struct gb_loopback *gb);
+static void gb_loopback_check_attr(struct gb_loopback *gb)
+{
+ if (gb->us_wait > GB_LOOPBACK_US_WAIT_MAX)
+ gb->us_wait = GB_LOOPBACK_US_WAIT_MAX;
+ if (gb->size > gb_dev.size_max)
+ gb->size = gb_dev.size_max;
+ gb->requests_timedout = 0;
+ gb->requests_completed = 0;
+ gb->iteration_count = 0;
+ gb->send_count = 0;
+ gb->error = 0;
+
+ if (kfifo_depth < gb->iteration_max) {
+ dev_warn(gb->dev,
+ "cannot log bytes %u kfifo_depth %u\n",
+ gb->iteration_max, kfifo_depth);
+ }
+ kfifo_reset_out(&gb->kfifo_lat);
+
+ switch (gb->type) {
+ case GB_LOOPBACK_TYPE_PING:
+ case GB_LOOPBACK_TYPE_TRANSFER:
+ case GB_LOOPBACK_TYPE_SINK:
+ gb->jiffy_timeout = usecs_to_jiffies(gb->timeout);
+ if (!gb->jiffy_timeout)
+ gb->jiffy_timeout = GB_LOOPBACK_TIMEOUT_MIN;
+ else if (gb->jiffy_timeout > GB_LOOPBACK_TIMEOUT_MAX)
+ gb->jiffy_timeout = GB_LOOPBACK_TIMEOUT_MAX;
+ gb_loopback_reset_stats(gb);
+ wake_up(&gb->wq);
+ break;
+ default:
+ gb->type = 0;
+ break;
+ }
+}
+
+/* Time to send and receive one message */
+gb_loopback_stats_attrs(latency);
+/* Number of requests sent per second on this cport */
+gb_loopback_stats_attrs(requests_per_second);
+/* Quantity of data sent and received on this cport */
+gb_loopback_stats_attrs(throughput);
+/* Latency across the UniPro link from APBridge's perspective */
+gb_loopback_stats_attrs(apbridge_unipro_latency);
+/* Firmware induced overhead in the GPBridge */
+gb_loopback_stats_attrs(gbphy_firmware_latency);
+
+/* Number of errors encountered during loop */
+gb_loopback_ro_attr(error);
+/* Number of requests successfully completed async */
+gb_loopback_ro_attr(requests_completed);
+/* Number of requests timed out async */
+gb_loopback_ro_attr(requests_timedout);
+/* Timeout minimum in useconds */
+gb_loopback_ro_attr(timeout_min);
+/* Timeout minimum in useconds */
+gb_loopback_ro_attr(timeout_max);
+
+/*
+ * Type of loopback message to send based on protocol type definitions
+ * 0 => Don't send message
+ * 2 => Send ping message continuously (message without payload)
+ * 3 => Send transfer message continuously (message with payload,
+ * payload returned in response)
+ * 4 => Send a sink message (message with payload, no payload in response)
+ */
+gb_dev_loopback_rw_attr(type, d);
+/* Size of transfer message payload: 0-4096 bytes */
+gb_dev_loopback_rw_attr(size, u);
+/* Time to wait between two messages: 0-1000 ms */
+gb_dev_loopback_rw_attr(us_wait, d);
+/* Maximum iterations for a given operation: 1-(2^32-1), 0 implies infinite */
+gb_dev_loopback_rw_attr(iteration_max, u);
+/* The current index of the for (i = 0; i < iteration_max; i++) loop */
+gb_dev_loopback_ro_attr(iteration_count, false);
+/* A flag to indicate synchronous or asynchronous operations */
+gb_dev_loopback_rw_attr(async, u);
+/* Timeout of an individual asynchronous request */
+gb_dev_loopback_rw_attr(timeout, u);
+/* Maximum number of in-flight operations before back-off */
+gb_dev_loopback_rw_attr(outstanding_operations_max, u);
+
+static struct attribute *loopback_attrs[] = {
+ &dev_attr_latency_min.attr,
+ &dev_attr_latency_max.attr,
+ &dev_attr_latency_avg.attr,
+ &dev_attr_requests_per_second_min.attr,
+ &dev_attr_requests_per_second_max.attr,
+ &dev_attr_requests_per_second_avg.attr,
+ &dev_attr_throughput_min.attr,
+ &dev_attr_throughput_max.attr,
+ &dev_attr_throughput_avg.attr,
+ &dev_attr_apbridge_unipro_latency_min.attr,
+ &dev_attr_apbridge_unipro_latency_max.attr,
+ &dev_attr_apbridge_unipro_latency_avg.attr,
+ &dev_attr_gbphy_firmware_latency_min.attr,
+ &dev_attr_gbphy_firmware_latency_max.attr,
+ &dev_attr_gbphy_firmware_latency_avg.attr,
+ &dev_attr_type.attr,
+ &dev_attr_size.attr,
+ &dev_attr_us_wait.attr,
+ &dev_attr_iteration_count.attr,
+ &dev_attr_iteration_max.attr,
+ &dev_attr_async.attr,
+ &dev_attr_error.attr,
+ &dev_attr_requests_completed.attr,
+ &dev_attr_requests_timedout.attr,
+ &dev_attr_timeout.attr,
+ &dev_attr_outstanding_operations_max.attr,
+ &dev_attr_timeout_min.attr,
+ &dev_attr_timeout_max.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(loopback);
+
+static void gb_loopback_calculate_stats(struct gb_loopback *gb, bool error);
+
+static u32 gb_loopback_nsec_to_usec_latency(u64 elapsed_nsecs)
+{
+ do_div(elapsed_nsecs, NSEC_PER_USEC);
+ return elapsed_nsecs;
+}
+
+static u64 __gb_loopback_calc_latency(u64 t1, u64 t2)
+{
+ if (t2 > t1)
+ return t2 - t1;
+ else
+ return NSEC_PER_DAY - t2 + t1;
+}
+
+static u64 gb_loopback_calc_latency(ktime_t ts, ktime_t te)
+{
+ return __gb_loopback_calc_latency(ktime_to_ns(ts), ktime_to_ns(te));
+}
+
+static int gb_loopback_operation_sync(struct gb_loopback *gb, int type,
+ void *request, int request_size,
+ void *response, int response_size)
+{
+ struct gb_operation *operation;
+ ktime_t ts, te;
+ int ret;
+
+ ts = ktime_get();
+ operation = gb_operation_create(gb->connection, type, request_size,
+ response_size, GFP_KERNEL);
+ if (!operation)
+ return -ENOMEM;
+
+ if (request_size)
+ memcpy(operation->request->payload, request, request_size);
+
+ ret = gb_operation_request_send_sync(operation);
+ if (ret) {
+ dev_err(&gb->connection->bundle->dev,
+ "synchronous operation failed: %d\n", ret);
+ goto out_put_operation;
+ } else {
+ if (response_size == operation->response->payload_size) {
+ memcpy(response, operation->response->payload,
+ response_size);
+ } else {
+ dev_err(&gb->connection->bundle->dev,
+ "response size %zu expected %d\n",
+ operation->response->payload_size,
+ response_size);
+ ret = -EINVAL;
+ goto out_put_operation;
+ }
+ }
+
+ te = ktime_get();
+
+ /* Calculate the total time the message took */
+ gb->elapsed_nsecs = gb_loopback_calc_latency(ts, te);
+
+out_put_operation:
+ gb_operation_put(operation);
+
+ return ret;
+}
+
+static void gb_loopback_async_wait_all(struct gb_loopback *gb)
+{
+ wait_event(gb->wq_completion,
+ !atomic_read(&gb->outstanding_operations));
+}
+
+static void gb_loopback_async_operation_callback(struct gb_operation *operation)
+{
+ struct gb_loopback_async_operation *op_async;
+ struct gb_loopback *gb;
+ ktime_t te;
+ int result;
+
+ te = ktime_get();
+ result = gb_operation_result(operation);
+ op_async = gb_operation_get_data(operation);
+ gb = op_async->gb;
+
+ mutex_lock(&gb->mutex);
+
+ if (!result && op_async->completion)
+ result = op_async->completion(op_async);
+
+ if (!result) {
+ gb->elapsed_nsecs = gb_loopback_calc_latency(op_async->ts, te);
+ } else {
+ gb->error++;
+ if (result == -ETIMEDOUT)
+ gb->requests_timedout++;
+ }
+
+ gb->iteration_count++;
+ gb_loopback_calculate_stats(gb, result);
+
+ mutex_unlock(&gb->mutex);
+
+ dev_dbg(&gb->connection->bundle->dev, "complete operation %d\n",
+ operation->id);
+
+ /* Wake up waiters */
+ atomic_dec(&op_async->gb->outstanding_operations);
+ wake_up(&gb->wq_completion);
+
+ /* Release resources */
+ gb_operation_put(operation);
+ kfree(op_async);
+}
+
+static int gb_loopback_async_operation(struct gb_loopback *gb, int type,
+ void *request, int request_size,
+ int response_size,
+ void *completion)
+{
+ struct gb_loopback_async_operation *op_async;
+ struct gb_operation *operation;
+ int ret;
+
+ op_async = kzalloc(sizeof(*op_async), GFP_KERNEL);
+ if (!op_async)
+ return -ENOMEM;
+
+ operation = gb_operation_create(gb->connection, type, request_size,
+ response_size, GFP_KERNEL);
+ if (!operation) {
+ kfree(op_async);
+ return -ENOMEM;
+ }
+
+ if (request_size)
+ memcpy(operation->request->payload, request, request_size);
+
+ gb_operation_set_data(operation, op_async);
+
+ op_async->gb = gb;
+ op_async->operation = operation;
+ op_async->completion = completion;
+
+ op_async->ts = ktime_get();
+
+ atomic_inc(&gb->outstanding_operations);
+ ret = gb_operation_request_send(operation,
+ gb_loopback_async_operation_callback,
+ jiffies_to_msecs(gb->jiffy_timeout),
+ GFP_KERNEL);
+ if (ret) {
+ atomic_dec(&gb->outstanding_operations);
+ gb_operation_put(operation);
+ kfree(op_async);
+ }
+ return ret;
+}
+
+static int gb_loopback_sync_sink(struct gb_loopback *gb, u32 len)
+{
+ struct gb_loopback_transfer_request *request;
+ int retval;
+
+ request = kmalloc(len + sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->len = cpu_to_le32(len);
+ retval = gb_loopback_operation_sync(gb, GB_LOOPBACK_TYPE_SINK,
+ request, len + sizeof(*request),
+ NULL, 0);
+ kfree(request);
+ return retval;
+}
+
+static int gb_loopback_sync_transfer(struct gb_loopback *gb, u32 len)
+{
+ struct gb_loopback_transfer_request *request;
+ struct gb_loopback_transfer_response *response;
+ int retval;
+
+ gb->apbridge_latency_ts = 0;
+ gb->gbphy_latency_ts = 0;
+
+ request = kmalloc(len + sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+ response = kmalloc(len + sizeof(*response), GFP_KERNEL);
+ if (!response) {
+ kfree(request);
+ return -ENOMEM;
+ }
+
+ memset(request->data, 0x5A, len);
+
+ request->len = cpu_to_le32(len);
+ retval = gb_loopback_operation_sync(gb, GB_LOOPBACK_TYPE_TRANSFER,
+ request, len + sizeof(*request),
+ response, len + sizeof(*response));
+ if (retval)
+ goto gb_error;
+
+ if (memcmp(request->data, response->data, len)) {
+ dev_err(&gb->connection->bundle->dev,
+ "Loopback Data doesn't match\n");
+ retval = -EREMOTEIO;
+ }
+ gb->apbridge_latency_ts = (u32)__le32_to_cpu(response->reserved0);
+ gb->gbphy_latency_ts = (u32)__le32_to_cpu(response->reserved1);
+
+gb_error:
+ kfree(request);
+ kfree(response);
+
+ return retval;
+}
+
+static int gb_loopback_sync_ping(struct gb_loopback *gb)
+{
+ return gb_loopback_operation_sync(gb, GB_LOOPBACK_TYPE_PING,
+ NULL, 0, NULL, 0);
+}
+
+static int gb_loopback_async_sink(struct gb_loopback *gb, u32 len)
+{
+ struct gb_loopback_transfer_request *request;
+ int retval;
+
+ request = kmalloc(len + sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->len = cpu_to_le32(len);
+ retval = gb_loopback_async_operation(gb, GB_LOOPBACK_TYPE_SINK,
+ request, len + sizeof(*request),
+ 0, NULL);
+ kfree(request);
+ return retval;
+}
+
+static int gb_loopback_async_transfer_complete(
+ struct gb_loopback_async_operation *op_async)
+{
+ struct gb_loopback *gb;
+ struct gb_operation *operation;
+ struct gb_loopback_transfer_request *request;
+ struct gb_loopback_transfer_response *response;
+ size_t len;
+ int retval = 0;
+
+ gb = op_async->gb;
+ operation = op_async->operation;
+ request = operation->request->payload;
+ response = operation->response->payload;
+ len = le32_to_cpu(request->len);
+
+ if (memcmp(request->data, response->data, len)) {
+ dev_err(&gb->connection->bundle->dev,
+ "Loopback Data doesn't match operation id %d\n",
+ operation->id);
+ retval = -EREMOTEIO;
+ } else {
+ gb->apbridge_latency_ts =
+ (u32)__le32_to_cpu(response->reserved0);
+ gb->gbphy_latency_ts =
+ (u32)__le32_to_cpu(response->reserved1);
+ }
+
+ return retval;
+}
+
+static int gb_loopback_async_transfer(struct gb_loopback *gb, u32 len)
+{
+ struct gb_loopback_transfer_request *request;
+ int retval, response_len;
+
+ request = kmalloc(len + sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ memset(request->data, 0x5A, len);
+
+ request->len = cpu_to_le32(len);
+ response_len = sizeof(struct gb_loopback_transfer_response);
+ retval = gb_loopback_async_operation(gb, GB_LOOPBACK_TYPE_TRANSFER,
+ request, len + sizeof(*request),
+ len + response_len,
+ gb_loopback_async_transfer_complete);
+ if (retval)
+ goto gb_error;
+
+gb_error:
+ kfree(request);
+ return retval;
+}
+
+static int gb_loopback_async_ping(struct gb_loopback *gb)
+{
+ return gb_loopback_async_operation(gb, GB_LOOPBACK_TYPE_PING,
+ NULL, 0, 0, NULL);
+}
+
+static int gb_loopback_request_handler(struct gb_operation *operation)
+{
+ struct gb_connection *connection = operation->connection;
+ struct gb_loopback_transfer_request *request;
+ struct gb_loopback_transfer_response *response;
+ struct device *dev = &connection->bundle->dev;
+ size_t len;
+
+ /* By convention, the AP initiates the version operation */
+ switch (operation->type) {
+ case GB_LOOPBACK_TYPE_PING:
+ case GB_LOOPBACK_TYPE_SINK:
+ return 0;
+ case GB_LOOPBACK_TYPE_TRANSFER:
+ if (operation->request->payload_size < sizeof(*request)) {
+ dev_err(dev, "transfer request too small (%zu < %zu)\n",
+ operation->request->payload_size,
+ sizeof(*request));
+ return -EINVAL; /* -EMSGSIZE */
+ }
+ request = operation->request->payload;
+ len = le32_to_cpu(request->len);
+ if (len > gb_dev.size_max) {
+ dev_err(dev, "transfer request too large (%zu > %zu)\n",
+ len, gb_dev.size_max);
+ return -EINVAL;
+ }
+
+ if (!gb_operation_response_alloc(operation,
+ len + sizeof(*response), GFP_KERNEL)) {
+ dev_err(dev, "error allocating response\n");
+ return -ENOMEM;
+ }
+ response = operation->response->payload;
+ response->len = cpu_to_le32(len);
+ if (len)
+ memcpy(response->data, request->data, len);
+
+ return 0;
+ default:
+ dev_err(dev, "unsupported request: %u\n", operation->type);
+ return -EINVAL;
+ }
+}
+
+static void gb_loopback_reset_stats(struct gb_loopback *gb)
+{
+ struct gb_loopback_stats reset = {
+ .min = U32_MAX,
+ };
+
+ /* Reset per-connection stats */
+ memcpy(&gb->latency, &reset,
+ sizeof(struct gb_loopback_stats));
+ memcpy(&gb->throughput, &reset,
+ sizeof(struct gb_loopback_stats));
+ memcpy(&gb->requests_per_second, &reset,
+ sizeof(struct gb_loopback_stats));
+ memcpy(&gb->apbridge_unipro_latency, &reset,
+ sizeof(struct gb_loopback_stats));
+ memcpy(&gb->gbphy_firmware_latency, &reset,
+ sizeof(struct gb_loopback_stats));
+
+ /* Should be initialized at least once per transaction set */
+ gb->apbridge_latency_ts = 0;
+ gb->gbphy_latency_ts = 0;
+ gb->ts = ktime_set(0, 0);
+}
+
+static void gb_loopback_update_stats(struct gb_loopback_stats *stats, u32 val)
+{
+ if (stats->min > val)
+ stats->min = val;
+ if (stats->max < val)
+ stats->max = val;
+ stats->sum += val;
+ stats->count++;
+}
+
+static void gb_loopback_update_stats_window(struct gb_loopback_stats *stats,
+ u64 val, u32 count)
+{
+ stats->sum += val;
+ stats->count += count;
+
+ do_div(val, count);
+ if (stats->min > val)
+ stats->min = val;
+ if (stats->max < val)
+ stats->max = val;
+}
+
+static void gb_loopback_requests_update(struct gb_loopback *gb, u32 latency)
+{
+ u64 req = gb->requests_completed * USEC_PER_SEC;
+
+ gb_loopback_update_stats_window(&gb->requests_per_second, req, latency);
+}
+
+static void gb_loopback_throughput_update(struct gb_loopback *gb, u32 latency)
+{
+ u64 aggregate_size = sizeof(struct gb_operation_msg_hdr) * 2;
+
+ switch (gb->type) {
+ case GB_LOOPBACK_TYPE_PING:
+ break;
+ case GB_LOOPBACK_TYPE_SINK:
+ aggregate_size += sizeof(struct gb_loopback_transfer_request) +
+ gb->size;
+ break;
+ case GB_LOOPBACK_TYPE_TRANSFER:
+ aggregate_size += sizeof(struct gb_loopback_transfer_request) +
+ sizeof(struct gb_loopback_transfer_response) +
+ gb->size * 2;
+ break;
+ default:
+ return;
+ }
+
+ aggregate_size *= gb->requests_completed;
+ aggregate_size *= USEC_PER_SEC;
+ gb_loopback_update_stats_window(&gb->throughput, aggregate_size,
+ latency);
+}
+
+static void gb_loopback_calculate_latency_stats(struct gb_loopback *gb)
+{
+ u32 lat;
+
+ /* Express latency in terms of microseconds */
+ lat = gb_loopback_nsec_to_usec_latency(gb->elapsed_nsecs);
+
+ /* Log latency stastic */
+ gb_loopback_update_stats(&gb->latency, lat);
+
+ /* Raw latency log on a per thread basis */
+ kfifo_in(&gb->kfifo_lat, (unsigned char *)&lat, sizeof(lat));
+
+ /* Log the firmware supplied latency values */
+ gb_loopback_update_stats(&gb->apbridge_unipro_latency,
+ gb->apbridge_latency_ts);
+ gb_loopback_update_stats(&gb->gbphy_firmware_latency,
+ gb->gbphy_latency_ts);
+}
+
+static void gb_loopback_calculate_stats(struct gb_loopback *gb, bool error)
+{
+ u64 nlat;
+ u32 lat;
+ ktime_t te;
+
+ if (!error) {
+ gb->requests_completed++;
+ gb_loopback_calculate_latency_stats(gb);
+ }
+
+ te = ktime_get();
+ nlat = gb_loopback_calc_latency(gb->ts, te);
+ if (nlat >= NSEC_PER_SEC || gb->iteration_count == gb->iteration_max) {
+ lat = gb_loopback_nsec_to_usec_latency(nlat);
+
+ gb_loopback_throughput_update(gb, lat);
+ gb_loopback_requests_update(gb, lat);
+
+ if (gb->iteration_count != gb->iteration_max) {
+ gb->ts = te;
+ gb->requests_completed = 0;
+ }
+ }
+}
+
+static void gb_loopback_async_wait_to_send(struct gb_loopback *gb)
+{
+ if (!(gb->async && gb->outstanding_operations_max))
+ return;
+ wait_event_interruptible(gb->wq_completion,
+ (atomic_read(&gb->outstanding_operations) <
+ gb->outstanding_operations_max) ||
+ kthread_should_stop());
+}
+
+static int gb_loopback_fn(void *data)
+{
+ int error = 0;
+ int us_wait = 0;
+ int type;
+ int ret;
+ u32 size;
+
+ struct gb_loopback *gb = data;
+ struct gb_bundle *bundle = gb->connection->bundle;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ while (1) {
+ if (!gb->type) {
+ gb_pm_runtime_put_autosuspend(bundle);
+ wait_event_interruptible(gb->wq, gb->type ||
+ kthread_should_stop());
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+ }
+
+ if (kthread_should_stop())
+ break;
+
+ /* Limit the maximum number of in-flight async operations */
+ gb_loopback_async_wait_to_send(gb);
+ if (kthread_should_stop())
+ break;
+
+ mutex_lock(&gb->mutex);
+
+ /* Optionally terminate */
+ if (gb->send_count == gb->iteration_max) {
+ mutex_unlock(&gb->mutex);
+
+ /* Wait for synchronous and asynchronous completion */
+ gb_loopback_async_wait_all(gb);
+
+ /* Mark complete unless user-space has poked us */
+ mutex_lock(&gb->mutex);
+ if (gb->iteration_count == gb->iteration_max) {
+ gb->type = 0;
+ gb->send_count = 0;
+ sysfs_notify(&gb->dev->kobj, NULL,
+ "iteration_count");
+ dev_dbg(&bundle->dev, "load test complete\n");
+ } else {
+ dev_dbg(&bundle->dev,
+ "continuing on with new test set\n");
+ }
+ mutex_unlock(&gb->mutex);
+ continue;
+ }
+ size = gb->size;
+ us_wait = gb->us_wait;
+ type = gb->type;
+ if (ktime_to_ns(gb->ts) == 0)
+ gb->ts = ktime_get();
+
+ /* Else operations to perform */
+ if (gb->async) {
+ if (type == GB_LOOPBACK_TYPE_PING)
+ error = gb_loopback_async_ping(gb);
+ else if (type == GB_LOOPBACK_TYPE_TRANSFER)
+ error = gb_loopback_async_transfer(gb, size);
+ else if (type == GB_LOOPBACK_TYPE_SINK)
+ error = gb_loopback_async_sink(gb, size);
+
+ if (error) {
+ gb->error++;
+ gb->iteration_count++;
+ }
+ } else {
+ /* We are effectively single threaded here */
+ if (type == GB_LOOPBACK_TYPE_PING)
+ error = gb_loopback_sync_ping(gb);
+ else if (type == GB_LOOPBACK_TYPE_TRANSFER)
+ error = gb_loopback_sync_transfer(gb, size);
+ else if (type == GB_LOOPBACK_TYPE_SINK)
+ error = gb_loopback_sync_sink(gb, size);
+
+ if (error)
+ gb->error++;
+ gb->iteration_count++;
+ gb_loopback_calculate_stats(gb, !!error);
+ }
+ gb->send_count++;
+ mutex_unlock(&gb->mutex);
+
+ if (us_wait) {
+ if (us_wait < 20000)
+ usleep_range(us_wait, us_wait + 100);
+ else
+ msleep(us_wait / 1000);
+ }
+ }
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+}
+
+static int gb_loopback_dbgfs_latency_show_common(struct seq_file *s,
+ struct kfifo *kfifo,
+ struct mutex *mutex)
+{
+ u32 latency;
+ int retval;
+
+ if (kfifo_len(kfifo) == 0) {
+ retval = -EAGAIN;
+ goto done;
+ }
+
+ mutex_lock(mutex);
+ retval = kfifo_out(kfifo, &latency, sizeof(latency));
+ if (retval > 0) {
+ seq_printf(s, "%u", latency);
+ retval = 0;
+ }
+ mutex_unlock(mutex);
+done:
+ return retval;
+}
+
+static int gb_loopback_dbgfs_latency_show(struct seq_file *s, void *unused)
+{
+ struct gb_loopback *gb = s->private;
+
+ return gb_loopback_dbgfs_latency_show_common(s, &gb->kfifo_lat,
+ &gb->mutex);
+}
+DEFINE_SHOW_ATTRIBUTE(gb_loopback_dbgfs_latency);
+
+#define DEBUGFS_NAMELEN 32
+
+static int gb_loopback_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_loopback *gb;
+ struct device *dev;
+ int retval;
+ char name[DEBUGFS_NAMELEN];
+ unsigned long flags;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_LOOPBACK)
+ return -ENODEV;
+
+ gb = kzalloc(sizeof(*gb), GFP_KERNEL);
+ if (!gb)
+ return -ENOMEM;
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ gb_loopback_request_handler);
+ if (IS_ERR(connection)) {
+ retval = PTR_ERR(connection);
+ goto out_kzalloc;
+ }
+
+ gb->connection = connection;
+ greybus_set_drvdata(bundle, gb);
+
+ init_waitqueue_head(&gb->wq);
+ init_waitqueue_head(&gb->wq_completion);
+ atomic_set(&gb->outstanding_operations, 0);
+ gb_loopback_reset_stats(gb);
+
+ /* Reported values to user-space for min/max timeouts */
+ gb->timeout_min = jiffies_to_usecs(GB_LOOPBACK_TIMEOUT_MIN);
+ gb->timeout_max = jiffies_to_usecs(GB_LOOPBACK_TIMEOUT_MAX);
+
+ if (!gb_dev.count) {
+ /* Calculate maximum payload */
+ gb_dev.size_max = gb_operation_get_payload_size_max(connection);
+ if (gb_dev.size_max <=
+ sizeof(struct gb_loopback_transfer_request)) {
+ retval = -EINVAL;
+ goto out_connection_destroy;
+ }
+ gb_dev.size_max -= sizeof(struct gb_loopback_transfer_request);
+ }
+
+ /* Create per-connection sysfs and debugfs data-points */
+ snprintf(name, sizeof(name), "raw_latency_%s",
+ dev_name(&connection->bundle->dev));
+ gb->file = debugfs_create_file(name, S_IFREG | 0444, gb_dev.root, gb,
+ &gb_loopback_dbgfs_latency_fops);
+
+ gb->id = ida_simple_get(&loopback_ida, 0, 0, GFP_KERNEL);
+ if (gb->id < 0) {
+ retval = gb->id;
+ goto out_debugfs_remove;
+ }
+
+ retval = gb_connection_enable(connection);
+ if (retval)
+ goto out_ida_remove;
+
+ dev = device_create_with_groups(&loopback_class,
+ &connection->bundle->dev,
+ MKDEV(0, 0), gb, loopback_groups,
+ "gb_loopback%d", gb->id);
+ if (IS_ERR(dev)) {
+ retval = PTR_ERR(dev);
+ goto out_connection_disable;
+ }
+ gb->dev = dev;
+
+ /* Allocate kfifo */
+ if (kfifo_alloc(&gb->kfifo_lat, kfifo_depth * sizeof(u32),
+ GFP_KERNEL)) {
+ retval = -ENOMEM;
+ goto out_conn;
+ }
+ /* Fork worker thread */
+ mutex_init(&gb->mutex);
+ gb->task = kthread_run(gb_loopback_fn, gb, "gb_loopback");
+ if (IS_ERR(gb->task)) {
+ retval = PTR_ERR(gb->task);
+ goto out_kfifo;
+ }
+
+ spin_lock_irqsave(&gb_dev.lock, flags);
+ gb_dev.count++;
+ spin_unlock_irqrestore(&gb_dev.lock, flags);
+
+ gb_connection_latency_tag_enable(connection);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+
+out_kfifo:
+ kfifo_free(&gb->kfifo_lat);
+out_conn:
+ device_unregister(dev);
+out_connection_disable:
+ gb_connection_disable(connection);
+out_ida_remove:
+ ida_simple_remove(&loopback_ida, gb->id);
+out_debugfs_remove:
+ debugfs_remove(gb->file);
+out_connection_destroy:
+ gb_connection_destroy(connection);
+out_kzalloc:
+ kfree(gb);
+
+ return retval;
+}
+
+static void gb_loopback_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_loopback *gb = greybus_get_drvdata(bundle);
+ unsigned long flags;
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ gb_pm_runtime_get_noresume(bundle);
+
+ gb_connection_disable(gb->connection);
+
+ if (!IS_ERR_OR_NULL(gb->task))
+ kthread_stop(gb->task);
+
+ kfifo_free(&gb->kfifo_lat);
+ gb_connection_latency_tag_disable(gb->connection);
+ debugfs_remove(gb->file);
+
+ /*
+ * FIXME: gb_loopback_async_wait_all() is redundant now, as connection
+ * is disabled at the beginning and so we can't have any more
+ * incoming/outgoing requests.
+ */
+ gb_loopback_async_wait_all(gb);
+
+ spin_lock_irqsave(&gb_dev.lock, flags);
+ gb_dev.count--;
+ spin_unlock_irqrestore(&gb_dev.lock, flags);
+
+ device_unregister(gb->dev);
+ ida_simple_remove(&loopback_ida, gb->id);
+
+ gb_connection_destroy(gb->connection);
+ kfree(gb);
+}
+
+static const struct greybus_bundle_id gb_loopback_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_LOOPBACK) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_loopback_id_table);
+
+static struct greybus_driver gb_loopback_driver = {
+ .name = "loopback",
+ .probe = gb_loopback_probe,
+ .disconnect = gb_loopback_disconnect,
+ .id_table = gb_loopback_id_table,
+};
+
+static int loopback_init(void)
+{
+ int retval;
+
+ spin_lock_init(&gb_dev.lock);
+ gb_dev.root = debugfs_create_dir("gb_loopback", NULL);
+
+ retval = class_register(&loopback_class);
+ if (retval)
+ goto err;
+
+ retval = greybus_register(&gb_loopback_driver);
+ if (retval)
+ goto err_unregister;
+
+ return 0;
+
+err_unregister:
+ class_unregister(&loopback_class);
+err:
+ debugfs_remove_recursive(gb_dev.root);
+ return retval;
+}
+module_init(loopback_init);
+
+static void __exit loopback_exit(void)
+{
+ debugfs_remove_recursive(gb_dev.root);
+ greybus_deregister(&gb_loopback_driver);
+ class_unregister(&loopback_class);
+ ida_destroy(&loopback_ida);
+}
+module_exit(loopback_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/power_supply.c b/drivers/staging/greybus/power_supply.c
new file mode 100644
index 0000000000..75cb170e05
--- /dev/null
+++ b/drivers/staging/greybus/power_supply.c
@@ -0,0 +1,1139 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Power Supply driver for a Greybus module.
+ *
+ * Copyright 2014-2015 Google Inc.
+ * Copyright 2014-2015 Linaro Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/greybus.h>
+
+#define PROP_MAX 32
+
+struct gb_power_supply_prop {
+ enum power_supply_property prop;
+ u8 gb_prop;
+ int val;
+ int previous_val;
+ bool is_writeable;
+};
+
+struct gb_power_supply {
+ u8 id;
+ bool registered;
+ struct power_supply *psy;
+ struct power_supply_desc desc;
+ char name[64];
+ struct gb_power_supplies *supplies;
+ struct delayed_work work;
+ char *manufacturer;
+ char *model_name;
+ char *serial_number;
+ u8 type;
+ u8 properties_count;
+ u8 properties_count_str;
+ unsigned long last_update;
+ u8 cache_invalid;
+ unsigned int update_interval;
+ bool changed;
+ struct gb_power_supply_prop *props;
+ enum power_supply_property *props_raw;
+ bool pm_acquired;
+ struct mutex supply_lock;
+};
+
+struct gb_power_supplies {
+ struct gb_connection *connection;
+ u8 supplies_count;
+ struct gb_power_supply *supply;
+ struct mutex supplies_lock;
+};
+
+#define to_gb_power_supply(x) power_supply_get_drvdata(x)
+
+/*
+ * General power supply properties that could be absent from various reasons,
+ * like kernel versions or vendor specific versions
+ */
+#ifndef POWER_SUPPLY_PROP_VOLTAGE_BOOT
+ #define POWER_SUPPLY_PROP_VOLTAGE_BOOT -1
+#endif
+#ifndef POWER_SUPPLY_PROP_CURRENT_BOOT
+ #define POWER_SUPPLY_PROP_CURRENT_BOOT -1
+#endif
+#ifndef POWER_SUPPLY_PROP_CALIBRATE
+ #define POWER_SUPPLY_PROP_CALIBRATE -1
+#endif
+
+/* cache time in milliseconds, if cache_time is set to 0 cache is disable */
+static unsigned int cache_time = 1000;
+/*
+ * update interval initial and maximum value, between the two will
+ * back-off exponential
+ */
+static unsigned int update_interval_init = 1 * HZ;
+static unsigned int update_interval_max = 30 * HZ;
+
+struct gb_power_supply_changes {
+ enum power_supply_property prop;
+ u32 tolerance_change;
+ void (*prop_changed)(struct gb_power_supply *gbpsy,
+ struct gb_power_supply_prop *prop);
+};
+
+static void gb_power_supply_state_change(struct gb_power_supply *gbpsy,
+ struct gb_power_supply_prop *prop);
+
+static const struct gb_power_supply_changes psy_props_changes[] = {
+ { .prop = GB_POWER_SUPPLY_PROP_STATUS,
+ .tolerance_change = 0,
+ .prop_changed = gb_power_supply_state_change,
+ },
+ { .prop = GB_POWER_SUPPLY_PROP_TEMP,
+ .tolerance_change = 500,
+ .prop_changed = NULL,
+ },
+ { .prop = GB_POWER_SUPPLY_PROP_ONLINE,
+ .tolerance_change = 0,
+ .prop_changed = NULL,
+ },
+};
+
+static int get_psp_from_gb_prop(int gb_prop, enum power_supply_property *psp)
+{
+ int prop;
+
+ switch (gb_prop) {
+ case GB_POWER_SUPPLY_PROP_STATUS:
+ prop = POWER_SUPPLY_PROP_STATUS;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_TYPE:
+ prop = POWER_SUPPLY_PROP_CHARGE_TYPE;
+ break;
+ case GB_POWER_SUPPLY_PROP_HEALTH:
+ prop = POWER_SUPPLY_PROP_HEALTH;
+ break;
+ case GB_POWER_SUPPLY_PROP_PRESENT:
+ prop = POWER_SUPPLY_PROP_PRESENT;
+ break;
+ case GB_POWER_SUPPLY_PROP_ONLINE:
+ prop = POWER_SUPPLY_PROP_ONLINE;
+ break;
+ case GB_POWER_SUPPLY_PROP_AUTHENTIC:
+ prop = POWER_SUPPLY_PROP_AUTHENTIC;
+ break;
+ case GB_POWER_SUPPLY_PROP_TECHNOLOGY:
+ prop = POWER_SUPPLY_PROP_TECHNOLOGY;
+ break;
+ case GB_POWER_SUPPLY_PROP_CYCLE_COUNT:
+ prop = POWER_SUPPLY_PROP_CYCLE_COUNT;
+ break;
+ case GB_POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ prop = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ prop = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+ break;
+ case GB_POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
+ break;
+ case GB_POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
+ break;
+ case GB_POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ prop = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ break;
+ case GB_POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ prop = POWER_SUPPLY_PROP_VOLTAGE_AVG;
+ break;
+ case GB_POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ prop = POWER_SUPPLY_PROP_VOLTAGE_OCV;
+ break;
+ case GB_POWER_SUPPLY_PROP_VOLTAGE_BOOT:
+ prop = POWER_SUPPLY_PROP_VOLTAGE_BOOT;
+ break;
+ case GB_POWER_SUPPLY_PROP_CURRENT_MAX:
+ prop = POWER_SUPPLY_PROP_CURRENT_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_CURRENT_NOW:
+ prop = POWER_SUPPLY_PROP_CURRENT_NOW;
+ break;
+ case GB_POWER_SUPPLY_PROP_CURRENT_AVG:
+ prop = POWER_SUPPLY_PROP_CURRENT_AVG;
+ break;
+ case GB_POWER_SUPPLY_PROP_CURRENT_BOOT:
+ prop = POWER_SUPPLY_PROP_CURRENT_BOOT;
+ break;
+ case GB_POWER_SUPPLY_PROP_POWER_NOW:
+ prop = POWER_SUPPLY_PROP_POWER_NOW;
+ break;
+ case GB_POWER_SUPPLY_PROP_POWER_AVG:
+ prop = POWER_SUPPLY_PROP_POWER_AVG;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
+ prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_FULL:
+ prop = POWER_SUPPLY_PROP_CHARGE_FULL;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_EMPTY:
+ prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_NOW:
+ prop = POWER_SUPPLY_PROP_CHARGE_NOW;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_AVG:
+ prop = POWER_SUPPLY_PROP_CHARGE_AVG;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ prop = POWER_SUPPLY_PROP_CHARGE_COUNTER;
+ break;
+ case GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ prop = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT;
+ break;
+ case GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ prop = POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ prop = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE;
+ break;
+ case GB_POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ prop = POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ prop = POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
+ prop = POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ prop = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
+ break;
+ case GB_POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
+ break;
+ case GB_POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN:
+ prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN;
+ break;
+ case GB_POWER_SUPPLY_PROP_ENERGY_FULL:
+ prop = POWER_SUPPLY_PROP_ENERGY_FULL;
+ break;
+ case GB_POWER_SUPPLY_PROP_ENERGY_EMPTY:
+ prop = POWER_SUPPLY_PROP_ENERGY_EMPTY;
+ break;
+ case GB_POWER_SUPPLY_PROP_ENERGY_NOW:
+ prop = POWER_SUPPLY_PROP_ENERGY_NOW;
+ break;
+ case GB_POWER_SUPPLY_PROP_ENERGY_AVG:
+ prop = POWER_SUPPLY_PROP_ENERGY_AVG;
+ break;
+ case GB_POWER_SUPPLY_PROP_CAPACITY:
+ prop = POWER_SUPPLY_PROP_CAPACITY;
+ break;
+ case GB_POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
+ prop = POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN;
+ break;
+ case GB_POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX:
+ prop = POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ prop = POWER_SUPPLY_PROP_CAPACITY_LEVEL;
+ break;
+ case GB_POWER_SUPPLY_PROP_TEMP:
+ prop = POWER_SUPPLY_PROP_TEMP;
+ break;
+ case GB_POWER_SUPPLY_PROP_TEMP_MAX:
+ prop = POWER_SUPPLY_PROP_TEMP_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_TEMP_MIN:
+ prop = POWER_SUPPLY_PROP_TEMP_MIN;
+ break;
+ case GB_POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ prop = POWER_SUPPLY_PROP_TEMP_ALERT_MIN;
+ break;
+ case GB_POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ prop = POWER_SUPPLY_PROP_TEMP_ALERT_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_TEMP_AMBIENT:
+ prop = POWER_SUPPLY_PROP_TEMP_AMBIENT;
+ break;
+ case GB_POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN:
+ prop = POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN;
+ break;
+ case GB_POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX:
+ prop = POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX;
+ break;
+ case GB_POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ prop = POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW;
+ break;
+ case GB_POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ prop = POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG;
+ break;
+ case GB_POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ prop = POWER_SUPPLY_PROP_TIME_TO_FULL_NOW;
+ break;
+ case GB_POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ prop = POWER_SUPPLY_PROP_TIME_TO_FULL_AVG;
+ break;
+ case GB_POWER_SUPPLY_PROP_TYPE:
+ prop = POWER_SUPPLY_PROP_TYPE;
+ break;
+ case GB_POWER_SUPPLY_PROP_SCOPE:
+ prop = POWER_SUPPLY_PROP_SCOPE;
+ break;
+ case GB_POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ prop = POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT;
+ break;
+ case GB_POWER_SUPPLY_PROP_CALIBRATE:
+ prop = POWER_SUPPLY_PROP_CALIBRATE;
+ break;
+ default:
+ prop = -1;
+ break;
+ }
+
+ if (prop < 0)
+ return prop;
+
+ *psp = (enum power_supply_property)prop;
+
+ return 0;
+}
+
+static struct gb_connection *get_conn_from_psy(struct gb_power_supply *gbpsy)
+{
+ return gbpsy->supplies->connection;
+}
+
+static struct gb_power_supply_prop *get_psy_prop(struct gb_power_supply *gbpsy,
+ enum power_supply_property psp)
+{
+ int i;
+
+ for (i = 0; i < gbpsy->properties_count; i++)
+ if (gbpsy->props[i].prop == psp)
+ return &gbpsy->props[i];
+ return NULL;
+}
+
+static int is_psy_prop_writeable(struct gb_power_supply *gbpsy,
+ enum power_supply_property psp)
+{
+ struct gb_power_supply_prop *prop;
+
+ prop = get_psy_prop(gbpsy, psp);
+ if (!prop)
+ return -ENOENT;
+ return prop->is_writeable ? 1 : 0;
+}
+
+static int is_prop_valint(enum power_supply_property psp)
+{
+ return ((psp < POWER_SUPPLY_PROP_MODEL_NAME) ? 1 : 0);
+}
+
+static void next_interval(struct gb_power_supply *gbpsy)
+{
+ if (gbpsy->update_interval == update_interval_max)
+ return;
+
+ /* do some exponential back-off in the update interval */
+ gbpsy->update_interval *= 2;
+ if (gbpsy->update_interval > update_interval_max)
+ gbpsy->update_interval = update_interval_max;
+}
+
+static void __gb_power_supply_changed(struct gb_power_supply *gbpsy)
+{
+ power_supply_changed(gbpsy->psy);
+}
+
+static void gb_power_supply_state_change(struct gb_power_supply *gbpsy,
+ struct gb_power_supply_prop *prop)
+{
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+ int ret;
+
+ /*
+ * Check gbpsy->pm_acquired to make sure only one pair of 'get_sync'
+ * and 'put_autosuspend' runtime pm call for state property change.
+ */
+ mutex_lock(&gbpsy->supply_lock);
+
+ if ((prop->val == GB_POWER_SUPPLY_STATUS_CHARGING) &&
+ !gbpsy->pm_acquired) {
+ ret = gb_pm_runtime_get_sync(connection->bundle);
+ if (ret)
+ dev_err(&connection->bundle->dev,
+ "Fail to set wake lock for charging state\n");
+ else
+ gbpsy->pm_acquired = true;
+ } else {
+ if (gbpsy->pm_acquired) {
+ ret = gb_pm_runtime_put_autosuspend(connection->bundle);
+ if (ret)
+ dev_err(&connection->bundle->dev,
+ "Fail to set wake unlock for none charging\n");
+ else
+ gbpsy->pm_acquired = false;
+ }
+ }
+
+ mutex_unlock(&gbpsy->supply_lock);
+}
+
+static void check_changed(struct gb_power_supply *gbpsy,
+ struct gb_power_supply_prop *prop)
+{
+ const struct gb_power_supply_changes *psyc;
+ int val = prop->val;
+ int prev_val = prop->previous_val;
+ bool changed = false;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(psy_props_changes); i++) {
+ psyc = &psy_props_changes[i];
+ if (prop->prop == psyc->prop) {
+ if (!psyc->tolerance_change)
+ changed = true;
+ else if (val < prev_val &&
+ prev_val - val > psyc->tolerance_change)
+ changed = true;
+ else if (val > prev_val &&
+ val - prev_val > psyc->tolerance_change)
+ changed = true;
+
+ if (changed && psyc->prop_changed)
+ psyc->prop_changed(gbpsy, prop);
+
+ if (changed)
+ gbpsy->changed = true;
+ break;
+ }
+ }
+}
+
+static int total_props(struct gb_power_supply *gbpsy)
+{
+ /* this return the intval plus the strval properties */
+ return (gbpsy->properties_count + gbpsy->properties_count_str);
+}
+
+static void prop_append(struct gb_power_supply *gbpsy,
+ enum power_supply_property prop)
+{
+ enum power_supply_property *new_props_raw;
+
+ gbpsy->properties_count_str++;
+ new_props_raw = krealloc(gbpsy->props_raw, total_props(gbpsy) *
+ sizeof(enum power_supply_property),
+ GFP_KERNEL);
+ if (!new_props_raw)
+ return;
+ gbpsy->props_raw = new_props_raw;
+ gbpsy->props_raw[total_props(gbpsy) - 1] = prop;
+}
+
+static int __gb_power_supply_set_name(char *init_name, char *name, size_t len)
+{
+ unsigned int i = 0;
+ int ret = 0;
+ struct power_supply *psy;
+
+ if (!strlen(init_name))
+ init_name = "gb_power_supply";
+ strscpy(name, init_name, len);
+
+ while ((ret < len) && (psy = power_supply_get_by_name(name))) {
+ power_supply_put(psy);
+
+ ret = snprintf(name, len, "%s_%u", init_name, ++i);
+ }
+ if (ret >= len)
+ return -ENOMEM;
+ return i;
+}
+
+static void _gb_power_supply_append_props(struct gb_power_supply *gbpsy)
+{
+ if (strlen(gbpsy->manufacturer))
+ prop_append(gbpsy, POWER_SUPPLY_PROP_MANUFACTURER);
+ if (strlen(gbpsy->model_name))
+ prop_append(gbpsy, POWER_SUPPLY_PROP_MODEL_NAME);
+ if (strlen(gbpsy->serial_number))
+ prop_append(gbpsy, POWER_SUPPLY_PROP_SERIAL_NUMBER);
+}
+
+static int gb_power_supply_description_get(struct gb_power_supply *gbpsy)
+{
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+ struct gb_power_supply_get_description_request req;
+ struct gb_power_supply_get_description_response resp;
+ int ret;
+
+ req.psy_id = gbpsy->id;
+
+ ret = gb_operation_sync(connection,
+ GB_POWER_SUPPLY_TYPE_GET_DESCRIPTION,
+ &req, sizeof(req), &resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
+
+ gbpsy->manufacturer = kstrndup(resp.manufacturer, PROP_MAX, GFP_KERNEL);
+ if (!gbpsy->manufacturer)
+ return -ENOMEM;
+ gbpsy->model_name = kstrndup(resp.model, PROP_MAX, GFP_KERNEL);
+ if (!gbpsy->model_name)
+ return -ENOMEM;
+ gbpsy->serial_number = kstrndup(resp.serial_number, PROP_MAX,
+ GFP_KERNEL);
+ if (!gbpsy->serial_number)
+ return -ENOMEM;
+
+ gbpsy->type = le16_to_cpu(resp.type);
+ gbpsy->properties_count = resp.properties_count;
+
+ return 0;
+}
+
+static int gb_power_supply_prop_descriptors_get(struct gb_power_supply *gbpsy)
+{
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+ struct gb_power_supply_get_property_descriptors_request *req;
+ struct gb_power_supply_get_property_descriptors_response *resp;
+ struct gb_operation *op;
+ u8 props_count = gbpsy->properties_count;
+ enum power_supply_property psp;
+ int ret;
+ int i, r = 0;
+
+ if (props_count == 0)
+ return 0;
+
+ op = gb_operation_create(connection,
+ GB_POWER_SUPPLY_TYPE_GET_PROP_DESCRIPTORS,
+ sizeof(*req),
+ struct_size(resp, props, props_count),
+ GFP_KERNEL);
+ if (!op)
+ return -ENOMEM;
+
+ req = op->request->payload;
+ req->psy_id = gbpsy->id;
+
+ ret = gb_operation_request_send_sync(op);
+ if (ret < 0)
+ goto out_put_operation;
+
+ resp = op->response->payload;
+
+ /* validate received properties */
+ for (i = 0; i < props_count; i++) {
+ ret = get_psp_from_gb_prop(resp->props[i].property, &psp);
+ if (ret < 0) {
+ dev_warn(&connection->bundle->dev,
+ "greybus property %u it is not supported by this kernel, dropped\n",
+ resp->props[i].property);
+ gbpsy->properties_count--;
+ }
+ }
+
+ gbpsy->props = kcalloc(gbpsy->properties_count, sizeof(*gbpsy->props),
+ GFP_KERNEL);
+ if (!gbpsy->props) {
+ ret = -ENOMEM;
+ goto out_put_operation;
+ }
+
+ gbpsy->props_raw = kcalloc(gbpsy->properties_count,
+ sizeof(*gbpsy->props_raw), GFP_KERNEL);
+ if (!gbpsy->props_raw) {
+ ret = -ENOMEM;
+ goto out_put_operation;
+ }
+
+ /* Store available properties, skip the ones we do not support */
+ for (i = 0; i < props_count; i++) {
+ ret = get_psp_from_gb_prop(resp->props[i].property, &psp);
+ if (ret < 0) {
+ r++;
+ continue;
+ }
+ gbpsy->props[i - r].prop = psp;
+ gbpsy->props[i - r].gb_prop = resp->props[i].property;
+ gbpsy->props_raw[i - r] = psp;
+ if (resp->props[i].is_writeable)
+ gbpsy->props[i - r].is_writeable = true;
+ }
+
+ /*
+ * now append the properties that we already got information in the
+ * get_description operation. (char * ones)
+ */
+ _gb_power_supply_append_props(gbpsy);
+
+ ret = 0;
+out_put_operation:
+ gb_operation_put(op);
+
+ return ret;
+}
+
+static int __gb_power_supply_property_update(struct gb_power_supply *gbpsy,
+ enum power_supply_property psp)
+{
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+ struct gb_power_supply_prop *prop;
+ struct gb_power_supply_get_property_request req;
+ struct gb_power_supply_get_property_response resp;
+ int val;
+ int ret;
+
+ prop = get_psy_prop(gbpsy, psp);
+ if (!prop)
+ return -EINVAL;
+ req.psy_id = gbpsy->id;
+ req.property = prop->gb_prop;
+
+ ret = gb_operation_sync(connection, GB_POWER_SUPPLY_TYPE_GET_PROPERTY,
+ &req, sizeof(req), &resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
+
+ val = le32_to_cpu(resp.prop_val);
+ if (val == prop->val)
+ return 0;
+
+ prop->previous_val = prop->val;
+ prop->val = val;
+
+ check_changed(gbpsy, prop);
+
+ return 0;
+}
+
+static int __gb_power_supply_property_get(struct gb_power_supply *gbpsy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct gb_power_supply_prop *prop;
+
+ prop = get_psy_prop(gbpsy, psp);
+ if (!prop)
+ return -EINVAL;
+
+ val->intval = prop->val;
+ return 0;
+}
+
+static int __gb_power_supply_property_strval_get(struct gb_power_supply *gbpsy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = gbpsy->model_name;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = gbpsy->manufacturer;
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = gbpsy->serial_number;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int _gb_power_supply_property_get(struct gb_power_supply *gbpsy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+ int ret;
+
+ /*
+ * Properties of type const char *, were already fetched on
+ * get_description operation and should be cached in gb
+ */
+ if (is_prop_valint(psp))
+ ret = __gb_power_supply_property_get(gbpsy, psp, val);
+ else
+ ret = __gb_power_supply_property_strval_get(gbpsy, psp, val);
+
+ if (ret < 0)
+ dev_err(&connection->bundle->dev, "get property %u\n", psp);
+
+ return 0;
+}
+
+static int is_cache_valid(struct gb_power_supply *gbpsy)
+{
+ /* check if cache is good enough or it has expired */
+ if (gbpsy->cache_invalid) {
+ gbpsy->cache_invalid = 0;
+ return 0;
+ }
+
+ if (gbpsy->last_update &&
+ time_is_after_jiffies(gbpsy->last_update +
+ msecs_to_jiffies(cache_time)))
+ return 1;
+
+ return 0;
+}
+
+static int gb_power_supply_status_get(struct gb_power_supply *gbpsy)
+{
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+ int ret = 0;
+ int i;
+
+ if (is_cache_valid(gbpsy))
+ return 0;
+
+ ret = gb_pm_runtime_get_sync(connection->bundle);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < gbpsy->properties_count; i++) {
+ ret = __gb_power_supply_property_update(gbpsy,
+ gbpsy->props[i].prop);
+ if (ret < 0)
+ break;
+ }
+
+ if (ret == 0)
+ gbpsy->last_update = jiffies;
+
+ gb_pm_runtime_put_autosuspend(connection->bundle);
+ return ret;
+}
+
+static void gb_power_supply_status_update(struct gb_power_supply *gbpsy)
+{
+ /* check if there a change that need to be reported */
+ gb_power_supply_status_get(gbpsy);
+
+ if (!gbpsy->changed)
+ return;
+
+ gbpsy->update_interval = update_interval_init;
+ __gb_power_supply_changed(gbpsy);
+ gbpsy->changed = false;
+}
+
+static void gb_power_supply_work(struct work_struct *work)
+{
+ struct gb_power_supply *gbpsy = container_of(work,
+ struct gb_power_supply,
+ work.work);
+
+ /*
+ * if the poll interval is not set, disable polling, this is helpful
+ * specially at unregister time.
+ */
+ if (!gbpsy->update_interval)
+ return;
+
+ gb_power_supply_status_update(gbpsy);
+ next_interval(gbpsy);
+ schedule_delayed_work(&gbpsy->work, gbpsy->update_interval);
+}
+
+static int get_property(struct power_supply *b,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct gb_power_supply *gbpsy = to_gb_power_supply(b);
+
+ gb_power_supply_status_get(gbpsy);
+
+ return _gb_power_supply_property_get(gbpsy, psp, val);
+}
+
+static int gb_power_supply_property_set(struct gb_power_supply *gbpsy,
+ enum power_supply_property psp,
+ int val)
+{
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+ struct gb_power_supply_prop *prop;
+ struct gb_power_supply_set_property_request req;
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(connection->bundle);
+ if (ret)
+ return ret;
+
+ prop = get_psy_prop(gbpsy, psp);
+ if (!prop) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ req.psy_id = gbpsy->id;
+ req.property = prop->gb_prop;
+ req.prop_val = cpu_to_le32((s32)val);
+
+ ret = gb_operation_sync(connection, GB_POWER_SUPPLY_TYPE_SET_PROPERTY,
+ &req, sizeof(req), NULL, 0);
+ if (ret < 0)
+ goto out;
+
+ /* cache immediately the new value */
+ prop->val = val;
+
+out:
+ gb_pm_runtime_put_autosuspend(connection->bundle);
+ return ret;
+}
+
+static int set_property(struct power_supply *b,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct gb_power_supply *gbpsy = to_gb_power_supply(b);
+
+ return gb_power_supply_property_set(gbpsy, psp, val->intval);
+}
+
+static int property_is_writeable(struct power_supply *b,
+ enum power_supply_property psp)
+{
+ struct gb_power_supply *gbpsy = to_gb_power_supply(b);
+
+ return is_psy_prop_writeable(gbpsy, psp);
+}
+
+static int gb_power_supply_register(struct gb_power_supply *gbpsy)
+{
+ struct gb_connection *connection = get_conn_from_psy(gbpsy);
+ struct power_supply_config cfg = {};
+
+ cfg.drv_data = gbpsy;
+
+ gbpsy->desc.name = gbpsy->name;
+ gbpsy->desc.type = gbpsy->type;
+ gbpsy->desc.properties = gbpsy->props_raw;
+ gbpsy->desc.num_properties = total_props(gbpsy);
+ gbpsy->desc.get_property = get_property;
+ gbpsy->desc.set_property = set_property;
+ gbpsy->desc.property_is_writeable = property_is_writeable;
+
+ gbpsy->psy = power_supply_register(&connection->bundle->dev,
+ &gbpsy->desc, &cfg);
+ return PTR_ERR_OR_ZERO(gbpsy->psy);
+}
+
+static void _gb_power_supply_free(struct gb_power_supply *gbpsy)
+{
+ kfree(gbpsy->serial_number);
+ kfree(gbpsy->model_name);
+ kfree(gbpsy->manufacturer);
+ kfree(gbpsy->props_raw);
+ kfree(gbpsy->props);
+}
+
+static void _gb_power_supply_release(struct gb_power_supply *gbpsy)
+{
+ gbpsy->update_interval = 0;
+
+ cancel_delayed_work_sync(&gbpsy->work);
+
+ if (gbpsy->registered)
+ power_supply_unregister(gbpsy->psy);
+
+ _gb_power_supply_free(gbpsy);
+}
+
+static void _gb_power_supplies_release(struct gb_power_supplies *supplies)
+{
+ int i;
+
+ if (!supplies->supply)
+ return;
+
+ mutex_lock(&supplies->supplies_lock);
+ for (i = 0; i < supplies->supplies_count; i++)
+ _gb_power_supply_release(&supplies->supply[i]);
+ kfree(supplies->supply);
+ mutex_unlock(&supplies->supplies_lock);
+ kfree(supplies);
+}
+
+static int gb_power_supplies_get_count(struct gb_power_supplies *supplies)
+{
+ struct gb_power_supply_get_supplies_response resp;
+ int ret;
+
+ ret = gb_operation_sync(supplies->connection,
+ GB_POWER_SUPPLY_TYPE_GET_SUPPLIES,
+ NULL, 0, &resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
+
+ if (!resp.supplies_count)
+ return -EINVAL;
+
+ supplies->supplies_count = resp.supplies_count;
+
+ return ret;
+}
+
+static int gb_power_supply_config(struct gb_power_supplies *supplies, int id)
+{
+ struct gb_power_supply *gbpsy = &supplies->supply[id];
+ int ret;
+
+ gbpsy->supplies = supplies;
+ gbpsy->id = id;
+
+ ret = gb_power_supply_description_get(gbpsy);
+ if (ret < 0)
+ return ret;
+
+ return gb_power_supply_prop_descriptors_get(gbpsy);
+}
+
+static int gb_power_supply_enable(struct gb_power_supply *gbpsy)
+{
+ int ret;
+
+ /* guarantee that we have an unique name, before register */
+ ret = __gb_power_supply_set_name(gbpsy->model_name, gbpsy->name,
+ sizeof(gbpsy->name));
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&gbpsy->supply_lock);
+
+ ret = gb_power_supply_register(gbpsy);
+ if (ret < 0)
+ return ret;
+
+ gbpsy->update_interval = update_interval_init;
+ INIT_DELAYED_WORK(&gbpsy->work, gb_power_supply_work);
+ schedule_delayed_work(&gbpsy->work, 0);
+
+ /* everything went fine, mark it for release code to know */
+ gbpsy->registered = true;
+
+ return 0;
+}
+
+static int gb_power_supplies_setup(struct gb_power_supplies *supplies)
+{
+ struct gb_connection *connection = supplies->connection;
+ int ret;
+ int i;
+
+ mutex_lock(&supplies->supplies_lock);
+
+ ret = gb_power_supplies_get_count(supplies);
+ if (ret < 0)
+ goto out;
+
+ supplies->supply = kcalloc(supplies->supplies_count,
+ sizeof(struct gb_power_supply),
+ GFP_KERNEL);
+
+ if (!supplies->supply) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < supplies->supplies_count; i++) {
+ ret = gb_power_supply_config(supplies, i);
+ if (ret < 0) {
+ dev_err(&connection->bundle->dev,
+ "Fail to configure supplies devices\n");
+ goto out;
+ }
+ }
+out:
+ mutex_unlock(&supplies->supplies_lock);
+ return ret;
+}
+
+static int gb_power_supplies_register(struct gb_power_supplies *supplies)
+{
+ struct gb_connection *connection = supplies->connection;
+ int ret = 0;
+ int i;
+
+ mutex_lock(&supplies->supplies_lock);
+
+ for (i = 0; i < supplies->supplies_count; i++) {
+ ret = gb_power_supply_enable(&supplies->supply[i]);
+ if (ret < 0) {
+ dev_err(&connection->bundle->dev,
+ "Fail to enable supplies devices\n");
+ break;
+ }
+ }
+
+ mutex_unlock(&supplies->supplies_lock);
+ return ret;
+}
+
+static int gb_supplies_request_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_power_supplies *supplies = gb_connection_get_data(connection);
+ struct gb_power_supply *gbpsy;
+ struct gb_message *request;
+ struct gb_power_supply_event_request *payload;
+ u8 psy_id;
+ u8 event;
+ int ret = 0;
+
+ if (op->type != GB_POWER_SUPPLY_TYPE_EVENT) {
+ dev_err(&connection->bundle->dev,
+ "Unsupported unsolicited event: %u\n", op->type);
+ return -EINVAL;
+ }
+
+ request = op->request;
+
+ if (request->payload_size < sizeof(*payload)) {
+ dev_err(&connection->bundle->dev,
+ "Wrong event size received (%zu < %zu)\n",
+ request->payload_size, sizeof(*payload));
+ return -EINVAL;
+ }
+
+ payload = request->payload;
+ psy_id = payload->psy_id;
+ mutex_lock(&supplies->supplies_lock);
+ if (psy_id >= supplies->supplies_count ||
+ !supplies->supply[psy_id].registered) {
+ dev_err(&connection->bundle->dev,
+ "Event received for unconfigured power_supply id: %d\n",
+ psy_id);
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ event = payload->event;
+ /*
+ * we will only handle events after setup is done and before release is
+ * running. For that just check update_interval.
+ */
+ gbpsy = &supplies->supply[psy_id];
+ if (!gbpsy->update_interval) {
+ ret = -ESHUTDOWN;
+ goto out_unlock;
+ }
+
+ if (event & GB_POWER_SUPPLY_UPDATE) {
+ /*
+ * we need to make sure we invalidate cache, if not no new
+ * values for the properties will be fetch and the all propose
+ * of this event is missed
+ */
+ gbpsy->cache_invalid = 1;
+ gb_power_supply_status_update(gbpsy);
+ }
+
+out_unlock:
+ mutex_unlock(&supplies->supplies_lock);
+ return ret;
+}
+
+static int gb_power_supply_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_power_supplies *supplies;
+ int ret;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_POWER_SUPPLY)
+ return -ENODEV;
+
+ supplies = kzalloc(sizeof(*supplies), GFP_KERNEL);
+ if (!supplies)
+ return -ENOMEM;
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ gb_supplies_request_handler);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ goto out;
+ }
+
+ supplies->connection = connection;
+ gb_connection_set_data(connection, supplies);
+
+ mutex_init(&supplies->supplies_lock);
+
+ greybus_set_drvdata(bundle, supplies);
+
+ /* We aren't ready to receive an incoming request yet */
+ ret = gb_connection_enable_tx(connection);
+ if (ret)
+ goto error_connection_destroy;
+
+ ret = gb_power_supplies_setup(supplies);
+ if (ret < 0)
+ goto error_connection_disable;
+
+ /* We are ready to receive an incoming request now, enable RX as well */
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto error_connection_disable;
+
+ ret = gb_power_supplies_register(supplies);
+ if (ret < 0)
+ goto error_connection_disable;
+
+ gb_pm_runtime_put_autosuspend(bundle);
+ return 0;
+
+error_connection_disable:
+ gb_connection_disable(connection);
+error_connection_destroy:
+ gb_connection_destroy(connection);
+out:
+ _gb_power_supplies_release(supplies);
+ return ret;
+}
+
+static void gb_power_supply_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_power_supplies *supplies = greybus_get_drvdata(bundle);
+
+ gb_connection_disable(supplies->connection);
+ gb_connection_destroy(supplies->connection);
+
+ _gb_power_supplies_release(supplies);
+}
+
+static const struct greybus_bundle_id gb_power_supply_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_POWER_SUPPLY) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_power_supply_id_table);
+
+static struct greybus_driver gb_power_supply_driver = {
+ .name = "power_supply",
+ .probe = gb_power_supply_probe,
+ .disconnect = gb_power_supply_disconnect,
+ .id_table = gb_power_supply_id_table,
+};
+module_greybus_driver(gb_power_supply_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/pwm.c b/drivers/staging/greybus/pwm.c
new file mode 100644
index 0000000000..57cc1960d0
--- /dev/null
+++ b/drivers/staging/greybus/pwm.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PWM Greybus driver.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pwm.h>
+#include <linux/greybus.h>
+
+#include "gbphy.h"
+
+struct gb_pwm_chip {
+ struct gb_connection *connection;
+ u8 pwm_max; /* max pwm number */
+
+ struct pwm_chip chip;
+};
+
+static inline struct gb_pwm_chip *pwm_chip_to_gb_pwm_chip(struct pwm_chip *chip)
+{
+ return container_of(chip, struct gb_pwm_chip, chip);
+}
+
+static int gb_pwm_count_operation(struct gb_pwm_chip *pwmc)
+{
+ struct gb_pwm_count_response response;
+ int ret;
+
+ ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_PWM_COUNT,
+ NULL, 0, &response, sizeof(response));
+ if (ret)
+ return ret;
+ pwmc->pwm_max = response.count;
+ return 0;
+}
+
+static int gb_pwm_activate_operation(struct gb_pwm_chip *pwmc,
+ u8 which)
+{
+ struct gb_pwm_activate_request request;
+ struct gbphy_device *gbphy_dev;
+ int ret;
+
+ if (which > pwmc->pwm_max)
+ return -EINVAL;
+
+ request.which = which;
+
+ gbphy_dev = to_gbphy_dev(pwmc->chip.dev);
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ return ret;
+
+ ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_ACTIVATE,
+ &request, sizeof(request), NULL, 0);
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+
+ return ret;
+}
+
+static int gb_pwm_deactivate_operation(struct gb_pwm_chip *pwmc,
+ u8 which)
+{
+ struct gb_pwm_deactivate_request request;
+ struct gbphy_device *gbphy_dev;
+ int ret;
+
+ if (which > pwmc->pwm_max)
+ return -EINVAL;
+
+ request.which = which;
+
+ gbphy_dev = to_gbphy_dev(pwmc->chip.dev);
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ return ret;
+
+ ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_DEACTIVATE,
+ &request, sizeof(request), NULL, 0);
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+
+ return ret;
+}
+
+static int gb_pwm_config_operation(struct gb_pwm_chip *pwmc,
+ u8 which, u32 duty, u32 period)
+{
+ struct gb_pwm_config_request request;
+ struct gbphy_device *gbphy_dev;
+ int ret;
+
+ if (which > pwmc->pwm_max)
+ return -EINVAL;
+
+ request.which = which;
+ request.duty = cpu_to_le32(duty);
+ request.period = cpu_to_le32(period);
+
+ gbphy_dev = to_gbphy_dev(pwmc->chip.dev);
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ return ret;
+
+ ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_CONFIG,
+ &request, sizeof(request), NULL, 0);
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+
+ return ret;
+}
+
+static int gb_pwm_set_polarity_operation(struct gb_pwm_chip *pwmc,
+ u8 which, u8 polarity)
+{
+ struct gb_pwm_polarity_request request;
+ struct gbphy_device *gbphy_dev;
+ int ret;
+
+ if (which > pwmc->pwm_max)
+ return -EINVAL;
+
+ request.which = which;
+ request.polarity = polarity;
+
+ gbphy_dev = to_gbphy_dev(pwmc->chip.dev);
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ return ret;
+
+ ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_POLARITY,
+ &request, sizeof(request), NULL, 0);
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+
+ return ret;
+}
+
+static int gb_pwm_enable_operation(struct gb_pwm_chip *pwmc,
+ u8 which)
+{
+ struct gb_pwm_enable_request request;
+ struct gbphy_device *gbphy_dev;
+ int ret;
+
+ if (which > pwmc->pwm_max)
+ return -EINVAL;
+
+ request.which = which;
+
+ gbphy_dev = to_gbphy_dev(pwmc->chip.dev);
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ return ret;
+
+ ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_ENABLE,
+ &request, sizeof(request), NULL, 0);
+ if (ret)
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+
+ return ret;
+}
+
+static int gb_pwm_disable_operation(struct gb_pwm_chip *pwmc,
+ u8 which)
+{
+ struct gb_pwm_disable_request request;
+ struct gbphy_device *gbphy_dev;
+ int ret;
+
+ if (which > pwmc->pwm_max)
+ return -EINVAL;
+
+ request.which = which;
+
+ ret = gb_operation_sync(pwmc->connection, GB_PWM_TYPE_DISABLE,
+ &request, sizeof(request), NULL, 0);
+
+ gbphy_dev = to_gbphy_dev(pwmc->chip.dev);
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+
+ return ret;
+}
+
+static int gb_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip);
+
+ return gb_pwm_activate_operation(pwmc, pwm->hwpwm);
+};
+
+static void gb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip);
+
+ if (pwm_is_enabled(pwm))
+ dev_warn(chip->dev, "freeing PWM device without disabling\n");
+
+ gb_pwm_deactivate_operation(pwmc, pwm->hwpwm);
+}
+
+static int gb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ int err;
+ bool enabled = pwm->state.enabled;
+ u64 period = state->period;
+ u64 duty_cycle = state->duty_cycle;
+ struct gb_pwm_chip *pwmc = pwm_chip_to_gb_pwm_chip(chip);
+
+ /* Set polarity */
+ if (state->polarity != pwm->state.polarity) {
+ if (enabled) {
+ gb_pwm_disable_operation(pwmc, pwm->hwpwm);
+ enabled = false;
+ }
+ err = gb_pwm_set_polarity_operation(pwmc, pwm->hwpwm, state->polarity);
+ if (err)
+ return err;
+ }
+
+ if (!state->enabled) {
+ if (enabled)
+ gb_pwm_disable_operation(pwmc, pwm->hwpwm);
+ return 0;
+ }
+
+ /*
+ * Set period and duty cycle
+ *
+ * PWM privodes 64-bit period and duty_cycle, but greybus only accepts
+ * 32-bit, so their values have to be limited to U32_MAX.
+ */
+ if (period > U32_MAX)
+ period = U32_MAX;
+
+ if (duty_cycle > period)
+ duty_cycle = period;
+
+ err = gb_pwm_config_operation(pwmc, pwm->hwpwm, duty_cycle, period);
+ if (err)
+ return err;
+
+ /* enable/disable */
+ if (!enabled)
+ return gb_pwm_enable_operation(pwmc, pwm->hwpwm);
+
+ return 0;
+}
+
+static const struct pwm_ops gb_pwm_ops = {
+ .request = gb_pwm_request,
+ .free = gb_pwm_free,
+ .apply = gb_pwm_apply,
+ .owner = THIS_MODULE,
+};
+
+static int gb_pwm_probe(struct gbphy_device *gbphy_dev,
+ const struct gbphy_device_id *id)
+{
+ struct gb_connection *connection;
+ struct gb_pwm_chip *pwmc;
+ struct pwm_chip *chip;
+ int ret;
+
+ pwmc = kzalloc(sizeof(*pwmc), GFP_KERNEL);
+ if (!pwmc)
+ return -ENOMEM;
+
+ connection = gb_connection_create(gbphy_dev->bundle,
+ le16_to_cpu(gbphy_dev->cport_desc->id),
+ NULL);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ goto exit_pwmc_free;
+ }
+
+ pwmc->connection = connection;
+ gb_connection_set_data(connection, pwmc);
+ gb_gbphy_set_data(gbphy_dev, pwmc);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto exit_connection_destroy;
+
+ /* Query number of pwms present */
+ ret = gb_pwm_count_operation(pwmc);
+ if (ret)
+ goto exit_connection_disable;
+
+ chip = &pwmc->chip;
+
+ chip->dev = &gbphy_dev->dev;
+ chip->ops = &gb_pwm_ops;
+ chip->npwm = pwmc->pwm_max + 1;
+
+ ret = pwmchip_add(chip);
+ if (ret) {
+ dev_err(&gbphy_dev->dev,
+ "failed to register PWM: %d\n", ret);
+ goto exit_connection_disable;
+ }
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+ return 0;
+
+exit_connection_disable:
+ gb_connection_disable(connection);
+exit_connection_destroy:
+ gb_connection_destroy(connection);
+exit_pwmc_free:
+ kfree(pwmc);
+ return ret;
+}
+
+static void gb_pwm_remove(struct gbphy_device *gbphy_dev)
+{
+ struct gb_pwm_chip *pwmc = gb_gbphy_get_data(gbphy_dev);
+ struct gb_connection *connection = pwmc->connection;
+ int ret;
+
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ gbphy_runtime_get_noresume(gbphy_dev);
+
+ pwmchip_remove(&pwmc->chip);
+ gb_connection_disable(connection);
+ gb_connection_destroy(connection);
+ kfree(pwmc);
+}
+
+static const struct gbphy_device_id gb_pwm_id_table[] = {
+ { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_PWM) },
+ { },
+};
+MODULE_DEVICE_TABLE(gbphy, gb_pwm_id_table);
+
+static struct gbphy_driver pwm_driver = {
+ .name = "pwm",
+ .probe = gb_pwm_probe,
+ .remove = gb_pwm_remove,
+ .id_table = gb_pwm_id_table,
+};
+
+module_gbphy_driver(pwm_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/raw.c b/drivers/staging/greybus/raw.c
new file mode 100644
index 0000000000..8bca8cb12c
--- /dev/null
+++ b/drivers/staging/greybus/raw.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus driver for the Raw protocol
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sizes.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/uaccess.h>
+#include <linux/greybus.h>
+
+struct gb_raw {
+ struct gb_connection *connection;
+
+ struct list_head list;
+ int list_data;
+ struct mutex list_lock;
+ dev_t dev;
+ struct cdev cdev;
+ struct device *device;
+};
+
+struct raw_data {
+ struct list_head entry;
+ u32 len;
+ u8 data[];
+};
+
+static struct class *raw_class;
+static int raw_major;
+static const struct file_operations raw_fops;
+static DEFINE_IDA(minors);
+
+/* Number of minor devices this driver supports */
+#define NUM_MINORS 256
+
+/* Maximum size of any one send data buffer we support */
+#define MAX_PACKET_SIZE (PAGE_SIZE * 2)
+
+/*
+ * Maximum size of the data in the receive buffer we allow before we start to
+ * drop messages on the floor
+ */
+#define MAX_DATA_SIZE (MAX_PACKET_SIZE * 8)
+
+/*
+ * Add the raw data message to the list of received messages.
+ */
+static int receive_data(struct gb_raw *raw, u32 len, u8 *data)
+{
+ struct raw_data *raw_data;
+ struct device *dev = &raw->connection->bundle->dev;
+ int retval = 0;
+
+ if (len > MAX_PACKET_SIZE) {
+ dev_err(dev, "Too big of a data packet, rejected\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&raw->list_lock);
+ if ((raw->list_data + len) > MAX_DATA_SIZE) {
+ dev_err(dev, "Too much data in receive buffer, now dropping packets\n");
+ retval = -EINVAL;
+ goto exit;
+ }
+
+ raw_data = kmalloc(sizeof(*raw_data) + len, GFP_KERNEL);
+ if (!raw_data) {
+ retval = -ENOMEM;
+ goto exit;
+ }
+
+ raw->list_data += len;
+ raw_data->len = len;
+ memcpy(&raw_data->data[0], data, len);
+
+ list_add_tail(&raw_data->entry, &raw->list);
+exit:
+ mutex_unlock(&raw->list_lock);
+ return retval;
+}
+
+static int gb_raw_request_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct device *dev = &connection->bundle->dev;
+ struct gb_raw *raw = greybus_get_drvdata(connection->bundle);
+ struct gb_raw_send_request *receive;
+ u32 len;
+
+ if (op->type != GB_RAW_TYPE_SEND) {
+ dev_err(dev, "unknown request type 0x%02x\n", op->type);
+ return -EINVAL;
+ }
+
+ /* Verify size of payload */
+ if (op->request->payload_size < sizeof(*receive)) {
+ dev_err(dev, "raw receive request too small (%zu < %zu)\n",
+ op->request->payload_size, sizeof(*receive));
+ return -EINVAL;
+ }
+ receive = op->request->payload;
+ len = le32_to_cpu(receive->len);
+ if (len != (int)(op->request->payload_size - sizeof(__le32))) {
+ dev_err(dev, "raw receive request wrong size %d vs %d\n", len,
+ (int)(op->request->payload_size - sizeof(__le32)));
+ return -EINVAL;
+ }
+ if (len == 0) {
+ dev_err(dev, "raw receive request of 0 bytes?\n");
+ return -EINVAL;
+ }
+
+ return receive_data(raw, len, receive->data);
+}
+
+static int gb_raw_send(struct gb_raw *raw, u32 len, const char __user *data)
+{
+ struct gb_connection *connection = raw->connection;
+ struct gb_raw_send_request *request;
+ int retval;
+
+ request = kmalloc(len + sizeof(*request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ if (copy_from_user(&request->data[0], data, len)) {
+ kfree(request);
+ return -EFAULT;
+ }
+
+ request->len = cpu_to_le32(len);
+
+ retval = gb_operation_sync(connection, GB_RAW_TYPE_SEND,
+ request, len + sizeof(*request),
+ NULL, 0);
+
+ kfree(request);
+ return retval;
+}
+
+static int gb_raw_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_raw *raw;
+ int retval;
+ int minor;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_RAW)
+ return -ENODEV;
+
+ raw = kzalloc(sizeof(*raw), GFP_KERNEL);
+ if (!raw)
+ return -ENOMEM;
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ gb_raw_request_handler);
+ if (IS_ERR(connection)) {
+ retval = PTR_ERR(connection);
+ goto error_free;
+ }
+
+ INIT_LIST_HEAD(&raw->list);
+ mutex_init(&raw->list_lock);
+
+ raw->connection = connection;
+ greybus_set_drvdata(bundle, raw);
+
+ minor = ida_simple_get(&minors, 0, 0, GFP_KERNEL);
+ if (minor < 0) {
+ retval = minor;
+ goto error_connection_destroy;
+ }
+
+ raw->dev = MKDEV(raw_major, minor);
+ cdev_init(&raw->cdev, &raw_fops);
+
+ retval = gb_connection_enable(connection);
+ if (retval)
+ goto error_remove_ida;
+
+ retval = cdev_add(&raw->cdev, raw->dev, 1);
+ if (retval)
+ goto error_connection_disable;
+
+ raw->device = device_create(raw_class, &connection->bundle->dev,
+ raw->dev, raw, "gb!raw%d", minor);
+ if (IS_ERR(raw->device)) {
+ retval = PTR_ERR(raw->device);
+ goto error_del_cdev;
+ }
+
+ return 0;
+
+error_del_cdev:
+ cdev_del(&raw->cdev);
+
+error_connection_disable:
+ gb_connection_disable(connection);
+
+error_remove_ida:
+ ida_simple_remove(&minors, minor);
+
+error_connection_destroy:
+ gb_connection_destroy(connection);
+
+error_free:
+ kfree(raw);
+ return retval;
+}
+
+static void gb_raw_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_raw *raw = greybus_get_drvdata(bundle);
+ struct gb_connection *connection = raw->connection;
+ struct raw_data *raw_data;
+ struct raw_data *temp;
+
+ // FIXME - handle removing a connection when the char device node is open.
+ device_destroy(raw_class, raw->dev);
+ cdev_del(&raw->cdev);
+ gb_connection_disable(connection);
+ ida_simple_remove(&minors, MINOR(raw->dev));
+ gb_connection_destroy(connection);
+
+ mutex_lock(&raw->list_lock);
+ list_for_each_entry_safe(raw_data, temp, &raw->list, entry) {
+ list_del(&raw_data->entry);
+ kfree(raw_data);
+ }
+ mutex_unlock(&raw->list_lock);
+
+ kfree(raw);
+}
+
+/*
+ * Character device node interfaces.
+ *
+ * Note, we are using read/write to only allow a single read/write per message.
+ * This means for read(), you have to provide a big enough buffer for the full
+ * message to be copied into. If the buffer isn't big enough, the read() will
+ * fail with -ENOSPC.
+ */
+
+static int raw_open(struct inode *inode, struct file *file)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct gb_raw *raw = container_of(cdev, struct gb_raw, cdev);
+
+ file->private_data = raw;
+ return 0;
+}
+
+static ssize_t raw_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct gb_raw *raw = file->private_data;
+ int retval;
+
+ if (!count)
+ return 0;
+
+ if (count > MAX_PACKET_SIZE)
+ return -E2BIG;
+
+ retval = gb_raw_send(raw, count, buf);
+ if (retval)
+ return retval;
+
+ return count;
+}
+
+static ssize_t raw_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct gb_raw *raw = file->private_data;
+ int retval = 0;
+ struct raw_data *raw_data;
+
+ mutex_lock(&raw->list_lock);
+ if (list_empty(&raw->list))
+ goto exit;
+
+ raw_data = list_first_entry(&raw->list, struct raw_data, entry);
+ if (raw_data->len > count) {
+ retval = -ENOSPC;
+ goto exit;
+ }
+
+ if (copy_to_user(buf, &raw_data->data[0], raw_data->len)) {
+ retval = -EFAULT;
+ goto exit;
+ }
+
+ list_del(&raw_data->entry);
+ raw->list_data -= raw_data->len;
+ retval = raw_data->len;
+ kfree(raw_data);
+
+exit:
+ mutex_unlock(&raw->list_lock);
+ return retval;
+}
+
+static const struct file_operations raw_fops = {
+ .owner = THIS_MODULE,
+ .write = raw_write,
+ .read = raw_read,
+ .open = raw_open,
+ .llseek = noop_llseek,
+};
+
+static const struct greybus_bundle_id gb_raw_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_RAW) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_raw_id_table);
+
+static struct greybus_driver gb_raw_driver = {
+ .name = "raw",
+ .probe = gb_raw_probe,
+ .disconnect = gb_raw_disconnect,
+ .id_table = gb_raw_id_table,
+};
+
+static int raw_init(void)
+{
+ dev_t dev;
+ int retval;
+
+ raw_class = class_create("gb_raw");
+ if (IS_ERR(raw_class)) {
+ retval = PTR_ERR(raw_class);
+ goto error_class;
+ }
+
+ retval = alloc_chrdev_region(&dev, 0, NUM_MINORS, "gb_raw");
+ if (retval < 0)
+ goto error_chrdev;
+
+ raw_major = MAJOR(dev);
+
+ retval = greybus_register(&gb_raw_driver);
+ if (retval)
+ goto error_gb;
+
+ return 0;
+
+error_gb:
+ unregister_chrdev_region(dev, NUM_MINORS);
+error_chrdev:
+ class_destroy(raw_class);
+error_class:
+ return retval;
+}
+module_init(raw_init);
+
+static void __exit raw_exit(void)
+{
+ greybus_deregister(&gb_raw_driver);
+ unregister_chrdev_region(MKDEV(raw_major, 0), NUM_MINORS);
+ class_destroy(raw_class);
+ ida_destroy(&minors);
+}
+module_exit(raw_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/sdio.c b/drivers/staging/greybus/sdio.c
new file mode 100644
index 0000000000..25bee5335c
--- /dev/null
+++ b/drivers/staging/greybus/sdio.c
@@ -0,0 +1,883 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SD/MMC Greybus driver.
+ *
+ * Copyright 2014-2015 Google Inc.
+ * Copyright 2014-2015 Linaro Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/scatterlist.h>
+#include <linux/workqueue.h>
+#include <linux/greybus.h>
+
+#include "gbphy.h"
+
+struct gb_sdio_host {
+ struct gb_connection *connection;
+ struct gbphy_device *gbphy_dev;
+ struct mmc_host *mmc;
+ struct mmc_request *mrq;
+ struct mutex lock; /* lock for this host */
+ size_t data_max;
+ spinlock_t xfer; /* lock to cancel ongoing transfer */
+ bool xfer_stop;
+ struct workqueue_struct *mrq_workqueue;
+ struct work_struct mrqwork;
+ u8 queued_events;
+ bool removed;
+ bool card_present;
+ bool read_only;
+};
+
+#define GB_SDIO_RSP_R1_R5_R6_R7 (GB_SDIO_RSP_PRESENT | GB_SDIO_RSP_CRC | \
+ GB_SDIO_RSP_OPCODE)
+#define GB_SDIO_RSP_R3_R4 (GB_SDIO_RSP_PRESENT)
+#define GB_SDIO_RSP_R2 (GB_SDIO_RSP_PRESENT | GB_SDIO_RSP_CRC | \
+ GB_SDIO_RSP_136)
+#define GB_SDIO_RSP_R1B (GB_SDIO_RSP_PRESENT | GB_SDIO_RSP_CRC | \
+ GB_SDIO_RSP_OPCODE | GB_SDIO_RSP_BUSY)
+
+/* kernel vdd starts at 0x80 and we need to translate to greybus ones 0x01 */
+#define GB_SDIO_VDD_SHIFT 8
+
+#ifndef MMC_CAP2_CORE_RUNTIME_PM
+#define MMC_CAP2_CORE_RUNTIME_PM 0
+#endif
+
+static inline bool single_op(struct mmc_command *cmd)
+{
+ u32 opcode = cmd->opcode;
+
+ return opcode == MMC_WRITE_BLOCK ||
+ opcode == MMC_READ_SINGLE_BLOCK;
+}
+
+static void _gb_sdio_set_host_caps(struct gb_sdio_host *host, u32 r)
+{
+ u32 caps = 0;
+ u32 caps2 = 0;
+
+ caps = ((r & GB_SDIO_CAP_NONREMOVABLE) ? MMC_CAP_NONREMOVABLE : 0) |
+ ((r & GB_SDIO_CAP_4_BIT_DATA) ? MMC_CAP_4_BIT_DATA : 0) |
+ ((r & GB_SDIO_CAP_8_BIT_DATA) ? MMC_CAP_8_BIT_DATA : 0) |
+ ((r & GB_SDIO_CAP_MMC_HS) ? MMC_CAP_MMC_HIGHSPEED : 0) |
+ ((r & GB_SDIO_CAP_SD_HS) ? MMC_CAP_SD_HIGHSPEED : 0) |
+ ((r & GB_SDIO_CAP_1_2V_DDR) ? MMC_CAP_1_2V_DDR : 0) |
+ ((r & GB_SDIO_CAP_1_8V_DDR) ? MMC_CAP_1_8V_DDR : 0) |
+ ((r & GB_SDIO_CAP_POWER_OFF_CARD) ? MMC_CAP_POWER_OFF_CARD : 0) |
+ ((r & GB_SDIO_CAP_UHS_SDR12) ? MMC_CAP_UHS_SDR12 : 0) |
+ ((r & GB_SDIO_CAP_UHS_SDR25) ? MMC_CAP_UHS_SDR25 : 0) |
+ ((r & GB_SDIO_CAP_UHS_SDR50) ? MMC_CAP_UHS_SDR50 : 0) |
+ ((r & GB_SDIO_CAP_UHS_SDR104) ? MMC_CAP_UHS_SDR104 : 0) |
+ ((r & GB_SDIO_CAP_UHS_DDR50) ? MMC_CAP_UHS_DDR50 : 0) |
+ ((r & GB_SDIO_CAP_DRIVER_TYPE_A) ? MMC_CAP_DRIVER_TYPE_A : 0) |
+ ((r & GB_SDIO_CAP_DRIVER_TYPE_C) ? MMC_CAP_DRIVER_TYPE_C : 0) |
+ ((r & GB_SDIO_CAP_DRIVER_TYPE_D) ? MMC_CAP_DRIVER_TYPE_D : 0);
+
+ caps2 = ((r & GB_SDIO_CAP_HS200_1_2V) ? MMC_CAP2_HS200_1_2V_SDR : 0) |
+ ((r & GB_SDIO_CAP_HS400_1_2V) ? MMC_CAP2_HS400_1_2V : 0) |
+ ((r & GB_SDIO_CAP_HS400_1_8V) ? MMC_CAP2_HS400_1_8V : 0) |
+ ((r & GB_SDIO_CAP_HS200_1_8V) ? MMC_CAP2_HS200_1_8V_SDR : 0);
+
+ host->mmc->caps = caps;
+ host->mmc->caps2 = caps2 | MMC_CAP2_CORE_RUNTIME_PM;
+
+ if (caps & MMC_CAP_NONREMOVABLE)
+ host->card_present = true;
+}
+
+static u32 _gb_sdio_get_host_ocr(u32 ocr)
+{
+ return (((ocr & GB_SDIO_VDD_165_195) ? MMC_VDD_165_195 : 0) |
+ ((ocr & GB_SDIO_VDD_20_21) ? MMC_VDD_20_21 : 0) |
+ ((ocr & GB_SDIO_VDD_21_22) ? MMC_VDD_21_22 : 0) |
+ ((ocr & GB_SDIO_VDD_22_23) ? MMC_VDD_22_23 : 0) |
+ ((ocr & GB_SDIO_VDD_23_24) ? MMC_VDD_23_24 : 0) |
+ ((ocr & GB_SDIO_VDD_24_25) ? MMC_VDD_24_25 : 0) |
+ ((ocr & GB_SDIO_VDD_25_26) ? MMC_VDD_25_26 : 0) |
+ ((ocr & GB_SDIO_VDD_26_27) ? MMC_VDD_26_27 : 0) |
+ ((ocr & GB_SDIO_VDD_27_28) ? MMC_VDD_27_28 : 0) |
+ ((ocr & GB_SDIO_VDD_28_29) ? MMC_VDD_28_29 : 0) |
+ ((ocr & GB_SDIO_VDD_29_30) ? MMC_VDD_29_30 : 0) |
+ ((ocr & GB_SDIO_VDD_30_31) ? MMC_VDD_30_31 : 0) |
+ ((ocr & GB_SDIO_VDD_31_32) ? MMC_VDD_31_32 : 0) |
+ ((ocr & GB_SDIO_VDD_32_33) ? MMC_VDD_32_33 : 0) |
+ ((ocr & GB_SDIO_VDD_33_34) ? MMC_VDD_33_34 : 0) |
+ ((ocr & GB_SDIO_VDD_34_35) ? MMC_VDD_34_35 : 0) |
+ ((ocr & GB_SDIO_VDD_35_36) ? MMC_VDD_35_36 : 0)
+ );
+}
+
+static int gb_sdio_get_caps(struct gb_sdio_host *host)
+{
+ struct gb_sdio_get_caps_response response;
+ struct mmc_host *mmc = host->mmc;
+ u16 data_max;
+ u32 blksz;
+ u32 ocr;
+ u32 r;
+ int ret;
+
+ ret = gb_operation_sync(host->connection, GB_SDIO_TYPE_GET_CAPABILITIES,
+ NULL, 0, &response, sizeof(response));
+ if (ret < 0)
+ return ret;
+ r = le32_to_cpu(response.caps);
+
+ _gb_sdio_set_host_caps(host, r);
+
+ /* get the max block size that could fit our payload */
+ data_max = gb_operation_get_payload_size_max(host->connection);
+ data_max = min(data_max - sizeof(struct gb_sdio_transfer_request),
+ data_max - sizeof(struct gb_sdio_transfer_response));
+
+ blksz = min_t(u16, le16_to_cpu(response.max_blk_size), data_max);
+ blksz = max_t(u32, 512, blksz);
+
+ mmc->max_blk_size = rounddown_pow_of_two(blksz);
+ mmc->max_blk_count = le16_to_cpu(response.max_blk_count);
+ host->data_max = data_max;
+
+ /* get ocr supported values */
+ ocr = _gb_sdio_get_host_ocr(le32_to_cpu(response.ocr));
+ mmc->ocr_avail = ocr;
+ mmc->ocr_avail_sdio = mmc->ocr_avail;
+ mmc->ocr_avail_sd = mmc->ocr_avail;
+ mmc->ocr_avail_mmc = mmc->ocr_avail;
+
+ /* get frequency range values */
+ mmc->f_min = le32_to_cpu(response.f_min);
+ mmc->f_max = le32_to_cpu(response.f_max);
+
+ return 0;
+}
+
+static void _gb_queue_event(struct gb_sdio_host *host, u8 event)
+{
+ if (event & GB_SDIO_CARD_INSERTED)
+ host->queued_events &= ~GB_SDIO_CARD_REMOVED;
+ else if (event & GB_SDIO_CARD_REMOVED)
+ host->queued_events &= ~GB_SDIO_CARD_INSERTED;
+
+ host->queued_events |= event;
+}
+
+static int _gb_sdio_process_events(struct gb_sdio_host *host, u8 event)
+{
+ u8 state_changed = 0;
+
+ if (event & GB_SDIO_CARD_INSERTED) {
+ if (host->mmc->caps & MMC_CAP_NONREMOVABLE)
+ return 0;
+ if (host->card_present)
+ return 0;
+ host->card_present = true;
+ state_changed = 1;
+ }
+
+ if (event & GB_SDIO_CARD_REMOVED) {
+ if (host->mmc->caps & MMC_CAP_NONREMOVABLE)
+ return 0;
+ if (!(host->card_present))
+ return 0;
+ host->card_present = false;
+ state_changed = 1;
+ }
+
+ if (event & GB_SDIO_WP)
+ host->read_only = true;
+
+ if (state_changed) {
+ dev_info(mmc_dev(host->mmc), "card %s now event\n",
+ (host->card_present ? "inserted" : "removed"));
+ mmc_detect_change(host->mmc, 0);
+ }
+
+ return 0;
+}
+
+static int gb_sdio_request_handler(struct gb_operation *op)
+{
+ struct gb_sdio_host *host = gb_connection_get_data(op->connection);
+ struct gb_message *request;
+ struct gb_sdio_event_request *payload;
+ u8 type = op->type;
+ int ret = 0;
+ u8 event;
+
+ if (type != GB_SDIO_TYPE_EVENT) {
+ dev_err(mmc_dev(host->mmc),
+ "unsupported unsolicited event: %u\n", type);
+ return -EINVAL;
+ }
+
+ request = op->request;
+
+ if (request->payload_size < sizeof(*payload)) {
+ dev_err(mmc_dev(host->mmc), "wrong event size received (%zu < %zu)\n",
+ request->payload_size, sizeof(*payload));
+ return -EINVAL;
+ }
+
+ payload = request->payload;
+ event = payload->event;
+
+ if (host->removed)
+ _gb_queue_event(host, event);
+ else
+ ret = _gb_sdio_process_events(host, event);
+
+ return ret;
+}
+
+static int gb_sdio_set_ios(struct gb_sdio_host *host,
+ struct gb_sdio_set_ios_request *request)
+{
+ int ret;
+
+ ret = gbphy_runtime_get_sync(host->gbphy_dev);
+ if (ret)
+ return ret;
+
+ ret = gb_operation_sync(host->connection, GB_SDIO_TYPE_SET_IOS, request,
+ sizeof(*request), NULL, 0);
+
+ gbphy_runtime_put_autosuspend(host->gbphy_dev);
+
+ return ret;
+}
+
+static int _gb_sdio_send(struct gb_sdio_host *host, struct mmc_data *data,
+ size_t len, u16 nblocks, off_t skip)
+{
+ struct gb_sdio_transfer_request *request;
+ struct gb_sdio_transfer_response *response;
+ struct gb_operation *operation;
+ struct scatterlist *sg = data->sg;
+ unsigned int sg_len = data->sg_len;
+ size_t copied;
+ u16 send_blksz;
+ u16 send_blocks;
+ int ret;
+
+ WARN_ON(len > host->data_max);
+
+ operation = gb_operation_create(host->connection, GB_SDIO_TYPE_TRANSFER,
+ len + sizeof(*request),
+ sizeof(*response), GFP_KERNEL);
+ if (!operation)
+ return -ENOMEM;
+
+ request = operation->request->payload;
+ request->data_flags = data->flags >> 8;
+ request->data_blocks = cpu_to_le16(nblocks);
+ request->data_blksz = cpu_to_le16(data->blksz);
+
+ copied = sg_pcopy_to_buffer(sg, sg_len, &request->data[0], len, skip);
+
+ if (copied != len) {
+ ret = -EINVAL;
+ goto err_put_operation;
+ }
+
+ ret = gb_operation_request_send_sync(operation);
+ if (ret < 0)
+ goto err_put_operation;
+
+ response = operation->response->payload;
+
+ send_blocks = le16_to_cpu(response->data_blocks);
+ send_blksz = le16_to_cpu(response->data_blksz);
+
+ if (len != send_blksz * send_blocks) {
+ dev_err(mmc_dev(host->mmc), "send: size received: %zu != %d\n",
+ len, send_blksz * send_blocks);
+ ret = -EINVAL;
+ }
+
+err_put_operation:
+ gb_operation_put(operation);
+
+ return ret;
+}
+
+static int _gb_sdio_recv(struct gb_sdio_host *host, struct mmc_data *data,
+ size_t len, u16 nblocks, off_t skip)
+{
+ struct gb_sdio_transfer_request *request;
+ struct gb_sdio_transfer_response *response;
+ struct gb_operation *operation;
+ struct scatterlist *sg = data->sg;
+ unsigned int sg_len = data->sg_len;
+ size_t copied;
+ u16 recv_blksz;
+ u16 recv_blocks;
+ int ret;
+
+ WARN_ON(len > host->data_max);
+
+ operation = gb_operation_create(host->connection, GB_SDIO_TYPE_TRANSFER,
+ sizeof(*request),
+ len + sizeof(*response), GFP_KERNEL);
+ if (!operation)
+ return -ENOMEM;
+
+ request = operation->request->payload;
+ request->data_flags = data->flags >> 8;
+ request->data_blocks = cpu_to_le16(nblocks);
+ request->data_blksz = cpu_to_le16(data->blksz);
+
+ ret = gb_operation_request_send_sync(operation);
+ if (ret < 0)
+ goto err_put_operation;
+
+ response = operation->response->payload;
+ recv_blocks = le16_to_cpu(response->data_blocks);
+ recv_blksz = le16_to_cpu(response->data_blksz);
+
+ if (len != recv_blksz * recv_blocks) {
+ dev_err(mmc_dev(host->mmc), "recv: size received: %d != %zu\n",
+ recv_blksz * recv_blocks, len);
+ ret = -EINVAL;
+ goto err_put_operation;
+ }
+
+ copied = sg_pcopy_from_buffer(sg, sg_len, &response->data[0], len,
+ skip);
+ if (copied != len)
+ ret = -EINVAL;
+
+err_put_operation:
+ gb_operation_put(operation);
+
+ return ret;
+}
+
+static int gb_sdio_transfer(struct gb_sdio_host *host, struct mmc_data *data)
+{
+ size_t left, len;
+ off_t skip = 0;
+ int ret = 0;
+ u16 nblocks;
+
+ if (single_op(data->mrq->cmd) && data->blocks > 1) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ left = data->blksz * data->blocks;
+
+ while (left) {
+ /* check is a stop transmission is pending */
+ spin_lock(&host->xfer);
+ if (host->xfer_stop) {
+ host->xfer_stop = false;
+ spin_unlock(&host->xfer);
+ ret = -EINTR;
+ goto out;
+ }
+ spin_unlock(&host->xfer);
+ len = min(left, host->data_max);
+ nblocks = len / data->blksz;
+ len = nblocks * data->blksz;
+
+ if (data->flags & MMC_DATA_READ) {
+ ret = _gb_sdio_recv(host, data, len, nblocks, skip);
+ if (ret < 0)
+ goto out;
+ } else {
+ ret = _gb_sdio_send(host, data, len, nblocks, skip);
+ if (ret < 0)
+ goto out;
+ }
+ data->bytes_xfered += len;
+ left -= len;
+ skip += len;
+ }
+
+out:
+ data->error = ret;
+ return ret;
+}
+
+static int gb_sdio_command(struct gb_sdio_host *host, struct mmc_command *cmd)
+{
+ struct gb_sdio_command_request request = {0};
+ struct gb_sdio_command_response response;
+ struct mmc_data *data = host->mrq->data;
+ unsigned int timeout_ms;
+ u8 cmd_flags;
+ u8 cmd_type;
+ int i;
+ int ret;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ cmd_flags = GB_SDIO_RSP_NONE;
+ break;
+ case MMC_RSP_R1:
+ cmd_flags = GB_SDIO_RSP_R1_R5_R6_R7;
+ break;
+ case MMC_RSP_R1B:
+ cmd_flags = GB_SDIO_RSP_R1B;
+ break;
+ case MMC_RSP_R2:
+ cmd_flags = GB_SDIO_RSP_R2;
+ break;
+ case MMC_RSP_R3:
+ cmd_flags = GB_SDIO_RSP_R3_R4;
+ break;
+ default:
+ dev_err(mmc_dev(host->mmc), "cmd flag invalid 0x%04x\n",
+ mmc_resp_type(cmd));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ switch (mmc_cmd_type(cmd)) {
+ case MMC_CMD_BC:
+ cmd_type = GB_SDIO_CMD_BC;
+ break;
+ case MMC_CMD_BCR:
+ cmd_type = GB_SDIO_CMD_BCR;
+ break;
+ case MMC_CMD_AC:
+ cmd_type = GB_SDIO_CMD_AC;
+ break;
+ case MMC_CMD_ADTC:
+ cmd_type = GB_SDIO_CMD_ADTC;
+ break;
+ default:
+ dev_err(mmc_dev(host->mmc), "cmd type invalid 0x%04x\n",
+ mmc_cmd_type(cmd));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ request.cmd = cmd->opcode;
+ request.cmd_flags = cmd_flags;
+ request.cmd_type = cmd_type;
+ request.cmd_arg = cpu_to_le32(cmd->arg);
+ /* some controllers need to know at command time data details */
+ if (data) {
+ request.data_blocks = cpu_to_le16(data->blocks);
+ request.data_blksz = cpu_to_le16(data->blksz);
+ }
+
+ timeout_ms = cmd->busy_timeout ? cmd->busy_timeout :
+ GB_OPERATION_TIMEOUT_DEFAULT;
+
+ ret = gb_operation_sync_timeout(host->connection, GB_SDIO_TYPE_COMMAND,
+ &request, sizeof(request), &response,
+ sizeof(response), timeout_ms);
+ if (ret < 0)
+ goto out;
+
+ /* no response expected */
+ if (cmd_flags == GB_SDIO_RSP_NONE)
+ goto out;
+
+ /* long response expected */
+ if (cmd_flags & GB_SDIO_RSP_R2)
+ for (i = 0; i < 4; i++)
+ cmd->resp[i] = le32_to_cpu(response.resp[i]);
+ else
+ cmd->resp[0] = le32_to_cpu(response.resp[0]);
+
+out:
+ cmd->error = ret;
+ return ret;
+}
+
+static void gb_sdio_mrq_work(struct work_struct *work)
+{
+ struct gb_sdio_host *host;
+ struct mmc_request *mrq;
+ int ret;
+
+ host = container_of(work, struct gb_sdio_host, mrqwork);
+
+ ret = gbphy_runtime_get_sync(host->gbphy_dev);
+ if (ret)
+ return;
+
+ mutex_lock(&host->lock);
+ mrq = host->mrq;
+ if (!mrq) {
+ mutex_unlock(&host->lock);
+ gbphy_runtime_put_autosuspend(host->gbphy_dev);
+ dev_err(mmc_dev(host->mmc), "mmc request is NULL");
+ return;
+ }
+
+ if (host->removed) {
+ mrq->cmd->error = -ESHUTDOWN;
+ goto done;
+ }
+
+ if (mrq->sbc) {
+ ret = gb_sdio_command(host, mrq->sbc);
+ if (ret < 0)
+ goto done;
+ }
+
+ ret = gb_sdio_command(host, mrq->cmd);
+ if (ret < 0)
+ goto done;
+
+ if (mrq->data) {
+ ret = gb_sdio_transfer(host, mrq->data);
+ if (ret < 0)
+ goto done;
+ }
+
+ if (mrq->stop) {
+ ret = gb_sdio_command(host, mrq->stop);
+ if (ret < 0)
+ goto done;
+ }
+
+done:
+ host->mrq = NULL;
+ mutex_unlock(&host->lock);
+ mmc_request_done(host->mmc, mrq);
+ gbphy_runtime_put_autosuspend(host->gbphy_dev);
+}
+
+static void gb_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct gb_sdio_host *host = mmc_priv(mmc);
+ struct mmc_command *cmd = mrq->cmd;
+
+ /* Check if it is a cancel to ongoing transfer */
+ if (cmd->opcode == MMC_STOP_TRANSMISSION) {
+ spin_lock(&host->xfer);
+ host->xfer_stop = true;
+ spin_unlock(&host->xfer);
+ }
+
+ mutex_lock(&host->lock);
+
+ WARN_ON(host->mrq);
+ host->mrq = mrq;
+
+ if (host->removed) {
+ mrq->cmd->error = -ESHUTDOWN;
+ goto out;
+ }
+ if (!host->card_present) {
+ mrq->cmd->error = -ENOMEDIUM;
+ goto out;
+ }
+
+ queue_work(host->mrq_workqueue, &host->mrqwork);
+
+ mutex_unlock(&host->lock);
+ return;
+
+out:
+ host->mrq = NULL;
+ mutex_unlock(&host->lock);
+ mmc_request_done(mmc, mrq);
+}
+
+static void gb_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct gb_sdio_host *host = mmc_priv(mmc);
+ struct gb_sdio_set_ios_request request;
+ int ret;
+ u8 power_mode;
+ u8 bus_width;
+ u8 timing;
+ u8 signal_voltage;
+ u8 drv_type;
+ u32 vdd = 0;
+
+ mutex_lock(&host->lock);
+ request.clock = cpu_to_le32(ios->clock);
+
+ if (ios->vdd)
+ vdd = 1 << (ios->vdd - GB_SDIO_VDD_SHIFT);
+ request.vdd = cpu_to_le32(vdd);
+
+ request.bus_mode = ios->bus_mode == MMC_BUSMODE_OPENDRAIN ?
+ GB_SDIO_BUSMODE_OPENDRAIN :
+ GB_SDIO_BUSMODE_PUSHPULL;
+
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ default:
+ power_mode = GB_SDIO_POWER_OFF;
+ break;
+ case MMC_POWER_UP:
+ power_mode = GB_SDIO_POWER_UP;
+ break;
+ case MMC_POWER_ON:
+ power_mode = GB_SDIO_POWER_ON;
+ break;
+ case MMC_POWER_UNDEFINED:
+ power_mode = GB_SDIO_POWER_UNDEFINED;
+ break;
+ }
+ request.power_mode = power_mode;
+
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_1:
+ bus_width = GB_SDIO_BUS_WIDTH_1;
+ break;
+ case MMC_BUS_WIDTH_4:
+ default:
+ bus_width = GB_SDIO_BUS_WIDTH_4;
+ break;
+ case MMC_BUS_WIDTH_8:
+ bus_width = GB_SDIO_BUS_WIDTH_8;
+ break;
+ }
+ request.bus_width = bus_width;
+
+ switch (ios->timing) {
+ case MMC_TIMING_LEGACY:
+ default:
+ timing = GB_SDIO_TIMING_LEGACY;
+ break;
+ case MMC_TIMING_MMC_HS:
+ timing = GB_SDIO_TIMING_MMC_HS;
+ break;
+ case MMC_TIMING_SD_HS:
+ timing = GB_SDIO_TIMING_SD_HS;
+ break;
+ case MMC_TIMING_UHS_SDR12:
+ timing = GB_SDIO_TIMING_UHS_SDR12;
+ break;
+ case MMC_TIMING_UHS_SDR25:
+ timing = GB_SDIO_TIMING_UHS_SDR25;
+ break;
+ case MMC_TIMING_UHS_SDR50:
+ timing = GB_SDIO_TIMING_UHS_SDR50;
+ break;
+ case MMC_TIMING_UHS_SDR104:
+ timing = GB_SDIO_TIMING_UHS_SDR104;
+ break;
+ case MMC_TIMING_UHS_DDR50:
+ timing = GB_SDIO_TIMING_UHS_DDR50;
+ break;
+ case MMC_TIMING_MMC_DDR52:
+ timing = GB_SDIO_TIMING_MMC_DDR52;
+ break;
+ case MMC_TIMING_MMC_HS200:
+ timing = GB_SDIO_TIMING_MMC_HS200;
+ break;
+ case MMC_TIMING_MMC_HS400:
+ timing = GB_SDIO_TIMING_MMC_HS400;
+ break;
+ }
+ request.timing = timing;
+
+ switch (ios->signal_voltage) {
+ case MMC_SIGNAL_VOLTAGE_330:
+ signal_voltage = GB_SDIO_SIGNAL_VOLTAGE_330;
+ break;
+ case MMC_SIGNAL_VOLTAGE_180:
+ default:
+ signal_voltage = GB_SDIO_SIGNAL_VOLTAGE_180;
+ break;
+ case MMC_SIGNAL_VOLTAGE_120:
+ signal_voltage = GB_SDIO_SIGNAL_VOLTAGE_120;
+ break;
+ }
+ request.signal_voltage = signal_voltage;
+
+ switch (ios->drv_type) {
+ case MMC_SET_DRIVER_TYPE_A:
+ drv_type = GB_SDIO_SET_DRIVER_TYPE_A;
+ break;
+ case MMC_SET_DRIVER_TYPE_C:
+ drv_type = GB_SDIO_SET_DRIVER_TYPE_C;
+ break;
+ case MMC_SET_DRIVER_TYPE_D:
+ drv_type = GB_SDIO_SET_DRIVER_TYPE_D;
+ break;
+ case MMC_SET_DRIVER_TYPE_B:
+ default:
+ drv_type = GB_SDIO_SET_DRIVER_TYPE_B;
+ break;
+ }
+ request.drv_type = drv_type;
+
+ ret = gb_sdio_set_ios(host, &request);
+ if (ret < 0)
+ goto out;
+
+ memcpy(&mmc->ios, ios, sizeof(mmc->ios));
+
+out:
+ mutex_unlock(&host->lock);
+}
+
+static int gb_mmc_get_ro(struct mmc_host *mmc)
+{
+ struct gb_sdio_host *host = mmc_priv(mmc);
+
+ mutex_lock(&host->lock);
+ if (host->removed) {
+ mutex_unlock(&host->lock);
+ return -ESHUTDOWN;
+ }
+ mutex_unlock(&host->lock);
+
+ return host->read_only;
+}
+
+static int gb_mmc_get_cd(struct mmc_host *mmc)
+{
+ struct gb_sdio_host *host = mmc_priv(mmc);
+
+ mutex_lock(&host->lock);
+ if (host->removed) {
+ mutex_unlock(&host->lock);
+ return -ESHUTDOWN;
+ }
+ mutex_unlock(&host->lock);
+
+ return host->card_present;
+}
+
+static int gb_mmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ return 0;
+}
+
+static const struct mmc_host_ops gb_sdio_ops = {
+ .request = gb_mmc_request,
+ .set_ios = gb_mmc_set_ios,
+ .get_ro = gb_mmc_get_ro,
+ .get_cd = gb_mmc_get_cd,
+ .start_signal_voltage_switch = gb_mmc_switch_voltage,
+};
+
+static int gb_sdio_probe(struct gbphy_device *gbphy_dev,
+ const struct gbphy_device_id *id)
+{
+ struct gb_connection *connection;
+ struct mmc_host *mmc;
+ struct gb_sdio_host *host;
+ int ret = 0;
+
+ mmc = mmc_alloc_host(sizeof(*host), &gbphy_dev->dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ connection = gb_connection_create(gbphy_dev->bundle,
+ le16_to_cpu(gbphy_dev->cport_desc->id),
+ gb_sdio_request_handler);
+ if (IS_ERR(connection)) {
+ ret = PTR_ERR(connection);
+ goto exit_mmc_free;
+ }
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->removed = true;
+
+ host->connection = connection;
+ gb_connection_set_data(connection, host);
+ host->gbphy_dev = gbphy_dev;
+ gb_gbphy_set_data(gbphy_dev, host);
+
+ ret = gb_connection_enable_tx(connection);
+ if (ret)
+ goto exit_connection_destroy;
+
+ ret = gb_sdio_get_caps(host);
+ if (ret < 0)
+ goto exit_connection_disable;
+
+ mmc->ops = &gb_sdio_ops;
+
+ mmc->max_segs = host->mmc->max_blk_count;
+
+ /* for now we make a map 1:1 between max request and segment size */
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_seg_size = mmc->max_req_size;
+
+ mutex_init(&host->lock);
+ spin_lock_init(&host->xfer);
+ host->mrq_workqueue = alloc_workqueue("mmc-%s", 0, 1,
+ dev_name(&gbphy_dev->dev));
+ if (!host->mrq_workqueue) {
+ ret = -ENOMEM;
+ goto exit_connection_disable;
+ }
+ INIT_WORK(&host->mrqwork, gb_sdio_mrq_work);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto exit_wq_destroy;
+
+ ret = mmc_add_host(mmc);
+ if (ret < 0)
+ goto exit_wq_destroy;
+ host->removed = false;
+ ret = _gb_sdio_process_events(host, host->queued_events);
+ host->queued_events = 0;
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+
+ return ret;
+
+exit_wq_destroy:
+ destroy_workqueue(host->mrq_workqueue);
+exit_connection_disable:
+ gb_connection_disable(connection);
+exit_connection_destroy:
+ gb_connection_destroy(connection);
+exit_mmc_free:
+ mmc_free_host(mmc);
+
+ return ret;
+}
+
+static void gb_sdio_remove(struct gbphy_device *gbphy_dev)
+{
+ struct gb_sdio_host *host = gb_gbphy_get_data(gbphy_dev);
+ struct gb_connection *connection = host->connection;
+ struct mmc_host *mmc;
+ int ret;
+
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ gbphy_runtime_get_noresume(gbphy_dev);
+
+ mutex_lock(&host->lock);
+ host->removed = true;
+ mmc = host->mmc;
+ gb_connection_set_data(connection, NULL);
+ mutex_unlock(&host->lock);
+
+ destroy_workqueue(host->mrq_workqueue);
+ gb_connection_disable_rx(connection);
+ mmc_remove_host(mmc);
+ gb_connection_disable(connection);
+ gb_connection_destroy(connection);
+ mmc_free_host(mmc);
+}
+
+static const struct gbphy_device_id gb_sdio_id_table[] = {
+ { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_SDIO) },
+ { },
+};
+MODULE_DEVICE_TABLE(gbphy, gb_sdio_id_table);
+
+static struct gbphy_driver sdio_driver = {
+ .name = "sdio",
+ .probe = gb_sdio_probe,
+ .remove = gb_sdio_remove,
+ .id_table = gb_sdio_id_table,
+};
+
+module_gbphy_driver(sdio_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/spi.c b/drivers/staging/greybus/spi.c
new file mode 100644
index 0000000000..68e8d272db
--- /dev/null
+++ b/drivers/staging/greybus/spi.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SPI bridge PHY driver.
+ *
+ * Copyright 2014-2016 Google Inc.
+ * Copyright 2014-2016 Linaro Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/greybus.h>
+
+#include "gbphy.h"
+#include "spilib.h"
+
+static struct spilib_ops *spilib_ops;
+
+static int gb_spi_probe(struct gbphy_device *gbphy_dev,
+ const struct gbphy_device_id *id)
+{
+ struct gb_connection *connection;
+ int ret;
+
+ connection = gb_connection_create(gbphy_dev->bundle,
+ le16_to_cpu(gbphy_dev->cport_desc->id),
+ NULL);
+ if (IS_ERR(connection))
+ return PTR_ERR(connection);
+
+ ret = gb_connection_enable(connection);
+ if (ret)
+ goto exit_connection_destroy;
+
+ ret = gb_spilib_master_init(connection, &gbphy_dev->dev, spilib_ops);
+ if (ret)
+ goto exit_connection_disable;
+
+ gb_gbphy_set_data(gbphy_dev, connection);
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+ return 0;
+
+exit_connection_disable:
+ gb_connection_disable(connection);
+exit_connection_destroy:
+ gb_connection_destroy(connection);
+
+ return ret;
+}
+
+static void gb_spi_remove(struct gbphy_device *gbphy_dev)
+{
+ struct gb_connection *connection = gb_gbphy_get_data(gbphy_dev);
+ int ret;
+
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ gbphy_runtime_get_noresume(gbphy_dev);
+
+ gb_spilib_master_exit(connection);
+ gb_connection_disable(connection);
+ gb_connection_destroy(connection);
+}
+
+static const struct gbphy_device_id gb_spi_id_table[] = {
+ { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_SPI) },
+ { },
+};
+MODULE_DEVICE_TABLE(gbphy, gb_spi_id_table);
+
+static struct gbphy_driver spi_driver = {
+ .name = "spi",
+ .probe = gb_spi_probe,
+ .remove = gb_spi_remove,
+ .id_table = gb_spi_id_table,
+};
+
+module_gbphy_driver(spi_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/spilib.c b/drivers/staging/greybus/spilib.c
new file mode 100644
index 0000000000..efb3bec58e
--- /dev/null
+++ b/drivers/staging/greybus/spilib.c
@@ -0,0 +1,570 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus SPI library
+ *
+ * Copyright 2014-2016 Google Inc.
+ * Copyright 2014-2016 Linaro Ltd.
+ */
+
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/greybus.h>
+#include <linux/spi/spi.h>
+
+#include "spilib.h"
+
+struct gb_spilib {
+ struct gb_connection *connection;
+ struct device *parent;
+ struct spi_transfer *first_xfer;
+ struct spi_transfer *last_xfer;
+ struct spilib_ops *ops;
+ u32 rx_xfer_offset;
+ u32 tx_xfer_offset;
+ u32 last_xfer_size;
+ unsigned int op_timeout;
+ u16 mode;
+ u16 flags;
+ u32 bits_per_word_mask;
+ u8 num_chipselect;
+ u32 min_speed_hz;
+ u32 max_speed_hz;
+};
+
+#define GB_SPI_STATE_MSG_DONE ((void *)0)
+#define GB_SPI_STATE_MSG_IDLE ((void *)1)
+#define GB_SPI_STATE_MSG_RUNNING ((void *)2)
+#define GB_SPI_STATE_OP_READY ((void *)3)
+#define GB_SPI_STATE_OP_DONE ((void *)4)
+#define GB_SPI_STATE_MSG_ERROR ((void *)-1)
+
+#define XFER_TIMEOUT_TOLERANCE 200
+
+static struct spi_master *get_master_from_spi(struct gb_spilib *spi)
+{
+ return gb_connection_get_data(spi->connection);
+}
+
+static int tx_header_fit_operation(u32 tx_size, u32 count, size_t data_max)
+{
+ size_t headers_size;
+
+ data_max -= sizeof(struct gb_spi_transfer_request);
+ headers_size = (count + 1) * sizeof(struct gb_spi_transfer);
+
+ return tx_size + headers_size > data_max ? 0 : 1;
+}
+
+static size_t calc_rx_xfer_size(u32 rx_size, u32 *tx_xfer_size, u32 len,
+ size_t data_max)
+{
+ size_t rx_xfer_size;
+
+ data_max -= sizeof(struct gb_spi_transfer_response);
+
+ if (rx_size + len > data_max)
+ rx_xfer_size = data_max - rx_size;
+ else
+ rx_xfer_size = len;
+
+ /* if this is a write_read, for symmetry read the same as write */
+ if (*tx_xfer_size && rx_xfer_size > *tx_xfer_size)
+ rx_xfer_size = *tx_xfer_size;
+ if (*tx_xfer_size && rx_xfer_size < *tx_xfer_size)
+ *tx_xfer_size = rx_xfer_size;
+
+ return rx_xfer_size;
+}
+
+static size_t calc_tx_xfer_size(u32 tx_size, u32 count, size_t len,
+ size_t data_max)
+{
+ size_t headers_size;
+
+ data_max -= sizeof(struct gb_spi_transfer_request);
+ headers_size = (count + 1) * sizeof(struct gb_spi_transfer);
+
+ if (tx_size + headers_size + len > data_max)
+ return data_max - (tx_size + sizeof(struct gb_spi_transfer));
+
+ return len;
+}
+
+static void clean_xfer_state(struct gb_spilib *spi)
+{
+ spi->first_xfer = NULL;
+ spi->last_xfer = NULL;
+ spi->rx_xfer_offset = 0;
+ spi->tx_xfer_offset = 0;
+ spi->last_xfer_size = 0;
+ spi->op_timeout = 0;
+}
+
+static bool is_last_xfer_done(struct gb_spilib *spi)
+{
+ struct spi_transfer *last_xfer = spi->last_xfer;
+
+ if ((spi->tx_xfer_offset + spi->last_xfer_size == last_xfer->len) ||
+ (spi->rx_xfer_offset + spi->last_xfer_size == last_xfer->len))
+ return true;
+
+ return false;
+}
+
+static int setup_next_xfer(struct gb_spilib *spi, struct spi_message *msg)
+{
+ struct spi_transfer *last_xfer = spi->last_xfer;
+
+ if (msg->state != GB_SPI_STATE_OP_DONE)
+ return 0;
+
+ /*
+ * if we transferred all content of the last transfer, reset values and
+ * check if this was the last transfer in the message
+ */
+ if (is_last_xfer_done(spi)) {
+ spi->tx_xfer_offset = 0;
+ spi->rx_xfer_offset = 0;
+ spi->op_timeout = 0;
+ if (last_xfer == list_last_entry(&msg->transfers,
+ struct spi_transfer,
+ transfer_list))
+ msg->state = GB_SPI_STATE_MSG_DONE;
+ else
+ spi->first_xfer = list_next_entry(last_xfer,
+ transfer_list);
+ return 0;
+ }
+
+ spi->first_xfer = last_xfer;
+ if (last_xfer->tx_buf)
+ spi->tx_xfer_offset += spi->last_xfer_size;
+
+ if (last_xfer->rx_buf)
+ spi->rx_xfer_offset += spi->last_xfer_size;
+
+ return 0;
+}
+
+static struct spi_transfer *get_next_xfer(struct spi_transfer *xfer,
+ struct spi_message *msg)
+{
+ if (xfer == list_last_entry(&msg->transfers, struct spi_transfer,
+ transfer_list))
+ return NULL;
+
+ return list_next_entry(xfer, transfer_list);
+}
+
+/* Routines to transfer data */
+static struct gb_operation *gb_spi_operation_create(struct gb_spilib *spi,
+ struct gb_connection *connection, struct spi_message *msg)
+{
+ struct gb_spi_transfer_request *request;
+ struct spi_device *dev = msg->spi;
+ struct spi_transfer *xfer;
+ struct gb_spi_transfer *gb_xfer;
+ struct gb_operation *operation;
+ u32 tx_size = 0, rx_size = 0, count = 0, xfer_len = 0, request_size;
+ u32 tx_xfer_size = 0, rx_xfer_size = 0, len;
+ u32 total_len = 0;
+ unsigned int xfer_timeout;
+ size_t data_max;
+ void *tx_data;
+
+ data_max = gb_operation_get_payload_size_max(connection);
+ xfer = spi->first_xfer;
+
+ /* Find number of transfers queued and tx/rx length in the message */
+
+ while (msg->state != GB_SPI_STATE_OP_READY) {
+ msg->state = GB_SPI_STATE_MSG_RUNNING;
+ spi->last_xfer = xfer;
+
+ if (!xfer->tx_buf && !xfer->rx_buf) {
+ dev_err(spi->parent,
+ "bufferless transfer, length %u\n", xfer->len);
+ msg->state = GB_SPI_STATE_MSG_ERROR;
+ return NULL;
+ }
+
+ tx_xfer_size = 0;
+ rx_xfer_size = 0;
+
+ if (xfer->tx_buf) {
+ len = xfer->len - spi->tx_xfer_offset;
+ if (!tx_header_fit_operation(tx_size, count, data_max))
+ break;
+ tx_xfer_size = calc_tx_xfer_size(tx_size, count,
+ len, data_max);
+ spi->last_xfer_size = tx_xfer_size;
+ }
+
+ if (xfer->rx_buf) {
+ len = xfer->len - spi->rx_xfer_offset;
+ rx_xfer_size = calc_rx_xfer_size(rx_size, &tx_xfer_size,
+ len, data_max);
+ spi->last_xfer_size = rx_xfer_size;
+ }
+
+ tx_size += tx_xfer_size;
+ rx_size += rx_xfer_size;
+
+ total_len += spi->last_xfer_size;
+ count++;
+
+ xfer = get_next_xfer(xfer, msg);
+ if (!xfer || total_len >= data_max)
+ msg->state = GB_SPI_STATE_OP_READY;
+ }
+
+ /*
+ * In addition to space for all message descriptors we need
+ * to have enough to hold all tx data.
+ */
+ request_size = sizeof(*request);
+ request_size += count * sizeof(*gb_xfer);
+ request_size += tx_size;
+
+ /* Response consists only of incoming data */
+ operation = gb_operation_create(connection, GB_SPI_TYPE_TRANSFER,
+ request_size, rx_size, GFP_KERNEL);
+ if (!operation)
+ return NULL;
+
+ request = operation->request->payload;
+ request->count = cpu_to_le16(count);
+ request->mode = dev->mode;
+ request->chip_select = spi_get_chipselect(dev, 0);
+
+ gb_xfer = &request->transfers[0];
+ tx_data = gb_xfer + count; /* place tx data after last gb_xfer */
+
+ /* Fill in the transfers array */
+ xfer = spi->first_xfer;
+ while (msg->state != GB_SPI_STATE_OP_DONE) {
+ int xfer_delay;
+
+ if (xfer == spi->last_xfer)
+ xfer_len = spi->last_xfer_size;
+ else
+ xfer_len = xfer->len;
+
+ /* make sure we do not timeout in a slow transfer */
+ xfer_timeout = xfer_len * 8 * MSEC_PER_SEC / xfer->speed_hz;
+ xfer_timeout += GB_OPERATION_TIMEOUT_DEFAULT;
+
+ if (xfer_timeout > spi->op_timeout)
+ spi->op_timeout = xfer_timeout;
+
+ gb_xfer->speed_hz = cpu_to_le32(xfer->speed_hz);
+ gb_xfer->len = cpu_to_le32(xfer_len);
+ xfer_delay = spi_delay_to_ns(&xfer->delay, xfer) / 1000;
+ xfer_delay = clamp_t(u16, xfer_delay, 0, U16_MAX);
+ gb_xfer->delay_usecs = cpu_to_le16(xfer_delay);
+ gb_xfer->cs_change = xfer->cs_change;
+ gb_xfer->bits_per_word = xfer->bits_per_word;
+
+ /* Copy tx data */
+ if (xfer->tx_buf) {
+ gb_xfer->xfer_flags |= GB_SPI_XFER_WRITE;
+ memcpy(tx_data, xfer->tx_buf + spi->tx_xfer_offset,
+ xfer_len);
+ tx_data += xfer_len;
+ }
+
+ if (xfer->rx_buf)
+ gb_xfer->xfer_flags |= GB_SPI_XFER_READ;
+
+ if (xfer == spi->last_xfer) {
+ if (!is_last_xfer_done(spi))
+ gb_xfer->xfer_flags |= GB_SPI_XFER_INPROGRESS;
+ msg->state = GB_SPI_STATE_OP_DONE;
+ continue;
+ }
+
+ gb_xfer++;
+ xfer = get_next_xfer(xfer, msg);
+ }
+
+ msg->actual_length += total_len;
+
+ return operation;
+}
+
+static void gb_spi_decode_response(struct gb_spilib *spi,
+ struct spi_message *msg,
+ struct gb_spi_transfer_response *response)
+{
+ struct spi_transfer *xfer = spi->first_xfer;
+ void *rx_data = response->data;
+ u32 xfer_len;
+
+ while (xfer) {
+ /* Copy rx data */
+ if (xfer->rx_buf) {
+ if (xfer == spi->first_xfer)
+ xfer_len = xfer->len - spi->rx_xfer_offset;
+ else if (xfer == spi->last_xfer)
+ xfer_len = spi->last_xfer_size;
+ else
+ xfer_len = xfer->len;
+
+ memcpy(xfer->rx_buf + spi->rx_xfer_offset, rx_data,
+ xfer_len);
+ rx_data += xfer_len;
+ }
+
+ if (xfer == spi->last_xfer)
+ break;
+
+ xfer = list_next_entry(xfer, transfer_list);
+ }
+}
+
+static int gb_spi_transfer_one_message(struct spi_master *master,
+ struct spi_message *msg)
+{
+ struct gb_spilib *spi = spi_master_get_devdata(master);
+ struct gb_connection *connection = spi->connection;
+ struct gb_spi_transfer_response *response;
+ struct gb_operation *operation;
+ int ret = 0;
+
+ spi->first_xfer = list_first_entry_or_null(&msg->transfers,
+ struct spi_transfer,
+ transfer_list);
+ if (!spi->first_xfer) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ msg->state = GB_SPI_STATE_MSG_IDLE;
+
+ while (msg->state != GB_SPI_STATE_MSG_DONE &&
+ msg->state != GB_SPI_STATE_MSG_ERROR) {
+ operation = gb_spi_operation_create(spi, connection, msg);
+ if (!operation) {
+ msg->state = GB_SPI_STATE_MSG_ERROR;
+ ret = -EINVAL;
+ continue;
+ }
+
+ ret = gb_operation_request_send_sync_timeout(operation,
+ spi->op_timeout);
+ if (!ret) {
+ response = operation->response->payload;
+ if (response)
+ gb_spi_decode_response(spi, msg, response);
+ } else {
+ dev_err(spi->parent,
+ "transfer operation failed: %d\n", ret);
+ msg->state = GB_SPI_STATE_MSG_ERROR;
+ }
+
+ gb_operation_put(operation);
+ setup_next_xfer(spi, msg);
+ }
+
+out:
+ msg->status = ret;
+ clean_xfer_state(spi);
+ spi_finalize_current_message(master);
+
+ return ret;
+}
+
+static int gb_spi_prepare_transfer_hardware(struct spi_master *master)
+{
+ struct gb_spilib *spi = spi_master_get_devdata(master);
+
+ return spi->ops->prepare_transfer_hardware(spi->parent);
+}
+
+static int gb_spi_unprepare_transfer_hardware(struct spi_master *master)
+{
+ struct gb_spilib *spi = spi_master_get_devdata(master);
+
+ spi->ops->unprepare_transfer_hardware(spi->parent);
+
+ return 0;
+}
+
+static int gb_spi_setup(struct spi_device *spi)
+{
+ /* Nothing to do for now */
+ return 0;
+}
+
+static void gb_spi_cleanup(struct spi_device *spi)
+{
+ /* Nothing to do for now */
+}
+
+/* Routines to get controller information */
+
+/*
+ * Map Greybus spi mode bits/flags/bpw into Linux ones.
+ * All bits are same for now and so these macro's return same values.
+ */
+#define gb_spi_mode_map(mode) mode
+#define gb_spi_flags_map(flags) flags
+
+static int gb_spi_get_master_config(struct gb_spilib *spi)
+{
+ struct gb_spi_master_config_response response;
+ u16 mode, flags;
+ int ret;
+
+ ret = gb_operation_sync(spi->connection, GB_SPI_TYPE_MASTER_CONFIG,
+ NULL, 0, &response, sizeof(response));
+ if (ret < 0)
+ return ret;
+
+ mode = le16_to_cpu(response.mode);
+ spi->mode = gb_spi_mode_map(mode);
+
+ flags = le16_to_cpu(response.flags);
+ spi->flags = gb_spi_flags_map(flags);
+
+ spi->bits_per_word_mask = le32_to_cpu(response.bits_per_word_mask);
+ spi->num_chipselect = response.num_chipselect;
+
+ spi->min_speed_hz = le32_to_cpu(response.min_speed_hz);
+ spi->max_speed_hz = le32_to_cpu(response.max_speed_hz);
+
+ return 0;
+}
+
+static int gb_spi_setup_device(struct gb_spilib *spi, u8 cs)
+{
+ struct spi_master *master = get_master_from_spi(spi);
+ struct gb_spi_device_config_request request;
+ struct gb_spi_device_config_response response;
+ struct spi_board_info spi_board = { {0} };
+ struct spi_device *spidev;
+ int ret;
+ u8 dev_type;
+
+ request.chip_select = cs;
+
+ ret = gb_operation_sync(spi->connection, GB_SPI_TYPE_DEVICE_CONFIG,
+ &request, sizeof(request),
+ &response, sizeof(response));
+ if (ret < 0)
+ return ret;
+
+ dev_type = response.device_type;
+
+ if (dev_type == GB_SPI_SPI_DEV)
+ strscpy(spi_board.modalias, "spidev",
+ sizeof(spi_board.modalias));
+ else if (dev_type == GB_SPI_SPI_NOR)
+ strscpy(spi_board.modalias, "spi-nor",
+ sizeof(spi_board.modalias));
+ else if (dev_type == GB_SPI_SPI_MODALIAS)
+ memcpy(spi_board.modalias, response.name,
+ sizeof(spi_board.modalias));
+ else
+ return -EINVAL;
+
+ spi_board.mode = le16_to_cpu(response.mode);
+ spi_board.bus_num = master->bus_num;
+ spi_board.chip_select = cs;
+ spi_board.max_speed_hz = le32_to_cpu(response.max_speed_hz);
+
+ spidev = spi_new_device(master, &spi_board);
+ if (!spidev)
+ return -EINVAL;
+
+ return 0;
+}
+
+int gb_spilib_master_init(struct gb_connection *connection, struct device *dev,
+ struct spilib_ops *ops)
+{
+ struct gb_spilib *spi;
+ struct spi_master *master;
+ int ret;
+ u8 i;
+
+ /* Allocate master with space for data */
+ master = spi_alloc_master(dev, sizeof(*spi));
+ if (!master) {
+ dev_err(dev, "cannot alloc SPI master\n");
+ return -ENOMEM;
+ }
+
+ spi = spi_master_get_devdata(master);
+ spi->connection = connection;
+ gb_connection_set_data(connection, master);
+ spi->parent = dev;
+ spi->ops = ops;
+
+ /* get master configuration */
+ ret = gb_spi_get_master_config(spi);
+ if (ret)
+ goto exit_spi_put;
+
+ master->bus_num = -1; /* Allow spi-core to allocate it dynamically */
+ master->num_chipselect = spi->num_chipselect;
+ master->mode_bits = spi->mode;
+ master->flags = spi->flags;
+ master->bits_per_word_mask = spi->bits_per_word_mask;
+
+ /* Attach methods */
+ master->cleanup = gb_spi_cleanup;
+ master->setup = gb_spi_setup;
+ master->transfer_one_message = gb_spi_transfer_one_message;
+
+ if (ops && ops->prepare_transfer_hardware) {
+ master->prepare_transfer_hardware =
+ gb_spi_prepare_transfer_hardware;
+ }
+
+ if (ops && ops->unprepare_transfer_hardware) {
+ master->unprepare_transfer_hardware =
+ gb_spi_unprepare_transfer_hardware;
+ }
+
+ master->auto_runtime_pm = true;
+
+ ret = spi_register_master(master);
+ if (ret < 0)
+ goto exit_spi_put;
+
+ /* now, fetch the devices configuration */
+ for (i = 0; i < spi->num_chipselect; i++) {
+ ret = gb_spi_setup_device(spi, i);
+ if (ret < 0) {
+ dev_err(dev, "failed to allocate spi device %d: %d\n",
+ i, ret);
+ goto exit_spi_unregister;
+ }
+ }
+
+ return 0;
+
+exit_spi_put:
+ spi_master_put(master);
+
+ return ret;
+
+exit_spi_unregister:
+ spi_unregister_master(master);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(gb_spilib_master_init);
+
+void gb_spilib_master_exit(struct gb_connection *connection)
+{
+ struct spi_master *master = gb_connection_get_data(connection);
+
+ spi_unregister_master(master);
+}
+EXPORT_SYMBOL_GPL(gb_spilib_master_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/spilib.h b/drivers/staging/greybus/spilib.h
new file mode 100644
index 0000000000..9d416839e3
--- /dev/null
+++ b/drivers/staging/greybus/spilib.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Greybus SPI library header
+ *
+ * copyright 2016 google inc.
+ * copyright 2016 linaro ltd.
+ *
+ * released under the gplv2 only.
+ */
+
+#ifndef __SPILIB_H
+#define __SPILIB_H
+
+struct device;
+struct gb_connection;
+
+struct spilib_ops {
+ int (*prepare_transfer_hardware)(struct device *dev);
+ void (*unprepare_transfer_hardware)(struct device *dev);
+};
+
+int gb_spilib_master_init(struct gb_connection *connection,
+ struct device *dev, struct spilib_ops *ops);
+void gb_spilib_master_exit(struct gb_connection *connection);
+
+#endif /* __SPILIB_H */
diff --git a/drivers/staging/greybus/uart.c b/drivers/staging/greybus/uart.c
new file mode 100644
index 0000000000..999ce613dc
--- /dev/null
+++ b/drivers/staging/greybus/uart.c
@@ -0,0 +1,1027 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UART driver for the Greybus "generic" UART module.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ *
+ * Heavily based on drivers/usb/class/cdc-acm.c and
+ * drivers/usb/serial/usb-serial.c.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/sched/signal.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/mutex.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/idr.h>
+#include <linux/fs.h>
+#include <linux/kdev_t.h>
+#include <linux/kfifo.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+#include <linux/greybus.h>
+
+#include "gbphy.h"
+
+#define GB_NUM_MINORS 16 /* 16 is more than enough */
+#define GB_NAME "ttyGB"
+
+#define GB_UART_WRITE_FIFO_SIZE PAGE_SIZE
+#define GB_UART_WRITE_ROOM_MARGIN 1 /* leave some space in fifo */
+#define GB_UART_FIRMWARE_CREDITS 4096
+#define GB_UART_CREDIT_WAIT_TIMEOUT_MSEC 10000
+
+struct gb_tty {
+ struct gbphy_device *gbphy_dev;
+ struct tty_port port;
+ void *buffer;
+ size_t buffer_payload_max;
+ struct gb_connection *connection;
+ u16 cport_id;
+ unsigned int minor;
+ unsigned char clocal;
+ bool disconnected;
+ spinlock_t read_lock;
+ spinlock_t write_lock;
+ struct async_icount iocount;
+ struct async_icount oldcount;
+ wait_queue_head_t wioctl;
+ struct mutex mutex;
+ u8 ctrlin; /* input control lines */
+ u8 ctrlout; /* output control lines */
+ struct gb_uart_set_line_coding_request line_coding;
+ struct work_struct tx_work;
+ struct kfifo write_fifo;
+ bool close_pending;
+ unsigned int credits;
+ struct completion credits_complete;
+};
+
+static struct tty_driver *gb_tty_driver;
+static DEFINE_IDR(tty_minors);
+static DEFINE_MUTEX(table_lock);
+
+static int gb_uart_receive_data_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_tty *gb_tty = gb_connection_get_data(connection);
+ struct tty_port *port = &gb_tty->port;
+ struct gb_message *request = op->request;
+ struct gb_uart_recv_data_request *receive_data;
+ u16 recv_data_size;
+ int count;
+ unsigned long tty_flags = TTY_NORMAL;
+
+ if (request->payload_size < sizeof(*receive_data)) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "short receive-data request received (%zu < %zu)\n",
+ request->payload_size, sizeof(*receive_data));
+ return -EINVAL;
+ }
+
+ receive_data = op->request->payload;
+ recv_data_size = le16_to_cpu(receive_data->size);
+
+ if (recv_data_size != request->payload_size - sizeof(*receive_data)) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "malformed receive-data request received (%u != %zu)\n",
+ recv_data_size,
+ request->payload_size - sizeof(*receive_data));
+ return -EINVAL;
+ }
+
+ if (!recv_data_size)
+ return -EINVAL;
+
+ if (receive_data->flags) {
+ if (receive_data->flags & GB_UART_RECV_FLAG_BREAK)
+ tty_flags = TTY_BREAK;
+ else if (receive_data->flags & GB_UART_RECV_FLAG_PARITY)
+ tty_flags = TTY_PARITY;
+ else if (receive_data->flags & GB_UART_RECV_FLAG_FRAMING)
+ tty_flags = TTY_FRAME;
+
+ /* overrun is special, not associated with a char */
+ if (receive_data->flags & GB_UART_RECV_FLAG_OVERRUN)
+ tty_insert_flip_char(port, 0, TTY_OVERRUN);
+ }
+ count = tty_insert_flip_string_fixed_flag(port, receive_data->data,
+ tty_flags, recv_data_size);
+ if (count != recv_data_size) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "UART: RX 0x%08x bytes only wrote 0x%08x\n",
+ recv_data_size, count);
+ }
+ if (count)
+ tty_flip_buffer_push(port);
+ return 0;
+}
+
+static int gb_uart_serial_state_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_tty *gb_tty = gb_connection_get_data(connection);
+ struct gb_message *request = op->request;
+ struct gb_uart_serial_state_request *serial_state;
+
+ if (request->payload_size < sizeof(*serial_state)) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "short serial-state event received (%zu < %zu)\n",
+ request->payload_size, sizeof(*serial_state));
+ return -EINVAL;
+ }
+
+ serial_state = request->payload;
+ gb_tty->ctrlin = serial_state->control;
+
+ return 0;
+}
+
+static int gb_uart_receive_credits_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_tty *gb_tty = gb_connection_get_data(connection);
+ struct gb_message *request = op->request;
+ struct gb_uart_receive_credits_request *credit_request;
+ unsigned long flags;
+ unsigned int incoming_credits;
+ int ret = 0;
+
+ if (request->payload_size < sizeof(*credit_request)) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "short receive_credits event received (%zu < %zu)\n",
+ request->payload_size,
+ sizeof(*credit_request));
+ return -EINVAL;
+ }
+
+ credit_request = request->payload;
+ incoming_credits = le16_to_cpu(credit_request->count);
+
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ gb_tty->credits += incoming_credits;
+ if (gb_tty->credits > GB_UART_FIRMWARE_CREDITS) {
+ gb_tty->credits -= incoming_credits;
+ ret = -EINVAL;
+ }
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+
+ if (ret) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "invalid number of incoming credits: %d\n",
+ incoming_credits);
+ return ret;
+ }
+
+ if (!gb_tty->close_pending)
+ schedule_work(&gb_tty->tx_work);
+
+ /*
+ * the port the tty layer may be waiting for credits
+ */
+ tty_port_tty_wakeup(&gb_tty->port);
+
+ if (gb_tty->credits == GB_UART_FIRMWARE_CREDITS)
+ complete(&gb_tty->credits_complete);
+
+ return ret;
+}
+
+static int gb_uart_request_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_tty *gb_tty = gb_connection_get_data(connection);
+ int type = op->type;
+ int ret;
+
+ switch (type) {
+ case GB_UART_TYPE_RECEIVE_DATA:
+ ret = gb_uart_receive_data_handler(op);
+ break;
+ case GB_UART_TYPE_SERIAL_STATE:
+ ret = gb_uart_serial_state_handler(op);
+ break;
+ case GB_UART_TYPE_RECEIVE_CREDITS:
+ ret = gb_uart_receive_credits_handler(op);
+ break;
+ default:
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "unsupported unsolicited request: 0x%02x\n", type);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void gb_uart_tx_write_work(struct work_struct *work)
+{
+ struct gb_uart_send_data_request *request;
+ struct gb_tty *gb_tty;
+ unsigned long flags;
+ unsigned int send_size;
+ int ret;
+
+ gb_tty = container_of(work, struct gb_tty, tx_work);
+ request = gb_tty->buffer;
+
+ while (1) {
+ if (gb_tty->close_pending)
+ break;
+
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ send_size = gb_tty->buffer_payload_max;
+ if (send_size > gb_tty->credits)
+ send_size = gb_tty->credits;
+
+ send_size = kfifo_out_peek(&gb_tty->write_fifo,
+ &request->data[0],
+ send_size);
+ if (!send_size) {
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+ break;
+ }
+
+ gb_tty->credits -= send_size;
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+
+ request->size = cpu_to_le16(send_size);
+ ret = gb_operation_sync(gb_tty->connection,
+ GB_UART_TYPE_SEND_DATA,
+ request, sizeof(*request) + send_size,
+ NULL, 0);
+ if (ret) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "send data error: %d\n", ret);
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ gb_tty->credits += send_size;
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+ if (!gb_tty->close_pending)
+ schedule_work(work);
+ return;
+ }
+
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ ret = kfifo_out(&gb_tty->write_fifo, &request->data[0],
+ send_size);
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+
+ tty_port_tty_wakeup(&gb_tty->port);
+ }
+}
+
+static int send_line_coding(struct gb_tty *tty)
+{
+ return gb_operation_sync(tty->connection, GB_UART_TYPE_SET_LINE_CODING,
+ &tty->line_coding, sizeof(tty->line_coding),
+ NULL, 0);
+}
+
+static int send_control(struct gb_tty *gb_tty, u8 control)
+{
+ struct gb_uart_set_control_line_state_request request;
+
+ request.control = control;
+ return gb_operation_sync(gb_tty->connection,
+ GB_UART_TYPE_SET_CONTROL_LINE_STATE,
+ &request, sizeof(request), NULL, 0);
+}
+
+static int send_break(struct gb_tty *gb_tty, u8 state)
+{
+ struct gb_uart_set_break_request request;
+
+ if ((state != 0) && (state != 1)) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "invalid break state of %d\n", state);
+ return -EINVAL;
+ }
+
+ request.state = state;
+ return gb_operation_sync(gb_tty->connection, GB_UART_TYPE_SEND_BREAK,
+ &request, sizeof(request), NULL, 0);
+}
+
+static int gb_uart_wait_for_all_credits(struct gb_tty *gb_tty)
+{
+ int ret;
+
+ if (gb_tty->credits == GB_UART_FIRMWARE_CREDITS)
+ return 0;
+
+ ret = wait_for_completion_timeout(&gb_tty->credits_complete,
+ msecs_to_jiffies(GB_UART_CREDIT_WAIT_TIMEOUT_MSEC));
+ if (!ret) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "time out waiting for credits\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int gb_uart_flush(struct gb_tty *gb_tty, u8 flags)
+{
+ struct gb_uart_serial_flush_request request;
+
+ request.flags = flags;
+ return gb_operation_sync(gb_tty->connection, GB_UART_TYPE_FLUSH_FIFOS,
+ &request, sizeof(request), NULL, 0);
+}
+
+static struct gb_tty *get_gb_by_minor(unsigned int minor)
+{
+ struct gb_tty *gb_tty;
+
+ mutex_lock(&table_lock);
+ gb_tty = idr_find(&tty_minors, minor);
+ if (gb_tty) {
+ mutex_lock(&gb_tty->mutex);
+ if (gb_tty->disconnected) {
+ mutex_unlock(&gb_tty->mutex);
+ gb_tty = NULL;
+ } else {
+ tty_port_get(&gb_tty->port);
+ mutex_unlock(&gb_tty->mutex);
+ }
+ }
+ mutex_unlock(&table_lock);
+ return gb_tty;
+}
+
+static int alloc_minor(struct gb_tty *gb_tty)
+{
+ int minor;
+
+ mutex_lock(&table_lock);
+ minor = idr_alloc(&tty_minors, gb_tty, 0, GB_NUM_MINORS, GFP_KERNEL);
+ mutex_unlock(&table_lock);
+ if (minor >= 0)
+ gb_tty->minor = minor;
+ return minor;
+}
+
+static void release_minor(struct gb_tty *gb_tty)
+{
+ int minor = gb_tty->minor;
+
+ gb_tty->minor = 0; /* Maybe should use an invalid value instead */
+ mutex_lock(&table_lock);
+ idr_remove(&tty_minors, minor);
+ mutex_unlock(&table_lock);
+}
+
+static int gb_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty;
+ int retval;
+
+ gb_tty = get_gb_by_minor(tty->index);
+ if (!gb_tty)
+ return -ENODEV;
+
+ retval = tty_standard_install(driver, tty);
+ if (retval)
+ goto error;
+
+ tty->driver_data = gb_tty;
+ return 0;
+error:
+ tty_port_put(&gb_tty->port);
+ return retval;
+}
+
+static int gb_tty_open(struct tty_struct *tty, struct file *file)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ return tty_port_open(&gb_tty->port, tty, file);
+}
+
+static void gb_tty_close(struct tty_struct *tty, struct file *file)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ tty_port_close(&gb_tty->port, tty, file);
+}
+
+static void gb_tty_cleanup(struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ tty_port_put(&gb_tty->port);
+}
+
+static void gb_tty_hangup(struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ tty_port_hangup(&gb_tty->port);
+}
+
+static ssize_t gb_tty_write(struct tty_struct *tty, const u8 *buf, size_t count)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ count = kfifo_in_spinlocked(&gb_tty->write_fifo, buf, count,
+ &gb_tty->write_lock);
+ if (count && !gb_tty->close_pending)
+ schedule_work(&gb_tty->tx_work);
+
+ return count;
+}
+
+static unsigned int gb_tty_write_room(struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+ unsigned long flags;
+ int room;
+
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ room = kfifo_avail(&gb_tty->write_fifo);
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+
+ room -= GB_UART_WRITE_ROOM_MARGIN;
+ if (room < 0)
+ return 0;
+
+ return room;
+}
+
+static unsigned int gb_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+ unsigned long flags;
+ unsigned int chars;
+
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ chars = kfifo_len(&gb_tty->write_fifo);
+ if (gb_tty->credits < GB_UART_FIRMWARE_CREDITS)
+ chars += GB_UART_FIRMWARE_CREDITS - gb_tty->credits;
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+
+ return chars;
+}
+
+static int gb_tty_break_ctl(struct tty_struct *tty, int state)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ return send_break(gb_tty, state ? 1 : 0);
+}
+
+static void gb_tty_set_termios(struct tty_struct *tty,
+ const struct ktermios *termios_old)
+{
+ struct gb_uart_set_line_coding_request newline;
+ struct gb_tty *gb_tty = tty->driver_data;
+ struct ktermios *termios = &tty->termios;
+ u8 newctrl = gb_tty->ctrlout;
+
+ newline.rate = cpu_to_le32(tty_get_baud_rate(tty));
+ newline.format = termios->c_cflag & CSTOPB ?
+ GB_SERIAL_2_STOP_BITS : GB_SERIAL_1_STOP_BITS;
+ newline.parity = termios->c_cflag & PARENB ?
+ (termios->c_cflag & PARODD ? 1 : 2) +
+ (termios->c_cflag & CMSPAR ? 2 : 0) : 0;
+
+ newline.data_bits = tty_get_char_size(termios->c_cflag);
+
+ /* FIXME: needs to clear unsupported bits in the termios */
+ gb_tty->clocal = ((termios->c_cflag & CLOCAL) != 0);
+
+ if (C_BAUD(tty) == B0) {
+ newline.rate = gb_tty->line_coding.rate;
+ newctrl &= ~(GB_UART_CTRL_DTR | GB_UART_CTRL_RTS);
+ } else if (termios_old && (termios_old->c_cflag & CBAUD) == B0) {
+ newctrl |= (GB_UART_CTRL_DTR | GB_UART_CTRL_RTS);
+ }
+
+ if (newctrl != gb_tty->ctrlout) {
+ gb_tty->ctrlout = newctrl;
+ send_control(gb_tty, newctrl);
+ }
+
+ if (C_CRTSCTS(tty) && C_BAUD(tty) != B0)
+ newline.flow_control = GB_SERIAL_AUTO_RTSCTS_EN;
+ else
+ newline.flow_control = 0;
+
+ if (memcmp(&gb_tty->line_coding, &newline, sizeof(newline))) {
+ memcpy(&gb_tty->line_coding, &newline, sizeof(newline));
+ send_line_coding(gb_tty);
+ }
+}
+
+static int gb_tty_tiocmget(struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ return (gb_tty->ctrlout & GB_UART_CTRL_DTR ? TIOCM_DTR : 0) |
+ (gb_tty->ctrlout & GB_UART_CTRL_RTS ? TIOCM_RTS : 0) |
+ (gb_tty->ctrlin & GB_UART_CTRL_DSR ? TIOCM_DSR : 0) |
+ (gb_tty->ctrlin & GB_UART_CTRL_RI ? TIOCM_RI : 0) |
+ (gb_tty->ctrlin & GB_UART_CTRL_DCD ? TIOCM_CD : 0) |
+ TIOCM_CTS;
+}
+
+static int gb_tty_tiocmset(struct tty_struct *tty, unsigned int set,
+ unsigned int clear)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+ u8 newctrl = gb_tty->ctrlout;
+
+ set = (set & TIOCM_DTR ? GB_UART_CTRL_DTR : 0) |
+ (set & TIOCM_RTS ? GB_UART_CTRL_RTS : 0);
+ clear = (clear & TIOCM_DTR ? GB_UART_CTRL_DTR : 0) |
+ (clear & TIOCM_RTS ? GB_UART_CTRL_RTS : 0);
+
+ newctrl = (newctrl & ~clear) | set;
+ if (gb_tty->ctrlout == newctrl)
+ return 0;
+
+ gb_tty->ctrlout = newctrl;
+ return send_control(gb_tty, newctrl);
+}
+
+static void gb_tty_throttle(struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+ unsigned char stop_char;
+ int retval;
+
+ if (I_IXOFF(tty)) {
+ stop_char = STOP_CHAR(tty);
+ retval = gb_tty_write(tty, &stop_char, 1);
+ if (retval <= 0)
+ return;
+ }
+
+ if (tty->termios.c_cflag & CRTSCTS) {
+ gb_tty->ctrlout &= ~GB_UART_CTRL_RTS;
+ retval = send_control(gb_tty, gb_tty->ctrlout);
+ }
+}
+
+static void gb_tty_unthrottle(struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+ unsigned char start_char;
+ int retval;
+
+ if (I_IXOFF(tty)) {
+ start_char = START_CHAR(tty);
+ retval = gb_tty_write(tty, &start_char, 1);
+ if (retval <= 0)
+ return;
+ }
+
+ if (tty->termios.c_cflag & CRTSCTS) {
+ gb_tty->ctrlout |= GB_UART_CTRL_RTS;
+ retval = send_control(gb_tty, gb_tty->ctrlout);
+ }
+}
+
+static int get_serial_info(struct tty_struct *tty,
+ struct serial_struct *ss)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ ss->line = gb_tty->minor;
+ ss->close_delay = jiffies_to_msecs(gb_tty->port.close_delay) / 10;
+ ss->closing_wait =
+ gb_tty->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ?
+ ASYNC_CLOSING_WAIT_NONE :
+ jiffies_to_msecs(gb_tty->port.closing_wait) / 10;
+
+ return 0;
+}
+
+static int set_serial_info(struct tty_struct *tty,
+ struct serial_struct *ss)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+ unsigned int closing_wait;
+ unsigned int close_delay;
+ int retval = 0;
+
+ close_delay = msecs_to_jiffies(ss->close_delay * 10);
+ closing_wait = ss->closing_wait == ASYNC_CLOSING_WAIT_NONE ?
+ ASYNC_CLOSING_WAIT_NONE :
+ msecs_to_jiffies(ss->closing_wait * 10);
+
+ mutex_lock(&gb_tty->port.mutex);
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((close_delay != gb_tty->port.close_delay) ||
+ (closing_wait != gb_tty->port.closing_wait))
+ retval = -EPERM;
+ } else {
+ gb_tty->port.close_delay = close_delay;
+ gb_tty->port.closing_wait = closing_wait;
+ }
+ mutex_unlock(&gb_tty->port.mutex);
+ return retval;
+}
+
+static int wait_serial_change(struct gb_tty *gb_tty, unsigned long arg)
+{
+ int retval = 0;
+ DECLARE_WAITQUEUE(wait, current);
+ struct async_icount old;
+ struct async_icount new;
+
+ if (!(arg & (TIOCM_DSR | TIOCM_RI | TIOCM_CD)))
+ return -EINVAL;
+
+ do {
+ spin_lock_irq(&gb_tty->read_lock);
+ old = gb_tty->oldcount;
+ new = gb_tty->iocount;
+ gb_tty->oldcount = new;
+ spin_unlock_irq(&gb_tty->read_lock);
+
+ if ((arg & TIOCM_DSR) && (old.dsr != new.dsr))
+ break;
+ if ((arg & TIOCM_CD) && (old.dcd != new.dcd))
+ break;
+ if ((arg & TIOCM_RI) && (old.rng != new.rng))
+ break;
+
+ add_wait_queue(&gb_tty->wioctl, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule();
+ remove_wait_queue(&gb_tty->wioctl, &wait);
+ if (gb_tty->disconnected) {
+ if (arg & TIOCM_CD)
+ break;
+ retval = -ENODEV;
+ } else if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ }
+ } while (!retval);
+
+ return retval;
+}
+
+static int gb_tty_get_icount(struct tty_struct *tty,
+ struct serial_icounter_struct *icount)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ icount->dsr = gb_tty->iocount.dsr;
+ icount->rng = gb_tty->iocount.rng;
+ icount->dcd = gb_tty->iocount.dcd;
+ icount->frame = gb_tty->iocount.frame;
+ icount->overrun = gb_tty->iocount.overrun;
+ icount->parity = gb_tty->iocount.parity;
+ icount->brk = gb_tty->iocount.brk;
+
+ return 0;
+}
+
+static int gb_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+ unsigned long arg)
+{
+ struct gb_tty *gb_tty = tty->driver_data;
+
+ switch (cmd) {
+ case TIOCMIWAIT:
+ return wait_serial_change(gb_tty, arg);
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+static void gb_tty_dtr_rts(struct tty_port *port, bool active)
+{
+ struct gb_tty *gb_tty;
+ u8 newctrl;
+
+ gb_tty = container_of(port, struct gb_tty, port);
+ newctrl = gb_tty->ctrlout;
+
+ if (active)
+ newctrl |= (GB_UART_CTRL_DTR | GB_UART_CTRL_RTS);
+ else
+ newctrl &= ~(GB_UART_CTRL_DTR | GB_UART_CTRL_RTS);
+
+ gb_tty->ctrlout = newctrl;
+ send_control(gb_tty, newctrl);
+}
+
+static int gb_tty_port_activate(struct tty_port *port,
+ struct tty_struct *tty)
+{
+ struct gb_tty *gb_tty;
+
+ gb_tty = container_of(port, struct gb_tty, port);
+
+ return gbphy_runtime_get_sync(gb_tty->gbphy_dev);
+}
+
+static void gb_tty_port_shutdown(struct tty_port *port)
+{
+ struct gb_tty *gb_tty;
+ unsigned long flags;
+ int ret;
+
+ gb_tty = container_of(port, struct gb_tty, port);
+
+ gb_tty->close_pending = true;
+
+ cancel_work_sync(&gb_tty->tx_work);
+
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ kfifo_reset_out(&gb_tty->write_fifo);
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+
+ if (gb_tty->credits == GB_UART_FIRMWARE_CREDITS)
+ goto out;
+
+ ret = gb_uart_flush(gb_tty, GB_SERIAL_FLAG_FLUSH_TRANSMITTER);
+ if (ret) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "error flushing transmitter: %d\n", ret);
+ }
+
+ gb_uart_wait_for_all_credits(gb_tty);
+
+out:
+ gb_tty->close_pending = false;
+
+ gbphy_runtime_put_autosuspend(gb_tty->gbphy_dev);
+}
+
+static void gb_tty_port_destruct(struct tty_port *port)
+{
+ struct gb_tty *gb_tty = container_of(port, struct gb_tty, port);
+
+ if (gb_tty->minor != GB_NUM_MINORS)
+ release_minor(gb_tty);
+ kfifo_free(&gb_tty->write_fifo);
+ kfree(gb_tty->buffer);
+ kfree(gb_tty);
+}
+
+static const struct tty_operations gb_ops = {
+ .install = gb_tty_install,
+ .open = gb_tty_open,
+ .close = gb_tty_close,
+ .cleanup = gb_tty_cleanup,
+ .hangup = gb_tty_hangup,
+ .write = gb_tty_write,
+ .write_room = gb_tty_write_room,
+ .ioctl = gb_tty_ioctl,
+ .throttle = gb_tty_throttle,
+ .unthrottle = gb_tty_unthrottle,
+ .chars_in_buffer = gb_tty_chars_in_buffer,
+ .break_ctl = gb_tty_break_ctl,
+ .set_termios = gb_tty_set_termios,
+ .tiocmget = gb_tty_tiocmget,
+ .tiocmset = gb_tty_tiocmset,
+ .get_icount = gb_tty_get_icount,
+ .set_serial = set_serial_info,
+ .get_serial = get_serial_info,
+};
+
+static const struct tty_port_operations gb_port_ops = {
+ .dtr_rts = gb_tty_dtr_rts,
+ .activate = gb_tty_port_activate,
+ .shutdown = gb_tty_port_shutdown,
+ .destruct = gb_tty_port_destruct,
+};
+
+static int gb_uart_probe(struct gbphy_device *gbphy_dev,
+ const struct gbphy_device_id *id)
+{
+ struct gb_connection *connection;
+ size_t max_payload;
+ struct gb_tty *gb_tty;
+ struct device *tty_dev;
+ int retval;
+ int minor;
+
+ connection = gb_connection_create(gbphy_dev->bundle,
+ le16_to_cpu(gbphy_dev->cport_desc->id),
+ gb_uart_request_handler);
+ if (IS_ERR(connection))
+ return PTR_ERR(connection);
+
+ max_payload = gb_operation_get_payload_size_max(connection);
+ if (max_payload < sizeof(struct gb_uart_send_data_request)) {
+ retval = -EINVAL;
+ goto exit_connection_destroy;
+ }
+
+ gb_tty = kzalloc(sizeof(*gb_tty), GFP_KERNEL);
+ if (!gb_tty) {
+ retval = -ENOMEM;
+ goto exit_connection_destroy;
+ }
+
+ tty_port_init(&gb_tty->port);
+ gb_tty->port.ops = &gb_port_ops;
+ gb_tty->minor = GB_NUM_MINORS;
+
+ gb_tty->buffer_payload_max = max_payload -
+ sizeof(struct gb_uart_send_data_request);
+
+ gb_tty->buffer = kzalloc(gb_tty->buffer_payload_max, GFP_KERNEL);
+ if (!gb_tty->buffer) {
+ retval = -ENOMEM;
+ goto exit_put_port;
+ }
+
+ INIT_WORK(&gb_tty->tx_work, gb_uart_tx_write_work);
+
+ retval = kfifo_alloc(&gb_tty->write_fifo, GB_UART_WRITE_FIFO_SIZE,
+ GFP_KERNEL);
+ if (retval)
+ goto exit_put_port;
+
+ gb_tty->credits = GB_UART_FIRMWARE_CREDITS;
+ init_completion(&gb_tty->credits_complete);
+
+ minor = alloc_minor(gb_tty);
+ if (minor < 0) {
+ if (minor == -ENOSPC) {
+ dev_err(&gbphy_dev->dev,
+ "no more free minor numbers\n");
+ retval = -ENODEV;
+ } else {
+ retval = minor;
+ }
+ goto exit_put_port;
+ }
+
+ gb_tty->minor = minor;
+ spin_lock_init(&gb_tty->write_lock);
+ spin_lock_init(&gb_tty->read_lock);
+ init_waitqueue_head(&gb_tty->wioctl);
+ mutex_init(&gb_tty->mutex);
+
+ gb_tty->connection = connection;
+ gb_tty->gbphy_dev = gbphy_dev;
+ gb_connection_set_data(connection, gb_tty);
+ gb_gbphy_set_data(gbphy_dev, gb_tty);
+
+ retval = gb_connection_enable_tx(connection);
+ if (retval)
+ goto exit_put_port;
+
+ send_control(gb_tty, gb_tty->ctrlout);
+
+ /* initialize the uart to be 9600n81 */
+ gb_tty->line_coding.rate = cpu_to_le32(9600);
+ gb_tty->line_coding.format = GB_SERIAL_1_STOP_BITS;
+ gb_tty->line_coding.parity = GB_SERIAL_NO_PARITY;
+ gb_tty->line_coding.data_bits = 8;
+ send_line_coding(gb_tty);
+
+ retval = gb_connection_enable(connection);
+ if (retval)
+ goto exit_connection_disable;
+
+ tty_dev = tty_port_register_device(&gb_tty->port, gb_tty_driver, minor,
+ &gbphy_dev->dev);
+ if (IS_ERR(tty_dev)) {
+ retval = PTR_ERR(tty_dev);
+ goto exit_connection_disable;
+ }
+
+ gbphy_runtime_put_autosuspend(gbphy_dev);
+ return 0;
+
+exit_connection_disable:
+ gb_connection_disable(connection);
+exit_put_port:
+ tty_port_put(&gb_tty->port);
+exit_connection_destroy:
+ gb_connection_destroy(connection);
+
+ return retval;
+}
+
+static void gb_uart_remove(struct gbphy_device *gbphy_dev)
+{
+ struct gb_tty *gb_tty = gb_gbphy_get_data(gbphy_dev);
+ struct gb_connection *connection = gb_tty->connection;
+ struct tty_struct *tty;
+ int ret;
+
+ ret = gbphy_runtime_get_sync(gbphy_dev);
+ if (ret)
+ gbphy_runtime_get_noresume(gbphy_dev);
+
+ mutex_lock(&gb_tty->mutex);
+ gb_tty->disconnected = true;
+
+ wake_up_all(&gb_tty->wioctl);
+ mutex_unlock(&gb_tty->mutex);
+
+ tty = tty_port_tty_get(&gb_tty->port);
+ if (tty) {
+ tty_vhangup(tty);
+ tty_kref_put(tty);
+ }
+
+ gb_connection_disable_rx(connection);
+ tty_unregister_device(gb_tty_driver, gb_tty->minor);
+
+ gb_connection_disable(connection);
+ gb_connection_destroy(connection);
+
+ tty_port_put(&gb_tty->port);
+}
+
+static int gb_tty_init(void)
+{
+ int retval = 0;
+
+ gb_tty_driver = tty_alloc_driver(GB_NUM_MINORS, 0);
+ if (IS_ERR(gb_tty_driver)) {
+ pr_err("Can not allocate tty driver\n");
+ retval = -ENOMEM;
+ goto fail_unregister_dev;
+ }
+
+ gb_tty_driver->driver_name = "gb";
+ gb_tty_driver->name = GB_NAME;
+ gb_tty_driver->major = 0;
+ gb_tty_driver->minor_start = 0;
+ gb_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ gb_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ gb_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+ gb_tty_driver->init_termios = tty_std_termios;
+ gb_tty_driver->init_termios.c_cflag = B9600 | CS8 |
+ CREAD | HUPCL | CLOCAL;
+ tty_set_operations(gb_tty_driver, &gb_ops);
+
+ retval = tty_register_driver(gb_tty_driver);
+ if (retval) {
+ pr_err("Can not register tty driver: %d\n", retval);
+ goto fail_put_gb_tty;
+ }
+
+ return 0;
+
+fail_put_gb_tty:
+ tty_driver_kref_put(gb_tty_driver);
+fail_unregister_dev:
+ return retval;
+}
+
+static void gb_tty_exit(void)
+{
+ tty_unregister_driver(gb_tty_driver);
+ tty_driver_kref_put(gb_tty_driver);
+ idr_destroy(&tty_minors);
+}
+
+static const struct gbphy_device_id gb_uart_id_table[] = {
+ { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_UART) },
+ { },
+};
+MODULE_DEVICE_TABLE(gbphy, gb_uart_id_table);
+
+static struct gbphy_driver uart_driver = {
+ .name = "uart",
+ .probe = gb_uart_probe,
+ .remove = gb_uart_remove,
+ .id_table = gb_uart_id_table,
+};
+
+static int gb_uart_driver_init(void)
+{
+ int ret;
+
+ ret = gb_tty_init();
+ if (ret)
+ return ret;
+
+ ret = gb_gbphy_register(&uart_driver);
+ if (ret) {
+ gb_tty_exit();
+ return ret;
+ }
+
+ return 0;
+}
+module_init(gb_uart_driver_init);
+
+static void gb_uart_driver_exit(void)
+{
+ gb_gbphy_deregister(&uart_driver);
+ gb_tty_exit();
+}
+
+module_exit(gb_uart_driver_exit);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/usb.c b/drivers/staging/greybus/usb.c
new file mode 100644
index 0000000000..b7badf87a3
--- /dev/null
+++ b/drivers/staging/greybus/usb.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB host driver for the Greybus "generic" USB module.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/greybus.h>
+
+#include "gbphy.h"
+
+/* Greybus USB request types */
+#define GB_USB_TYPE_HCD_START 0x02
+#define GB_USB_TYPE_HCD_STOP 0x03
+#define GB_USB_TYPE_HUB_CONTROL 0x04
+
+struct gb_usb_hub_control_request {
+ __le16 typeReq;
+ __le16 wValue;
+ __le16 wIndex;
+ __le16 wLength;
+};
+
+struct gb_usb_hub_control_response {
+ DECLARE_FLEX_ARRAY(u8, buf);
+};
+
+struct gb_usb_device {
+ struct gb_connection *connection;
+ struct gbphy_device *gbphy_dev;
+};
+
+static inline struct gb_usb_device *to_gb_usb_device(struct usb_hcd *hcd)
+{
+ return (struct gb_usb_device *)hcd->hcd_priv;
+}
+
+static inline struct usb_hcd *gb_usb_device_to_hcd(struct gb_usb_device *dev)
+{
+ return container_of((void *)dev, struct usb_hcd, hcd_priv);
+}
+
+static void hcd_stop(struct usb_hcd *hcd)
+{
+ struct gb_usb_device *dev = to_gb_usb_device(hcd);
+ int ret;
+
+ ret = gb_operation_sync(dev->connection, GB_USB_TYPE_HCD_STOP,
+ NULL, 0, NULL, 0);
+ if (ret)
+ dev_err(&dev->gbphy_dev->dev, "HCD stop failed '%d'\n", ret);
+}
+
+static int hcd_start(struct usb_hcd *hcd)
+{
+ struct usb_bus *bus = hcd_to_bus(hcd);
+ struct gb_usb_device *dev = to_gb_usb_device(hcd);
+ int ret;
+
+ ret = gb_operation_sync(dev->connection, GB_USB_TYPE_HCD_START,
+ NULL, 0, NULL, 0);
+ if (ret) {
+ dev_err(&dev->gbphy_dev->dev, "HCD start failed '%d'\n", ret);
+ return ret;
+ }
+
+ hcd->state = HC_STATE_RUNNING;
+ if (bus->root_hub)
+ usb_hcd_resume_root_hub(hcd);
+ return 0;
+}
+
+static int urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags)
+{
+ return -ENXIO;
+}
+
+static int urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
+{
+ return -ENXIO;
+}
+
+static int get_frame_number(struct usb_hcd *hcd)
+{
+ return 0;
+}
+
+static int hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+ return 0;
+}
+
+static int hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex,
+ char *buf, u16 wLength)
+{
+ struct gb_usb_device *dev = to_gb_usb_device(hcd);
+ struct gb_operation *operation;
+ struct gb_usb_hub_control_request *request;
+ struct gb_usb_hub_control_response *response;
+ size_t response_size;
+ int ret;
+
+ /* FIXME: handle unspecified lengths */
+ response_size = sizeof(*response) + wLength;
+
+ operation = gb_operation_create(dev->connection,
+ GB_USB_TYPE_HUB_CONTROL,
+ sizeof(*request),
+ response_size,
+ GFP_KERNEL);
+ if (!operation)
+ return -ENOMEM;
+
+ request = operation->request->payload;
+ request->typeReq = cpu_to_le16(typeReq);
+ request->wValue = cpu_to_le16(wValue);
+ request->wIndex = cpu_to_le16(wIndex);
+ request->wLength = cpu_to_le16(wLength);
+
+ ret = gb_operation_request_send_sync(operation);
+ if (ret)
+ goto out;
+
+ if (wLength) {
+ /* Greybus core has verified response size */
+ response = operation->response->payload;
+ memcpy(buf, response->buf, wLength);
+ }
+out:
+ gb_operation_put(operation);
+
+ return ret;
+}
+
+static const struct hc_driver usb_gb_hc_driver = {
+ .description = "greybus-hcd",
+ .product_desc = "Greybus USB Host Controller",
+ .hcd_priv_size = sizeof(struct gb_usb_device),
+
+ .flags = HCD_USB2,
+
+ .start = hcd_start,
+ .stop = hcd_stop,
+
+ .urb_enqueue = urb_enqueue,
+ .urb_dequeue = urb_dequeue,
+
+ .get_frame_number = get_frame_number,
+ .hub_status_data = hub_status_data,
+ .hub_control = hub_control,
+};
+
+static int gb_usb_probe(struct gbphy_device *gbphy_dev,
+ const struct gbphy_device_id *id)
+{
+ struct gb_connection *connection;
+ struct device *dev = &gbphy_dev->dev;
+ struct gb_usb_device *gb_usb_dev;
+ struct usb_hcd *hcd;
+ int retval;
+
+ hcd = usb_create_hcd(&usb_gb_hc_driver, dev, dev_name(dev));
+ if (!hcd)
+ return -ENOMEM;
+
+ connection = gb_connection_create(gbphy_dev->bundle,
+ le16_to_cpu(gbphy_dev->cport_desc->id),
+ NULL);
+ if (IS_ERR(connection)) {
+ retval = PTR_ERR(connection);
+ goto exit_usb_put;
+ }
+
+ gb_usb_dev = to_gb_usb_device(hcd);
+ gb_usb_dev->connection = connection;
+ gb_connection_set_data(connection, gb_usb_dev);
+ gb_usb_dev->gbphy_dev = gbphy_dev;
+ gb_gbphy_set_data(gbphy_dev, gb_usb_dev);
+
+ hcd->has_tt = 1;
+
+ retval = gb_connection_enable(connection);
+ if (retval)
+ goto exit_connection_destroy;
+
+ /*
+ * FIXME: The USB bridged-PHY protocol driver depends on changes to
+ * USB core which are not yet upstream.
+ *
+ * Disable for now.
+ */
+ if (1) {
+ dev_warn(dev, "USB protocol disabled\n");
+ retval = -EPROTONOSUPPORT;
+ goto exit_connection_disable;
+ }
+
+ retval = usb_add_hcd(hcd, 0, 0);
+ if (retval)
+ goto exit_connection_disable;
+
+ return 0;
+
+exit_connection_disable:
+ gb_connection_disable(connection);
+exit_connection_destroy:
+ gb_connection_destroy(connection);
+exit_usb_put:
+ usb_put_hcd(hcd);
+
+ return retval;
+}
+
+static void gb_usb_remove(struct gbphy_device *gbphy_dev)
+{
+ struct gb_usb_device *gb_usb_dev = gb_gbphy_get_data(gbphy_dev);
+ struct gb_connection *connection = gb_usb_dev->connection;
+ struct usb_hcd *hcd = gb_usb_device_to_hcd(gb_usb_dev);
+
+ usb_remove_hcd(hcd);
+ gb_connection_disable(connection);
+ gb_connection_destroy(connection);
+ usb_put_hcd(hcd);
+}
+
+static const struct gbphy_device_id gb_usb_id_table[] = {
+ { GBPHY_PROTOCOL(GREYBUS_PROTOCOL_USB) },
+ { },
+};
+MODULE_DEVICE_TABLE(gbphy, gb_usb_id_table);
+
+static struct gbphy_driver usb_driver = {
+ .name = "usb",
+ .probe = gb_usb_probe,
+ .remove = gb_usb_remove,
+ .id_table = gb_usb_id_table,
+};
+
+module_gbphy_driver(usb_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/greybus/vibrator.c b/drivers/staging/greybus/vibrator.c
new file mode 100644
index 0000000000..227e18d92a
--- /dev/null
+++ b/drivers/staging/greybus/vibrator.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Greybus Vibrator protocol driver.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/kdev_t.h>
+#include <linux/idr.h>
+#include <linux/pm_runtime.h>
+#include <linux/greybus.h>
+
+struct gb_vibrator_device {
+ struct gb_connection *connection;
+ struct device *dev;
+ int minor; /* vibrator minor number */
+ struct delayed_work delayed_work;
+};
+
+/* Greybus Vibrator operation types */
+#define GB_VIBRATOR_TYPE_ON 0x02
+#define GB_VIBRATOR_TYPE_OFF 0x03
+
+static int turn_off(struct gb_vibrator_device *vib)
+{
+ struct gb_bundle *bundle = vib->connection->bundle;
+ int ret;
+
+ ret = gb_operation_sync(vib->connection, GB_VIBRATOR_TYPE_OFF,
+ NULL, 0, NULL, 0);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return ret;
+}
+
+static int turn_on(struct gb_vibrator_device *vib, u16 timeout_ms)
+{
+ struct gb_bundle *bundle = vib->connection->bundle;
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ return ret;
+
+ /* Vibrator was switched ON earlier */
+ if (cancel_delayed_work_sync(&vib->delayed_work))
+ turn_off(vib);
+
+ ret = gb_operation_sync(vib->connection, GB_VIBRATOR_TYPE_ON,
+ NULL, 0, NULL, 0);
+ if (ret) {
+ gb_pm_runtime_put_autosuspend(bundle);
+ return ret;
+ }
+
+ schedule_delayed_work(&vib->delayed_work, msecs_to_jiffies(timeout_ms));
+
+ return 0;
+}
+
+static void gb_vibrator_worker(struct work_struct *work)
+{
+ struct delayed_work *delayed_work = to_delayed_work(work);
+ struct gb_vibrator_device *vib =
+ container_of(delayed_work,
+ struct gb_vibrator_device,
+ delayed_work);
+
+ turn_off(vib);
+}
+
+static ssize_t timeout_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct gb_vibrator_device *vib = dev_get_drvdata(dev);
+ unsigned long val;
+ int retval;
+
+ retval = kstrtoul(buf, 10, &val);
+ if (retval < 0) {
+ dev_err(dev, "could not parse timeout value %d\n", retval);
+ return retval;
+ }
+
+ if (val)
+ retval = turn_on(vib, (u16)val);
+ else
+ retval = turn_off(vib);
+ if (retval)
+ return retval;
+
+ return count;
+}
+static DEVICE_ATTR_WO(timeout);
+
+static struct attribute *vibrator_attrs[] = {
+ &dev_attr_timeout.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(vibrator);
+
+static struct class vibrator_class = {
+ .name = "vibrator",
+ .dev_groups = vibrator_groups,
+};
+
+static DEFINE_IDA(minors);
+
+static int gb_vibrator_probe(struct gb_bundle *bundle,
+ const struct greybus_bundle_id *id)
+{
+ struct greybus_descriptor_cport *cport_desc;
+ struct gb_connection *connection;
+ struct gb_vibrator_device *vib;
+ struct device *dev;
+ int retval;
+
+ if (bundle->num_cports != 1)
+ return -ENODEV;
+
+ cport_desc = &bundle->cport_desc[0];
+ if (cport_desc->protocol_id != GREYBUS_PROTOCOL_VIBRATOR)
+ return -ENODEV;
+
+ vib = kzalloc(sizeof(*vib), GFP_KERNEL);
+ if (!vib)
+ return -ENOMEM;
+
+ connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+ NULL);
+ if (IS_ERR(connection)) {
+ retval = PTR_ERR(connection);
+ goto err_free_vib;
+ }
+ gb_connection_set_data(connection, vib);
+
+ vib->connection = connection;
+
+ greybus_set_drvdata(bundle, vib);
+
+ retval = gb_connection_enable(connection);
+ if (retval)
+ goto err_connection_destroy;
+
+ /*
+ * For now we create a device in sysfs for the vibrator, but odds are
+ * there is a "real" device somewhere in the kernel for this, but I
+ * can't find it at the moment...
+ */
+ vib->minor = ida_simple_get(&minors, 0, 0, GFP_KERNEL);
+ if (vib->minor < 0) {
+ retval = vib->minor;
+ goto err_connection_disable;
+ }
+ dev = device_create(&vibrator_class, &bundle->dev,
+ MKDEV(0, 0), vib, "vibrator%d", vib->minor);
+ if (IS_ERR(dev)) {
+ retval = -EINVAL;
+ goto err_ida_remove;
+ }
+ vib->dev = dev;
+
+ INIT_DELAYED_WORK(&vib->delayed_work, gb_vibrator_worker);
+
+ gb_pm_runtime_put_autosuspend(bundle);
+
+ return 0;
+
+err_ida_remove:
+ ida_simple_remove(&minors, vib->minor);
+err_connection_disable:
+ gb_connection_disable(connection);
+err_connection_destroy:
+ gb_connection_destroy(connection);
+err_free_vib:
+ kfree(vib);
+
+ return retval;
+}
+
+static void gb_vibrator_disconnect(struct gb_bundle *bundle)
+{
+ struct gb_vibrator_device *vib = greybus_get_drvdata(bundle);
+ int ret;
+
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret)
+ gb_pm_runtime_get_noresume(bundle);
+
+ if (cancel_delayed_work_sync(&vib->delayed_work))
+ turn_off(vib);
+
+ device_unregister(vib->dev);
+ ida_simple_remove(&minors, vib->minor);
+ gb_connection_disable(vib->connection);
+ gb_connection_destroy(vib->connection);
+ kfree(vib);
+}
+
+static const struct greybus_bundle_id gb_vibrator_id_table[] = {
+ { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_VIBRATOR) },
+ { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_vibrator_id_table);
+
+static struct greybus_driver gb_vibrator_driver = {
+ .name = "vibrator",
+ .probe = gb_vibrator_probe,
+ .disconnect = gb_vibrator_disconnect,
+ .id_table = gb_vibrator_id_table,
+};
+
+static __init int gb_vibrator_init(void)
+{
+ int retval;
+
+ retval = class_register(&vibrator_class);
+ if (retval)
+ return retval;
+
+ retval = greybus_register(&gb_vibrator_driver);
+ if (retval)
+ goto err_class_unregister;
+
+ return 0;
+
+err_class_unregister:
+ class_unregister(&vibrator_class);
+
+ return retval;
+}
+module_init(gb_vibrator_init);
+
+static __exit void gb_vibrator_exit(void)
+{
+ greybus_deregister(&gb_vibrator_driver);
+ class_unregister(&vibrator_class);
+ ida_destroy(&minors);
+}
+module_exit(gb_vibrator_exit);
+
+MODULE_LICENSE("GPL v2");