aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging')
-rw-r--r--drivers/staging/Kconfig3
-rw-r--r--drivers/staging/Makefile2
-rw-r--r--drivers/staging/android/vsoc.c8
-rw-r--r--drivers/staging/isdn/hysdn/Kconfig2
-rw-r--r--drivers/staging/media/Kconfig4
-rw-r--r--drivers/staging/media/Makefile2
-rw-r--r--drivers/staging/media/bcm2048/Kconfig14
-rw-r--r--drivers/staging/media/bcm2048/Makefile2
-rw-r--r--drivers/staging/media/bcm2048/TODO24
-rw-r--r--drivers/staging/media/bcm2048/radio-bcm2048.c2689
-rw-r--r--drivers/staging/media/bcm2048/radio-bcm2048.h26
-rw-r--r--drivers/staging/media/davinci_vpfe/Kconfig13
-rw-r--r--drivers/staging/media/davinci_vpfe/Makefile11
-rw-r--r--drivers/staging/media/davinci_vpfe/TODO38
-rw-r--r--drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt154
-rw-r--r--drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h1287
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe.c1852
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe.h174
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c1038
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h556
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif.c1070
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif.h228
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h90
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif.c2097
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif.h200
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif_regs.h291
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_resizer.c1995
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_resizer.h241
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe.h83
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c716
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h90
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_video.c1646
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_video.h150
-rw-r--r--drivers/staging/media/hantro/Kconfig2
-rw-r--r--drivers/staging/media/hantro/Makefile7
-rw-r--r--drivers/staging/media/hantro/hantro.h51
-rw-r--r--drivers/staging/media/hantro/hantro_drv.c106
-rw-r--r--drivers/staging/media/hantro/hantro_g1_h264_dec.c292
-rw-r--r--drivers/staging/media/hantro/hantro_g1_mpeg2_dec.c14
-rw-r--r--drivers/staging/media/hantro/hantro_g1_vp8_dec.c503
-rw-r--r--drivers/staging/media/hantro/hantro_h1_jpeg_enc.c11
-rw-r--r--drivers/staging/media/hantro/hantro_h264.c646
-rw-r--r--drivers/staging/media/hantro/hantro_hw.h80
-rw-r--r--drivers/staging/media/hantro/hantro_v4l2.c11
-rw-r--r--drivers/staging/media/hantro/hantro_vp8.c201
-rw-r--r--drivers/staging/media/hantro/rk3288_vpu_hw.c41
-rw-r--r--drivers/staging/media/hantro/rk3399_vpu_hw.c39
-rw-r--r--drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c12
-rw-r--r--drivers/staging/media/hantro/rk3399_vpu_hw_mpeg2_dec.c14
-rw-r--r--drivers/staging/media/hantro/rk3399_vpu_hw_vp8_dec.c595
-rw-r--r--drivers/staging/media/imx/Kconfig5
-rw-r--r--drivers/staging/media/imx/Makefile3
-rw-r--r--drivers/staging/media/imx/imx-media-csc-scaler.c925
-rw-r--r--drivers/staging/media/imx/imx-media-dev.c28
-rw-r--r--drivers/staging/media/imx/imx-media-internal-sd.c4
-rw-r--r--drivers/staging/media/imx/imx-media-utils.c2
-rw-r--r--drivers/staging/media/imx/imx-media.h12
-rw-r--r--drivers/staging/media/imx/imx6-mipi-csi2.c12
-rw-r--r--drivers/staging/media/imx/imx7-media-csi.c30
-rw-r--r--drivers/staging/media/ipu3/ipu3-tables.h4
-rw-r--r--drivers/staging/media/ipu3/ipu3.c3
-rw-r--r--drivers/staging/media/omap4iss/iss_video.c40
-rw-r--r--drivers/staging/media/omap4iss/iss_video.h2
-rw-r--r--drivers/staging/media/soc_camera/soc_camera.c2
-rw-r--r--drivers/staging/media/sunxi/cedrus/cedrus.c63
-rw-r--r--drivers/staging/media/sunxi/cedrus/cedrus.h7
-rw-r--r--drivers/staging/media/sunxi/cedrus/cedrus_dec.c2
-rw-r--r--drivers/staging/media/sunxi/cedrus/cedrus_h264.c4
-rw-r--r--drivers/staging/media/sunxi/cedrus/cedrus_hw.c3
-rw-r--r--drivers/staging/media/sunxi/cedrus/cedrus_regs.h2
-rw-r--r--drivers/staging/media/sunxi/cedrus/cedrus_video.c28
-rw-r--r--drivers/staging/media/tegra-vde/Kconfig2
-rw-r--r--drivers/staging/octeon-usb/octeon-hcd.c2
-rw-r--r--drivers/staging/uwb/Kconfig72
-rw-r--r--drivers/staging/uwb/Makefile32
-rw-r--r--drivers/staging/uwb/TODO8
-rw-r--r--drivers/staging/uwb/address.c352
-rw-r--r--drivers/staging/uwb/allocator.c374
-rw-r--r--drivers/staging/uwb/beacon.c595
-rw-r--r--drivers/staging/uwb/driver.c143
-rw-r--r--drivers/staging/uwb/drp-avail.c278
-rw-r--r--drivers/staging/uwb/drp-ie.c305
-rw-r--r--drivers/staging/uwb/drp.c842
-rw-r--r--drivers/staging/uwb/est.c450
-rw-r--r--drivers/staging/uwb/hwa-rc.c929
-rw-r--r--drivers/staging/uwb/i1480/Makefile2
-rw-r--r--drivers/staging/uwb/i1480/dfu/Makefile10
-rw-r--r--drivers/staging/uwb/i1480/dfu/dfu.c198
-rw-r--r--drivers/staging/uwb/i1480/dfu/i1480-dfu.h246
-rw-r--r--drivers/staging/uwb/i1480/dfu/mac.c496
-rw-r--r--drivers/staging/uwb/i1480/dfu/phy.c190
-rw-r--r--drivers/staging/uwb/i1480/dfu/usb.c448
-rw-r--r--drivers/staging/uwb/i1480/i1480-est.c85
-rw-r--r--drivers/staging/uwb/ie-rcv.c42
-rw-r--r--drivers/staging/uwb/ie.c366
-rw-r--r--drivers/staging/uwb/include/debug-cmd.h57
-rw-r--r--drivers/staging/uwb/include/spec.h767
-rw-r--r--drivers/staging/uwb/include/umc.h192
-rw-r--r--drivers/staging/uwb/include/whci.h102
-rw-r--r--drivers/staging/uwb/lc-dev.c457
-rw-r--r--drivers/staging/uwb/lc-rc.c569
-rw-r--r--drivers/staging/uwb/neh.c606
-rw-r--r--drivers/staging/uwb/pal.c128
-rw-r--r--drivers/staging/uwb/radio.c196
-rw-r--r--drivers/staging/uwb/reset.c379
-rw-r--r--drivers/staging/uwb/rsv.c1000
-rw-r--r--drivers/staging/uwb/scan.c120
-rw-r--r--drivers/staging/uwb/umc-bus.c211
-rw-r--r--drivers/staging/uwb/umc-dev.c94
-rw-r--r--drivers/staging/uwb/umc-drv.c31
-rw-r--r--drivers/staging/uwb/uwb-debug.c354
-rw-r--r--drivers/staging/uwb/uwb-internal.h366
-rw-r--r--drivers/staging/uwb/uwb.h817
-rw-r--r--drivers/staging/uwb/uwbd.c356
-rw-r--r--drivers/staging/uwb/whc-rc.c467
-rw-r--r--drivers/staging/uwb/whci.c257
-rw-r--r--drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c29
-rw-r--r--drivers/staging/vc04_services/bcm2835-camera/mmal-common.h1
-rw-r--r--drivers/staging/wusbcore/Documentation/wusb-cbaf130
-rw-r--r--drivers/staging/wusbcore/Documentation/wusb-design-overview.rst457
-rw-r--r--drivers/staging/wusbcore/Kconfig39
-rw-r--r--drivers/staging/wusbcore/Makefile28
-rw-r--r--drivers/staging/wusbcore/TODO8
-rw-r--r--drivers/staging/wusbcore/cbaf.c645
-rw-r--r--drivers/staging/wusbcore/crypto.c441
-rw-r--r--drivers/staging/wusbcore/dev-sysfs.c124
-rw-r--r--drivers/staging/wusbcore/devconnect.c1085
-rw-r--r--drivers/staging/wusbcore/host/Kconfig28
-rw-r--r--drivers/staging/wusbcore/host/Makefile3
-rw-r--r--drivers/staging/wusbcore/host/hwa-hc.c875
-rw-r--r--drivers/staging/wusbcore/host/whci/Makefile14
-rw-r--r--drivers/staging/wusbcore/host/whci/asl.c376
-rw-r--r--drivers/staging/wusbcore/host/whci/debug.c153
-rw-r--r--drivers/staging/wusbcore/host/whci/hcd.c356
-rw-r--r--drivers/staging/wusbcore/host/whci/hw.c93
-rw-r--r--drivers/staging/wusbcore/host/whci/init.c177
-rw-r--r--drivers/staging/wusbcore/host/whci/int.c82
-rw-r--r--drivers/staging/wusbcore/host/whci/pzl.c404
-rw-r--r--drivers/staging/wusbcore/host/whci/qset.c831
-rw-r--r--drivers/staging/wusbcore/host/whci/whcd.h202
-rw-r--r--drivers/staging/wusbcore/host/whci/whci-hc.h401
-rw-r--r--drivers/staging/wusbcore/host/whci/wusb.c210
-rw-r--r--drivers/staging/wusbcore/include/association.h151
-rw-r--r--drivers/staging/wusbcore/include/wusb-wa.h304
-rw-r--r--drivers/staging/wusbcore/include/wusb.h362
-rw-r--r--drivers/staging/wusbcore/mmc.c303
-rw-r--r--drivers/staging/wusbcore/pal.c45
-rw-r--r--drivers/staging/wusbcore/reservation.c110
-rw-r--r--drivers/staging/wusbcore/rh.c426
-rw-r--r--drivers/staging/wusbcore/security.c599
-rw-r--r--drivers/staging/wusbcore/wa-hc.c88
-rw-r--r--drivers/staging/wusbcore/wa-hc.h467
-rw-r--r--drivers/staging/wusbcore/wa-nep.c289
-rw-r--r--drivers/staging/wusbcore/wa-rpipe.c539
-rw-r--r--drivers/staging/wusbcore/wa-xfer.c2927
-rw-r--r--drivers/staging/wusbcore/wusbhc.c490
-rw-r--r--drivers/staging/wusbcore/wusbhc.h487
157 files changed, 32387 insertions, 16987 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index fbdc33874780..b9c7f0dc653b 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -118,6 +118,9 @@ source "drivers/staging/kpc2000/Kconfig"
source "drivers/staging/isdn/Kconfig"
+source "drivers/staging/wusbcore/Kconfig"
+source "drivers/staging/uwb/Kconfig"
+
source "drivers/staging/exfat/Kconfig"
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index ca13f87b1e1b..4b5962006c4b 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -49,4 +49,6 @@ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/
obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/
obj-$(CONFIG_KPC2000) += kpc2000/
obj-$(CONFIG_ISDN_CAPI) += isdn/
+obj-$(CONFIG_UWB) += uwb/
+obj-$(CONFIG_USB_WUSB) += wusbcore/
obj-$(CONFIG_EXFAT_FS) += exfat/
diff --git a/drivers/staging/android/vsoc.c b/drivers/staging/android/vsoc.c
index 00a1ec7b9154..1240bb0317d9 100644
--- a/drivers/staging/android/vsoc.c
+++ b/drivers/staging/android/vsoc.c
@@ -437,12 +437,10 @@ static int handle_vsoc_cond_wait(struct file *filp, struct vsoc_cond_wait *arg)
return -EINVAL;
wake_time = ktime_set(arg->wake_time_sec, arg->wake_time_nsec);
- hrtimer_init_on_stack(&to->timer, CLOCK_MONOTONIC,
- HRTIMER_MODE_ABS);
+ hrtimer_init_sleeper_on_stack(to, CLOCK_MONOTONIC,
+ HRTIMER_MODE_ABS);
hrtimer_set_expires_range_ns(&to->timer, wake_time,
current->timer_slack_ns);
-
- hrtimer_init_sleeper(to, current);
}
while (1) {
@@ -460,7 +458,7 @@ static int handle_vsoc_cond_wait(struct file *filp, struct vsoc_cond_wait *arg)
break;
}
if (to) {
- hrtimer_start_expires(&to->timer, HRTIMER_MODE_ABS);
+ hrtimer_sleeper_start_expires(to, HRTIMER_MODE_ABS);
if (likely(to->task))
freezable_schedule();
hrtimer_cancel(&to->timer);
diff --git a/drivers/staging/isdn/hysdn/Kconfig b/drivers/staging/isdn/hysdn/Kconfig
index 1971ef850c9a..4c8a9283b9dd 100644
--- a/drivers/staging/isdn/hysdn/Kconfig
+++ b/drivers/staging/isdn/hysdn/Kconfig
@@ -5,7 +5,7 @@ config HYSDN
help
Say Y here if you have one of Hypercope's active PCI ISDN cards
Champ, Ergo and Metro. You will then get a module called hysdn.
- Please read the file <file:Documentation/isdn/README.hysdn> for more
+ Please read the file <file:Documentation/isdn/hysdn.rst> for more
information.
config HYSDN_CAPI
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig
index 534d85d6c5e3..642adc4c24d2 100644
--- a/drivers/staging/media/Kconfig
+++ b/drivers/staging/media/Kconfig
@@ -22,10 +22,6 @@ if STAGING_MEDIA && MEDIA_SUPPORT
# Please keep them in alphabetic order
source "drivers/staging/media/allegro-dvt/Kconfig"
-source "drivers/staging/media/bcm2048/Kconfig"
-
-source "drivers/staging/media/davinci_vpfe/Kconfig"
-
source "drivers/staging/media/hantro/Kconfig"
source "drivers/staging/media/imx/Kconfig"
diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
index c486298194da..2f1711a8aeed 100644
--- a/drivers/staging/media/Makefile
+++ b/drivers/staging/media/Makefile
@@ -1,8 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VIDEO_ALLEGRO_DVT) += allegro-dvt/
-obj-$(CONFIG_I2C_BCM2048) += bcm2048/
obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/
-obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/
obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/
obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/
obj-$(CONFIG_VIDEO_SUNXI) += sunxi/
diff --git a/drivers/staging/media/bcm2048/Kconfig b/drivers/staging/media/bcm2048/Kconfig
deleted file mode 100644
index ab2d50cac140..000000000000
--- a/drivers/staging/media/bcm2048/Kconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-#
-# Multimedia Video device configuration
-#
-
-config I2C_BCM2048
- tristate "Broadcom BCM2048 FM Radio Receiver support"
- depends on I2C && VIDEO_V4L2 && RADIO_ADAPTERS
- help
- Say Y here if you want support to BCM2048 FM Radio Receiver.
- This device driver supports only i2c bus.
-
- To compile this driver as a module, choose M here: the
- module will be called radio-bcm2048.
diff --git a/drivers/staging/media/bcm2048/Makefile b/drivers/staging/media/bcm2048/Makefile
deleted file mode 100644
index f42056848dc6..000000000000
--- a/drivers/staging/media/bcm2048/Makefile
+++ /dev/null
@@ -1,2 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_I2C_BCM2048) += radio-bcm2048.o
diff --git a/drivers/staging/media/bcm2048/TODO b/drivers/staging/media/bcm2048/TODO
deleted file mode 100644
index 6bee2a2dad68..000000000000
--- a/drivers/staging/media/bcm2048/TODO
+++ /dev/null
@@ -1,24 +0,0 @@
-TODO:
-
-From the initial code review:
-
-The main thing you need to do is to implement all the controls using the
-control framework (see Documentation/media/kapi/v4l2-controls.rst).
-Most drivers are by now converted to the control framework, so you will
-find many examples of how to do this in drivers/media/radio.
-
-The sysfs stuff should be replaced by controls as well. A lot of the RDS
-support is now available as controls (although there may well be some
-missing features, but that is easy enough to add). Since the RDS data is
-actually read() from the device I am not sure whether the RDS
-properties/controls should be there at all.
-
-Correct Coding Style, as this driver also violates several Style
-rules, and do evil tricks, like returning from a function inside a
-macro.
-
-Finally this driver should probably be split up into two parts: one
-v4l2_subdev-based core driver and one platform driver. See e.g.
-radio-si4713/si4713-i2c.c as a good example. But I would wait with that
-until the rest of the driver is cleaned up. Then I have a better idea of
-whether this is necessary or not.
diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.c b/drivers/staging/media/bcm2048/radio-bcm2048.c
deleted file mode 100644
index 2c60a1fb6350..000000000000
--- a/drivers/staging/media/bcm2048/radio-bcm2048.c
+++ /dev/null
@@ -1,2689 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * drivers/staging/media/radio-bcm2048.c
- *
- * Driver for I2C Broadcom BCM2048 FM Radio Receiver:
- *
- * Copyright (C) Nokia Corporation
- * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com>
- *
- * Copyright (C) Nils Faerber <nils.faerber@kernelconcepts.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- */
-
-/*
- * History:
- * Eero Nurkkala <ext-eero.nurkkala@nokia.com>
- * Version 0.0.1
- * - Initial implementation
- * 2010-02-21 Nils Faerber <nils.faerber@kernelconcepts.de>
- * Version 0.0.2
- * - Add support for interrupt driven rds data reading
- */
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/version.h>
-#include <linux/interrupt.h>
-#include <linux/sysfs.h>
-#include <linux/completion.h>
-#include <linux/delay.h>
-#include <linux/i2c.h>
-#include <linux/videodev2.h>
-#include <linux/mutex.h>
-#include <linux/slab.h>
-#include <media/v4l2-common.h>
-#include <media/v4l2-ioctl.h>
-#include "radio-bcm2048.h"
-
-/* driver definitions */
-#define BCM2048_DRIVER_AUTHOR "Eero Nurkkala <ext-eero.nurkkala@nokia.com>"
-#define BCM2048_DRIVER_NAME BCM2048_NAME
-#define BCM2048_DRIVER_CARD "Broadcom bcm2048 FM Radio Receiver"
-#define BCM2048_DRIVER_DESC "I2C driver for BCM2048 FM Radio Receiver"
-
-/* I2C Control Registers */
-#define BCM2048_I2C_FM_RDS_SYSTEM 0x00
-#define BCM2048_I2C_FM_CTRL 0x01
-#define BCM2048_I2C_RDS_CTRL0 0x02
-#define BCM2048_I2C_RDS_CTRL1 0x03
-#define BCM2048_I2C_FM_AUDIO_PAUSE 0x04
-#define BCM2048_I2C_FM_AUDIO_CTRL0 0x05
-#define BCM2048_I2C_FM_AUDIO_CTRL1 0x06
-#define BCM2048_I2C_FM_SEARCH_CTRL0 0x07
-#define BCM2048_I2C_FM_SEARCH_CTRL1 0x08
-#define BCM2048_I2C_FM_SEARCH_TUNE_MODE 0x09
-#define BCM2048_I2C_FM_FREQ0 0x0a
-#define BCM2048_I2C_FM_FREQ1 0x0b
-#define BCM2048_I2C_FM_AF_FREQ0 0x0c
-#define BCM2048_I2C_FM_AF_FREQ1 0x0d
-#define BCM2048_I2C_FM_CARRIER 0x0e
-#define BCM2048_I2C_FM_RSSI 0x0f
-#define BCM2048_I2C_FM_RDS_MASK0 0x10
-#define BCM2048_I2C_FM_RDS_MASK1 0x11
-#define BCM2048_I2C_FM_RDS_FLAG0 0x12
-#define BCM2048_I2C_FM_RDS_FLAG1 0x13
-#define BCM2048_I2C_RDS_WLINE 0x14
-#define BCM2048_I2C_RDS_BLKB_MATCH0 0x16
-#define BCM2048_I2C_RDS_BLKB_MATCH1 0x17
-#define BCM2048_I2C_RDS_BLKB_MASK0 0x18
-#define BCM2048_I2C_RDS_BLKB_MASK1 0x19
-#define BCM2048_I2C_RDS_PI_MATCH0 0x1a
-#define BCM2048_I2C_RDS_PI_MATCH1 0x1b
-#define BCM2048_I2C_RDS_PI_MASK0 0x1c
-#define BCM2048_I2C_RDS_PI_MASK1 0x1d
-#define BCM2048_I2C_SPARE1 0x20
-#define BCM2048_I2C_SPARE2 0x21
-#define BCM2048_I2C_FM_RDS_REV 0x28
-#define BCM2048_I2C_SLAVE_CONFIGURATION 0x29
-#define BCM2048_I2C_RDS_DATA 0x80
-#define BCM2048_I2C_FM_BEST_TUNE_MODE 0x90
-
-/* BCM2048_I2C_FM_RDS_SYSTEM */
-#define BCM2048_FM_ON 0x01
-#define BCM2048_RDS_ON 0x02
-
-/* BCM2048_I2C_FM_CTRL */
-#define BCM2048_BAND_SELECT 0x01
-#define BCM2048_STEREO_MONO_AUTO_SELECT 0x02
-#define BCM2048_STEREO_MONO_MANUAL_SELECT 0x04
-#define BCM2048_STEREO_MONO_BLEND_SWITCH 0x08
-#define BCM2048_HI_LO_INJECTION 0x10
-
-/* BCM2048_I2C_RDS_CTRL0 */
-#define BCM2048_RBDS_RDS_SELECT 0x01
-#define BCM2048_FLUSH_FIFO 0x02
-
-/* BCM2048_I2C_FM_AUDIO_PAUSE */
-#define BCM2048_AUDIO_PAUSE_RSSI_TRESH 0x0f
-#define BCM2048_AUDIO_PAUSE_DURATION 0xf0
-
-/* BCM2048_I2C_FM_AUDIO_CTRL0 */
-#define BCM2048_RF_MUTE 0x01
-#define BCM2048_MANUAL_MUTE 0x02
-#define BCM2048_DAC_OUTPUT_LEFT 0x04
-#define BCM2048_DAC_OUTPUT_RIGHT 0x08
-#define BCM2048_AUDIO_ROUTE_DAC 0x10
-#define BCM2048_AUDIO_ROUTE_I2S 0x20
-#define BCM2048_DE_EMPHASIS_SELECT 0x40
-#define BCM2048_AUDIO_BANDWIDTH_SELECT 0x80
-
-/* BCM2048_I2C_FM_SEARCH_CTRL0 */
-#define BCM2048_SEARCH_RSSI_THRESHOLD 0x7f
-#define BCM2048_SEARCH_DIRECTION 0x80
-
-/* BCM2048_I2C_FM_SEARCH_TUNE_MODE */
-#define BCM2048_FM_AUTO_SEARCH 0x03
-
-/* BCM2048_I2C_FM_RSSI */
-#define BCM2048_RSSI_VALUE 0xff
-
-/* BCM2048_I2C_FM_RDS_MASK0 */
-/* BCM2048_I2C_FM_RDS_MASK1 */
-#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED 0x01
-#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL 0x02
-#define BCM2048_FM_FLAG_RSSI_LOW 0x04
-#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH 0x08
-#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION 0x10
-#define BCM2048_FLAG_STEREO_DETECTED 0x20
-#define BCM2048_FLAG_STEREO_ACTIVE 0x40
-
-/* BCM2048_I2C_RDS_DATA */
-#define BCM2048_SLAVE_ADDRESS 0x3f
-#define BCM2048_SLAVE_ENABLE 0x80
-
-/* BCM2048_I2C_FM_BEST_TUNE_MODE */
-#define BCM2048_BEST_TUNE_MODE 0x80
-
-#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED 0x01
-#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL 0x02
-#define BCM2048_FM_FLAG_RSSI_LOW 0x04
-#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH 0x08
-#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION 0x10
-#define BCM2048_FLAG_STEREO_DETECTED 0x20
-#define BCM2048_FLAG_STEREO_ACTIVE 0x40
-
-#define BCM2048_RDS_FLAG_FIFO_WLINE 0x02
-#define BCM2048_RDS_FLAG_B_BLOCK_MATCH 0x08
-#define BCM2048_RDS_FLAG_SYNC_LOST 0x10
-#define BCM2048_RDS_FLAG_PI_MATCH 0x20
-
-#define BCM2048_RDS_MARK_END_BYTE0 0x7C
-#define BCM2048_RDS_MARK_END_BYTEN 0xFF
-
-#define BCM2048_FM_FLAGS_ALL (FM_FLAG_SEARCH_TUNE_FINISHED | \
- FM_FLAG_SEARCH_TUNE_FAIL | \
- FM_FLAG_RSSI_LOW | \
- FM_FLAG_CARRIER_ERROR_HIGH | \
- FM_FLAG_AUDIO_PAUSE_INDICATION | \
- FLAG_STEREO_DETECTED | FLAG_STEREO_ACTIVE)
-
-#define BCM2048_RDS_FLAGS_ALL (RDS_FLAG_FIFO_WLINE | \
- RDS_FLAG_B_BLOCK_MATCH | \
- RDS_FLAG_SYNC_LOST | RDS_FLAG_PI_MATCH)
-
-#define BCM2048_DEFAULT_TIMEOUT 1500
-#define BCM2048_AUTO_SEARCH_TIMEOUT 3000
-
-#define BCM2048_FREQDEV_UNIT 10000
-#define BCM2048_FREQV4L2_MULTI 625
-#define dev_to_v4l2(f) (((f) * BCM2048_FREQDEV_UNIT) / BCM2048_FREQV4L2_MULTI)
-#define v4l2_to_dev(f) (((f) * BCM2048_FREQV4L2_MULTI) / BCM2048_FREQDEV_UNIT)
-
-#define msb(x) ((u8)((u16)(x) >> 8))
-#define lsb(x) ((u8)((u16)(x) & 0x00FF))
-#define compose_u16(msb, lsb) (((u16)(msb) << 8) | (lsb))
-
-#define BCM2048_DEFAULT_POWERING_DELAY 20
-#define BCM2048_DEFAULT_REGION 0x02
-#define BCM2048_DEFAULT_MUTE 0x01
-#define BCM2048_DEFAULT_RSSI_THRESHOLD 0x64
-#define BCM2048_DEFAULT_RDS_WLINE 0x7E
-
-#define BCM2048_FM_SEARCH_INACTIVE 0x00
-#define BCM2048_FM_PRE_SET_MODE 0x01
-#define BCM2048_FM_AUTO_SEARCH_MODE 0x02
-#define BCM2048_FM_AF_JUMP_MODE 0x03
-
-#define BCM2048_FREQUENCY_BASE 64000
-
-#define BCM2048_POWER_ON 0x01
-#define BCM2048_POWER_OFF 0x00
-
-#define BCM2048_ITEM_ENABLED 0x01
-#define BCM2048_SEARCH_DIRECTION_UP 0x01
-
-#define BCM2048_DE_EMPHASIS_75us 75
-#define BCM2048_DE_EMPHASIS_50us 50
-
-#define BCM2048_SCAN_FAIL 0x00
-#define BCM2048_SCAN_OK 0x01
-
-#define BCM2048_FREQ_ERROR_FLOOR -20
-#define BCM2048_FREQ_ERROR_ROOF 20
-
-/* -60 dB is reported as full signal strength */
-#define BCM2048_RSSI_LEVEL_BASE -60
-#define BCM2048_RSSI_LEVEL_ROOF -100
-#define BCM2048_RSSI_LEVEL_ROOF_NEG 100
-#define BCM2048_SIGNAL_MULTIPLIER (0xFFFF / \
- (BCM2048_RSSI_LEVEL_ROOF_NEG + \
- BCM2048_RSSI_LEVEL_BASE))
-
-#define BCM2048_RDS_FIFO_DUPLE_SIZE 0x03
-#define BCM2048_RDS_CRC_MASK 0x0F
-#define BCM2048_RDS_CRC_NONE 0x00
-#define BCM2048_RDS_CRC_MAX_2BITS 0x04
-#define BCM2048_RDS_CRC_LEAST_2BITS 0x08
-#define BCM2048_RDS_CRC_UNRECOVARABLE 0x0C
-
-#define BCM2048_RDS_BLOCK_MASK 0xF0
-#define BCM2048_RDS_BLOCK_A 0x00
-#define BCM2048_RDS_BLOCK_B 0x10
-#define BCM2048_RDS_BLOCK_C 0x20
-#define BCM2048_RDS_BLOCK_D 0x30
-#define BCM2048_RDS_BLOCK_C_SCORED 0x40
-#define BCM2048_RDS_BLOCK_E 0x60
-
-#define BCM2048_RDS_RT 0x20
-#define BCM2048_RDS_PS 0x00
-
-#define BCM2048_RDS_GROUP_AB_MASK 0x08
-#define BCM2048_RDS_GROUP_A 0x00
-#define BCM2048_RDS_GROUP_B 0x08
-
-#define BCM2048_RDS_RT_AB_MASK 0x10
-#define BCM2048_RDS_RT_A 0x00
-#define BCM2048_RDS_RT_B 0x10
-#define BCM2048_RDS_RT_INDEX 0x0F
-
-#define BCM2048_RDS_PS_INDEX 0x03
-
-struct rds_info {
- u16 rds_pi;
-#define BCM2048_MAX_RDS_RT (64 + 1)
- u8 rds_rt[BCM2048_MAX_RDS_RT];
- u8 rds_rt_group_b;
- u8 rds_rt_ab;
-#define BCM2048_MAX_RDS_PS (8 + 1)
- u8 rds_ps[BCM2048_MAX_RDS_PS];
- u8 rds_ps_group;
- u8 rds_ps_group_cnt;
-#define BCM2048_MAX_RDS_RADIO_TEXT 255
- u8 radio_text[BCM2048_MAX_RDS_RADIO_TEXT + 3];
- u8 text_len;
-};
-
-struct region_info {
- u32 bottom_frequency;
- u32 top_frequency;
- u8 deemphasis;
- u8 channel_spacing;
- u8 region;
-};
-
-struct bcm2048_device {
- struct i2c_client *client;
- struct video_device videodev;
- struct work_struct work;
- struct completion compl;
- struct mutex mutex;
- struct bcm2048_platform_data *platform_data;
- struct rds_info rds_info;
- struct region_info region_info;
- u16 frequency;
- u8 cache_fm_rds_system;
- u8 cache_fm_ctrl;
- u8 cache_fm_audio_ctrl0;
- u8 cache_fm_search_ctrl0;
- u8 power_state;
- u8 rds_state;
- u8 fifo_size;
- u8 scan_state;
- u8 mute_state;
-
- /* for rds data device read */
- wait_queue_head_t read_queue;
- unsigned int users;
- unsigned char rds_data_available;
- unsigned int rd_index;
-};
-
-static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */
-module_param(radio_nr, int, 0000);
-MODULE_PARM_DESC(radio_nr,
- "Minor number for radio device (-1 ==> auto assign)");
-
-static const struct region_info region_configs[] = {
- /* USA */
- {
- .channel_spacing = 20,
- .bottom_frequency = 87500,
- .top_frequency = 108000,
- .deemphasis = 75,
- .region = 0,
- },
- /* Australia */
- {
- .channel_spacing = 20,
- .bottom_frequency = 87500,
- .top_frequency = 108000,
- .deemphasis = 50,
- .region = 1,
- },
- /* Europe */
- {
- .channel_spacing = 10,
- .bottom_frequency = 87500,
- .top_frequency = 108000,
- .deemphasis = 50,
- .region = 2,
- },
- /* Japan */
- {
- .channel_spacing = 10,
- .bottom_frequency = 76000,
- .top_frequency = 90000,
- .deemphasis = 50,
- .region = 3,
- },
-};
-
-/*
- * I2C Interface read / write
- */
-static int bcm2048_send_command(struct bcm2048_device *bdev, unsigned int reg,
- unsigned int value)
-{
- struct i2c_client *client = bdev->client;
- u8 data[2];
-
- if (!bdev->power_state) {
- dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
- return -EIO;
- }
-
- data[0] = reg & 0xff;
- data[1] = value & 0xff;
-
- if (i2c_master_send(client, data, 2) == 2)
- return 0;
-
- dev_err(&bdev->client->dev, "BCM I2C error!\n");
- dev_err(&bdev->client->dev, "Is Bluetooth up and running?\n");
- return -EIO;
-}
-
-static int bcm2048_recv_command(struct bcm2048_device *bdev, unsigned int reg,
- u8 *value)
-{
- struct i2c_client *client = bdev->client;
-
- if (!bdev->power_state) {
- dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
- return -EIO;
- }
-
- value[0] = i2c_smbus_read_byte_data(client, reg & 0xff);
-
- return 0;
-}
-
-static int bcm2048_recv_duples(struct bcm2048_device *bdev, unsigned int reg,
- u8 *value, u8 duples)
-{
- struct i2c_client *client = bdev->client;
- struct i2c_adapter *adap = client->adapter;
- struct i2c_msg msg[2];
- u8 buf;
-
- if (!bdev->power_state) {
- dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
- return -EIO;
- }
-
- buf = reg & 0xff;
-
- msg[0].addr = client->addr;
- msg[0].flags = client->flags & I2C_M_TEN;
- msg[0].len = 1;
- msg[0].buf = &buf;
-
- msg[1].addr = client->addr;
- msg[1].flags = client->flags & I2C_M_TEN;
- msg[1].flags |= I2C_M_RD;
- msg[1].len = duples;
- msg[1].buf = value;
-
- return i2c_transfer(adap, msg, 2);
-}
-
-/*
- * BCM2048 - I2C register programming helpers
- */
-static int bcm2048_set_power_state(struct bcm2048_device *bdev, u8 power)
-{
- int err = 0;
-
- mutex_lock(&bdev->mutex);
-
- if (power) {
- bdev->power_state = BCM2048_POWER_ON;
- bdev->cache_fm_rds_system |= BCM2048_FM_ON;
- } else {
- bdev->cache_fm_rds_system &= ~BCM2048_FM_ON;
- }
-
- /*
- * Warning! FM cannot be turned off because then
- * the I2C communications get ruined!
- * Comment off the "if (power)" when the chip works!
- */
- if (power)
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM,
- bdev->cache_fm_rds_system);
- msleep(BCM2048_DEFAULT_POWERING_DELAY);
-
- if (!power)
- bdev->power_state = BCM2048_POWER_OFF;
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_power_state(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err && (value & BCM2048_FM_ON))
- return BCM2048_POWER_ON;
-
- return err;
-}
-
-static int bcm2048_set_rds_no_lock(struct bcm2048_device *bdev, u8 rds_on)
-{
- int err;
- u8 flags;
-
- bdev->cache_fm_rds_system &= ~BCM2048_RDS_ON;
-
- if (rds_on) {
- bdev->cache_fm_rds_system |= BCM2048_RDS_ON;
- bdev->rds_state = BCM2048_RDS_ON;
- flags = BCM2048_RDS_FLAG_FIFO_WLINE;
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
- flags);
- } else {
- flags = 0;
- bdev->rds_state = 0;
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
- flags);
- memset(&bdev->rds_info, 0, sizeof(bdev->rds_info));
- }
- if (err)
- return err;
-
- return bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM,
- bdev->cache_fm_rds_system);
-}
-
-static int bcm2048_get_rds_no_lock(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value);
-
- if (!err && (value & BCM2048_RDS_ON))
- return BCM2048_ITEM_ENABLED;
-
- return err;
-}
-
-static int bcm2048_set_rds(struct bcm2048_device *bdev, u8 rds_on)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_set_rds_no_lock(bdev, rds_on);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_rds(struct bcm2048_device *bdev)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_get_rds_no_lock(bdev);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_rds_pi(struct bcm2048_device *bdev)
-{
- return bdev->rds_info.rds_pi;
-}
-
-static int bcm2048_set_fm_automatic_stereo_mono(struct bcm2048_device *bdev,
- u8 enabled)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- bdev->cache_fm_ctrl &= ~BCM2048_STEREO_MONO_AUTO_SELECT;
-
- if (enabled)
- bdev->cache_fm_ctrl |= BCM2048_STEREO_MONO_AUTO_SELECT;
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL,
- bdev->cache_fm_ctrl);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_set_fm_hi_lo_injection(struct bcm2048_device *bdev,
- u8 hi_lo)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- bdev->cache_fm_ctrl &= ~BCM2048_HI_LO_INJECTION;
-
- if (hi_lo)
- bdev->cache_fm_ctrl |= BCM2048_HI_LO_INJECTION;
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL,
- bdev->cache_fm_ctrl);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_fm_hi_lo_injection(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CTRL, &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err && (value & BCM2048_HI_LO_INJECTION))
- return BCM2048_ITEM_ENABLED;
-
- return err;
-}
-
-static int bcm2048_set_fm_frequency(struct bcm2048_device *bdev, u32 frequency)
-{
- int err;
-
- if (frequency < bdev->region_info.bottom_frequency ||
- frequency > bdev->region_info.top_frequency)
- return -EDOM;
-
- frequency -= BCM2048_FREQUENCY_BASE;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ0, lsb(frequency));
- err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ1,
- msb(frequency));
-
- if (!err)
- bdev->frequency = frequency;
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_fm_frequency(struct bcm2048_device *bdev)
-{
- int err;
- u8 lsb = 0, msb = 0;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ0, &lsb);
- err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ1, &msb);
-
- mutex_unlock(&bdev->mutex);
-
- if (err)
- return err;
-
- err = compose_u16(msb, lsb);
- err += BCM2048_FREQUENCY_BASE;
-
- return err;
-}
-
-static int bcm2048_set_fm_af_frequency(struct bcm2048_device *bdev,
- u32 frequency)
-{
- int err;
-
- if (frequency < bdev->region_info.bottom_frequency ||
- frequency > bdev->region_info.top_frequency)
- return -EDOM;
-
- frequency -= BCM2048_FREQUENCY_BASE;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ0,
- lsb(frequency));
- err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ1,
- msb(frequency));
- if (!err)
- bdev->frequency = frequency;
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_fm_af_frequency(struct bcm2048_device *bdev)
-{
- int err;
- u8 lsb = 0, msb = 0;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ0, &lsb);
- err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ1, &msb);
-
- mutex_unlock(&bdev->mutex);
-
- if (err)
- return err;
-
- err = compose_u16(msb, lsb);
- err += BCM2048_FREQUENCY_BASE;
-
- return err;
-}
-
-static int bcm2048_set_fm_deemphasis(struct bcm2048_device *bdev, int d)
-{
- int err;
- u8 deemphasis;
-
- if (d == BCM2048_DE_EMPHASIS_75us)
- deemphasis = BCM2048_DE_EMPHASIS_SELECT;
- else
- deemphasis = 0;
-
- mutex_lock(&bdev->mutex);
-
- bdev->cache_fm_audio_ctrl0 &= ~BCM2048_DE_EMPHASIS_SELECT;
- bdev->cache_fm_audio_ctrl0 |= deemphasis;
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
- bdev->cache_fm_audio_ctrl0);
-
- if (!err)
- bdev->region_info.deemphasis = d;
-
- mutex_unlock(&bdev->mutex);
-
- return err;
-}
-
-static int bcm2048_get_fm_deemphasis(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err) {
- if (value & BCM2048_DE_EMPHASIS_SELECT)
- return BCM2048_DE_EMPHASIS_75us;
-
- return BCM2048_DE_EMPHASIS_50us;
- }
-
- return err;
-}
-
-static int bcm2048_set_region(struct bcm2048_device *bdev, u8 region)
-{
- int err;
- u32 new_frequency = 0;
-
- if (region >= ARRAY_SIZE(region_configs))
- return -EINVAL;
-
- mutex_lock(&bdev->mutex);
- bdev->region_info = region_configs[region];
-
- if (region_configs[region].bottom_frequency < 87500)
- bdev->cache_fm_ctrl |= BCM2048_BAND_SELECT;
- else
- bdev->cache_fm_ctrl &= ~BCM2048_BAND_SELECT;
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL,
- bdev->cache_fm_ctrl);
- if (err) {
- mutex_unlock(&bdev->mutex);
- goto done;
- }
- mutex_unlock(&bdev->mutex);
-
- if (bdev->frequency < region_configs[region].bottom_frequency ||
- bdev->frequency > region_configs[region].top_frequency)
- new_frequency = region_configs[region].bottom_frequency;
-
- if (new_frequency > 0) {
- err = bcm2048_set_fm_frequency(bdev, new_frequency);
-
- if (err)
- goto done;
- }
-
- err = bcm2048_set_fm_deemphasis(bdev,
- region_configs[region].deemphasis);
-
-done:
- return err;
-}
-
-static int bcm2048_get_region(struct bcm2048_device *bdev)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
- err = bdev->region_info.region;
- mutex_unlock(&bdev->mutex);
-
- return err;
-}
-
-static int bcm2048_set_mute(struct bcm2048_device *bdev, u16 mute)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE);
-
- if (mute)
- bdev->cache_fm_audio_ctrl0 |= (BCM2048_RF_MUTE |
- BCM2048_MANUAL_MUTE);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
- bdev->cache_fm_audio_ctrl0);
-
- if (!err)
- bdev->mute_state = mute;
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_mute(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- if (bdev->power_state) {
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
- &value);
- if (!err)
- err = value & (BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE);
- } else {
- err = bdev->mute_state;
- }
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_set_audio_route(struct bcm2048_device *bdev, u8 route)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- route &= (BCM2048_AUDIO_ROUTE_DAC | BCM2048_AUDIO_ROUTE_I2S);
- bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_AUDIO_ROUTE_DAC |
- BCM2048_AUDIO_ROUTE_I2S);
- bdev->cache_fm_audio_ctrl0 |= route;
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
- bdev->cache_fm_audio_ctrl0);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_audio_route(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return value & (BCM2048_AUDIO_ROUTE_DAC |
- BCM2048_AUDIO_ROUTE_I2S);
-
- return err;
-}
-
-static int bcm2048_set_dac_output(struct bcm2048_device *bdev, u8 channels)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_DAC_OUTPUT_LEFT |
- BCM2048_DAC_OUTPUT_RIGHT);
- bdev->cache_fm_audio_ctrl0 |= channels;
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
- bdev->cache_fm_audio_ctrl0);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_dac_output(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return value & (BCM2048_DAC_OUTPUT_LEFT |
- BCM2048_DAC_OUTPUT_RIGHT);
-
- return err;
-}
-
-static int bcm2048_set_fm_search_rssi_threshold(struct bcm2048_device *bdev,
- u8 threshold)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- threshold &= BCM2048_SEARCH_RSSI_THRESHOLD;
- bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_RSSI_THRESHOLD;
- bdev->cache_fm_search_ctrl0 |= threshold;
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0,
- bdev->cache_fm_search_ctrl0);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_fm_search_rssi_threshold(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return value & BCM2048_SEARCH_RSSI_THRESHOLD;
-
- return err;
-}
-
-static int bcm2048_set_fm_search_mode_direction(struct bcm2048_device *bdev,
- u8 direction)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_DIRECTION;
-
- if (direction)
- bdev->cache_fm_search_ctrl0 |= BCM2048_SEARCH_DIRECTION;
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0,
- bdev->cache_fm_search_ctrl0);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_fm_search_mode_direction(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err && (value & BCM2048_SEARCH_DIRECTION))
- return BCM2048_SEARCH_DIRECTION_UP;
-
- return err;
-}
-
-static int bcm2048_set_fm_search_tune_mode(struct bcm2048_device *bdev,
- u8 mode)
-{
- int err, timeout, restart_rds = 0;
- u8 value, flags;
-
- value = mode & BCM2048_FM_AUTO_SEARCH;
-
- flags = BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED |
- BCM2048_FM_FLAG_SEARCH_TUNE_FAIL;
-
- mutex_lock(&bdev->mutex);
-
- /*
- * If RDS is enabled, and frequency is changed, RDS quits working.
- * Thus, always restart RDS if it's enabled. Moreover, RDS must
- * not be enabled while changing the frequency because it can
- * provide a race to the mutex from the workqueue handler if RDS
- * IRQ occurs while waiting for frequency changed IRQ.
- */
- if (bcm2048_get_rds_no_lock(bdev)) {
- err = bcm2048_set_rds_no_lock(bdev, 0);
- if (err)
- goto unlock;
- restart_rds = 1;
- }
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK0, flags);
-
- if (err)
- goto unlock;
-
- bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE, value);
-
- if (mode != BCM2048_FM_AUTO_SEARCH_MODE)
- timeout = BCM2048_DEFAULT_TIMEOUT;
- else
- timeout = BCM2048_AUTO_SEARCH_TIMEOUT;
-
- if (!wait_for_completion_timeout(&bdev->compl,
- msecs_to_jiffies(timeout)))
- dev_err(&bdev->client->dev, "IRQ timeout.\n");
-
- if (value)
- if (!bdev->scan_state)
- err = -EIO;
-
-unlock:
- if (restart_rds)
- err |= bcm2048_set_rds_no_lock(bdev, 1);
-
- mutex_unlock(&bdev->mutex);
-
- return err;
-}
-
-static int bcm2048_get_fm_search_tune_mode(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE,
- &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return value & BCM2048_FM_AUTO_SEARCH;
-
- return err;
-}
-
-static int bcm2048_set_rds_b_block_mask(struct bcm2048_device *bdev, u16 mask)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_BLKB_MASK0,
- lsb(mask));
- err |= bcm2048_send_command(bdev, BCM2048_I2C_RDS_BLKB_MASK1,
- msb(mask));
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_rds_b_block_mask(struct bcm2048_device *bdev)
-{
- int err;
- u8 lsb = 0, msb = 0;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_BLKB_MASK0, &lsb);
- err |= bcm2048_recv_command(bdev, BCM2048_I2C_RDS_BLKB_MASK1, &msb);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return compose_u16(msb, lsb);
-
- return err;
-}
-
-static int bcm2048_set_rds_b_block_match(struct bcm2048_device *bdev,
- u16 match)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_BLKB_MATCH0,
- lsb(match));
- err |= bcm2048_send_command(bdev, BCM2048_I2C_RDS_BLKB_MATCH1,
- msb(match));
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_rds_b_block_match(struct bcm2048_device *bdev)
-{
- int err;
- u8 lsb = 0, msb = 0;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_BLKB_MATCH0, &lsb);
- err |= bcm2048_recv_command(bdev, BCM2048_I2C_RDS_BLKB_MATCH1, &msb);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return compose_u16(msb, lsb);
-
- return err;
-}
-
-static int bcm2048_set_rds_pi_mask(struct bcm2048_device *bdev, u16 mask)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_PI_MASK0, lsb(mask));
- err |= bcm2048_send_command(bdev, BCM2048_I2C_RDS_PI_MASK1, msb(mask));
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_rds_pi_mask(struct bcm2048_device *bdev)
-{
- int err;
- u8 lsb = 0, msb = 0;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_PI_MASK0, &lsb);
- err |= bcm2048_recv_command(bdev, BCM2048_I2C_RDS_PI_MASK1, &msb);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return compose_u16(msb, lsb);
-
- return err;
-}
-
-static int bcm2048_set_rds_pi_match(struct bcm2048_device *bdev, u16 match)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_PI_MATCH0,
- lsb(match));
- err |= bcm2048_send_command(bdev, BCM2048_I2C_RDS_PI_MATCH1,
- msb(match));
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_rds_pi_match(struct bcm2048_device *bdev)
-{
- int err;
- u8 lsb = 0, msb = 0;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_PI_MATCH0, &lsb);
- err |= bcm2048_recv_command(bdev, BCM2048_I2C_RDS_PI_MATCH1, &msb);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return compose_u16(msb, lsb);
-
- return err;
-}
-
-static int bcm2048_set_fm_rds_mask(struct bcm2048_device *bdev, u16 mask)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK0, lsb(mask));
- err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1, msb(mask));
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_fm_rds_mask(struct bcm2048_device *bdev)
-{
- int err;
- u8 value0 = 0, value1 = 0;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK0, &value0);
- err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK1, &value1);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return compose_u16(value1, value0);
-
- return err;
-}
-
-static int bcm2048_get_fm_rds_flags(struct bcm2048_device *bdev)
-{
- int err;
- u8 value0 = 0, value1 = 0;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &value0);
- err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &value1);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return compose_u16(value1, value0);
-
- return err;
-}
-
-static int bcm2048_get_region_bottom_frequency(struct bcm2048_device *bdev)
-{
- return bdev->region_info.bottom_frequency;
-}
-
-static int bcm2048_get_region_top_frequency(struct bcm2048_device *bdev)
-{
- return bdev->region_info.top_frequency;
-}
-
-static int bcm2048_set_fm_best_tune_mode(struct bcm2048_device *bdev, u8 mode)
-{
- int err;
- u8 value = 0;
-
- mutex_lock(&bdev->mutex);
-
- /* Perform read as the manual indicates */
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
- &value);
- value &= ~BCM2048_BEST_TUNE_MODE;
-
- if (mode)
- value |= BCM2048_BEST_TUNE_MODE;
- err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
- value);
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_fm_best_tune_mode(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
- &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err && (value & BCM2048_BEST_TUNE_MODE))
- return BCM2048_ITEM_ENABLED;
-
- return err;
-}
-
-static int bcm2048_get_fm_carrier_error(struct bcm2048_device *bdev)
-{
- int err = 0;
- s8 value;
-
- mutex_lock(&bdev->mutex);
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CARRIER, &value);
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return value;
-
- return err;
-}
-
-static int bcm2048_get_fm_rssi(struct bcm2048_device *bdev)
-{
- int err;
- s8 value;
-
- mutex_lock(&bdev->mutex);
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RSSI, &value);
- mutex_unlock(&bdev->mutex);
-
- if (!err)
- return value;
-
- return err;
-}
-
-static int bcm2048_set_rds_wline(struct bcm2048_device *bdev, u8 wline)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_WLINE, wline);
-
- if (!err)
- bdev->fifo_size = wline;
-
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_rds_wline(struct bcm2048_device *bdev)
-{
- int err;
- u8 value;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_WLINE, &value);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err) {
- bdev->fifo_size = value;
- return value;
- }
-
- return err;
-}
-
-static int bcm2048_checkrev(struct bcm2048_device *bdev)
-{
- int err;
- u8 version;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_REV, &version);
-
- mutex_unlock(&bdev->mutex);
-
- if (!err) {
- dev_info(&bdev->client->dev, "BCM2048 Version 0x%x\n",
- version);
- return version;
- }
-
- return err;
-}
-
-static int bcm2048_get_rds_rt(struct bcm2048_device *bdev, char *data)
-{
- int err = 0, i, j = 0, ce = 0, cr = 0;
- char data_buffer[BCM2048_MAX_RDS_RT + 1];
-
- mutex_lock(&bdev->mutex);
-
- if (!bdev->rds_info.text_len) {
- err = -EINVAL;
- goto unlock;
- }
-
- for (i = 0; i < BCM2048_MAX_RDS_RT; i++) {
- if (bdev->rds_info.rds_rt[i]) {
- ce = i;
- /* Skip the carriage return */
- if (bdev->rds_info.rds_rt[i] != 0x0d) {
- data_buffer[j++] = bdev->rds_info.rds_rt[i];
- } else {
- cr = i;
- break;
- }
- }
- }
-
- if (j <= BCM2048_MAX_RDS_RT)
- data_buffer[j] = 0;
-
- for (i = 0; i < BCM2048_MAX_RDS_RT; i++) {
- if (!bdev->rds_info.rds_rt[i]) {
- if (cr && (i < cr)) {
- err = -EBUSY;
- goto unlock;
- }
- if (i < ce) {
- if (cr && (i >= cr))
- break;
- err = -EBUSY;
- goto unlock;
- }
- }
- }
-
- memcpy(data, data_buffer, sizeof(data_buffer));
-
-unlock:
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static int bcm2048_get_rds_ps(struct bcm2048_device *bdev, char *data)
-{
- int err = 0, i, j = 0;
- char data_buffer[BCM2048_MAX_RDS_PS + 1];
-
- mutex_lock(&bdev->mutex);
-
- if (!bdev->rds_info.text_len) {
- err = -EINVAL;
- goto unlock;
- }
-
- for (i = 0; i < BCM2048_MAX_RDS_PS; i++) {
- if (bdev->rds_info.rds_ps[i]) {
- data_buffer[j++] = bdev->rds_info.rds_ps[i];
- } else {
- if (i < (BCM2048_MAX_RDS_PS - 1)) {
- err = -EBUSY;
- goto unlock;
- }
- }
- }
-
- if (j <= BCM2048_MAX_RDS_PS)
- data_buffer[j] = 0;
-
- memcpy(data, data_buffer, sizeof(data_buffer));
-
-unlock:
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-static void bcm2048_parse_rds_pi(struct bcm2048_device *bdev)
-{
- int i, cnt = 0;
- u16 pi;
-
- for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
- /* Block A match, only data without crc errors taken */
- if (bdev->rds_info.radio_text[i] == BCM2048_RDS_BLOCK_A) {
- pi = (bdev->rds_info.radio_text[i + 1] << 8) +
- bdev->rds_info.radio_text[i + 2];
-
- if (!bdev->rds_info.rds_pi) {
- bdev->rds_info.rds_pi = pi;
- return;
- }
- if (pi != bdev->rds_info.rds_pi) {
- cnt++;
- if (cnt > 3) {
- bdev->rds_info.rds_pi = pi;
- cnt = 0;
- }
- } else {
- cnt = 0;
- }
- }
- }
-}
-
-static int bcm2048_rds_block_crc(struct bcm2048_device *bdev, int i)
-{
- return bdev->rds_info.radio_text[i] & BCM2048_RDS_CRC_MASK;
-}
-
-static void bcm2048_parse_rds_rt_block(struct bcm2048_device *bdev, int i,
- int index, int crc)
-{
- /* Good data will overwrite poor data */
- if (crc) {
- if (!bdev->rds_info.rds_rt[index])
- bdev->rds_info.rds_rt[index] =
- bdev->rds_info.radio_text[i + 1];
- if (!bdev->rds_info.rds_rt[index + 1])
- bdev->rds_info.rds_rt[index + 1] =
- bdev->rds_info.radio_text[i + 2];
- } else {
- bdev->rds_info.rds_rt[index] =
- bdev->rds_info.radio_text[i + 1];
- bdev->rds_info.rds_rt[index + 1] =
- bdev->rds_info.radio_text[i + 2];
- }
-}
-
-static int bcm2048_parse_rt_match_b(struct bcm2048_device *bdev, int i)
-{
- int crc, rt_id, rt_group_b, rt_ab, index = 0;
-
- crc = bcm2048_rds_block_crc(bdev, i);
-
- if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
- return -EIO;
-
- if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
- BCM2048_RDS_BLOCK_B) {
- rt_id = bdev->rds_info.radio_text[i + 1] &
- BCM2048_RDS_BLOCK_MASK;
- rt_group_b = bdev->rds_info.radio_text[i + 1] &
- BCM2048_RDS_GROUP_AB_MASK;
- rt_ab = bdev->rds_info.radio_text[i + 2] &
- BCM2048_RDS_RT_AB_MASK;
-
- if (rt_group_b != bdev->rds_info.rds_rt_group_b) {
- memset(bdev->rds_info.rds_rt, 0,
- sizeof(bdev->rds_info.rds_rt));
- bdev->rds_info.rds_rt_group_b = rt_group_b;
- }
-
- if (rt_id == BCM2048_RDS_RT) {
- /* A to B or (vice versa), means: clear screen */
- if (rt_ab != bdev->rds_info.rds_rt_ab) {
- memset(bdev->rds_info.rds_rt, 0,
- sizeof(bdev->rds_info.rds_rt));
- bdev->rds_info.rds_rt_ab = rt_ab;
- }
-
- index = bdev->rds_info.radio_text[i + 2] &
- BCM2048_RDS_RT_INDEX;
-
- if (bdev->rds_info.rds_rt_group_b)
- index <<= 1;
- else
- index <<= 2;
-
- return index;
- }
- }
-
- return -EIO;
-}
-
-static int bcm2048_parse_rt_match_c(struct bcm2048_device *bdev, int i,
- int index)
-{
- int crc;
-
- crc = bcm2048_rds_block_crc(bdev, i);
-
- if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
- return 0;
-
- if ((index + 2) >= BCM2048_MAX_RDS_RT) {
- dev_err(&bdev->client->dev,
- "Incorrect index = %d\n", index);
- return 0;
- }
-
- if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
- BCM2048_RDS_BLOCK_C) {
- if (bdev->rds_info.rds_rt_group_b)
- return 1;
- bcm2048_parse_rds_rt_block(bdev, i, index, crc);
- return 1;
- }
-
- return 0;
-}
-
-static void bcm2048_parse_rt_match_d(struct bcm2048_device *bdev, int i,
- int index)
-{
- int crc;
-
- crc = bcm2048_rds_block_crc(bdev, i);
-
- if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
- return;
-
- if ((index + 4) >= BCM2048_MAX_RDS_RT) {
- dev_err(&bdev->client->dev,
- "Incorrect index = %d\n", index);
- return;
- }
-
- if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
- BCM2048_RDS_BLOCK_D)
- bcm2048_parse_rds_rt_block(bdev, i, index + 2, crc);
-}
-
-static void bcm2048_parse_rds_rt(struct bcm2048_device *bdev)
-{
- int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0;
-
- for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
- if (match_b) {
- match_b = 0;
- index = bcm2048_parse_rt_match_b(bdev, i);
- if (index >= 0 && index <= (BCM2048_MAX_RDS_RT - 5))
- match_c = 1;
- continue;
- } else if (match_c) {
- match_c = 0;
- if (bcm2048_parse_rt_match_c(bdev, i, index))
- match_d = 1;
- continue;
- } else if (match_d) {
- match_d = 0;
- bcm2048_parse_rt_match_d(bdev, i, index);
- continue;
- }
-
- /* Skip erroneous blocks due to messed up A block altogether */
- if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
- BCM2048_RDS_BLOCK_A) {
- crc = bcm2048_rds_block_crc(bdev, i);
- if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
- continue;
- /* Synchronize to a good RDS PI */
- if (((bdev->rds_info.radio_text[i + 1] << 8) +
- bdev->rds_info.radio_text[i + 2]) ==
- bdev->rds_info.rds_pi)
- match_b = 1;
- }
- }
-}
-
-static void bcm2048_parse_rds_ps_block(struct bcm2048_device *bdev, int i,
- int index, int crc)
-{
- /* Good data will overwrite poor data */
- if (crc) {
- if (!bdev->rds_info.rds_ps[index])
- bdev->rds_info.rds_ps[index] =
- bdev->rds_info.radio_text[i + 1];
- if (!bdev->rds_info.rds_ps[index + 1])
- bdev->rds_info.rds_ps[index + 1] =
- bdev->rds_info.radio_text[i + 2];
- } else {
- bdev->rds_info.rds_ps[index] =
- bdev->rds_info.radio_text[i + 1];
- bdev->rds_info.rds_ps[index + 1] =
- bdev->rds_info.radio_text[i + 2];
- }
-}
-
-static int bcm2048_parse_ps_match_c(struct bcm2048_device *bdev, int i,
- int index)
-{
- int crc;
-
- crc = bcm2048_rds_block_crc(bdev, i);
-
- if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
- return 0;
-
- if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
- BCM2048_RDS_BLOCK_C)
- return 1;
-
- return 0;
-}
-
-static void bcm2048_parse_ps_match_d(struct bcm2048_device *bdev, int i,
- int index)
-{
- int crc;
-
- crc = bcm2048_rds_block_crc(bdev, i);
-
- if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
- return;
-
- if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
- BCM2048_RDS_BLOCK_D)
- bcm2048_parse_rds_ps_block(bdev, i, index, crc);
-}
-
-static int bcm2048_parse_ps_match_b(struct bcm2048_device *bdev, int i)
-{
- int crc, index, ps_id, ps_group;
-
- crc = bcm2048_rds_block_crc(bdev, i);
-
- if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
- return -EIO;
-
- /* Block B Radio PS match */
- if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
- BCM2048_RDS_BLOCK_B) {
- ps_id = bdev->rds_info.radio_text[i + 1] &
- BCM2048_RDS_BLOCK_MASK;
- ps_group = bdev->rds_info.radio_text[i + 1] &
- BCM2048_RDS_GROUP_AB_MASK;
-
- /*
- * Poor RSSI will lead to RDS data corruption
- * So using 3 (same) sequential values to justify major changes
- */
- if (ps_group != bdev->rds_info.rds_ps_group) {
- if (crc == BCM2048_RDS_CRC_NONE) {
- bdev->rds_info.rds_ps_group_cnt++;
- if (bdev->rds_info.rds_ps_group_cnt > 2) {
- bdev->rds_info.rds_ps_group = ps_group;
- bdev->rds_info.rds_ps_group_cnt = 0;
- dev_err(&bdev->client->dev,
- "RDS PS Group change!\n");
- } else {
- return -EIO;
- }
- } else {
- bdev->rds_info.rds_ps_group_cnt = 0;
- }
- }
-
- if (ps_id == BCM2048_RDS_PS) {
- index = bdev->rds_info.radio_text[i + 2] &
- BCM2048_RDS_PS_INDEX;
- index <<= 1;
- return index;
- }
- }
-
- return -EIO;
-}
-
-static void bcm2048_parse_rds_ps(struct bcm2048_device *bdev)
-{
- int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0;
-
- for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
- if (match_b) {
- match_b = 0;
- index = bcm2048_parse_ps_match_b(bdev, i);
- if (index >= 0 && index < (BCM2048_MAX_RDS_PS - 1))
- match_c = 1;
- continue;
- } else if (match_c) {
- match_c = 0;
- if (bcm2048_parse_ps_match_c(bdev, i, index))
- match_d = 1;
- continue;
- } else if (match_d) {
- match_d = 0;
- bcm2048_parse_ps_match_d(bdev, i, index);
- continue;
- }
-
- /* Skip erroneous blocks due to messed up A block altogether */
- if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
- BCM2048_RDS_BLOCK_A) {
- crc = bcm2048_rds_block_crc(bdev, i);
- if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
- continue;
- /* Synchronize to a good RDS PI */
- if (((bdev->rds_info.radio_text[i + 1] << 8) +
- bdev->rds_info.radio_text[i + 2]) ==
- bdev->rds_info.rds_pi)
- match_b = 1;
- }
- }
-}
-
-static void bcm2048_rds_fifo_receive(struct bcm2048_device *bdev)
-{
- int err;
-
- mutex_lock(&bdev->mutex);
-
- err = bcm2048_recv_duples(bdev, BCM2048_I2C_RDS_DATA,
- bdev->rds_info.radio_text, bdev->fifo_size);
- if (err != 2) {
- dev_err(&bdev->client->dev, "RDS Read problem\n");
- mutex_unlock(&bdev->mutex);
- return;
- }
-
- bdev->rds_info.text_len = bdev->fifo_size;
-
- bcm2048_parse_rds_pi(bdev);
- bcm2048_parse_rds_rt(bdev);
- bcm2048_parse_rds_ps(bdev);
-
- mutex_unlock(&bdev->mutex);
-
- wake_up_interruptible(&bdev->read_queue);
-}
-
-static int bcm2048_get_rds_data(struct bcm2048_device *bdev, char *data)
-{
- int err = 0, i, p = 0;
- char *data_buffer;
-
- mutex_lock(&bdev->mutex);
-
- if (!bdev->rds_info.text_len) {
- err = -EINVAL;
- goto unlock;
- }
-
- data_buffer = kcalloc(BCM2048_MAX_RDS_RADIO_TEXT, 5, GFP_KERNEL);
- if (!data_buffer) {
- err = -ENOMEM;
- goto unlock;
- }
-
- for (i = 0; i < bdev->rds_info.text_len; i++) {
- p += sprintf(data_buffer + p, "%x ",
- bdev->rds_info.radio_text[i]);
- }
-
- memcpy(data, data_buffer, p);
- kfree(data_buffer);
-
-unlock:
- mutex_unlock(&bdev->mutex);
- return err;
-}
-
-/*
- * BCM2048 default initialization sequence
- */
-static int bcm2048_init(struct bcm2048_device *bdev)
-{
- int err;
-
- err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON);
- if (err < 0)
- goto exit;
-
- err = bcm2048_set_audio_route(bdev, BCM2048_AUDIO_ROUTE_DAC);
- if (err < 0)
- goto exit;
-
- err = bcm2048_set_dac_output(bdev, BCM2048_DAC_OUTPUT_LEFT |
- BCM2048_DAC_OUTPUT_RIGHT);
-
-exit:
- return err;
-}
-
-/*
- * BCM2048 default deinitialization sequence
- */
-static int bcm2048_deinit(struct bcm2048_device *bdev)
-{
- int err;
-
- err = bcm2048_set_audio_route(bdev, 0);
- if (err < 0)
- return err;
-
- err = bcm2048_set_dac_output(bdev, 0);
- if (err < 0)
- return err;
-
- return bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
-}
-
-/*
- * BCM2048 probe sequence
- */
-static int bcm2048_probe(struct bcm2048_device *bdev)
-{
- int err;
-
- err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON);
- if (err < 0)
- goto unlock;
-
- err = bcm2048_checkrev(bdev);
- if (err < 0)
- goto unlock;
-
- err = bcm2048_set_mute(bdev, BCM2048_DEFAULT_MUTE);
- if (err < 0)
- goto unlock;
-
- err = bcm2048_set_region(bdev, BCM2048_DEFAULT_REGION);
- if (err < 0)
- goto unlock;
-
- err = bcm2048_set_fm_search_rssi_threshold(bdev,
- BCM2048_DEFAULT_RSSI_THRESHOLD);
- if (err < 0)
- goto unlock;
-
- err = bcm2048_set_fm_automatic_stereo_mono(bdev, BCM2048_ITEM_ENABLED);
- if (err < 0)
- goto unlock;
-
- err = bcm2048_get_rds_wline(bdev);
- if (err < BCM2048_DEFAULT_RDS_WLINE)
- err = bcm2048_set_rds_wline(bdev, BCM2048_DEFAULT_RDS_WLINE);
- if (err < 0)
- goto unlock;
-
- err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
-
- init_waitqueue_head(&bdev->read_queue);
- bdev->rds_data_available = 0;
- bdev->rd_index = 0;
- bdev->users = 0;
-
-unlock:
- return err;
-}
-
-/*
- * BCM2048 workqueue handler
- */
-static void bcm2048_work(struct work_struct *work)
-{
- struct bcm2048_device *bdev;
- u8 flag_lsb = 0, flag_msb = 0, flags;
-
- bdev = container_of(work, struct bcm2048_device, work);
- bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &flag_lsb);
- bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &flag_msb);
-
- if (flag_lsb & (BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED |
- BCM2048_FM_FLAG_SEARCH_TUNE_FAIL)) {
- if (flag_lsb & BCM2048_FM_FLAG_SEARCH_TUNE_FAIL)
- bdev->scan_state = BCM2048_SCAN_FAIL;
- else
- bdev->scan_state = BCM2048_SCAN_OK;
-
- complete(&bdev->compl);
- }
-
- if (flag_msb & BCM2048_RDS_FLAG_FIFO_WLINE) {
- bcm2048_rds_fifo_receive(bdev);
- if (bdev->rds_state) {
- flags = BCM2048_RDS_FLAG_FIFO_WLINE;
- bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
- flags);
- }
- bdev->rds_data_available = 1;
- bdev->rd_index = 0; /* new data, new start */
- }
-}
-
-/*
- * BCM2048 interrupt handler
- */
-static irqreturn_t bcm2048_handler(int irq, void *dev)
-{
- struct bcm2048_device *bdev = dev;
-
- dev_dbg(&bdev->client->dev, "IRQ called, queuing work\n");
- if (bdev->power_state)
- schedule_work(&bdev->work);
-
- return IRQ_HANDLED;
-}
-
-/*
- * BCM2048 sysfs interface definitions
- */
-#define property_write(prop, type, mask, check) \
-static ssize_t bcm2048_##prop##_write(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, \
- size_t count) \
-{ \
- struct bcm2048_device *bdev = dev_get_drvdata(dev); \
- type value; \
- int err; \
- \
- if (!bdev) \
- return -ENODEV; \
- \
- if (sscanf(buf, mask, &value) != 1) \
- return -EINVAL; \
- \
- if (check) \
- return -EDOM; \
- \
- err = bcm2048_set_##prop(bdev, value); \
- \
- return err < 0 ? err : count; \
-}
-
-#define property_read(prop, mask) \
-static ssize_t bcm2048_##prop##_read(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
-{ \
- struct bcm2048_device *bdev = dev_get_drvdata(dev); \
- int value; \
- \
- if (!bdev) \
- return -ENODEV; \
- \
- value = bcm2048_get_##prop(bdev); \
- \
- if (value >= 0) \
- value = sprintf(buf, mask "\n", value); \
- \
- return value; \
-}
-
-#define property_signed_read(prop, size, mask) \
-static ssize_t bcm2048_##prop##_read(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
-{ \
- struct bcm2048_device *bdev = dev_get_drvdata(dev); \
- size value; \
- \
- if (!bdev) \
- return -ENODEV; \
- \
- value = bcm2048_get_##prop(bdev); \
- \
- return sprintf(buf, mask "\n", value); \
-}
-
-#define DEFINE_SYSFS_PROPERTY(prop, prop_type, mask, check) \
-property_write(prop, prop_type, mask, check) \
-property_read(prop, mask) \
-
-#define property_str_read(prop, size) \
-static ssize_t bcm2048_##prop##_read(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
-{ \
- struct bcm2048_device *bdev = dev_get_drvdata(dev); \
- int count; \
- u8 *out; \
- \
- if (!bdev) \
- return -ENODEV; \
- \
- out = kzalloc((size) + 1, GFP_KERNEL); \
- if (!out) \
- return -ENOMEM; \
- \
- bcm2048_get_##prop(bdev, out); \
- count = sprintf(buf, "%s\n", out); \
- \
- kfree(out); \
- \
- return count; \
-}
-
-DEFINE_SYSFS_PROPERTY(power_state, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(mute, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(audio_route, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(dac_output, unsigned int, "%u", 0)
-
-DEFINE_SYSFS_PROPERTY(fm_hi_lo_injection, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(fm_frequency, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(fm_af_frequency, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(fm_deemphasis, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(fm_rds_mask, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(fm_best_tune_mode, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(fm_search_rssi_threshold, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(fm_search_mode_direction, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(fm_search_tune_mode, unsigned int, "%u", value > 3)
-
-DEFINE_SYSFS_PROPERTY(rds, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(rds_b_block_mask, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(rds_b_block_match, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(rds_pi_mask, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(rds_pi_match, unsigned int, "%u", 0)
-DEFINE_SYSFS_PROPERTY(rds_wline, unsigned int, "%u", 0)
-property_read(rds_pi, "%x")
-property_str_read(rds_rt, (BCM2048_MAX_RDS_RT + 1))
-property_str_read(rds_ps, (BCM2048_MAX_RDS_PS + 1))
-
-property_read(fm_rds_flags, "%u")
-property_str_read(rds_data, BCM2048_MAX_RDS_RADIO_TEXT * 5)
-
-property_read(region_bottom_frequency, "%u")
-property_read(region_top_frequency, "%u")
-property_signed_read(fm_carrier_error, int, "%d")
-property_signed_read(fm_rssi, int, "%d")
-DEFINE_SYSFS_PROPERTY(region, unsigned int, "%u", 0)
-
-static struct device_attribute attrs[] = {
- __ATTR(power_state, 0644, bcm2048_power_state_read,
- bcm2048_power_state_write),
- __ATTR(mute, 0644, bcm2048_mute_read,
- bcm2048_mute_write),
- __ATTR(audio_route, 0644, bcm2048_audio_route_read,
- bcm2048_audio_route_write),
- __ATTR(dac_output, 0644, bcm2048_dac_output_read,
- bcm2048_dac_output_write),
- __ATTR(fm_hi_lo_injection, 0644,
- bcm2048_fm_hi_lo_injection_read,
- bcm2048_fm_hi_lo_injection_write),
- __ATTR(fm_frequency, 0644, bcm2048_fm_frequency_read,
- bcm2048_fm_frequency_write),
- __ATTR(fm_af_frequency, 0644,
- bcm2048_fm_af_frequency_read,
- bcm2048_fm_af_frequency_write),
- __ATTR(fm_deemphasis, 0644, bcm2048_fm_deemphasis_read,
- bcm2048_fm_deemphasis_write),
- __ATTR(fm_rds_mask, 0644, bcm2048_fm_rds_mask_read,
- bcm2048_fm_rds_mask_write),
- __ATTR(fm_best_tune_mode, 0644,
- bcm2048_fm_best_tune_mode_read,
- bcm2048_fm_best_tune_mode_write),
- __ATTR(fm_search_rssi_threshold, 0644,
- bcm2048_fm_search_rssi_threshold_read,
- bcm2048_fm_search_rssi_threshold_write),
- __ATTR(fm_search_mode_direction, 0644,
- bcm2048_fm_search_mode_direction_read,
- bcm2048_fm_search_mode_direction_write),
- __ATTR(fm_search_tune_mode, 0644,
- bcm2048_fm_search_tune_mode_read,
- bcm2048_fm_search_tune_mode_write),
- __ATTR(rds, 0644, bcm2048_rds_read,
- bcm2048_rds_write),
- __ATTR(rds_b_block_mask, 0644,
- bcm2048_rds_b_block_mask_read,
- bcm2048_rds_b_block_mask_write),
- __ATTR(rds_b_block_match, 0644,
- bcm2048_rds_b_block_match_read,
- bcm2048_rds_b_block_match_write),
- __ATTR(rds_pi_mask, 0644, bcm2048_rds_pi_mask_read,
- bcm2048_rds_pi_mask_write),
- __ATTR(rds_pi_match, 0644, bcm2048_rds_pi_match_read,
- bcm2048_rds_pi_match_write),
- __ATTR(rds_wline, 0644, bcm2048_rds_wline_read,
- bcm2048_rds_wline_write),
- __ATTR(rds_pi, 0444, bcm2048_rds_pi_read, NULL),
- __ATTR(rds_rt, 0444, bcm2048_rds_rt_read, NULL),
- __ATTR(rds_ps, 0444, bcm2048_rds_ps_read, NULL),
- __ATTR(fm_rds_flags, 0444, bcm2048_fm_rds_flags_read, NULL),
- __ATTR(region_bottom_frequency, 0444,
- bcm2048_region_bottom_frequency_read, NULL),
- __ATTR(region_top_frequency, 0444,
- bcm2048_region_top_frequency_read, NULL),
- __ATTR(fm_carrier_error, 0444,
- bcm2048_fm_carrier_error_read, NULL),
- __ATTR(fm_rssi, 0444,
- bcm2048_fm_rssi_read, NULL),
- __ATTR(region, 0644, bcm2048_region_read,
- bcm2048_region_write),
- __ATTR(rds_data, 0444, bcm2048_rds_data_read, NULL),
-};
-
-static int bcm2048_sysfs_unregister_properties(struct bcm2048_device *bdev,
- int size)
-{
- int i;
-
- for (i = 0; i < size; i++)
- device_remove_file(&bdev->client->dev, &attrs[i]);
-
- return 0;
-}
-
-static int bcm2048_sysfs_register_properties(struct bcm2048_device *bdev)
-{
- int err = 0;
- int i;
-
- for (i = 0; i < ARRAY_SIZE(attrs); i++) {
- if (device_create_file(&bdev->client->dev, &attrs[i]) != 0) {
- dev_err(&bdev->client->dev,
- "could not register sysfs entry\n");
- err = -EBUSY;
- bcm2048_sysfs_unregister_properties(bdev, i);
- break;
- }
- }
-
- return err;
-}
-
-static int bcm2048_fops_open(struct file *file)
-{
- struct bcm2048_device *bdev = video_drvdata(file);
-
- bdev->users++;
- bdev->rd_index = 0;
- bdev->rds_data_available = 0;
-
- return 0;
-}
-
-static int bcm2048_fops_release(struct file *file)
-{
- struct bcm2048_device *bdev = video_drvdata(file);
-
- bdev->users--;
-
- return 0;
-}
-
-static __poll_t bcm2048_fops_poll(struct file *file,
- struct poll_table_struct *pts)
-{
- struct bcm2048_device *bdev = video_drvdata(file);
- __poll_t retval = 0;
-
- poll_wait(file, &bdev->read_queue, pts);
-
- if (bdev->rds_data_available)
- retval = EPOLLIN | EPOLLRDNORM;
-
- return retval;
-}
-
-static ssize_t bcm2048_fops_read(struct file *file, char __user *buf,
- size_t count, loff_t *ppos)
-{
- struct bcm2048_device *bdev = video_drvdata(file);
- int i;
- int retval = 0;
-
- /* we return at least 3 bytes, one block */
- count = (count / 3) * 3; /* only multiples of 3 */
- if (count < 3)
- return -ENOBUFS;
-
- while (!bdev->rds_data_available) {
- if (file->f_flags & O_NONBLOCK) {
- retval = -EWOULDBLOCK;
- goto done;
- }
- /* interruptible_sleep_on(&bdev->read_queue); */
- if (wait_event_interruptible(bdev->read_queue,
- bdev->rds_data_available) < 0) {
- retval = -EINTR;
- goto done;
- }
- }
-
- mutex_lock(&bdev->mutex);
- /* copy data to userspace */
- i = bdev->fifo_size - bdev->rd_index;
- if (count > i)
- count = (i / 3) * 3;
-
- i = 0;
- while (i < count) {
- unsigned char tmpbuf[3];
-
- tmpbuf[i] = bdev->rds_info.radio_text[bdev->rd_index + i + 2];
- tmpbuf[i + 1] =
- bdev->rds_info.radio_text[bdev->rd_index + i + 1];
- tmpbuf[i + 2] =
- (bdev->rds_info.radio_text[bdev->rd_index + i] &
- 0xf0) >> 4;
- if ((bdev->rds_info.radio_text[bdev->rd_index + i] &
- BCM2048_RDS_CRC_MASK) == BCM2048_RDS_CRC_UNRECOVARABLE)
- tmpbuf[i + 2] |= 0x80;
- if (copy_to_user(buf + i, tmpbuf, 3)) {
- retval = -EFAULT;
- break;
- }
- i += 3;
- }
-
- bdev->rd_index += i;
- if (bdev->rd_index >= bdev->fifo_size)
- bdev->rds_data_available = 0;
-
- mutex_unlock(&bdev->mutex);
- if (retval == 0)
- retval = i;
-
-done:
- return retval;
-}
-
-/*
- * bcm2048_fops - file operations interface
- */
-static const struct v4l2_file_operations bcm2048_fops = {
- .owner = THIS_MODULE,
- .unlocked_ioctl = video_ioctl2,
- /* for RDS read support */
- .open = bcm2048_fops_open,
- .release = bcm2048_fops_release,
- .read = bcm2048_fops_read,
- .poll = bcm2048_fops_poll
-};
-
-/*
- * Video4Linux Interface
- */
-static struct v4l2_queryctrl bcm2048_v4l2_queryctrl[] = {
- {
- .id = V4L2_CID_AUDIO_VOLUME,
- .flags = V4L2_CTRL_FLAG_DISABLED,
- },
- {
- .id = V4L2_CID_AUDIO_BALANCE,
- .flags = V4L2_CTRL_FLAG_DISABLED,
- },
- {
- .id = V4L2_CID_AUDIO_BASS,
- .flags = V4L2_CTRL_FLAG_DISABLED,
- },
- {
- .id = V4L2_CID_AUDIO_TREBLE,
- .flags = V4L2_CTRL_FLAG_DISABLED,
- },
- {
- .id = V4L2_CID_AUDIO_MUTE,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "Mute",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 1,
- },
- {
- .id = V4L2_CID_AUDIO_LOUDNESS,
- .flags = V4L2_CTRL_FLAG_DISABLED,
- },
-};
-
-static int bcm2048_vidioc_querycap(struct file *file, void *priv,
- struct v4l2_capability *capability)
-{
- struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
-
- strscpy(capability->driver, BCM2048_DRIVER_NAME,
- sizeof(capability->driver));
- strscpy(capability->card, BCM2048_DRIVER_CARD,
- sizeof(capability->card));
- snprintf(capability->bus_info, 32, "I2C: 0x%X", bdev->client->addr);
- return 0;
-}
-
-static int bcm2048_vidioc_g_input(struct file *filp, void *priv,
- unsigned int *i)
-{
- *i = 0;
-
- return 0;
-}
-
-static int bcm2048_vidioc_s_input(struct file *filp, void *priv,
- unsigned int i)
-{
- if (i)
- return -EINVAL;
-
- return 0;
-}
-
-static int bcm2048_vidioc_queryctrl(struct file *file, void *priv,
- struct v4l2_queryctrl *qc)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(bcm2048_v4l2_queryctrl); i++) {
- if (qc->id && qc->id == bcm2048_v4l2_queryctrl[i].id) {
- *qc = bcm2048_v4l2_queryctrl[i];
- return 0;
- }
- }
-
- return -EINVAL;
-}
-
-static int bcm2048_vidioc_g_ctrl(struct file *file, void *priv,
- struct v4l2_control *ctrl)
-{
- struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
- int err = 0;
-
- if (!bdev)
- return -ENODEV;
-
- switch (ctrl->id) {
- case V4L2_CID_AUDIO_MUTE:
- err = bcm2048_get_mute(bdev);
- if (err >= 0)
- ctrl->value = err;
- break;
- }
-
- return err;
-}
-
-static int bcm2048_vidioc_s_ctrl(struct file *file, void *priv,
- struct v4l2_control *ctrl)
-{
- struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
- int err = 0;
-
- if (!bdev)
- return -ENODEV;
-
- switch (ctrl->id) {
- case V4L2_CID_AUDIO_MUTE:
- if (ctrl->value) {
- if (bdev->power_state) {
- err = bcm2048_set_mute(bdev, ctrl->value);
- err |= bcm2048_deinit(bdev);
- }
- } else {
- if (!bdev->power_state) {
- err = bcm2048_init(bdev);
- err |= bcm2048_set_mute(bdev, ctrl->value);
- }
- }
- break;
- }
-
- return err;
-}
-
-static int bcm2048_vidioc_g_audio(struct file *file, void *priv,
- struct v4l2_audio *audio)
-{
- if (audio->index > 1)
- return -EINVAL;
-
- strscpy(audio->name, "Radio", sizeof(audio->name));
- audio->capability = V4L2_AUDCAP_STEREO;
-
- return 0;
-}
-
-static int bcm2048_vidioc_s_audio(struct file *file, void *priv,
- const struct v4l2_audio *audio)
-{
- if (audio->index != 0)
- return -EINVAL;
-
- return 0;
-}
-
-static int bcm2048_vidioc_g_tuner(struct file *file, void *priv,
- struct v4l2_tuner *tuner)
-{
- struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
- s8 f_error;
- s8 rssi;
-
- if (!bdev)
- return -ENODEV;
-
- if (tuner->index > 0)
- return -EINVAL;
-
- strscpy(tuner->name, "FM Receiver", sizeof(tuner->name));
- tuner->type = V4L2_TUNER_RADIO;
- tuner->rangelow =
- dev_to_v4l2(bcm2048_get_region_bottom_frequency(bdev));
- tuner->rangehigh =
- dev_to_v4l2(bcm2048_get_region_top_frequency(bdev));
- tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;
- tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW;
- tuner->audmode = V4L2_TUNER_MODE_STEREO;
- tuner->afc = 0;
- if (bdev->power_state) {
- /*
- * Report frequencies with high carrier errors to have zero
- * signal level
- */
- f_error = bcm2048_get_fm_carrier_error(bdev);
- if (f_error < BCM2048_FREQ_ERROR_FLOOR ||
- f_error > BCM2048_FREQ_ERROR_ROOF) {
- tuner->signal = 0;
- } else {
- /*
- * RSSI level -60 dB is defined to report full
- * signal strength
- */
- rssi = bcm2048_get_fm_rssi(bdev);
- if (rssi >= BCM2048_RSSI_LEVEL_BASE) {
- tuner->signal = 0xFFFF;
- } else if (rssi > BCM2048_RSSI_LEVEL_ROOF) {
- tuner->signal = (rssi +
- BCM2048_RSSI_LEVEL_ROOF_NEG)
- * BCM2048_SIGNAL_MULTIPLIER;
- } else {
- tuner->signal = 0;
- }
- }
- } else {
- tuner->signal = 0;
- }
-
- return 0;
-}
-
-static int bcm2048_vidioc_s_tuner(struct file *file, void *priv,
- const struct v4l2_tuner *tuner)
-{
- struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
-
- if (!bdev)
- return -ENODEV;
-
- if (tuner->index > 0)
- return -EINVAL;
-
- return 0;
-}
-
-static int bcm2048_vidioc_g_frequency(struct file *file, void *priv,
- struct v4l2_frequency *freq)
-{
- struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
- int err = 0;
- int f;
-
- if (!bdev->power_state)
- return -ENODEV;
-
- freq->type = V4L2_TUNER_RADIO;
- f = bcm2048_get_fm_frequency(bdev);
-
- if (f < 0)
- err = f;
- else
- freq->frequency = dev_to_v4l2(f);
-
- return err;
-}
-
-static int bcm2048_vidioc_s_frequency(struct file *file, void *priv,
- const struct v4l2_frequency *freq)
-{
- struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
- int err;
-
- if (freq->type != V4L2_TUNER_RADIO)
- return -EINVAL;
-
- if (!bdev->power_state)
- return -ENODEV;
-
- err = bcm2048_set_fm_frequency(bdev, v4l2_to_dev(freq->frequency));
- err |= bcm2048_set_fm_search_tune_mode(bdev, BCM2048_FM_PRE_SET_MODE);
-
- return err;
-}
-
-static int bcm2048_vidioc_s_hw_freq_seek(struct file *file, void *priv,
- const struct v4l2_hw_freq_seek *seek)
-{
- struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
- int err;
-
- if (!bdev->power_state)
- return -ENODEV;
-
- if ((seek->tuner != 0) || (seek->type != V4L2_TUNER_RADIO))
- return -EINVAL;
-
- err = bcm2048_set_fm_search_mode_direction(bdev, seek->seek_upward);
- err |= bcm2048_set_fm_search_tune_mode(bdev,
- BCM2048_FM_AUTO_SEARCH_MODE);
-
- return err;
-}
-
-static const struct v4l2_ioctl_ops bcm2048_ioctl_ops = {
- .vidioc_querycap = bcm2048_vidioc_querycap,
- .vidioc_g_input = bcm2048_vidioc_g_input,
- .vidioc_s_input = bcm2048_vidioc_s_input,
- .vidioc_queryctrl = bcm2048_vidioc_queryctrl,
- .vidioc_g_ctrl = bcm2048_vidioc_g_ctrl,
- .vidioc_s_ctrl = bcm2048_vidioc_s_ctrl,
- .vidioc_g_audio = bcm2048_vidioc_g_audio,
- .vidioc_s_audio = bcm2048_vidioc_s_audio,
- .vidioc_g_tuner = bcm2048_vidioc_g_tuner,
- .vidioc_s_tuner = bcm2048_vidioc_s_tuner,
- .vidioc_g_frequency = bcm2048_vidioc_g_frequency,
- .vidioc_s_frequency = bcm2048_vidioc_s_frequency,
- .vidioc_s_hw_freq_seek = bcm2048_vidioc_s_hw_freq_seek,
-};
-
-/*
- * bcm2048_viddev_template - video device interface
- */
-static const struct video_device bcm2048_viddev_template = {
- .fops = &bcm2048_fops,
- .name = BCM2048_DRIVER_NAME,
- .release = video_device_release_empty,
- .ioctl_ops = &bcm2048_ioctl_ops,
- .device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
- V4L2_CAP_HW_FREQ_SEEK,
-};
-
-/*
- * I2C driver interface
- */
-static int bcm2048_i2c_driver_probe(struct i2c_client *client)
-{
- struct bcm2048_device *bdev;
- int err;
-
- bdev = kzalloc(sizeof(*bdev), GFP_KERNEL);
- if (!bdev) {
- err = -ENOMEM;
- goto exit;
- }
-
- bdev->client = client;
- i2c_set_clientdata(client, bdev);
- mutex_init(&bdev->mutex);
- init_completion(&bdev->compl);
- INIT_WORK(&bdev->work, bcm2048_work);
-
- if (client->irq) {
- err = request_irq(client->irq,
- bcm2048_handler, IRQF_TRIGGER_FALLING,
- client->name, bdev);
- if (err < 0) {
- dev_err(&client->dev, "Could not request IRQ\n");
- goto free_bdev;
- }
- dev_dbg(&client->dev, "IRQ requested.\n");
- } else {
- dev_dbg(&client->dev, "IRQ not configured. Using timeouts.\n");
- }
-
- bdev->videodev = bcm2048_viddev_template;
- video_set_drvdata(&bdev->videodev, bdev);
- if (video_register_device(&bdev->videodev, VFL_TYPE_RADIO, radio_nr)) {
- dev_dbg(&client->dev, "Could not register video device.\n");
- err = -EIO;
- goto free_irq;
- }
-
- err = bcm2048_sysfs_register_properties(bdev);
- if (err < 0) {
- dev_dbg(&client->dev, "Could not register sysfs interface.\n");
- goto free_registration;
- }
-
- err = bcm2048_probe(bdev);
- if (err < 0) {
- dev_dbg(&client->dev, "Failed to probe device information.\n");
- goto free_sysfs;
- }
-
- return 0;
-
-free_sysfs:
- bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs));
-free_registration:
- video_unregister_device(&bdev->videodev);
-free_irq:
- if (client->irq)
- free_irq(client->irq, bdev);
-free_bdev:
- i2c_set_clientdata(client, NULL);
- kfree(bdev);
-exit:
- return err;
-}
-
-static int bcm2048_i2c_driver_remove(struct i2c_client *client)
-{
- struct bcm2048_device *bdev = i2c_get_clientdata(client);
-
- if (!client->adapter)
- return -ENODEV;
-
- if (bdev) {
- bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs));
- video_unregister_device(&bdev->videodev);
-
- if (bdev->power_state)
- bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
-
- if (client->irq > 0)
- free_irq(client->irq, bdev);
-
- cancel_work_sync(&bdev->work);
-
- kfree(bdev);
- }
-
- return 0;
-}
-
-/*
- * bcm2048_i2c_driver - i2c driver interface
- */
-static const struct i2c_device_id bcm2048_id[] = {
- { "bcm2048", 0 },
- { },
-};
-MODULE_DEVICE_TABLE(i2c, bcm2048_id);
-
-static struct i2c_driver bcm2048_i2c_driver = {
- .driver = {
- .name = BCM2048_DRIVER_NAME,
- },
- .probe_new = bcm2048_i2c_driver_probe,
- .remove = bcm2048_i2c_driver_remove,
- .id_table = bcm2048_id,
-};
-
-module_i2c_driver(bcm2048_i2c_driver);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR(BCM2048_DRIVER_AUTHOR);
-MODULE_DESCRIPTION(BCM2048_DRIVER_DESC);
-MODULE_VERSION("0.0.2");
diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.h b/drivers/staging/media/bcm2048/radio-bcm2048.h
deleted file mode 100644
index 22887a075257..000000000000
--- a/drivers/staging/media/bcm2048/radio-bcm2048.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * drivers/staging/media/radio-bcm2048.h
- *
- * Property and command definitions for bcm2048 radio receiver chip.
- *
- * Copyright (C) Nokia Corporation
- * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- */
-
-#ifndef BCM2048_H
-#define BCM2048_H
-
-#define BCM2048_NAME "bcm2048"
-#define BCM2048_I2C_ADDR 0x22
-
-#endif /* ifndef BCM2048_H */
diff --git a/drivers/staging/media/davinci_vpfe/Kconfig b/drivers/staging/media/davinci_vpfe/Kconfig
deleted file mode 100644
index 94bf6746c03f..000000000000
--- a/drivers/staging/media/davinci_vpfe/Kconfig
+++ /dev/null
@@ -1,13 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-config VIDEO_DM365_VPFE
- tristate "DM365 VPFE Media Controller Capture Driver"
- depends on VIDEO_V4L2
- depends on (ARCH_DAVINCI_DM365 && !VIDEO_DM365_ISIF) || (COMPILE_TEST && !ARCH_OMAP1)
- depends on VIDEO_V4L2_SUBDEV_API
- depends on VIDEO_DAVINCI_VPBE_DISPLAY
- select VIDEOBUF2_DMA_CONTIG
- help
- Support for DM365 VPFE based Media Controller Capture driver.
-
- To compile this driver as a module, choose M here: the
- module will be called vpfe-mc-capture.
diff --git a/drivers/staging/media/davinci_vpfe/Makefile b/drivers/staging/media/davinci_vpfe/Makefile
deleted file mode 100644
index 0ae8c5014f74..000000000000
--- a/drivers/staging/media/davinci_vpfe/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci-vfpe.o
-
-davinci-vfpe-objs := \
- dm365_isif.o dm365_ipipe_hw.o dm365_ipipe.o \
- dm365_resizer.o dm365_ipipeif.o vpfe_mc_capture.o vpfe_video.o
-
-# Allow building it with COMPILE_TEST on other archs
-ifndef CONFIG_ARCH_DAVINCI
-ccflags-y += -I $(srctree)/arch/arm/mach-davinci/include/
-endif
diff --git a/drivers/staging/media/davinci_vpfe/TODO b/drivers/staging/media/davinci_vpfe/TODO
deleted file mode 100644
index cc8bd9306f2a..000000000000
--- a/drivers/staging/media/davinci_vpfe/TODO
+++ /dev/null
@@ -1,38 +0,0 @@
-TODO (general):
-==================================
-
-- User space interface refinement
- - Controls should be used when possible rather than private ioctl
- - No enums should be used
- - Use of MC and V4L2 subdev APIs when applicable
- - Single interface header might suffice
- - Current interface forces to configure everything at once
-- Get rid of the dm365_ipipe_hw.[ch] layer
-- Active external sub-devices defined by link configuration; no strcmp
- needed
-- More generic platform data (i2c adapters)
-- The driver should have no knowledge of possible external subdevs; see
- struct vpfe_subdev_id
-- Some of the hardware control should be refactorede
-- Check proper serialisation (through mutexes and spinlocks)
-- Names that are visible in kernel global namespace should have a common
- prefix (or a few)
-- While replacing the older driver in media folder, provide a compatibility
- layer and compatibility tests that warrants (using the libv4l's LD_PRELOAD
- approach) there is no regression for the users using the older driver.
-- make it independent of arch-specific APIs (mach/mux.h).
-
-Building of uImage and Applications:
-==================================
-
-As of now since the interface will undergo few changes all the include
-files are present in staging itself, to build for dm365 follow below steps,
-
-- copy vpfe.h from drivers/staging/media/davinci_vpfe/ to
- include/media/davinci/ folder for building the uImage.
-- copy davinci_vpfe_user.h from drivers/staging/media/davinci_vpfe/ to
- include/uapi/linux/davinci_vpfe.h, and add a entry in Kbuild (required
- for building application).
-- copy dm365_ipipeif_user.h from drivers/staging/media/davinci_vpfe/ to
- include/uapi/linux/dm365_ipipeif.h and a entry in Kbuild (required
- for building application).
diff --git a/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt b/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt
deleted file mode 100644
index a1e91778aa9b..000000000000
--- a/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt
+++ /dev/null
@@ -1,154 +0,0 @@
-Davinci Video processing Front End (VPFE) driver
-
-Copyright (C) 2012 Texas Instruments Inc
-
-Contacts: Manjunath Hadli <manjunath.hadli@ti.com>
- Prabhakar Lad <prabhakar.lad@ti.com>
-
-
-Introduction
-============
-
-This file documents the Texas Instruments Davinci Video processing Front End
-(VPFE) driver located under drivers/media/platform/davinci. The original driver
-exists for Davinci VPFE, which is now being changed to Media Controller
-Framework.
-
-Currently the driver has been successfully used on the following
-version of Davinci:
-
- DM365/DM368
-
-The driver implements V4L2, Media controller and v4l2_subdev interfaces. Sensor,
-lens and flash drivers using the v4l2_subdev interface in the kernel are
-supported.
-
-
-Split to subdevs
-================
-
-The Davinci VPFE is split into V4L2 subdevs, each of the blocks inside the VPFE
-having one subdev to represent it. Each of the subdevs provide a V4L2 subdev
-interface to userspace.
-
- DAVINCI ISIF
- DAVINCI IPIPEIF
- DAVINCI IPIPE
- DAVINCI CROP RESIZER
- DAVINCI RESIZER A
- DAVINCI RESIZER B
-
-Each possible link in the VPFE is modelled by a link in the Media controller
-interface. For an example program see [1].
-
-
-ISIF, IPIPE, and RESIZER block IOCTLs
-======================================
-
-The Davinci Video processing Front End (VPFE) driver supports standard V4L2
-IOCTLs and controls where possible and practical. Much of the functions provided
-by the VPFE, however, does not fall under the standard IOCTL's.
-
-In general, there is a private ioctl for configuring each of the blocks
-containing hardware-dependent functions.
-
-The following private IOCTLs are supported:
-
- VIDIOC_VPFE_ISIF_[S/G]_RAW_PARAMS
- VIDIOC_VPFE_IPIPE_[S/G]_CONFIG
- VIDIOC_VPFE_RSZ_[S/G]_CONFIG
-
-The parameter structures used by these ioctl's are described in
-include/uapi/linux/davinci_vpfe.h.
-
-The VIDIOC_VPFE_ISIF_S_RAW_PARAMS, VIDIOC_VPFE_IPIPE_S_CONFIG and
-VIDIOC_VPFE_RSZ_S_CONFIG are used to configure, enable and disable functions in
-the isif, ipipe and resizer blocks respectively. These IOCTL's control several
-functions in the blocks they control. VIDIOC_VPFE_ISIF_S_RAW_PARAMS IOCTL
-accepts a pointer to struct vpfe_isif_raw_config as its argument. Similarly
-VIDIOC_VPFE_IPIPE_S_CONFIG accepts a pointer to struct vpfe_ipipe_config. And
-VIDIOC_VPFE_RSZ_S_CONFIG accepts a pointer to struct vpfe_rsz_config as its
-argument. Similarly VIDIOC_VPFE_ISIF_G_RAW_PARAMS, VIDIOC_VPFE_IPIPE_G_CONFIG
-and VIDIOC_VPFE_RSZ_G_CONFIG are used to get the current configuration set in
-the isif, ipipe and resizer blocks respectively.
-
-The detailed functions of the VPFE itself related to a given VPFE block is
-described in the Technical Reference Manuals (TRMs) --- see the end of the
-document for those.
-
-
-IPIPEIF block IOCTLs
-======================================
-
-The following private IOCTLs are supported:
-
- VIDIOC_VPFE_IPIPEIF_[S/G]_CONFIG
-
-The parameter structures used by these ioctl's are described in
-include/uapi/linux/dm365_ipipeif.h
-
-The VIDIOC_VPFE_IPIPEIF_S_CONFIG is used to configure the ipipeif
-hardware block. The VIDIOC_VPFE_IPIPEIF_S_CONFIG and
-VIDIOC_VPFE_IPIPEIF_G_CONFIG accepts a pointer to struct ipipeif_params
-as its argument.
-
-
-VPFE Operating Modes
-==========================================
-
-a: Continuous Modes
-------------------------
-
-1: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> SDRAM
-
-2: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> DAVINCI IPIPEIF--->|
- |
- <--------------------<----------------<---------------------<---|
- |
- V
- DAVINCI CROP RESIZER--->DAVINCI RESIZER [A/B]---> SDRAM
-
-3: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> DAVINCI IPIPEIF--->|
- |
- <--------------------<----------------<---------------------<---|
- |
- V
- DAVINCI IPIPE---> DAVINCI CROP RESIZER--->DAVINCI RESIZER [A/B]---> SDRAM
-
-a: Single Shot Modes
-------------------------
-
-1: SDRAM---> DAVINCI IPIPEIF---> DAVINCI IPIPE---> DAVINCI CROP RESIZER--->|
- |
- <----------------<----------------<------------------<---------------<--|
- |
- V
-DAVINCI RESIZER [A/B]---> SDRAM
-
-2: SDRAM---> DAVINCI IPIPEIF---> DAVINCI CROP RESIZER--->|
- |
- <----------------<----------------<---------------<---|
- |
- V
-DAVINCI RESIZER [A/B]---> SDRAM
-
-
-Technical reference manuals (TRMs) and other documentation
-==========================================================
-
-Davinci DM365 TRM:
-<URL:http://www.ti.com/lit/ds/sprs457e/sprs457e.pdf>
-Referenced MARCH 2009-REVISED JUNE 2011
-
-Davinci DM368 TRM:
-<URL:http://www.ti.com/lit/ds/sprs668c/sprs668c.pdf>
-Referenced APRIL 2010-REVISED JUNE 2011
-
-Davinci Video Processing Front End (VPFE) DM36x
-<URL:http://www.ti.com/lit/ug/sprufg8c/sprufg8c.pdf>
-
-
-References
-==========
-
-[1] http://git.ideasonboard.org/?p=media-ctl.git;a=summary
diff --git a/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h b/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h
deleted file mode 100644
index 8d772029c91d..000000000000
--- a/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h
+++ /dev/null
@@ -1,1287 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_USER_H
-#define _DAVINCI_VPFE_USER_H
-
-#include <linux/types.h>
-#include <linux/videodev2.h>
-
-/*
- * Private IOCTL
- *
- * VIDIOC_VPFE_ISIF_S_RAW_PARAMS: Set raw params in isif
- * VIDIOC_VPFE_ISIF_G_RAW_PARAMS: Get raw params from isif
- * VIDIOC_VPFE_PRV_S_CONFIG: Set ipipe engine configuration
- * VIDIOC_VPFE_PRV_G_CONFIG: Get ipipe engine configuration
- * VIDIOC_VPFE_RSZ_S_CONFIG: Set resizer engine configuration
- * VIDIOC_VPFE_RSZ_G_CONFIG: Get resizer engine configuration
- */
-
-#define VIDIOC_VPFE_ISIF_S_RAW_PARAMS \
- _IOW('V', BASE_VIDIOC_PRIVATE + 1, struct vpfe_isif_raw_config)
-#define VIDIOC_VPFE_ISIF_G_RAW_PARAMS \
- _IOR('V', BASE_VIDIOC_PRIVATE + 2, struct vpfe_isif_raw_config)
-#define VIDIOC_VPFE_IPIPE_S_CONFIG \
- _IOWR('P', BASE_VIDIOC_PRIVATE + 3, struct vpfe_ipipe_config)
-#define VIDIOC_VPFE_IPIPE_G_CONFIG \
- _IOWR('P', BASE_VIDIOC_PRIVATE + 4, struct vpfe_ipipe_config)
-#define VIDIOC_VPFE_RSZ_S_CONFIG \
- _IOWR('R', BASE_VIDIOC_PRIVATE + 5, struct vpfe_rsz_config)
-#define VIDIOC_VPFE_RSZ_G_CONFIG \
- _IOWR('R', BASE_VIDIOC_PRIVATE + 6, struct vpfe_rsz_config)
-
-/*
- * Private Control's for ISIF
- */
-#define VPFE_ISIF_CID_CRGAIN (V4L2_CID_USER_BASE | 0xa001)
-#define VPFE_ISIF_CID_CGRGAIN (V4L2_CID_USER_BASE | 0xa002)
-#define VPFE_ISIF_CID_CGBGAIN (V4L2_CID_USER_BASE | 0xa003)
-#define VPFE_ISIF_CID_CBGAIN (V4L2_CID_USER_BASE | 0xa004)
-#define VPFE_ISIF_CID_GAIN_OFFSET (V4L2_CID_USER_BASE | 0xa005)
-
-/*
- * Private Control's for ISIF and IPIPEIF
- */
-#define VPFE_CID_DPCM_PREDICTOR (V4L2_CID_USER_BASE | 0xa006)
-
-/************************************************************************
- * Vertical Defect Correction parameters
- ***********************************************************************/
-
-/**
- * vertical defect correction methods
- */
-enum vpfe_isif_vdfc_corr_mode {
- /* Defect level subtraction. Just fed through if saturating */
- VPFE_ISIF_VDFC_NORMAL,
- /**
- * Defect level subtraction. Horizontal interpolation ((i-2)+(i+2))/2
- * if data saturating
- */
- VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT,
- /* Horizontal interpolation (((i-2)+(i+2))/2) */
- VPFE_ISIF_VDFC_HORZ_INTERPOL
-};
-
-/**
- * Max Size of the Vertical Defect Correction table
- */
-#define VPFE_ISIF_VDFC_TABLE_SIZE 8
-
-/**
- * Values used for shifting up the vdfc defect level
- */
-enum vpfe_isif_vdfc_shift {
- /* No Shift */
- VPFE_ISIF_VDFC_NO_SHIFT,
- /* Shift by 1 bit */
- VPFE_ISIF_VDFC_SHIFT_1,
- /* Shift by 2 bit */
- VPFE_ISIF_VDFC_SHIFT_2,
- /* Shift by 3 bit */
- VPFE_ISIF_VDFC_SHIFT_3,
- /* Shift by 4 bit */
- VPFE_ISIF_VDFC_SHIFT_4
-};
-
-/**
- * Defect Correction (DFC) table entry
- */
-struct vpfe_isif_vdfc_entry {
- /* vertical position of defect */
- unsigned short pos_vert;
- /* horizontal position of defect */
- unsigned short pos_horz;
- /**
- * Defect level of Vertical line defect position. This is subtracted
- * from the data at the defect position
- */
- unsigned char level_at_pos;
- /**
- * Defect level of the pixels upper than the vertical line defect.
- * This is subtracted from the data
- */
- unsigned char level_up_pixels;
- /**
- * Defect level of the pixels lower than the vertical line defect.
- * This is subtracted from the data
- */
- unsigned char level_low_pixels;
-};
-
-/**
- * Structure for Defect Correction (DFC) parameter
- */
-struct vpfe_isif_dfc {
- /* enable vertical defect correction */
- unsigned char en;
- /* Correction methods */
- enum vpfe_isif_vdfc_corr_mode corr_mode;
- /**
- * 0 - whole line corrected, 1 - not
- * pixels upper than the defect
- */
- unsigned char corr_whole_line;
- /**
- * defect level shift value. level_at_pos, level_upper_pos,
- * and level_lower_pos can be shifted up by this value
- */
- enum vpfe_isif_vdfc_shift def_level_shift;
- /* defect saturation level */
- unsigned short def_sat_level;
- /* number of vertical defects. Max is VPFE_ISIF_VDFC_TABLE_SIZE */
- short num_vdefects;
- /* VDFC table ptr */
- struct vpfe_isif_vdfc_entry table[VPFE_ISIF_VDFC_TABLE_SIZE];
-};
-
-/************************************************************************
- * Digital/Black clamp or DC Subtract parameters
- ************************************************************************/
-/**
- * Horizontal Black Clamp modes
- */
-enum vpfe_isif_horz_bc_mode {
- /**
- * Horizontal clamp disabled. Only vertical clamp
- * value is subtracted
- */
- VPFE_ISIF_HORZ_BC_DISABLE,
- /**
- * Horizontal clamp value is calculated and subtracted
- * from image data along with vertical clamp value
- */
- VPFE_ISIF_HORZ_BC_CLAMP_CALC_ENABLED,
- /**
- * Horizontal clamp value calculated from previous image
- * is subtracted from image data along with vertical clamp
- * value. How the horizontal clamp value for the first image
- * is calculated in this case ???
- */
- VPFE_ISIF_HORZ_BC_CLAMP_NOT_UPDATED
-};
-
-/**
- * Base window selection for Horizontal Black Clamp calculations
- */
-enum vpfe_isif_horz_bc_base_win_sel {
- /* Select Most left window for bc calculation */
- VPFE_ISIF_SEL_MOST_LEFT_WIN,
-
- /* Select Most right window for bc calculation */
- VPFE_ISIF_SEL_MOST_RIGHT_WIN,
-};
-
-/* Size of window in horizontal direction for horizontal bc */
-enum vpfe_isif_horz_bc_sz_h {
- VPFE_ISIF_HORZ_BC_SZ_H_2PIXELS,
- VPFE_ISIF_HORZ_BC_SZ_H_4PIXELS,
- VPFE_ISIF_HORZ_BC_SZ_H_8PIXELS,
- VPFE_ISIF_HORZ_BC_SZ_H_16PIXELS
-};
-
-/* Size of window in vertcal direction for vertical bc */
-enum vpfe_isif_horz_bc_sz_v {
- VPFE_ISIF_HORZ_BC_SZ_H_32PIXELS,
- VPFE_ISIF_HORZ_BC_SZ_H_64PIXELS,
- VPFE_ISIF_HORZ_BC_SZ_H_128PIXELS,
- VPFE_ISIF_HORZ_BC_SZ_H_256PIXELS
-};
-
-/**
- * Structure for Horizontal Black Clamp config params
- */
-struct vpfe_isif_horz_bclamp {
- /* horizontal clamp mode */
- enum vpfe_isif_horz_bc_mode mode;
- /**
- * pixel value limit enable.
- * 0 - limit disabled
- * 1 - pixel value limited to 1023
- */
- unsigned char clamp_pix_limit;
- /**
- * Select most left or right window for clamp val
- * calculation
- */
- enum vpfe_isif_horz_bc_base_win_sel base_win_sel_calc;
- /* Window count per color for calculation. range 1-32 */
- unsigned char win_count_calc;
- /* Window start position - horizontal for calculation. 0 - 8191 */
- unsigned short win_start_h_calc;
- /* Window start position - vertical for calculation 0 - 8191 */
- unsigned short win_start_v_calc;
- /* Width of the sample window in pixels for calculation */
- enum vpfe_isif_horz_bc_sz_h win_h_sz_calc;
- /* Height of the sample window in pixels for calculation */
- enum vpfe_isif_horz_bc_sz_v win_v_sz_calc;
-};
-
-/**
- * Black Clamp vertical reset values
- */
-enum vpfe_isif_vert_bc_reset_val_sel {
- /* Reset value used is the clamp value calculated */
- VPFE_ISIF_VERT_BC_USE_HORZ_CLAMP_VAL,
- /* Reset value used is reset_clamp_val configured */
- VPFE_ISIF_VERT_BC_USE_CONFIG_CLAMP_VAL,
- /* No update, previous image value is used */
- VPFE_ISIF_VERT_BC_NO_UPDATE
-};
-
-enum vpfe_isif_vert_bc_sz_h {
- VPFE_ISIF_VERT_BC_SZ_H_2PIXELS,
- VPFE_ISIF_VERT_BC_SZ_H_4PIXELS,
- VPFE_ISIF_VERT_BC_SZ_H_8PIXELS,
- VPFE_ISIF_VERT_BC_SZ_H_16PIXELS,
- VPFE_ISIF_VERT_BC_SZ_H_32PIXELS,
- VPFE_ISIF_VERT_BC_SZ_H_64PIXELS
-};
-
-/**
- * Structure for Vertical Black Clamp configuration params
- */
-struct vpfe_isif_vert_bclamp {
- /* Reset value selection for vertical clamp calculation */
- enum vpfe_isif_vert_bc_reset_val_sel reset_val_sel;
- /* U12 value if reset_sel = ISIF_BC_VERT_USE_CONFIG_CLAMP_VAL */
- unsigned short reset_clamp_val;
- /**
- * U8Q8. Line average coefficient used in vertical clamp
- * calculation
- */
- unsigned char line_ave_coef;
- /* Width in pixels of the optical black region used for calculation. */
- enum vpfe_isif_vert_bc_sz_h ob_h_sz_calc;
- /* Height of the optical black region for calculation */
- unsigned short ob_v_sz_calc;
- /* Optical black region start position - horizontal. 0 - 8191 */
- unsigned short ob_start_h;
- /* Optical black region start position - vertical 0 - 8191 */
- unsigned short ob_start_v;
-};
-
-/**
- * Structure for Black Clamp configuration params
- */
-struct vpfe_isif_black_clamp {
- /**
- * this offset value is added irrespective of the clamp
- * enable status. S13
- */
- unsigned short dc_offset;
- /**
- * Enable black/digital clamp value to be subtracted
- * from the image data
- */
- unsigned char en;
- /**
- * black clamp mode. same/separate clamp for 4 colors
- * 0 - disable - same clamp value for all colors
- * 1 - clamp value calculated separately for all colors
- */
- unsigned char bc_mode_color;
- /* Vertical start position for bc subtraction */
- unsigned short vert_start_sub;
- /* Black clamp for horizontal direction */
- struct vpfe_isif_horz_bclamp horz;
- /* Black clamp for vertical direction */
- struct vpfe_isif_vert_bclamp vert;
-};
-
-/*************************************************************************
- ** Color Space Conversion (CSC)
- *************************************************************************/
-/**
- * Number of Coefficient values used for CSC
- */
-#define VPFE_ISIF_CSC_NUM_COEFF 16
-
-struct float_8_bit {
- /* 8 bit integer part */
- __u8 integer;
- /* 8 bit decimal part */
- __u8 decimal;
-};
-
-struct float_16_bit {
- /* 16 bit integer part */
- __u16 integer;
- /* 16 bit decimal part */
- __u16 decimal;
-};
-
-/*************************************************************************
- ** Color Space Conversion parameters
- *************************************************************************/
-/**
- * Structure used for CSC config params
- */
-struct vpfe_isif_color_space_conv {
- /* Enable color space conversion */
- unsigned char en;
- /**
- * csc coefficient table. S8Q5, M00 at index 0, M01 at index 1, and
- * so forth
- */
- struct float_8_bit coeff[VPFE_ISIF_CSC_NUM_COEFF];
-};
-
-enum vpfe_isif_datasft {
- /* No Shift */
- VPFE_ISIF_NO_SHIFT,
- /* 1 bit Shift */
- VPFE_ISIF_1BIT_SHIFT,
- /* 2 bit Shift */
- VPFE_ISIF_2BIT_SHIFT,
- /* 3 bit Shift */
- VPFE_ISIF_3BIT_SHIFT,
- /* 4 bit Shift */
- VPFE_ISIF_4BIT_SHIFT,
- /* 5 bit Shift */
- VPFE_ISIF_5BIT_SHIFT,
- /* 6 bit Shift */
- VPFE_ISIF_6BIT_SHIFT
-};
-
-#define VPFE_ISIF_LINEAR_TAB_SIZE 192
-/*************************************************************************
- ** Linearization parameters
- *************************************************************************/
-/**
- * Structure for Sensor data linearization
- */
-struct vpfe_isif_linearize {
- /* Enable or Disable linearization of data */
- unsigned char en;
- /* Shift value applied */
- enum vpfe_isif_datasft corr_shft;
- /* scale factor applied U11Q10 */
- struct float_16_bit scale_fact;
- /* Size of the linear table */
- unsigned short table[VPFE_ISIF_LINEAR_TAB_SIZE];
-};
-
-/*************************************************************************
- ** ISIF Raw configuration parameters
- *************************************************************************/
-enum vpfe_isif_fmt_mode {
- VPFE_ISIF_SPLIT,
- VPFE_ISIF_COMBINE
-};
-
-enum vpfe_isif_lnum {
- VPFE_ISIF_1LINE,
- VPFE_ISIF_2LINES,
- VPFE_ISIF_3LINES,
- VPFE_ISIF_4LINES
-};
-
-enum vpfe_isif_line {
- VPFE_ISIF_1STLINE,
- VPFE_ISIF_2NDLINE,
- VPFE_ISIF_3RDLINE,
- VPFE_ISIF_4THLINE
-};
-
-struct vpfe_isif_fmtplen {
- /**
- * number of program entries for SET0, range 1 - 16
- * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is
- * ISIF_COMBINE
- */
- unsigned short plen0;
- /**
- * number of program entries for SET1, range 1 - 16
- * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is
- * ISIF_COMBINE
- */
- unsigned short plen1;
- /**
- * number of program entries for SET2, range 1 - 16
- * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is
- * ISIF_COMBINE
- */
- unsigned short plen2;
- /**
- * number of program entries for SET3, range 1 - 16
- * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is
- * ISIF_COMBINE
- */
- unsigned short plen3;
-};
-
-struct vpfe_isif_fmt_cfg {
- /* Split or combine or line alternate */
- enum vpfe_isif_fmt_mode fmtmode;
- /* enable or disable line alternating mode */
- unsigned char ln_alter_en;
- /* Split/combine line number */
- enum vpfe_isif_lnum lnum;
- /* Address increment Range 1 - 16 */
- unsigned int addrinc;
-};
-
-struct vpfe_isif_fmt_addr_ptr {
- /* Initial address */
- unsigned int init_addr;
- /* output line number */
- enum vpfe_isif_line out_line;
-};
-
-struct vpfe_isif_fmtpgm_ap {
- /* program address pointer */
- unsigned char pgm_aptr;
- /* program address increment or decrement */
- unsigned char pgmupdt;
-};
-
-struct vpfe_isif_data_formatter {
- /* Enable/Disable data formatter */
- unsigned char en;
- /* data formatter configuration */
- struct vpfe_isif_fmt_cfg cfg;
- /* Formatter program entries length */
- struct vpfe_isif_fmtplen plen;
- /* first pixel in a line fed to formatter */
- unsigned short fmtrlen;
- /* HD interval for output line. Only valid when split line */
- unsigned short fmthcnt;
- /* formatter address pointers */
- struct vpfe_isif_fmt_addr_ptr fmtaddr_ptr[16];
- /* program enable/disable */
- unsigned char pgm_en[32];
- /* program address pointers */
- struct vpfe_isif_fmtpgm_ap fmtpgm_ap[32];
-};
-
-struct vpfe_isif_df_csc {
- /* Color Space Conversion configuration, 0 - csc, 1 - df */
- unsigned int df_or_csc;
- /* csc configuration valid if df_or_csc is 0 */
- struct vpfe_isif_color_space_conv csc;
- /* data formatter configuration valid if df_or_csc is 1 */
- struct vpfe_isif_data_formatter df;
- /* start pixel in a line at the input */
- unsigned int start_pix;
- /* number of pixels in input line */
- unsigned int num_pixels;
- /* start line at the input */
- unsigned int start_line;
- /* number of lines at the input */
- unsigned int num_lines;
-};
-
-struct vpfe_isif_gain_offsets_adj {
- /* Enable or Disable Gain adjustment for SDRAM data */
- unsigned char gain_sdram_en;
- /* Enable or Disable Gain adjustment for IPIPE data */
- unsigned char gain_ipipe_en;
- /* Enable or Disable Gain adjustment for H3A data */
- unsigned char gain_h3a_en;
- /* Enable or Disable Gain adjustment for SDRAM data */
- unsigned char offset_sdram_en;
- /* Enable or Disable Gain adjustment for IPIPE data */
- unsigned char offset_ipipe_en;
- /* Enable or Disable Gain adjustment for H3A data */
- unsigned char offset_h3a_en;
-};
-
-struct vpfe_isif_cul {
- /* Horizontal Cull pattern for odd lines */
- unsigned char hcpat_odd;
- /* Horizontal Cull pattern for even lines */
- unsigned char hcpat_even;
- /* Vertical Cull pattern */
- unsigned char vcpat;
- /* Enable or disable lpf. Apply when cull is enabled */
- unsigned char en_lpf;
-};
-
-/* all the stuff in this struct will be provided by userland */
-struct vpfe_isif_raw_config {
- /* Linearization parameters for image sensor data input */
- struct vpfe_isif_linearize linearize;
- /* Data formatter or CSC */
- struct vpfe_isif_df_csc df_csc;
- /* Defect Pixel Correction (DFC) confguration */
- struct vpfe_isif_dfc dfc;
- /* Black/Digital Clamp configuration */
- struct vpfe_isif_black_clamp bclamp;
- /* Gain, offset adjustments */
- struct vpfe_isif_gain_offsets_adj gain_offset;
- /* Culling */
- struct vpfe_isif_cul culling;
- /* horizontal offset for Gain/LSC/DFC */
- unsigned short horz_offset;
- /* vertical offset for Gain/LSC/DFC */
- unsigned short vert_offset;
-};
-
-/**********************************************************************
- * IPIPE API Structures
- **********************************************************************/
-
-/* IPIPE module configurations */
-
-/* IPIPE input configuration */
-#define VPFE_IPIPE_INPUT_CONFIG BIT(0)
-/* LUT based Defect Pixel Correction */
-#define VPFE_IPIPE_LUTDPC BIT(1)
-/* On the fly (OTF) Defect Pixel Correction */
-#define VPFE_IPIPE_OTFDPC BIT(2)
-/* Noise Filter - 1 */
-#define VPFE_IPIPE_NF1 BIT(3)
-/* Noise Filter - 2 */
-#define VPFE_IPIPE_NF2 BIT(4)
-/* White Balance. Also a control ID */
-#define VPFE_IPIPE_WB BIT(5)
-/* 1st RGB to RBG Blend module */
-#define VPFE_IPIPE_RGB2RGB_1 BIT(6)
-/* 2nd RGB to RBG Blend module */
-#define VPFE_IPIPE_RGB2RGB_2 BIT(7)
-/* Gamma Correction */
-#define VPFE_IPIPE_GAMMA BIT(8)
-/* 3D LUT color conversion */
-#define VPFE_IPIPE_3D_LUT BIT(9)
-/* RGB to YCbCr module */
-#define VPFE_IPIPE_RGB2YUV BIT(10)
-/* YUV 422 conversion module */
-#define VPFE_IPIPE_YUV422_CONV BIT(11)
-/* Edge Enhancement */
-#define VPFE_IPIPE_YEE BIT(12)
-/* Green Imbalance Correction */
-#define VPFE_IPIPE_GIC BIT(13)
-/* CFA Interpolation */
-#define VPFE_IPIPE_CFA BIT(14)
-/* Chroma Artifact Reduction */
-#define VPFE_IPIPE_CAR BIT(15)
-/* Chroma Gain Suppression */
-#define VPFE_IPIPE_CGS BIT(16)
-/* Global brightness and contrast control */
-#define VPFE_IPIPE_GBCE BIT(17)
-
-#define VPFE_IPIPE_MAX_MODULES 18
-
-struct ipipe_float_u16 {
- unsigned short integer;
- unsigned short decimal;
-};
-
-struct ipipe_float_s16 {
- short integer;
- unsigned short decimal;
-};
-
-struct ipipe_float_u8 {
- unsigned char integer;
- unsigned char decimal;
-};
-
-/* Copy method selection for vertical correction
- * Used when ipipe_dfc_corr_meth is IPIPE_DPC_CTORB_AFTER_HINT
- */
-enum vpfe_ipipe_dpc_corr_meth {
- /* replace by black or white dot specified by repl_white */
- VPFE_IPIPE_DPC_REPL_BY_DOT = 0,
- /* Copy from left */
- VPFE_IPIPE_DPC_CL = 1,
- /* Copy from right */
- VPFE_IPIPE_DPC_CR = 2,
- /* Horizontal interpolation */
- VPFE_IPIPE_DPC_H_INTP = 3,
- /* Vertical interpolation */
- VPFE_IPIPE_DPC_V_INTP = 4,
- /* Copy from top */
- VPFE_IPIPE_DPC_CT = 5,
- /* Copy from bottom */
- VPFE_IPIPE_DPC_CB = 6,
- /* 2D interpolation */
- VPFE_IPIPE_DPC_2D_INTP = 7,
-};
-
-struct vpfe_ipipe_lutdpc_entry {
- /* Horizontal position */
- unsigned short horz_pos;
- /* vertical position */
- unsigned short vert_pos;
- enum vpfe_ipipe_dpc_corr_meth method;
-};
-
-#define VPFE_IPIPE_MAX_SIZE_DPC 256
-
-/* Structure for configuring DPC module */
-struct vpfe_ipipe_lutdpc {
- /* 0 - disable, 1 - enable */
- unsigned char en;
- /* 0 - replace with black dot, 1 - white dot when correction
- * method is IPIPE_DFC_REPL_BY_DOT=0,
- */
- unsigned char repl_white;
- /* number of entries in the correction table. Currently only
- * support up-to 256 entries. infinite mode is not supported
- */
- unsigned short dpc_size;
- struct vpfe_ipipe_lutdpc_entry table[VPFE_IPIPE_MAX_SIZE_DPC];
-};
-
-enum vpfe_ipipe_otfdpc_det_meth {
- VPFE_IPIPE_DPC_OTF_MIN_MAX,
- VPFE_IPIPE_DPC_OTF_MIN_MAX2
-};
-
-struct vpfe_ipipe_otfdpc_thr {
- unsigned short r;
- unsigned short gr;
- unsigned short gb;
- unsigned short b;
-};
-
-enum vpfe_ipipe_otfdpc_alg {
- VPFE_IPIPE_OTFDPC_2_0,
- VPFE_IPIPE_OTFDPC_3_0
-};
-
-struct vpfe_ipipe_otfdpc_2_0_cfg {
- /* defect detection threshold for MIN_MAX2 method (DPC 2.0 alg) */
- struct vpfe_ipipe_otfdpc_thr det_thr;
- /* defect correction threshold for MIN_MAX2 method (DPC 2.0 alg) or
- * maximum value for MIN_MAX method
- */
- struct vpfe_ipipe_otfdpc_thr corr_thr;
-};
-
-struct vpfe_ipipe_otfdpc_3_0_cfg {
- /* DPC3.0 activity adj shf. activity = (max2-min2) >> (6 -shf)
- */
- unsigned char act_adj_shf;
- /* DPC3.0 detection threshold, THR */
- unsigned short det_thr;
- /* DPC3.0 detection threshold slope, SLP */
- unsigned short det_slp;
- /* DPC3.0 detection threshold min, MIN */
- unsigned short det_thr_min;
- /* DPC3.0 detection threshold max, MAX */
- unsigned short det_thr_max;
- /* DPC3.0 correction threshold, THR */
- unsigned short corr_thr;
- /* DPC3.0 correction threshold slope, SLP */
- unsigned short corr_slp;
- /* DPC3.0 correction threshold min, MIN */
- unsigned short corr_thr_min;
- /* DPC3.0 correction threshold max, MAX */
- unsigned short corr_thr_max;
-};
-
-struct vpfe_ipipe_otfdpc {
- /* 0 - disable, 1 - enable */
- unsigned char en;
- /* defect detection method */
- enum vpfe_ipipe_otfdpc_det_meth det_method;
- /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is
- * used
- */
- enum vpfe_ipipe_otfdpc_alg alg;
- union {
- /* if alg is IPIPE_OTFDPC_2_0 */
- struct vpfe_ipipe_otfdpc_2_0_cfg dpc_2_0;
- /* if alg is IPIPE_OTFDPC_3_0 */
- struct vpfe_ipipe_otfdpc_3_0_cfg dpc_3_0;
- } alg_cfg;
-};
-
-/* Threshold values table size */
-#define VPFE_IPIPE_NF_THR_TABLE_SIZE 8
-/* Intensity values table size */
-#define VPFE_IPIPE_NF_STR_TABLE_SIZE 8
-
-/* NF, sampling method for green pixels */
-enum vpfe_ipipe_nf_sampl_meth {
- /* Same as R or B */
- VPFE_IPIPE_NF_BOX,
- /* Diamond mode */
- VPFE_IPIPE_NF_DIAMOND
-};
-
-/* Structure for configuring NF module */
-struct vpfe_ipipe_nf {
- /* 0 - disable, 1 - enable */
- unsigned char en;
- /* Sampling method for green pixels */
- enum vpfe_ipipe_nf_sampl_meth gr_sample_meth;
- /* Down shift value in LUT reference address
- */
- unsigned char shft_val;
- /* Spread value in NF algorithm
- */
- unsigned char spread_val;
- /* Apply LSC gain to threshold. Enable this only if
- * LSC is enabled in ISIF
- */
- unsigned char apply_lsc_gain;
- /* Threshold values table */
- unsigned short thr[VPFE_IPIPE_NF_THR_TABLE_SIZE];
- /* intensity values table */
- unsigned char str[VPFE_IPIPE_NF_STR_TABLE_SIZE];
- /* Edge detection minimum threshold */
- unsigned short edge_det_min_thr;
- /* Edge detection maximum threshold */
- unsigned short edge_det_max_thr;
-};
-
-enum vpfe_ipipe_gic_alg {
- VPFE_IPIPE_GIC_ALG_CONST_GAIN,
- VPFE_IPIPE_GIC_ALG_ADAPT_GAIN
-};
-
-enum vpfe_ipipe_gic_thr_sel {
- VPFE_IPIPE_GIC_THR_REG,
- VPFE_IPIPE_GIC_THR_NF
-};
-
-enum vpfe_ipipe_gic_wt_fn_type {
- /* Use difference as index */
- VPFE_IPIPE_GIC_WT_FN_TYP_DIF,
- /* Use weight function as index */
- VPFE_IPIPE_GIC_WT_FN_TYP_HP_VAL
-};
-
-/* structure for Green Imbalance Correction */
-struct vpfe_ipipe_gic {
- /* 0 - disable, 1 - enable */
- unsigned char en;
- /* 0 - Constant gain , 1 - Adaptive gain algorithm */
- enum vpfe_ipipe_gic_alg gic_alg;
- /* GIC gain or weight. Used for Constant gain and Adaptive algorithms
- */
- unsigned short gain;
- /* Threshold selection. GIC register values or NF2 thr table */
- enum vpfe_ipipe_gic_thr_sel thr_sel;
- /* thr1. Used when thr_sel is IPIPE_GIC_THR_REG */
- unsigned short thr;
- /* this value is used for thr2-thr1, thr3-thr2 or
- * thr4-thr3 when wt_fn_type is index. Otherwise it
- * is the
- */
- unsigned short slope;
- /* Apply LSC gain to threshold. Enable this only if
- * LSC is enabled in ISIF & thr_sel is IPIPE_GIC_THR_REG
- */
- unsigned char apply_lsc_gain;
- /* Multiply Nf2 threshold by this gain. Use this when thr_sel
- * is IPIPE_GIC_THR_NF
- */
- struct ipipe_float_u8 nf2_thr_gain;
- /* Weight function uses difference as index or high pass value.
- * Used for adaptive gain algorithm
- */
- enum vpfe_ipipe_gic_wt_fn_type wt_fn_type;
-};
-
-/* Structure for configuring WB module */
-struct vpfe_ipipe_wb {
- /* Offset (S12) for R */
- short ofst_r;
- /* Offset (S12) for Gr */
- short ofst_gr;
- /* Offset (S12) for Gb */
- short ofst_gb;
- /* Offset (S12) for B */
- short ofst_b;
- /* Gain (U13Q9) for Red */
- struct ipipe_float_u16 gain_r;
- /* Gain (U13Q9) for Gr */
- struct ipipe_float_u16 gain_gr;
- /* Gain (U13Q9) for Gb */
- struct ipipe_float_u16 gain_gb;
- /* Gain (U13Q9) for Blue */
- struct ipipe_float_u16 gain_b;
-};
-
-enum vpfe_ipipe_cfa_alg {
- /* Algorithm is 2DirAC */
- VPFE_IPIPE_CFA_ALG_2DIRAC,
- /* Algorithm is 2DirAC + Digital Antialiasing (DAA) */
- VPFE_IPIPE_CFA_ALG_2DIRAC_DAA,
- /* Algorithm is DAA */
- VPFE_IPIPE_CFA_ALG_DAA
-};
-
-/* Structure for CFA Interpolation */
-struct vpfe_ipipe_cfa {
- /* 2DirAC or 2DirAC + DAA */
- enum vpfe_ipipe_cfa_alg alg;
- /* 2Dir CFA HP value Low Threshold */
- unsigned short hpf_thr_2dir;
- /* 2Dir CFA HP value slope */
- unsigned short hpf_slp_2dir;
- /* 2Dir CFA HP mix threshold */
- unsigned short hp_mix_thr_2dir;
- /* 2Dir CFA HP mix slope */
- unsigned short hp_mix_slope_2dir;
- /* 2Dir Direction threshold */
- unsigned short dir_thr_2dir;
- /* 2Dir Direction slope */
- unsigned short dir_slope_2dir;
- /* 2Dir Non Directional Weight */
- unsigned short nd_wt_2dir;
- /* DAA Mono Hue Fraction */
- unsigned short hue_fract_daa;
- /* DAA Mono Edge threshold */
- unsigned short edge_thr_daa;
- /* DAA Mono threshold minimum */
- unsigned short thr_min_daa;
- /* DAA Mono threshold slope */
- unsigned short thr_slope_daa;
- /* DAA Mono slope minimum */
- unsigned short slope_min_daa;
- /* DAA Mono slope slope */
- unsigned short slope_slope_daa;
- /* DAA Mono LP wight */
- unsigned short lp_wt_daa;
-};
-
-/* Struct for configuring RGB2RGB blending module */
-struct vpfe_ipipe_rgb2rgb {
- /* Matrix coefficient for RR S12Q8 for ID = 1 and S11Q8 for ID = 2 */
- struct ipipe_float_s16 coef_rr;
- /* Matrix coefficient for GR S12Q8/S11Q8 */
- struct ipipe_float_s16 coef_gr;
- /* Matrix coefficient for BR S12Q8/S11Q8 */
- struct ipipe_float_s16 coef_br;
- /* Matrix coefficient for RG S12Q8/S11Q8 */
- struct ipipe_float_s16 coef_rg;
- /* Matrix coefficient for GG S12Q8/S11Q8 */
- struct ipipe_float_s16 coef_gg;
- /* Matrix coefficient for BG S12Q8/S11Q8 */
- struct ipipe_float_s16 coef_bg;
- /* Matrix coefficient for RB S12Q8/S11Q8 */
- struct ipipe_float_s16 coef_rb;
- /* Matrix coefficient for GB S12Q8/S11Q8 */
- struct ipipe_float_s16 coef_gb;
- /* Matrix coefficient for BB S12Q8/S11Q8 */
- struct ipipe_float_s16 coef_bb;
- /* Output offset for R S13/S11 */
- int out_ofst_r;
- /* Output offset for G S13/S11 */
- int out_ofst_g;
- /* Output offset for B S13/S11 */
- int out_ofst_b;
-};
-
-#define VPFE_IPIPE_MAX_SIZE_GAMMA 512
-
-enum vpfe_ipipe_gamma_tbl_size {
- VPFE_IPIPE_GAMMA_TBL_SZ_64 = 64,
- VPFE_IPIPE_GAMMA_TBL_SZ_128 = 128,
- VPFE_IPIPE_GAMMA_TBL_SZ_256 = 256,
- VPFE_IPIPE_GAMMA_TBL_SZ_512 = 512,
-};
-
-enum vpfe_ipipe_gamma_tbl_sel {
- VPFE_IPIPE_GAMMA_TBL_RAM = 0,
- VPFE_IPIPE_GAMMA_TBL_ROM = 1,
-};
-
-struct vpfe_ipipe_gamma_entry {
- /* 10 bit slope */
- short slope;
- /* 10 bit offset */
- unsigned short offset;
-};
-
-/* Structure for configuring Gamma correction module */
-struct vpfe_ipipe_gamma {
- /* 0 - Enable Gamma correction for Red
- * 1 - bypass Gamma correction. Data is divided by 16
- */
- unsigned char bypass_r;
- /* 0 - Enable Gamma correction for Blue
- * 1 - bypass Gamma correction. Data is divided by 16
- */
- unsigned char bypass_b;
- /* 0 - Enable Gamma correction for Green
- * 1 - bypass Gamma correction. Data is divided by 16
- */
- unsigned char bypass_g;
- /* IPIPE_GAMMA_TBL_RAM or IPIPE_GAMMA_TBL_ROM */
- enum vpfe_ipipe_gamma_tbl_sel tbl_sel;
- /* Table size for RAM gamma table.
- */
- enum vpfe_ipipe_gamma_tbl_size tbl_size;
- /* R table */
- struct vpfe_ipipe_gamma_entry table_r[VPFE_IPIPE_MAX_SIZE_GAMMA];
- /* Blue table */
- struct vpfe_ipipe_gamma_entry table_b[VPFE_IPIPE_MAX_SIZE_GAMMA];
- /* Green table */
- struct vpfe_ipipe_gamma_entry table_g[VPFE_IPIPE_MAX_SIZE_GAMMA];
-};
-
-#define VPFE_IPIPE_MAX_SIZE_3D_LUT 729
-
-struct vpfe_ipipe_3d_lut_entry {
- /* 10 bit entry for red */
- unsigned short r;
- /* 10 bit entry for green */
- unsigned short g;
- /* 10 bit entry for blue */
- unsigned short b;
-};
-
-/* structure for 3D-LUT */
-struct vpfe_ipipe_3d_lut {
- /* enable/disable 3D lut */
- unsigned char en;
- /* 3D - LUT table entry */
- struct vpfe_ipipe_3d_lut_entry table[VPFE_IPIPE_MAX_SIZE_3D_LUT];
-};
-
-/* Struct for configuring rgb2ycbcr module */
-struct vpfe_ipipe_rgb2yuv {
- /* Matrix coefficient for RY S12Q8 */
- struct ipipe_float_s16 coef_ry;
- /* Matrix coefficient for GY S12Q8 */
- struct ipipe_float_s16 coef_gy;
- /* Matrix coefficient for BY S12Q8 */
- struct ipipe_float_s16 coef_by;
- /* Matrix coefficient for RCb S12Q8 */
- struct ipipe_float_s16 coef_rcb;
- /* Matrix coefficient for GCb S12Q8 */
- struct ipipe_float_s16 coef_gcb;
- /* Matrix coefficient for BCb S12Q8 */
- struct ipipe_float_s16 coef_bcb;
- /* Matrix coefficient for RCr S12Q8 */
- struct ipipe_float_s16 coef_rcr;
- /* Matrix coefficient for GCr S12Q8 */
- struct ipipe_float_s16 coef_gcr;
- /* Matrix coefficient for BCr S12Q8 */
- struct ipipe_float_s16 coef_bcr;
- /* Output offset for R S11 */
- int out_ofst_y;
- /* Output offset for Cb S11 */
- int out_ofst_cb;
- /* Output offset for Cr S11 */
- int out_ofst_cr;
-};
-
-enum vpfe_ipipe_gbce_type {
- VPFE_IPIPE_GBCE_Y_VAL_TBL = 0,
- VPFE_IPIPE_GBCE_GAIN_TBL = 1,
-};
-
-#define VPFE_IPIPE_MAX_SIZE_GBCE_LUT 1024
-
-/* structure for Global brightness and Contrast */
-struct vpfe_ipipe_gbce {
- /* enable/disable GBCE */
- unsigned char en;
- /* Y - value table or Gain table */
- enum vpfe_ipipe_gbce_type type;
- /* ptr to LUT for GBCE with 1024 entries */
- unsigned short table[VPFE_IPIPE_MAX_SIZE_GBCE_LUT];
-};
-
-/* Chrominance position. Applicable only for YCbCr input
- * Applied after edge enhancement
- */
-enum vpfe_chr_pos {
- /* Co-siting, same position with luminance */
- VPFE_IPIPE_YUV422_CHR_POS_COSITE = 0,
- /* Centering, In the middle of luminance */
- VPFE_IPIPE_YUV422_CHR_POS_CENTRE = 1,
-};
-
-/* Structure for configuring yuv422 conversion module */
-struct vpfe_ipipe_yuv422_conv {
- /* Max Chrominance value */
- unsigned char en_chrom_lpf;
- /* 1 - enable LPF for chrminance, 0 - disable */
- enum vpfe_chr_pos chrom_pos;
-};
-
-#define VPFE_IPIPE_MAX_SIZE_YEE_LUT 1024
-
-enum vpfe_ipipe_yee_merge_meth {
- VPFE_IPIPE_YEE_ABS_MAX = 0,
- VPFE_IPIPE_YEE_EE_ES = 1,
-};
-
-/* Structure for configuring YUV Edge Enhancement module */
-struct vpfe_ipipe_yee {
- /* 1 - enable enhancement, 0 - disable */
- unsigned char en;
- /* enable/disable halo reduction in edge sharpner */
- unsigned char en_halo_red;
- /* Merge method between Edge Enhancer and Edge sharpner */
- enum vpfe_ipipe_yee_merge_meth merge_meth;
- /* HPF Shift length */
- unsigned char hpf_shft;
- /* HPF Coefficient 00, S10 */
- short hpf_coef_00;
- /* HPF Coefficient 01, S10 */
- short hpf_coef_01;
- /* HPF Coefficient 02, S10 */
- short hpf_coef_02;
- /* HPF Coefficient 10, S10 */
- short hpf_coef_10;
- /* HPF Coefficient 11, S10 */
- short hpf_coef_11;
- /* HPF Coefficient 12, S10 */
- short hpf_coef_12;
- /* HPF Coefficient 20, S10 */
- short hpf_coef_20;
- /* HPF Coefficient 21, S10 */
- short hpf_coef_21;
- /* HPF Coefficient 22, S10 */
- short hpf_coef_22;
- /* Lower threshold before referring to LUT */
- unsigned short yee_thr;
- /* Edge sharpener Gain */
- unsigned short es_gain;
- /* Edge sharpener lower threshold */
- unsigned short es_thr1;
- /* Edge sharpener upper threshold */
- unsigned short es_thr2;
- /* Edge sharpener gain on gradient */
- unsigned short es_gain_grad;
- /* Edge sharpener offset on gradient */
- unsigned short es_ofst_grad;
- /* Ptr to EE table. Must have 1024 entries */
- short table[VPFE_IPIPE_MAX_SIZE_YEE_LUT];
-};
-
-enum vpfe_ipipe_car_meth {
- /* Chromatic Gain Control */
- VPFE_IPIPE_CAR_CHR_GAIN_CTRL = 0,
- /* Dynamic switching between CHR_GAIN_CTRL
- * and MED_FLTR
- */
- VPFE_IPIPE_CAR_DYN_SWITCH = 1,
- /* Median Filter */
- VPFE_IPIPE_CAR_MED_FLTR = 2,
-};
-
-enum vpfe_ipipe_car_hpf_type {
- VPFE_IPIPE_CAR_HPF_Y = 0,
- VPFE_IPIPE_CAR_HPF_H = 1,
- VPFE_IPIPE_CAR_HPF_V = 2,
- VPFE_IPIPE_CAR_HPF_2D = 3,
- /* 2D HPF from YUV Edge Enhancement */
- VPFE_IPIPE_CAR_HPF_2D_YEE = 4,
-};
-
-struct vpfe_ipipe_car_gain {
- /* csup_gain */
- unsigned char gain;
- /* csup_shf. */
- unsigned char shft;
- /* gain minimum */
- unsigned short gain_min;
-};
-
-/* Structure for Chromatic Artifact Reduction */
-struct vpfe_ipipe_car {
- /* enable/disable */
- unsigned char en;
- /* Gain control or Dynamic switching */
- enum vpfe_ipipe_car_meth meth;
- /* Gain1 function configuration for Gain control */
- struct vpfe_ipipe_car_gain gain1;
- /* Gain2 function configuration for Gain control */
- struct vpfe_ipipe_car_gain gain2;
- /* HPF type used for CAR */
- enum vpfe_ipipe_car_hpf_type hpf;
- /* csup_thr: HPF threshold for Gain control */
- unsigned char hpf_thr;
- /* Down shift value for hpf. 2 bits */
- unsigned char hpf_shft;
- /* switch limit for median filter */
- unsigned char sw0;
- /* switch coefficient for Gain control */
- unsigned char sw1;
-};
-
-/* structure for Chromatic Gain Suppression */
-struct vpfe_ipipe_cgs {
- /* enable/disable */
- unsigned char en;
- /* gain1 bright side threshold */
- unsigned char h_thr;
- /* gain1 bright side slope */
- unsigned char h_slope;
- /* gain1 down shift value for bright side */
- unsigned char h_shft;
- /* gain1 bright side minimum gain */
- unsigned char h_min;
-};
-
-/* Max pixels allowed in the input. If above this either decimation
- * or frame division mode to be enabled
- */
-#define VPFE_IPIPE_MAX_INPUT_WIDTH 2600
-
-struct vpfe_ipipe_input_config {
- unsigned int vst;
- unsigned int hst;
-};
-
-/**
- * struct vpfe_ipipe_config - IPIPE engine configuration (user)
- * @input_config: Pointer to structure for ipipe configuration.
- * @flag: Specifies which ISP IPIPE functions should be enabled.
- * @lutdpc: Pointer to luma enhancement structure.
- * @otfdpc: Pointer to structure for defect correction.
- * @nf1: Pointer to structure for Noise Filter.
- * @nf2: Pointer to structure for Noise Filter.
- * @gic: Pointer to structure for Green Imbalance.
- * @wbal: Pointer to structure for White Balance.
- * @cfa: Pointer to structure containing the CFA interpolation.
- * @rgb2rgb1: Pointer to structure for RGB to RGB Blending.
- * @rgb2rgb2: Pointer to structure for RGB to RGB Blending.
- * @gamma: Pointer to gamma structure.
- * @lut: Pointer to structure for 3D LUT.
- * @rgb2yuv: Pointer to structure for RGB-YCbCr conversion.
- * @gbce: Pointer to structure for Global Brightness,Contrast Control.
- * @yuv422_conv: Pointer to structure for YUV 422 conversion.
- * @yee: Pointer to structure for Edge Enhancer.
- * @car: Pointer to structure for Chromatic Artifact Reduction.
- * @cgs: Pointer to structure for Chromatic Gain Suppression.
- */
-struct vpfe_ipipe_config {
- __u32 flag;
- struct vpfe_ipipe_input_config __user *input_config;
- struct vpfe_ipipe_lutdpc __user *lutdpc;
- struct vpfe_ipipe_otfdpc __user *otfdpc;
- struct vpfe_ipipe_nf __user *nf1;
- struct vpfe_ipipe_nf __user *nf2;
- struct vpfe_ipipe_gic __user *gic;
- struct vpfe_ipipe_wb __user *wbal;
- struct vpfe_ipipe_cfa __user *cfa;
- struct vpfe_ipipe_rgb2rgb __user *rgb2rgb1;
- struct vpfe_ipipe_rgb2rgb __user *rgb2rgb2;
- struct vpfe_ipipe_gamma __user *gamma;
- struct vpfe_ipipe_3d_lut __user *lut;
- struct vpfe_ipipe_rgb2yuv __user *rgb2yuv;
- struct vpfe_ipipe_gbce __user *gbce;
- struct vpfe_ipipe_yuv422_conv __user *yuv422_conv;
- struct vpfe_ipipe_yee __user *yee;
- struct vpfe_ipipe_car __user *car;
- struct vpfe_ipipe_cgs __user *cgs;
-};
-
-/*******************************************************************
- ** Resizer API structures
- *******************************************************************/
-/* Interpolation types used for horizontal rescale */
-enum vpfe_rsz_intp_t {
- VPFE_RSZ_INTP_CUBIC,
- VPFE_RSZ_INTP_LINEAR
-};
-
-/* Horizontal LPF intensity selection */
-enum vpfe_rsz_h_lpf_lse_t {
- VPFE_RSZ_H_LPF_LSE_INTERN,
- VPFE_RSZ_H_LPF_LSE_USER_VAL
-};
-
-enum vpfe_rsz_down_scale_ave_sz {
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- VPFE_IPIPE_DWN_SCALE_1_OVER_4,
- VPFE_IPIPE_DWN_SCALE_1_OVER_8,
- VPFE_IPIPE_DWN_SCALE_1_OVER_16,
- VPFE_IPIPE_DWN_SCALE_1_OVER_32,
- VPFE_IPIPE_DWN_SCALE_1_OVER_64,
- VPFE_IPIPE_DWN_SCALE_1_OVER_128,
- VPFE_IPIPE_DWN_SCALE_1_OVER_256
-};
-
-struct vpfe_rsz_output_spec {
- /* enable horizontal flip */
- unsigned char h_flip;
- /* enable vertical flip */
- unsigned char v_flip;
- /* line start offset for y. */
- unsigned int vst_y;
- /* line start offset for c. Only for 420 */
- unsigned int vst_c;
- /* vertical rescale interpolation type, YCbCr or Luminance */
- enum vpfe_rsz_intp_t v_typ_y;
- /* vertical rescale interpolation type for Chrominance */
- enum vpfe_rsz_intp_t v_typ_c;
- /* vertical lpf intensity - Luminance */
- unsigned char v_lpf_int_y;
- /* vertical lpf intensity - Chrominance */
- unsigned char v_lpf_int_c;
- /* horizontal rescale interpolation types, YCbCr or Luminance */
- enum vpfe_rsz_intp_t h_typ_y;
- /* horizontal rescale interpolation types, Chrominance */
- enum vpfe_rsz_intp_t h_typ_c;
- /* horizontal lpf intensity - Luminance */
- unsigned char h_lpf_int_y;
- /* horizontal lpf intensity - Chrominance */
- unsigned char h_lpf_int_c;
- /* Use down scale mode for scale down */
- unsigned char en_down_scale;
- /* if downscale, set the downscale more average size for horizontal
- * direction. Used only if output width and height is less than
- * input sizes
- */
- enum vpfe_rsz_down_scale_ave_sz h_dscale_ave_sz;
- /* if downscale, set the downscale more average size for vertical
- * direction. Used only if output width and height is less than
- * input sizes
- */
- enum vpfe_rsz_down_scale_ave_sz v_dscale_ave_sz;
- /* Y offset. If set, the offset would be added to the base address
- */
- unsigned int user_y_ofst;
- /* C offset. If set, the offset would be added to the base address
- */
- unsigned int user_c_ofst;
-};
-
-struct vpfe_rsz_config_params {
- unsigned int vst;
- /* horizontal start position of the image
- * data to IPIPE
- */
- unsigned int hst;
- /* output spec of the image data coming out of resizer - 0(UYVY).
- */
- struct vpfe_rsz_output_spec output1;
- /* output spec of the image data coming out of resizer - 1(UYVY).
- */
- struct vpfe_rsz_output_spec output2;
- /* 0 , chroma sample at odd pixel, 1 - even pixel */
- unsigned char chroma_sample_even;
- unsigned char frame_div_mode_en;
- unsigned char yuv_y_min;
- unsigned char yuv_y_max;
- unsigned char yuv_c_min;
- unsigned char yuv_c_max;
- enum vpfe_chr_pos out_chr_pos;
- unsigned char bypass;
-};
-
-/* Structure for VIDIOC_VPFE_RSZ_[S/G]_CONFIG IOCTLs */
-struct vpfe_rsz_config {
- struct vpfe_rsz_config_params *config;
-};
-
-#endif /* _DAVINCI_VPFE_USER_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe.c b/drivers/staging/media/davinci_vpfe/dm365_ipipe.c
deleted file mode 100644
index 52397ad0e3e2..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_ipipe.c
+++ /dev/null
@@ -1,1852 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- *
- *
- * IPIPE allows fine tuning of the input image using different
- * tuning modules in IPIPE. Some examples :- Noise filter, Defect
- * pixel correction etc. It essentially operate on Bayer Raw data
- * or YUV raw data. To do image tuning, application call,
- *
- */
-
-#include <linux/slab.h>
-#include <linux/bitops.h>
-
-#include "dm365_ipipe.h"
-#include "dm365_ipipe_hw.h"
-#include "vpfe_mc_capture.h"
-
-#define MIN_OUT_WIDTH 32
-#define MIN_OUT_HEIGHT 32
-
-/* ipipe input format's */
-static const unsigned int ipipe_input_fmts[] = {
- MEDIA_BUS_FMT_UYVY8_2X8,
- MEDIA_BUS_FMT_SGRBG12_1X12,
- MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
- MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8,
-};
-
-/* ipipe output format's */
-static const unsigned int ipipe_output_fmts[] = {
- MEDIA_BUS_FMT_UYVY8_2X8,
-};
-
-static int ipipe_validate_lutdpc_params(struct vpfe_ipipe_lutdpc *lutdpc)
-{
- int i;
-
- if (lutdpc->en > 1 || lutdpc->repl_white > 1 ||
- lutdpc->dpc_size > LUT_DPC_MAX_SIZE)
- return -EINVAL;
-
- if (lutdpc->en)
- return -EINVAL;
-
- for (i = 0; i < lutdpc->dpc_size; i++)
- if (lutdpc->table[i].horz_pos > LUT_DPC_H_POS_MASK ||
- lutdpc->table[i].vert_pos > LUT_DPC_V_POS_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_lutdpc_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_lutdpc *lutdpc = &ipipe->config.lutdpc;
- struct vpfe_ipipe_lutdpc *dpc_param;
-
- if (!param) {
- memset((void *)lutdpc, 0, sizeof(struct vpfe_ipipe_lutdpc));
- goto success;
- }
-
- dpc_param = param;
- lutdpc->en = dpc_param->en;
- lutdpc->repl_white = dpc_param->repl_white;
- lutdpc->dpc_size = dpc_param->dpc_size;
- memcpy(&lutdpc->table, &dpc_param->table,
- (dpc_param->dpc_size * sizeof(struct vpfe_ipipe_lutdpc_entry)));
- if (ipipe_validate_lutdpc_params(lutdpc) < 0)
- return -EINVAL;
-
-success:
- ipipe_set_lutdpc_regs(ipipe->base_addr, ipipe->isp5_base_addr, lutdpc);
-
- return 0;
-}
-
-static int ipipe_get_lutdpc_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_lutdpc *lut_param = param;
- struct vpfe_ipipe_lutdpc *lutdpc = &ipipe->config.lutdpc;
-
- lut_param->en = lutdpc->en;
- lut_param->repl_white = lutdpc->repl_white;
- lut_param->dpc_size = lutdpc->dpc_size;
- memcpy(&lut_param->table, &lutdpc->table,
- (lutdpc->dpc_size * sizeof(struct vpfe_ipipe_lutdpc_entry)));
-
- return 0;
-}
-
-static int ipipe_set_input_config(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_input_config *config = &ipipe->config.input_config;
-
- if (!param)
- memset(config, 0, sizeof(struct vpfe_ipipe_input_config));
- else
- memcpy(config, param, sizeof(struct vpfe_ipipe_input_config));
- return 0;
-}
-
-static int ipipe_get_input_config(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_input_config *config = &ipipe->config.input_config;
-
- if (!param)
- return -EINVAL;
-
- memcpy(param, config, sizeof(struct vpfe_ipipe_input_config));
-
- return 0;
-}
-
-static int ipipe_validate_otfdpc_params(struct vpfe_ipipe_otfdpc *dpc_param)
-{
- struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_2_0;
- struct vpfe_ipipe_otfdpc_3_0_cfg *dpc_3_0;
-
- if (dpc_param->en > 1)
- return -EINVAL;
-
- if (dpc_param->alg == VPFE_IPIPE_OTFDPC_2_0) {
- dpc_2_0 = &dpc_param->alg_cfg.dpc_2_0;
- if (dpc_2_0->det_thr.r > OTFDPC_DPC2_THR_MASK ||
- dpc_2_0->det_thr.gr > OTFDPC_DPC2_THR_MASK ||
- dpc_2_0->det_thr.gb > OTFDPC_DPC2_THR_MASK ||
- dpc_2_0->det_thr.b > OTFDPC_DPC2_THR_MASK ||
- dpc_2_0->corr_thr.r > OTFDPC_DPC2_THR_MASK ||
- dpc_2_0->corr_thr.gr > OTFDPC_DPC2_THR_MASK ||
- dpc_2_0->corr_thr.gb > OTFDPC_DPC2_THR_MASK ||
- dpc_2_0->corr_thr.b > OTFDPC_DPC2_THR_MASK)
- return -EINVAL;
- return 0;
- }
-
- dpc_3_0 = &dpc_param->alg_cfg.dpc_3_0;
-
- if (dpc_3_0->act_adj_shf > OTF_DPC3_0_SHF_MASK ||
- dpc_3_0->det_thr > OTF_DPC3_0_DET_MASK ||
- dpc_3_0->det_slp > OTF_DPC3_0_SLP_MASK ||
- dpc_3_0->det_thr_min > OTF_DPC3_0_DET_MASK ||
- dpc_3_0->det_thr_max > OTF_DPC3_0_DET_MASK ||
- dpc_3_0->corr_thr > OTF_DPC3_0_CORR_MASK ||
- dpc_3_0->corr_slp > OTF_DPC3_0_SLP_MASK ||
- dpc_3_0->corr_thr_min > OTF_DPC3_0_CORR_MASK ||
- dpc_3_0->corr_thr_max > OTF_DPC3_0_CORR_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_otfdpc_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_otfdpc *dpc_param = param;
- struct vpfe_ipipe_otfdpc *otfdpc = &ipipe->config.otfdpc;
- struct device *dev;
-
- if (!param) {
- memset((void *)otfdpc, 0, sizeof(struct ipipe_otfdpc_2_0));
- goto success;
- }
- dev = ipipe->subdev.v4l2_dev->dev;
- memcpy(otfdpc, dpc_param, sizeof(struct vpfe_ipipe_otfdpc));
- if (ipipe_validate_otfdpc_params(otfdpc) < 0) {
- dev_err(dev, "Invalid otfdpc params\n");
- return -EINVAL;
- }
-
-success:
- ipipe_set_otfdpc_regs(ipipe->base_addr, otfdpc);
-
- return 0;
-}
-
-static int ipipe_get_otfdpc_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_otfdpc *dpc_param = param;
- struct vpfe_ipipe_otfdpc *otfdpc = &ipipe->config.otfdpc;
-
- memcpy(dpc_param, otfdpc, sizeof(struct vpfe_ipipe_otfdpc));
- return 0;
-}
-
-static int ipipe_validate_nf_params(struct vpfe_ipipe_nf *nf_param)
-{
- int i;
-
- if (nf_param->en > 1 || nf_param->shft_val > D2F_SHFT_VAL_MASK ||
- nf_param->spread_val > D2F_SPR_VAL_MASK ||
- nf_param->apply_lsc_gain > 1 ||
- nf_param->edge_det_min_thr > D2F_EDGE_DET_THR_MASK ||
- nf_param->edge_det_max_thr > D2F_EDGE_DET_THR_MASK)
- return -EINVAL;
-
- for (i = 0; i < VPFE_IPIPE_NF_THR_TABLE_SIZE; i++)
- if (nf_param->thr[i] > D2F_THR_VAL_MASK)
- return -EINVAL;
-
- for (i = 0; i < VPFE_IPIPE_NF_STR_TABLE_SIZE; i++)
- if (nf_param->str[i] > D2F_STR_VAL_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_nf_params(struct vpfe_ipipe_device *ipipe,
- unsigned int id, void *param)
-{
- struct vpfe_ipipe_nf *nf_param = param;
- struct vpfe_ipipe_nf *nf = &ipipe->config.nf1;
- struct device *dev;
-
- if (id == IPIPE_D2F_2ND)
- nf = &ipipe->config.nf2;
-
- if (!nf_param) {
- memset((void *)nf, 0, sizeof(struct vpfe_ipipe_nf));
- goto success;
- }
-
- dev = ipipe->subdev.v4l2_dev->dev;
- memcpy(nf, nf_param, sizeof(struct vpfe_ipipe_nf));
- if (ipipe_validate_nf_params(nf) < 0) {
- dev_err(dev, "Invalid nf params\n");
- return -EINVAL;
- }
-
-success:
- ipipe_set_d2f_regs(ipipe->base_addr, id, nf);
-
- return 0;
-}
-
-static int ipipe_set_nf1_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- return ipipe_set_nf_params(ipipe, IPIPE_D2F_1ST, param);
-}
-
-static int ipipe_set_nf2_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- return ipipe_set_nf_params(ipipe, IPIPE_D2F_2ND, param);
-}
-
-static int ipipe_get_nf_params(struct vpfe_ipipe_device *ipipe,
- unsigned int id, void *param)
-{
- struct vpfe_ipipe_nf *nf_param = param;
- struct vpfe_ipipe_nf *nf = &ipipe->config.nf1;
-
- if (id == IPIPE_D2F_2ND)
- nf = &ipipe->config.nf2;
-
- memcpy(nf_param, nf, sizeof(struct vpfe_ipipe_nf));
-
- return 0;
-}
-
-static int ipipe_get_nf1_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- return ipipe_get_nf_params(ipipe, IPIPE_D2F_1ST, param);
-}
-
-static int ipipe_get_nf2_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- return ipipe_get_nf_params(ipipe, IPIPE_D2F_2ND, param);
-}
-
-static int ipipe_validate_gic_params(struct vpfe_ipipe_gic *gic)
-{
- if (gic->en > 1 || gic->gain > GIC_GAIN_MASK ||
- gic->thr > GIC_THR_MASK || gic->slope > GIC_SLOPE_MASK ||
- gic->apply_lsc_gain > 1 ||
- gic->nf2_thr_gain.integer > GIC_NFGAN_INT_MASK ||
- gic->nf2_thr_gain.decimal > GIC_NFGAN_DECI_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_gic_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_gic *gic_param = param;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
- struct vpfe_ipipe_gic *gic = &ipipe->config.gic;
-
- if (!gic_param) {
- memset((void *)gic, 0, sizeof(struct vpfe_ipipe_gic));
- goto success;
- }
-
- memcpy(gic, gic_param, sizeof(struct vpfe_ipipe_gic));
- if (ipipe_validate_gic_params(gic) < 0) {
- dev_err(dev, "Invalid gic params\n");
- return -EINVAL;
- }
-
-success:
- ipipe_set_gic_regs(ipipe->base_addr, gic);
-
- return 0;
-}
-
-static int ipipe_get_gic_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_gic *gic_param = param;
- struct vpfe_ipipe_gic *gic = &ipipe->config.gic;
-
- memcpy(gic_param, gic, sizeof(struct vpfe_ipipe_gic));
-
- return 0;
-}
-
-static int ipipe_validate_wb_params(struct vpfe_ipipe_wb *wbal)
-{
- if (wbal->ofst_r > WB_OFFSET_MASK ||
- wbal->ofst_gr > WB_OFFSET_MASK ||
- wbal->ofst_gb > WB_OFFSET_MASK ||
- wbal->ofst_b > WB_OFFSET_MASK ||
- wbal->gain_r.integer > WB_GAIN_INT_MASK ||
- wbal->gain_r.decimal > WB_GAIN_DECI_MASK ||
- wbal->gain_gr.integer > WB_GAIN_INT_MASK ||
- wbal->gain_gr.decimal > WB_GAIN_DECI_MASK ||
- wbal->gain_gb.integer > WB_GAIN_INT_MASK ||
- wbal->gain_gb.decimal > WB_GAIN_DECI_MASK ||
- wbal->gain_b.integer > WB_GAIN_INT_MASK ||
- wbal->gain_b.decimal > WB_GAIN_DECI_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_wb_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_wb *wb_param = param;
- struct vpfe_ipipe_wb *wbal = &ipipe->config.wbal;
-
- if (!wb_param) {
- const struct vpfe_ipipe_wb wb_defaults = {
- .gain_r = {2, 0x0},
- .gain_gr = {2, 0x0},
- .gain_gb = {2, 0x0},
- .gain_b = {2, 0x0}
- };
- memcpy(wbal, &wb_defaults, sizeof(struct vpfe_ipipe_wb));
- goto success;
- }
-
- memcpy(wbal, wb_param, sizeof(struct vpfe_ipipe_wb));
- if (ipipe_validate_wb_params(wbal) < 0)
- return -EINVAL;
-
-success:
- ipipe_set_wb_regs(ipipe->base_addr, wbal);
-
- return 0;
-}
-
-static int ipipe_get_wb_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_wb *wb_param = param;
- struct vpfe_ipipe_wb *wbal = &ipipe->config.wbal;
-
- memcpy(wb_param, wbal, sizeof(struct vpfe_ipipe_wb));
- return 0;
-}
-
-static int ipipe_validate_cfa_params(struct vpfe_ipipe_cfa *cfa)
-{
- if (cfa->hpf_thr_2dir > CFA_HPF_THR_2DIR_MASK ||
- cfa->hpf_slp_2dir > CFA_HPF_SLOPE_2DIR_MASK ||
- cfa->hp_mix_thr_2dir > CFA_HPF_MIX_THR_2DIR_MASK ||
- cfa->hp_mix_slope_2dir > CFA_HPF_MIX_SLP_2DIR_MASK ||
- cfa->dir_thr_2dir > CFA_DIR_THR_2DIR_MASK ||
- cfa->dir_slope_2dir > CFA_DIR_SLP_2DIR_MASK ||
- cfa->nd_wt_2dir > CFA_ND_WT_2DIR_MASK ||
- cfa->hue_fract_daa > CFA_DAA_HUE_FRA_MASK ||
- cfa->edge_thr_daa > CFA_DAA_EDG_THR_MASK ||
- cfa->thr_min_daa > CFA_DAA_THR_MIN_MASK ||
- cfa->thr_slope_daa > CFA_DAA_THR_SLP_MASK ||
- cfa->slope_min_daa > CFA_DAA_SLP_MIN_MASK ||
- cfa->slope_slope_daa > CFA_DAA_SLP_SLP_MASK ||
- cfa->lp_wt_daa > CFA_DAA_LP_WT_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_cfa_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_cfa *cfa_param = param;
- struct vpfe_ipipe_cfa *cfa = &ipipe->config.cfa;
-
- if (!cfa_param) {
- memset(cfa, 0, sizeof(struct vpfe_ipipe_cfa));
- cfa->alg = VPFE_IPIPE_CFA_ALG_2DIRAC;
- goto success;
- }
-
- memcpy(cfa, cfa_param, sizeof(struct vpfe_ipipe_cfa));
- if (ipipe_validate_cfa_params(cfa) < 0)
- return -EINVAL;
-
-success:
- ipipe_set_cfa_regs(ipipe->base_addr, cfa);
-
- return 0;
-}
-
-static int ipipe_get_cfa_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_cfa *cfa_param = param;
- struct vpfe_ipipe_cfa *cfa = &ipipe->config.cfa;
-
- memcpy(cfa_param, cfa, sizeof(struct vpfe_ipipe_cfa));
- return 0;
-}
-
-static int
-ipipe_validate_rgb2rgb_params(struct vpfe_ipipe_rgb2rgb *rgb2rgb,
- unsigned int id)
-{
- u32 gain_int_upper = RGB2RGB_1_GAIN_INT_MASK;
- u32 offset_upper = RGB2RGB_1_OFST_MASK;
-
- if (id == IPIPE_RGB2RGB_2) {
- offset_upper = RGB2RGB_2_OFST_MASK;
- gain_int_upper = RGB2RGB_2_GAIN_INT_MASK;
- }
-
- if (rgb2rgb->coef_rr.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_rr.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->coef_gr.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_gr.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->coef_br.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_br.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->coef_rg.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_rg.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->coef_gg.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_gg.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->coef_bg.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_bg.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->coef_rb.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_rb.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->coef_gb.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_gb.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->coef_bb.decimal > RGB2RGB_GAIN_DECI_MASK ||
- rgb2rgb->coef_bb.integer > gain_int_upper)
- return -EINVAL;
-
- if (rgb2rgb->out_ofst_r > offset_upper ||
- rgb2rgb->out_ofst_g > offset_upper ||
- rgb2rgb->out_ofst_b > offset_upper)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_rgb2rgb_params(struct vpfe_ipipe_device *ipipe,
- unsigned int id, void *param)
-{
- struct vpfe_ipipe_rgb2rgb *rgb2rgb = &ipipe->config.rgb2rgb1;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
- struct vpfe_ipipe_rgb2rgb *rgb2rgb_param;
-
- rgb2rgb_param = param;
-
- if (id == IPIPE_RGB2RGB_2)
- rgb2rgb = &ipipe->config.rgb2rgb2;
-
- if (!rgb2rgb_param) {
- const struct vpfe_ipipe_rgb2rgb rgb2rgb_defaults = {
- .coef_rr = {1, 0}, /* 256 */
- .coef_gr = {0, 0},
- .coef_br = {0, 0},
- .coef_rg = {0, 0},
- .coef_gg = {1, 0}, /* 256 */
- .coef_bg = {0, 0},
- .coef_rb = {0, 0},
- .coef_gb = {0, 0},
- .coef_bb = {1, 0}, /* 256 */
- };
- /* Copy defaults for rgb2rgb conversion */
- memcpy(rgb2rgb, &rgb2rgb_defaults,
- sizeof(struct vpfe_ipipe_rgb2rgb));
- goto success;
- }
-
- memcpy(rgb2rgb, rgb2rgb_param, sizeof(struct vpfe_ipipe_rgb2rgb));
- if (ipipe_validate_rgb2rgb_params(rgb2rgb, id) < 0) {
- dev_err(dev, "Invalid rgb2rgb params\n");
- return -EINVAL;
- }
-
-success:
- ipipe_set_rgb2rgb_regs(ipipe->base_addr, id, rgb2rgb);
-
- return 0;
-}
-
-static int
-ipipe_set_rgb2rgb_1_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- return ipipe_set_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_1, param);
-}
-
-static int
-ipipe_set_rgb2rgb_2_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- return ipipe_set_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_2, param);
-}
-
-static int ipipe_get_rgb2rgb_params(struct vpfe_ipipe_device *ipipe,
- unsigned int id, void *param)
-{
- struct vpfe_ipipe_rgb2rgb *rgb2rgb = &ipipe->config.rgb2rgb1;
- struct vpfe_ipipe_rgb2rgb *rgb2rgb_param;
-
- rgb2rgb_param = param;
-
- if (id == IPIPE_RGB2RGB_2)
- rgb2rgb = &ipipe->config.rgb2rgb2;
-
- memcpy(rgb2rgb_param, rgb2rgb, sizeof(struct vpfe_ipipe_rgb2rgb));
-
- return 0;
-}
-
-static int
-ipipe_get_rgb2rgb_1_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- return ipipe_get_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_1, param);
-}
-
-static int
-ipipe_get_rgb2rgb_2_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- return ipipe_get_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_2, param);
-}
-
-static int
-ipipe_validate_gamma_entry(struct vpfe_ipipe_gamma_entry *table, int size)
-{
- int i;
-
- if (!table)
- return -EINVAL;
-
- for (i = 0; i < size; i++)
- if (table[i].slope > GAMMA_MASK ||
- table[i].offset > GAMMA_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int
-ipipe_validate_gamma_params(struct vpfe_ipipe_gamma *gamma, struct device *dev)
-{
- int table_size;
- int err;
-
- if (gamma->bypass_r > 1 ||
- gamma->bypass_b > 1 ||
- gamma->bypass_g > 1)
- return -EINVAL;
-
- if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM)
- return 0;
-
- table_size = gamma->tbl_size;
- if (!gamma->bypass_r) {
- err = ipipe_validate_gamma_entry(gamma->table_r, table_size);
- if (err) {
- dev_err(dev, "GAMMA R - table entry invalid\n");
- return err;
- }
- }
-
- if (!gamma->bypass_b) {
- err = ipipe_validate_gamma_entry(gamma->table_b, table_size);
- if (err) {
- dev_err(dev, "GAMMA B - table entry invalid\n");
- return err;
- }
- }
-
- if (!gamma->bypass_g) {
- err = ipipe_validate_gamma_entry(gamma->table_g, table_size);
- if (err) {
- dev_err(dev, "GAMMA G - table entry invalid\n");
- return err;
- }
- }
-
- return 0;
-}
-
-static int
-ipipe_set_gamma_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_gamma *gamma_param = param;
- struct vpfe_ipipe_gamma *gamma = &ipipe->config.gamma;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
- int table_size;
-
- if (!gamma_param) {
- memset(gamma, 0, sizeof(struct vpfe_ipipe_gamma));
- gamma->tbl_sel = VPFE_IPIPE_GAMMA_TBL_ROM;
- goto success;
- }
-
- gamma->bypass_r = gamma_param->bypass_r;
- gamma->bypass_b = gamma_param->bypass_b;
- gamma->bypass_g = gamma_param->bypass_g;
- gamma->tbl_sel = gamma_param->tbl_sel;
- gamma->tbl_size = gamma_param->tbl_size;
-
- if (ipipe_validate_gamma_params(gamma, dev) < 0)
- return -EINVAL;
-
- if (gamma_param->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM)
- goto success;
-
- table_size = gamma->tbl_size;
- if (!gamma_param->bypass_r)
- memcpy(&gamma->table_r, &gamma_param->table_r,
- (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
-
- if (!gamma_param->bypass_b)
- memcpy(&gamma->table_b, &gamma_param->table_b,
- (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
-
- if (!gamma_param->bypass_g)
- memcpy(&gamma->table_g, &gamma_param->table_g,
- (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
-
-success:
- ipipe_set_gamma_regs(ipipe->base_addr, ipipe->isp5_base_addr, gamma);
-
- return 0;
-}
-
-static int ipipe_get_gamma_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_gamma *gamma_param = param;
- struct vpfe_ipipe_gamma *gamma = &ipipe->config.gamma;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
- int table_size;
-
- gamma_param->bypass_r = gamma->bypass_r;
- gamma_param->bypass_g = gamma->bypass_g;
- gamma_param->bypass_b = gamma->bypass_b;
- gamma_param->tbl_sel = gamma->tbl_sel;
- gamma_param->tbl_size = gamma->tbl_size;
-
- if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM)
- return 0;
-
- table_size = gamma->tbl_size;
-
- if (!gamma->bypass_r) {
- dev_err(dev,
- "%s: table ptr empty for R\n", __func__);
- return -EINVAL;
- }
- memcpy(gamma_param->table_r, gamma->table_r,
- (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
-
- if (!gamma->bypass_g) {
- dev_err(dev, "%s: table ptr empty for G\n", __func__);
- return -EINVAL;
- }
- memcpy(gamma_param->table_g, gamma->table_g,
- (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
-
- if (!gamma->bypass_b) {
- dev_err(dev, "%s: table ptr empty for B\n", __func__);
- return -EINVAL;
- }
- memcpy(gamma_param->table_b, gamma->table_b,
- (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
-
- return 0;
-}
-
-static int ipipe_validate_3d_lut_params(struct vpfe_ipipe_3d_lut *lut)
-{
- int i;
-
- if (!lut->en)
- return 0;
-
- for (i = 0; i < VPFE_IPIPE_MAX_SIZE_3D_LUT; i++)
- if (lut->table[i].r > D3_LUT_ENTRY_MASK ||
- lut->table[i].g > D3_LUT_ENTRY_MASK ||
- lut->table[i].b > D3_LUT_ENTRY_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_get_3d_lut_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_3d_lut *lut_param = param;
- struct vpfe_ipipe_3d_lut *lut = &ipipe->config.lut;
-
- lut_param->en = lut->en;
-
- memcpy(lut_param->table, &lut->table,
- (VPFE_IPIPE_MAX_SIZE_3D_LUT *
- sizeof(struct vpfe_ipipe_3d_lut_entry)));
-
- return 0;
-}
-
-static int
-ipipe_set_3d_lut_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_3d_lut *lut_param = param;
- struct vpfe_ipipe_3d_lut *lut = &ipipe->config.lut;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
-
- if (!lut_param) {
- memset(lut, 0, sizeof(struct vpfe_ipipe_3d_lut));
- goto success;
- }
-
- memcpy(lut, lut_param, sizeof(struct vpfe_ipipe_3d_lut));
- if (ipipe_validate_3d_lut_params(lut) < 0) {
- dev_err(dev, "Invalid 3D-LUT Params\n");
- return -EINVAL;
- }
-
-success:
- ipipe_set_3d_lut_regs(ipipe->base_addr, ipipe->isp5_base_addr, lut);
-
- return 0;
-}
-
-static int ipipe_validate_rgb2yuv_params(struct vpfe_ipipe_rgb2yuv *rgb2yuv)
-{
- if (rgb2yuv->coef_ry.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_ry.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->coef_gy.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_gy.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->coef_by.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_by.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->coef_rcb.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_rcb.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->coef_gcb.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_gcb.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->coef_bcb.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_bcb.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->coef_rcr.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_rcr.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->coef_gcr.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_gcr.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->coef_bcr.decimal > RGB2YCBCR_COEF_DECI_MASK ||
- rgb2yuv->coef_bcr.integer > RGB2YCBCR_COEF_INT_MASK)
- return -EINVAL;
-
- if (rgb2yuv->out_ofst_y > RGB2YCBCR_OFST_MASK ||
- rgb2yuv->out_ofst_cb > RGB2YCBCR_OFST_MASK ||
- rgb2yuv->out_ofst_cr > RGB2YCBCR_OFST_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int
-ipipe_set_rgb2yuv_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_rgb2yuv *rgb2yuv = &ipipe->config.rgb2yuv;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
- struct vpfe_ipipe_rgb2yuv *rgb2yuv_param;
-
- rgb2yuv_param = param;
- if (!rgb2yuv_param) {
- /* Defaults for rgb2yuv conversion */
- const struct vpfe_ipipe_rgb2yuv rgb2yuv_defaults = {
- .coef_ry = {0, 0x4d},
- .coef_gy = {0, 0x96},
- .coef_by = {0, 0x1d},
- .coef_rcb = {0xf, 0xd5},
- .coef_gcb = {0xf, 0xab},
- .coef_bcb = {0, 0x80},
- .coef_rcr = {0, 0x80},
- .coef_gcr = {0xf, 0x95},
- .coef_bcr = {0xf, 0xeb},
- .out_ofst_cb = 0x80,
- .out_ofst_cr = 0x80,
- };
- /* Copy defaults for rgb2yuv conversion */
- memcpy(rgb2yuv, &rgb2yuv_defaults,
- sizeof(struct vpfe_ipipe_rgb2yuv));
- goto success;
- }
-
- memcpy(rgb2yuv, rgb2yuv_param, sizeof(struct vpfe_ipipe_rgb2yuv));
- if (ipipe_validate_rgb2yuv_params(rgb2yuv) < 0) {
- dev_err(dev, "Invalid rgb2yuv params\n");
- return -EINVAL;
- }
-
-success:
- ipipe_set_rgb2ycbcr_regs(ipipe->base_addr, rgb2yuv);
-
- return 0;
-}
-
-static int
-ipipe_get_rgb2yuv_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_rgb2yuv *rgb2yuv = &ipipe->config.rgb2yuv;
- struct vpfe_ipipe_rgb2yuv *rgb2yuv_param;
-
- rgb2yuv_param = param;
- memcpy(rgb2yuv_param, rgb2yuv, sizeof(struct vpfe_ipipe_rgb2yuv));
- return 0;
-}
-
-static int ipipe_validate_gbce_params(struct vpfe_ipipe_gbce *gbce)
-{
- u32 max = GBCE_Y_VAL_MASK;
- int i;
-
- if (!gbce->en)
- return 0;
-
- if (gbce->type == VPFE_IPIPE_GBCE_GAIN_TBL)
- max = GBCE_GAIN_VAL_MASK;
-
- for (i = 0; i < VPFE_IPIPE_MAX_SIZE_GBCE_LUT; i++)
- if (gbce->table[i] > max)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_gbce_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_gbce *gbce_param = param;
- struct vpfe_ipipe_gbce *gbce = &ipipe->config.gbce;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
-
- if (!gbce_param) {
- memset(gbce, 0, sizeof(struct vpfe_ipipe_gbce));
- } else {
- memcpy(gbce, gbce_param, sizeof(struct vpfe_ipipe_gbce));
- if (ipipe_validate_gbce_params(gbce) < 0) {
- dev_err(dev, "Invalid gbce params\n");
- return -EINVAL;
- }
- }
-
- ipipe_set_gbce_regs(ipipe->base_addr, ipipe->isp5_base_addr, gbce);
-
- return 0;
-}
-
-static int ipipe_get_gbce_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_gbce *gbce_param = param;
- struct vpfe_ipipe_gbce *gbce = &ipipe->config.gbce;
-
- gbce_param->en = gbce->en;
- gbce_param->type = gbce->type;
-
- memcpy(gbce_param->table, gbce->table,
- (VPFE_IPIPE_MAX_SIZE_GBCE_LUT * sizeof(unsigned short)));
-
- return 0;
-}
-
-static int
-ipipe_validate_yuv422_conv_params(struct vpfe_ipipe_yuv422_conv *yuv422_conv)
-{
- if (yuv422_conv->en_chrom_lpf > 1)
- return -EINVAL;
-
- return 0;
-}
-
-static int
-ipipe_set_yuv422_conv_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_yuv422_conv *yuv422_conv = &ipipe->config.yuv422_conv;
- struct vpfe_ipipe_yuv422_conv *yuv422_conv_param;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
-
- yuv422_conv_param = param;
- if (!yuv422_conv_param) {
- memset(yuv422_conv, 0, sizeof(struct vpfe_ipipe_yuv422_conv));
- yuv422_conv->chrom_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE;
- } else {
- memcpy(yuv422_conv, yuv422_conv_param,
- sizeof(struct vpfe_ipipe_yuv422_conv));
- if (ipipe_validate_yuv422_conv_params(yuv422_conv) < 0) {
- dev_err(dev, "Invalid yuv422 params\n");
- return -EINVAL;
- }
- }
-
- ipipe_set_yuv422_conv_regs(ipipe->base_addr, yuv422_conv);
-
- return 0;
-}
-
-static int
-ipipe_get_yuv422_conv_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_yuv422_conv *yuv422_conv = &ipipe->config.yuv422_conv;
- struct vpfe_ipipe_yuv422_conv *yuv422_conv_param;
-
- yuv422_conv_param = param;
- memcpy(yuv422_conv_param, yuv422_conv,
- sizeof(struct vpfe_ipipe_yuv422_conv));
-
- return 0;
-}
-
-static int ipipe_validate_yee_params(struct vpfe_ipipe_yee *yee)
-{
- int i;
-
- if (yee->en > 1 ||
- yee->en_halo_red > 1 ||
- yee->hpf_shft > YEE_HPF_SHIFT_MASK)
- return -EINVAL;
-
- if (yee->hpf_coef_00 > YEE_COEF_MASK ||
- yee->hpf_coef_01 > YEE_COEF_MASK ||
- yee->hpf_coef_02 > YEE_COEF_MASK ||
- yee->hpf_coef_10 > YEE_COEF_MASK ||
- yee->hpf_coef_11 > YEE_COEF_MASK ||
- yee->hpf_coef_12 > YEE_COEF_MASK ||
- yee->hpf_coef_20 > YEE_COEF_MASK ||
- yee->hpf_coef_21 > YEE_COEF_MASK ||
- yee->hpf_coef_22 > YEE_COEF_MASK)
- return -EINVAL;
-
- if (yee->yee_thr > YEE_THR_MASK ||
- yee->es_gain > YEE_ES_GAIN_MASK ||
- yee->es_thr1 > YEE_ES_THR1_MASK ||
- yee->es_thr2 > YEE_THR_MASK ||
- yee->es_gain_grad > YEE_THR_MASK ||
- yee->es_ofst_grad > YEE_THR_MASK)
- return -EINVAL;
-
- for (i = 0; i < VPFE_IPIPE_MAX_SIZE_YEE_LUT; i++)
- if (yee->table[i] > YEE_ENTRY_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_yee_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_yee *yee_param = param;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
- struct vpfe_ipipe_yee *yee = &ipipe->config.yee;
-
- if (!yee_param) {
- memset(yee, 0, sizeof(struct vpfe_ipipe_yee));
- } else {
- memcpy(yee, yee_param, sizeof(struct vpfe_ipipe_yee));
- if (ipipe_validate_yee_params(yee) < 0) {
- dev_err(dev, "Invalid yee params\n");
- return -EINVAL;
- }
- }
-
- ipipe_set_ee_regs(ipipe->base_addr, ipipe->isp5_base_addr, yee);
-
- return 0;
-}
-
-static int ipipe_get_yee_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_yee *yee_param = param;
- struct vpfe_ipipe_yee *yee = &ipipe->config.yee;
-
- yee_param->en = yee->en;
- yee_param->en_halo_red = yee->en_halo_red;
- yee_param->merge_meth = yee->merge_meth;
- yee_param->hpf_shft = yee->hpf_shft;
- yee_param->hpf_coef_00 = yee->hpf_coef_00;
- yee_param->hpf_coef_01 = yee->hpf_coef_01;
- yee_param->hpf_coef_02 = yee->hpf_coef_02;
- yee_param->hpf_coef_10 = yee->hpf_coef_10;
- yee_param->hpf_coef_11 = yee->hpf_coef_11;
- yee_param->hpf_coef_12 = yee->hpf_coef_12;
- yee_param->hpf_coef_20 = yee->hpf_coef_20;
- yee_param->hpf_coef_21 = yee->hpf_coef_21;
- yee_param->hpf_coef_22 = yee->hpf_coef_22;
- yee_param->yee_thr = yee->yee_thr;
- yee_param->es_gain = yee->es_gain;
- yee_param->es_thr1 = yee->es_thr1;
- yee_param->es_thr2 = yee->es_thr2;
- yee_param->es_gain_grad = yee->es_gain_grad;
- yee_param->es_ofst_grad = yee->es_ofst_grad;
- memcpy(yee_param->table, &yee->table,
- (VPFE_IPIPE_MAX_SIZE_YEE_LUT * sizeof(short)));
-
- return 0;
-}
-
-static int ipipe_validate_car_params(struct vpfe_ipipe_car *car)
-{
- if (car->en > 1 || car->hpf_shft > CAR_HPF_SHIFT_MASK ||
- car->gain1.shft > CAR_GAIN1_SHFT_MASK ||
- car->gain1.gain_min > CAR_GAIN_MIN_MASK ||
- car->gain2.shft > CAR_GAIN2_SHFT_MASK ||
- car->gain2.gain_min > CAR_GAIN_MIN_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_car_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_car *car_param = param;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
- struct vpfe_ipipe_car *car = &ipipe->config.car;
-
- if (!car_param) {
- memset(car, 0, sizeof(struct vpfe_ipipe_car));
- } else {
- memcpy(car, car_param, sizeof(struct vpfe_ipipe_car));
- if (ipipe_validate_car_params(car) < 0) {
- dev_err(dev, "Invalid car params\n");
- return -EINVAL;
- }
- }
-
- ipipe_set_car_regs(ipipe->base_addr, car);
-
- return 0;
-}
-
-static int ipipe_get_car_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_car *car_param = param;
- struct vpfe_ipipe_car *car = &ipipe->config.car;
-
- memcpy(car_param, car, sizeof(struct vpfe_ipipe_car));
- return 0;
-}
-
-static int ipipe_validate_cgs_params(struct vpfe_ipipe_cgs *cgs)
-{
- if (cgs->en > 1 || cgs->h_shft > CAR_SHIFT_MASK)
- return -EINVAL;
-
- return 0;
-}
-
-static int ipipe_set_cgs_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_cgs *cgs_param = param;
- struct device *dev = ipipe->subdev.v4l2_dev->dev;
- struct vpfe_ipipe_cgs *cgs = &ipipe->config.cgs;
-
- if (!cgs_param) {
- memset(cgs, 0, sizeof(struct vpfe_ipipe_cgs));
- } else {
- memcpy(cgs, cgs_param, sizeof(struct vpfe_ipipe_cgs));
- if (ipipe_validate_cgs_params(cgs) < 0) {
- dev_err(dev, "Invalid cgs params\n");
- return -EINVAL;
- }
- }
-
- ipipe_set_cgs_regs(ipipe->base_addr, cgs);
-
- return 0;
-}
-
-static int ipipe_get_cgs_params(struct vpfe_ipipe_device *ipipe, void *param)
-{
- struct vpfe_ipipe_cgs *cgs_param = param;
- struct vpfe_ipipe_cgs *cgs = &ipipe->config.cgs;
-
- memcpy(cgs_param, cgs, sizeof(struct vpfe_ipipe_cgs));
-
- return 0;
-}
-
-static const struct ipipe_module_if ipipe_modules[VPFE_IPIPE_MAX_MODULES] = {
- /* VPFE_IPIPE_INPUT_CONFIG */ {
- offsetof(struct ipipe_module_params, input_config),
- FIELD_SIZEOF(struct ipipe_module_params, input_config),
- offsetof(struct vpfe_ipipe_config, input_config),
- ipipe_set_input_config,
- ipipe_get_input_config,
- }, /* VPFE_IPIPE_LUTDPC */ {
- offsetof(struct ipipe_module_params, lutdpc),
- FIELD_SIZEOF(struct ipipe_module_params, lutdpc),
- offsetof(struct vpfe_ipipe_config, lutdpc),
- ipipe_set_lutdpc_params,
- ipipe_get_lutdpc_params,
- }, /* VPFE_IPIPE_OTFDPC */ {
- offsetof(struct ipipe_module_params, otfdpc),
- FIELD_SIZEOF(struct ipipe_module_params, otfdpc),
- offsetof(struct vpfe_ipipe_config, otfdpc),
- ipipe_set_otfdpc_params,
- ipipe_get_otfdpc_params,
- }, /* VPFE_IPIPE_NF1 */ {
- offsetof(struct ipipe_module_params, nf1),
- FIELD_SIZEOF(struct ipipe_module_params, nf1),
- offsetof(struct vpfe_ipipe_config, nf1),
- ipipe_set_nf1_params,
- ipipe_get_nf1_params,
- }, /* VPFE_IPIPE_NF2 */ {
- offsetof(struct ipipe_module_params, nf2),
- FIELD_SIZEOF(struct ipipe_module_params, nf2),
- offsetof(struct vpfe_ipipe_config, nf2),
- ipipe_set_nf2_params,
- ipipe_get_nf2_params,
- }, /* VPFE_IPIPE_WB */ {
- offsetof(struct ipipe_module_params, wbal),
- FIELD_SIZEOF(struct ipipe_module_params, wbal),
- offsetof(struct vpfe_ipipe_config, wbal),
- ipipe_set_wb_params,
- ipipe_get_wb_params,
- }, /* VPFE_IPIPE_RGB2RGB_1 */ {
- offsetof(struct ipipe_module_params, rgb2rgb1),
- FIELD_SIZEOF(struct ipipe_module_params, rgb2rgb1),
- offsetof(struct vpfe_ipipe_config, rgb2rgb1),
- ipipe_set_rgb2rgb_1_params,
- ipipe_get_rgb2rgb_1_params,
- }, /* VPFE_IPIPE_RGB2RGB_2 */ {
- offsetof(struct ipipe_module_params, rgb2rgb2),
- FIELD_SIZEOF(struct ipipe_module_params, rgb2rgb2),
- offsetof(struct vpfe_ipipe_config, rgb2rgb2),
- ipipe_set_rgb2rgb_2_params,
- ipipe_get_rgb2rgb_2_params,
- }, /* VPFE_IPIPE_GAMMA */ {
- offsetof(struct ipipe_module_params, gamma),
- FIELD_SIZEOF(struct ipipe_module_params, gamma),
- offsetof(struct vpfe_ipipe_config, gamma),
- ipipe_set_gamma_params,
- ipipe_get_gamma_params,
- }, /* VPFE_IPIPE_3D_LUT */ {
- offsetof(struct ipipe_module_params, lut),
- FIELD_SIZEOF(struct ipipe_module_params, lut),
- offsetof(struct vpfe_ipipe_config, lut),
- ipipe_set_3d_lut_params,
- ipipe_get_3d_lut_params,
- }, /* VPFE_IPIPE_RGB2YUV */ {
- offsetof(struct ipipe_module_params, rgb2yuv),
- FIELD_SIZEOF(struct ipipe_module_params, rgb2yuv),
- offsetof(struct vpfe_ipipe_config, rgb2yuv),
- ipipe_set_rgb2yuv_params,
- ipipe_get_rgb2yuv_params,
- }, /* VPFE_IPIPE_YUV422_CONV */ {
- offsetof(struct ipipe_module_params, yuv422_conv),
- FIELD_SIZEOF(struct ipipe_module_params, yuv422_conv),
- offsetof(struct vpfe_ipipe_config, yuv422_conv),
- ipipe_set_yuv422_conv_params,
- ipipe_get_yuv422_conv_params,
- }, /* VPFE_IPIPE_YEE */ {
- offsetof(struct ipipe_module_params, yee),
- FIELD_SIZEOF(struct ipipe_module_params, yee),
- offsetof(struct vpfe_ipipe_config, yee),
- ipipe_set_yee_params,
- ipipe_get_yee_params,
- }, /* VPFE_IPIPE_GIC */ {
- offsetof(struct ipipe_module_params, gic),
- FIELD_SIZEOF(struct ipipe_module_params, gic),
- offsetof(struct vpfe_ipipe_config, gic),
- ipipe_set_gic_params,
- ipipe_get_gic_params,
- }, /* VPFE_IPIPE_CFA */ {
- offsetof(struct ipipe_module_params, cfa),
- FIELD_SIZEOF(struct ipipe_module_params, cfa),
- offsetof(struct vpfe_ipipe_config, cfa),
- ipipe_set_cfa_params,
- ipipe_get_cfa_params,
- }, /* VPFE_IPIPE_CAR */ {
- offsetof(struct ipipe_module_params, car),
- FIELD_SIZEOF(struct ipipe_module_params, car),
- offsetof(struct vpfe_ipipe_config, car),
- ipipe_set_car_params,
- ipipe_get_car_params,
- }, /* VPFE_IPIPE_CGS */ {
- offsetof(struct ipipe_module_params, cgs),
- FIELD_SIZEOF(struct ipipe_module_params, cgs),
- offsetof(struct vpfe_ipipe_config, cgs),
- ipipe_set_cgs_params,
- ipipe_get_cgs_params,
- }, /* VPFE_IPIPE_GBCE */ {
- offsetof(struct ipipe_module_params, gbce),
- FIELD_SIZEOF(struct ipipe_module_params, gbce),
- offsetof(struct vpfe_ipipe_config, gbce),
- ipipe_set_gbce_params,
- ipipe_get_gbce_params,
- },
-};
-
-static int ipipe_s_config(struct v4l2_subdev *sd, struct vpfe_ipipe_config *cfg)
-{
- struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
- unsigned int i;
- int rval = 0;
- struct ipipe_module_params *params;
-
- for (i = 0; i < ARRAY_SIZE(ipipe_modules); i++) {
- const struct ipipe_module_if *module_if;
- void *from, *to;
- size_t size;
-
- if (!(cfg->flag & BIT(i)))
- continue;
-
- module_if = &ipipe_modules[i];
- from = *(void **)((void *)cfg + module_if->config_offset);
-
- params = kmalloc(sizeof(*params), GFP_KERNEL);
- if (!params)
- return -ENOMEM;
- to = (void *)params + module_if->param_offset;
- size = module_if->param_size;
-
- if (to && from && size) {
- if (copy_from_user(to, (void __user *)from, size)) {
- rval = -EFAULT;
- goto error_free;
- }
- rval = module_if->set(ipipe, to);
- if (rval)
- goto error_free;
- } else if (to && !from && size) {
- rval = module_if->set(ipipe, NULL);
- if (rval)
- goto error_free;
- }
- kfree(params);
- }
- return rval;
-
-error_free:
- kfree(params);
- return rval;
-}
-
-static int ipipe_g_config(struct v4l2_subdev *sd, struct vpfe_ipipe_config *cfg)
-{
- struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
- unsigned int i;
- int rval = 0;
-
- for (i = 1; i < ARRAY_SIZE(ipipe_modules); i++) {
- const struct ipipe_module_if *module_if;
- struct ipipe_module_params *params;
- void *from, *to;
- size_t size;
-
- if (!(cfg->flag & BIT(i)))
- continue;
-
- module_if = &ipipe_modules[i];
- to = *(void **)((void *)cfg + module_if->config_offset);
-
- params = kmalloc(sizeof(*params), GFP_KERNEL);
- from = (void *)params + module_if->param_offset;
- size = module_if->param_size;
-
- if (to && from && size) {
- rval = module_if->get(ipipe, from);
- if (rval)
- goto error;
- if (copy_to_user((void __user *)to, from, size)) {
- rval = -EFAULT;
- break;
- }
- }
- kfree(params);
- }
-error:
- return rval;
-}
-
-/*
- * ipipe_ioctl() - Handle ipipe module private ioctl's
- * @sd: pointer to v4l2 subdev structure
- * @cmd: configuration command
- * @arg: configuration argument
- */
-static long ipipe_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
-{
- switch (cmd) {
- case VIDIOC_VPFE_IPIPE_S_CONFIG:
- return ipipe_s_config(sd, arg);
-
- case VIDIOC_VPFE_IPIPE_G_CONFIG:
- return ipipe_g_config(sd, arg);
-
- default:
- return -ENOIOCTLCMD;
- }
-}
-
-void vpfe_ipipe_enable(struct vpfe_device *vpfe_dev, int en)
-{
- struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
- struct vpfe_ipipe_device *ipipe = &vpfe_dev->vpfe_ipipe;
- unsigned char val;
-
- if (ipipe->input == IPIPE_INPUT_NONE)
- return;
-
- /* ipipe is set to single shot */
- if (ipipeif->input == IPIPEIF_INPUT_MEMORY && en) {
- /* for single-shot mode, need to wait for h/w to
- * reset many register bits
- */
- do {
- val = regr_ip(vpfe_dev->vpfe_ipipe.base_addr,
- IPIPE_SRC_EN);
- } while (val);
- }
- regw_ip(vpfe_dev->vpfe_ipipe.base_addr, en, IPIPE_SRC_EN);
-}
-
-/*
- * ipipe_set_stream() - Enable/Disable streaming on the ipipe subdevice
- * @sd: pointer to v4l2 subdev structure
- * @enable: 1 == Enable, 0 == Disable
- */
-static int ipipe_set_stream(struct v4l2_subdev *sd, int enable)
-{
- struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
- struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe);
-
- if (enable && ipipe->input != IPIPE_INPUT_NONE &&
- ipipe->output != IPIPE_OUTPUT_NONE) {
- if (config_ipipe_hw(ipipe) < 0)
- return -EINVAL;
- }
-
- vpfe_ipipe_enable(vpfe_dev, enable);
-
- return 0;
-}
-
-/*
- * __ipipe_get_format() - helper function for getting ipipe format
- * @ipipe: pointer to ipipe private structure.
- * @pad: pad number.
- * @cfg: V4L2 subdev pad config
- * @which: wanted subdev format.
- *
- */
-static struct v4l2_mbus_framefmt *
-__ipipe_get_format(struct vpfe_ipipe_device *ipipe,
- struct v4l2_subdev_pad_config *cfg, unsigned int pad,
- enum v4l2_subdev_format_whence which)
-{
- if (which == V4L2_SUBDEV_FORMAT_TRY)
- return v4l2_subdev_get_try_format(&ipipe->subdev, cfg, pad);
-
- return &ipipe->formats[pad];
-}
-
-/*
- * ipipe_try_format() - Handle try format by pad subdev method
- * @ipipe: VPFE ipipe device.
- * @cfg: V4L2 subdev pad config
- * @pad: pad num.
- * @fmt: pointer to v4l2 format structure.
- * @which : wanted subdev format
- */
-static void
-ipipe_try_format(struct vpfe_ipipe_device *ipipe,
- struct v4l2_subdev_pad_config *cfg, unsigned int pad,
- struct v4l2_mbus_framefmt *fmt,
- enum v4l2_subdev_format_whence which)
-{
- unsigned int max_out_height;
- unsigned int max_out_width;
- unsigned int i;
-
- max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A;
- max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A;
-
- if (pad == IPIPE_PAD_SINK) {
- for (i = 0; i < ARRAY_SIZE(ipipe_input_fmts); i++)
- if (fmt->code == ipipe_input_fmts[i])
- break;
-
- /* If not found, use SBGGR10 as default */
- if (i >= ARRAY_SIZE(ipipe_input_fmts))
- fmt->code = MEDIA_BUS_FMT_SGRBG12_1X12;
- } else if (pad == IPIPE_PAD_SOURCE) {
- for (i = 0; i < ARRAY_SIZE(ipipe_output_fmts); i++)
- if (fmt->code == ipipe_output_fmts[i])
- break;
-
- /* If not found, use UYVY as default */
- if (i >= ARRAY_SIZE(ipipe_output_fmts))
- fmt->code = MEDIA_BUS_FMT_UYVY8_2X8;
- }
-
- fmt->width = clamp_t(u32, fmt->width, MIN_OUT_HEIGHT, max_out_width);
- fmt->height = clamp_t(u32, fmt->height, MIN_OUT_WIDTH, max_out_height);
-}
-
-/*
- * ipipe_set_format() - Handle set format by pads subdev method
- * @sd: pointer to v4l2 subdev structure
- * @cfg: V4L2 subdev pad config
- * @fmt: pointer to v4l2 subdev format structure
- * return -EINVAL or zero on success
- */
-static int
-ipipe_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt *format;
-
- format = __ipipe_get_format(ipipe, cfg, fmt->pad, fmt->which);
- if (!format)
- return -EINVAL;
-
- ipipe_try_format(ipipe, cfg, fmt->pad, &fmt->format, fmt->which);
- *format = fmt->format;
-
- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
- return 0;
-
- if (fmt->pad == IPIPE_PAD_SINK &&
- (ipipe->input == IPIPE_INPUT_CCDC ||
- ipipe->input == IPIPE_INPUT_MEMORY))
- ipipe->formats[fmt->pad] = fmt->format;
- else if (fmt->pad == IPIPE_PAD_SOURCE &&
- ipipe->output == IPIPE_OUTPUT_RESIZER)
- ipipe->formats[fmt->pad] = fmt->format;
- else
- return -EINVAL;
-
- return 0;
-}
-
-/*
- * ipipe_get_format() - Handle get format by pads subdev method.
- * @sd: pointer to v4l2 subdev structure.
- * @cfg: V4L2 subdev pad config
- * @fmt: pointer to v4l2 subdev format structure.
- */
-static int
-ipipe_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
-
- if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
- fmt->format = ipipe->formats[fmt->pad];
- else
- fmt->format = *(v4l2_subdev_get_try_format(sd, cfg, fmt->pad));
-
- return 0;
-}
-
-/*
- * ipipe_enum_frame_size() - enum frame sizes on pads
- * @sd: pointer to v4l2 subdev structure.
- * @cfg: V4L2 subdev pad config
- * @fse: pointer to v4l2_subdev_frame_size_enum structure.
- */
-static int
-ipipe_enum_frame_size(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_frame_size_enum *fse)
-{
- struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt format;
-
- if (fse->index != 0)
- return -EINVAL;
-
- format.code = fse->code;
- format.width = 1;
- format.height = 1;
- ipipe_try_format(ipipe, cfg, fse->pad, &format, fse->which);
- fse->min_width = format.width;
- fse->min_height = format.height;
-
- if (format.code != fse->code)
- return -EINVAL;
-
- format.code = fse->code;
- format.width = -1;
- format.height = -1;
- ipipe_try_format(ipipe, cfg, fse->pad, &format, fse->which);
- fse->max_width = format.width;
- fse->max_height = format.height;
-
- return 0;
-}
-
-/*
- * ipipe_enum_mbus_code() - enum mbus codes for pads
- * @sd: pointer to v4l2 subdev structure.
- * @cfg: V4L2 subdev pad config
- * @code: pointer to v4l2_subdev_mbus_code_enum structure
- */
-static int
-ipipe_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_mbus_code_enum *code)
-{
- switch (code->pad) {
- case IPIPE_PAD_SINK:
- if (code->index >= ARRAY_SIZE(ipipe_input_fmts))
- return -EINVAL;
- code->code = ipipe_input_fmts[code->index];
- break;
-
- case IPIPE_PAD_SOURCE:
- if (code->index >= ARRAY_SIZE(ipipe_output_fmts))
- return -EINVAL;
- code->code = ipipe_output_fmts[code->index];
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-/*
- * ipipe_s_ctrl() - Handle set control subdev method
- * @ctrl: pointer to v4l2 control structure
- */
-static int ipipe_s_ctrl(struct v4l2_ctrl *ctrl)
-{
- struct vpfe_ipipe_device *ipipe =
- container_of(ctrl->handler, struct vpfe_ipipe_device, ctrls);
- struct ipipe_lum_adj *lum_adj = &ipipe->config.lum_adj;
-
- switch (ctrl->id) {
- case V4L2_CID_BRIGHTNESS:
- lum_adj->brightness = ctrl->val;
- ipipe_set_lum_adj_regs(ipipe->base_addr, lum_adj);
- break;
-
- case V4L2_CID_CONTRAST:
- lum_adj->contrast = ctrl->val;
- ipipe_set_lum_adj_regs(ipipe->base_addr, lum_adj);
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-/*
- * ipipe_init_formats() - Initialize formats on all pads
- * @sd: pointer to v4l2 subdev structure.
- * @fh: V4L2 subdev file handle
- *
- * Initialize all pad formats with default values. Try formats are initialized
- * on the file handle.
- */
-static int
-ipipe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
-{
- struct v4l2_subdev_format format;
-
- memset(&format, 0, sizeof(format));
- format.pad = IPIPE_PAD_SINK;
- format.which = V4L2_SUBDEV_FORMAT_TRY;
- format.format.code = MEDIA_BUS_FMT_SGRBG12_1X12;
- format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
- format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
- ipipe_set_format(sd, fh->pad, &format);
-
- memset(&format, 0, sizeof(format));
- format.pad = IPIPE_PAD_SOURCE;
- format.which = V4L2_SUBDEV_FORMAT_TRY;
- format.format.code = MEDIA_BUS_FMT_UYVY8_2X8;
- format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
- format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
- ipipe_set_format(sd, fh->pad, &format);
-
- return 0;
-}
-
-/* subdev core operations */
-static const struct v4l2_subdev_core_ops ipipe_v4l2_core_ops = {
- .ioctl = ipipe_ioctl,
-};
-
-static const struct v4l2_ctrl_ops ipipe_ctrl_ops = {
- .s_ctrl = ipipe_s_ctrl,
-};
-
-/* subdev file operations */
-static const struct v4l2_subdev_internal_ops ipipe_v4l2_internal_ops = {
- .open = ipipe_init_formats,
-};
-
-/* subdev video operations */
-static const struct v4l2_subdev_video_ops ipipe_v4l2_video_ops = {
- .s_stream = ipipe_set_stream,
-};
-
-/* subdev pad operations */
-static const struct v4l2_subdev_pad_ops ipipe_v4l2_pad_ops = {
- .enum_mbus_code = ipipe_enum_mbus_code,
- .enum_frame_size = ipipe_enum_frame_size,
- .get_fmt = ipipe_get_format,
- .set_fmt = ipipe_set_format,
-};
-
-/* v4l2 subdev operation */
-static const struct v4l2_subdev_ops ipipe_v4l2_ops = {
- .core = &ipipe_v4l2_core_ops,
- .video = &ipipe_v4l2_video_ops,
- .pad = &ipipe_v4l2_pad_ops,
-};
-
-/*
- * Media entity operations
- */
-
-/*
- * ipipe_link_setup() - Setup ipipe connections
- * @entity: ipipe media entity
- * @local: Pad at the local end of the link
- * @remote: Pad at the remote end of the link
- * @flags: Link flags
- *
- * return -EINVAL or zero on success
- */
-static int
-ipipe_link_setup(struct media_entity *entity, const struct media_pad *local,
- const struct media_pad *remote, u32 flags)
-{
- struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
- struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
- struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe);
- u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
-
- if (!is_media_entity_v4l2_subdev(remote->entity))
- return -EINVAL;
-
- switch (local->index) {
- case IPIPE_PAD_SINK:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- ipipe->input = IPIPE_INPUT_NONE;
- break;
- }
- if (ipipe->input != IPIPE_INPUT_NONE)
- return -EBUSY;
- if (ipipeif_sink == IPIPEIF_INPUT_MEMORY)
- ipipe->input = IPIPE_INPUT_MEMORY;
- else
- ipipe->input = IPIPE_INPUT_CCDC;
- break;
-
- case IPIPE_PAD_SOURCE:
- /* out to RESIZER */
- if (flags & MEDIA_LNK_FL_ENABLED)
- ipipe->output = IPIPE_OUTPUT_RESIZER;
- else
- ipipe->output = IPIPE_OUTPUT_NONE;
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-static const struct media_entity_operations ipipe_media_ops = {
- .link_setup = ipipe_link_setup,
-};
-
-/*
- * vpfe_ipipe_unregister_entities() - ipipe unregister entity
- * @vpfe_ipipe: pointer to ipipe subdevice structure.
- */
-void vpfe_ipipe_unregister_entities(struct vpfe_ipipe_device *vpfe_ipipe)
-{
- /* unregister subdev */
- v4l2_device_unregister_subdev(&vpfe_ipipe->subdev);
- /* cleanup entity */
- media_entity_cleanup(&vpfe_ipipe->subdev.entity);
-}
-
-/*
- * vpfe_ipipe_register_entities() - ipipe register entity
- * @ipipe: pointer to ipipe subdevice structure.
- * @vdev: pointer to v4l2 device structure.
- */
-int
-vpfe_ipipe_register_entities(struct vpfe_ipipe_device *ipipe,
- struct v4l2_device *vdev)
-{
- int ret;
-
- /* Register the subdev */
- ret = v4l2_device_register_subdev(vdev, &ipipe->subdev);
- if (ret) {
- pr_err("Failed to register ipipe as v4l2 subdevice\n");
- return ret;
- }
-
- return ret;
-}
-
-#define IPIPE_CONTRAST_HIGH 0xff
-#define IPIPE_BRIGHT_HIGH 0xff
-
-/*
- * vpfe_ipipe_init() - ipipe module initialization.
- * @ipipe: pointer to ipipe subdevice structure.
- * @pdev: platform device pointer.
- */
-int
-vpfe_ipipe_init(struct vpfe_ipipe_device *ipipe, struct platform_device *pdev)
-{
- struct media_pad *pads = &ipipe->pads[0];
- struct v4l2_subdev *sd = &ipipe->subdev;
- struct media_entity *me = &sd->entity;
- struct resource *res, *res2, *memres;
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
- if (!res)
- return -ENOENT;
-
- memres = request_mem_region(res->start, resource_size(res), res->name);
- if (!memres)
- return -EBUSY;
- ipipe->base_addr = ioremap_nocache(memres->start,
- resource_size(memres));
- if (!ipipe->base_addr)
- goto error_release;
-
- res2 = platform_get_resource(pdev, IORESOURCE_MEM, 6);
- if (!res2)
- goto error_unmap;
- ipipe->isp5_base_addr = ioremap_nocache(res2->start,
- resource_size(res2));
- if (!ipipe->isp5_base_addr)
- goto error_unmap;
-
- v4l2_subdev_init(sd, &ipipe_v4l2_ops);
- sd->internal_ops = &ipipe_v4l2_internal_ops;
- strscpy(sd->name, "DAVINCI IPIPE", sizeof(sd->name));
- sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
- v4l2_set_subdevdata(sd, ipipe);
- sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
-
- pads[IPIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- pads[IPIPE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
-
- ipipe->input = IPIPE_INPUT_NONE;
- ipipe->output = IPIPE_OUTPUT_NONE;
-
- me->ops = &ipipe_media_ops;
- v4l2_ctrl_handler_init(&ipipe->ctrls, 2);
- v4l2_ctrl_new_std(&ipipe->ctrls, &ipipe_ctrl_ops,
- V4L2_CID_BRIGHTNESS, 0,
- IPIPE_BRIGHT_HIGH, 1, 16);
- v4l2_ctrl_new_std(&ipipe->ctrls, &ipipe_ctrl_ops,
- V4L2_CID_CONTRAST, 0,
- IPIPE_CONTRAST_HIGH, 1, 16);
- v4l2_ctrl_handler_setup(&ipipe->ctrls);
- sd->ctrl_handler = &ipipe->ctrls;
-
- return media_entity_pads_init(me, IPIPE_PADS_NUM, pads);
-
-error_unmap:
- iounmap(ipipe->base_addr);
-error_release:
- release_mem_region(memres->start, resource_size(memres));
- return -ENOMEM;
-}
-
-/*
- * vpfe_ipipe_cleanup() - ipipe subdevice cleanup.
- * @ipipe: pointer to ipipe subdevice
- * @dev: pointer to platform device
- */
-void vpfe_ipipe_cleanup(struct vpfe_ipipe_device *ipipe,
- struct platform_device *pdev)
-{
- struct resource *res;
-
- v4l2_ctrl_handler_free(&ipipe->ctrls);
-
- iounmap(ipipe->base_addr);
- iounmap(ipipe->isp5_base_addr);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
- if (res)
- release_mem_region(res->start, resource_size(res));
-}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe.h b/drivers/staging/media/davinci_vpfe/dm365_ipipe.h
deleted file mode 100644
index 866ae12aeb07..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_ipipe.h
+++ /dev/null
@@ -1,174 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_DM365_IPIPE_H
-#define _DAVINCI_VPFE_DM365_IPIPE_H
-
-#include <linux/platform_device.h>
-
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-subdev.h>
-
-#include "davinci_vpfe_user.h"
-#include "vpfe_video.h"
-
-enum ipipe_noise_filter {
- IPIPE_D2F_1ST = 0,
- IPIPE_D2F_2ND = 1,
-};
-
-/* Used for driver storage */
-struct ipipe_otfdpc_2_0 {
- /* 0 - disable, 1 - enable */
- unsigned char en;
- /* defect detection method */
- enum vpfe_ipipe_otfdpc_det_meth det_method;
- /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is
- * used
- */
- enum vpfe_ipipe_otfdpc_alg alg;
- struct vpfe_ipipe_otfdpc_2_0_cfg otfdpc_2_0;
-};
-
-struct ipipe_otfdpc_3_0 {
- /* 0 - disable, 1 - enable */
- unsigned char en;
- /* defect detection method */
- enum vpfe_ipipe_otfdpc_det_meth det_method;
- /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is
- * used
- */
- enum vpfe_ipipe_otfdpc_alg alg;
- struct vpfe_ipipe_otfdpc_3_0_cfg otfdpc_3_0;
-};
-
-/* Structure for configuring Luminance Adjustment module */
-struct ipipe_lum_adj {
- /* Brightness adjustments */
- unsigned char brightness;
- /* contrast adjustments */
- unsigned char contrast;
-};
-
-enum ipipe_rgb2rgb {
- IPIPE_RGB2RGB_1 = 0,
- IPIPE_RGB2RGB_2 = 1,
-};
-
-struct ipipe_module_params {
- __u32 flag;
- struct vpfe_ipipe_input_config input_config;
- struct vpfe_ipipe_lutdpc lutdpc;
- struct vpfe_ipipe_otfdpc otfdpc;
- struct vpfe_ipipe_nf nf1;
- struct vpfe_ipipe_nf nf2;
- struct vpfe_ipipe_gic gic;
- struct vpfe_ipipe_wb wbal;
- struct vpfe_ipipe_cfa cfa;
- struct vpfe_ipipe_rgb2rgb rgb2rgb1;
- struct vpfe_ipipe_rgb2rgb rgb2rgb2;
- struct vpfe_ipipe_gamma gamma;
- struct vpfe_ipipe_3d_lut lut;
- struct vpfe_ipipe_rgb2yuv rgb2yuv;
- struct vpfe_ipipe_gbce gbce;
- struct vpfe_ipipe_yuv422_conv yuv422_conv;
- struct vpfe_ipipe_yee yee;
- struct vpfe_ipipe_car car;
- struct vpfe_ipipe_cgs cgs;
- struct ipipe_lum_adj lum_adj;
-};
-
-#define IPIPE_PAD_SINK 0
-#define IPIPE_PAD_SOURCE 1
-
-#define IPIPE_PADS_NUM 2
-
-#define IPIPE_OUTPUT_NONE 0
-#define IPIPE_OUTPUT_RESIZER (1 << 0)
-
-enum ipipe_input_entity {
- IPIPE_INPUT_NONE = 0,
- IPIPE_INPUT_MEMORY = 1,
- IPIPE_INPUT_CCDC = 2,
-};
-
-
-struct vpfe_ipipe_device {
- struct v4l2_subdev subdev;
- struct media_pad pads[IPIPE_PADS_NUM];
- struct v4l2_mbus_framefmt formats[IPIPE_PADS_NUM];
- enum ipipe_input_entity input;
- unsigned int output;
- struct v4l2_ctrl_handler ctrls;
- void __iomem *base_addr;
- void __iomem *isp5_base_addr;
- struct ipipe_module_params config;
-};
-
-struct ipipe_module_if {
- unsigned int param_offset;
- unsigned int param_size;
- unsigned int config_offset;
- int (*set)(struct vpfe_ipipe_device *ipipe, void *param);
- int (*get)(struct vpfe_ipipe_device *ipipe, void *param);
-};
-
-/* data paths */
-enum ipipe_data_paths {
- IPIPE_RAW2YUV,
- /* Bayer RAW input to YCbCr output */
- IPIPE_RAW2RAW,
- /* Bayer Raw to Bayer output */
- IPIPE_RAW2BOX,
- /* Bayer Raw to Boxcar output */
- IPIPE_YUV2YUV
- /* YUV Raw to YUV Raw output */
-};
-
-#define IPIPE_COLPTN_R_Ye 0x0
-#define IPIPE_COLPTN_Gr_Cy 0x1
-#define IPIPE_COLPTN_Gb_G 0x2
-#define IPIPE_COLPTN_B_Mg 0x3
-
-#define COLPAT_EE_SHIFT 0
-#define COLPAT_EO_SHIFT 2
-#define COLPAT_OE_SHIFT 4
-#define COLPAT_OO_SHIFT 6
-
-#define ipipe_sgrbg_pattern \
- (IPIPE_COLPTN_Gr_Cy << COLPAT_EE_SHIFT | \
- IPIPE_COLPTN_R_Ye << COLPAT_EO_SHIFT | \
- IPIPE_COLPTN_B_Mg << COLPAT_OE_SHIFT | \
- IPIPE_COLPTN_Gb_G << COLPAT_OO_SHIFT)
-
-#define ipipe_srggb_pattern \
- (IPIPE_COLPTN_R_Ye << COLPAT_EE_SHIFT | \
- IPIPE_COLPTN_Gr_Cy << COLPAT_EO_SHIFT | \
- IPIPE_COLPTN_Gb_G << COLPAT_OE_SHIFT | \
- IPIPE_COLPTN_B_Mg << COLPAT_OO_SHIFT)
-
-int vpfe_ipipe_register_entities(struct vpfe_ipipe_device *ipipe,
- struct v4l2_device *v4l2_dev);
-int vpfe_ipipe_init(struct vpfe_ipipe_device *ipipe,
- struct platform_device *pdev);
-void vpfe_ipipe_unregister_entities(struct vpfe_ipipe_device *ipipe);
-void vpfe_ipipe_cleanup(struct vpfe_ipipe_device *ipipe,
- struct platform_device *pdev);
-void vpfe_ipipe_enable(struct vpfe_device *vpfe_dev, int en);
-
-#endif /* _DAVINCI_VPFE_DM365_IPIPE_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c
deleted file mode 100644
index 110473c30577..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c
+++ /dev/null
@@ -1,1038 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#include "dm365_ipipe_hw.h"
-
-#define IPIPE_MODE_CONTINUOUS 0
-#define IPIPE_MODE_SINGLE_SHOT 1
-
-static void ipipe_clock_enable(void __iomem *base_addr)
-{
- /* enable IPIPE MMR for register write access */
- regw_ip(base_addr, IPIPE_GCK_MMR_DEFAULT, IPIPE_GCK_MMR);
-
- /* enable the clock wb,cfa,dfc,d2f,pre modules */
- regw_ip(base_addr, IPIPE_GCK_PIX_DEFAULT, IPIPE_GCK_PIX);
-}
-
-static void
-rsz_set_common_params(void __iomem *rsz_base, struct resizer_params *params)
-{
- struct rsz_common_params *rsz_common = &params->rsz_common;
- u32 val;
-
- /* Set mode */
- regw_rsz(rsz_base, params->oper_mode, RSZ_SRC_MODE);
-
- /* data source selection and bypass */
- val = (rsz_common->passthrough << RSZ_BYPASS_SHIFT) |
- rsz_common->source;
- regw_rsz(rsz_base, val, RSZ_SRC_FMT0);
-
- /* src image selection */
- val = (rsz_common->raw_flip & 1) |
- (rsz_common->src_img_fmt << RSZ_SRC_IMG_FMT_SHIFT) |
- ((rsz_common->y_c & 1) << RSZ_SRC_Y_C_SEL_SHIFT);
- regw_rsz(rsz_base, val, RSZ_SRC_FMT1);
-
- regw_rsz(rsz_base, rsz_common->vps & IPIPE_RSZ_VPS_MASK, RSZ_SRC_VPS);
- regw_rsz(rsz_base, rsz_common->hps & IPIPE_RSZ_HPS_MASK, RSZ_SRC_HPS);
- regw_rsz(rsz_base, rsz_common->vsz & IPIPE_RSZ_VSZ_MASK, RSZ_SRC_VSZ);
- regw_rsz(rsz_base, rsz_common->hsz & IPIPE_RSZ_HSZ_MASK, RSZ_SRC_HSZ);
- regw_rsz(rsz_base, rsz_common->yuv_y_min, RSZ_YUV_Y_MIN);
- regw_rsz(rsz_base, rsz_common->yuv_y_max, RSZ_YUV_Y_MAX);
- regw_rsz(rsz_base, rsz_common->yuv_c_min, RSZ_YUV_C_MIN);
- regw_rsz(rsz_base, rsz_common->yuv_c_max, RSZ_YUV_C_MAX);
- /* chromatic position */
- regw_rsz(rsz_base, rsz_common->out_chr_pos, RSZ_YUV_PHS);
-}
-
-static void
-rsz_set_rsz_regs(void __iomem *rsz_base, unsigned int rsz_id,
- struct resizer_params *params)
-{
- struct resizer_scale_param *rsc_params;
- struct rsz_ext_mem_param *ext_mem;
- struct resizer_rgb *rgb;
- u32 reg_base;
- u32 val;
-
- rsc_params = &params->rsz_rsc_param[rsz_id];
- rgb = &params->rsz2rgb[rsz_id];
- ext_mem = &params->ext_mem_param[rsz_id];
-
- if (rsz_id == RSZ_A) {
- val = rsc_params->h_flip << RSZA_H_FLIP_SHIFT;
- val |= rsc_params->v_flip << RSZA_V_FLIP_SHIFT;
- reg_base = RSZ_EN_A;
- } else {
- val = rsc_params->h_flip << RSZB_H_FLIP_SHIFT;
- val |= rsc_params->v_flip << RSZB_V_FLIP_SHIFT;
- reg_base = RSZ_EN_B;
- }
- /* update flip settings */
- regw_rsz(rsz_base, val, RSZ_SEQ);
-
- regw_rsz(rsz_base, params->oper_mode, reg_base + RSZ_MODE);
-
- val = (rsc_params->cen << RSZ_CEN_SHIFT) | rsc_params->yen;
- regw_rsz(rsz_base, val, reg_base + RSZ_420);
-
- regw_rsz(rsz_base, rsc_params->i_vps & RSZ_VPS_MASK,
- reg_base + RSZ_I_VPS);
- regw_rsz(rsz_base, rsc_params->i_hps & RSZ_HPS_MASK,
- reg_base + RSZ_I_HPS);
- regw_rsz(rsz_base, rsc_params->o_vsz & RSZ_O_VSZ_MASK,
- reg_base + RSZ_O_VSZ);
- regw_rsz(rsz_base, rsc_params->o_hsz & RSZ_O_HSZ_MASK,
- reg_base + RSZ_O_HSZ);
- regw_rsz(rsz_base, rsc_params->v_phs_y & RSZ_V_PHS_MASK,
- reg_base + RSZ_V_PHS_Y);
- regw_rsz(rsz_base, rsc_params->v_phs_c & RSZ_V_PHS_MASK,
- reg_base + RSZ_V_PHS_C);
-
- /* keep this additional adjustment to zero for now */
- regw_rsz(rsz_base, rsc_params->v_dif & RSZ_V_DIF_MASK,
- reg_base + RSZ_V_DIF);
-
- val = (rsc_params->v_typ_y & 1) |
- ((rsc_params->v_typ_c & 1) << RSZ_TYP_C_SHIFT);
- regw_rsz(rsz_base, val, reg_base + RSZ_V_TYP);
-
- val = (rsc_params->v_lpf_int_y & RSZ_LPF_INT_MASK) |
- ((rsc_params->v_lpf_int_c & RSZ_LPF_INT_MASK) <<
- RSZ_LPF_INT_C_SHIFT);
- regw_rsz(rsz_base, val, reg_base + RSZ_V_LPF);
-
- regw_rsz(rsz_base, rsc_params->h_phs &
- RSZ_H_PHS_MASK, reg_base + RSZ_H_PHS);
-
- regw_rsz(rsz_base, 0, reg_base + RSZ_H_PHS_ADJ);
- regw_rsz(rsz_base, rsc_params->h_dif &
- RSZ_H_DIF_MASK, reg_base + RSZ_H_DIF);
-
- val = (rsc_params->h_typ_y & 1) |
- ((rsc_params->h_typ_c & 1) << RSZ_TYP_C_SHIFT);
- regw_rsz(rsz_base, val, reg_base + RSZ_H_TYP);
-
- val = (rsc_params->h_lpf_int_y & RSZ_LPF_INT_MASK) |
- ((rsc_params->h_lpf_int_c & RSZ_LPF_INT_MASK) <<
- RSZ_LPF_INT_C_SHIFT);
- regw_rsz(rsz_base, val, reg_base + RSZ_H_LPF);
-
- regw_rsz(rsz_base, rsc_params->dscale_en & 1, reg_base + RSZ_DWN_EN);
-
- val = (rsc_params->h_dscale_ave_sz & RSZ_DWN_SCALE_AV_SZ_MASK) |
- ((rsc_params->v_dscale_ave_sz & RSZ_DWN_SCALE_AV_SZ_MASK) <<
- RSZ_DWN_SCALE_AV_SZ_V_SHIFT);
- regw_rsz(rsz_base, val, reg_base + RSZ_DWN_AV);
-
- /* setting rgb conversion parameters */
- regw_rsz(rsz_base, rgb->rgb_en, reg_base + RSZ_RGB_EN);
-
- val = (rgb->rgb_typ << RSZ_RGB_TYP_SHIFT) |
- (rgb->rgb_msk0 << RSZ_RGB_MSK0_SHIFT) |
- (rgb->rgb_msk1 << RSZ_RGB_MSK1_SHIFT);
- regw_rsz(rsz_base, val, reg_base + RSZ_RGB_TYP);
-
- regw_rsz(rsz_base, rgb->rgb_alpha_val & RSZ_RGB_ALPHA_MASK,
- reg_base + RSZ_RGB_BLD);
-
- /* setting external memory parameters */
- regw_rsz(rsz_base, ext_mem->rsz_sdr_oft_y, reg_base + RSZ_SDR_Y_OFT);
- regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_s_y,
- reg_base + RSZ_SDR_Y_PTR_S);
- regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_e_y,
- reg_base + RSZ_SDR_Y_PTR_E);
- regw_rsz(rsz_base, ext_mem->rsz_sdr_oft_c, reg_base + RSZ_SDR_C_OFT);
- regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_s_c,
- reg_base + RSZ_SDR_C_PTR_S);
- regw_rsz(rsz_base, (ext_mem->rsz_sdr_ptr_e_c >> 1),
- reg_base + RSZ_SDR_C_PTR_E);
-}
-
-/*set the registers of either RSZ0 or RSZ1 */
-static void
-ipipe_setup_resizer(void __iomem *rsz_base, struct resizer_params *params)
-{
- /* enable MMR gate to write to Resizer */
- regw_rsz(rsz_base, 1, RSZ_GCK_MMR);
-
- /* Enable resizer if it is not in bypass mode */
- if (params->rsz_common.passthrough)
- regw_rsz(rsz_base, 0, RSZ_GCK_SDR);
- else
- regw_rsz(rsz_base, 1, RSZ_GCK_SDR);
-
- rsz_set_common_params(rsz_base, params);
-
- regw_rsz(rsz_base, params->rsz_en[RSZ_A], RSZ_EN_A);
-
- if (params->rsz_en[RSZ_A])
- /*setting rescale parameters */
- rsz_set_rsz_regs(rsz_base, RSZ_A, params);
-
- regw_rsz(rsz_base, params->rsz_en[RSZ_B], RSZ_EN_B);
-
- if (params->rsz_en[RSZ_B])
- rsz_set_rsz_regs(rsz_base, RSZ_B, params);
-}
-
-static u32 ipipe_get_color_pat(u32 pix)
-{
- switch (pix) {
- case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8:
- case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8:
- case MEDIA_BUS_FMT_SGRBG12_1X12:
- return ipipe_sgrbg_pattern;
-
- default:
- return ipipe_srggb_pattern;
- }
-}
-
-static int ipipe_get_data_path(struct vpfe_ipipe_device *ipipe)
-{
- u32 temp_pix_fmt;
-
- switch (ipipe->formats[IPIPE_PAD_SINK].code) {
- case MEDIA_BUS_FMT_SBGGR8_1X8:
- case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8:
- case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8:
- case MEDIA_BUS_FMT_SGRBG12_1X12:
- temp_pix_fmt = MEDIA_BUS_FMT_SGRBG12_1X12;
- break;
-
- default:
- temp_pix_fmt = MEDIA_BUS_FMT_UYVY8_2X8;
- }
-
- if (temp_pix_fmt == MEDIA_BUS_FMT_SGRBG12_1X12) {
- if (ipipe->formats[IPIPE_PAD_SOURCE].code ==
- MEDIA_BUS_FMT_SGRBG12_1X12)
- return IPIPE_RAW2RAW;
- return IPIPE_RAW2YUV;
- }
-
- return IPIPE_YUV2YUV;
-}
-
-static int get_ipipe_mode(struct vpfe_ipipe_device *ipipe)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe);
- u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
-
- if (ipipeif_sink == IPIPEIF_INPUT_MEMORY)
- return IPIPE_MODE_SINGLE_SHOT;
- if (ipipeif_sink == IPIPEIF_INPUT_ISIF)
- return IPIPE_MODE_CONTINUOUS;
-
- return -EINVAL;
-}
-
-int config_ipipe_hw(struct vpfe_ipipe_device *ipipe)
-{
- struct vpfe_ipipe_input_config *config = &ipipe->config.input_config;
- void __iomem *ipipe_base = ipipe->base_addr;
- struct v4l2_mbus_framefmt *outformat;
- u32 color_pat;
- int ipipe_mode;
- u32 data_path;
-
- /* enable clock to IPIPE */
- vpss_enable_clock(VPSS_IPIPE_CLOCK, 1);
- ipipe_clock_enable(ipipe_base);
-
- if (ipipe->input == IPIPE_INPUT_NONE) {
- regw_ip(ipipe_base, 0, IPIPE_SRC_EN);
- return 0;
- }
-
- ipipe_mode = get_ipipe_mode(ipipe);
- if (ipipe_mode < 0) {
- pr_err("Failed to get ipipe mode");
- return -EINVAL;
- }
- regw_ip(ipipe_base, ipipe_mode, IPIPE_SRC_MODE);
-
- data_path = ipipe_get_data_path(ipipe);
- regw_ip(ipipe_base, data_path, IPIPE_SRC_FMT);
-
- regw_ip(ipipe_base, config->vst & IPIPE_RSZ_VPS_MASK, IPIPE_SRC_VPS);
- regw_ip(ipipe_base, config->hst & IPIPE_RSZ_HPS_MASK, IPIPE_SRC_HPS);
-
- outformat = &ipipe->formats[IPIPE_PAD_SOURCE];
- regw_ip(ipipe_base, (outformat->height + 1) & IPIPE_RSZ_VSZ_MASK,
- IPIPE_SRC_VSZ);
- regw_ip(ipipe_base, (outformat->width + 1) & IPIPE_RSZ_HSZ_MASK,
- IPIPE_SRC_HSZ);
-
- if (data_path == IPIPE_RAW2YUV ||
- data_path == IPIPE_RAW2RAW) {
- color_pat =
- ipipe_get_color_pat(ipipe->formats[IPIPE_PAD_SINK].code);
- regw_ip(ipipe_base, color_pat, IPIPE_SRC_COL);
- }
-
- return 0;
-}
-
-/*
- * config_rsz_hw() - Performs hardware setup of resizer.
- */
-int config_rsz_hw(struct vpfe_resizer_device *resizer,
- struct resizer_params *config)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- void __iomem *ipipe_base = vpfe_dev->vpfe_ipipe.base_addr;
- void __iomem *rsz_base = vpfe_dev->vpfe_resizer.base_addr;
-
- /* enable VPSS clock */
- vpss_enable_clock(VPSS_IPIPE_CLOCK, 1);
- ipipe_clock_enable(ipipe_base);
-
- ipipe_setup_resizer(rsz_base, config);
-
- return 0;
-}
-
-static void
-rsz_set_y_address(void __iomem *rsz_base, unsigned int address,
- unsigned int offset)
-{
- u32 val;
-
- val = address & SET_LOW_ADDR;
- regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_BAD_L);
- regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_SAD_L);
-
- val = (address & SET_HIGH_ADDR) >> 16;
- regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_BAD_H);
- regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_SAD_H);
-}
-
-static void
-rsz_set_c_address(void __iomem *rsz_base, unsigned int address,
- unsigned int offset)
-{
- u32 val;
-
- val = address & SET_LOW_ADDR;
- regw_rsz(rsz_base, val, offset + RSZ_SDR_C_BAD_L);
- regw_rsz(rsz_base, val, offset + RSZ_SDR_C_SAD_L);
-
- val = (address & SET_HIGH_ADDR) >> 16;
- regw_rsz(rsz_base, val, offset + RSZ_SDR_C_BAD_H);
- regw_rsz(rsz_base, val, offset + RSZ_SDR_C_SAD_H);
-}
-
-/*
- * resizer_set_outaddr() - set the address for given resize_no
- * @rsz_base: resizer base address
- * @params: pointer to ipipe_params structure
- * @resize_no: 0 - Resizer-A, 1 - Resizer B
- * @address: the address to set
- */
-int
-resizer_set_outaddr(void __iomem *rsz_base, struct resizer_params *params,
- int resize_no, unsigned int address)
-{
- struct resizer_scale_param *rsc_param;
- struct rsz_ext_mem_param *mem_param;
- struct rsz_common_params *rsz_common;
- unsigned int rsz_start_add;
- unsigned int val;
-
- if (resize_no != RSZ_A && resize_no != RSZ_B)
- return -EINVAL;
-
- mem_param = &params->ext_mem_param[resize_no];
- rsc_param = &params->rsz_rsc_param[resize_no];
- rsz_common = &params->rsz_common;
-
- if (resize_no == RSZ_A)
- rsz_start_add = RSZ_EN_A;
- else
- rsz_start_add = RSZ_EN_B;
-
- /* y_c = 0 for y, = 1 for c */
- if (rsz_common->src_img_fmt == RSZ_IMG_420) {
- if (rsz_common->y_c) {
- /* C channel */
- val = address + mem_param->flip_ofst_c;
- rsz_set_c_address(rsz_base, val, rsz_start_add);
- } else {
- val = address + mem_param->flip_ofst_y;
- rsz_set_y_address(rsz_base, val, rsz_start_add);
- }
- } else {
- if (rsc_param->cen && rsc_param->yen) {
- /* 420 */
- val = address + mem_param->c_offset +
- mem_param->flip_ofst_c +
- mem_param->user_y_ofst +
- mem_param->user_c_ofst;
- if (resize_no == RSZ_B)
- val +=
- params->ext_mem_param[RSZ_A].user_y_ofst +
- params->ext_mem_param[RSZ_A].user_c_ofst;
- /* set C address */
- rsz_set_c_address(rsz_base, val, rsz_start_add);
- }
- val = address + mem_param->flip_ofst_y + mem_param->user_y_ofst;
- if (resize_no == RSZ_B)
- val += params->ext_mem_param[RSZ_A].user_y_ofst +
- params->ext_mem_param[RSZ_A].user_c_ofst;
- /* set Y address */
- rsz_set_y_address(rsz_base, val, rsz_start_add);
- }
- /* resizer must be enabled */
- regw_rsz(rsz_base, params->rsz_en[resize_no], rsz_start_add);
-
- return 0;
-}
-
-void
-ipipe_set_lutdpc_regs(void __iomem *base_addr, void __iomem *isp5_base_addr,
- struct vpfe_ipipe_lutdpc *dpc)
-{
- u32 max_tbl_size = LUT_DPC_MAX_SIZE >> 1;
- u32 lut_start_addr = DPC_TB0_START_ADDR;
- u32 val;
- u32 count;
-
- ipipe_clock_enable(base_addr);
- regw_ip(base_addr, dpc->en, DPC_LUT_EN);
-
- if (dpc->en != 1)
- return;
-
- val = LUTDPC_TBL_256_EN | (dpc->repl_white & 1);
- regw_ip(base_addr, val, DPC_LUT_SEL);
- regw_ip(base_addr, LUT_DPC_START_ADDR, DPC_LUT_ADR);
- regw_ip(base_addr, dpc->dpc_size, DPC_LUT_SIZ & LUT_DPC_SIZE_MASK);
-
- for (count = 0; count < dpc->dpc_size; count++) {
- if (count >= max_tbl_size)
- lut_start_addr = DPC_TB1_START_ADDR;
- val = (dpc->table[count].horz_pos & LUT_DPC_H_POS_MASK) |
- ((dpc->table[count].vert_pos & LUT_DPC_V_POS_MASK) <<
- LUT_DPC_V_POS_SHIFT) | (dpc->table[count].method <<
- LUT_DPC_CORR_METH_SHIFT);
- w_ip_table(isp5_base_addr, val, (lut_start_addr +
- ((count % max_tbl_size) << 2)));
- }
-}
-
-static void
-set_dpc_thresholds(void __iomem *base_addr,
- struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_thr)
-{
- regw_ip(base_addr, dpc_thr->corr_thr.r & OTFDPC_DPC2_THR_MASK,
- DPC_OTF_2C_THR_R);
- regw_ip(base_addr, dpc_thr->corr_thr.gr & OTFDPC_DPC2_THR_MASK,
- DPC_OTF_2C_THR_GR);
- regw_ip(base_addr, dpc_thr->corr_thr.gb & OTFDPC_DPC2_THR_MASK,
- DPC_OTF_2C_THR_GB);
- regw_ip(base_addr, dpc_thr->corr_thr.b & OTFDPC_DPC2_THR_MASK,
- DPC_OTF_2C_THR_B);
- regw_ip(base_addr, dpc_thr->det_thr.r & OTFDPC_DPC2_THR_MASK,
- DPC_OTF_2D_THR_R);
- regw_ip(base_addr, dpc_thr->det_thr.gr & OTFDPC_DPC2_THR_MASK,
- DPC_OTF_2D_THR_GR);
- regw_ip(base_addr, dpc_thr->det_thr.gb & OTFDPC_DPC2_THR_MASK,
- DPC_OTF_2D_THR_GB);
- regw_ip(base_addr, dpc_thr->det_thr.b & OTFDPC_DPC2_THR_MASK,
- DPC_OTF_2D_THR_B);
-}
-
-void ipipe_set_otfdpc_regs(void __iomem *base_addr,
- struct vpfe_ipipe_otfdpc *otfdpc)
-{
- struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_2_0 = &otfdpc->alg_cfg.dpc_2_0;
- struct vpfe_ipipe_otfdpc_3_0_cfg *dpc_3_0 = &otfdpc->alg_cfg.dpc_3_0;
- u32 val;
-
- ipipe_clock_enable(base_addr);
-
- regw_ip(base_addr, (otfdpc->en & 1), DPC_OTF_EN);
- if (!otfdpc->en)
- return;
-
- /* dpc enabled */
- val = (otfdpc->det_method << OTF_DET_METHOD_SHIFT) | otfdpc->alg;
- regw_ip(base_addr, val, DPC_OTF_TYP);
-
- if (otfdpc->det_method == VPFE_IPIPE_DPC_OTF_MIN_MAX) {
- /* ALG= 0, TYP = 0, DPC_OTF_2D_THR_[x]=0
- * DPC_OTF_2C_THR_[x] = Maximum thresohld
- * MinMax method
- */
- dpc_2_0->det_thr.r = dpc_2_0->det_thr.gb =
- dpc_2_0->det_thr.gr = dpc_2_0->det_thr.b = 0;
- set_dpc_thresholds(base_addr, dpc_2_0);
- return;
- }
- /* MinMax2 */
- if (otfdpc->alg == VPFE_IPIPE_OTFDPC_2_0) {
- set_dpc_thresholds(base_addr, dpc_2_0);
- return;
- }
- regw_ip(base_addr, dpc_3_0->act_adj_shf &
- OTF_DPC3_0_SHF_MASK, DPC_OTF_3_SHF);
- /* Detection thresholds */
- regw_ip(base_addr, ((dpc_3_0->det_thr & OTF_DPC3_0_THR_MASK) <<
- OTF_DPC3_0_THR_SHIFT), DPC_OTF_3D_THR);
- regw_ip(base_addr, dpc_3_0->det_slp &
- OTF_DPC3_0_SLP_MASK, DPC_OTF_3D_SLP);
- regw_ip(base_addr, dpc_3_0->det_thr_min &
- OTF_DPC3_0_DET_MASK, DPC_OTF_3D_MIN);
- regw_ip(base_addr, dpc_3_0->det_thr_max &
- OTF_DPC3_0_DET_MASK, DPC_OTF_3D_MAX);
- /* Correction thresholds */
- regw_ip(base_addr, ((dpc_3_0->corr_thr & OTF_DPC3_0_THR_MASK) <<
- OTF_DPC3_0_THR_SHIFT), DPC_OTF_3C_THR);
- regw_ip(base_addr, dpc_3_0->corr_slp &
- OTF_DPC3_0_SLP_MASK, DPC_OTF_3C_SLP);
- regw_ip(base_addr, dpc_3_0->corr_thr_min &
- OTF_DPC3_0_CORR_MASK, DPC_OTF_3C_MIN);
- regw_ip(base_addr, dpc_3_0->corr_thr_max &
- OTF_DPC3_0_CORR_MASK, DPC_OTF_3C_MAX);
-}
-
-/* 2D Noise filter */
-void
-ipipe_set_d2f_regs(void __iomem *base_addr, unsigned int id,
- struct vpfe_ipipe_nf *noise_filter)
-{
-
- u32 offset = D2F_1ST;
- int count;
- u32 val;
-
- if (id == IPIPE_D2F_2ND)
- offset = D2F_2ND;
-
- ipipe_clock_enable(base_addr);
- regw_ip(base_addr, noise_filter->en & 1, offset + D2F_EN);
- if (!noise_filter->en)
- return;
-
- /*noise filter enabled */
- /* Combine all the fields to make D2F_CFG register of IPIPE */
- val = ((noise_filter->spread_val & D2F_SPR_VAL_MASK) <<
- D2F_SPR_VAL_SHIFT) | ((noise_filter->shft_val &
- D2F_SHFT_VAL_MASK) << D2F_SHFT_VAL_SHIFT) |
- (noise_filter->gr_sample_meth << D2F_SAMPLE_METH_SHIFT) |
- ((noise_filter->apply_lsc_gain & 1) <<
- D2F_APPLY_LSC_GAIN_SHIFT) | D2F_USE_SPR_REG_VAL;
- regw_ip(base_addr, val, offset + D2F_TYP);
-
- /* edge detection minimum */
- regw_ip(base_addr, noise_filter->edge_det_min_thr &
- D2F_EDGE_DET_THR_MASK, offset + D2F_EDG_MIN);
-
- /* edge detection maximum */
- regw_ip(base_addr, noise_filter->edge_det_max_thr &
- D2F_EDGE_DET_THR_MASK, offset + D2F_EDG_MAX);
-
- for (count = 0; count < VPFE_IPIPE_NF_STR_TABLE_SIZE; count++)
- regw_ip(base_addr,
- (noise_filter->str[count] & D2F_STR_VAL_MASK),
- offset + D2F_STR + count * 4);
-
- for (count = 0; count < VPFE_IPIPE_NF_THR_TABLE_SIZE; count++)
- regw_ip(base_addr, noise_filter->thr[count] & D2F_THR_VAL_MASK,
- offset + D2F_THR + count * 4);
-}
-
-#define IPIPE_U8Q5(decimal, integer) \
- (((decimal & 0x1f) | ((integer & 0x7) << 5)))
-
-/* Green Imbalance Correction */
-void ipipe_set_gic_regs(void __iomem *base_addr, struct vpfe_ipipe_gic *gic)
-{
- u32 val;
-
- ipipe_clock_enable(base_addr);
- regw_ip(base_addr, gic->en & 1, GIC_EN);
-
- if (!gic->en)
- return;
-
- /*gic enabled */
- val = (gic->wt_fn_type << GIC_TYP_SHIFT) |
- (gic->thr_sel << GIC_THR_SEL_SHIFT) |
- ((gic->apply_lsc_gain & 1) << GIC_APPLY_LSC_GAIN_SHIFT);
- regw_ip(base_addr, val, GIC_TYP);
-
- regw_ip(base_addr, gic->gain & GIC_GAIN_MASK, GIC_GAN);
-
- if (gic->gic_alg != VPFE_IPIPE_GIC_ALG_ADAPT_GAIN) {
- /* Constant Gain. Set threshold to maximum */
- regw_ip(base_addr, GIC_THR_MASK, GIC_THR);
- return;
- }
-
- if (gic->thr_sel == VPFE_IPIPE_GIC_THR_REG) {
- regw_ip(base_addr, gic->thr & GIC_THR_MASK, GIC_THR);
- regw_ip(base_addr, gic->slope & GIC_SLOPE_MASK, GIC_SLP);
- } else {
- /* Use NF thresholds */
- val = IPIPE_U8Q5(gic->nf2_thr_gain.decimal,
- gic->nf2_thr_gain.integer);
- regw_ip(base_addr, val, GIC_NFGAN);
- }
-}
-
-#define IPIPE_U13Q9(decimal, integer) \
- (((decimal & 0x1ff) | ((integer & 0xf) << 9)))
-/* White balance */
-void ipipe_set_wb_regs(void __iomem *base_addr, struct vpfe_ipipe_wb *wb)
-{
- u32 val;
-
- ipipe_clock_enable(base_addr);
- /* Ofsets. S12 */
- regw_ip(base_addr, wb->ofst_r & WB_OFFSET_MASK, WB2_OFT_R);
- regw_ip(base_addr, wb->ofst_gr & WB_OFFSET_MASK, WB2_OFT_GR);
- regw_ip(base_addr, wb->ofst_gb & WB_OFFSET_MASK, WB2_OFT_GB);
- regw_ip(base_addr, wb->ofst_b & WB_OFFSET_MASK, WB2_OFT_B);
-
- /* Gains. U13Q9 */
- val = IPIPE_U13Q9(wb->gain_r.decimal, wb->gain_r.integer);
- regw_ip(base_addr, val, WB2_WGN_R);
-
- val = IPIPE_U13Q9(wb->gain_gr.decimal, wb->gain_gr.integer);
- regw_ip(base_addr, val, WB2_WGN_GR);
-
- val = IPIPE_U13Q9(wb->gain_gb.decimal, wb->gain_gb.integer);
- regw_ip(base_addr, val, WB2_WGN_GB);
-
- val = IPIPE_U13Q9(wb->gain_b.decimal, wb->gain_b.integer);
- regw_ip(base_addr, val, WB2_WGN_B);
-}
-
-/* CFA */
-void ipipe_set_cfa_regs(void __iomem *base_addr, struct vpfe_ipipe_cfa *cfa)
-{
- ipipe_clock_enable(base_addr);
-
- regw_ip(base_addr, cfa->alg, CFA_MODE);
- regw_ip(base_addr, cfa->hpf_thr_2dir & CFA_HPF_THR_2DIR_MASK,
- CFA_2DIR_HPF_THR);
- regw_ip(base_addr, cfa->hpf_slp_2dir & CFA_HPF_SLOPE_2DIR_MASK,
- CFA_2DIR_HPF_SLP);
- regw_ip(base_addr, cfa->hp_mix_thr_2dir & CFA_HPF_MIX_THR_2DIR_MASK,
- CFA_2DIR_MIX_THR);
- regw_ip(base_addr, cfa->hp_mix_slope_2dir & CFA_HPF_MIX_SLP_2DIR_MASK,
- CFA_2DIR_MIX_SLP);
- regw_ip(base_addr, cfa->dir_thr_2dir & CFA_DIR_THR_2DIR_MASK,
- CFA_2DIR_DIR_THR);
- regw_ip(base_addr, cfa->dir_slope_2dir & CFA_DIR_SLP_2DIR_MASK,
- CFA_2DIR_DIR_SLP);
- regw_ip(base_addr, cfa->nd_wt_2dir & CFA_ND_WT_2DIR_MASK,
- CFA_2DIR_NDWT);
- regw_ip(base_addr, cfa->hue_fract_daa & CFA_DAA_HUE_FRA_MASK,
- CFA_MONO_HUE_FRA);
- regw_ip(base_addr, cfa->edge_thr_daa & CFA_DAA_EDG_THR_MASK,
- CFA_MONO_EDG_THR);
- regw_ip(base_addr, cfa->thr_min_daa & CFA_DAA_THR_MIN_MASK,
- CFA_MONO_THR_MIN);
- regw_ip(base_addr, cfa->thr_slope_daa & CFA_DAA_THR_SLP_MASK,
- CFA_MONO_THR_SLP);
- regw_ip(base_addr, cfa->slope_min_daa & CFA_DAA_SLP_MIN_MASK,
- CFA_MONO_SLP_MIN);
- regw_ip(base_addr, cfa->slope_slope_daa & CFA_DAA_SLP_SLP_MASK,
- CFA_MONO_SLP_SLP);
- regw_ip(base_addr, cfa->lp_wt_daa & CFA_DAA_LP_WT_MASK,
- CFA_MONO_LPWT);
-}
-
-void
-ipipe_set_rgb2rgb_regs(void __iomem *base_addr, unsigned int id,
- struct vpfe_ipipe_rgb2rgb *rgb)
-{
- u32 offset_mask = RGB2RGB_1_OFST_MASK;
- u32 offset = RGB1_MUL_BASE;
- u32 integ_mask = 0xf;
- u32 val;
-
- ipipe_clock_enable(base_addr);
-
- if (id == IPIPE_RGB2RGB_2) {
- /*
- * For second RGB module, gain integer is 3 bits instead
- * of 4, offset has 11 bits insread of 13
- */
- offset = RGB2_MUL_BASE;
- integ_mask = 0x7;
- offset_mask = RGB2RGB_2_OFST_MASK;
- }
- /* Gains */
- val = (rgb->coef_rr.decimal & 0xff) |
- ((rgb->coef_rr.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_RR);
- val = (rgb->coef_gr.decimal & 0xff) |
- ((rgb->coef_gr.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_GR);
- val = (rgb->coef_br.decimal & 0xff) |
- ((rgb->coef_br.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_BR);
- val = (rgb->coef_rg.decimal & 0xff) |
- ((rgb->coef_rg.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_RG);
- val = (rgb->coef_gg.decimal & 0xff) |
- ((rgb->coef_gg.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_GG);
- val = (rgb->coef_bg.decimal & 0xff) |
- ((rgb->coef_bg.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_BG);
- val = (rgb->coef_rb.decimal & 0xff) |
- ((rgb->coef_rb.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_RB);
- val = (rgb->coef_gb.decimal & 0xff) |
- ((rgb->coef_gb.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_GB);
- val = (rgb->coef_bb.decimal & 0xff) |
- ((rgb->coef_bb.integer & integ_mask) << 8);
- regw_ip(base_addr, val, offset + RGB_MUL_BB);
-
- /* Offsets */
- regw_ip(base_addr, rgb->out_ofst_r & offset_mask, offset + RGB_OFT_OR);
- regw_ip(base_addr, rgb->out_ofst_g & offset_mask, offset + RGB_OFT_OG);
- regw_ip(base_addr, rgb->out_ofst_b & offset_mask, offset + RGB_OFT_OB);
-}
-
-static void
-ipipe_update_gamma_tbl(void __iomem *isp5_base_addr,
- struct vpfe_ipipe_gamma_entry *table, int size, u32 addr)
-{
- int count;
- u32 val;
-
- for (count = 0; count < size; count++) {
- val = table[count].slope & GAMMA_MASK;
- val |= (table[count].offset & GAMMA_MASK) << GAMMA_SHIFT;
- w_ip_table(isp5_base_addr, val, (addr + (count * 4)));
- }
-}
-
-void
-ipipe_set_gamma_regs(void __iomem *base_addr, void __iomem *isp5_base_addr,
- struct vpfe_ipipe_gamma *gamma)
-{
- int table_size;
- u32 val;
-
- ipipe_clock_enable(base_addr);
- val = (gamma->bypass_r << GAMMA_BYPR_SHIFT) |
- (gamma->bypass_b << GAMMA_BYPG_SHIFT) |
- (gamma->bypass_g << GAMMA_BYPB_SHIFT) |
- (gamma->tbl_sel << GAMMA_TBL_SEL_SHIFT) |
- (gamma->tbl_size << GAMMA_TBL_SIZE_SHIFT);
-
- regw_ip(base_addr, val, GMM_CFG);
-
- if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM)
- return;
-
- table_size = gamma->tbl_size;
-
- if (!gamma->bypass_r)
- ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_r,
- table_size, GAMMA_R_START_ADDR);
- if (!gamma->bypass_b)
- ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_b,
- table_size, GAMMA_B_START_ADDR);
- if (!gamma->bypass_g)
- ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_g,
- table_size, GAMMA_G_START_ADDR);
-}
-
-void
-ipipe_set_3d_lut_regs(void __iomem *base_addr, void __iomem *isp5_base_addr,
- struct vpfe_ipipe_3d_lut *lut_3d)
-{
- struct vpfe_ipipe_3d_lut_entry *tbl;
- u32 bnk_index;
- u32 tbl_index;
- u32 val;
- u32 i;
-
- ipipe_clock_enable(base_addr);
- regw_ip(base_addr, lut_3d->en, D3LUT_EN);
-
- if (!lut_3d->en)
- return;
-
- /* valid table */
- tbl = lut_3d->table;
- for (i = 0; i < VPFE_IPIPE_MAX_SIZE_3D_LUT; i++) {
- /*
- * Each entry has 0-9 (B), 10-19 (G) and
- * 20-29 R values
- */
- val = tbl[i].b & D3_LUT_ENTRY_MASK;
- val |= (tbl[i].g & D3_LUT_ENTRY_MASK) <<
- D3_LUT_ENTRY_G_SHIFT;
- val |= (tbl[i].r & D3_LUT_ENTRY_MASK) <<
- D3_LUT_ENTRY_R_SHIFT;
- bnk_index = i % 4;
- tbl_index = i >> 2;
- tbl_index <<= 2;
- if (bnk_index == 0)
- w_ip_table(isp5_base_addr, val,
- tbl_index + D3L_TB0_START_ADDR);
- else if (bnk_index == 1)
- w_ip_table(isp5_base_addr, val,
- tbl_index + D3L_TB1_START_ADDR);
- else if (bnk_index == 2)
- w_ip_table(isp5_base_addr, val,
- tbl_index + D3L_TB2_START_ADDR);
- else
- w_ip_table(isp5_base_addr, val,
- tbl_index + D3L_TB3_START_ADDR);
- }
-}
-
-/* Lumina adjustments */
-void
-ipipe_set_lum_adj_regs(void __iomem *base_addr, struct ipipe_lum_adj *lum_adj)
-{
- u32 val;
-
- ipipe_clock_enable(base_addr);
-
- /* combine fields of YUV_ADJ to set brightness and contrast */
- val = lum_adj->contrast << LUM_ADJ_CONTR_SHIFT |
- lum_adj->brightness << LUM_ADJ_BRIGHT_SHIFT;
- regw_ip(base_addr, val, YUV_ADJ);
-}
-
-inline u32 ipipe_s12q8(unsigned short decimal, short integer)
-{
- return (decimal & 0xff) | ((integer & 0xf) << 8);
-}
-
-void ipipe_set_rgb2ycbcr_regs(void __iomem *base_addr,
- struct vpfe_ipipe_rgb2yuv *yuv)
-{
- u32 val;
-
- /* S10Q8 */
- ipipe_clock_enable(base_addr);
- val = ipipe_s12q8(yuv->coef_ry.decimal, yuv->coef_ry.integer);
- regw_ip(base_addr, val, YUV_MUL_RY);
- val = ipipe_s12q8(yuv->coef_gy.decimal, yuv->coef_gy.integer);
- regw_ip(base_addr, val, YUV_MUL_GY);
- val = ipipe_s12q8(yuv->coef_by.decimal, yuv->coef_by.integer);
- regw_ip(base_addr, val, YUV_MUL_BY);
- val = ipipe_s12q8(yuv->coef_rcb.decimal, yuv->coef_rcb.integer);
- regw_ip(base_addr, val, YUV_MUL_RCB);
- val = ipipe_s12q8(yuv->coef_gcb.decimal, yuv->coef_gcb.integer);
- regw_ip(base_addr, val, YUV_MUL_GCB);
- val = ipipe_s12q8(yuv->coef_bcb.decimal, yuv->coef_bcb.integer);
- regw_ip(base_addr, val, YUV_MUL_BCB);
- val = ipipe_s12q8(yuv->coef_rcr.decimal, yuv->coef_rcr.integer);
- regw_ip(base_addr, val, YUV_MUL_RCR);
- val = ipipe_s12q8(yuv->coef_gcr.decimal, yuv->coef_gcr.integer);
- regw_ip(base_addr, val, YUV_MUL_GCR);
- val = ipipe_s12q8(yuv->coef_bcr.decimal, yuv->coef_bcr.integer);
- regw_ip(base_addr, val, YUV_MUL_BCR);
- regw_ip(base_addr, yuv->out_ofst_y & RGB2YCBCR_OFST_MASK, YUV_OFT_Y);
- regw_ip(base_addr, yuv->out_ofst_cb & RGB2YCBCR_OFST_MASK, YUV_OFT_CB);
- regw_ip(base_addr, yuv->out_ofst_cr & RGB2YCBCR_OFST_MASK, YUV_OFT_CR);
-}
-
-/* YUV 422 conversion */
-void
-ipipe_set_yuv422_conv_regs(void __iomem *base_addr,
- struct vpfe_ipipe_yuv422_conv *conv)
-{
- u32 val;
-
- ipipe_clock_enable(base_addr);
-
- /* Combine all the fields to make YUV_PHS register of IPIPE */
- val = (conv->chrom_pos << 0) | (conv->en_chrom_lpf << 1);
- regw_ip(base_addr, val, YUV_PHS);
-}
-
-void
-ipipe_set_gbce_regs(void __iomem *base_addr, void __iomem *isp5_base_addr,
- struct vpfe_ipipe_gbce *gbce)
-{
- unsigned int count;
- u32 mask = GBCE_Y_VAL_MASK;
-
- if (gbce->type == VPFE_IPIPE_GBCE_GAIN_TBL)
- mask = GBCE_GAIN_VAL_MASK;
-
- ipipe_clock_enable(base_addr);
- regw_ip(base_addr, gbce->en & 1, GBCE_EN);
-
- if (!gbce->en)
- return;
-
- regw_ip(base_addr, gbce->type, GBCE_TYP);
-
- for (count = 0; count < VPFE_IPIPE_MAX_SIZE_GBCE_LUT; count += 2)
- w_ip_table(isp5_base_addr, ((gbce->table[count + 1] & mask) <<
- GBCE_ENTRY_SHIFT) | (gbce->table[count] & mask),
- ((count/2) << 2) + GBCE_TB_START_ADDR);
-}
-
-void
-ipipe_set_ee_regs(void __iomem *base_addr, void __iomem *isp5_base_addr,
- struct vpfe_ipipe_yee *ee)
-{
- unsigned int count;
- u32 val;
-
- ipipe_clock_enable(base_addr);
- regw_ip(base_addr, ee->en, YEE_EN);
-
- if (!ee->en)
- return;
-
- val = ee->en_halo_red & 1;
- val |= ee->merge_meth << YEE_HALO_RED_EN_SHIFT;
- regw_ip(base_addr, val, YEE_TYP);
-
- regw_ip(base_addr, ee->hpf_shft, YEE_SHF);
- regw_ip(base_addr, ee->hpf_coef_00 & YEE_COEF_MASK, YEE_MUL_00);
- regw_ip(base_addr, ee->hpf_coef_01 & YEE_COEF_MASK, YEE_MUL_01);
- regw_ip(base_addr, ee->hpf_coef_02 & YEE_COEF_MASK, YEE_MUL_02);
- regw_ip(base_addr, ee->hpf_coef_10 & YEE_COEF_MASK, YEE_MUL_10);
- regw_ip(base_addr, ee->hpf_coef_11 & YEE_COEF_MASK, YEE_MUL_11);
- regw_ip(base_addr, ee->hpf_coef_12 & YEE_COEF_MASK, YEE_MUL_12);
- regw_ip(base_addr, ee->hpf_coef_20 & YEE_COEF_MASK, YEE_MUL_20);
- regw_ip(base_addr, ee->hpf_coef_21 & YEE_COEF_MASK, YEE_MUL_21);
- regw_ip(base_addr, ee->hpf_coef_22 & YEE_COEF_MASK, YEE_MUL_22);
- regw_ip(base_addr, ee->yee_thr & YEE_THR_MASK, YEE_THR);
- regw_ip(base_addr, ee->es_gain & YEE_ES_GAIN_MASK, YEE_E_GAN);
- regw_ip(base_addr, ee->es_thr1 & YEE_ES_THR1_MASK, YEE_E_THR1);
- regw_ip(base_addr, ee->es_thr2 & YEE_THR_MASK, YEE_E_THR2);
- regw_ip(base_addr, ee->es_gain_grad & YEE_THR_MASK, YEE_G_GAN);
- regw_ip(base_addr, ee->es_ofst_grad & YEE_THR_MASK, YEE_G_OFT);
-
- for (count = 0; count < VPFE_IPIPE_MAX_SIZE_YEE_LUT; count += 2)
- w_ip_table(isp5_base_addr, ((ee->table[count + 1] &
- YEE_ENTRY_MASK) << YEE_ENTRY_SHIFT) |
- (ee->table[count] & YEE_ENTRY_MASK),
- ((count/2) << 2) + YEE_TB_START_ADDR);
-}
-
-/* Chromatic Artifact Correction. CAR */
-static void ipipe_set_mf(void __iomem *base_addr)
-{
- /* typ to dynamic switch */
- regw_ip(base_addr, VPFE_IPIPE_CAR_DYN_SWITCH, CAR_TYP);
- /* Set SW0 to maximum */
- regw_ip(base_addr, CAR_MF_THR, CAR_SW);
-}
-
-static void
-ipipe_set_gain_ctrl(void __iomem *base_addr, struct vpfe_ipipe_car *car)
-{
- regw_ip(base_addr, VPFE_IPIPE_CAR_CHR_GAIN_CTRL, CAR_TYP);
- regw_ip(base_addr, car->hpf, CAR_HPF_TYP);
- regw_ip(base_addr, car->hpf_shft & CAR_HPF_SHIFT_MASK, CAR_HPF_SHF);
- regw_ip(base_addr, car->hpf_thr, CAR_HPF_THR);
- regw_ip(base_addr, car->gain1.gain, CAR_GN1_GAN);
- regw_ip(base_addr, car->gain1.shft & CAR_GAIN1_SHFT_MASK, CAR_GN1_SHF);
- regw_ip(base_addr, car->gain1.gain_min & CAR_GAIN_MIN_MASK,
- CAR_GN1_MIN);
- regw_ip(base_addr, car->gain2.gain, CAR_GN2_GAN);
- regw_ip(base_addr, car->gain2.shft & CAR_GAIN2_SHFT_MASK, CAR_GN2_SHF);
- regw_ip(base_addr, car->gain2.gain_min & CAR_GAIN_MIN_MASK,
- CAR_GN2_MIN);
-}
-
-void ipipe_set_car_regs(void __iomem *base_addr, struct vpfe_ipipe_car *car)
-{
- u32 val;
-
- ipipe_clock_enable(base_addr);
- regw_ip(base_addr, car->en, CAR_EN);
-
- if (!car->en)
- return;
-
- switch (car->meth) {
- case VPFE_IPIPE_CAR_MED_FLTR:
- ipipe_set_mf(base_addr);
- break;
-
- case VPFE_IPIPE_CAR_CHR_GAIN_CTRL:
- ipipe_set_gain_ctrl(base_addr, car);
- break;
-
- default:
- /* Dynamic switch between MF and Gain Ctrl. */
- ipipe_set_mf(base_addr);
- ipipe_set_gain_ctrl(base_addr, car);
- /* Set the threshold for switching between
- * the two Here we overwrite the MF SW0 value
- */
- regw_ip(base_addr, VPFE_IPIPE_CAR_DYN_SWITCH, CAR_TYP);
- val = car->sw1;
- val <<= CAR_SW1_SHIFT;
- val |= car->sw0;
- regw_ip(base_addr, val, CAR_SW);
- }
-}
-
-/* Chromatic Gain Suppression */
-void ipipe_set_cgs_regs(void __iomem *base_addr, struct vpfe_ipipe_cgs *cgs)
-{
- ipipe_clock_enable(base_addr);
- regw_ip(base_addr, cgs->en, CGS_EN);
-
- if (!cgs->en)
- return;
-
- /* Set the bright side parameters */
- regw_ip(base_addr, cgs->h_thr, CGS_GN1_H_THR);
- regw_ip(base_addr, cgs->h_slope, CGS_GN1_H_GAN);
- regw_ip(base_addr, cgs->h_shft & CAR_SHIFT_MASK, CGS_GN1_H_SHF);
- regw_ip(base_addr, cgs->h_min, CGS_GN1_H_MIN);
-}
-
-void rsz_src_enable(void __iomem *rsz_base, int enable)
-{
- regw_rsz(rsz_base, enable, RSZ_SRC_EN);
-}
-
-int rsz_enable(void __iomem *rsz_base, int rsz_id, int enable)
-{
- if (rsz_id == RSZ_A) {
- regw_rsz(rsz_base, enable, RSZ_EN_A);
- /* We always enable RSZ_A. RSZ_B is enable upon request from
- * application. So enable RSZ_SRC_EN along with RSZ_A
- */
- regw_rsz(rsz_base, enable, RSZ_SRC_EN);
- } else if (rsz_id == RSZ_B) {
- regw_rsz(rsz_base, enable, RSZ_EN_B);
- } else {
- BUG();
- }
-
- return 0;
-}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h
deleted file mode 100644
index 16b6a14b7058..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h
+++ /dev/null
@@ -1,556 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_DM365_IPIPE_HW_H
-#define _DAVINCI_VPFE_DM365_IPIPE_HW_H
-
-#include "vpfe_mc_capture.h"
-
-#define SET_LOW_ADDR 0x0000ffff
-#define SET_HIGH_ADDR 0xffff0000
-
-/* Below are the internal tables */
-#define DPC_TB0_START_ADDR 0x8000
-#define DPC_TB1_START_ADDR 0x8400
-
-#define GAMMA_R_START_ADDR 0xa800
-#define GAMMA_G_START_ADDR 0xb000
-#define GAMMA_B_START_ADDR 0xb800
-
-/* RAM table addresses for edge enhancement correction*/
-#define YEE_TB_START_ADDR 0x8800
-
-/* RAM table address for GBC LUT */
-#define GBCE_TB_START_ADDR 0x9000
-
-/* RAM table for 3D NF LUT */
-#define D3L_TB0_START_ADDR 0x9800
-#define D3L_TB1_START_ADDR 0x9c00
-#define D3L_TB2_START_ADDR 0xa000
-#define D3L_TB3_START_ADDR 0xa400
-
-/* IPIPE Register Offsets from the base address */
-#define IPIPE_SRC_EN 0x0000
-#define IPIPE_SRC_MODE 0x0004
-#define IPIPE_SRC_FMT 0x0008
-#define IPIPE_SRC_COL 0x000c
-#define IPIPE_SRC_VPS 0x0010
-#define IPIPE_SRC_VSZ 0x0014
-#define IPIPE_SRC_HPS 0x0018
-#define IPIPE_SRC_HSZ 0x001c
-
-#define IPIPE_SEL_SBU 0x0020
-
-#define IPIPE_DMA_STA 0x0024
-#define IPIPE_GCK_MMR 0x0028
-#define IPIPE_GCK_PIX 0x002c
-#define IPIPE_RESERVED0 0x0030
-
-/* Defect Correction */
-#define DPC_LUT_EN 0x0034
-#define DPC_LUT_SEL 0x0038
-#define DPC_LUT_ADR 0x003c
-#define DPC_LUT_SIZ 0x0040
-#define DPC_OTF_EN 0x0044
-#define DPC_OTF_TYP 0x0048
-#define DPC_OTF_2D_THR_R 0x004c
-#define DPC_OTF_2D_THR_GR 0x0050
-#define DPC_OTF_2D_THR_GB 0x0054
-#define DPC_OTF_2D_THR_B 0x0058
-#define DPC_OTF_2C_THR_R 0x005c
-#define DPC_OTF_2C_THR_GR 0x0060
-#define DPC_OTF_2C_THR_GB 0x0064
-#define DPC_OTF_2C_THR_B 0x0068
-#define DPC_OTF_3_SHF 0x006c
-#define DPC_OTF_3D_THR 0x0070
-#define DPC_OTF_3D_SLP 0x0074
-#define DPC_OTF_3D_MIN 0x0078
-#define DPC_OTF_3D_MAX 0x007c
-#define DPC_OTF_3C_THR 0x0080
-#define DPC_OTF_3C_SLP 0x0084
-#define DPC_OTF_3C_MIN 0x0088
-#define DPC_OTF_3C_MAX 0x008c
-
-/* Lense Shading Correction */
-#define LSC_VOFT 0x90
-#define LSC_VA2 0x94
-#define LSC_VA1 0x98
-#define LSC_VS 0x9c
-#define LSC_HOFT 0xa0
-#define LSC_HA2 0xa4
-#define LSC_HA1 0xa8
-#define LSC_HS 0xac
-#define LSC_GAIN_R 0xb0
-#define LSC_GAIN_GR 0xb4
-#define LSC_GAIN_GB 0xb8
-#define LSC_GAIN_B 0xbc
-#define LSC_OFT_R 0xc0
-#define LSC_OFT_GR 0xc4
-#define LSC_OFT_GB 0xc8
-#define LSC_OFT_B 0xcc
-#define LSC_SHF 0xd0
-#define LSC_MAX 0xd4
-
-/* Noise Filter 1. Ofsets from start address given */
-#define D2F_1ST 0xd8
-#define D2F_EN 0x0
-#define D2F_TYP 0x4
-#define D2F_THR 0x8
-#define D2F_STR 0x28
-#define D2F_SPR 0x48
-#define D2F_EDG_MIN 0x68
-#define D2F_EDG_MAX 0x6c
-
-/* Noise Filter 2 */
-#define D2F_2ND 0x148
-
-/* GIC */
-#define GIC_EN 0x1b8
-#define GIC_TYP 0x1bc
-#define GIC_GAN 0x1c0
-#define GIC_NFGAN 0x1c4
-#define GIC_THR 0x1c8
-#define GIC_SLP 0x1cc
-
-/* White Balance */
-#define WB2_OFT_R 0x1d0
-#define WB2_OFT_GR 0x1d4
-#define WB2_OFT_GB 0x1d8
-#define WB2_OFT_B 0x1dc
-#define WB2_WGN_R 0x1e0
-#define WB2_WGN_GR 0x1e4
-#define WB2_WGN_GB 0x1e8
-#define WB2_WGN_B 0x1ec
-
-/* CFA interpolation */
-#define CFA_MODE 0x1f0
-#define CFA_2DIR_HPF_THR 0x1f4
-#define CFA_2DIR_HPF_SLP 0x1f8
-#define CFA_2DIR_MIX_THR 0x1fc
-#define CFA_2DIR_MIX_SLP 0x200
-#define CFA_2DIR_DIR_THR 0x204
-#define CFA_2DIR_DIR_SLP 0x208
-#define CFA_2DIR_NDWT 0x20c
-#define CFA_MONO_HUE_FRA 0x210
-#define CFA_MONO_EDG_THR 0x214
-#define CFA_MONO_THR_MIN 0x218
-#define CFA_MONO_THR_SLP 0x21c
-#define CFA_MONO_SLP_MIN 0x220
-#define CFA_MONO_SLP_SLP 0x224
-#define CFA_MONO_LPWT 0x228
-
-/* RGB to RGB conversiona - 1st */
-#define RGB1_MUL_BASE 0x22c
-/* Offsets from base */
-#define RGB_MUL_RR 0x0
-#define RGB_MUL_GR 0x4
-#define RGB_MUL_BR 0x8
-#define RGB_MUL_RG 0xc
-#define RGB_MUL_GG 0x10
-#define RGB_MUL_BG 0x14
-#define RGB_MUL_RB 0x18
-#define RGB_MUL_GB 0x1c
-#define RGB_MUL_BB 0x20
-#define RGB_OFT_OR 0x24
-#define RGB_OFT_OG 0x28
-#define RGB_OFT_OB 0x2c
-
-/* Gamma */
-#define GMM_CFG 0x25c
-
-/* RGB to RGB conversiona - 2nd */
-#define RGB2_MUL_BASE 0x260
-
-/* 3D LUT */
-#define D3LUT_EN 0x290
-
-/* RGB to YUV(YCbCr) conversion */
-#define YUV_ADJ 0x294
-#define YUV_MUL_RY 0x298
-#define YUV_MUL_GY 0x29c
-#define YUV_MUL_BY 0x2a0
-#define YUV_MUL_RCB 0x2a4
-#define YUV_MUL_GCB 0x2a8
-#define YUV_MUL_BCB 0x2ac
-#define YUV_MUL_RCR 0x2b0
-#define YUV_MUL_GCR 0x2b4
-#define YUV_MUL_BCR 0x2b8
-#define YUV_OFT_Y 0x2bc
-#define YUV_OFT_CB 0x2c0
-#define YUV_OFT_CR 0x2c4
-#define YUV_PHS 0x2c8
-
-/* Global Brightness and Contrast */
-#define GBCE_EN 0x2cc
-#define GBCE_TYP 0x2d0
-
-/* Edge Enhancer */
-#define YEE_EN 0x2d4
-#define YEE_TYP 0x2d8
-#define YEE_SHF 0x2dc
-#define YEE_MUL_00 0x2e0
-#define YEE_MUL_01 0x2e4
-#define YEE_MUL_02 0x2e8
-#define YEE_MUL_10 0x2ec
-#define YEE_MUL_11 0x2f0
-#define YEE_MUL_12 0x2f4
-#define YEE_MUL_20 0x2f8
-#define YEE_MUL_21 0x2fc
-#define YEE_MUL_22 0x300
-#define YEE_THR 0x304
-#define YEE_E_GAN 0x308
-#define YEE_E_THR1 0x30c
-#define YEE_E_THR2 0x310
-#define YEE_G_GAN 0x314
-#define YEE_G_OFT 0x318
-
-/* Chroma Artifact Reduction */
-#define CAR_EN 0x31c
-#define CAR_TYP 0x320
-#define CAR_SW 0x324
-#define CAR_HPF_TYP 0x328
-#define CAR_HPF_SHF 0x32c
-#define CAR_HPF_THR 0x330
-#define CAR_GN1_GAN 0x334
-#define CAR_GN1_SHF 0x338
-#define CAR_GN1_MIN 0x33c
-#define CAR_GN2_GAN 0x340
-#define CAR_GN2_SHF 0x344
-#define CAR_GN2_MIN 0x348
-
-/* Chroma Gain Suppression */
-#define CGS_EN 0x34c
-#define CGS_GN1_L_THR 0x350
-#define CGS_GN1_L_GAN 0x354
-#define CGS_GN1_L_SHF 0x358
-#define CGS_GN1_L_MIN 0x35c
-#define CGS_GN1_H_THR 0x360
-#define CGS_GN1_H_GAN 0x364
-#define CGS_GN1_H_SHF 0x368
-#define CGS_GN1_H_MIN 0x36c
-#define CGS_GN2_L_THR 0x370
-#define CGS_GN2_L_GAN 0x374
-#define CGS_GN2_L_SHF 0x378
-#define CGS_GN2_L_MIN 0x37c
-
-/* Resizer */
-#define RSZ_SRC_EN 0x0
-#define RSZ_SRC_MODE 0x4
-#define RSZ_SRC_FMT0 0x8
-#define RSZ_SRC_FMT1 0xc
-#define RSZ_SRC_VPS 0x10
-#define RSZ_SRC_VSZ 0x14
-#define RSZ_SRC_HPS 0x18
-#define RSZ_SRC_HSZ 0x1c
-#define RSZ_DMA_RZA 0x20
-#define RSZ_DMA_RZB 0x24
-#define RSZ_DMA_STA 0x28
-#define RSZ_GCK_MMR 0x2c
-#define RSZ_RESERVED0 0x30
-#define RSZ_GCK_SDR 0x34
-#define RSZ_IRQ_RZA 0x38
-#define RSZ_IRQ_RZB 0x3c
-#define RSZ_YUV_Y_MIN 0x40
-#define RSZ_YUV_Y_MAX 0x44
-#define RSZ_YUV_C_MIN 0x48
-#define RSZ_YUV_C_MAX 0x4c
-#define RSZ_YUV_PHS 0x50
-#define RSZ_SEQ 0x54
-
-/* Resizer Rescale Parameters */
-#define RSZ_EN_A 0x58
-#define RSZ_EN_B 0xe8
-/*
- * offset of the registers to be added with base register of
- * either RSZ0 or RSZ1
- */
-#define RSZ_MODE 0x4
-#define RSZ_420 0x8
-#define RSZ_I_VPS 0xc
-#define RSZ_I_HPS 0x10
-#define RSZ_O_VSZ 0x14
-#define RSZ_O_HSZ 0x18
-#define RSZ_V_PHS_Y 0x1c
-#define RSZ_V_PHS_C 0x20
-#define RSZ_V_DIF 0x24
-#define RSZ_V_TYP 0x28
-#define RSZ_V_LPF 0x2c
-#define RSZ_H_PHS 0x30
-#define RSZ_H_PHS_ADJ 0x34
-#define RSZ_H_DIF 0x38
-#define RSZ_H_TYP 0x3c
-#define RSZ_H_LPF 0x40
-#define RSZ_DWN_EN 0x44
-#define RSZ_DWN_AV 0x48
-
-/* Resizer RGB Conversion Parameters */
-#define RSZ_RGB_EN 0x4c
-#define RSZ_RGB_TYP 0x50
-#define RSZ_RGB_BLD 0x54
-
-/* Resizer External Memory Parameters */
-#define RSZ_SDR_Y_BAD_H 0x58
-#define RSZ_SDR_Y_BAD_L 0x5c
-#define RSZ_SDR_Y_SAD_H 0x60
-#define RSZ_SDR_Y_SAD_L 0x64
-#define RSZ_SDR_Y_OFT 0x68
-#define RSZ_SDR_Y_PTR_S 0x6c
-#define RSZ_SDR_Y_PTR_E 0x70
-#define RSZ_SDR_C_BAD_H 0x74
-#define RSZ_SDR_C_BAD_L 0x78
-#define RSZ_SDR_C_SAD_H 0x7c
-#define RSZ_SDR_C_SAD_L 0x80
-#define RSZ_SDR_C_OFT 0x84
-#define RSZ_SDR_C_PTR_S 0x88
-#define RSZ_SDR_C_PTR_E 0x8c
-
-/* Macro for resizer */
-#define RSZ_YUV_Y_MIN 0x40
-#define RSZ_YUV_Y_MAX 0x44
-#define RSZ_YUV_C_MIN 0x48
-#define RSZ_YUV_C_MAX 0x4c
-
-#define IPIPE_GCK_MMR_DEFAULT 1
-#define IPIPE_GCK_PIX_DEFAULT 0xe
-#define RSZ_GCK_MMR_DEFAULT 1
-#define RSZ_GCK_SDR_DEFAULT 1
-
-/* LUTDPC */
-#define LUTDPC_TBL_256_EN 0
-#define LUTDPC_INF_TBL_EN 1
-#define LUT_DPC_START_ADDR 0
-#define LUT_DPC_H_POS_MASK 0x1fff
-#define LUT_DPC_V_POS_MASK 0x1fff
-#define LUT_DPC_V_POS_SHIFT 13
-#define LUT_DPC_CORR_METH_SHIFT 26
-#define LUT_DPC_MAX_SIZE 256
-#define LUT_DPC_SIZE_MASK 0x3ff
-
-/* OTFDPC */
-#define OTFDPC_DPC2_THR_MASK 0xfff
-#define OTF_DET_METHOD_SHIFT 1
-#define OTF_DPC3_0_SHF_MASK 3
-#define OTF_DPC3_0_THR_SHIFT 6
-#define OTF_DPC3_0_THR_MASK 0x3f
-#define OTF_DPC3_0_SLP_MASK 0x3f
-#define OTF_DPC3_0_DET_MASK 0xfff
-#define OTF_DPC3_0_CORR_MASK 0xfff
-
-/* NF (D2F) */
-#define D2F_SPR_VAL_MASK 0x1f
-#define D2F_SPR_VAL_SHIFT 0
-#define D2F_SHFT_VAL_MASK 3
-#define D2F_SHFT_VAL_SHIFT 5
-#define D2F_SAMPLE_METH_SHIFT 7
-#define D2F_APPLY_LSC_GAIN_SHIFT 8
-#define D2F_USE_SPR_REG_VAL 0
-#define D2F_STR_VAL_MASK 0x1f
-#define D2F_THR_VAL_MASK 0x3ff
-#define D2F_EDGE_DET_THR_MASK 0x7ff
-
-/* Green Imbalance Correction */
-#define GIC_TYP_SHIFT 0
-#define GIC_THR_SEL_SHIFT 1
-#define GIC_APPLY_LSC_GAIN_SHIFT 2
-#define GIC_GAIN_MASK 0xff
-#define GIC_THR_MASK 0xfff
-#define GIC_SLOPE_MASK 0xfff
-#define GIC_NFGAN_INT_MASK 7
-#define GIC_NFGAN_DECI_MASK 0x1f
-
-/* WB */
-#define WB_OFFSET_MASK 0xfff
-#define WB_GAIN_INT_MASK 0xf
-#define WB_GAIN_DECI_MASK 0x1ff
-
-/* CFA */
-#define CFA_HPF_THR_2DIR_MASK 0x1fff
-#define CFA_HPF_SLOPE_2DIR_MASK 0x3ff
-#define CFA_HPF_MIX_THR_2DIR_MASK 0x1fff
-#define CFA_HPF_MIX_SLP_2DIR_MASK 0x3ff
-#define CFA_DIR_THR_2DIR_MASK 0x3ff
-#define CFA_DIR_SLP_2DIR_MASK 0x7f
-#define CFA_ND_WT_2DIR_MASK 0x3f
-#define CFA_DAA_HUE_FRA_MASK 0x3f
-#define CFA_DAA_EDG_THR_MASK 0xff
-#define CFA_DAA_THR_MIN_MASK 0x3ff
-#define CFA_DAA_THR_SLP_MASK 0x3ff
-#define CFA_DAA_SLP_MIN_MASK 0x3ff
-#define CFA_DAA_SLP_SLP_MASK 0x3ff
-#define CFA_DAA_LP_WT_MASK 0x3f
-
-/* RGB2RGB */
-#define RGB2RGB_1_OFST_MASK 0x1fff
-#define RGB2RGB_1_GAIN_INT_MASK 0xf
-#define RGB2RGB_GAIN_DECI_MASK 0xff
-#define RGB2RGB_2_OFST_MASK 0x7ff
-#define RGB2RGB_2_GAIN_INT_MASK 0x7
-
-/* Gamma */
-#define GAMMA_BYPR_SHIFT 0
-#define GAMMA_BYPG_SHIFT 1
-#define GAMMA_BYPB_SHIFT 2
-#define GAMMA_TBL_SEL_SHIFT 4
-#define GAMMA_TBL_SIZE_SHIFT 5
-#define GAMMA_MASK 0x3ff
-#define GAMMA_SHIFT 10
-
-/* 3D LUT */
-#define D3_LUT_ENTRY_MASK 0x3ff
-#define D3_LUT_ENTRY_R_SHIFT 20
-#define D3_LUT_ENTRY_G_SHIFT 10
-#define D3_LUT_ENTRY_B_SHIFT 0
-
-/* Lumina adj */
-#define LUM_ADJ_CONTR_SHIFT 0
-#define LUM_ADJ_BRIGHT_SHIFT 8
-
-/* RGB2YCbCr */
-#define RGB2YCBCR_OFST_MASK 0x7ff
-#define RGB2YCBCR_COEF_INT_MASK 0xf
-#define RGB2YCBCR_COEF_DECI_MASK 0xff
-
-/* GBCE */
-#define GBCE_Y_VAL_MASK 0xff
-#define GBCE_GAIN_VAL_MASK 0x3ff
-#define GBCE_ENTRY_SHIFT 10
-
-/* Edge Enhancements */
-#define YEE_HALO_RED_EN_SHIFT 1
-#define YEE_HPF_SHIFT_MASK 0xf
-#define YEE_COEF_MASK 0x3ff
-#define YEE_THR_MASK 0x3f
-#define YEE_ES_GAIN_MASK 0xfff
-#define YEE_ES_THR1_MASK 0xfff
-#define YEE_ENTRY_SHIFT 9
-#define YEE_ENTRY_MASK 0x1ff
-
-/* CAR */
-#define CAR_MF_THR 0xff
-#define CAR_SW1_SHIFT 8
-#define CAR_GAIN1_SHFT_MASK 7
-#define CAR_GAIN_MIN_MASK 0x1ff
-#define CAR_GAIN2_SHFT_MASK 0xf
-#define CAR_HPF_SHIFT_MASK 3
-
-/* CGS */
-#define CAR_SHIFT_MASK 3
-
-/* Resizer */
-#define RSZ_BYPASS_SHIFT 1
-#define RSZ_SRC_IMG_FMT_SHIFT 1
-#define RSZ_SRC_Y_C_SEL_SHIFT 2
-#define IPIPE_RSZ_VPS_MASK 0xffff
-#define IPIPE_RSZ_HPS_MASK 0xffff
-#define IPIPE_RSZ_VSZ_MASK 0x1fff
-#define IPIPE_RSZ_HSZ_MASK 0x1fff
-#define RSZ_HPS_MASK 0x1fff
-#define RSZ_VPS_MASK 0x1fff
-#define RSZ_O_HSZ_MASK 0x1fff
-#define RSZ_O_VSZ_MASK 0x1fff
-#define RSZ_V_PHS_MASK 0x3fff
-#define RSZ_V_DIF_MASK 0x3fff
-
-#define RSZA_H_FLIP_SHIFT 0
-#define RSZA_V_FLIP_SHIFT 1
-#define RSZB_H_FLIP_SHIFT 2
-#define RSZB_V_FLIP_SHIFT 3
-#define RSZ_A 0
-#define RSZ_B 1
-#define RSZ_CEN_SHIFT 1
-#define RSZ_YEN_SHIFT 0
-#define RSZ_TYP_Y_SHIFT 0
-#define RSZ_TYP_C_SHIFT 1
-#define RSZ_LPF_INT_MASK 0x3f
-#define RSZ_LPF_INT_C_SHIFT 6
-#define RSZ_H_PHS_MASK 0x3fff
-#define RSZ_H_DIF_MASK 0x3fff
-#define RSZ_DIFF_DOWN_THR 256
-#define RSZ_DWN_SCALE_AV_SZ_V_SHIFT 3
-#define RSZ_DWN_SCALE_AV_SZ_MASK 7
-#define RSZ_RGB_MSK1_SHIFT 2
-#define RSZ_RGB_MSK0_SHIFT 1
-#define RSZ_RGB_TYP_SHIFT 0
-#define RSZ_RGB_ALPHA_MASK 0xff
-
-static inline u32 regr_ip(void __iomem *addr, u32 offset)
-{
- return readl(addr + offset);
-}
-
-static inline void regw_ip(void __iomem *addr, u32 val, u32 offset)
-{
- writel(val, addr + offset);
-}
-
-static inline u32 w_ip_table(void __iomem *addr, u32 val, u32 offset)
-{
- writel(val, addr + offset);
-
- return val;
-}
-
-static inline u32 regr_rsz(void __iomem *addr, u32 offset)
-{
- return readl(addr + offset);
-}
-
-static inline u32 regw_rsz(void __iomem *addr, u32 val, u32 offset)
-{
- writel(val, addr + offset);
-
- return val;
-}
-
-int config_ipipe_hw(struct vpfe_ipipe_device *ipipe);
-int resizer_set_outaddr(void __iomem *rsz_base, struct resizer_params *params,
- int resize_no, unsigned int address);
-int rsz_enable(void __iomem *rsz_base, int rsz_id, int enable);
-void rsz_src_enable(void __iomem *rsz_base, int enable);
-void rsz_set_in_pix_format(unsigned char y_c);
-int config_rsz_hw(struct vpfe_resizer_device *resizer,
- struct resizer_params *config);
-void ipipe_set_d2f_regs(void __iomem *base_addr, unsigned int id,
- struct vpfe_ipipe_nf *noise_filter);
-void ipipe_set_rgb2rgb_regs(void __iomem *base_addr, unsigned int id,
- struct vpfe_ipipe_rgb2rgb *rgb);
-void ipipe_set_yuv422_conv_regs(void __iomem *base_addr,
- struct vpfe_ipipe_yuv422_conv *conv);
-void ipipe_set_lum_adj_regs(void __iomem *base_addr,
- struct ipipe_lum_adj *lum_adj);
-void ipipe_set_rgb2ycbcr_regs(void __iomem *base_addr,
- struct vpfe_ipipe_rgb2yuv *yuv);
-void ipipe_set_lutdpc_regs(void __iomem *base_addr,
- void __iomem *isp5_base_addr, struct vpfe_ipipe_lutdpc *lutdpc);
-void ipipe_set_otfdpc_regs(void __iomem *base_addr,
- struct vpfe_ipipe_otfdpc *otfdpc);
-void ipipe_set_3d_lut_regs(void __iomem *base_addr,
- void __iomem *isp5_base_addr, struct vpfe_ipipe_3d_lut *lut_3d);
-void ipipe_set_gamma_regs(void __iomem *base_addr,
- void __iomem *isp5_base_addr, struct vpfe_ipipe_gamma *gamma);
-void ipipe_set_ee_regs(void __iomem *base_addr,
- void __iomem *isp5_base_addr, struct vpfe_ipipe_yee *ee);
-void ipipe_set_gbce_regs(void __iomem *base_addr,
- void __iomem *isp5_base_addr, struct vpfe_ipipe_gbce *gbce);
-void ipipe_set_gic_regs(void __iomem *base_addr, struct vpfe_ipipe_gic *gic);
-void ipipe_set_cfa_regs(void __iomem *base_addr, struct vpfe_ipipe_cfa *cfa);
-void ipipe_set_car_regs(void __iomem *base_addr, struct vpfe_ipipe_car *car);
-void ipipe_set_cgs_regs(void __iomem *base_addr, struct vpfe_ipipe_cgs *cgs);
-void ipipe_set_wb_regs(void __iomem *base_addr, struct vpfe_ipipe_wb *wb);
-
-#endif /* _DAVINCI_VPFE_DM365_IPIPE_HW_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c
deleted file mode 100644
index 51d4cd1bdb97..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c
+++ /dev/null
@@ -1,1070 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#include "dm365_ipipeif.h"
-#include "vpfe_mc_capture.h"
-
-static const unsigned int ipipeif_input_fmts[] = {
- MEDIA_BUS_FMT_UYVY8_2X8,
- MEDIA_BUS_FMT_SGRBG12_1X12,
- MEDIA_BUS_FMT_Y8_1X8,
- MEDIA_BUS_FMT_UV8_1X8,
- MEDIA_BUS_FMT_YDYUYDYV8_1X16,
- MEDIA_BUS_FMT_SBGGR8_1X8,
-};
-
-static const unsigned int ipipeif_output_fmts[] = {
- MEDIA_BUS_FMT_UYVY8_2X8,
- MEDIA_BUS_FMT_SGRBG12_1X12,
- MEDIA_BUS_FMT_Y8_1X8,
- MEDIA_BUS_FMT_UV8_1X8,
- MEDIA_BUS_FMT_YDYUYDYV8_1X16,
- MEDIA_BUS_FMT_SBGGR8_1X8,
- MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
- MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8,
-};
-
-static int
-ipipeif_get_pack_mode(u32 in_pix_fmt)
-{
- switch (in_pix_fmt) {
- case MEDIA_BUS_FMT_SBGGR8_1X8:
- case MEDIA_BUS_FMT_Y8_1X8:
- case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8:
- case MEDIA_BUS_FMT_UV8_1X8:
- return IPIPEIF_5_1_PACK_8_BIT;
-
- case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8:
- return IPIPEIF_5_1_PACK_8_BIT_A_LAW;
-
- case MEDIA_BUS_FMT_SGRBG12_1X12:
- return IPIPEIF_5_1_PACK_16_BIT;
-
- case MEDIA_BUS_FMT_SBGGR12_1X12:
- return IPIPEIF_5_1_PACK_12_BIT;
-
- default:
- return IPIPEIF_5_1_PACK_16_BIT;
- }
-}
-
-static inline u32 ipipeif_read(void __iomem *addr, u32 offset)
-{
- return readl(addr + offset);
-}
-
-static inline void ipipeif_write(u32 val, void __iomem *addr, u32 offset)
-{
- writel(val, addr + offset);
-}
-
-static void ipipeif_config_dpc(void __iomem *addr, struct ipipeif_dpc *dpc)
-{
- u32 val = 0;
-
- if (dpc->en) {
- val = (dpc->en & 1) << IPIPEIF_DPC2_EN_SHIFT;
- val |= dpc->thr & IPIPEIF_DPC2_THR_MASK;
- }
- ipipeif_write(val, addr, IPIPEIF_DPC2);
-}
-
-#define IPIPEIF_MODE_CONTINUOUS 0
-#define IPIPEIF_MODE_ONE_SHOT 1
-
-static int get_oneshot_mode(enum ipipeif_input_entity input)
-{
- if (input == IPIPEIF_INPUT_MEMORY)
- return IPIPEIF_MODE_ONE_SHOT;
- if (input == IPIPEIF_INPUT_ISIF)
- return IPIPEIF_MODE_CONTINUOUS;
-
- return -EINVAL;
-}
-
-static int
-ipipeif_get_cfg_src1(struct vpfe_ipipeif_device *ipipeif)
-{
- struct v4l2_mbus_framefmt *informat;
-
- informat = &ipipeif->formats[IPIPEIF_PAD_SINK];
- if (ipipeif->input == IPIPEIF_INPUT_MEMORY &&
- (informat->code == MEDIA_BUS_FMT_Y8_1X8 ||
- informat->code == MEDIA_BUS_FMT_UV8_1X8))
- return IPIPEIF_CCDC;
-
- return IPIPEIF_SRC1_PARALLEL_PORT;
-}
-
-static int
-ipipeif_get_data_shift(struct vpfe_ipipeif_device *ipipeif)
-{
- struct v4l2_mbus_framefmt *informat;
-
- informat = &ipipeif->formats[IPIPEIF_PAD_SINK];
-
- switch (informat->code) {
- case MEDIA_BUS_FMT_SGRBG12_1X12:
- return IPIPEIF_5_1_BITS11_0;
-
- case MEDIA_BUS_FMT_Y8_1X8:
- case MEDIA_BUS_FMT_UV8_1X8:
- return IPIPEIF_5_1_BITS11_0;
-
- default:
- return IPIPEIF_5_1_BITS7_0;
- }
-}
-
-static enum ipipeif_input_source
-ipipeif_get_source(struct vpfe_ipipeif_device *ipipeif)
-{
- struct v4l2_mbus_framefmt *informat;
-
- informat = &ipipeif->formats[IPIPEIF_PAD_SINK];
- if (ipipeif->input == IPIPEIF_INPUT_ISIF)
- return IPIPEIF_CCDC;
-
- if (informat->code == MEDIA_BUS_FMT_UYVY8_2X8)
- return IPIPEIF_SDRAM_YUV;
-
- return IPIPEIF_SDRAM_RAW;
-}
-
-void vpfe_ipipeif_ss_buffer_isr(struct vpfe_ipipeif_device *ipipeif)
-{
- struct vpfe_video_device *video_in = &ipipeif->video_in;
-
- if (ipipeif->input != IPIPEIF_INPUT_MEMORY)
- return;
-
- spin_lock(&video_in->dma_queue_lock);
- vpfe_video_process_buffer_complete(video_in);
- video_in->state = VPFE_VIDEO_BUFFER_NOT_QUEUED;
- vpfe_video_schedule_next_buffer(video_in);
- spin_unlock(&video_in->dma_queue_lock);
-}
-
-int vpfe_ipipeif_decimation_enabled(struct vpfe_device *vpfe_dev)
-{
- struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
-
- return ipipeif->config.decimation;
-}
-
-int vpfe_ipipeif_get_rsz(struct vpfe_device *vpfe_dev)
-{
- struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
-
- return ipipeif->config.rsz;
-}
-
-#define RD_DATA_15_2 0x7
-
-/*
- * ipipeif_hw_setup() - This function sets up IPIPEIF
- * @sd: pointer to v4l2 subdev structure
- * return -EINVAL or zero on success
- */
-static int ipipeif_hw_setup(struct v4l2_subdev *sd)
-{
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt *informat, *outformat;
- struct ipipeif_params params = ipipeif->config;
- enum ipipeif_input_source ipipeif_source;
- u32 isif_port_if;
- void __iomem *ipipeif_base_addr;
- unsigned long val;
- int data_shift;
- int pack_mode;
- int source1;
- int tmp;
-
- ipipeif_base_addr = ipipeif->ipipeif_base_addr;
-
- /* Enable clock to IPIPEIF and IPIPE */
- vpss_enable_clock(VPSS_IPIPEIF_CLOCK, 1);
-
- informat = &ipipeif->formats[IPIPEIF_PAD_SINK];
- outformat = &ipipeif->formats[IPIPEIF_PAD_SOURCE];
-
- /* Combine all the fields to make CFG1 register of IPIPEIF */
- tmp = val = get_oneshot_mode(ipipeif->input);
- if (tmp < 0) {
- dev_err(&sd->devnode->dev, "ipipeif: links setup required");
- return -EINVAL;
- }
- val <<= ONESHOT_SHIFT;
-
- ipipeif_source = ipipeif_get_source(ipipeif);
- val |= ipipeif_source << INPSRC_SHIFT;
-
- val |= params.clock_select << CLKSEL_SHIFT;
- val |= params.avg_filter << AVGFILT_SHIFT;
- val |= params.decimation << DECIM_SHIFT;
-
- pack_mode = ipipeif_get_pack_mode(informat->code);
- val |= pack_mode << PACK8IN_SHIFT;
-
- source1 = ipipeif_get_cfg_src1(ipipeif);
- val |= source1 << INPSRC1_SHIFT;
-
- data_shift = ipipeif_get_data_shift(ipipeif);
- if (ipipeif_source != IPIPEIF_SDRAM_YUV)
- val |= data_shift << DATASFT_SHIFT;
- else
- val &= ~(RD_DATA_15_2 << DATASFT_SHIFT);
-
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG1);
-
- switch (ipipeif_source) {
- case IPIPEIF_CCDC:
- ipipeif_write(ipipeif->gain, ipipeif_base_addr, IPIPEIF_GAIN);
- break;
-
- case IPIPEIF_SDRAM_RAW:
- case IPIPEIF_CCDC_DARKFM:
- ipipeif_write(ipipeif->gain, ipipeif_base_addr, IPIPEIF_GAIN);
- /* fall through */
- case IPIPEIF_SDRAM_YUV:
- val |= data_shift << DATASFT_SHIFT;
- ipipeif_write(params.ppln, ipipeif_base_addr, IPIPEIF_PPLN);
- ipipeif_write(params.lpfr, ipipeif_base_addr, IPIPEIF_LPFR);
- ipipeif_write(informat->width, ipipeif_base_addr, IPIPEIF_HNUM);
- ipipeif_write(informat->height,
- ipipeif_base_addr, IPIPEIF_VNUM);
- break;
-
- default:
- return -EINVAL;
- }
-
- /*check if decimation is enable or not */
- if (params.decimation)
- ipipeif_write(params.rsz, ipipeif_base_addr, IPIPEIF_RSZ);
-
- /* Setup sync alignment and initial rsz position */
- val = params.if_5_1.align_sync & 1;
- val <<= IPIPEIF_INIRSZ_ALNSYNC_SHIFT;
- val |= params.if_5_1.rsz_start & IPIPEIF_INIRSZ_MASK;
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_INIRSZ);
- isif_port_if = informat->code;
-
- if (isif_port_if == MEDIA_BUS_FMT_Y8_1X8)
- isif_port_if = MEDIA_BUS_FMT_YUYV8_1X16;
- else if (isif_port_if == MEDIA_BUS_FMT_UV8_1X8)
- isif_port_if = MEDIA_BUS_FMT_SGRBG12_1X12;
-
- /* Enable DPCM decompression */
- switch (ipipeif_source) {
- case IPIPEIF_SDRAM_RAW:
- val = 0;
- if (outformat->code == MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8) {
- val = 1;
- val |= (IPIPEIF_DPCM_8BIT_10BIT & 1) <<
- IPIPEIF_DPCM_BITS_SHIFT;
- val |= (ipipeif->dpcm_predictor & 1) <<
- IPIPEIF_DPCM_PRED_SHIFT;
- }
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DPCM);
-
- /* set DPC */
- ipipeif_config_dpc(ipipeif_base_addr, &params.if_5_1.dpc);
-
- ipipeif_write(params.if_5_1.clip,
- ipipeif_base_addr, IPIPEIF_OCLIP);
-
- /* fall through for SDRAM YUV mode */
- /* configure CFG2 */
- val = ipipeif_read(ipipeif_base_addr, IPIPEIF_CFG2);
- switch (isif_port_if) {
- case MEDIA_BUS_FMT_YUYV8_1X16:
- case MEDIA_BUS_FMT_UYVY8_2X8:
- case MEDIA_BUS_FMT_Y8_1X8:
- clear_bit(IPIPEIF_CFG2_YUV8_SHIFT, &val);
- set_bit(IPIPEIF_CFG2_YUV16_SHIFT, &val);
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2);
- break;
-
- default:
- clear_bit(IPIPEIF_CFG2_YUV8_SHIFT, &val);
- clear_bit(IPIPEIF_CFG2_YUV16_SHIFT, &val);
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2);
- break;
- }
- /* fall through */
-
- case IPIPEIF_SDRAM_YUV:
- /* Set clock divider */
- if (params.clock_select == IPIPEIF_SDRAM_CLK) {
- val = ipipeif_read(ipipeif_base_addr, IPIPEIF_CLKDIV);
- val |= (params.if_5_1.clk_div.m - 1) <<
- IPIPEIF_CLKDIV_M_SHIFT;
- val |= (params.if_5_1.clk_div.n - 1);
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CLKDIV);
- }
- break;
-
- case IPIPEIF_CCDC:
- case IPIPEIF_CCDC_DARKFM:
- /* set DPC */
- ipipeif_config_dpc(ipipeif_base_addr, &params.if_5_1.dpc);
-
- /* Set DF gain & threshold control */
- val = 0;
- if (params.if_5_1.df_gain_en) {
- val = params.if_5_1.df_gain_thr &
- IPIPEIF_DF_GAIN_THR_MASK;
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DFSGTH);
- val = (params.if_5_1.df_gain_en & 1) <<
- IPIPEIF_DF_GAIN_EN_SHIFT;
- val |= params.if_5_1.df_gain &
- IPIPEIF_DF_GAIN_MASK;
- }
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DFSGVL);
- /* configure CFG2 */
- val = VPFE_PINPOL_POSITIVE << IPIPEIF_CFG2_HDPOL_SHIFT;
- val |= VPFE_PINPOL_POSITIVE << IPIPEIF_CFG2_VDPOL_SHIFT;
-
- switch (isif_port_if) {
- case MEDIA_BUS_FMT_YUYV8_1X16:
- case MEDIA_BUS_FMT_YUYV10_1X20:
- clear_bit(IPIPEIF_CFG2_YUV8_SHIFT, &val);
- set_bit(IPIPEIF_CFG2_YUV16_SHIFT, &val);
- break;
-
- case MEDIA_BUS_FMT_YUYV8_2X8:
- case MEDIA_BUS_FMT_UYVY8_2X8:
- case MEDIA_BUS_FMT_Y8_1X8:
- case MEDIA_BUS_FMT_YUYV10_2X10:
- set_bit(IPIPEIF_CFG2_YUV8_SHIFT, &val);
- set_bit(IPIPEIF_CFG2_YUV16_SHIFT, &val);
- val |= IPIPEIF_CBCR_Y << IPIPEIF_CFG2_YUV8P_SHIFT;
- break;
-
- default:
- /* Bayer */
- ipipeif_write(params.if_5_1.clip, ipipeif_base_addr,
- IPIPEIF_OCLIP);
- }
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2);
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int
-ipipeif_set_config(struct v4l2_subdev *sd, struct ipipeif_params *config)
-{
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
- struct device *dev = ipipeif->subdev.v4l2_dev->dev;
-
- if (!config) {
- dev_err(dev, "Invalid configuration pointer\n");
- return -EINVAL;
- }
-
- ipipeif->config.clock_select = config->clock_select;
- ipipeif->config.ppln = config->ppln;
- ipipeif->config.lpfr = config->lpfr;
- ipipeif->config.rsz = config->rsz;
- ipipeif->config.decimation = config->decimation;
- if (ipipeif->config.decimation &&
- (ipipeif->config.rsz < IPIPEIF_RSZ_MIN ||
- ipipeif->config.rsz > IPIPEIF_RSZ_MAX)) {
- dev_err(dev, "rsz range is %d to %d\n",
- IPIPEIF_RSZ_MIN, IPIPEIF_RSZ_MAX);
- return -EINVAL;
- }
-
- ipipeif->config.avg_filter = config->avg_filter;
-
- ipipeif->config.if_5_1.df_gain_thr = config->if_5_1.df_gain_thr;
- ipipeif->config.if_5_1.df_gain = config->if_5_1.df_gain;
- ipipeif->config.if_5_1.df_gain_en = config->if_5_1.df_gain_en;
-
- ipipeif->config.if_5_1.rsz_start = config->if_5_1.rsz_start;
- ipipeif->config.if_5_1.align_sync = config->if_5_1.align_sync;
- ipipeif->config.if_5_1.clip = config->if_5_1.clip;
-
- ipipeif->config.if_5_1.dpc.en = config->if_5_1.dpc.en;
- ipipeif->config.if_5_1.dpc.thr = config->if_5_1.dpc.thr;
-
- ipipeif->config.if_5_1.clk_div.m = config->if_5_1.clk_div.m;
- ipipeif->config.if_5_1.clk_div.n = config->if_5_1.clk_div.n;
-
- return 0;
-}
-
-static int
-ipipeif_get_config(struct v4l2_subdev *sd, void *arg)
-{
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
- struct ipipeif_params *config = arg;
- struct device *dev = ipipeif->subdev.v4l2_dev->dev;
-
- if (!arg) {
- dev_err(dev, "Invalid configuration pointer\n");
- return -EINVAL;
- }
-
- config->clock_select = ipipeif->config.clock_select;
- config->ppln = ipipeif->config.ppln;
- config->lpfr = ipipeif->config.lpfr;
- config->rsz = ipipeif->config.rsz;
- config->decimation = ipipeif->config.decimation;
- config->avg_filter = ipipeif->config.avg_filter;
-
- config->if_5_1.df_gain_thr = ipipeif->config.if_5_1.df_gain_thr;
- config->if_5_1.df_gain = ipipeif->config.if_5_1.df_gain;
- config->if_5_1.df_gain_en = ipipeif->config.if_5_1.df_gain_en;
-
- config->if_5_1.rsz_start = ipipeif->config.if_5_1.rsz_start;
- config->if_5_1.align_sync = ipipeif->config.if_5_1.align_sync;
- config->if_5_1.clip = ipipeif->config.if_5_1.clip;
-
- config->if_5_1.dpc.en = ipipeif->config.if_5_1.dpc.en;
- config->if_5_1.dpc.thr = ipipeif->config.if_5_1.dpc.thr;
-
- config->if_5_1.clk_div.m = ipipeif->config.if_5_1.clk_div.m;
- config->if_5_1.clk_div.n = ipipeif->config.if_5_1.clk_div.n;
-
- return 0;
-}
-
-/*
- * ipipeif_ioctl() - Handle ipipeif module private ioctl's
- * @sd: pointer to v4l2 subdev structure
- * @cmd: configuration command
- * @arg: configuration argument
- */
-static long ipipeif_ioctl(struct v4l2_subdev *sd,
- unsigned int cmd, void *arg)
-{
- struct ipipeif_params *config = arg;
- int ret = -ENOIOCTLCMD;
-
- switch (cmd) {
- case VIDIOC_VPFE_IPIPEIF_S_CONFIG:
- ret = ipipeif_set_config(sd, config);
- break;
-
- case VIDIOC_VPFE_IPIPEIF_G_CONFIG:
- ret = ipipeif_get_config(sd, arg);
- break;
- }
- return ret;
-}
-
-/*
- * ipipeif_s_ctrl() - Handle set control subdev method
- * @ctrl: pointer to v4l2 control structure
- */
-static int ipipeif_s_ctrl(struct v4l2_ctrl *ctrl)
-{
- struct vpfe_ipipeif_device *ipipeif =
- container_of(ctrl->handler, struct vpfe_ipipeif_device, ctrls);
-
- switch (ctrl->id) {
- case VPFE_CID_DPCM_PREDICTOR:
- ipipeif->dpcm_predictor = ctrl->val;
- break;
-
- case V4L2_CID_GAIN:
- ipipeif->gain = ctrl->val;
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-#define ENABLE_IPIPEIF 0x1
-
-void vpfe_ipipeif_enable(struct vpfe_device *vpfe_dev)
-{
- struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
- void __iomem *ipipeif_base_addr = ipipeif->ipipeif_base_addr;
- unsigned char val;
-
- if (ipipeif->input != IPIPEIF_INPUT_MEMORY)
- return;
-
- do {
- val = ipipeif_read(ipipeif_base_addr, IPIPEIF_ENABLE);
- } while (val & 0x1);
-
- ipipeif_write(ENABLE_IPIPEIF, ipipeif_base_addr, IPIPEIF_ENABLE);
-}
-
-/*
- * ipipeif_set_stream() - Enable/Disable streaming on ipipeif subdev
- * @sd: pointer to v4l2 subdev structure
- * @enable: 1 == Enable, 0 == Disable
- */
-static int ipipeif_set_stream(struct v4l2_subdev *sd, int enable)
-{
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
- struct vpfe_device *vpfe_dev = to_vpfe_device(ipipeif);
- int ret = 0;
-
- if (!enable)
- return ret;
-
- ret = ipipeif_hw_setup(sd);
- if (!ret)
- vpfe_ipipeif_enable(vpfe_dev);
-
- return ret;
-}
-
-/*
- * ipipeif_enum_mbus_code() - Handle pixel format enumeration
- * @sd: pointer to v4l2 subdev structure
- * @cfg: V4L2 subdev pad config
- * @code: pointer to v4l2_subdev_mbus_code_enum structure
- * return -EINVAL or zero on success
- */
-static int ipipeif_enum_mbus_code(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_mbus_code_enum *code)
-{
- switch (code->pad) {
- case IPIPEIF_PAD_SINK:
- if (code->index >= ARRAY_SIZE(ipipeif_input_fmts))
- return -EINVAL;
-
- code->code = ipipeif_input_fmts[code->index];
- break;
-
- case IPIPEIF_PAD_SOURCE:
- if (code->index >= ARRAY_SIZE(ipipeif_output_fmts))
- return -EINVAL;
-
- code->code = ipipeif_output_fmts[code->index];
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-/*
- * ipipeif_get_format() - Handle get format by pads subdev method
- * @sd: pointer to v4l2 subdev structure
- * @cfg: V4L2 subdev pad config
- * @fmt: pointer to v4l2 subdev format structure
- */
-static int
-ipipeif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
-
- if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
- fmt->format = ipipeif->formats[fmt->pad];
- else
- fmt->format = *(v4l2_subdev_get_try_format(sd, cfg, fmt->pad));
-
- return 0;
-}
-
-#define MIN_OUT_WIDTH 32
-#define MIN_OUT_HEIGHT 32
-
-/*
- * ipipeif_try_format() - Handle try format by pad subdev method
- * @ipipeif: VPFE ipipeif device.
- * @cfg: V4L2 subdev pad config
- * @pad: pad num.
- * @fmt: pointer to v4l2 format structure.
- * @which : wanted subdev format
- */
-static void
-ipipeif_try_format(struct vpfe_ipipeif_device *ipipeif,
- struct v4l2_subdev_pad_config *cfg, unsigned int pad,
- struct v4l2_mbus_framefmt *fmt,
- enum v4l2_subdev_format_whence which)
-{
- unsigned int max_out_height;
- unsigned int max_out_width;
- unsigned int i;
-
- max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A;
- max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A;
-
- if (pad == IPIPEIF_PAD_SINK) {
- for (i = 0; i < ARRAY_SIZE(ipipeif_input_fmts); i++)
- if (fmt->code == ipipeif_input_fmts[i])
- break;
-
- /* If not found, use SBGGR10 as default */
- if (i >= ARRAY_SIZE(ipipeif_input_fmts))
- fmt->code = MEDIA_BUS_FMT_SGRBG12_1X12;
- } else if (pad == IPIPEIF_PAD_SOURCE) {
- for (i = 0; i < ARRAY_SIZE(ipipeif_output_fmts); i++)
- if (fmt->code == ipipeif_output_fmts[i])
- break;
-
- /* If not found, use UYVY as default */
- if (i >= ARRAY_SIZE(ipipeif_output_fmts))
- fmt->code = MEDIA_BUS_FMT_UYVY8_2X8;
- }
-
- fmt->width = clamp_t(u32, fmt->width, MIN_OUT_HEIGHT, max_out_width);
- fmt->height = clamp_t(u32, fmt->height, MIN_OUT_WIDTH, max_out_height);
-}
-
-static int
-ipipeif_enum_frame_size(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_frame_size_enum *fse)
-{
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt format;
-
- if (fse->index != 0)
- return -EINVAL;
-
- format.code = fse->code;
- format.width = 1;
- format.height = 1;
- ipipeif_try_format(ipipeif, cfg, fse->pad, &format, fse->which);
- fse->min_width = format.width;
- fse->min_height = format.height;
-
- if (format.code != fse->code)
- return -EINVAL;
-
- format.code = fse->code;
- format.width = -1;
- format.height = -1;
- ipipeif_try_format(ipipeif, cfg, fse->pad, &format, fse->which);
- fse->max_width = format.width;
- fse->max_height = format.height;
-
- return 0;
-}
-
-/*
- * __ipipeif_get_format() - helper function for getting ipipeif format
- * @ipipeif: pointer to ipipeif private structure.
- * @cfg: V4L2 subdev pad config
- * @pad: pad number.
- * @which: wanted subdev format.
- *
- */
-static struct v4l2_mbus_framefmt *
-__ipipeif_get_format(struct vpfe_ipipeif_device *ipipeif,
- struct v4l2_subdev_pad_config *cfg, unsigned int pad,
- enum v4l2_subdev_format_whence which)
-{
- if (which == V4L2_SUBDEV_FORMAT_TRY)
- return v4l2_subdev_get_try_format(&ipipeif->subdev, cfg, pad);
-
- return &ipipeif->formats[pad];
-}
-
-/*
- * ipipeif_set_format() - Handle set format by pads subdev method
- * @sd: pointer to v4l2 subdev structure
- * @cfg: V4L2 subdev pad config
- * @fmt: pointer to v4l2 subdev format structure
- * return -EINVAL or zero on success
- */
-static int
-ipipeif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt *format;
-
- format = __ipipeif_get_format(ipipeif, cfg, fmt->pad, fmt->which);
- if (!format)
- return -EINVAL;
-
- ipipeif_try_format(ipipeif, cfg, fmt->pad, &fmt->format, fmt->which);
- *format = fmt->format;
-
- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
- return 0;
-
- if (fmt->pad == IPIPEIF_PAD_SINK &&
- ipipeif->input != IPIPEIF_INPUT_NONE)
- ipipeif->formats[fmt->pad] = fmt->format;
- else if (fmt->pad == IPIPEIF_PAD_SOURCE &&
- ipipeif->output != IPIPEIF_OUTPUT_NONE)
- ipipeif->formats[fmt->pad] = fmt->format;
- else
- return -EINVAL;
-
- return 0;
-}
-
-static void ipipeif_set_default_config(struct vpfe_ipipeif_device *ipipeif)
-{
-#define WIDTH_I 640
-#define HEIGHT_I 480
-
- const struct ipipeif_params ipipeif_defaults = {
- .clock_select = IPIPEIF_SDRAM_CLK,
- .ppln = WIDTH_I + 8,
- .lpfr = HEIGHT_I + 10,
- .rsz = 16, /* resize ratio 16/rsz */
- .decimation = IPIPEIF_DECIMATION_OFF,
- .avg_filter = IPIPEIF_AVG_OFF,
- .if_5_1 = {
- .clk_div = {
- .m = 1, /* clock = sdram clock * (m/n) */
- .n = 6
- },
- .clip = 4095,
- },
- };
- memcpy(&ipipeif->config, &ipipeif_defaults,
- sizeof(struct ipipeif_params));
-}
-
-/*
- * ipipeif_init_formats() - Initialize formats on all pads
- * @sd: VPFE ipipeif V4L2 subdevice
- * @fh: V4L2 subdev file handle
- *
- * Initialize all pad formats with default values. Try formats are initialized
- * on the file handle.
- */
-static int
-ipipeif_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
-{
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
- struct v4l2_subdev_format format;
-
- memset(&format, 0, sizeof(format));
- format.pad = IPIPEIF_PAD_SINK;
- format.which = V4L2_SUBDEV_FORMAT_TRY;
- format.format.code = MEDIA_BUS_FMT_SGRBG12_1X12;
- format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
- format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
- ipipeif_set_format(sd, fh->pad, &format);
-
- memset(&format, 0, sizeof(format));
- format.pad = IPIPEIF_PAD_SOURCE;
- format.which = V4L2_SUBDEV_FORMAT_TRY;
- format.format.code = MEDIA_BUS_FMT_UYVY8_2X8;
- format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
- format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
- ipipeif_set_format(sd, fh->pad, &format);
-
- ipipeif_set_default_config(ipipeif);
-
- return 0;
-}
-
-/*
- * ipipeif_video_in_queue() - ipipeif video in queue
- * @vpfe_dev: vpfe device pointer
- * @addr: buffer address
- */
-static int
-ipipeif_video_in_queue(struct vpfe_device *vpfe_dev, unsigned long addr)
-{
- struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
- void __iomem *ipipeif_base_addr = ipipeif->ipipeif_base_addr;
- unsigned int adofs;
- u32 val;
-
- if (ipipeif->input != IPIPEIF_INPUT_MEMORY)
- return -EINVAL;
-
- switch (ipipeif->formats[IPIPEIF_PAD_SINK].code) {
- case MEDIA_BUS_FMT_Y8_1X8:
- case MEDIA_BUS_FMT_UV8_1X8:
- case MEDIA_BUS_FMT_YDYUYDYV8_1X16:
- adofs = ipipeif->formats[IPIPEIF_PAD_SINK].width;
- break;
-
- default:
- adofs = ipipeif->formats[IPIPEIF_PAD_SINK].width << 1;
- break;
- }
-
- /* adjust the line len to be a multiple of 32 */
- adofs += 31;
- adofs &= ~0x1f;
- val = (adofs >> 5) & IPIPEIF_ADOFS_LSB_MASK;
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADOFS);
-
- /* lower sixteen bit */
- val = (addr >> IPIPEIF_ADDRL_SHIFT) & IPIPEIF_ADDRL_MASK;
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADDRL);
-
- /* upper next seven bit */
- val = (addr >> IPIPEIF_ADDRU_SHIFT) & IPIPEIF_ADDRU_MASK;
- ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADDRU);
-
- return 0;
-}
-
-/* subdev core operations */
-static const struct v4l2_subdev_core_ops ipipeif_v4l2_core_ops = {
- .ioctl = ipipeif_ioctl,
-};
-
-static const struct v4l2_ctrl_ops ipipeif_ctrl_ops = {
- .s_ctrl = ipipeif_s_ctrl,
-};
-
-static const struct v4l2_ctrl_config vpfe_ipipeif_dpcm_pred = {
- .ops = &ipipeif_ctrl_ops,
- .id = VPFE_CID_DPCM_PREDICTOR,
- .name = "DPCM Predictor",
- .type = V4L2_CTRL_TYPE_INTEGER,
- .min = 0,
- .max = 1,
- .step = 1,
- .def = 0,
-};
-
-/* subdev file operations */
-static const struct v4l2_subdev_internal_ops ipipeif_v4l2_internal_ops = {
- .open = ipipeif_init_formats,
-};
-
-/* subdev video operations */
-static const struct v4l2_subdev_video_ops ipipeif_v4l2_video_ops = {
- .s_stream = ipipeif_set_stream,
-};
-
-/* subdev pad operations */
-static const struct v4l2_subdev_pad_ops ipipeif_v4l2_pad_ops = {
- .enum_mbus_code = ipipeif_enum_mbus_code,
- .enum_frame_size = ipipeif_enum_frame_size,
- .get_fmt = ipipeif_get_format,
- .set_fmt = ipipeif_set_format,
-};
-
-/* subdev operations */
-static const struct v4l2_subdev_ops ipipeif_v4l2_ops = {
- .core = &ipipeif_v4l2_core_ops,
- .video = &ipipeif_v4l2_video_ops,
- .pad = &ipipeif_v4l2_pad_ops,
-};
-
-static const struct vpfe_video_operations video_in_ops = {
- .queue = ipipeif_video_in_queue,
-};
-
-static int
-ipipeif_link_setup(struct media_entity *entity, const struct media_pad *local,
- const struct media_pad *remote, u32 flags)
-{
- struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
- struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
- struct vpfe_device *vpfe = to_vpfe_device(ipipeif);
- unsigned int index = local->index;
-
- /* FIXME: this is actually a hack! */
- if (is_media_entity_v4l2_subdev(remote->entity))
- index |= 2 << 16;
-
- switch (index) {
- case IPIPEIF_PAD_SINK:
- /* Single shot mode */
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- ipipeif->input = IPIPEIF_INPUT_NONE;
- break;
- }
- ipipeif->input = IPIPEIF_INPUT_MEMORY;
- break;
-
- case IPIPEIF_PAD_SINK | 2 << 16:
- /* read from isif */
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- ipipeif->input = IPIPEIF_INPUT_NONE;
- break;
- }
- if (ipipeif->input != IPIPEIF_INPUT_NONE)
- return -EBUSY;
-
- ipipeif->input = IPIPEIF_INPUT_ISIF;
- break;
-
- case IPIPEIF_PAD_SOURCE | 2 << 16:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- ipipeif->output = IPIPEIF_OUTPUT_NONE;
- break;
- }
- if (remote->entity == &vpfe->vpfe_ipipe.subdev.entity)
- /* connencted to ipipe */
- ipipeif->output = IPIPEIF_OUTPUT_IPIPE;
- else if (remote->entity == &vpfe->vpfe_resizer.crop_resizer.subdev.entity)
- /* connected to resizer */
- ipipeif->output = IPIPEIF_OUTPUT_RESIZER;
- else
- return -EINVAL;
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-static const struct media_entity_operations ipipeif_media_ops = {
- .link_setup = ipipeif_link_setup,
-};
-
-/*
- * vpfe_ipipeif_unregister_entities() - Unregister entity
- * @ipipeif - pointer to ipipeif subdevice structure.
- */
-void vpfe_ipipeif_unregister_entities(struct vpfe_ipipeif_device *ipipeif)
-{
- /* unregister video device */
- vpfe_video_unregister(&ipipeif->video_in);
-
- /* unregister subdev */
- v4l2_device_unregister_subdev(&ipipeif->subdev);
- /* cleanup entity */
- media_entity_cleanup(&ipipeif->subdev.entity);
-}
-
-int
-vpfe_ipipeif_register_entities(struct vpfe_ipipeif_device *ipipeif,
- struct v4l2_device *vdev)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(ipipeif);
- unsigned int flags;
- int ret;
-
- /* Register the subdev */
- ret = v4l2_device_register_subdev(vdev, &ipipeif->subdev);
- if (ret < 0)
- return ret;
-
- ret = vpfe_video_register(&ipipeif->video_in, vdev);
- if (ret) {
- pr_err("Failed to register ipipeif video-in device\n");
- goto fail;
- }
- ipipeif->video_in.vpfe_dev = vpfe_dev;
-
- flags = 0;
- ret = media_create_pad_link(&ipipeif->video_in.video_dev.entity, 0,
- &ipipeif->subdev.entity, 0, flags);
- if (ret < 0)
- goto fail;
-
- return 0;
-fail:
- v4l2_device_unregister_subdev(&ipipeif->subdev);
-
- return ret;
-}
-
-#define IPIPEIF_GAIN_HIGH 0x3ff
-#define IPIPEIF_DEFAULT_GAIN 0x200
-
-int vpfe_ipipeif_init(struct vpfe_ipipeif_device *ipipeif,
- struct platform_device *pdev)
-{
- struct v4l2_subdev *sd = &ipipeif->subdev;
- struct media_pad *pads = &ipipeif->pads[0];
- struct media_entity *me = &sd->entity;
- static resource_size_t res_len;
- struct resource *res;
- int ret;
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
- if (!res)
- return -ENOENT;
-
- res_len = resource_size(res);
- res = request_mem_region(res->start, res_len, res->name);
- if (!res)
- return -EBUSY;
-
- ipipeif->ipipeif_base_addr = ioremap_nocache(res->start, res_len);
- if (!ipipeif->ipipeif_base_addr) {
- ret = -EBUSY;
- goto fail;
- }
-
- v4l2_subdev_init(sd, &ipipeif_v4l2_ops);
-
- sd->internal_ops = &ipipeif_v4l2_internal_ops;
- strscpy(sd->name, "DAVINCI IPIPEIF", sizeof(sd->name));
- sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
-
- v4l2_set_subdevdata(sd, ipipeif);
-
- sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
- pads[IPIPEIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- pads[IPIPEIF_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
- ipipeif->input = IPIPEIF_INPUT_NONE;
- ipipeif->output = IPIPEIF_OUTPUT_NONE;
- me->ops = &ipipeif_media_ops;
-
- ret = media_entity_pads_init(me, IPIPEIF_NUM_PADS, pads);
- if (ret)
- goto fail;
-
- v4l2_ctrl_handler_init(&ipipeif->ctrls, 2);
- v4l2_ctrl_new_std(&ipipeif->ctrls, &ipipeif_ctrl_ops,
- V4L2_CID_GAIN, 0,
- IPIPEIF_GAIN_HIGH, 1, IPIPEIF_DEFAULT_GAIN);
- v4l2_ctrl_new_custom(&ipipeif->ctrls, &vpfe_ipipeif_dpcm_pred, NULL);
- v4l2_ctrl_handler_setup(&ipipeif->ctrls);
- sd->ctrl_handler = &ipipeif->ctrls;
-
- ipipeif->video_in.ops = &video_in_ops;
- ipipeif->video_in.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
- ret = vpfe_video_init(&ipipeif->video_in, "IPIPEIF");
- if (ret) {
- pr_err("Failed to init IPIPEIF video-in device\n");
- goto fail;
- }
- ipipeif_set_default_config(ipipeif);
- return 0;
-fail:
- release_mem_region(res->start, res_len);
- return ret;
-}
-
-void
-vpfe_ipipeif_cleanup(struct vpfe_ipipeif_device *ipipeif,
- struct platform_device *pdev)
-{
- struct resource *res;
-
- v4l2_ctrl_handler_free(&ipipeif->ctrls);
- iounmap(ipipeif->ipipeif_base_addr);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
- if (res)
- release_mem_region(res->start, resource_size(res));
-
-}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h
deleted file mode 100644
index 4d126fc871f3..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h
+++ /dev/null
@@ -1,228 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_DM365_IPIPEIF_H
-#define _DAVINCI_VPFE_DM365_IPIPEIF_H
-
-#include <linux/platform_device.h>
-
-#include <media/davinci/vpss.h>
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-subdev.h>
-
-#include "dm365_ipipeif_user.h"
-#include "vpfe_video.h"
-
-/* IPIPE base specific types */
-enum ipipeif_data_shift {
- IPIPEIF_BITS15_2 = 0,
- IPIPEIF_BITS14_1 = 1,
- IPIPEIF_BITS13_0 = 2,
- IPIPEIF_BITS12_0 = 3,
- IPIPEIF_BITS11_0 = 4,
- IPIPEIF_BITS10_0 = 5,
- IPIPEIF_BITS9_0 = 6,
-};
-
-enum ipipeif_clkdiv {
- IPIPEIF_DIVIDE_HALF = 0,
- IPIPEIF_DIVIDE_THIRD = 1,
- IPIPEIF_DIVIDE_FOURTH = 2,
- IPIPEIF_DIVIDE_FIFTH = 3,
- IPIPEIF_DIVIDE_SIXTH = 4,
- IPIPEIF_DIVIDE_EIGHTH = 5,
- IPIPEIF_DIVIDE_SIXTEENTH = 6,
- IPIPEIF_DIVIDE_THIRTY = 7,
-};
-
-enum ipipeif_pack_mode {
- IPIPEIF_PACK_16_BIT = 0,
- IPIPEIF_PACK_8_BIT = 1,
-};
-
-enum ipipeif_5_1_pack_mode {
- IPIPEIF_5_1_PACK_16_BIT = 0,
- IPIPEIF_5_1_PACK_8_BIT = 1,
- IPIPEIF_5_1_PACK_8_BIT_A_LAW = 2,
- IPIPEIF_5_1_PACK_12_BIT = 3
-};
-
-enum ipipeif_input_source {
- IPIPEIF_CCDC = 0,
- IPIPEIF_SDRAM_RAW = 1,
- IPIPEIF_CCDC_DARKFM = 2,
- IPIPEIF_SDRAM_YUV = 3,
-};
-
-enum ipipeif_ialaw {
- IPIPEIF_ALAW_OFF = 0,
- IPIPEIF_ALAW_ON = 1,
-};
-
-enum ipipeif_input_src1 {
- IPIPEIF_SRC1_PARALLEL_PORT = 0,
- IPIPEIF_SRC1_SDRAM_RAW = 1,
- IPIPEIF_SRC1_ISIF_DARKFM = 2,
- IPIPEIF_SRC1_SDRAM_YUV = 3,
-};
-
-enum ipipeif_dfs_dir {
- IPIPEIF_PORT_MINUS_SDRAM = 0,
- IPIPEIF_SDRAM_MINUS_PORT = 1,
-};
-
-enum ipipeif_chroma_phase {
- IPIPEIF_CBCR_Y = 0,
- IPIPEIF_Y_CBCR = 1,
-};
-
-enum ipipeif_dpcm_type {
- IPIPEIF_DPCM_8BIT_10BIT = 0,
- IPIPEIF_DPCM_8BIT_12BIT = 1,
-};
-
-/* data shift for IPIPE 5.1 */
-enum ipipeif_5_1_data_shift {
- IPIPEIF_5_1_BITS11_0 = 0,
- IPIPEIF_5_1_BITS10_0 = 1,
- IPIPEIF_5_1_BITS9_0 = 2,
- IPIPEIF_5_1_BITS8_0 = 3,
- IPIPEIF_5_1_BITS7_0 = 4,
- IPIPEIF_5_1_BITS15_4 = 5,
-};
-
-#define IPIPEIF_PAD_SINK 0
-#define IPIPEIF_PAD_SOURCE 1
-
-#define IPIPEIF_NUM_PADS 2
-
-enum ipipeif_input_entity {
- IPIPEIF_INPUT_NONE = 0,
- IPIPEIF_INPUT_ISIF = 1,
- IPIPEIF_INPUT_MEMORY = 2,
-};
-
-enum ipipeif_output_entity {
- IPIPEIF_OUTPUT_NONE = 0,
- IPIPEIF_OUTPUT_IPIPE = 1,
- IPIPEIF_OUTPUT_RESIZER = 2,
-};
-
-struct vpfe_ipipeif_device {
- struct v4l2_subdev subdev;
- struct media_pad pads[IPIPEIF_NUM_PADS];
- struct v4l2_mbus_framefmt formats[IPIPEIF_NUM_PADS];
- enum ipipeif_input_entity input;
- unsigned int output;
- struct vpfe_video_device video_in;
- struct v4l2_ctrl_handler ctrls;
- void __iomem *ipipeif_base_addr;
- struct ipipeif_params config;
- int dpcm_predictor;
- int gain;
-};
-
-/* IPIPEIF Register Offsets from the base address */
-#define IPIPEIF_ENABLE 0x00
-#define IPIPEIF_CFG1 0x04
-#define IPIPEIF_PPLN 0x08
-#define IPIPEIF_LPFR 0x0c
-#define IPIPEIF_HNUM 0x10
-#define IPIPEIF_VNUM 0x14
-#define IPIPEIF_ADDRU 0x18
-#define IPIPEIF_ADDRL 0x1c
-#define IPIPEIF_ADOFS 0x20
-#define IPIPEIF_RSZ 0x24
-#define IPIPEIF_GAIN 0x28
-
-/* Below registers are available only on IPIPE 5.1 */
-#define IPIPEIF_DPCM 0x2c
-#define IPIPEIF_CFG2 0x30
-#define IPIPEIF_INIRSZ 0x34
-#define IPIPEIF_OCLIP 0x38
-#define IPIPEIF_DTUDF 0x3c
-#define IPIPEIF_CLKDIV 0x40
-#define IPIPEIF_DPC1 0x44
-#define IPIPEIF_DPC2 0x48
-#define IPIPEIF_DFSGVL 0x4c
-#define IPIPEIF_DFSGTH 0x50
-#define IPIPEIF_RSZ3A 0x54
-#define IPIPEIF_INIRSZ3A 0x58
-#define IPIPEIF_RSZ_MIN 16
-#define IPIPEIF_RSZ_MAX 112
-#define IPIPEIF_RSZ_CONST 16
-
-#define IPIPEIF_ADOFS_LSB_MASK 0x1ff
-#define IPIPEIF_ADOFS_LSB_SHIFT 5
-#define IPIPEIF_ADOFS_MSB_MASK 0x200
-#define IPIPEIF_ADDRU_MASK 0x7ff
-#define IPIPEIF_ADDRL_SHIFT 5
-#define IPIPEIF_ADDRL_MASK 0xffff
-#define IPIPEIF_ADDRU_SHIFT 21
-#define IPIPEIF_ADDRMSB_SHIFT 31
-#define IPIPEIF_ADDRMSB_LEFT_SHIFT 10
-
-/* CFG1 Masks and shifts */
-#define ONESHOT_SHIFT 0
-#define DECIM_SHIFT 1
-#define INPSRC_SHIFT 2
-#define CLKDIV_SHIFT 4
-#define AVGFILT_SHIFT 7
-#define PACK8IN_SHIFT 8
-#define IALAW_SHIFT 9
-#define CLKSEL_SHIFT 10
-#define DATASFT_SHIFT 11
-#define INPSRC1_SHIFT 14
-
-/* DPC2 */
-#define IPIPEIF_DPC2_EN_SHIFT 12
-#define IPIPEIF_DPC2_THR_MASK 0xfff
-/* Applicable for IPIPE 5.1 */
-#define IPIPEIF_DF_GAIN_EN_SHIFT 10
-#define IPIPEIF_DF_GAIN_MASK 0x3ff
-#define IPIPEIF_DF_GAIN_THR_MASK 0xfff
-/* DPCM */
-#define IPIPEIF_DPCM_BITS_SHIFT 2
-#define IPIPEIF_DPCM_PRED_SHIFT 1
-/* CFG2 */
-#define IPIPEIF_CFG2_HDPOL_SHIFT 1
-#define IPIPEIF_CFG2_VDPOL_SHIFT 2
-#define IPIPEIF_CFG2_YUV8_SHIFT 6
-#define IPIPEIF_CFG2_YUV16_SHIFT 3
-#define IPIPEIF_CFG2_YUV8P_SHIFT 7
-
-/* INIRSZ */
-#define IPIPEIF_INIRSZ_ALNSYNC_SHIFT 13
-#define IPIPEIF_INIRSZ_MASK 0x1fff
-
-/* CLKDIV */
-#define IPIPEIF_CLKDIV_M_SHIFT 8
-
-void vpfe_ipipeif_enable(struct vpfe_device *vpfe_dev);
-void vpfe_ipipeif_ss_buffer_isr(struct vpfe_ipipeif_device *ipipeif);
-int vpfe_ipipeif_decimation_enabled(struct vpfe_device *vpfe_dev);
-int vpfe_ipipeif_get_rsz(struct vpfe_device *vpfe_dev);
-void vpfe_ipipeif_cleanup(struct vpfe_ipipeif_device *ipipeif,
- struct platform_device *pdev);
-int vpfe_ipipeif_init(struct vpfe_ipipeif_device *ipipeif,
- struct platform_device *pdev);
-int vpfe_ipipeif_register_entities(struct vpfe_ipipeif_device *ipipeif,
- struct v4l2_device *vdev);
-void vpfe_ipipeif_unregister_entities(struct vpfe_ipipeif_device *ipipeif);
-
-#endif /* _DAVINCI_VPFE_DM365_IPIPEIF_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h b/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h
deleted file mode 100644
index 046dbdec67d8..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_DM365_IPIPEIF_USER_H
-#define _DAVINCI_VPFE_DM365_IPIPEIF_USER_H
-
-/* clockdiv for IPIPE 5.1 */
-struct ipipeif_5_1_clkdiv {
- unsigned char m;
- unsigned char n;
-};
-
-enum ipipeif_decimation {
- IPIPEIF_DECIMATION_OFF,
- IPIPEIF_DECIMATION_ON
-};
-
-/* DPC at the if for IPIPE 5.1 */
-struct ipipeif_dpc {
- /* 0 - disable, 1 - enable */
- unsigned char en;
- /* threshold */
- unsigned short thr;
-};
-
-enum ipipeif_clock {
- IPIPEIF_PIXCEL_CLK,
- IPIPEIF_SDRAM_CLK
-};
-
-enum ipipeif_avg_filter {
- IPIPEIF_AVG_OFF,
- IPIPEIF_AVG_ON
-};
-
-struct ipipeif_5_1 {
- struct ipipeif_5_1_clkdiv clk_div;
- /* Defect pixel correction */
- struct ipipeif_dpc dpc;
- /* clipped to this value */
- unsigned short clip;
- /* Align HSync and VSync to rsz_start */
- unsigned char align_sync;
- /* resizer start position */
- unsigned int rsz_start;
- /* DF gain enable */
- unsigned char df_gain_en;
- /* DF gain value */
- unsigned short df_gain;
- /* DF gain threshold value */
- unsigned short df_gain_thr;
-};
-
-struct ipipeif_params {
- enum ipipeif_clock clock_select;
- unsigned int ppln;
- unsigned int lpfr;
- unsigned char rsz;
- enum ipipeif_decimation decimation;
- enum ipipeif_avg_filter avg_filter;
- /* IPIPE 5.1 */
- struct ipipeif_5_1 if_5_1;
-};
-
-/*
- * Private IOCTL
- * VIDIOC_VPFE_IPIPEIF_S_CONFIG: Set IPIEIF configuration
- * VIDIOC_VPFE_IPIPEIF_G_CONFIG: Get IPIEIF configuration
- */
-#define VIDIOC_VPFE_IPIPEIF_S_CONFIG \
- _IOWR('I', BASE_VIDIOC_PRIVATE + 1, struct ipipeif_params)
-#define VIDIOC_VPFE_IPIPEIF_G_CONFIG \
- _IOWR('I', BASE_VIDIOC_PRIVATE + 2, struct ipipeif_params)
-
-#endif /* _DAVINCI_VPFE_DM365_IPIPEIF_USER_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif.c b/drivers/staging/media/davinci_vpfe/dm365_isif.c
deleted file mode 100644
index 05a997f7aa5d..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_isif.c
+++ /dev/null
@@ -1,2097 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#include <linux/delay.h>
-#include "dm365_isif.h"
-#include "vpfe_mc_capture.h"
-
-#define MAX_WIDTH 4096
-#define MAX_HEIGHT 4096
-
-static const unsigned int isif_fmts[] = {
- MEDIA_BUS_FMT_YUYV8_2X8,
- MEDIA_BUS_FMT_UYVY8_2X8,
- MEDIA_BUS_FMT_YUYV8_1X16,
- MEDIA_BUS_FMT_YUYV10_1X20,
- MEDIA_BUS_FMT_SGRBG12_1X12,
- MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8,
- MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
-};
-
-#define ISIF_COLPTN_R_Ye 0x0
-#define ISIF_COLPTN_Gr_Cy 0x1
-#define ISIF_COLPTN_Gb_G 0x2
-#define ISIF_COLPTN_B_Mg 0x3
-
-#define ISIF_CCOLP_CP01_0 0
-#define ISIF_CCOLP_CP03_2 2
-#define ISIF_CCOLP_CP05_4 4
-#define ISIF_CCOLP_CP07_6 6
-#define ISIF_CCOLP_CP11_0 8
-#define ISIF_CCOLP_CP13_2 10
-#define ISIF_CCOLP_CP15_4 12
-#define ISIF_CCOLP_CP17_6 14
-
-static const u32 isif_sgrbg_pattern =
- ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP01_0 |
- ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP03_2 |
- ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP05_4 |
- ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP07_6 |
- ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP11_0 |
- ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP13_2 |
- ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP15_4 |
- ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP17_6;
-
-static const u32 isif_srggb_pattern =
- ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP01_0 |
- ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP03_2 |
- ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP05_4 |
- ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP07_6 |
- ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP11_0 |
- ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP13_2 |
- ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP15_4 |
- ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP17_6;
-
-static inline u32 isif_read(void __iomem *base_addr, u32 offset)
-{
- return readl(base_addr + offset);
-}
-
-static inline void isif_write(void __iomem *base_addr, u32 val, u32 offset)
-{
- writel(val, base_addr + offset);
-}
-
-static inline u32 isif_merge(void __iomem *base_addr, u32 mask, u32 val,
- u32 offset)
-{
- u32 new_val = (isif_read(base_addr, offset) & ~mask) | (val & mask);
-
- isif_write(base_addr, new_val, offset);
-
- return new_val;
-}
-
-static void isif_enable_output_to_sdram(struct vpfe_isif_device *isif, int en)
-{
- isif_merge(isif->isif_cfg.base_addr, ISIF_SYNCEN_WEN_MASK,
- en << ISIF_SYNCEN_WEN_SHIFT, SYNCEN);
-}
-
-static inline void
-isif_regw_lin_tbl(struct vpfe_isif_device *isif, u32 val, u32 offset, int i)
-{
- if (!i)
- writel(val, isif->isif_cfg.linear_tbl0_addr + offset);
- else
- writel(val, isif->isif_cfg.linear_tbl1_addr + offset);
-}
-
-static void isif_disable_all_modules(struct vpfe_isif_device *isif)
-{
- /* disable BC */
- isif_write(isif->isif_cfg.base_addr, 0, CLAMPCFG);
- /* disable vdfc */
- isif_write(isif->isif_cfg.base_addr, 0, DFCCTL);
- /* disable CSC */
- isif_write(isif->isif_cfg.base_addr, 0, CSCCTL);
- /* disable linearization */
- isif_write(isif->isif_cfg.base_addr, 0, LINCFG0);
-}
-
-static void isif_enable(struct vpfe_isif_device *isif, int en)
-{
- if (!en)
- /* Before disable isif, disable all ISIF modules */
- isif_disable_all_modules(isif);
-
- /*
- * wait for next VD. Assume lowest scan rate is 12 Hz. So
- * 100 msec delay is good enough
- */
- msleep(100);
- isif_merge(isif->isif_cfg.base_addr, ISIF_SYNCEN_VDHDEN_MASK,
- en, SYNCEN);
-}
-
-/*
- * ISIF helper functions
- */
-
-#define DM365_ISIF_MDFS_OFFSET 15
-#define DM365_ISIF_MDFS_MASK 0x1
-
-/* get field id in isif hardware */
-enum v4l2_field vpfe_isif_get_fid(struct vpfe_device *vpfe_dev)
-{
- struct vpfe_isif_device *isif = &vpfe_dev->vpfe_isif;
- u32 field_status;
-
- field_status = isif_read(isif->isif_cfg.base_addr, MODESET);
- return (field_status >> DM365_ISIF_MDFS_OFFSET) &
- DM365_ISIF_MDFS_MASK;
-}
-
-static int
-isif_set_pixel_format(struct vpfe_isif_device *isif, unsigned int pixfmt)
-{
- if (isif->formats[ISIF_PAD_SINK].code == MEDIA_BUS_FMT_SGRBG12_1X12) {
- if (pixfmt == V4L2_PIX_FMT_SBGGR16)
- isif->isif_cfg.data_pack = ISIF_PACK_16BIT;
- else if ((pixfmt == V4L2_PIX_FMT_SGRBG10DPCM8) ||
- (pixfmt == V4L2_PIX_FMT_SGRBG10ALAW8))
- isif->isif_cfg.data_pack = ISIF_PACK_8BIT;
- else
- return -EINVAL;
-
- isif->isif_cfg.bayer.pix_fmt = ISIF_PIXFMT_RAW;
- isif->isif_cfg.bayer.v4l2_pix_fmt = pixfmt;
- } else {
- if (pixfmt == V4L2_PIX_FMT_YUYV)
- isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_YCBYCR;
- else if (pixfmt == V4L2_PIX_FMT_UYVY)
- isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_CBYCRY;
- else
- return -EINVAL;
-
- isif->isif_cfg.data_pack = ISIF_PACK_8BIT;
- isif->isif_cfg.ycbcr.v4l2_pix_fmt = pixfmt;
- }
-
- return 0;
-}
-
-static int
-isif_set_frame_format(struct vpfe_isif_device *isif,
- enum isif_frmfmt frm_fmt)
-{
- if (isif->formats[ISIF_PAD_SINK].code == MEDIA_BUS_FMT_SGRBG12_1X12)
- isif->isif_cfg.bayer.frm_fmt = frm_fmt;
- else
- isif->isif_cfg.ycbcr.frm_fmt = frm_fmt;
-
- return 0;
-}
-
-static int isif_set_image_window(struct vpfe_isif_device *isif)
-{
- struct v4l2_rect *win = &isif->crop;
-
- if (isif->formats[ISIF_PAD_SINK].code == MEDIA_BUS_FMT_SGRBG12_1X12) {
- isif->isif_cfg.bayer.win.top = win->top;
- isif->isif_cfg.bayer.win.left = win->left;
- isif->isif_cfg.bayer.win.width = win->width;
- isif->isif_cfg.bayer.win.height = win->height;
- return 0;
- }
- isif->isif_cfg.ycbcr.win.top = win->top;
- isif->isif_cfg.ycbcr.win.left = win->left;
- isif->isif_cfg.ycbcr.win.width = win->width;
- isif->isif_cfg.ycbcr.win.height = win->height;
-
- return 0;
-}
-
-static int
-isif_set_buftype(struct vpfe_isif_device *isif, enum isif_buftype buf_type)
-{
- if (isif->formats[ISIF_PAD_SINK].code == MEDIA_BUS_FMT_SGRBG12_1X12)
- isif->isif_cfg.bayer.buf_type = buf_type;
- else
- isif->isif_cfg.ycbcr.buf_type = buf_type;
-
- return 0;
-}
-
-/* configure format in isif hardware */
-static int
-isif_config_format(struct vpfe_device *vpfe_dev, unsigned int pad)
-{
- struct vpfe_isif_device *vpfe_isif = &vpfe_dev->vpfe_isif;
- enum isif_frmfmt frm_fmt = ISIF_FRMFMT_INTERLACED;
- struct v4l2_pix_format format;
- int ret = 0;
-
- v4l2_fill_pix_format(&format, &vpfe_dev->vpfe_isif.formats[pad]);
- mbus_to_pix(&vpfe_dev->vpfe_isif.formats[pad], &format);
-
- if (isif_set_pixel_format(vpfe_isif, format.pixelformat) < 0) {
- v4l2_err(&vpfe_dev->v4l2_dev,
- "Failed to set pixel format in isif\n");
- return -EINVAL;
- }
-
- /* call for s_crop will override these values */
- vpfe_isif->crop.left = 0;
- vpfe_isif->crop.top = 0;
- vpfe_isif->crop.width = format.width;
- vpfe_isif->crop.height = format.height;
-
- /* configure the image window */
- isif_set_image_window(vpfe_isif);
-
- switch (vpfe_dev->vpfe_isif.formats[pad].field) {
- case V4L2_FIELD_INTERLACED:
- /* do nothing, since it is default */
- ret = isif_set_buftype(vpfe_isif, ISIF_BUFTYPE_FLD_INTERLEAVED);
- break;
-
- case V4L2_FIELD_NONE:
- frm_fmt = ISIF_FRMFMT_PROGRESSIVE;
- /* buffer type only applicable for interlaced scan */
- break;
-
- case V4L2_FIELD_SEQ_TB:
- ret = isif_set_buftype(vpfe_isif, ISIF_BUFTYPE_FLD_SEPARATED);
- break;
-
- default:
- return -EINVAL;
- }
-
- /* set the frame format */
- if (!ret)
- ret = isif_set_frame_format(vpfe_isif, frm_fmt);
-
- return ret;
-}
-
-/*
- * isif_try_format() - Try video format on a pad
- * @isif: VPFE isif device
- * @cfg: V4L2 subdev pad config
- * @fmt: pointer to v4l2 subdev format structure
- */
-static void
-isif_try_format(struct vpfe_isif_device *isif,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- unsigned int width = fmt->format.width;
- unsigned int height = fmt->format.height;
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE(isif_fmts); i++) {
- if (fmt->format.code == isif_fmts[i])
- break;
- }
-
- /* If not found, use YUYV8_2x8 as default */
- if (i >= ARRAY_SIZE(isif_fmts))
- fmt->format.code = MEDIA_BUS_FMT_YUYV8_2X8;
-
- /* Clamp the size. */
- fmt->format.width = clamp_t(u32, width, 32, MAX_WIDTH);
- fmt->format.height = clamp_t(u32, height, 32, MAX_HEIGHT);
-
- /* The data formatter truncates the number of horizontal output
- * pixels to a multiple of 16. To avoid clipping data, allow
- * callers to request an output size bigger than the input size
- * up to the nearest multiple of 16.
- */
- if (fmt->pad == ISIF_PAD_SOURCE)
- fmt->format.width &= ~15;
-}
-
-/*
- * vpfe_isif_buffer_isr() - isif module non-progressive buffer scheduling isr
- * @isif: Pointer to isif subdevice.
- */
-void vpfe_isif_buffer_isr(struct vpfe_isif_device *isif)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(isif);
- struct vpfe_video_device *video = &isif->video_out;
- enum v4l2_field field;
- int fid;
-
- if (!video->started)
- return;
-
- field = video->fmt.fmt.pix.field;
-
- if (field == V4L2_FIELD_NONE) {
- /* handle progressive frame capture */
- if (video->cur_frm != video->next_frm)
- vpfe_video_process_buffer_complete(video);
- return;
- }
-
- /* interlaced or TB capture check which field we
- * are in hardware
- */
- fid = vpfe_isif_get_fid(vpfe_dev);
-
- /* switch the software maintained field id */
- video->field_id ^= 1;
- if (fid == video->field_id) {
- /* we are in-sync here,continue */
- if (fid == 0) {
- /*
- * One frame is just being captured. If the
- * next frame is available, release the current
- * frame and move on
- */
- if (video->cur_frm != video->next_frm)
- vpfe_video_process_buffer_complete(video);
- /*
- * based on whether the two fields are stored
- * interleavely or separately in memory,
- * reconfigure the ISIF memory address
- */
- if (field == V4L2_FIELD_SEQ_TB)
- vpfe_video_schedule_bottom_field(video);
- return;
- }
- /*
- * if one field is just being captured configure
- * the next frame get the next frame from the
- * empty queue if no frame is available hold on
- * to the current buffer
- */
- spin_lock(&video->dma_queue_lock);
- if (!list_empty(&video->dma_queue) &&
- video->cur_frm == video->next_frm)
- vpfe_video_schedule_next_buffer(video);
- spin_unlock(&video->dma_queue_lock);
- } else if (fid == 0) {
- /*
- * out of sync. Recover from any hardware out-of-sync.
- * May loose one frame
- */
- video->field_id = fid;
- }
-}
-
-/*
- * vpfe_isif_vidint1_isr() - ISIF module progressive buffer scheduling isr
- * @isif: Pointer to isif subdevice.
- */
-void vpfe_isif_vidint1_isr(struct vpfe_isif_device *isif)
-{
- struct vpfe_video_device *video = &isif->video_out;
-
- if (!video->started)
- return;
-
- spin_lock(&video->dma_queue_lock);
- if (video->fmt.fmt.pix.field == V4L2_FIELD_NONE &&
- !list_empty(&video->dma_queue) && video->cur_frm == video->next_frm)
- vpfe_video_schedule_next_buffer(video);
-
- spin_unlock(&video->dma_queue_lock);
-}
-
-/*
- * VPFE video operations
- */
-
-static int isif_video_queue(struct vpfe_device *vpfe_dev, unsigned long addr)
-{
- struct vpfe_isif_device *isif = &vpfe_dev->vpfe_isif;
-
- isif_write(isif->isif_cfg.base_addr, (addr >> 21) &
- ISIF_CADU_BITS, CADU);
- isif_write(isif->isif_cfg.base_addr, (addr >> 5) &
- ISIF_CADL_BITS, CADL);
-
- return 0;
-}
-
-static const struct vpfe_video_operations isif_video_ops = {
- .queue = isif_video_queue,
-};
-
-/*
- * V4L2 subdev operations
- */
-
-/* Parameter operations */
-static int isif_get_params(struct v4l2_subdev *sd, void *params)
-{
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
-
- /* only raw module parameters can be set through the IOCTL */
- if (isif->formats[ISIF_PAD_SINK].code != MEDIA_BUS_FMT_SGRBG12_1X12)
- return -EINVAL;
- memcpy(params, &isif->isif_cfg.bayer.config_params,
- sizeof(isif->isif_cfg.bayer.config_params));
- return 0;
-}
-
-static int isif_validate_df_csc_params(const struct vpfe_isif_df_csc *df_csc)
-{
- const struct vpfe_isif_color_space_conv *csc;
- int err = -EINVAL;
- int i;
-
- if (!df_csc->df_or_csc) {
- /* csc configuration */
- csc = &df_csc->csc;
- if (csc->en) {
- for (i = 0; i < VPFE_ISIF_CSC_NUM_COEFF; i++)
- if (csc->coeff[i].integer >
- ISIF_CSC_COEF_INTEG_MASK ||
- csc->coeff[i].decimal >
- ISIF_CSC_COEF_DECIMAL_MASK) {
- pr_err("Invalid CSC coefficients\n");
- return err;
- }
- }
- }
- if (df_csc->start_pix > ISIF_DF_CSC_SPH_MASK) {
- pr_err("Invalid df_csc start pix value\n");
- return err;
- }
-
- if (df_csc->num_pixels > ISIF_DF_NUMPIX) {
- pr_err("Invalid df_csc num pixels value\n");
- return err;
- }
-
- if (df_csc->start_line > ISIF_DF_CSC_LNH_MASK) {
- pr_err("Invalid df_csc start_line value\n");
- return err;
- }
-
- if (df_csc->num_lines > ISIF_DF_NUMLINES) {
- pr_err("Invalid df_csc num_lines value\n");
- return err;
- }
-
- return 0;
-}
-
-#define DM365_ISIF_MAX_VDFLSFT 4
-#define DM365_ISIF_MAX_VDFSLV 4095
-#define DM365_ISIF_MAX_DFCMEM0 0x1fff
-#define DM365_ISIF_MAX_DFCMEM1 0x1fff
-
-static int isif_validate_dfc_params(const struct vpfe_isif_dfc *dfc)
-{
- int err = -EINVAL;
- int i;
-
- if (!dfc->en)
- return 0;
-
- if (dfc->corr_whole_line > 1) {
- pr_err("Invalid corr_whole_line value\n");
- return err;
- }
-
- if (dfc->def_level_shift > DM365_ISIF_MAX_VDFLSFT) {
- pr_err("Invalid def_level_shift value\n");
- return err;
- }
-
- if (dfc->def_sat_level > DM365_ISIF_MAX_VDFSLV) {
- pr_err("Invalid def_sat_level value\n");
- return err;
- }
-
- if (!dfc->num_vdefects ||
- dfc->num_vdefects > VPFE_ISIF_VDFC_TABLE_SIZE) {
- pr_err("Invalid num_vdefects value\n");
- return err;
- }
-
- for (i = 0; i < VPFE_ISIF_VDFC_TABLE_SIZE; i++) {
- if (dfc->table[i].pos_vert > DM365_ISIF_MAX_DFCMEM0) {
- pr_err("Invalid pos_vert value\n");
- return err;
- }
- if (dfc->table[i].pos_horz > DM365_ISIF_MAX_DFCMEM1) {
- pr_err("Invalid pos_horz value\n");
- return err;
- }
- }
-
- return 0;
-}
-
-#define DM365_ISIF_MAX_CLVRV 0xfff
-#define DM365_ISIF_MAX_CLDC 0x1fff
-#define DM365_ISIF_MAX_CLHSH 0x1fff
-#define DM365_ISIF_MAX_CLHSV 0x1fff
-#define DM365_ISIF_MAX_CLVSH 0x1fff
-#define DM365_ISIF_MAX_CLVSV 0x1fff
-#define DM365_ISIF_MAX_HEIGHT_BLACK_REGION 0x1fff
-
-static int isif_validate_bclamp_params(const struct vpfe_isif_black_clamp *bclamp)
-{
- int err = -EINVAL;
-
- if (bclamp->dc_offset > DM365_ISIF_MAX_CLDC) {
- pr_err("Invalid bclamp dc_offset value\n");
- return err;
- }
- if (!bclamp->en)
- return 0;
- if (bclamp->horz.clamp_pix_limit > 1) {
- pr_err("Invalid bclamp horz clamp_pix_limit value\n");
- return err;
- }
- if (bclamp->horz.win_count_calc < 1 ||
- bclamp->horz.win_count_calc > 32) {
- pr_err("Invalid bclamp horz win_count_calc value\n");
- return err;
- }
- if (bclamp->horz.win_start_h_calc > DM365_ISIF_MAX_CLHSH) {
- pr_err("Invalid bclamp win_start_v_calc value\n");
- return err;
- }
-
- if (bclamp->horz.win_start_v_calc > DM365_ISIF_MAX_CLHSV) {
- pr_err("Invalid bclamp win_start_v_calc value\n");
- return err;
- }
- if (bclamp->vert.reset_clamp_val > DM365_ISIF_MAX_CLVRV) {
- pr_err("Invalid bclamp reset_clamp_val value\n");
- return err;
- }
- if (bclamp->vert.ob_v_sz_calc > DM365_ISIF_MAX_HEIGHT_BLACK_REGION) {
- pr_err("Invalid bclamp ob_v_sz_calc value\n");
- return err;
- }
- if (bclamp->vert.ob_start_h > DM365_ISIF_MAX_CLVSH) {
- pr_err("Invalid bclamp ob_start_h value\n");
- return err;
- }
- if (bclamp->vert.ob_start_v > DM365_ISIF_MAX_CLVSV) {
- pr_err("Invalid bclamp ob_start_h value\n");
- return err;
- }
- return 0;
-}
-
-static int
-isif_validate_raw_params(const struct vpfe_isif_raw_config *params)
-{
- int ret;
-
- ret = isif_validate_df_csc_params(&params->df_csc);
- if (ret)
- return ret;
- ret = isif_validate_dfc_params(&params->dfc);
- if (ret)
- return ret;
- return isif_validate_bclamp_params(&params->bclamp);
-}
-
-static int isif_set_params(struct v4l2_subdev *sd, const struct vpfe_isif_raw_config *params)
-{
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
- int ret = -EINVAL;
-
- /* only raw module parameters can be set through the IOCTL */
- if (isif->formats[ISIF_PAD_SINK].code != MEDIA_BUS_FMT_SGRBG12_1X12)
- return ret;
-
- if (!isif_validate_raw_params(params)) {
- memcpy(&isif->isif_cfg.bayer.config_params, params,
- sizeof(*params));
- ret = 0;
- }
- return ret;
-}
-/*
- * isif_ioctl() - isif module private ioctl's
- * @sd: VPFE isif V4L2 subdevice
- * @cmd: ioctl command
- * @arg: ioctl argument
- *
- * Return 0 on success or a negative error code otherwise.
- */
-static long isif_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
-{
- switch (cmd) {
- case VIDIOC_VPFE_ISIF_S_RAW_PARAMS:
- return isif_set_params(sd, arg);
-
- case VIDIOC_VPFE_ISIF_G_RAW_PARAMS:
- return isif_get_params(sd, arg);
-
- default:
- return -ENOIOCTLCMD;
- }
-}
-
-static void isif_config_gain_offset(struct vpfe_isif_device *isif)
-{
- struct vpfe_isif_gain_offsets_adj *gain_off_ptr =
- &isif->isif_cfg.bayer.config_params.gain_offset;
- void __iomem *base = isif->isif_cfg.base_addr;
- u32 val;
-
- val = ((gain_off_ptr->gain_sdram_en & 1) << GAIN_SDRAM_EN_SHIFT) |
- ((gain_off_ptr->gain_ipipe_en & 1) << GAIN_IPIPE_EN_SHIFT) |
- ((gain_off_ptr->gain_h3a_en & 1) << GAIN_H3A_EN_SHIFT) |
- ((gain_off_ptr->offset_sdram_en & 1) << OFST_SDRAM_EN_SHIFT) |
- ((gain_off_ptr->offset_ipipe_en & 1) << OFST_IPIPE_EN_SHIFT) |
- ((gain_off_ptr->offset_h3a_en & 1) << OFST_H3A_EN_SHIFT);
- isif_merge(base, GAIN_OFFSET_EN_MASK, val, CGAMMAWD);
-
- isif_write(base, isif->isif_cfg.isif_gain_params.cr_gain, CRGAIN);
- isif_write(base, isif->isif_cfg.isif_gain_params.cgr_gain, CGRGAIN);
- isif_write(base, isif->isif_cfg.isif_gain_params.cgb_gain, CGBGAIN);
- isif_write(base, isif->isif_cfg.isif_gain_params.cb_gain, CBGAIN);
- isif_write(base, isif->isif_cfg.isif_gain_params.offset & OFFSET_MASK,
- COFSTA);
-
-}
-
-static void isif_config_bclamp(struct vpfe_isif_device *isif,
- struct vpfe_isif_black_clamp *bc)
-{
- u32 val;
-
- /**
- * DC Offset is always added to image data irrespective of bc enable
- * status
- */
- val = bc->dc_offset & ISIF_BC_DCOFFSET_MASK;
- isif_write(isif->isif_cfg.base_addr, val, CLDCOFST);
-
- if (!bc->en)
- return;
-
- val = (bc->bc_mode_color & ISIF_BC_MODE_COLOR_MASK) <<
- ISIF_BC_MODE_COLOR_SHIFT;
-
- /* Enable BC and horizontal clamp calculation parameters */
- val = val | 1 | ((bc->horz.mode & ISIF_HORZ_BC_MODE_MASK) <<
- ISIF_HORZ_BC_MODE_SHIFT);
-
- isif_write(isif->isif_cfg.base_addr, val, CLAMPCFG);
-
- if (bc->horz.mode != VPFE_ISIF_HORZ_BC_DISABLE) {
- /*
- * Window count for calculation
- * Base window selection
- * pixel limit
- * Horizontal size of window
- * vertical size of the window
- * Horizontal start position of the window
- * Vertical start position of the window
- */
- val = (bc->horz.win_count_calc & ISIF_HORZ_BC_WIN_COUNT_MASK) |
- ((bc->horz.base_win_sel_calc & 1) <<
- ISIF_HORZ_BC_WIN_SEL_SHIFT) |
- ((bc->horz.clamp_pix_limit & 1) <<
- ISIF_HORZ_BC_PIX_LIMIT_SHIFT) |
- ((bc->horz.win_h_sz_calc &
- ISIF_HORZ_BC_WIN_H_SIZE_MASK) <<
- ISIF_HORZ_BC_WIN_H_SIZE_SHIFT) |
- ((bc->horz.win_v_sz_calc &
- ISIF_HORZ_BC_WIN_V_SIZE_MASK) <<
- ISIF_HORZ_BC_WIN_V_SIZE_SHIFT);
-
- isif_write(isif->isif_cfg.base_addr, val, CLHWIN0);
-
- val = bc->horz.win_start_h_calc & ISIF_HORZ_BC_WIN_START_H_MASK;
- isif_write(isif->isif_cfg.base_addr, val, CLHWIN1);
-
- val = bc->horz.win_start_v_calc & ISIF_HORZ_BC_WIN_START_V_MASK;
- isif_write(isif->isif_cfg.base_addr, val, CLHWIN2);
- }
-
- /* vertical clamp calculation parameters */
- /* OB H Valid */
- val = bc->vert.ob_h_sz_calc & ISIF_VERT_BC_OB_H_SZ_MASK;
-
- /* Reset clamp value sel for previous line */
- val |= (bc->vert.reset_val_sel & ISIF_VERT_BC_RST_VAL_SEL_MASK) <<
- ISIF_VERT_BC_RST_VAL_SEL_SHIFT;
-
- /* Line average coefficient */
- val |= bc->vert.line_ave_coef << ISIF_VERT_BC_LINE_AVE_COEF_SHIFT;
- isif_write(isif->isif_cfg.base_addr, val, CLVWIN0);
-
- /* Configured reset value */
- if (bc->vert.reset_val_sel == VPFE_ISIF_VERT_BC_USE_CONFIG_CLAMP_VAL) {
- val = bc->vert.reset_clamp_val & ISIF_VERT_BC_RST_VAL_MASK;
- isif_write(isif->isif_cfg.base_addr, val, CLVRV);
- }
-
- /* Optical Black horizontal start position */
- val = bc->vert.ob_start_h & ISIF_VERT_BC_OB_START_HORZ_MASK;
- isif_write(isif->isif_cfg.base_addr, val, CLVWIN1);
-
- /* Optical Black vertical start position */
- val = bc->vert.ob_start_v & ISIF_VERT_BC_OB_START_VERT_MASK;
- isif_write(isif->isif_cfg.base_addr, val, CLVWIN2);
-
- val = bc->vert.ob_v_sz_calc & ISIF_VERT_BC_OB_VERT_SZ_MASK;
- isif_write(isif->isif_cfg.base_addr, val, CLVWIN3);
-
- /* Vertical start position for BC subtraction */
- val = bc->vert_start_sub & ISIF_BC_VERT_START_SUB_V_MASK;
- isif_write(isif->isif_cfg.base_addr, val, CLSV);
-}
-
-/* This function will configure the window size to be capture in ISIF reg */
-static void
-isif_setwin(struct vpfe_isif_device *isif, struct v4l2_rect *image_win,
- enum isif_frmfmt frm_fmt, int ppc, int mode)
-{
- int horz_nr_pixels;
- int vert_nr_lines;
- int horz_start;
- int vert_start;
- int mid_img;
-
- /*
- * ppc - per pixel count. indicates how many pixels per cell
- * output to SDRAM. example, for ycbcr, it is one y and one c, so 2.
- * raw capture this is 1
- */
- horz_start = image_win->left << (ppc - 1);
- horz_nr_pixels = (image_win->width << (ppc - 1)) - 1;
-
- /* Writing the horizontal info into the registers */
- isif_write(isif->isif_cfg.base_addr,
- horz_start & START_PX_HOR_MASK, SPH);
- isif_write(isif->isif_cfg.base_addr,
- horz_nr_pixels & NUM_PX_HOR_MASK, LNH);
- vert_start = image_win->top;
-
- if (frm_fmt == ISIF_FRMFMT_INTERLACED) {
- vert_nr_lines = (image_win->height >> 1) - 1;
- vert_start >>= 1;
- /* To account for VD since line 0 doesn't have any data */
- vert_start += 1;
- } else {
- /* To account for VD since line 0 doesn't have any data */
- vert_start += 1;
- vert_nr_lines = image_win->height - 1;
- /* configure VDINT0 and VDINT1 */
- mid_img = vert_start + (image_win->height / 2);
- isif_write(isif->isif_cfg.base_addr, mid_img, VDINT1);
- }
-
- if (!mode)
- isif_write(isif->isif_cfg.base_addr, 0, VDINT0);
- else
- isif_write(isif->isif_cfg.base_addr, vert_nr_lines, VDINT0);
- isif_write(isif->isif_cfg.base_addr,
- vert_start & START_VER_ONE_MASK, SLV0);
- isif_write(isif->isif_cfg.base_addr,
- vert_start & START_VER_TWO_MASK, SLV1);
- isif_write(isif->isif_cfg.base_addr,
- vert_nr_lines & NUM_LINES_VER, LNV);
-}
-
-#define DM365_ISIF_DFCMWR_MEMORY_WRITE 1
-#define DM365_ISIF_DFCMRD_MEMORY_READ 0x2
-
-static void
-isif_config_dfc(struct vpfe_isif_device *isif, struct vpfe_isif_dfc *vdfc)
-{
-#define DFC_WRITE_WAIT_COUNT 1000
- u32 count = DFC_WRITE_WAIT_COUNT;
- u32 val;
- int i;
-
- if (!vdfc->en)
- return;
-
- /* Correction mode */
- val = (vdfc->corr_mode & ISIF_VDFC_CORR_MOD_MASK) <<
- ISIF_VDFC_CORR_MOD_SHIFT;
-
- /* Correct whole line or partial */
- if (vdfc->corr_whole_line)
- val |= BIT(ISIF_VDFC_CORR_WHOLE_LN_SHIFT);
-
- /* level shift value */
- val |= (vdfc->def_level_shift & ISIF_VDFC_LEVEL_SHFT_MASK) <<
- ISIF_VDFC_LEVEL_SHFT_SHIFT;
-
- isif_write(isif->isif_cfg.base_addr, val, DFCCTL);
-
- /* Defect saturation level */
- val = vdfc->def_sat_level & ISIF_VDFC_SAT_LEVEL_MASK;
- isif_write(isif->isif_cfg.base_addr, val, VDFSATLV);
-
- isif_write(isif->isif_cfg.base_addr, vdfc->table[0].pos_vert &
- ISIF_VDFC_POS_MASK, DFCMEM0);
- isif_write(isif->isif_cfg.base_addr, vdfc->table[0].pos_horz &
- ISIF_VDFC_POS_MASK, DFCMEM1);
- if (vdfc->corr_mode == VPFE_ISIF_VDFC_NORMAL ||
- vdfc->corr_mode == VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT) {
- isif_write(isif->isif_cfg.base_addr,
- vdfc->table[0].level_at_pos, DFCMEM2);
- isif_write(isif->isif_cfg.base_addr,
- vdfc->table[0].level_up_pixels, DFCMEM3);
- isif_write(isif->isif_cfg.base_addr,
- vdfc->table[0].level_low_pixels, DFCMEM4);
- }
-
- val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
- /* set DFCMARST and set DFCMWR */
- val |= BIT(ISIF_DFCMEMCTL_DFCMARST_SHIFT);
- val |= 1;
- isif_write(isif->isif_cfg.base_addr, val, DFCMEMCTL);
-
- while (count && (isif_read(isif->isif_cfg.base_addr, DFCMEMCTL) & 0x01))
- count--;
-
- val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
- if (!count) {
- pr_debug("defect table write timeout !!\n");
- return;
- }
-
- for (i = 1; i < vdfc->num_vdefects; i++) {
- isif_write(isif->isif_cfg.base_addr, vdfc->table[i].pos_vert &
- ISIF_VDFC_POS_MASK, DFCMEM0);
-
- isif_write(isif->isif_cfg.base_addr, vdfc->table[i].pos_horz &
- ISIF_VDFC_POS_MASK, DFCMEM1);
-
- if (vdfc->corr_mode == VPFE_ISIF_VDFC_NORMAL ||
- vdfc->corr_mode == VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT) {
- isif_write(isif->isif_cfg.base_addr,
- vdfc->table[i].level_at_pos, DFCMEM2);
- isif_write(isif->isif_cfg.base_addr,
- vdfc->table[i].level_up_pixels, DFCMEM3);
- isif_write(isif->isif_cfg.base_addr,
- vdfc->table[i].level_low_pixels, DFCMEM4);
- }
- val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
- /* clear DFCMARST and set DFCMWR */
- val &= ~BIT(ISIF_DFCMEMCTL_DFCMARST_SHIFT);
- val |= 1;
- isif_write(isif->isif_cfg.base_addr, val, DFCMEMCTL);
-
- count = DFC_WRITE_WAIT_COUNT;
- while (count && (isif_read(isif->isif_cfg.base_addr,
- DFCMEMCTL) & 0x01))
- count--;
-
- val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
- if (!count) {
- pr_debug("defect table write timeout !!\n");
- return;
- }
- }
- if (vdfc->num_vdefects < VPFE_ISIF_VDFC_TABLE_SIZE) {
- /* Extra cycle needed */
- isif_write(isif->isif_cfg.base_addr, 0, DFCMEM0);
- isif_write(isif->isif_cfg.base_addr,
- DM365_ISIF_MAX_DFCMEM1, DFCMEM1);
- isif_write(isif->isif_cfg.base_addr,
- DM365_ISIF_DFCMWR_MEMORY_WRITE, DFCMEMCTL);
- }
- /* enable VDFC */
- isif_merge(isif->isif_cfg.base_addr, (1 << ISIF_VDFC_EN_SHIFT),
- (1 << ISIF_VDFC_EN_SHIFT), DFCCTL);
-
- isif_merge(isif->isif_cfg.base_addr, (1 << ISIF_VDFC_EN_SHIFT),
- (0 << ISIF_VDFC_EN_SHIFT), DFCCTL);
-
- isif_write(isif->isif_cfg.base_addr, 0x6, DFCMEMCTL);
- for (i = 0; i < vdfc->num_vdefects; i++) {
- count = DFC_WRITE_WAIT_COUNT;
- while (count &&
- (isif_read(isif->isif_cfg.base_addr, DFCMEMCTL) & 0x2))
- count--;
- val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
- if (!count) {
- pr_debug("defect table write timeout !!\n");
- return;
- }
- isif_write(isif->isif_cfg.base_addr,
- DM365_ISIF_DFCMRD_MEMORY_READ, DFCMEMCTL);
- }
-}
-
-static void
-isif_config_csc(struct vpfe_isif_device *isif, struct vpfe_isif_df_csc *df_csc)
-{
- u32 val1;
- u32 val2;
- u32 i;
-
- if (!df_csc->csc.en) {
- isif_write(isif->isif_cfg.base_addr, 0, CSCCTL);
- return;
- }
- /* initialize all bits to 0 */
- val1 = 0;
- for (i = 0; i < VPFE_ISIF_CSC_NUM_COEFF; i++) {
- if ((i % 2) == 0) {
- /* CSCM - LSB */
- val1 = ((df_csc->csc.coeff[i].integer &
- ISIF_CSC_COEF_INTEG_MASK) <<
- ISIF_CSC_COEF_INTEG_SHIFT) |
- ((df_csc->csc.coeff[i].decimal &
- ISIF_CSC_COEF_DECIMAL_MASK));
- } else {
-
- /* CSCM - MSB */
- val2 = ((df_csc->csc.coeff[i].integer &
- ISIF_CSC_COEF_INTEG_MASK) <<
- ISIF_CSC_COEF_INTEG_SHIFT) |
- ((df_csc->csc.coeff[i].decimal &
- ISIF_CSC_COEF_DECIMAL_MASK));
- val2 <<= ISIF_CSCM_MSB_SHIFT;
- val2 |= val1;
- isif_write(isif->isif_cfg.base_addr, val2,
- (CSCM0 + ((i-1) << 1)));
- }
- }
- /* program the active area */
- isif_write(isif->isif_cfg.base_addr, df_csc->start_pix &
- ISIF_DF_CSC_SPH_MASK, FMTSPH);
- /*
- * one extra pixel as required for CSC. Actually number of
- * pixel - 1 should be configured in this register. So we
- * need to subtract 1 before writing to FMTSPH, but we will
- * not do this since csc requires one extra pixel
- */
- isif_write(isif->isif_cfg.base_addr, df_csc->num_pixels &
- ISIF_DF_CSC_SPH_MASK, FMTLNH);
- isif_write(isif->isif_cfg.base_addr, df_csc->start_line &
- ISIF_DF_CSC_SPH_MASK, FMTSLV);
- /*
- * one extra line as required for CSC. See reason documented for
- * num_pixels
- */
- isif_write(isif->isif_cfg.base_addr, df_csc->num_lines &
- ISIF_DF_CSC_SPH_MASK, FMTLNV);
- /* Enable CSC */
- isif_write(isif->isif_cfg.base_addr, 1, CSCCTL);
-}
-
-static void
-isif_config_linearization(struct vpfe_isif_device *isif,
- struct vpfe_isif_linearize *linearize)
-{
- u32 val;
- u32 i;
-
- if (!linearize->en) {
- isif_write(isif->isif_cfg.base_addr, 0, LINCFG0);
- return;
- }
- /* shift value for correction */
- val = (linearize->corr_shft & ISIF_LIN_CORRSFT_MASK) <<
- ISIF_LIN_CORRSFT_SHIFT;
- /* enable */
- val |= 1;
- isif_write(isif->isif_cfg.base_addr, val, LINCFG0);
- /* Scale factor */
- val = (linearize->scale_fact.integer & 1) <<
- ISIF_LIN_SCALE_FACT_INTEG_SHIFT;
- val |= linearize->scale_fact.decimal & ISIF_LIN_SCALE_FACT_DECIMAL_MASK;
- isif_write(isif->isif_cfg.base_addr, val, LINCFG1);
-
- for (i = 0; i < VPFE_ISIF_LINEAR_TAB_SIZE; i++) {
- val = linearize->table[i] & ISIF_LIN_ENTRY_MASK;
- if (i%2)
- isif_regw_lin_tbl(isif, val, ((i >> 1) << 2), 1);
- else
- isif_regw_lin_tbl(isif, val, ((i >> 1) << 2), 0);
- }
-}
-
-static void
-isif_config_culling(struct vpfe_isif_device *isif, struct vpfe_isif_cul *cul)
-{
- u32 val;
-
- /* Horizontal pattern */
- val = cul->hcpat_even << CULL_PAT_EVEN_LINE_SHIFT;
- val |= cul->hcpat_odd;
- isif_write(isif->isif_cfg.base_addr, val, CULH);
- /* vertical pattern */
- isif_write(isif->isif_cfg.base_addr, cul->vcpat, CULV);
- /* LPF */
- isif_merge(isif->isif_cfg.base_addr, ISIF_LPF_MASK << ISIF_LPF_SHIFT,
- cul->en_lpf << ISIF_LPF_SHIFT, MODESET);
-}
-
-static int isif_get_pix_fmt(u32 mbus_code)
-{
- switch (mbus_code) {
- case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8:
- case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8:
- case MEDIA_BUS_FMT_SGRBG12_1X12:
- return ISIF_PIXFMT_RAW;
-
- case MEDIA_BUS_FMT_YUYV8_2X8:
- case MEDIA_BUS_FMT_UYVY8_2X8:
- case MEDIA_BUS_FMT_YUYV10_2X10:
- case MEDIA_BUS_FMT_Y8_1X8:
- return ISIF_PIXFMT_YCBCR_8BIT;
-
- case MEDIA_BUS_FMT_YUYV8_1X16:
- case MEDIA_BUS_FMT_YUYV10_1X20:
- return ISIF_PIXFMT_YCBCR_16BIT;
-
- default:
- break;
- }
- return -EINVAL;
-}
-
-#define ISIF_INTERLACE_INVERSE_MODE 0x4b6d
-#define ISIF_INTERLACE_NON_INVERSE_MODE 0x0b6d
-#define ISIF_PROGRESSIVE_INVERSE_MODE 0x4000
-#define ISIF_PROGRESSIVE_NON_INVERSE_MODE 0x0000
-
-static int isif_config_raw(struct v4l2_subdev *sd, int mode)
-{
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
- struct isif_params_raw *params = &isif->isif_cfg.bayer;
- struct vpfe_isif_raw_config *module_params =
- &isif->isif_cfg.bayer.config_params;
- struct v4l2_mbus_framefmt *format;
- int pix_fmt;
- u32 val;
-
- format = &isif->formats[ISIF_PAD_SINK];
-
- /* In case of user has set BT656IF earlier, it should be reset
- * when configuring for raw input.
- */
- isif_write(isif->isif_cfg.base_addr, 0, REC656IF);
- /* Configure CCDCFG register
- * Set CCD Not to swap input since input is RAW data
- * Set FID detection function to Latch at V-Sync
- * Set WENLOG - isif valid area
- * Set TRGSEL
- * Set EXTRG
- * Packed to 8 or 16 bits
- */
- val = ISIF_YCINSWP_RAW | ISIF_CCDCFG_FIDMD_LATCH_VSYNC |
- ISIF_CCDCFG_WENLOG_AND | ISIF_CCDCFG_TRGSEL_WEN |
- ISIF_CCDCFG_EXTRG_DISABLE | (isif->isif_cfg.data_pack &
- ISIF_DATA_PACK_MASK);
- isif_write(isif->isif_cfg.base_addr, val, CCDCFG);
-
- pix_fmt = isif_get_pix_fmt(format->code);
- if (pix_fmt < 0) {
- pr_debug("Invalid pix_fmt(input mode)\n");
- return -EINVAL;
- }
- /*
- * Configure the vertical sync polarity(MODESET.VDPOL)
- * Configure the horizontal sync polarity (MODESET.HDPOL)
- * Configure frame id polarity (MODESET.FLDPOL)
- * Configure data polarity
- * Configure External WEN Selection
- * Configure frame format(progressive or interlace)
- * Configure pixel format (Input mode)
- * Configure the data shift
- */
- val = ISIF_VDHDOUT_INPUT | ((params->vd_pol & ISIF_VD_POL_MASK) <<
- ISIF_VD_POL_SHIFT) | ((params->hd_pol & ISIF_HD_POL_MASK) <<
- ISIF_HD_POL_SHIFT) | ((params->fid_pol & ISIF_FID_POL_MASK) <<
- ISIF_FID_POL_SHIFT) | ((ISIF_DATAPOL_NORMAL &
- ISIF_DATAPOL_MASK) << ISIF_DATAPOL_SHIFT) | ((ISIF_EXWEN_DISABLE &
- ISIF_EXWEN_MASK) << ISIF_EXWEN_SHIFT) | ((params->frm_fmt &
- ISIF_FRM_FMT_MASK) << ISIF_FRM_FMT_SHIFT) | ((pix_fmt &
- ISIF_INPUT_MASK) << ISIF_INPUT_SHIFT);
-
- /* currently only MEDIA_BUS_FMT_SGRBG12_1X12 is
- * supported. shift appropriately depending on
- * different MBUS fmt's added
- */
- if (format->code == MEDIA_BUS_FMT_SGRBG12_1X12)
- val |= ((VPFE_ISIF_NO_SHIFT &
- ISIF_DATASFT_MASK) << ISIF_DATASFT_SHIFT);
-
- isif_write(isif->isif_cfg.base_addr, val, MODESET);
- /*
- * Configure GAMMAWD register
- * CFA pattern setting
- */
- val = (params->cfa_pat & ISIF_GAMMAWD_CFA_MASK) <<
- ISIF_GAMMAWD_CFA_SHIFT;
- /* Gamma msb */
- if (params->v4l2_pix_fmt == V4L2_PIX_FMT_SGRBG10ALAW8)
- val = val | ISIF_ALAW_ENABLE;
-
- val = val | ((params->data_msb & ISIF_ALAW_GAMA_WD_MASK) <<
- ISIF_ALAW_GAMA_WD_SHIFT);
-
- isif_write(isif->isif_cfg.base_addr, val, CGAMMAWD);
- /* Configure DPCM compression settings */
- if (params->v4l2_pix_fmt == V4L2_PIX_FMT_SGRBG10DPCM8) {
- val = BIT(ISIF_DPCM_EN_SHIFT);
- val |= (params->dpcm_predictor &
- ISIF_DPCM_PREDICTOR_MASK) << ISIF_DPCM_PREDICTOR_SHIFT;
- }
- isif_write(isif->isif_cfg.base_addr, val, MISC);
- /* Configure Gain & Offset */
- isif_config_gain_offset(isif);
- /* Configure Color pattern */
- if (format->code == MEDIA_BUS_FMT_SGRBG12_1X12)
- val = isif_sgrbg_pattern;
- else
- /* default set to rggb */
- val = isif_srggb_pattern;
-
- isif_write(isif->isif_cfg.base_addr, val, CCOLP);
-
- /* Configure HSIZE register */
- val = (params->horz_flip_en & ISIF_HSIZE_FLIP_MASK) <<
- ISIF_HSIZE_FLIP_SHIFT;
-
- /* calculate line offset in 32 bytes based on pack value */
- if (isif->isif_cfg.data_pack == ISIF_PACK_8BIT)
- val |= ((params->win.width + 31) >> 5) & ISIF_LINEOFST_MASK;
- else if (isif->isif_cfg.data_pack == ISIF_PACK_12BIT)
- val |= ((((params->win.width + (params->win.width >> 2)) +
- 31) >> 5) & ISIF_LINEOFST_MASK);
- else
- val |= (((params->win.width * 2) + 31) >> 5) &
- ISIF_LINEOFST_MASK;
- isif_write(isif->isif_cfg.base_addr, val, HSIZE);
- /* Configure SDOFST register */
- if (params->frm_fmt == ISIF_FRMFMT_INTERLACED) {
- if (params->image_invert_en)
- /* For interlace inverse mode */
- isif_write(isif->isif_cfg.base_addr,
- ISIF_INTERLACE_INVERSE_MODE, SDOFST);
- else
- /* For interlace non inverse mode */
- isif_write(isif->isif_cfg.base_addr,
- ISIF_INTERLACE_NON_INVERSE_MODE, SDOFST);
- } else if (params->frm_fmt == ISIF_FRMFMT_PROGRESSIVE) {
- if (params->image_invert_en)
- isif_write(isif->isif_cfg.base_addr,
- ISIF_PROGRESSIVE_INVERSE_MODE, SDOFST);
- else
- /* For progessive non inverse mode */
- isif_write(isif->isif_cfg.base_addr,
- ISIF_PROGRESSIVE_NON_INVERSE_MODE, SDOFST);
- }
- /* Configure video window */
- isif_setwin(isif, &params->win, params->frm_fmt, 1, mode);
- /* Configure Black Clamp */
- isif_config_bclamp(isif, &module_params->bclamp);
- /* Configure Vertical Defection Pixel Correction */
- isif_config_dfc(isif, &module_params->dfc);
- if (!module_params->df_csc.df_or_csc)
- /* Configure Color Space Conversion */
- isif_config_csc(isif, &module_params->df_csc);
-
- isif_config_linearization(isif, &module_params->linearize);
- /* Configure Culling */
- isif_config_culling(isif, &module_params->culling);
- /* Configure Horizontal and vertical offsets(DFC,LSC,Gain) */
- val = module_params->horz_offset & ISIF_DATA_H_OFFSET_MASK;
- isif_write(isif->isif_cfg.base_addr, val, DATAHOFST);
-
- val = module_params->vert_offset & ISIF_DATA_V_OFFSET_MASK;
- isif_write(isif->isif_cfg.base_addr, val, DATAVOFST);
-
- return 0;
-}
-
-#define DM365_ISIF_HSIZE_MASK 0xffffffe0
-#define DM365_ISIF_SDOFST_2_LINES 0x00000249
-
-/* This function will configure ISIF for YCbCr parameters. */
-static int isif_config_ycbcr(struct v4l2_subdev *sd, int mode)
-{
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
- struct isif_ycbcr_config *params = &isif->isif_cfg.ycbcr;
- struct v4l2_mbus_framefmt *format;
- int pix_fmt;
- u32 modeset;
- u32 ccdcfg;
-
- format = &isif->formats[ISIF_PAD_SINK];
- /*
- * first reset the ISIF
- * all registers have default values after reset
- * This is important since we assume default values to be set in
- * a lot of registers that we didn't touch
- */
- /* start with all bits zero */
- ccdcfg = 0;
- modeset = 0;
- pix_fmt = isif_get_pix_fmt(format->code);
- if (pix_fmt < 0) {
- pr_debug("Invalid pix_fmt(input mode)\n");
- return -EINVAL;
- }
- /* configure pixel format or input mode */
- modeset = modeset | ((pix_fmt & ISIF_INPUT_MASK) <<
- ISIF_INPUT_SHIFT) | ((params->frm_fmt & ISIF_FRM_FMT_MASK) <<
- ISIF_FRM_FMT_SHIFT) | (((params->fid_pol &
- ISIF_FID_POL_MASK) << ISIF_FID_POL_SHIFT)) |
- (((params->hd_pol & ISIF_HD_POL_MASK) << ISIF_HD_POL_SHIFT)) |
- (((params->vd_pol & ISIF_VD_POL_MASK) << ISIF_VD_POL_SHIFT));
- /* pack the data to 8-bit CCDCCFG */
- switch (format->code) {
- case MEDIA_BUS_FMT_YUYV8_2X8:
- case MEDIA_BUS_FMT_UYVY8_2X8:
- if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) {
- pr_debug("Invalid pix_fmt(input mode)\n");
- return -EINVAL;
- }
- modeset |= ((VPFE_PINPOL_NEGATIVE & ISIF_VD_POL_MASK) <<
- ISIF_VD_POL_SHIFT);
- isif_write(isif->isif_cfg.base_addr, 3, REC656IF);
- ccdcfg = ccdcfg | ISIF_PACK_8BIT | ISIF_YCINSWP_YCBCR;
- break;
-
- case MEDIA_BUS_FMT_YUYV10_2X10:
- if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) {
- pr_debug("Invalid pix_fmt(input mode)\n");
- return -EINVAL;
- }
- /* setup BT.656, embedded sync */
- isif_write(isif->isif_cfg.base_addr, 3, REC656IF);
- /* enable 10 bit mode in ccdcfg */
- ccdcfg = ccdcfg | ISIF_PACK_8BIT | ISIF_YCINSWP_YCBCR |
- ISIF_BW656_ENABLE;
- break;
-
- case MEDIA_BUS_FMT_YUYV10_1X20:
- if (pix_fmt != ISIF_PIXFMT_YCBCR_16BIT) {
- pr_debug("Invalid pix_fmt(input mode)\n");
- return -EINVAL;
- }
- isif_write(isif->isif_cfg.base_addr, 3, REC656IF);
- break;
-
- case MEDIA_BUS_FMT_Y8_1X8:
- ccdcfg |= ISIF_PACK_8BIT;
- ccdcfg |= ISIF_YCINSWP_YCBCR;
- if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) {
- pr_debug("Invalid pix_fmt(input mode)\n");
- return -EINVAL;
- }
- break;
-
- case MEDIA_BUS_FMT_YUYV8_1X16:
- if (pix_fmt != ISIF_PIXFMT_YCBCR_16BIT) {
- pr_debug("Invalid pix_fmt(input mode)\n");
- return -EINVAL;
- }
- break;
-
- default:
- /* should never come here */
- pr_debug("Invalid interface type\n");
- return -EINVAL;
- }
- isif_write(isif->isif_cfg.base_addr, modeset, MODESET);
- /* Set up pix order */
- ccdcfg |= (params->pix_order & ISIF_PIX_ORDER_MASK) <<
- ISIF_PIX_ORDER_SHIFT;
- isif_write(isif->isif_cfg.base_addr, ccdcfg, CCDCFG);
- /* configure video window */
- if (format->code == MEDIA_BUS_FMT_YUYV10_1X20 ||
- format->code == MEDIA_BUS_FMT_YUYV8_1X16)
- isif_setwin(isif, &params->win, params->frm_fmt, 1, mode);
- else
- isif_setwin(isif, &params->win, params->frm_fmt, 2, mode);
-
- /*
- * configure the horizontal line offset
- * this is done by rounding up width to a multiple of 16 pixels
- * and multiply by two to account for y:cb:cr 4:2:2 data
- */
- isif_write(isif->isif_cfg.base_addr,
- ((((params->win.width * 2) + 31) &
- DM365_ISIF_HSIZE_MASK) >> 5), HSIZE);
-
- /* configure the memory line offset */
- if (params->frm_fmt == ISIF_FRMFMT_INTERLACED &&
- params->buf_type == ISIF_BUFTYPE_FLD_INTERLEAVED)
- /* two fields are interleaved in memory */
- isif_write(isif->isif_cfg.base_addr,
- DM365_ISIF_SDOFST_2_LINES, SDOFST);
- return 0;
-}
-
-static int isif_configure(struct v4l2_subdev *sd, int mode)
-{
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt *format;
-
- format = &isif->formats[ISIF_PAD_SINK];
-
- switch (format->code) {
- case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8:
- case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8:
- case MEDIA_BUS_FMT_SGRBG12_1X12:
- return isif_config_raw(sd, mode);
-
- case MEDIA_BUS_FMT_YUYV8_2X8:
- case MEDIA_BUS_FMT_UYVY8_2X8:
- case MEDIA_BUS_FMT_YUYV10_2X10:
- case MEDIA_BUS_FMT_Y8_1X8:
- case MEDIA_BUS_FMT_YUYV8_1X16:
- case MEDIA_BUS_FMT_YUYV10_1X20:
- return isif_config_ycbcr(sd, mode);
-
- default:
- break;
- }
- return -EINVAL;
-}
-
-/*
- * isif_set_stream() - Enable/Disable streaming on the ISIF module
- * @sd: VPFE ISIF V4L2 subdevice
- * @enable: Enable/disable stream
- */
-static int isif_set_stream(struct v4l2_subdev *sd, int enable)
-{
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
- int ret;
-
- if (enable) {
- ret = isif_configure(sd,
- (isif->output == ISIF_OUTPUT_MEMORY) ? 0 : 1);
- if (ret)
- return ret;
- if (isif->output == ISIF_OUTPUT_MEMORY)
- isif_enable_output_to_sdram(isif, 1);
- isif_enable(isif, 1);
- } else {
- isif_enable(isif, 0);
- isif_enable_output_to_sdram(isif, 0);
- }
-
- return 0;
-}
-
-/*
- * __isif_get_format() - helper function for getting isif format
- * @isif: pointer to isif private structure.
- * @pad: pad number.
- * @cfg: V4L2 subdev pad config
- * @which: wanted subdev format.
- */
-static struct v4l2_mbus_framefmt *
-__isif_get_format(struct vpfe_isif_device *isif,
- struct v4l2_subdev_pad_config *cfg, unsigned int pad,
- enum v4l2_subdev_format_whence which)
-{
- if (which == V4L2_SUBDEV_FORMAT_TRY)
- return v4l2_subdev_get_try_format(&isif->subdev, cfg, pad);
-
- return &isif->formats[pad];
-}
-
-/*
- * isif_set_format() - set format on pad
- * @sd : VPFE ISIF device
- * @cfg : V4L2 subdev pad config
- * @fmt : pointer to v4l2 subdev format structure
- *
- * Return 0 on success or -EINVAL if format or pad is invalid
- */
-static int
-isif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
- struct vpfe_device *vpfe_dev = to_vpfe_device(isif);
- struct v4l2_mbus_framefmt *format;
-
- format = __isif_get_format(isif, cfg, fmt->pad, fmt->which);
- if (format == NULL)
- return -EINVAL;
-
- isif_try_format(isif, cfg, fmt);
- memcpy(format, &fmt->format, sizeof(*format));
-
- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
- return 0;
-
- if (fmt->pad == ISIF_PAD_SOURCE)
- return isif_config_format(vpfe_dev, fmt->pad);
-
- return 0;
-}
-
-/*
- * isif_get_format() - Retrieve the video format on a pad
- * @sd: VPFE ISIF V4L2 subdevice
- * @cfg: V4L2 subdev pad config
- * @fmt: pointer to v4l2 subdev format structure
- *
- * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond
- * to the format type.
- */
-static int
-isif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt *format;
-
- format = __isif_get_format(vpfe_isif, cfg, fmt->pad, fmt->which);
- if (format == NULL)
- return -EINVAL;
-
- memcpy(&fmt->format, format, sizeof(fmt->format));
-
- return 0;
-}
-
-/*
- * isif_enum_frame_size() - enum frame sizes on pads
- * @sd: VPFE isif V4L2 subdevice
- * @cfg: V4L2 subdev pad config
- * @code: pointer to v4l2_subdev_frame_size_enum structure
- */
-static int
-isif_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_frame_size_enum *fse)
-{
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
- struct v4l2_subdev_format format;
-
- if (fse->index != 0)
- return -EINVAL;
-
- format.pad = fse->pad;
- format.format.code = fse->code;
- format.format.width = 1;
- format.format.height = 1;
- format.which = fse->which;
- isif_try_format(isif, cfg, &format);
- fse->min_width = format.format.width;
- fse->min_height = format.format.height;
-
- if (format.format.code != fse->code)
- return -EINVAL;
-
- format.pad = fse->pad;
- format.format.code = fse->code;
- format.format.width = -1;
- format.format.height = -1;
- format.which = fse->which;
- isif_try_format(isif, cfg, &format);
- fse->max_width = format.format.width;
- fse->max_height = format.format.height;
-
- return 0;
-}
-
-/*
- * isif_enum_mbus_code() - enum mbus codes for pads
- * @sd: VPFE isif V4L2 subdevice
- * @cfg: V4L2 subdev pad config
- * @code: pointer to v4l2_subdev_mbus_code_enum structure
- */
-static int
-isif_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_mbus_code_enum *code)
-{
- switch (code->pad) {
- case ISIF_PAD_SINK:
- case ISIF_PAD_SOURCE:
- if (code->index >= ARRAY_SIZE(isif_fmts))
- return -EINVAL;
- code->code = isif_fmts[code->index];
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-
-/*
- * isif_pad_set_selection() - set crop rectangle on pad
- * @sd: VPFE isif V4L2 subdevice
- * @cfg: V4L2 subdev pad config
- * @code: pointer to v4l2_subdev_mbus_code_enum structure
- *
- * Return 0 on success, -EINVAL if pad is invalid
- */
-static int
-isif_pad_set_selection(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_selection *sel)
-{
- struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt *format;
-
- /* check whether it's a valid pad and target */
- if (sel->pad != ISIF_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP)
- return -EINVAL;
-
- format = __isif_get_format(vpfe_isif, cfg, sel->pad, sel->which);
- if (format == NULL)
- return -EINVAL;
-
- /* check wether crop rect is within limits */
- if (sel->r.top < 0 || sel->r.left < 0 ||
- (sel->r.left + sel->r.width >
- vpfe_isif->formats[ISIF_PAD_SINK].width) ||
- (sel->r.top + sel->r.height >
- vpfe_isif->formats[ISIF_PAD_SINK].height)) {
- sel->r.left = 0;
- sel->r.top = 0;
- sel->r.width = format->width;
- sel->r.height = format->height;
- }
- /* adjust the width to 16 pixel boundary */
- sel->r.width = (sel->r.width + 15) & ~0xf;
- vpfe_isif->crop = sel->r;
- if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
- isif_set_image_window(vpfe_isif);
- } else {
- struct v4l2_rect *rect;
-
- rect = v4l2_subdev_get_try_crop(sd, cfg, ISIF_PAD_SINK);
- memcpy(rect, &vpfe_isif->crop, sizeof(*rect));
- }
- return 0;
-}
-
-/*
- * isif_pad_get_selection() - get crop rectangle on pad
- * @sd: VPFE isif V4L2 subdevice
- * @cfg: V4L2 subdev pad config
- * @code: pointer to v4l2_subdev_mbus_code_enum structure
- *
- * Return 0 on success, -EINVAL if pad is invalid
- */
-static int
-isif_pad_get_selection(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_selection *sel)
-{
- struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd);
-
- /* check whether it's a valid pad and target */
- if (sel->pad != ISIF_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP)
- return -EINVAL;
-
- if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
- struct v4l2_rect *rect;
-
- rect = v4l2_subdev_get_try_crop(sd, cfg, ISIF_PAD_SINK);
- memcpy(&sel->r, rect, sizeof(*rect));
- } else {
- sel->r = vpfe_isif->crop;
- }
-
- return 0;
-}
-
-/*
- * isif_init_formats() - Initialize formats on all pads
- * @sd: VPFE isif V4L2 subdevice
- * @fh: V4L2 subdev file handle
- *
- * Initialize all pad formats with default values. Try formats are initialized
- * on the file handle.
- */
-static int
-isif_init_formats(struct v4l2_subdev *sd,
- struct v4l2_subdev_fh *fh)
-{
- struct v4l2_subdev_format format;
- struct v4l2_subdev_selection sel;
-
- memset(&format, 0, sizeof(format));
- format.pad = ISIF_PAD_SINK;
- format.which = V4L2_SUBDEV_FORMAT_TRY;
- format.format.code = MEDIA_BUS_FMT_SGRBG12_1X12;
- format.format.width = MAX_WIDTH;
- format.format.height = MAX_HEIGHT;
- isif_set_format(sd, fh->pad, &format);
-
- memset(&format, 0, sizeof(format));
- format.pad = ISIF_PAD_SOURCE;
- format.which = V4L2_SUBDEV_FORMAT_TRY;
- format.format.code = MEDIA_BUS_FMT_SGRBG12_1X12;
- format.format.width = MAX_WIDTH;
- format.format.height = MAX_HEIGHT;
- isif_set_format(sd, fh->pad, &format);
-
- memset(&sel, 0, sizeof(sel));
- sel.pad = ISIF_PAD_SINK;
- sel.which = V4L2_SUBDEV_FORMAT_TRY;
- sel.target = V4L2_SEL_TGT_CROP;
- sel.r.width = MAX_WIDTH;
- sel.r.height = MAX_HEIGHT;
- isif_pad_set_selection(sd, fh->pad, &sel);
-
- return 0;
-}
-
-/* subdev core operations */
-static const struct v4l2_subdev_core_ops isif_v4l2_core_ops = {
- .ioctl = isif_ioctl,
-};
-
-/* subdev file operations */
-static const struct v4l2_subdev_internal_ops isif_v4l2_internal_ops = {
- .open = isif_init_formats,
-};
-
-/* subdev video operations */
-static const struct v4l2_subdev_video_ops isif_v4l2_video_ops = {
- .s_stream = isif_set_stream,
-};
-
-/* subdev pad operations */
-static const struct v4l2_subdev_pad_ops isif_v4l2_pad_ops = {
- .enum_mbus_code = isif_enum_mbus_code,
- .enum_frame_size = isif_enum_frame_size,
- .get_fmt = isif_get_format,
- .set_fmt = isif_set_format,
- .set_selection = isif_pad_set_selection,
- .get_selection = isif_pad_get_selection,
-};
-
-/* subdev operations */
-static const struct v4l2_subdev_ops isif_v4l2_ops = {
- .core = &isif_v4l2_core_ops,
- .video = &isif_v4l2_video_ops,
- .pad = &isif_v4l2_pad_ops,
-};
-
-/*
- * Media entity operations
- */
-
-/*
- * isif_link_setup() - Setup isif connections
- * @entity: isif media entity
- * @local: Pad at the local end of the link
- * @remote: Pad at the remote end of the link
- * @flags: Link flags
- *
- * return -EINVAL or zero on success
- */
-static int
-isif_link_setup(struct media_entity *entity, const struct media_pad *local,
- const struct media_pad *remote, u32 flags)
-{
- struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
- struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
- unsigned int index = local->index;
-
- /* FIXME: this is actually a hack! */
- if (is_media_entity_v4l2_subdev(remote->entity))
- index |= 2 << 16;
-
- switch (index) {
- case ISIF_PAD_SINK | 2 << 16:
- /* read from decoder/sensor */
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- isif->input = ISIF_INPUT_NONE;
- break;
- }
- if (isif->input != ISIF_INPUT_NONE)
- return -EBUSY;
- isif->input = ISIF_INPUT_PARALLEL;
- break;
-
- case ISIF_PAD_SOURCE:
- /* write to memory */
- if (flags & MEDIA_LNK_FL_ENABLED)
- isif->output = ISIF_OUTPUT_MEMORY;
- else
- isif->output = ISIF_OUTPUT_NONE;
- break;
-
- case ISIF_PAD_SOURCE | 2 << 16:
- if (flags & MEDIA_LNK_FL_ENABLED)
- isif->output = ISIF_OUTPUT_IPIPEIF;
- else
- isif->output = ISIF_OUTPUT_NONE;
- break;
-
- default:
- return -EINVAL;
- }
-
- return 0;
-}
-static const struct media_entity_operations isif_media_ops = {
- .link_setup = isif_link_setup,
-};
-
-/*
- * vpfe_isif_unregister_entities() - isif unregister entity
- * @isif - pointer to isif subdevice structure.
- */
-void vpfe_isif_unregister_entities(struct vpfe_isif_device *isif)
-{
- vpfe_video_unregister(&isif->video_out);
- /* unregister subdev */
- v4l2_device_unregister_subdev(&isif->subdev);
- /* cleanup entity */
- media_entity_cleanup(&isif->subdev.entity);
-}
-
-static void isif_restore_defaults(struct vpfe_isif_device *isif)
-{
- enum vpss_ccdc_source_sel source = VPSS_CCDCIN;
- int i;
-
- memset(&isif->isif_cfg.bayer.config_params, 0,
- sizeof(struct vpfe_isif_raw_config));
-
- isif->isif_cfg.bayer.config_params.linearize.corr_shft =
- VPFE_ISIF_NO_SHIFT;
- isif->isif_cfg.bayer.config_params.linearize.scale_fact.integer = 1;
- isif->isif_cfg.bayer.config_params.culling.hcpat_odd =
- ISIF_CULLING_HCAPT_ODD;
- isif->isif_cfg.bayer.config_params.culling.hcpat_even =
- ISIF_CULLING_HCAPT_EVEN;
- isif->isif_cfg.bayer.config_params.culling.vcpat = ISIF_CULLING_VCAPT;
- /* Enable clock to ISIF, IPIPEIF and BL */
- vpss_enable_clock(VPSS_CCDC_CLOCK, 1);
- vpss_enable_clock(VPSS_IPIPEIF_CLOCK, 1);
- vpss_enable_clock(VPSS_BL_CLOCK, 1);
-
- /* set all registers to default value */
- for (i = 0; i <= 0x1f8; i += 4)
- isif_write(isif->isif_cfg.base_addr, 0, i);
- /* no culling support */
- isif_write(isif->isif_cfg.base_addr, 0xffff, CULH);
- isif_write(isif->isif_cfg.base_addr, 0xff, CULV);
-
- /* Set default offset and gain */
- isif_config_gain_offset(isif);
- vpss_select_ccdc_source(source);
-}
-
-/*
- * vpfe_isif_register_entities() - isif register entity
- * @isif - pointer to isif subdevice structure.
- * @vdev: pointer to v4l2 device structure.
- */
-int vpfe_isif_register_entities(struct vpfe_isif_device *isif,
- struct v4l2_device *vdev)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(isif);
- unsigned int flags;
- int ret;
-
- /* Register the subdev */
- ret = v4l2_device_register_subdev(vdev, &isif->subdev);
- if (ret < 0)
- return ret;
-
- isif_restore_defaults(isif);
- ret = vpfe_video_register(&isif->video_out, vdev);
- if (ret) {
- pr_err("Failed to register isif video out device\n");
- goto out_video_register;
- }
- isif->video_out.vpfe_dev = vpfe_dev;
- flags = 0;
- /* connect isif to video node */
- ret = media_create_pad_link(&isif->subdev.entity, 1,
- &isif->video_out.video_dev.entity,
- 0, flags);
- if (ret < 0)
- goto out_create_link;
- return 0;
-out_create_link:
- vpfe_video_unregister(&isif->video_out);
-out_video_register:
- v4l2_device_unregister_subdev(&isif->subdev);
- return ret;
-}
-
-/* -------------------------------------------------------------------
- * V4L2 subdev control operations
- */
-
-static int vpfe_isif_s_ctrl(struct v4l2_ctrl *ctrl)
-{
- struct vpfe_isif_device *isif =
- container_of(ctrl->handler, struct vpfe_isif_device, ctrls);
- struct isif_oper_config *config = &isif->isif_cfg;
-
- switch (ctrl->id) {
- case VPFE_CID_DPCM_PREDICTOR:
- config->bayer.dpcm_predictor = ctrl->val;
- break;
-
- case VPFE_ISIF_CID_CRGAIN:
- config->isif_gain_params.cr_gain = ctrl->val;
- break;
-
- case VPFE_ISIF_CID_CGRGAIN:
- config->isif_gain_params.cgr_gain = ctrl->val;
- break;
-
- case VPFE_ISIF_CID_CGBGAIN:
- config->isif_gain_params.cgb_gain = ctrl->val;
- break;
-
- case VPFE_ISIF_CID_CBGAIN:
- config->isif_gain_params.cb_gain = ctrl->val;
- break;
-
- case VPFE_ISIF_CID_GAIN_OFFSET:
- config->isif_gain_params.offset = ctrl->val;
- break;
-
- default:
- return -EINVAL;
- }
- return 0;
-}
-
-static const struct v4l2_ctrl_ops vpfe_isif_ctrl_ops = {
- .s_ctrl = vpfe_isif_s_ctrl,
-};
-
-static const struct v4l2_ctrl_config vpfe_isif_dpcm_pred = {
- .ops = &vpfe_isif_ctrl_ops,
- .id = VPFE_CID_DPCM_PREDICTOR,
- .name = "DPCM Predictor",
- .type = V4L2_CTRL_TYPE_INTEGER,
- .min = 0,
- .max = 1,
- .step = 1,
- .def = 0,
-};
-
-static const struct v4l2_ctrl_config vpfe_isif_crgain = {
- .ops = &vpfe_isif_ctrl_ops,
- .id = VPFE_ISIF_CID_CRGAIN,
- .name = "CRGAIN",
- .type = V4L2_CTRL_TYPE_INTEGER,
- .min = 0,
- .max = (1 << 12) - 1,
- .step = 1,
- .def = 0,
-};
-
-static const struct v4l2_ctrl_config vpfe_isif_cgrgain = {
- .ops = &vpfe_isif_ctrl_ops,
- .id = VPFE_ISIF_CID_CGRGAIN,
- .name = "CGRGAIN",
- .type = V4L2_CTRL_TYPE_INTEGER,
- .min = 0,
- .max = (1 << 12) - 1,
- .step = 1,
- .def = 0,
-};
-
-static const struct v4l2_ctrl_config vpfe_isif_cgbgain = {
- .ops = &vpfe_isif_ctrl_ops,
- .id = VPFE_ISIF_CID_CGBGAIN,
- .name = "CGBGAIN",
- .type = V4L2_CTRL_TYPE_INTEGER,
- .min = 0,
- .max = (1 << 12) - 1,
- .step = 1,
- .def = 0,
-};
-
-static const struct v4l2_ctrl_config vpfe_isif_cbgain = {
- .ops = &vpfe_isif_ctrl_ops,
- .id = VPFE_ISIF_CID_CBGAIN,
- .name = "CBGAIN",
- .type = V4L2_CTRL_TYPE_INTEGER,
- .min = 0,
- .max = (1 << 12) - 1,
- .step = 1,
- .def = 0,
-};
-
-static const struct v4l2_ctrl_config vpfe_isif_gain_offset = {
- .ops = &vpfe_isif_ctrl_ops,
- .id = VPFE_ISIF_CID_GAIN_OFFSET,
- .name = "Gain Offset",
- .type = V4L2_CTRL_TYPE_INTEGER,
- .min = 0,
- .max = (1 << 12) - 1,
- .step = 1,
- .def = 0,
-};
-
-static void isif_remove(struct vpfe_isif_device *isif,
- struct platform_device *pdev)
-{
- struct resource *res;
- int i = 0;
-
- iounmap(isif->isif_cfg.base_addr);
- iounmap(isif->isif_cfg.linear_tbl0_addr);
- iounmap(isif->isif_cfg.linear_tbl1_addr);
-
- while (i < 3) {
- res = platform_get_resource(pdev, IORESOURCE_MEM, i);
- if (res)
- release_mem_region(res->start,
- resource_size(res));
- i++;
- }
-}
-
-static void isif_config_defaults(struct vpfe_isif_device *isif)
-{
- isif->isif_cfg.ycbcr.v4l2_pix_fmt = V4L2_PIX_FMT_UYVY;
- isif->isif_cfg.ycbcr.pix_fmt = ISIF_PIXFMT_YCBCR_8BIT;
- isif->isif_cfg.ycbcr.frm_fmt = ISIF_FRMFMT_INTERLACED;
- isif->isif_cfg.ycbcr.fid_pol = VPFE_PINPOL_POSITIVE;
- isif->isif_cfg.ycbcr.vd_pol = VPFE_PINPOL_POSITIVE;
- isif->isif_cfg.ycbcr.hd_pol = VPFE_PINPOL_POSITIVE;
- isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_CBYCRY;
- isif->isif_cfg.ycbcr.buf_type = ISIF_BUFTYPE_FLD_INTERLEAVED;
-
- isif->isif_cfg.bayer.v4l2_pix_fmt = V4L2_PIX_FMT_SGRBG10ALAW8;
- isif->isif_cfg.bayer.pix_fmt = ISIF_PIXFMT_RAW;
- isif->isif_cfg.bayer.frm_fmt = ISIF_FRMFMT_PROGRESSIVE;
- isif->isif_cfg.bayer.fid_pol = VPFE_PINPOL_POSITIVE;
- isif->isif_cfg.bayer.vd_pol = VPFE_PINPOL_POSITIVE;
- isif->isif_cfg.bayer.hd_pol = VPFE_PINPOL_POSITIVE;
- isif->isif_cfg.bayer.cfa_pat = ISIF_CFA_PAT_MOSAIC;
- isif->isif_cfg.bayer.data_msb = ISIF_BIT_MSB_11;
- isif->isif_cfg.data_pack = ISIF_PACK_8BIT;
-}
-/*
- * vpfe_isif_init() - Initialize V4L2 subdev and media entity
- * @isif: VPFE isif module
- * @pdev: Pointer to platform device structure.
- * Return 0 on success and a negative error code on failure.
- */
-int vpfe_isif_init(struct vpfe_isif_device *isif, struct platform_device *pdev)
-{
- struct v4l2_subdev *sd = &isif->subdev;
- struct media_pad *pads = &isif->pads[0];
- struct media_entity *me = &sd->entity;
- static resource_size_t res_len;
- struct resource *res;
- void __iomem *addr;
- int status;
- int i = 0;
-
- /* Get the ISIF base address, linearization table0 and table1 addr. */
- while (i < 3) {
- res = platform_get_resource(pdev, IORESOURCE_MEM, i);
- if (!res) {
- status = -ENOENT;
- goto fail_nobase_res;
- }
- res_len = resource_size(res);
- res = request_mem_region(res->start, res_len, res->name);
- if (!res) {
- status = -EBUSY;
- goto fail_nobase_res;
- }
- addr = ioremap_nocache(res->start, res_len);
- if (!addr) {
- status = -EBUSY;
- goto fail_base_iomap;
- }
- switch (i) {
- case 0:
- /* ISIF base address */
- isif->isif_cfg.base_addr = addr;
- break;
- case 1:
- /* ISIF linear tbl0 address */
- isif->isif_cfg.linear_tbl0_addr = addr;
- break;
- default:
- /* ISIF linear tbl0 address */
- isif->isif_cfg.linear_tbl1_addr = addr;
- break;
- }
- i++;
- }
- davinci_cfg_reg(DM365_VIN_CAM_WEN);
- davinci_cfg_reg(DM365_VIN_CAM_VD);
- davinci_cfg_reg(DM365_VIN_CAM_HD);
- davinci_cfg_reg(DM365_VIN_YIN4_7_EN);
- davinci_cfg_reg(DM365_VIN_YIN0_3_EN);
-
- /* queue ops */
- isif->video_out.ops = &isif_video_ops;
- v4l2_subdev_init(sd, &isif_v4l2_ops);
- sd->internal_ops = &isif_v4l2_internal_ops;
- strscpy(sd->name, "DAVINCI ISIF", sizeof(sd->name));
- sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
- v4l2_set_subdevdata(sd, isif);
- sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
- pads[ISIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- pads[ISIF_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
-
- isif->input = ISIF_INPUT_NONE;
- isif->output = ISIF_OUTPUT_NONE;
- me->ops = &isif_media_ops;
- status = media_entity_pads_init(me, ISIF_PADS_NUM, pads);
- if (status)
- goto isif_fail;
- isif->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- status = vpfe_video_init(&isif->video_out, "ISIF");
- if (status) {
- pr_err("Failed to init isif-out video device\n");
- goto isif_fail;
- }
- v4l2_ctrl_handler_init(&isif->ctrls, 6);
- v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_crgain, NULL);
- v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cgrgain, NULL);
- v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cgbgain, NULL);
- v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cbgain, NULL);
- v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_gain_offset, NULL);
- v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_dpcm_pred, NULL);
-
- v4l2_ctrl_handler_setup(&isif->ctrls);
- sd->ctrl_handler = &isif->ctrls;
- isif_config_defaults(isif);
- return 0;
-fail_base_iomap:
- release_mem_region(res->start, res_len);
- i--;
-fail_nobase_res:
- if (isif->isif_cfg.base_addr)
- iounmap(isif->isif_cfg.base_addr);
- if (isif->isif_cfg.linear_tbl0_addr)
- iounmap(isif->isif_cfg.linear_tbl0_addr);
-
- while (i >= 0) {
- res = platform_get_resource(pdev, IORESOURCE_MEM, i);
- release_mem_region(res->start, res_len);
- i--;
- }
- return status;
-isif_fail:
- v4l2_ctrl_handler_free(&isif->ctrls);
- isif_remove(isif, pdev);
- return status;
-}
-
-/*
- * vpfe_isif_cleanup - isif module cleanup
- * @isif: pointer to isif subdevice
- * @dev: pointer to platform device structure
- */
-void
-vpfe_isif_cleanup(struct vpfe_isif_device *isif, struct platform_device *pdev)
-{
- isif_remove(isif, pdev);
-}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif.h b/drivers/staging/media/davinci_vpfe/dm365_isif.h
deleted file mode 100644
index 0e1fe472fb2b..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_isif.h
+++ /dev/null
@@ -1,200 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_DM365_ISIF_H
-#define _DAVINCI_VPFE_DM365_ISIF_H
-
-#include <linux/platform_device.h>
-
-#include <mach/mux.h>
-
-#include <media/davinci/vpfe_types.h>
-#include <media/v4l2-ctrls.h>
-#include <media/v4l2-device.h>
-
-#include "davinci_vpfe_user.h"
-#include "dm365_isif_regs.h"
-#include "vpfe_video.h"
-
-#define ISIF_CULLING_HCAPT_ODD 0xff
-#define ISIF_CULLING_HCAPT_EVEN 0xff
-#define ISIF_CULLING_VCAPT 0xff
-
-#define ISIF_CADU_BITS 0x07ff
-#define ISIF_CADL_BITS 0x0ffff
-
-enum isif_pixfmt {
- ISIF_PIXFMT_RAW = 0,
- ISIF_PIXFMT_YCBCR_16BIT = 1,
- ISIF_PIXFMT_YCBCR_8BIT = 2,
-};
-
-enum isif_frmfmt {
- ISIF_FRMFMT_PROGRESSIVE = 0,
- ISIF_FRMFMT_INTERLACED = 1,
-};
-
-/* PIXEL ORDER IN MEMORY from LSB to MSB */
-/* only applicable for 8-bit input mode */
-enum isif_pixorder {
- ISIF_PIXORDER_YCBYCR = 0,
- ISIF_PIXORDER_CBYCRY = 1,
-};
-
-enum isif_buftype {
- ISIF_BUFTYPE_FLD_INTERLEAVED = 0,
- ISIF_BUFTYPE_FLD_SEPARATED = 1,
-};
-
-struct isif_ycbcr_config {
- /* v4l2 pixel format */
- unsigned long v4l2_pix_fmt;
- /* isif pixel format */
- enum isif_pixfmt pix_fmt;
- /* isif frame format */
- enum isif_frmfmt frm_fmt;
- /* isif crop window */
- struct v4l2_rect win;
- /* field polarity */
- enum vpfe_pin_pol fid_pol;
- /* interface VD polarity */
- enum vpfe_pin_pol vd_pol;
- /* interface HD polarity */
- enum vpfe_pin_pol hd_pol;
- /* isif pix order. Only used for ycbcr capture */
- enum isif_pixorder pix_order;
- /* isif buffer type. Only used for ycbcr capture */
- enum isif_buftype buf_type;
-};
-
-enum isif_cfa_pattern {
- ISIF_CFA_PAT_MOSAIC = 0,
- ISIF_CFA_PAT_STRIPE = 1,
-};
-
-enum isif_data_msb {
- /* MSB b15 */
- ISIF_BIT_MSB_15 = 0,
- /* MSB b14 */
- ISIF_BIT_MSB_14 = 1,
- /* MSB b13 */
- ISIF_BIT_MSB_13 = 2,
- /* MSB b12 */
- ISIF_BIT_MSB_12 = 3,
- /* MSB b11 */
- ISIF_BIT_MSB_11 = 4,
- /* MSB b10 */
- ISIF_BIT_MSB_10 = 5,
- /* MSB b9 */
- ISIF_BIT_MSB_9 = 6,
- /* MSB b8 */
- ISIF_BIT_MSB_8 = 7,
- /* MSB b7 */
- ISIF_BIT_MSB_7 = 8,
-};
-
-struct isif_params_raw {
- /* v4l2 pixel format */
- unsigned long v4l2_pix_fmt;
- /* isif pixel format */
- enum isif_pixfmt pix_fmt;
- /* isif frame format */
- enum isif_frmfmt frm_fmt;
- /* video window */
- struct v4l2_rect win;
- /* field polarity */
- enum vpfe_pin_pol fid_pol;
- /* interface VD polarity */
- enum vpfe_pin_pol vd_pol;
- /* interface HD polarity */
- enum vpfe_pin_pol hd_pol;
- /* buffer type. Applicable for interlaced mode */
- enum isif_buftype buf_type;
- /* cfa pattern */
- enum isif_cfa_pattern cfa_pat;
- /* Data MSB position */
- enum isif_data_msb data_msb;
- /* Enable horizontal flip */
- unsigned char horz_flip_en;
- /* Enable image invert vertically */
- unsigned char image_invert_en;
- unsigned char dpcm_predictor;
- struct vpfe_isif_raw_config config_params;
-};
-
-enum isif_data_pack {
- ISIF_PACK_16BIT = 0,
- ISIF_PACK_12BIT = 1,
- ISIF_PACK_8BIT = 2,
-};
-
-struct isif_gain_values {
- unsigned int cr_gain;
- unsigned int cgr_gain;
- unsigned int cgb_gain;
- unsigned int cb_gain;
- unsigned int offset;
-};
-
-struct isif_oper_config {
- struct isif_ycbcr_config ycbcr;
- struct isif_params_raw bayer;
- enum isif_data_pack data_pack;
- struct isif_gain_values isif_gain_params;
- void __iomem *base_addr;
- void __iomem *linear_tbl0_addr;
- void __iomem *linear_tbl1_addr;
-};
-
-#define ISIF_PAD_SINK 0
-#define ISIF_PAD_SOURCE 1
-
-#define ISIF_PADS_NUM 2
-
-enum isif_input_entity {
- ISIF_INPUT_NONE = 0,
- ISIF_INPUT_PARALLEL = 1,
-};
-
-#define ISIF_OUTPUT_NONE (0)
-#define ISIF_OUTPUT_MEMORY (1 << 0)
-#define ISIF_OUTPUT_IPIPEIF (1 << 1)
-
-struct vpfe_isif_device {
- struct v4l2_subdev subdev;
- struct media_pad pads[ISIF_PADS_NUM];
- struct v4l2_mbus_framefmt formats[ISIF_PADS_NUM];
- enum isif_input_entity input;
- unsigned int output;
- struct v4l2_ctrl_handler ctrls;
- struct v4l2_rect crop;
- struct isif_oper_config isif_cfg;
- struct vpfe_video_device video_out;
-};
-
-enum v4l2_field vpfe_isif_get_fid(struct vpfe_device *vpfe_dev);
-void vpfe_isif_unregister_entities(struct vpfe_isif_device *isif);
-int vpfe_isif_register_entities(struct vpfe_isif_device *isif,
- struct v4l2_device *dev);
-int vpfe_isif_init(struct vpfe_isif_device *isif, struct platform_device *pdev);
-void vpfe_isif_cleanup(struct vpfe_isif_device *vpfe_isif,
- struct platform_device *pdev);
-void vpfe_isif_vidint1_isr(struct vpfe_isif_device *isif);
-void vpfe_isif_buffer_isr(struct vpfe_isif_device *isif);
-
-#endif /* _DAVINCI_VPFE_DM365_ISIF_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h b/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h
deleted file mode 100644
index 6695680817b9..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h
+++ /dev/null
@@ -1,291 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_DM365_ISIF_REGS_H
-#define _DAVINCI_VPFE_DM365_ISIF_REGS_H
-
-/* ISIF registers relative offsets */
-#define SYNCEN 0x00
-#define MODESET 0x04
-#define HDW 0x08
-#define VDW 0x0c
-#define PPLN 0x10
-#define LPFR 0x14
-#define SPH 0x18
-#define LNH 0x1c
-#define SLV0 0x20
-#define SLV1 0x24
-#define LNV 0x28
-#define CULH 0x2c
-#define CULV 0x30
-#define HSIZE 0x34
-#define SDOFST 0x38
-#define CADU 0x3c
-#define CADL 0x40
-#define LINCFG0 0x44
-#define LINCFG1 0x48
-#define CCOLP 0x4c
-#define CRGAIN 0x50
-#define CGRGAIN 0x54
-#define CGBGAIN 0x58
-#define CBGAIN 0x5c
-#define COFSTA 0x60
-#define FLSHCFG0 0x64
-#define FLSHCFG1 0x68
-#define FLSHCFG2 0x6c
-#define VDINT0 0x70
-#define VDINT1 0x74
-#define VDINT2 0x78
-#define MISC 0x7c
-#define CGAMMAWD 0x80
-#define REC656IF 0x84
-#define CCDCFG 0x88
-/*****************************************************
- * Defect Correction registers
- *****************************************************/
-#define DFCCTL 0x8c
-#define VDFSATLV 0x90
-#define DFCMEMCTL 0x94
-#define DFCMEM0 0x98
-#define DFCMEM1 0x9c
-#define DFCMEM2 0xa0
-#define DFCMEM3 0xa4
-#define DFCMEM4 0xa8
-/****************************************************
- * Black Clamp registers
- ****************************************************/
-#define CLAMPCFG 0xac
-#define CLDCOFST 0xb0
-#define CLSV 0xb4
-#define CLHWIN0 0xb8
-#define CLHWIN1 0xbc
-#define CLHWIN2 0xc0
-#define CLVRV 0xc4
-#define CLVWIN0 0xc8
-#define CLVWIN1 0xcc
-#define CLVWIN2 0xd0
-#define CLVWIN3 0xd4
-/****************************************************
- * Lense Shading Correction
- ****************************************************/
-#define DATAHOFST 0xd8
-#define DATAVOFST 0xdc
-#define LSCHVAL 0xe0
-#define LSCVVAL 0xe4
-#define TWODLSCCFG 0xe8
-#define TWODLSCOFST 0xec
-#define TWODLSCINI 0xf0
-#define TWODLSCGRBU 0xf4
-#define TWODLSCGRBL 0xf8
-#define TWODLSCGROF 0xfc
-#define TWODLSCORBU 0x100
-#define TWODLSCORBL 0x104
-#define TWODLSCOROF 0x108
-#define TWODLSCIRQEN 0x10c
-#define TWODLSCIRQST 0x110
-/****************************************************
- * Data formatter
- ****************************************************/
-#define FMTCFG 0x114
-#define FMTPLEN 0x118
-#define FMTSPH 0x11c
-#define FMTLNH 0x120
-#define FMTSLV 0x124
-#define FMTLNV 0x128
-#define FMTRLEN 0x12c
-#define FMTHCNT 0x130
-#define FMTAPTR_BASE 0x134
-/* Below macro for addresses FMTAPTR0 - FMTAPTR15 */
-#define FMTAPTR(i) (FMTAPTR_BASE + (i * 4))
-#define FMTPGMVF0 0x174
-#define FMTPGMVF1 0x178
-#define FMTPGMAPU0 0x17c
-#define FMTPGMAPU1 0x180
-#define FMTPGMAPS0 0x184
-#define FMTPGMAPS1 0x188
-#define FMTPGMAPS2 0x18c
-#define FMTPGMAPS3 0x190
-#define FMTPGMAPS4 0x194
-#define FMTPGMAPS5 0x198
-#define FMTPGMAPS6 0x19c
-#define FMTPGMAPS7 0x1a0
-/************************************************
- * Color Space Converter
- ************************************************/
-#define CSCCTL 0x1a4
-#define CSCM0 0x1a8
-#define CSCM1 0x1ac
-#define CSCM2 0x1b0
-#define CSCM3 0x1b4
-#define CSCM4 0x1b8
-#define CSCM5 0x1bc
-#define CSCM6 0x1c0
-#define CSCM7 0x1c4
-#define OBWIN0 0x1c8
-#define OBWIN1 0x1cc
-#define OBWIN2 0x1d0
-#define OBWIN3 0x1d4
-#define OBVAL0 0x1d8
-#define OBVAL1 0x1dc
-#define OBVAL2 0x1e0
-#define OBVAL3 0x1e4
-#define OBVAL4 0x1e8
-#define OBVAL5 0x1ec
-#define OBVAL6 0x1f0
-#define OBVAL7 0x1f4
-#define CLKCTL 0x1f8
-
-/* Masks & Shifts below */
-#define START_PX_HOR_MASK 0x7fff
-#define NUM_PX_HOR_MASK 0x7fff
-#define START_VER_ONE_MASK 0x7fff
-#define START_VER_TWO_MASK 0x7fff
-#define NUM_LINES_VER 0x7fff
-
-/* gain - offset masks */
-#define OFFSET_MASK 0xfff
-#define GAIN_SDRAM_EN_SHIFT 12
-#define GAIN_IPIPE_EN_SHIFT 13
-#define GAIN_H3A_EN_SHIFT 14
-#define OFST_SDRAM_EN_SHIFT 8
-#define OFST_IPIPE_EN_SHIFT 9
-#define OFST_H3A_EN_SHIFT 10
-#define GAIN_OFFSET_EN_MASK 0x7700
-
-/* Culling */
-#define CULL_PAT_EVEN_LINE_SHIFT 8
-
-/* CCDCFG register */
-#define ISIF_YCINSWP_RAW (0x00 << 4)
-#define ISIF_YCINSWP_YCBCR (0x01 << 4)
-#define ISIF_CCDCFG_FIDMD_LATCH_VSYNC (0x00 << 6)
-#define ISIF_CCDCFG_WENLOG_AND (0x00 << 8)
-#define ISIF_CCDCFG_TRGSEL_WEN (0x00 << 9)
-#define ISIF_CCDCFG_EXTRG_DISABLE (0x00 << 10)
-#define ISIF_LATCH_ON_VSYNC_DISABLE (0x01 << 15)
-#define ISIF_LATCH_ON_VSYNC_ENABLE (0x00 << 15)
-#define ISIF_DATA_PACK_MASK 0x03
-#define ISIF_PIX_ORDER_SHIFT 11
-#define ISIF_PIX_ORDER_MASK 0x01
-#define ISIF_BW656_ENABLE (0x01 << 5)
-
-/* MODESET registers */
-#define ISIF_VDHDOUT_INPUT (0x00 << 0)
-#define ISIF_INPUT_MASK 0x03
-#define ISIF_INPUT_SHIFT 12
-#define ISIF_FID_POL_MASK 0x01
-#define ISIF_FID_POL_SHIFT 4
-#define ISIF_HD_POL_MASK 0x01
-#define ISIF_HD_POL_SHIFT 3
-#define ISIF_VD_POL_MASK 0x01
-#define ISIF_VD_POL_SHIFT 2
-#define ISIF_DATAPOL_NORMAL 0x00
-#define ISIF_DATAPOL_MASK 0x01
-#define ISIF_DATAPOL_SHIFT 6
-#define ISIF_EXWEN_DISABLE 0x00
-#define ISIF_EXWEN_MASK 0x01
-#define ISIF_EXWEN_SHIFT 5
-#define ISIF_FRM_FMT_MASK 0x01
-#define ISIF_FRM_FMT_SHIFT 7
-#define ISIF_DATASFT_MASK 0x07
-#define ISIF_DATASFT_SHIFT 8
-#define ISIF_LPF_SHIFT 14
-#define ISIF_LPF_MASK 0x1
-
-/* GAMMAWD registers */
-#define ISIF_ALAW_GAMA_WD_MASK 0xf
-#define ISIF_ALAW_GAMA_WD_SHIFT 1
-#define ISIF_ALAW_ENABLE 0x01
-#define ISIF_GAMMAWD_CFA_MASK 0x01
-#define ISIF_GAMMAWD_CFA_SHIFT 5
-
-/* HSIZE registers */
-#define ISIF_HSIZE_FLIP_MASK 0x01
-#define ISIF_HSIZE_FLIP_SHIFT 12
-#define ISIF_LINEOFST_MASK 0xfff
-
-/* MISC registers */
-#define ISIF_DPCM_EN_SHIFT 12
-#define ISIF_DPCM_PREDICTOR_SHIFT 13
-#define ISIF_DPCM_PREDICTOR_MASK 1
-
-/* Black clamp related */
-#define ISIF_BC_DCOFFSET_MASK 0x1fff
-#define ISIF_BC_MODE_COLOR_MASK 1
-#define ISIF_BC_MODE_COLOR_SHIFT 4
-#define ISIF_HORZ_BC_MODE_MASK 3
-#define ISIF_HORZ_BC_MODE_SHIFT 1
-#define ISIF_HORZ_BC_WIN_COUNT_MASK 0x1f
-#define ISIF_HORZ_BC_WIN_SEL_SHIFT 5
-#define ISIF_HORZ_BC_PIX_LIMIT_SHIFT 6
-#define ISIF_HORZ_BC_WIN_H_SIZE_MASK 3
-#define ISIF_HORZ_BC_WIN_H_SIZE_SHIFT 8
-#define ISIF_HORZ_BC_WIN_V_SIZE_MASK 3
-#define ISIF_HORZ_BC_WIN_V_SIZE_SHIFT 12
-#define ISIF_HORZ_BC_WIN_START_H_MASK 0x1fff
-#define ISIF_HORZ_BC_WIN_START_V_MASK 0x1fff
-#define ISIF_VERT_BC_OB_H_SZ_MASK 7
-#define ISIF_VERT_BC_RST_VAL_SEL_MASK 3
-#define ISIF_VERT_BC_RST_VAL_SEL_SHIFT 4
-#define ISIF_VERT_BC_LINE_AVE_COEF_SHIFT 8
-#define ISIF_VERT_BC_OB_START_HORZ_MASK 0x1fff
-#define ISIF_VERT_BC_OB_START_VERT_MASK 0x1fff
-#define ISIF_VERT_BC_OB_VERT_SZ_MASK 0x1fff
-#define ISIF_VERT_BC_RST_VAL_MASK 0xfff
-#define ISIF_BC_VERT_START_SUB_V_MASK 0x1fff
-
-/* VDFC registers */
-#define ISIF_VDFC_EN_SHIFT 4
-#define ISIF_VDFC_CORR_MOD_MASK 3
-#define ISIF_VDFC_CORR_MOD_SHIFT 5
-#define ISIF_VDFC_CORR_WHOLE_LN_SHIFT 7
-#define ISIF_VDFC_LEVEL_SHFT_MASK 7
-#define ISIF_VDFC_LEVEL_SHFT_SHIFT 8
-#define ISIF_VDFC_SAT_LEVEL_MASK 0xfff
-#define ISIF_VDFC_POS_MASK 0x1fff
-#define ISIF_DFCMEMCTL_DFCMARST_SHIFT 2
-
-/* CSC registers */
-#define ISIF_CSC_COEF_INTEG_MASK 7
-#define ISIF_CSC_COEF_DECIMAL_MASK 0x1f
-#define ISIF_CSC_COEF_INTEG_SHIFT 5
-#define ISIF_CSCM_MSB_SHIFT 8
-#define ISIF_DF_CSC_SPH_MASK 0x1fff
-#define ISIF_DF_CSC_LNH_MASK 0x1fff
-#define ISIF_DF_CSC_SLV_MASK 0x1fff
-#define ISIF_DF_CSC_LNV_MASK 0x1fff
-#define ISIF_DF_NUMLINES 0x7fff
-#define ISIF_DF_NUMPIX 0x1fff
-
-/* Offsets for LSC/DFC/Gain */
-#define ISIF_DATA_H_OFFSET_MASK 0x1fff
-#define ISIF_DATA_V_OFFSET_MASK 0x1fff
-
-/* Linearization */
-#define ISIF_LIN_CORRSFT_MASK 7
-#define ISIF_LIN_CORRSFT_SHIFT 4
-#define ISIF_LIN_SCALE_FACT_INTEG_SHIFT 10
-#define ISIF_LIN_SCALE_FACT_DECIMAL_MASK 0x3ff
-#define ISIF_LIN_ENTRY_MASK 0x3ff
-
-/* masks and shifts*/
-#define ISIF_SYNCEN_VDHDEN_MASK (1 << 0)
-#define ISIF_SYNCEN_WEN_MASK (1 << 1)
-#define ISIF_SYNCEN_WEN_SHIFT 1
-
-#endif /* _DAVINCI_VPFE_DM365_ISIF_REGS_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_resizer.c b/drivers/staging/media/davinci_vpfe/dm365_resizer.c
deleted file mode 100644
index 7adf1fae43f6..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_resizer.c
+++ /dev/null
@@ -1,1995 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- *
- *
- * Resizer allows upscaling or downscaling a image to a desired
- * resolution. There are 2 resizer modules. both operating on the
- * same input image, but can have different output resolution.
- */
-
-#include "dm365_ipipe_hw.h"
-#include "dm365_resizer.h"
-
-#define MIN_IN_WIDTH 32
-#define MIN_IN_HEIGHT 32
-#define MAX_IN_WIDTH 4095
-#define MAX_IN_HEIGHT 4095
-#define MIN_OUT_WIDTH 16
-#define MIN_OUT_HEIGHT 2
-
-static const unsigned int resizer_input_formats[] = {
- MEDIA_BUS_FMT_UYVY8_2X8,
- MEDIA_BUS_FMT_Y8_1X8,
- MEDIA_BUS_FMT_UV8_1X8,
- MEDIA_BUS_FMT_SGRBG12_1X12,
-};
-
-static const unsigned int resizer_output_formats[] = {
- MEDIA_BUS_FMT_UYVY8_2X8,
- MEDIA_BUS_FMT_Y8_1X8,
- MEDIA_BUS_FMT_UV8_1X8,
- MEDIA_BUS_FMT_YDYUYDYV8_1X16,
- MEDIA_BUS_FMT_SGRBG12_1X12,
-};
-
-/* resizer_calculate_line_length() - This function calculates the line length of
- * various image planes at the input and
- * output.
- */
-static void
-resizer_calculate_line_length(u32 pix, int width, int height,
- int *line_len, int *line_len_c)
-{
- *line_len = 0;
- *line_len_c = 0;
-
- if (pix == MEDIA_BUS_FMT_UYVY8_2X8 ||
- pix == MEDIA_BUS_FMT_SGRBG12_1X12) {
- *line_len = width << 1;
- } else {
- *line_len = width;
- *line_len_c = width;
- }
-
- /* adjust the line len to be a multiple of 32 */
- *line_len += 31;
- *line_len &= ~0x1f;
- *line_len_c += 31;
- *line_len_c &= ~0x1f;
-}
-
-static inline int
-resizer_validate_output_image_format(struct device *dev,
- struct v4l2_mbus_framefmt *format,
- int *in_line_len, int *in_line_len_c)
-{
- if (format->code != MEDIA_BUS_FMT_UYVY8_2X8 &&
- format->code != MEDIA_BUS_FMT_Y8_1X8 &&
- format->code != MEDIA_BUS_FMT_UV8_1X8 &&
- format->code != MEDIA_BUS_FMT_YDYUYDYV8_1X16 &&
- format->code != MEDIA_BUS_FMT_SGRBG12_1X12) {
- dev_err(dev, "Invalid Mbus format, %d\n", format->code);
- return -EINVAL;
- }
- if (!format->width || !format->height) {
- dev_err(dev, "invalid width or height\n");
- return -EINVAL;
- }
- resizer_calculate_line_length(format->code, format->width,
- format->height, in_line_len, in_line_len_c);
- return 0;
-}
-
-static void
-resizer_configure_passthru(struct vpfe_resizer_device *resizer, int bypass)
-{
- struct resizer_params *param = &resizer->config;
-
- param->rsz_rsc_param[RSZ_A].cen = DISABLE;
- param->rsz_rsc_param[RSZ_A].yen = DISABLE;
- param->rsz_rsc_param[RSZ_A].v_phs_y = 0;
- param->rsz_rsc_param[RSZ_A].v_phs_c = 0;
- param->rsz_rsc_param[RSZ_A].v_dif = 256;
- param->rsz_rsc_param[RSZ_A].v_lpf_int_y = 0;
- param->rsz_rsc_param[RSZ_A].v_lpf_int_c = 0;
- param->rsz_rsc_param[RSZ_A].h_phs = 0;
- param->rsz_rsc_param[RSZ_A].h_dif = 256;
- param->rsz_rsc_param[RSZ_A].h_lpf_int_y = 0;
- param->rsz_rsc_param[RSZ_A].h_lpf_int_c = 0;
- param->rsz_rsc_param[RSZ_A].dscale_en = DISABLE;
- param->rsz2rgb[RSZ_A].rgb_en = DISABLE;
- param->rsz_en[RSZ_A] = ENABLE;
- param->rsz_en[RSZ_B] = DISABLE;
- if (bypass) {
- param->rsz_rsc_param[RSZ_A].i_vps = 0;
- param->rsz_rsc_param[RSZ_A].i_hps = 0;
- /* Raw Bypass */
- param->rsz_common.passthrough = BYPASS_ON;
- }
-}
-
-static void
-configure_resizer_out_params(struct vpfe_resizer_device *resizer, int index,
- void *output_spec, unsigned char partial,
- unsigned int flag)
-{
- struct resizer_params *param = &resizer->config;
- struct v4l2_mbus_framefmt *outformat;
- struct vpfe_rsz_output_spec *output;
-
- if (index == RSZ_A &&
- resizer->resizer_a.output == RESIZER_OUTPUT_NONE) {
- param->rsz_en[index] = DISABLE;
- return;
- }
- if (index == RSZ_B &&
- resizer->resizer_b.output == RESIZER_OUTPUT_NONE) {
- param->rsz_en[index] = DISABLE;
- return;
- }
- output = output_spec;
- param->rsz_en[index] = ENABLE;
- if (partial) {
- param->rsz_rsc_param[index].h_flip = output->h_flip;
- param->rsz_rsc_param[index].v_flip = output->v_flip;
- param->rsz_rsc_param[index].v_typ_y = output->v_typ_y;
- param->rsz_rsc_param[index].v_typ_c = output->v_typ_c;
- param->rsz_rsc_param[index].v_lpf_int_y =
- output->v_lpf_int_y;
- param->rsz_rsc_param[index].v_lpf_int_c =
- output->v_lpf_int_c;
- param->rsz_rsc_param[index].h_typ_y = output->h_typ_y;
- param->rsz_rsc_param[index].h_typ_c = output->h_typ_c;
- param->rsz_rsc_param[index].h_lpf_int_y =
- output->h_lpf_int_y;
- param->rsz_rsc_param[index].h_lpf_int_c =
- output->h_lpf_int_c;
- param->rsz_rsc_param[index].dscale_en =
- output->en_down_scale;
- param->rsz_rsc_param[index].h_dscale_ave_sz =
- output->h_dscale_ave_sz;
- param->rsz_rsc_param[index].v_dscale_ave_sz =
- output->v_dscale_ave_sz;
- param->ext_mem_param[index].user_y_ofst =
- (output->user_y_ofst + 31) & ~0x1f;
- param->ext_mem_param[index].user_c_ofst =
- (output->user_c_ofst + 31) & ~0x1f;
- return;
- }
-
- if (index == RSZ_A)
- outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
- else
- outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
- param->rsz_rsc_param[index].o_vsz = outformat->height - 1;
- param->rsz_rsc_param[index].o_hsz = outformat->width - 1;
- param->ext_mem_param[index].rsz_sdr_ptr_s_y = output->vst_y;
- param->ext_mem_param[index].rsz_sdr_ptr_e_y = outformat->height;
- param->ext_mem_param[index].rsz_sdr_ptr_s_c = output->vst_c;
- param->ext_mem_param[index].rsz_sdr_ptr_e_c = outformat->height;
-
- if (!flag)
- return;
- /* update common parameters */
- param->rsz_rsc_param[index].h_flip = output->h_flip;
- param->rsz_rsc_param[index].v_flip = output->v_flip;
- param->rsz_rsc_param[index].v_typ_y = output->v_typ_y;
- param->rsz_rsc_param[index].v_typ_c = output->v_typ_c;
- param->rsz_rsc_param[index].v_lpf_int_y = output->v_lpf_int_y;
- param->rsz_rsc_param[index].v_lpf_int_c = output->v_lpf_int_c;
- param->rsz_rsc_param[index].h_typ_y = output->h_typ_y;
- param->rsz_rsc_param[index].h_typ_c = output->h_typ_c;
- param->rsz_rsc_param[index].h_lpf_int_y = output->h_lpf_int_y;
- param->rsz_rsc_param[index].h_lpf_int_c = output->h_lpf_int_c;
- param->rsz_rsc_param[index].dscale_en = output->en_down_scale;
- param->rsz_rsc_param[index].h_dscale_ave_sz = output->h_dscale_ave_sz;
- param->rsz_rsc_param[index].v_dscale_ave_sz = output->h_dscale_ave_sz;
- param->ext_mem_param[index].user_y_ofst =
- (output->user_y_ofst + 31) & ~0x1f;
- param->ext_mem_param[index].user_c_ofst =
- (output->user_c_ofst + 31) & ~0x1f;
-}
-
-/*
- * resizer_calculate_resize_ratios() - Calculates resize ratio for resizer
- * A or B. This is called after setting
- * the input size or output size.
- * @resizer: Pointer to VPFE resizer subdevice.
- * @index: index RSZ_A-resizer-A RSZ_B-resizer-B.
- */
-static void
-resizer_calculate_resize_ratios(struct vpfe_resizer_device *resizer, int index)
-{
- struct resizer_params *param = &resizer->config;
- struct v4l2_mbus_framefmt *informat, *outformat;
-
- informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK];
-
- if (index == RSZ_A)
- outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
- else
- outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
-
- if (outformat->field != V4L2_FIELD_INTERLACED)
- param->rsz_rsc_param[index].v_dif =
- ((informat->height) * 256) / (outformat->height);
- else
- param->rsz_rsc_param[index].v_dif =
- ((informat->height >> 1) * 256) / (outformat->height);
- param->rsz_rsc_param[index].h_dif =
- ((informat->width) * 256) / (outformat->width);
-}
-
-static void resizer_enable_422_420_conversion(struct resizer_params *param,
- int index, bool en)
-{
- param->rsz_rsc_param[index].cen = en;
- param->rsz_rsc_param[index].yen = en;
-}
-
-/* resizer_calculate_sdram_offsets() - This function calculates the offsets from
- * start of buffer for the C plane when
- * output format is YUV420SP. It also
- * calculates the offsets from the start of
- * the buffer when the image is flipped
- * vertically or horizontally for ycbcr/y/c
- * planes.
- * @resizer: Pointer to resizer subdevice.
- * @index: index RSZ_A-resizer-A RSZ_B-resizer-B.
- */
-static int
-resizer_calculate_sdram_offsets(struct vpfe_resizer_device *resizer, int index)
-{
- struct resizer_params *param = &resizer->config;
- struct v4l2_mbus_framefmt *outformat;
- int bytesperpixel = 2;
- int image_height;
- int image_width;
- int yuv_420 = 0;
- int offset = 0;
-
- if (index == RSZ_A)
- outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
- else
- outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
-
- image_height = outformat->height + 1;
- image_width = outformat->width + 1;
- param->ext_mem_param[index].c_offset = 0;
- param->ext_mem_param[index].flip_ofst_y = 0;
- param->ext_mem_param[index].flip_ofst_c = 0;
- if (outformat->code == MEDIA_BUS_FMT_YDYUYDYV8_1X16) {
- /* YUV 420 */
- yuv_420 = 1;
- bytesperpixel = 1;
- }
-
- if (param->rsz_rsc_param[index].h_flip)
- /* width * bytesperpixel - 1 */
- offset = (image_width * bytesperpixel) - 1;
- if (param->rsz_rsc_param[index].v_flip)
- offset += (image_height - 1) *
- param->ext_mem_param[index].rsz_sdr_oft_y;
- param->ext_mem_param[index].flip_ofst_y = offset;
- if (!yuv_420)
- return 0;
- offset = 0;
- /* half height for c-plane */
- if (param->rsz_rsc_param[index].h_flip)
- /* width * bytesperpixel - 1 */
- offset = image_width - 1;
- if (param->rsz_rsc_param[index].v_flip)
- offset += (((image_height >> 1) - 1) *
- param->ext_mem_param[index].rsz_sdr_oft_c);
- param->ext_mem_param[index].flip_ofst_c = offset;
- param->ext_mem_param[index].c_offset =
- param->ext_mem_param[index].rsz_sdr_oft_y * image_height;
- return 0;
-}
-
-static int resizer_configure_output_win(struct vpfe_resizer_device *resizer)
-{
- struct resizer_params *param = &resizer->config;
- struct vpfe_rsz_output_spec output_specs;
- struct v4l2_mbus_framefmt *outformat;
- int line_len_c;
- int line_len;
- int ret;
-
- outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
-
- memset(&output_specs, 0x0, sizeof(struct vpfe_rsz_output_spec));
- output_specs.vst_y = param->user_config.vst;
- if (outformat->code == MEDIA_BUS_FMT_YDYUYDYV8_1X16)
- output_specs.vst_c = param->user_config.vst;
-
- configure_resizer_out_params(resizer, RSZ_A, &output_specs, 0, 0);
- resizer_calculate_line_length(outformat->code,
- param->rsz_rsc_param[0].o_hsz + 1,
- param->rsz_rsc_param[0].o_vsz + 1,
- &line_len, &line_len_c);
- param->ext_mem_param[0].rsz_sdr_oft_y = line_len;
- param->ext_mem_param[0].rsz_sdr_oft_c = line_len_c;
- resizer_calculate_resize_ratios(resizer, RSZ_A);
- if (param->rsz_en[RSZ_B])
- resizer_calculate_resize_ratios(resizer, RSZ_B);
-
- if (outformat->code == MEDIA_BUS_FMT_YDYUYDYV8_1X16)
- resizer_enable_422_420_conversion(param, RSZ_A, ENABLE);
- else
- resizer_enable_422_420_conversion(param, RSZ_A, DISABLE);
-
- ret = resizer_calculate_sdram_offsets(resizer, RSZ_A);
- if (!ret && param->rsz_en[RSZ_B])
- ret = resizer_calculate_sdram_offsets(resizer, RSZ_B);
-
- if (ret)
- pr_err("Error in calculating sdram offsets\n");
- return ret;
-}
-
-static int
-resizer_calculate_down_scale_f_div_param(struct device *dev,
- int input_width, int output_width,
- struct resizer_scale_param *param)
-{
- /* rsz = R, input_width = H, output width = h in the equation */
- unsigned int two_power;
- unsigned int upper_h1;
- unsigned int upper_h2;
- unsigned int val1;
- unsigned int val;
- unsigned int rsz;
- unsigned int h1;
- unsigned int h2;
- unsigned int o;
- unsigned int n;
-
- upper_h1 = input_width >> 1;
- n = param->h_dscale_ave_sz;
- /* 2 ^ (scale+1) */
- two_power = 1 << (n + 1);
- upper_h1 = (upper_h1 >> (n + 1)) << (n + 1);
- upper_h2 = input_width - upper_h1;
- if (upper_h2 % two_power) {
- dev_err(dev, "frame halves to be a multiple of 2 power n+1\n");
- return -EINVAL;
- }
- two_power = 1 << n;
- rsz = (input_width << 8) / output_width;
- val = rsz * two_power;
- val = ((upper_h1 << 8) / val) + 1;
- if (!(val % 2)) {
- h1 = val;
- } else {
- val = upper_h1 << 8;
- val >>= n + 1;
- val -= rsz >> 1;
- val /= rsz << 1;
- val <<= 1;
- val += 2;
- h1 = val;
- }
- o = 10 + (two_power << 2);
- if (((input_width << 7) / rsz) % 2)
- o += ((DIV_ROUND_UP(rsz, 1024) << 1) << n);
- h2 = output_width - h1;
- /* phi */
- val = (h1 * rsz) - (((upper_h1 - (o - 10)) / two_power) << 8);
- /* skip */
- val1 = ((val - 1024) >> 9) << 1;
- param->f_div.num_passes = MAX_PASSES;
- param->f_div.pass[0].o_hsz = h1 - 1;
- param->f_div.pass[0].i_hps = 0;
- param->f_div.pass[0].h_phs = 0;
- param->f_div.pass[0].src_hps = 0;
- param->f_div.pass[0].src_hsz = upper_h1 + o;
- param->f_div.pass[1].o_hsz = h2 - 1;
- param->f_div.pass[1].i_hps = 10 + (val1 * two_power);
- param->f_div.pass[1].h_phs = val - (val1 << 8);
- param->f_div.pass[1].src_hps = upper_h1 - o;
- param->f_div.pass[1].src_hsz = upper_h2 + o;
-
- return 0;
-}
-
-static int
-resizer_configure_common_in_params(struct vpfe_resizer_device *resizer)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- struct resizer_params *param = &resizer->config;
- struct vpfe_rsz_config_params *user_config;
- struct v4l2_mbus_framefmt *informat;
-
- informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK];
- user_config = &resizer->config.user_config;
- param->rsz_common.vps = param->user_config.vst;
- param->rsz_common.hps = param->user_config.hst;
-
- if (vpfe_ipipeif_decimation_enabled(vpfe_dev))
- param->rsz_common.hsz = ((informat->width - 1) *
- IPIPEIF_RSZ_CONST) / vpfe_ipipeif_get_rsz(vpfe_dev);
- else
- param->rsz_common.hsz = informat->width - 1;
-
- if (informat->field == V4L2_FIELD_INTERLACED)
- param->rsz_common.vsz = (informat->height - 1) >> 1;
- else
- param->rsz_common.vsz = informat->height - 1;
-
- param->rsz_common.raw_flip = 0;
-
- if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_IPIPEIF)
- param->rsz_common.source = IPIPEIF_DATA;
- else
- param->rsz_common.source = IPIPE_DATA;
-
- switch (informat->code) {
- case MEDIA_BUS_FMT_UYVY8_2X8:
- param->rsz_common.src_img_fmt = RSZ_IMG_422;
- param->rsz_common.raw_flip = 0;
- break;
-
- case MEDIA_BUS_FMT_Y8_1X8:
- param->rsz_common.src_img_fmt = RSZ_IMG_420;
- /* Select y */
- param->rsz_common.y_c = 0;
- param->rsz_common.raw_flip = 0;
- break;
-
- case MEDIA_BUS_FMT_UV8_1X8:
- param->rsz_common.src_img_fmt = RSZ_IMG_420;
- /* Select y */
- param->rsz_common.y_c = 1;
- param->rsz_common.raw_flip = 0;
- break;
-
- case MEDIA_BUS_FMT_SGRBG12_1X12:
- param->rsz_common.raw_flip = 1;
- break;
-
- default:
- param->rsz_common.src_img_fmt = RSZ_IMG_422;
- param->rsz_common.source = IPIPE_DATA;
- }
-
- param->rsz_common.yuv_y_min = user_config->yuv_y_min;
- param->rsz_common.yuv_y_max = user_config->yuv_y_max;
- param->rsz_common.yuv_c_min = user_config->yuv_c_min;
- param->rsz_common.yuv_c_max = user_config->yuv_c_max;
- param->rsz_common.out_chr_pos = user_config->out_chr_pos;
- param->rsz_common.rsz_seq_crv = user_config->chroma_sample_even;
-
- return 0;
-}
-static int
-resizer_configure_in_continuous_mode(struct vpfe_resizer_device *resizer)
-{
- struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev;
- struct resizer_params *param = &resizer->config;
- struct vpfe_rsz_config_params *cont_config;
- int line_len_c;
- int line_len;
- int ret;
-
- if (resizer->resizer_a.output != RESIZER_OUTPUT_MEMORY) {
- dev_err(dev, "enable resizer - Resizer-A\n");
- return -EINVAL;
- }
-
- cont_config = &resizer->config.user_config;
- param->rsz_en[RSZ_A] = ENABLE;
- configure_resizer_out_params(resizer, RSZ_A,
- &cont_config->output1, 1, 0);
- param->rsz_en[RSZ_B] = DISABLE;
- param->oper_mode = RESIZER_MODE_CONTINUOUS;
-
- if (resizer->resizer_b.output == RESIZER_OUTPUT_MEMORY) {
- struct v4l2_mbus_framefmt *outformat2;
-
- param->rsz_en[RSZ_B] = ENABLE;
- outformat2 = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
- ret = resizer_validate_output_image_format(dev, outformat2,
- &line_len, &line_len_c);
- if (ret)
- return ret;
- param->ext_mem_param[RSZ_B].rsz_sdr_oft_y = line_len;
- param->ext_mem_param[RSZ_B].rsz_sdr_oft_c = line_len_c;
- configure_resizer_out_params(resizer, RSZ_B,
- &cont_config->output2, 0, 1);
- if (outformat2->code == MEDIA_BUS_FMT_YDYUYDYV8_1X16)
- resizer_enable_422_420_conversion(param,
- RSZ_B, ENABLE);
- else
- resizer_enable_422_420_conversion(param,
- RSZ_B, DISABLE);
- }
- resizer_configure_common_in_params(resizer);
- ret = resizer_configure_output_win(resizer);
- if (ret)
- return ret;
-
- param->rsz_common.passthrough = cont_config->bypass;
- if (cont_config->bypass)
- resizer_configure_passthru(resizer, 1);
-
- return 0;
-}
-
-static inline int
-resizer_validate_input_image_format(struct device *dev,
- u32 pix,
- int width, int height, int *line_len)
-{
- int val;
-
- if (pix != MEDIA_BUS_FMT_UYVY8_2X8 &&
- pix != MEDIA_BUS_FMT_Y8_1X8 &&
- pix != MEDIA_BUS_FMT_UV8_1X8 &&
- pix != MEDIA_BUS_FMT_SGRBG12_1X12) {
- dev_err(dev,
- "resizer validate output: pix format not supported, %d\n", pix);
- return -EINVAL;
- }
-
- if (!width || !height) {
- dev_err(dev,
- "resizer validate input: invalid width or height\n");
- return -EINVAL;
- }
-
- if (pix == MEDIA_BUS_FMT_UV8_1X8)
- resizer_calculate_line_length(pix, width,
- height, &val, line_len);
- else
- resizer_calculate_line_length(pix, width,
- height, line_len, &val);
-
- return 0;
-}
-
-static int
-resizer_validate_decimation(struct device *dev, enum ipipeif_decimation dec_en,
- unsigned char rsz, unsigned char frame_div_mode_en,
- int width)
-{
- if (dec_en && frame_div_mode_en) {
- dev_err(dev,
- "dec_en & frame_div_mode_en can not enabled simultaneously\n");
- return -EINVAL;
- }
-
- if (frame_div_mode_en) {
- dev_err(dev, "frame_div_mode mode not supported\n");
- return -EINVAL;
- }
-
- if (!dec_en)
- return 0;
-
- if (width <= VPFE_IPIPE_MAX_INPUT_WIDTH) {
- dev_err(dev,
- "image width to be more than %d for decimation\n",
- VPFE_IPIPE_MAX_INPUT_WIDTH);
- return -EINVAL;
- }
-
- if (rsz < IPIPEIF_RSZ_MIN || rsz > IPIPEIF_RSZ_MAX) {
- dev_err(dev, "rsz range is %d to %d\n",
- IPIPEIF_RSZ_MIN, IPIPEIF_RSZ_MAX);
- return -EINVAL;
- }
-
- return 0;
-}
-
-/* resizer_calculate_normal_f_div_param() - Algorithm to calculate the frame
- * division parameters for resizer.
- * in normal mode.
- */
-static int
-resizer_calculate_normal_f_div_param(struct device *dev, int input_width,
- int output_width, struct resizer_scale_param *param)
-{
- /* rsz = R, input_width = H, output width = h in the equation */
- unsigned int val1;
- unsigned int rsz;
- unsigned int val;
- unsigned int h1;
- unsigned int h2;
- unsigned int o;
-
- if (output_width > input_width) {
- dev_err(dev, "frame div mode is used for scale down only\n");
- return -EINVAL;
- }
-
- rsz = (input_width << 8) / output_width;
- val = rsz << 1;
- val = ((input_width << 8) / val) + 1;
- o = 14;
- if (!(val % 2)) {
- h1 = val;
- } else {
- val = input_width << 7;
- val -= rsz >> 1;
- val /= rsz << 1;
- val <<= 1;
- val += 2;
- o += (DIV_ROUND_UP(rsz, 1024) << 1);
- h1 = val;
- }
- h2 = output_width - h1;
- /* phi */
- val = (h1 * rsz) - (((input_width >> 1) - o) << 8);
- /* skip */
- val1 = ((val - 1024) >> 9) << 1;
- param->f_div.num_passes = MAX_PASSES;
- param->f_div.pass[0].o_hsz = h1 - 1;
- param->f_div.pass[0].i_hps = 0;
- param->f_div.pass[0].h_phs = 0;
- param->f_div.pass[0].src_hps = 0;
- param->f_div.pass[0].src_hsz = (input_width >> 2) + o;
- param->f_div.pass[1].o_hsz = h2 - 1;
- param->f_div.pass[1].i_hps = val1;
- param->f_div.pass[1].h_phs = val - (val1 << 8);
- param->f_div.pass[1].src_hps = (input_width >> 2) - o;
- param->f_div.pass[1].src_hsz = (input_width >> 2) + o;
-
- return 0;
-}
-
-static int
-resizer_configure_in_single_shot_mode(struct vpfe_resizer_device *resizer)
-{
- struct vpfe_rsz_config_params *config = &resizer->config.user_config;
- struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev;
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- struct v4l2_mbus_framefmt *outformat1, *outformat2;
- struct resizer_params *param = &resizer->config;
- struct v4l2_mbus_framefmt *informat;
- int decimation;
- int line_len_c;
- int line_len;
- int rsz;
- int ret;
-
- informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK];
- outformat1 = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
- outformat2 = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
-
- decimation = vpfe_ipipeif_decimation_enabled(vpfe_dev);
- rsz = vpfe_ipipeif_get_rsz(vpfe_dev);
- if (decimation && param->user_config.frame_div_mode_en) {
- dev_err(dev,
- "dec_en & frame_div_mode_en cannot enabled simultaneously\n");
- return -EINVAL;
- }
-
- ret = resizer_validate_decimation(dev, decimation, rsz,
- param->user_config.frame_div_mode_en, informat->width);
- if (ret)
- return -EINVAL;
-
- ret = resizer_validate_input_image_format(dev, informat->code,
- informat->width, informat->height, &line_len);
- if (ret)
- return -EINVAL;
-
- if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) {
- param->rsz_en[RSZ_A] = ENABLE;
- ret = resizer_validate_output_image_format(dev, outformat1,
- &line_len, &line_len_c);
- if (ret)
- return ret;
- param->ext_mem_param[RSZ_A].rsz_sdr_oft_y = line_len;
- param->ext_mem_param[RSZ_A].rsz_sdr_oft_c = line_len_c;
- configure_resizer_out_params(resizer, RSZ_A,
- &param->user_config.output1, 0, 1);
-
- if (outformat1->code == MEDIA_BUS_FMT_SGRBG12_1X12)
- param->rsz_common.raw_flip = 1;
- else
- param->rsz_common.raw_flip = 0;
-
- if (outformat1->code == MEDIA_BUS_FMT_YDYUYDYV8_1X16)
- resizer_enable_422_420_conversion(param,
- RSZ_A, ENABLE);
- else
- resizer_enable_422_420_conversion(param,
- RSZ_A, DISABLE);
- }
-
- if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) {
- param->rsz_en[RSZ_B] = ENABLE;
- ret = resizer_validate_output_image_format(dev, outformat2,
- &line_len, &line_len_c);
- if (ret)
- return ret;
- param->ext_mem_param[RSZ_B].rsz_sdr_oft_y = line_len;
- param->ext_mem_param[RSZ_B].rsz_sdr_oft_c = line_len_c;
- configure_resizer_out_params(resizer, RSZ_B,
- &param->user_config.output2, 0, 1);
- if (outformat2->code == MEDIA_BUS_FMT_YDYUYDYV8_1X16)
- resizer_enable_422_420_conversion(param,
- RSZ_B, ENABLE);
- else
- resizer_enable_422_420_conversion(param,
- RSZ_B, DISABLE);
- }
-
- resizer_configure_common_in_params(resizer);
- if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) {
- resizer_calculate_resize_ratios(resizer, RSZ_A);
- resizer_calculate_sdram_offsets(resizer, RSZ_A);
- /* Overriding resize ratio calculation */
- if (informat->code == MEDIA_BUS_FMT_UV8_1X8) {
- param->rsz_rsc_param[RSZ_A].v_dif =
- (((informat->height + 1) * 2) * 256) /
- (param->rsz_rsc_param[RSZ_A].o_vsz + 1);
- }
- }
-
- if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) {
- resizer_calculate_resize_ratios(resizer, RSZ_B);
- resizer_calculate_sdram_offsets(resizer, RSZ_B);
- /* Overriding resize ratio calculation */
- if (informat->code == MEDIA_BUS_FMT_UV8_1X8) {
- param->rsz_rsc_param[RSZ_B].v_dif =
- (((informat->height + 1) * 2) * 256) /
- (param->rsz_rsc_param[RSZ_B].o_vsz + 1);
- }
- }
- if (param->user_config.frame_div_mode_en &&
- param->rsz_en[RSZ_A]) {
- if (!param->rsz_rsc_param[RSZ_A].dscale_en)
- ret = resizer_calculate_normal_f_div_param(dev,
- informat->width,
- param->rsz_rsc_param[RSZ_A].o_vsz + 1,
- &param->rsz_rsc_param[RSZ_A]);
- else
- ret = resizer_calculate_down_scale_f_div_param(dev,
- informat->width,
- param->rsz_rsc_param[RSZ_A].o_vsz + 1,
- &param->rsz_rsc_param[RSZ_A]);
- if (ret)
- return -EINVAL;
- }
- if (param->user_config.frame_div_mode_en &&
- param->rsz_en[RSZ_B]) {
- if (!param->rsz_rsc_param[RSZ_B].dscale_en)
- ret = resizer_calculate_normal_f_div_param(dev,
- informat->width,
- param->rsz_rsc_param[RSZ_B].o_vsz + 1,
- &param->rsz_rsc_param[RSZ_B]);
- else
- ret = resizer_calculate_down_scale_f_div_param(dev,
- informat->width,
- param->rsz_rsc_param[RSZ_B].o_vsz + 1,
- &param->rsz_rsc_param[RSZ_B]);
- if (ret)
- return -EINVAL;
- }
- param->rsz_common.passthrough = config->bypass;
- if (config->bypass)
- resizer_configure_passthru(resizer, 1);
- return 0;
-}
-
-static void
-resizer_set_default_configuration(struct vpfe_resizer_device *resizer)
-{
-#define WIDTH_I 640
-#define HEIGHT_I 480
-#define WIDTH_O 640
-#define HEIGHT_O 480
- const struct resizer_params rsz_default_config = {
- .oper_mode = RESIZER_MODE_ONE_SHOT,
- .rsz_common = {
- .vsz = HEIGHT_I - 1,
- .hsz = WIDTH_I - 1,
- .src_img_fmt = RSZ_IMG_422,
- .raw_flip = 1, /* flip preserve Raw format */
- .source = IPIPE_DATA,
- .passthrough = BYPASS_OFF,
- .yuv_y_max = 255,
- .yuv_c_max = 255,
- .rsz_seq_crv = DISABLE,
- .out_chr_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE,
- },
- .rsz_rsc_param = {
- {
- .h_flip = DISABLE,
- .v_flip = DISABLE,
- .cen = DISABLE,
- .yen = DISABLE,
- .o_vsz = HEIGHT_O - 1,
- .o_hsz = WIDTH_O - 1,
- .v_dif = 256,
- .v_typ_y = VPFE_RSZ_INTP_CUBIC,
- .v_typ_c = VPFE_RSZ_INTP_CUBIC,
- .h_dif = 256,
- .h_typ_y = VPFE_RSZ_INTP_CUBIC,
- .h_typ_c = VPFE_RSZ_INTP_CUBIC,
- .h_dscale_ave_sz =
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- .v_dscale_ave_sz =
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- },
- {
- .h_flip = DISABLE,
- .v_flip = DISABLE,
- .cen = DISABLE,
- .yen = DISABLE,
- .o_vsz = HEIGHT_O - 1,
- .o_hsz = WIDTH_O - 1,
- .v_dif = 256,
- .v_typ_y = VPFE_RSZ_INTP_CUBIC,
- .v_typ_c = VPFE_RSZ_INTP_CUBIC,
- .h_dif = 256,
- .h_typ_y = VPFE_RSZ_INTP_CUBIC,
- .h_typ_c = VPFE_RSZ_INTP_CUBIC,
- .h_dscale_ave_sz =
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- .v_dscale_ave_sz =
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- },
- },
- .rsz2rgb = {
- {
- .rgb_en = DISABLE
- },
- {
- .rgb_en = DISABLE
- }
- },
- .ext_mem_param = {
- {
- .rsz_sdr_oft_y = WIDTH_O << 1,
- .rsz_sdr_ptr_e_y = HEIGHT_O,
- .rsz_sdr_oft_c = WIDTH_O,
- .rsz_sdr_ptr_e_c = HEIGHT_O >> 1,
- },
- {
- .rsz_sdr_oft_y = WIDTH_O << 1,
- .rsz_sdr_ptr_e_y = HEIGHT_O,
- .rsz_sdr_oft_c = WIDTH_O,
- .rsz_sdr_ptr_e_c = HEIGHT_O,
- },
- },
- .rsz_en[0] = ENABLE,
- .rsz_en[1] = DISABLE,
- .user_config = {
- .output1 = {
- .v_typ_y = VPFE_RSZ_INTP_CUBIC,
- .v_typ_c = VPFE_RSZ_INTP_CUBIC,
- .h_typ_y = VPFE_RSZ_INTP_CUBIC,
- .h_typ_c = VPFE_RSZ_INTP_CUBIC,
- .h_dscale_ave_sz =
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- .v_dscale_ave_sz =
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- },
- .output2 = {
- .v_typ_y = VPFE_RSZ_INTP_CUBIC,
- .v_typ_c = VPFE_RSZ_INTP_CUBIC,
- .h_typ_y = VPFE_RSZ_INTP_CUBIC,
- .h_typ_c = VPFE_RSZ_INTP_CUBIC,
- .h_dscale_ave_sz =
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- .v_dscale_ave_sz =
- VPFE_IPIPE_DWN_SCALE_1_OVER_2,
- },
- .yuv_y_max = 255,
- .yuv_c_max = 255,
- .out_chr_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE,
- },
- };
- memcpy(&resizer->config, &rsz_default_config,
- sizeof(struct resizer_params));
-}
-
-/*
- * resizer_set_configuration() - set resizer config
- * @resizer: vpfe resizer device pointer.
- * @chan_config: resizer channel configuration.
- */
-static int
-resizer_set_configuration(struct vpfe_resizer_device *resizer,
- struct vpfe_rsz_config *chan_config)
-{
- if (!chan_config->config)
- resizer_set_default_configuration(resizer);
- else
- if (copy_from_user(&resizer->config.user_config,
- (void __user *)chan_config->config,
- sizeof(struct vpfe_rsz_config_params)))
- return -EFAULT;
-
- return 0;
-}
-
-/*
- * resizer_get_configuration() - get resizer config
- * @resizer: vpfe resizer device pointer.
- * @channel: image processor logical channel.
- * @chan_config: resizer channel configuration.
- */
-static int
-resizer_get_configuration(struct vpfe_resizer_device *resizer,
- struct vpfe_rsz_config *chan_config)
-{
- struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev;
-
- if (!chan_config->config) {
- dev_err(dev, "Resizer channel invalid pointer\n");
- return -EINVAL;
- }
-
- if (copy_to_user((void __user *)chan_config->config,
- (void *)&resizer->config.user_config,
- sizeof(struct vpfe_rsz_config_params))) {
- dev_err(dev, "resizer_get_configuration: Error in copy to user\n");
- return -EFAULT;
- }
-
- return 0;
-}
-
-/*
- * VPFE video operations
- */
-
-/*
- * resizer_a_video_out_queue() - RESIZER-A video out queue
- * @vpfe_dev: vpfe device pointer.
- * @addr: buffer address.
- */
-static int resizer_a_video_out_queue(struct vpfe_device *vpfe_dev,
- unsigned long addr)
-{
- struct vpfe_resizer_device *resizer = &vpfe_dev->vpfe_resizer;
-
- return resizer_set_outaddr(resizer->base_addr,
- &resizer->config, RSZ_A, addr);
-}
-
-/*
- * resizer_b_video_out_queue() - RESIZER-B video out queue
- * @vpfe_dev: vpfe device pointer.
- * @addr: buffer address.
- */
-static int resizer_b_video_out_queue(struct vpfe_device *vpfe_dev,
- unsigned long addr)
-{
- struct vpfe_resizer_device *resizer = &vpfe_dev->vpfe_resizer;
-
- return resizer_set_outaddr(resizer->base_addr,
- &resizer->config, RSZ_B, addr);
-}
-
-static const struct vpfe_video_operations resizer_a_video_ops = {
- .queue = resizer_a_video_out_queue,
-};
-
-static const struct vpfe_video_operations resizer_b_video_ops = {
- .queue = resizer_b_video_out_queue,
-};
-
-static void resizer_enable(struct vpfe_resizer_device *resizer, int en)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
- unsigned char val;
-
- if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_NONE)
- return;
-
- if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_IPIPEIF &&
- ipipeif_sink == IPIPEIF_INPUT_MEMORY) {
- do {
- val = regr_rsz(resizer->base_addr, RSZ_SRC_EN);
- } while (val);
-
- if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) {
- do {
- val = regr_rsz(resizer->base_addr, RSZ_A);
- } while (val);
- }
- if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) {
- do {
- val = regr_rsz(resizer->base_addr, RSZ_B);
- } while (val);
- }
- }
- if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE)
- rsz_enable(resizer->base_addr, RSZ_A, en);
-
- if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE)
- rsz_enable(resizer->base_addr, RSZ_B, en);
-}
-
-
-/*
- * resizer_ss_isr() - resizer module single-shot buffer scheduling isr
- * @resizer: vpfe resizer device pointer.
- */
-static void resizer_ss_isr(struct vpfe_resizer_device *resizer)
-{
- struct vpfe_video_device *video_out = &resizer->resizer_a.video_out;
- struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out;
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- struct vpfe_pipeline *pipe = &video_out->pipe;
- u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
- u32 val;
-
- if (ipipeif_sink != IPIPEIF_INPUT_MEMORY)
- return;
-
- if (resizer->resizer_a.output == RESIZER_OUTPUT_MEMORY) {
- val = vpss_dma_complete_interrupt();
- if (val != 0 && val != 2)
- return;
- }
-
- if (resizer->resizer_a.output == RESIZER_OUTPUT_MEMORY) {
- spin_lock(&video_out->dma_queue_lock);
- vpfe_video_process_buffer_complete(video_out);
- video_out->state = VPFE_VIDEO_BUFFER_NOT_QUEUED;
- vpfe_video_schedule_next_buffer(video_out);
- spin_unlock(&video_out->dma_queue_lock);
- }
-
- /* If resizer B is enabled */
- if (pipe->output_num > 1 && resizer->resizer_b.output ==
- RESIZER_OUTPUT_MEMORY) {
- spin_lock(&video_out2->dma_queue_lock);
- vpfe_video_process_buffer_complete(video_out2);
- video_out2->state = VPFE_VIDEO_BUFFER_NOT_QUEUED;
- vpfe_video_schedule_next_buffer(video_out2);
- spin_unlock(&video_out2->dma_queue_lock);
- }
-
- /* start HW if buffers are queued */
- if (vpfe_video_is_pipe_ready(pipe) &&
- resizer->resizer_a.output == RESIZER_OUTPUT_MEMORY) {
- resizer_enable(resizer, 1);
- vpfe_ipipe_enable(vpfe_dev, 1);
- vpfe_ipipeif_enable(vpfe_dev);
- }
-}
-
-/*
- * vpfe_resizer_buffer_isr() - resizer module buffer scheduling isr
- * @resizer: vpfe resizer device pointer.
- */
-void vpfe_resizer_buffer_isr(struct vpfe_resizer_device *resizer)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- struct vpfe_video_device *video_out = &resizer->resizer_a.video_out;
- struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out;
- struct vpfe_pipeline *pipe = &resizer->resizer_a.video_out.pipe;
- enum v4l2_field field;
- int fid;
-
- if (!video_out->started)
- return;
-
- if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_NONE)
- return;
-
- field = video_out->fmt.fmt.pix.field;
- if (field == V4L2_FIELD_NONE) {
- /* handle progressive frame capture */
- if (video_out->cur_frm != video_out->next_frm) {
- vpfe_video_process_buffer_complete(video_out);
- if (pipe->output_num > 1)
- vpfe_video_process_buffer_complete(video_out2);
- }
-
- video_out->skip_frame_count--;
- if (!video_out->skip_frame_count) {
- video_out->skip_frame_count =
- video_out->skip_frame_count_init;
- rsz_src_enable(resizer->base_addr, 1);
- } else {
- rsz_src_enable(resizer->base_addr, 0);
- }
- return;
- }
-
- /* handle interlaced frame capture */
- fid = vpfe_isif_get_fid(vpfe_dev);
-
- /* switch the software maintained field id */
- video_out->field_id ^= 1;
- if (fid == video_out->field_id) {
- /*
- * we are in-sync here,continue.
- * One frame is just being captured. If the
- * next frame is available, release the current
- * frame and move on
- */
- if (fid == 0 && video_out->cur_frm != video_out->next_frm) {
- vpfe_video_process_buffer_complete(video_out);
- if (pipe->output_num > 1)
- vpfe_video_process_buffer_complete(video_out2);
- }
- } else if (fid == 0) {
- /*
- * out of sync. Recover from any hardware out-of-sync.
- * May loose one frame
- */
- video_out->field_id = fid;
- }
-}
-
-/*
- * vpfe_resizer_dma_isr() - resizer module dma isr
- * @resizer: vpfe resizer device pointer.
- */
-void vpfe_resizer_dma_isr(struct vpfe_resizer_device *resizer)
-{
- struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out;
- struct vpfe_video_device *video_out = &resizer->resizer_a.video_out;
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- struct vpfe_pipeline *pipe = &video_out->pipe;
- int schedule_capture = 0;
- enum v4l2_field field;
- int fid;
-
- if (!video_out->started)
- return;
-
- if (pipe->state == VPFE_PIPELINE_STREAM_SINGLESHOT) {
- resizer_ss_isr(resizer);
- return;
- }
-
- field = video_out->fmt.fmt.pix.field;
- if (field == V4L2_FIELD_NONE) {
- if (!list_empty(&video_out->dma_queue) &&
- video_out->cur_frm == video_out->next_frm)
- schedule_capture = 1;
- } else {
- fid = vpfe_isif_get_fid(vpfe_dev);
- if (fid == video_out->field_id) {
- /* we are in-sync here,continue */
- if (fid == 1 && !list_empty(&video_out->dma_queue) &&
- video_out->cur_frm == video_out->next_frm)
- schedule_capture = 1;
- }
- }
-
- if (!schedule_capture)
- return;
-
- spin_lock(&video_out->dma_queue_lock);
- vpfe_video_schedule_next_buffer(video_out);
- spin_unlock(&video_out->dma_queue_lock);
- if (pipe->output_num > 1) {
- spin_lock(&video_out2->dma_queue_lock);
- vpfe_video_schedule_next_buffer(video_out2);
- spin_unlock(&video_out2->dma_queue_lock);
- }
-}
-
-/*
- * V4L2 subdev operations
- */
-
-/*
- * resizer_ioctl() - Handle resizer module private ioctl's
- * @sd: pointer to v4l2 subdev structure
- * @cmd: configuration command
- * @arg: configuration argument
- */
-static long resizer_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
-{
- struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
- struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev;
- struct vpfe_rsz_config *user_config;
- int ret = -ENOIOCTLCMD;
-
- if (&resizer->crop_resizer.subdev != sd)
- return ret;
-
- switch (cmd) {
- case VIDIOC_VPFE_RSZ_S_CONFIG:
- user_config = arg;
- ret = resizer_set_configuration(resizer, user_config);
- break;
-
- case VIDIOC_VPFE_RSZ_G_CONFIG:
- user_config = arg;
- if (!user_config->config) {
- dev_err(dev, "error in VIDIOC_VPFE_RSZ_G_CONFIG\n");
- return -EINVAL;
- }
- ret = resizer_get_configuration(resizer, user_config);
- break;
- }
- return ret;
-}
-
-static int resizer_do_hw_setup(struct vpfe_resizer_device *resizer)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
- u16 ipipeif_source = vpfe_dev->vpfe_ipipeif.output;
- struct resizer_params *param = &resizer->config;
- int ret = 0;
-
- if (resizer->resizer_a.output == RESIZER_OUTPUT_MEMORY ||
- resizer->resizer_b.output == RESIZER_OUTPUT_MEMORY) {
- if (ipipeif_sink == IPIPEIF_INPUT_MEMORY &&
- ipipeif_source == IPIPEIF_OUTPUT_RESIZER)
- ret = resizer_configure_in_single_shot_mode(resizer);
- else
- ret = resizer_configure_in_continuous_mode(resizer);
- if (ret)
- return ret;
- ret = config_rsz_hw(resizer, param);
- }
- return ret;
-}
-
-/*
- * resizer_set_stream() - Enable/Disable streaming on resizer subdev
- * @sd: pointer to v4l2 subdev structure
- * @enable: 1 == Enable, 0 == Disable
- */
-static int resizer_set_stream(struct v4l2_subdev *sd, int enable)
-{
- struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
-
- if (&resizer->crop_resizer.subdev != sd)
- return 0;
-
- if (resizer->resizer_a.output != RESIZER_OUTPUT_MEMORY)
- return 0;
-
- switch (enable) {
- case 1:
- if (resizer_do_hw_setup(resizer) < 0)
- return -EINVAL;
- resizer_enable(resizer, enable);
- break;
-
- case 0:
- resizer_enable(resizer, enable);
- break;
- }
-
- return 0;
-}
-
-/*
- * __resizer_get_format() - helper function for getting resizer format
- * @sd: pointer to subdev.
- * @cfg: V4L2 subdev pad config
- * @pad: pad number.
- * @which: wanted subdev format.
- * Return wanted mbus frame format.
- */
-static struct v4l2_mbus_framefmt *
-__resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- unsigned int pad, enum v4l2_subdev_format_whence which)
-{
- struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
-
- if (which == V4L2_SUBDEV_FORMAT_TRY)
- return v4l2_subdev_get_try_format(sd, cfg, pad);
- if (&resizer->crop_resizer.subdev == sd)
- return &resizer->crop_resizer.formats[pad];
- if (&resizer->resizer_a.subdev == sd)
- return &resizer->resizer_a.formats[pad];
- if (&resizer->resizer_b.subdev == sd)
- return &resizer->resizer_b.formats[pad];
- return NULL;
-}
-
-/*
- * resizer_try_format() - Handle try format by pad subdev method
- * @sd: pointer to subdev.
- * @cfg: V4L2 subdev pad config
- * @pad: pad num.
- * @fmt: pointer to v4l2 format structure.
- * @which: wanted subdev format.
- */
-static void
-resizer_try_format(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg,
- unsigned int pad, struct v4l2_mbus_framefmt *fmt,
- enum v4l2_subdev_format_whence which)
-{
- struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
- unsigned int max_out_height;
- unsigned int max_out_width;
- unsigned int i;
-
- if ((&resizer->resizer_a.subdev == sd && pad == RESIZER_PAD_SINK) ||
- (&resizer->resizer_b.subdev == sd && pad == RESIZER_PAD_SINK) ||
- (&resizer->crop_resizer.subdev == sd &&
- (pad == RESIZER_CROP_PAD_SOURCE ||
- pad == RESIZER_CROP_PAD_SOURCE2 || pad == RESIZER_CROP_PAD_SINK))) {
- for (i = 0; i < ARRAY_SIZE(resizer_input_formats); i++) {
- if (fmt->code == resizer_input_formats[i])
- break;
- }
- /* If not found, use UYVY as default */
- if (i >= ARRAY_SIZE(resizer_input_formats))
- fmt->code = MEDIA_BUS_FMT_UYVY8_2X8;
-
- fmt->width = clamp_t(u32, fmt->width, MIN_IN_WIDTH,
- MAX_IN_WIDTH);
- fmt->height = clamp_t(u32, fmt->height, MIN_IN_HEIGHT,
- MAX_IN_HEIGHT);
- } else if (&resizer->resizer_a.subdev == sd &&
- pad == RESIZER_PAD_SOURCE) {
- max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A;
- max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A;
-
- for (i = 0; i < ARRAY_SIZE(resizer_output_formats); i++) {
- if (fmt->code == resizer_output_formats[i])
- break;
- }
- /* If not found, use UYVY as default */
- if (i >= ARRAY_SIZE(resizer_output_formats))
- fmt->code = MEDIA_BUS_FMT_UYVY8_2X8;
-
- fmt->width = clamp_t(u32, fmt->width, MIN_OUT_WIDTH,
- max_out_width);
- fmt->width &= ~15;
- fmt->height = clamp_t(u32, fmt->height, MIN_OUT_HEIGHT,
- max_out_height);
- } else if (&resizer->resizer_b.subdev == sd &&
- pad == RESIZER_PAD_SOURCE) {
- max_out_width = IPIPE_MAX_OUTPUT_WIDTH_B;
- max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_B;
-
- for (i = 0; i < ARRAY_SIZE(resizer_output_formats); i++) {
- if (fmt->code == resizer_output_formats[i])
- break;
- }
- /* If not found, use UYVY as default */
- if (i >= ARRAY_SIZE(resizer_output_formats))
- fmt->code = MEDIA_BUS_FMT_UYVY8_2X8;
-
- fmt->width = clamp_t(u32, fmt->width, MIN_OUT_WIDTH,
- max_out_width);
- fmt->width &= ~15;
- fmt->height = clamp_t(u32, fmt->height, MIN_OUT_HEIGHT,
- max_out_height);
- }
-}
-
-/*
- * resizer_set_format() - Handle set format by pads subdev method
- * @sd: pointer to v4l2 subdev structure
- * @cfg: V4L2 subdev pad config
- * @fmt: pointer to v4l2 subdev format structure
- * return -EINVAL or zero on success
- */
-static int resizer_set_format(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
- struct v4l2_mbus_framefmt *format;
-
- format = __resizer_get_format(sd, cfg, fmt->pad, fmt->which);
- if (format == NULL)
- return -EINVAL;
-
- resizer_try_format(sd, cfg, fmt->pad, &fmt->format, fmt->which);
- *format = fmt->format;
-
- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
- return 0;
-
- if (&resizer->crop_resizer.subdev == sd) {
- if (fmt->pad == RESIZER_CROP_PAD_SINK) {
- resizer->crop_resizer.formats[fmt->pad] = fmt->format;
- } else if (fmt->pad == RESIZER_CROP_PAD_SOURCE &&
- resizer->crop_resizer.output == RESIZER_A) {
- resizer->crop_resizer.formats[fmt->pad] = fmt->format;
- resizer->crop_resizer.
- formats[RESIZER_CROP_PAD_SOURCE2] = fmt->format;
- } else if (fmt->pad == RESIZER_CROP_PAD_SOURCE2 &&
- resizer->crop_resizer.output2 == RESIZER_B) {
- resizer->crop_resizer.formats[fmt->pad] = fmt->format;
- resizer->crop_resizer.
- formats[RESIZER_CROP_PAD_SOURCE] = fmt->format;
- } else {
- return -EINVAL;
- }
- } else if (&resizer->resizer_a.subdev == sd) {
- if (fmt->pad == RESIZER_PAD_SINK)
- resizer->resizer_a.formats[fmt->pad] = fmt->format;
- else if (fmt->pad == RESIZER_PAD_SOURCE)
- resizer->resizer_a.formats[fmt->pad] = fmt->format;
- else
- return -EINVAL;
- } else if (&resizer->resizer_b.subdev == sd) {
- if (fmt->pad == RESIZER_PAD_SINK)
- resizer->resizer_b.formats[fmt->pad] = fmt->format;
- else if (fmt->pad == RESIZER_PAD_SOURCE)
- resizer->resizer_b.formats[fmt->pad] = fmt->format;
- else
- return -EINVAL;
- } else {
- return -EINVAL;
- }
-
- return 0;
-}
-
-/*
- * resizer_get_format() - Retrieve the video format on a pad
- * @sd: pointer to v4l2 subdev structure.
- * @cfg: V4L2 subdev pad config
- * @fmt: pointer to v4l2 subdev format structure
- * return -EINVAL or zero on success
- */
-static int resizer_get_format(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_format *fmt)
-{
- struct v4l2_mbus_framefmt *format;
-
- format = __resizer_get_format(sd, cfg, fmt->pad, fmt->which);
- if (format == NULL)
- return -EINVAL;
-
- fmt->format = *format;
-
- return 0;
-}
-
-/*
- * resizer_enum_frame_size() - enum frame sizes on pads
- * @sd: Pointer to subdevice.
- * @cfg: V4L2 subdev pad config
- * @code: pointer to v4l2_subdev_frame_size_enum structure.
- */
-static int resizer_enum_frame_size(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_frame_size_enum *fse)
-{
- struct v4l2_mbus_framefmt format;
-
- if (fse->index != 0)
- return -EINVAL;
-
- format.code = fse->code;
- format.width = 1;
- format.height = 1;
- resizer_try_format(sd, cfg, fse->pad, &format, fse->which);
- fse->min_width = format.width;
- fse->min_height = format.height;
-
- if (format.code != fse->code)
- return -EINVAL;
-
- format.code = fse->code;
- format.width = -1;
- format.height = -1;
- resizer_try_format(sd, cfg, fse->pad, &format, fse->which);
- fse->max_width = format.width;
- fse->max_height = format.height;
-
- return 0;
-}
-
-/*
- * resizer_enum_mbus_code() - enum mbus codes for pads
- * @sd: Pointer to subdevice.
- * @cfg: V4L2 subdev pad config
- * @code: pointer to v4l2_subdev_mbus_code_enum structure
- */
-static int resizer_enum_mbus_code(struct v4l2_subdev *sd,
- struct v4l2_subdev_pad_config *cfg,
- struct v4l2_subdev_mbus_code_enum *code)
-{
- if (code->pad == RESIZER_PAD_SINK) {
- if (code->index >= ARRAY_SIZE(resizer_input_formats))
- return -EINVAL;
-
- code->code = resizer_input_formats[code->index];
- } else if (code->pad == RESIZER_PAD_SOURCE) {
- if (code->index >= ARRAY_SIZE(resizer_output_formats))
- return -EINVAL;
-
- code->code = resizer_output_formats[code->index];
- }
-
- return 0;
-}
-
-/*
- * resizer_init_formats() - Initialize formats on all pads
- * @sd: Pointer to subdevice.
- * @fh: V4L2 subdev file handle.
- *
- * Initialize all pad formats with default values. Try formats are
- * initialized on the file handle.
- */
-static int resizer_init_formats(struct v4l2_subdev *sd,
- struct v4l2_subdev_fh *fh)
-{
- __u32 which = V4L2_SUBDEV_FORMAT_TRY;
- struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
- struct v4l2_subdev_format format;
-
- if (&resizer->crop_resizer.subdev == sd) {
- memset(&format, 0, sizeof(format));
- format.pad = RESIZER_CROP_PAD_SINK;
- format.which = which;
- format.format.code = MEDIA_BUS_FMT_YUYV8_2X8;
- format.format.width = MAX_IN_WIDTH;
- format.format.height = MAX_IN_HEIGHT;
- resizer_set_format(sd, fh->pad, &format);
-
- memset(&format, 0, sizeof(format));
- format.pad = RESIZER_CROP_PAD_SOURCE;
- format.which = which;
- format.format.code = MEDIA_BUS_FMT_UYVY8_2X8;
- format.format.width = MAX_IN_WIDTH;
- format.format.height = MAX_IN_WIDTH;
- resizer_set_format(sd, fh->pad, &format);
-
- memset(&format, 0, sizeof(format));
- format.pad = RESIZER_CROP_PAD_SOURCE2;
- format.which = which;
- format.format.code = MEDIA_BUS_FMT_UYVY8_2X8;
- format.format.width = MAX_IN_WIDTH;
- format.format.height = MAX_IN_WIDTH;
- resizer_set_format(sd, fh->pad, &format);
- } else if (&resizer->resizer_a.subdev == sd) {
- memset(&format, 0, sizeof(format));
- format.pad = RESIZER_PAD_SINK;
- format.which = which;
- format.format.code = MEDIA_BUS_FMT_YUYV8_2X8;
- format.format.width = MAX_IN_WIDTH;
- format.format.height = MAX_IN_HEIGHT;
- resizer_set_format(sd, fh->pad, &format);
-
- memset(&format, 0, sizeof(format));
- format.pad = RESIZER_PAD_SOURCE;
- format.which = which;
- format.format.code = MEDIA_BUS_FMT_UYVY8_2X8;
- format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
- format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
- resizer_set_format(sd, fh->pad, &format);
- } else if (&resizer->resizer_b.subdev == sd) {
- memset(&format, 0, sizeof(format));
- format.pad = RESIZER_PAD_SINK;
- format.which = which;
- format.format.code = MEDIA_BUS_FMT_YUYV8_2X8;
- format.format.width = MAX_IN_WIDTH;
- format.format.height = MAX_IN_HEIGHT;
- resizer_set_format(sd, fh->pad, &format);
-
- memset(&format, 0, sizeof(format));
- format.pad = RESIZER_PAD_SOURCE;
- format.which = which;
- format.format.code = MEDIA_BUS_FMT_UYVY8_2X8;
- format.format.width = IPIPE_MAX_OUTPUT_WIDTH_B;
- format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_B;
- resizer_set_format(sd, fh->pad, &format);
- }
-
- return 0;
-}
-
-/* subdev core operations */
-static const struct v4l2_subdev_core_ops resizer_v4l2_core_ops = {
- .ioctl = resizer_ioctl,
-};
-
-/* subdev internal operations */
-static const struct v4l2_subdev_internal_ops resizer_v4l2_internal_ops = {
- .open = resizer_init_formats,
-};
-
-/* subdev video operations */
-static const struct v4l2_subdev_video_ops resizer_v4l2_video_ops = {
- .s_stream = resizer_set_stream,
-};
-
-/* subdev pad operations */
-static const struct v4l2_subdev_pad_ops resizer_v4l2_pad_ops = {
- .enum_mbus_code = resizer_enum_mbus_code,
- .enum_frame_size = resizer_enum_frame_size,
- .get_fmt = resizer_get_format,
- .set_fmt = resizer_set_format,
-};
-
-/* subdev operations */
-static const struct v4l2_subdev_ops resizer_v4l2_ops = {
- .core = &resizer_v4l2_core_ops,
- .video = &resizer_v4l2_video_ops,
- .pad = &resizer_v4l2_pad_ops,
-};
-
-/*
- * Media entity operations
- */
-
-/*
- * resizer_link_setup() - Setup resizer connections
- * @entity: Pointer to media entity structure
- * @local: Pointer to local pad array
- * @remote: Pointer to remote pad array
- * @flags: Link flags
- * return -EINVAL or zero on success
- */
-static int resizer_link_setup(struct media_entity *entity,
- const struct media_pad *local,
- const struct media_pad *remote, u32 flags)
-{
- struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
- struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- u16 ipipeif_source = vpfe_dev->vpfe_ipipeif.output;
- u16 ipipe_source = vpfe_dev->vpfe_ipipe.output;
- unsigned int index = local->index;
-
- /* FIXME: this is actually a hack! */
- if (is_media_entity_v4l2_subdev(remote->entity))
- index |= 2 << 16;
-
- if (&resizer->crop_resizer.subdev == sd) {
- switch (index) {
- case RESIZER_CROP_PAD_SINK | 2 << 16:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- resizer->crop_resizer.input =
- RESIZER_CROP_INPUT_NONE;
- break;
- }
-
- if (resizer->crop_resizer.input !=
- RESIZER_CROP_INPUT_NONE)
- return -EBUSY;
- if (ipipeif_source == IPIPEIF_OUTPUT_RESIZER)
- resizer->crop_resizer.input =
- RESIZER_CROP_INPUT_IPIPEIF;
- else if (ipipe_source == IPIPE_OUTPUT_RESIZER)
- resizer->crop_resizer.input =
- RESIZER_CROP_INPUT_IPIPE;
- else
- return -EINVAL;
- break;
-
- case RESIZER_CROP_PAD_SOURCE | 2 << 16:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- resizer->crop_resizer.output =
- RESIZER_CROP_OUTPUT_NONE;
- break;
- }
- if (resizer->crop_resizer.output !=
- RESIZER_CROP_OUTPUT_NONE)
- return -EBUSY;
- resizer->crop_resizer.output = RESIZER_A;
- break;
-
- case RESIZER_CROP_PAD_SOURCE2 | 2 << 16:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- resizer->crop_resizer.output2 =
- RESIZER_CROP_OUTPUT_NONE;
- break;
- }
- if (resizer->crop_resizer.output2 !=
- RESIZER_CROP_OUTPUT_NONE)
- return -EBUSY;
- resizer->crop_resizer.output2 = RESIZER_B;
- break;
-
- default:
- return -EINVAL;
- }
- } else if (&resizer->resizer_a.subdev == sd) {
- switch (index) {
- case RESIZER_PAD_SINK | 2 << 16:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- resizer->resizer_a.input = RESIZER_INPUT_NONE;
- break;
- }
- if (resizer->resizer_a.input != RESIZER_INPUT_NONE)
- return -EBUSY;
- resizer->resizer_a.input = RESIZER_INPUT_CROP_RESIZER;
- break;
-
- case RESIZER_PAD_SOURCE:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- resizer->resizer_a.output = RESIZER_OUTPUT_NONE;
- break;
- }
- if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE)
- return -EBUSY;
- resizer->resizer_a.output = RESIZER_OUTPUT_MEMORY;
- break;
-
- default:
- return -EINVAL;
- }
- } else if (&resizer->resizer_b.subdev == sd) {
- switch (index) {
- case RESIZER_PAD_SINK | 2 << 16:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- resizer->resizer_b.input = RESIZER_INPUT_NONE;
- break;
- }
- if (resizer->resizer_b.input != RESIZER_INPUT_NONE)
- return -EBUSY;
- resizer->resizer_b.input = RESIZER_INPUT_CROP_RESIZER;
- break;
-
- case RESIZER_PAD_SOURCE:
- if (!(flags & MEDIA_LNK_FL_ENABLED)) {
- resizer->resizer_b.output = RESIZER_OUTPUT_NONE;
- break;
- }
- if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE)
- return -EBUSY;
- resizer->resizer_b.output = RESIZER_OUTPUT_MEMORY;
- break;
-
- default:
- return -EINVAL;
- }
- } else {
- return -EINVAL;
- }
-
- return 0;
-}
-
-static const struct media_entity_operations resizer_media_ops = {
- .link_setup = resizer_link_setup,
-};
-
-/*
- * vpfe_resizer_unregister_entities() - Unregister entity
- * @vpfe_rsz - pointer to resizer subdevice structure.
- */
-void vpfe_resizer_unregister_entities(struct vpfe_resizer_device *vpfe_rsz)
-{
- /* unregister video devices */
- vpfe_video_unregister(&vpfe_rsz->resizer_a.video_out);
- vpfe_video_unregister(&vpfe_rsz->resizer_b.video_out);
-
- /* unregister subdev */
- v4l2_device_unregister_subdev(&vpfe_rsz->crop_resizer.subdev);
- v4l2_device_unregister_subdev(&vpfe_rsz->resizer_a.subdev);
- v4l2_device_unregister_subdev(&vpfe_rsz->resizer_b.subdev);
- /* cleanup entity */
- media_entity_cleanup(&vpfe_rsz->crop_resizer.subdev.entity);
- media_entity_cleanup(&vpfe_rsz->resizer_a.subdev.entity);
- media_entity_cleanup(&vpfe_rsz->resizer_b.subdev.entity);
-}
-
-/*
- * vpfe_resizer_register_entities() - Register entity
- * @resizer - pointer to resizer device.
- * @vdev: pointer to v4l2 device structure.
- */
-int vpfe_resizer_register_entities(struct vpfe_resizer_device *resizer,
- struct v4l2_device *vdev)
-{
- struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
- unsigned int flags = 0;
- int ret;
-
- /* Register the crop resizer subdev */
- ret = v4l2_device_register_subdev(vdev, &resizer->crop_resizer.subdev);
- if (ret < 0) {
- pr_err("Failed to register crop resizer as v4l2-subdev\n");
- return ret;
- }
- /* Register Resizer-A subdev */
- ret = v4l2_device_register_subdev(vdev, &resizer->resizer_a.subdev);
- if (ret < 0) {
- pr_err("Failed to register resizer-a as v4l2-subdev\n");
- return ret;
- }
- /* Register Resizer-B subdev */
- ret = v4l2_device_register_subdev(vdev, &resizer->resizer_b.subdev);
- if (ret < 0) {
- pr_err("Failed to register resizer-b as v4l2-subdev\n");
- return ret;
- }
- /* Register video-out device for resizer-a */
- ret = vpfe_video_register(&resizer->resizer_a.video_out, vdev);
- if (ret) {
- pr_err("Failed to register RSZ-A video-out device\n");
- goto out_video_out2_register;
- }
- resizer->resizer_a.video_out.vpfe_dev = vpfe_dev;
-
- /* Register video-out device for resizer-b */
- ret = vpfe_video_register(&resizer->resizer_b.video_out, vdev);
- if (ret) {
- pr_err("Failed to register RSZ-B video-out device\n");
- goto out_video_out2_register;
- }
- resizer->resizer_b.video_out.vpfe_dev = vpfe_dev;
-
- /* create link between Resizer Crop----> Resizer A*/
- ret = media_create_pad_link(&resizer->crop_resizer.subdev.entity, 1,
- &resizer->resizer_a.subdev.entity,
- 0, flags);
- if (ret < 0)
- goto out_create_link;
-
- /* create link between Resizer Crop----> Resizer B*/
- ret = media_create_pad_link(&resizer->crop_resizer.subdev.entity, 2,
- &resizer->resizer_b.subdev.entity,
- 0, flags);
- if (ret < 0)
- goto out_create_link;
-
- /* create link between Resizer A ----> video out */
- ret = media_create_pad_link(&resizer->resizer_a.subdev.entity, 1,
- &resizer->resizer_a.video_out.video_dev.entity, 0, flags);
- if (ret < 0)
- goto out_create_link;
-
- /* create link between Resizer B ----> video out */
- ret = media_create_pad_link(&resizer->resizer_b.subdev.entity, 1,
- &resizer->resizer_b.video_out.video_dev.entity, 0, flags);
- if (ret < 0)
- goto out_create_link;
-
- return 0;
-
-out_create_link:
- vpfe_video_unregister(&resizer->resizer_b.video_out);
-out_video_out2_register:
- vpfe_video_unregister(&resizer->resizer_a.video_out);
- v4l2_device_unregister_subdev(&resizer->crop_resizer.subdev);
- v4l2_device_unregister_subdev(&resizer->resizer_a.subdev);
- v4l2_device_unregister_subdev(&resizer->resizer_b.subdev);
- media_entity_cleanup(&resizer->crop_resizer.subdev.entity);
- media_entity_cleanup(&resizer->resizer_a.subdev.entity);
- media_entity_cleanup(&resizer->resizer_b.subdev.entity);
- return ret;
-}
-
-/*
- * vpfe_resizer_init() - resizer device initialization.
- * @vpfe_rsz - pointer to resizer device
- * @pdev: platform device pointer.
- */
-int vpfe_resizer_init(struct vpfe_resizer_device *vpfe_rsz,
- struct platform_device *pdev)
-{
- struct v4l2_subdev *sd = &vpfe_rsz->crop_resizer.subdev;
- struct media_pad *pads = &vpfe_rsz->crop_resizer.pads[0];
- struct media_entity *me = &sd->entity;
- resource_size_t res_len;
- struct resource *res;
- int ret;
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 5);
- if (!res)
- return -ENOENT;
-
- res_len = resource_size(res);
- res = request_mem_region(res->start, res_len, res->name);
- if (!res)
- return -EBUSY;
-
- vpfe_rsz->base_addr = ioremap_nocache(res->start, res_len);
- if (!vpfe_rsz->base_addr)
- return -EBUSY;
-
- v4l2_subdev_init(sd, &resizer_v4l2_ops);
- sd->internal_ops = &resizer_v4l2_internal_ops;
- strscpy(sd->name, "DAVINCI RESIZER CROP", sizeof(sd->name));
- sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
- v4l2_set_subdevdata(sd, vpfe_rsz);
- sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
-
- pads[RESIZER_CROP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- pads[RESIZER_CROP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
- pads[RESIZER_CROP_PAD_SOURCE2].flags = MEDIA_PAD_FL_SOURCE;
-
- vpfe_rsz->crop_resizer.input = RESIZER_CROP_INPUT_NONE;
- vpfe_rsz->crop_resizer.output = RESIZER_CROP_OUTPUT_NONE;
- vpfe_rsz->crop_resizer.output2 = RESIZER_CROP_OUTPUT_NONE;
- vpfe_rsz->crop_resizer.rsz_device = vpfe_rsz;
- me->ops = &resizer_media_ops;
- ret = media_entity_pads_init(me, RESIZER_CROP_PADS_NUM, pads);
- if (ret)
- return ret;
-
- sd = &vpfe_rsz->resizer_a.subdev;
- pads = &vpfe_rsz->resizer_a.pads[0];
- me = &sd->entity;
-
- v4l2_subdev_init(sd, &resizer_v4l2_ops);
- sd->internal_ops = &resizer_v4l2_internal_ops;
- strscpy(sd->name, "DAVINCI RESIZER A", sizeof(sd->name));
- sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
- v4l2_set_subdevdata(sd, vpfe_rsz);
- sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
-
- pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- pads[RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
-
- vpfe_rsz->resizer_a.input = RESIZER_INPUT_NONE;
- vpfe_rsz->resizer_a.output = RESIZER_OUTPUT_NONE;
- vpfe_rsz->resizer_a.rsz_device = vpfe_rsz;
- me->ops = &resizer_media_ops;
- ret = media_entity_pads_init(me, RESIZER_PADS_NUM, pads);
- if (ret)
- return ret;
-
- sd = &vpfe_rsz->resizer_b.subdev;
- pads = &vpfe_rsz->resizer_b.pads[0];
- me = &sd->entity;
-
- v4l2_subdev_init(sd, &resizer_v4l2_ops);
- sd->internal_ops = &resizer_v4l2_internal_ops;
- strscpy(sd->name, "DAVINCI RESIZER B", sizeof(sd->name));
- sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
- v4l2_set_subdevdata(sd, vpfe_rsz);
- sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
-
- pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
- pads[RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
-
- vpfe_rsz->resizer_b.input = RESIZER_INPUT_NONE;
- vpfe_rsz->resizer_b.output = RESIZER_OUTPUT_NONE;
- vpfe_rsz->resizer_b.rsz_device = vpfe_rsz;
- me->ops = &resizer_media_ops;
- ret = media_entity_pads_init(me, RESIZER_PADS_NUM, pads);
- if (ret)
- return ret;
-
- vpfe_rsz->resizer_a.video_out.ops = &resizer_a_video_ops;
- vpfe_rsz->resizer_a.video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ret = vpfe_video_init(&vpfe_rsz->resizer_a.video_out, "RSZ-A");
- if (ret) {
- pr_err("Failed to init RSZ video-out device\n");
- return ret;
- }
- vpfe_rsz->resizer_b.video_out.ops = &resizer_b_video_ops;
- vpfe_rsz->resizer_b.video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- ret = vpfe_video_init(&vpfe_rsz->resizer_b.video_out, "RSZ-B");
- if (ret) {
- pr_err("Failed to init RSZ video-out2 device\n");
- return ret;
- }
- memset(&vpfe_rsz->config, 0, sizeof(struct resizer_params));
-
- return 0;
-}
-
-void
-vpfe_resizer_cleanup(struct vpfe_resizer_device *vpfe_rsz,
- struct platform_device *pdev)
-{
- struct resource *res;
-
- iounmap(vpfe_rsz->base_addr);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 5);
- if (res)
- release_mem_region(res->start,
- resource_size(res));
-}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_resizer.h b/drivers/staging/media/davinci_vpfe/dm365_resizer.h
deleted file mode 100644
index 5e31de96b2c9..000000000000
--- a/drivers/staging/media/davinci_vpfe/dm365_resizer.h
+++ /dev/null
@@ -1,241 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_DM365_RESIZER_H
-#define _DAVINCI_VPFE_DM365_RESIZER_H
-
-enum resizer_oper_mode {
- RESIZER_MODE_CONTINUOUS = 0,
- RESIZER_MODE_ONE_SHOT = 1,
-};
-
-struct f_div_pass {
- unsigned int o_hsz;
- unsigned int i_hps;
- unsigned int h_phs;
- unsigned int src_hps;
- unsigned int src_hsz;
-};
-
-#define MAX_PASSES 2
-
-struct f_div_param {
- unsigned char en;
- unsigned int num_passes;
- struct f_div_pass pass[MAX_PASSES];
-};
-
-/* Resizer Rescale Parameters*/
-struct resizer_scale_param {
- bool h_flip;
- bool v_flip;
- bool cen;
- bool yen;
- unsigned short i_vps;
- unsigned short i_hps;
- unsigned short o_vsz;
- unsigned short o_hsz;
- unsigned short v_phs_y;
- unsigned short v_phs_c;
- unsigned short v_dif;
- /* resize method - Luminance */
- enum vpfe_rsz_intp_t v_typ_y;
- /* resize method - Chrominance */
- enum vpfe_rsz_intp_t v_typ_c;
- /* vertical lpf intensity - Luminance */
- unsigned char v_lpf_int_y;
- /* vertical lpf intensity - Chrominance */
- unsigned char v_lpf_int_c;
- unsigned short h_phs;
- unsigned short h_dif;
- /* resize method - Luminance */
- enum vpfe_rsz_intp_t h_typ_y;
- /* resize method - Chrominance */
- enum vpfe_rsz_intp_t h_typ_c;
- /* horizontal lpf intensity - Luminance */
- unsigned char h_lpf_int_y;
- /* horizontal lpf intensity - Chrominance */
- unsigned char h_lpf_int_c;
- bool dscale_en;
- enum vpfe_rsz_down_scale_ave_sz h_dscale_ave_sz;
- enum vpfe_rsz_down_scale_ave_sz v_dscale_ave_sz;
- /* store the calculated frame division parameter */
- struct f_div_param f_div;
-};
-
-enum resizer_rgb_t {
- OUTPUT_32BIT,
- OUTPUT_16BIT
-};
-
-enum resizer_rgb_msk_t {
- NOMASK = 0,
- MASKLAST2 = 1,
-};
-
-/* Resizer RGB Conversion Parameters */
-struct resizer_rgb {
- bool rgb_en;
- enum resizer_rgb_t rgb_typ;
- enum resizer_rgb_msk_t rgb_msk0;
- enum resizer_rgb_msk_t rgb_msk1;
- unsigned int rgb_alpha_val;
-};
-
-/* Resizer External Memory Parameters */
-struct rsz_ext_mem_param {
- unsigned int rsz_sdr_oft_y;
- unsigned int rsz_sdr_ptr_s_y;
- unsigned int rsz_sdr_ptr_e_y;
- unsigned int rsz_sdr_oft_c;
- unsigned int rsz_sdr_ptr_s_c;
- unsigned int rsz_sdr_ptr_e_c;
- /* offset to be added to buffer start when flipping for y/ycbcr */
- unsigned int flip_ofst_y;
- /* offset to be added to buffer start when flipping for c */
- unsigned int flip_ofst_c;
- /* c offset for YUV 420SP */
- unsigned int c_offset;
- /* User Defined Y offset for YUV 420SP or YUV420ILE data */
- unsigned int user_y_ofst;
- /* User Defined C offset for YUV 420SP data */
- unsigned int user_c_ofst;
-};
-
-enum rsz_data_source {
- IPIPE_DATA,
- IPIPEIF_DATA
-};
-
-enum rsz_src_img_fmt {
- RSZ_IMG_422,
- RSZ_IMG_420
-};
-
-enum rsz_dpaths_bypass_t {
- BYPASS_OFF = 0,
- BYPASS_ON = 1,
-};
-
-struct rsz_common_params {
- unsigned int vps;
- unsigned int vsz;
- unsigned int hps;
- unsigned int hsz;
- /* 420 or 422 */
- enum rsz_src_img_fmt src_img_fmt;
- /* Y or C when src_fmt is 420, 0 - y, 1 - c */
- unsigned char y_c;
- /* flip raw or ycbcr */
- unsigned char raw_flip;
- /* IPIPE or IPIPEIF data */
- enum rsz_data_source source;
- enum rsz_dpaths_bypass_t passthrough;
- unsigned char yuv_y_min;
- unsigned char yuv_y_max;
- unsigned char yuv_c_min;
- unsigned char yuv_c_max;
- bool rsz_seq_crv;
- enum vpfe_chr_pos out_chr_pos;
-};
-
-struct resizer_params {
- enum resizer_oper_mode oper_mode;
- struct rsz_common_params rsz_common;
- struct resizer_scale_param rsz_rsc_param[2];
- struct resizer_rgb rsz2rgb[2];
- struct rsz_ext_mem_param ext_mem_param[2];
- bool rsz_en[2];
- struct vpfe_rsz_config_params user_config;
-};
-
-#define ENABLE 1
-#define DISABLE (!ENABLE)
-
-#define RESIZER_CROP_PAD_SINK 0
-#define RESIZER_CROP_PAD_SOURCE 1
-#define RESIZER_CROP_PAD_SOURCE2 2
-
-#define RESIZER_CROP_PADS_NUM 3
-
-enum resizer_crop_input_entity {
- RESIZER_CROP_INPUT_NONE = 0,
- RESIZER_CROP_INPUT_IPIPEIF = 1,
- RESIZER_CROP_INPUT_IPIPE = 2,
-};
-
-enum resizer_crop_output_entity {
- RESIZER_CROP_OUTPUT_NONE,
- RESIZER_A,
- RESIZER_B,
-};
-
-struct dm365_crop_resizer_device {
- struct v4l2_subdev subdev;
- struct media_pad pads[RESIZER_CROP_PADS_NUM];
- struct v4l2_mbus_framefmt formats[RESIZER_CROP_PADS_NUM];
- enum resizer_crop_input_entity input;
- enum resizer_crop_output_entity output;
- enum resizer_crop_output_entity output2;
- struct vpfe_resizer_device *rsz_device;
-};
-
-#define RESIZER_PAD_SINK 0
-#define RESIZER_PAD_SOURCE 1
-
-#define RESIZER_PADS_NUM 2
-
-enum resizer_input_entity {
- RESIZER_INPUT_NONE = 0,
- RESIZER_INPUT_CROP_RESIZER = 1,
-};
-
-enum resizer_output_entity {
- RESIZER_OUTPUT_NONE = 0,
- RESIZER_OUTPUT_MEMORY = 1,
-};
-
-struct dm365_resizer_device {
- struct v4l2_subdev subdev;
- struct media_pad pads[RESIZER_PADS_NUM];
- struct v4l2_mbus_framefmt formats[RESIZER_PADS_NUM];
- enum resizer_input_entity input;
- enum resizer_output_entity output;
- struct vpfe_video_device video_out;
- struct vpfe_resizer_device *rsz_device;
-};
-
-struct vpfe_resizer_device {
- struct dm365_crop_resizer_device crop_resizer;
- struct dm365_resizer_device resizer_a;
- struct dm365_resizer_device resizer_b;
- struct resizer_params config;
- void __iomem *base_addr;
-};
-
-int vpfe_resizer_init(struct vpfe_resizer_device *vpfe_rsz,
- struct platform_device *pdev);
-int vpfe_resizer_register_entities(struct vpfe_resizer_device *vpfe_rsz,
- struct v4l2_device *v4l2_dev);
-void vpfe_resizer_unregister_entities(struct vpfe_resizer_device *vpfe_rsz);
-void vpfe_resizer_cleanup(struct vpfe_resizer_device *vpfe_rsz,
- struct platform_device *pdev);
-void vpfe_resizer_buffer_isr(struct vpfe_resizer_device *resizer);
-void vpfe_resizer_dma_isr(struct vpfe_resizer_device *resizer);
-
-#endif /* _DAVINCI_VPFE_DM365_RESIZER_H */
diff --git a/drivers/staging/media/davinci_vpfe/vpfe.h b/drivers/staging/media/davinci_vpfe/vpfe.h
deleted file mode 100644
index 1f8e011fc162..000000000000
--- a/drivers/staging/media/davinci_vpfe/vpfe.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _VPFE_H
-#define _VPFE_H
-
-#ifdef __KERNEL__
-#include <linux/v4l2-subdev.h>
-#include <linux/clk.h>
-#include <linux/i2c.h>
-
-#include <media/davinci/vpfe_types.h>
-
-#define CAPTURE_DRV_NAME "vpfe-capture"
-
-struct vpfe_route {
- __u32 input;
- __u32 output;
-};
-
-enum vpfe_subdev_id {
- VPFE_SUBDEV_TVP5146 = 1,
- VPFE_SUBDEV_MT9T031 = 2,
- VPFE_SUBDEV_TVP7002 = 3,
- VPFE_SUBDEV_MT9P031 = 4,
-};
-
-struct vpfe_ext_subdev_info {
- /* v4l2 subdev */
- struct v4l2_subdev *subdev;
- /* Sub device module name */
- char module_name[32];
- /* Sub device group id */
- int grp_id;
- /* Number of inputs supported */
- int num_inputs;
- /* inputs available at the sub device */
- struct v4l2_input *inputs;
- /* Sub dev routing information for each input */
- struct vpfe_route *routes;
- /* ccdc bus/interface configuration */
- struct vpfe_hw_if_param ccdc_if_params;
- /* i2c subdevice board info */
- struct i2c_board_info board_info;
- /* Is this a camera sub device ? */
- unsigned is_camera:1;
- /* check if sub dev supports routing */
- unsigned can_route:1;
- /* registered ? */
- unsigned registered:1;
-};
-
-struct vpfe_config {
- /* Number of sub devices connected to vpfe */
- int num_subdevs;
- /* information about each subdev */
- struct vpfe_ext_subdev_info *sub_devs;
- /* evm card info */
- char *card_name;
- /* setup function for the input path */
- int (*setup_input)(enum vpfe_subdev_id id);
- /* number of clocks */
- int num_clocks;
- /* clocks used for vpfe capture */
- char *clocks[];
-};
-#endif
-#endif
diff --git a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c
deleted file mode 100644
index 9dc28ffe38d5..000000000000
--- a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c
+++ /dev/null
@@ -1,716 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- *
- *
- * Driver name : VPFE Capture driver
- * VPFE Capture driver allows applications to capture and stream video
- * frames on DaVinci SoCs (DM6446, DM355 etc) from a YUV source such as
- * TVP5146 or Raw Bayer RGB image data from an image sensor
- * such as Microns' MT9T001, MT9T031 etc.
- *
- * These SoCs have, in common, a Video Processing Subsystem (VPSS) that
- * consists of a Video Processing Front End (VPFE) for capturing
- * video/raw image data and Video Processing Back End (VPBE) for displaying
- * YUV data through an in-built analog encoder or Digital LCD port. This
- * driver is for capture through VPFE. A typical EVM using these SoCs have
- * following high level configuration.
- *
- * decoder(TVP5146/ YUV/
- * MT9T001) --> Raw Bayer RGB ---> MUX -> VPFE (CCDC/ISIF)
- * data input | |
- * V |
- * SDRAM |
- * V
- * Image Processor
- * |
- * V
- * SDRAM
- * The data flow happens from a decoder connected to the VPFE over a
- * YUV embedded (BT.656/BT.1120) or separate sync or raw bayer rgb interface
- * and to the input of VPFE through an optional MUX (if more inputs are
- * to be interfaced on the EVM). The input data is first passed through
- * CCDC (CCD Controller, a.k.a Image Sensor Interface, ISIF). The CCDC
- * does very little or no processing on YUV data and does pre-process Raw
- * Bayer RGB data through modules such as Defect Pixel Correction (DFC)
- * Color Space Conversion (CSC), data gain/offset etc. After this, data
- * can be written to SDRAM or can be connected to the image processing
- * block such as IPIPE (on DM355/DM365 only).
- *
- * Features supported
- * - MMAP IO
- * - USERPTR IO
- * - Capture using TVP5146 over BT.656
- * - Support for interfacing decoders using sub device model
- * - Work with DM365 or DM355 or DM6446 CCDC to do Raw Bayer
- * RGB/YUV data capture to SDRAM.
- * - Chaining of Image Processor
- * - SINGLE-SHOT mode
- */
-
-#include <linux/interrupt.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-
-#include "vpfe.h"
-#include "vpfe_mc_capture.h"
-
-static bool debug;
-static bool interface;
-
-module_param(interface, bool, 0444);
-module_param(debug, bool, 0644);
-
-/*
- * VPFE capture can be used for capturing video such as from TVP5146 or TVP7002
- * and for capture raw bayer data from camera sensors such as mt9p031. At this
- * point there is problem in co-existence of mt9p031 and tvp5146 due to i2c
- * address collision. So set the variable below from bootargs to do either video
- * capture or camera capture.
- * interface = 0 - video capture (from TVP514x or such),
- * interface = 1 - Camera capture (from mt9p031 or such)
- * Re-visit this when we fix the co-existence issue
- */
-MODULE_PARM_DESC(interface, "interface 0-1 (default:0)");
-MODULE_PARM_DESC(debug, "Debug level 0-1");
-
-MODULE_DESCRIPTION("VPFE Video for Linux Capture Driver");
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Texas Instruments");
-
-/* map mbus_fmt to pixelformat */
-void mbus_to_pix(const struct v4l2_mbus_framefmt *mbus,
- struct v4l2_pix_format *pix)
-{
- switch (mbus->code) {
- case MEDIA_BUS_FMT_UYVY8_2X8:
- pix->pixelformat = V4L2_PIX_FMT_UYVY;
- pix->bytesperline = pix->width * 2;
- break;
-
- case MEDIA_BUS_FMT_YUYV8_2X8:
- pix->pixelformat = V4L2_PIX_FMT_YUYV;
- pix->bytesperline = pix->width * 2;
- break;
-
- case MEDIA_BUS_FMT_YUYV10_1X20:
- pix->pixelformat = V4L2_PIX_FMT_UYVY;
- pix->bytesperline = pix->width * 2;
- break;
-
- case MEDIA_BUS_FMT_SGRBG12_1X12:
- pix->pixelformat = V4L2_PIX_FMT_SBGGR16;
- pix->bytesperline = pix->width * 2;
- break;
-
- case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8:
- pix->pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8;
- pix->bytesperline = pix->width;
- break;
-
- case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8:
- pix->pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8;
- pix->bytesperline = pix->width;
- break;
-
- case MEDIA_BUS_FMT_YDYUYDYV8_1X16:
- pix->pixelformat = V4L2_PIX_FMT_NV12;
- pix->bytesperline = pix->width;
- break;
-
- case MEDIA_BUS_FMT_Y8_1X8:
- pix->pixelformat = V4L2_PIX_FMT_GREY;
- pix->bytesperline = pix->width;
- break;
-
- case MEDIA_BUS_FMT_UV8_1X8:
- pix->pixelformat = V4L2_PIX_FMT_UV8;
- pix->bytesperline = pix->width;
- break;
-
- default:
- pr_err("Invalid mbus code set\n");
- }
- /* pitch should be 32 bytes aligned */
- pix->bytesperline = ALIGN(pix->bytesperline, 32);
- if (pix->pixelformat == V4L2_PIX_FMT_NV12)
- pix->sizeimage = pix->bytesperline * pix->height +
- ((pix->bytesperline * pix->height) >> 1);
- else
- pix->sizeimage = pix->bytesperline * pix->height;
-}
-
-/* ISR for VINT0*/
-static irqreturn_t vpfe_isr(int irq, void *dev_id)
-{
- struct vpfe_device *vpfe_dev = dev_id;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "%s\n", __func__);
- vpfe_isif_buffer_isr(&vpfe_dev->vpfe_isif);
- vpfe_resizer_buffer_isr(&vpfe_dev->vpfe_resizer);
- return IRQ_HANDLED;
-}
-
-/* vpfe_vdint1_isr() - isr handler for VINT1 interrupt */
-static irqreturn_t vpfe_vdint1_isr(int irq, void *dev_id)
-{
- struct vpfe_device *vpfe_dev = dev_id;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "%s\n", __func__);
- vpfe_isif_vidint1_isr(&vpfe_dev->vpfe_isif);
- return IRQ_HANDLED;
-}
-
-/* vpfe_imp_dma_isr() - ISR for ipipe dma completion */
-static irqreturn_t vpfe_imp_dma_isr(int irq, void *dev_id)
-{
- struct vpfe_device *vpfe_dev = dev_id;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "%s\n", __func__);
- vpfe_ipipeif_ss_buffer_isr(&vpfe_dev->vpfe_ipipeif);
- vpfe_resizer_dma_isr(&vpfe_dev->vpfe_resizer);
- return IRQ_HANDLED;
-}
-
-/*
- * vpfe_disable_clock() - Disable clocks for vpfe capture driver
- * @vpfe_dev - ptr to vpfe capture device
- *
- * Disables clocks defined in vpfe configuration. The function
- * assumes that at least one clock is to be defined which is
- * true as of now.
- */
-static void vpfe_disable_clock(struct vpfe_device *vpfe_dev)
-{
- struct vpfe_config *vpfe_cfg = vpfe_dev->cfg;
- int i;
-
- for (i = 0; i < vpfe_cfg->num_clocks; i++) {
- clk_disable_unprepare(vpfe_dev->clks[i]);
- clk_put(vpfe_dev->clks[i]);
- }
- kzfree(vpfe_dev->clks);
- v4l2_info(vpfe_dev->pdev->driver, "vpfe capture clocks disabled\n");
-}
-
-/*
- * vpfe_enable_clock() - Enable clocks for vpfe capture driver
- * @vpfe_dev - ptr to vpfe capture device
- *
- * Enables clocks defined in vpfe configuration. The function
- * assumes that at least one clock is to be defined which is
- * true as of now.
- */
-static int vpfe_enable_clock(struct vpfe_device *vpfe_dev)
-{
- struct vpfe_config *vpfe_cfg = vpfe_dev->cfg;
- int ret = -EFAULT;
- int i;
-
- if (!vpfe_cfg->num_clocks)
- return 0;
-
- vpfe_dev->clks = kcalloc(vpfe_cfg->num_clocks,
- sizeof(*vpfe_dev->clks), GFP_KERNEL);
- if (!vpfe_dev->clks)
- return -ENOMEM;
-
- for (i = 0; i < vpfe_cfg->num_clocks; i++) {
- if (vpfe_cfg->clocks[i] == NULL) {
- v4l2_err(vpfe_dev->pdev->driver,
- "clock %s is not defined in vpfe config\n",
- vpfe_cfg->clocks[i]);
- goto out;
- }
-
- vpfe_dev->clks[i] =
- clk_get(vpfe_dev->pdev, vpfe_cfg->clocks[i]);
- if (IS_ERR(vpfe_dev->clks[i])) {
- v4l2_err(vpfe_dev->pdev->driver,
- "Failed to get clock %s\n",
- vpfe_cfg->clocks[i]);
- goto out;
- }
-
- if (clk_prepare_enable(vpfe_dev->clks[i])) {
- v4l2_err(vpfe_dev->pdev->driver,
- "vpfe clock %s not enabled\n",
- vpfe_cfg->clocks[i]);
- goto out;
- }
-
- v4l2_info(vpfe_dev->pdev->driver, "vpss clock %s enabled",
- vpfe_cfg->clocks[i]);
- }
-
- return 0;
-out:
- for (i = 0; i < vpfe_cfg->num_clocks; i++)
- if (!IS_ERR(vpfe_dev->clks[i])) {
- clk_disable_unprepare(vpfe_dev->clks[i]);
- clk_put(vpfe_dev->clks[i]);
- }
-
- v4l2_err(vpfe_dev->pdev->driver, "Failed to enable clocks\n");
- kzfree(vpfe_dev->clks);
-
- return ret;
-}
-
-/*
- * vpfe_detach_irq() - Detach IRQs for vpfe capture driver
- * @vpfe_dev - ptr to vpfe capture device
- *
- * Detach all IRQs defined in vpfe configuration.
- */
-static void vpfe_detach_irq(struct vpfe_device *vpfe_dev)
-{
- free_irq(vpfe_dev->ccdc_irq0, vpfe_dev);
- free_irq(vpfe_dev->ccdc_irq1, vpfe_dev);
- free_irq(vpfe_dev->imp_dma_irq, vpfe_dev);
-}
-
-/*
- * vpfe_attach_irq() - Attach IRQs for vpfe capture driver
- * @vpfe_dev - ptr to vpfe capture device
- *
- * Attach all IRQs defined in vpfe configuration.
- */
-static int vpfe_attach_irq(struct vpfe_device *vpfe_dev)
-{
- int ret;
-
- ret = request_irq(vpfe_dev->ccdc_irq0, vpfe_isr, 0,
- "vpfe_capture0", vpfe_dev);
- if (ret < 0) {
- v4l2_err(&vpfe_dev->v4l2_dev,
- "Error: requesting VINT0 interrupt\n");
- return ret;
- }
-
- ret = request_irq(vpfe_dev->ccdc_irq1, vpfe_vdint1_isr, 0,
- "vpfe_capture1", vpfe_dev);
- if (ret < 0) {
- v4l2_err(&vpfe_dev->v4l2_dev,
- "Error: requesting VINT1 interrupt\n");
- free_irq(vpfe_dev->ccdc_irq0, vpfe_dev);
- return ret;
- }
-
- ret = request_irq(vpfe_dev->imp_dma_irq, vpfe_imp_dma_isr,
- 0, "Imp_Sdram_Irq", vpfe_dev);
- if (ret < 0) {
- v4l2_err(&vpfe_dev->v4l2_dev,
- "Error: requesting IMP IRQ interrupt\n");
- free_irq(vpfe_dev->ccdc_irq1, vpfe_dev);
- free_irq(vpfe_dev->ccdc_irq0, vpfe_dev);
- return ret;
- }
-
- return 0;
-}
-
-/*
- * register_i2c_devices() - register all i2c v4l2 subdevs
- * @vpfe_dev - ptr to vpfe capture device
- *
- * register all i2c v4l2 subdevs
- */
-static int register_i2c_devices(struct vpfe_device *vpfe_dev)
-{
- struct vpfe_ext_subdev_info *sdinfo;
- struct vpfe_config *vpfe_cfg;
- struct i2c_adapter *i2c_adap;
- unsigned int num_subdevs;
- int ret;
- int i;
- int k;
-
- vpfe_cfg = vpfe_dev->cfg;
- i2c_adap = i2c_get_adapter(1);
- num_subdevs = vpfe_cfg->num_subdevs;
- vpfe_dev->sd =
- kcalloc(num_subdevs, sizeof(struct v4l2_subdev *),
- GFP_KERNEL);
- if (!vpfe_dev->sd)
- return -ENOMEM;
-
- for (i = 0, k = 0; i < num_subdevs; i++) {
- sdinfo = &vpfe_cfg->sub_devs[i];
- /*
- * register subdevices based on interface setting. Currently
- * tvp5146 and mt9p031 cannot co-exists due to i2c address
- * conflicts. So only one of them is registered. Re-visit this
- * once we have support for i2c switch handling in i2c driver
- * framework
- */
- if (interface == sdinfo->is_camera) {
- /* setup input path */
- if (vpfe_cfg->setup_input &&
- vpfe_cfg->setup_input(sdinfo->grp_id) < 0) {
- ret = -EFAULT;
- v4l2_info(&vpfe_dev->v4l2_dev,
- "could not setup input for %s\n",
- sdinfo->module_name);
- goto probe_sd_out;
- }
- /* Load up the subdevice */
- vpfe_dev->sd[k] =
- v4l2_i2c_new_subdev_board(&vpfe_dev->v4l2_dev,
- i2c_adap, &sdinfo->board_info,
- NULL);
- if (vpfe_dev->sd[k]) {
- v4l2_info(&vpfe_dev->v4l2_dev,
- "v4l2 sub device %s registered\n",
- sdinfo->module_name);
-
- vpfe_dev->sd[k]->grp_id = sdinfo->grp_id;
- k++;
-
- sdinfo->registered = 1;
- }
- } else {
- v4l2_info(&vpfe_dev->v4l2_dev,
- "v4l2 sub device %s is not registered\n",
- sdinfo->module_name);
- }
- }
- vpfe_dev->num_ext_subdevs = k;
-
- return 0;
-
-probe_sd_out:
- kzfree(vpfe_dev->sd);
-
- return ret;
-}
-
-/*
- * vpfe_register_entities() - register all v4l2 subdevs and media entities
- * @vpfe_dev - ptr to vpfe capture device
- *
- * register all v4l2 subdevs, media entities, and creates links
- * between entities
- */
-static int vpfe_register_entities(struct vpfe_device *vpfe_dev)
-{
- unsigned int flags = 0;
- int ret;
- int i;
-
- /* register i2c devices first */
- ret = register_i2c_devices(vpfe_dev);
- if (ret)
- return ret;
-
- /* register rest of the sub-devs */
- ret = vpfe_isif_register_entities(&vpfe_dev->vpfe_isif,
- &vpfe_dev->v4l2_dev);
- if (ret)
- return ret;
-
- ret = vpfe_ipipeif_register_entities(&vpfe_dev->vpfe_ipipeif,
- &vpfe_dev->v4l2_dev);
- if (ret)
- goto out_isif_register;
-
- ret = vpfe_ipipe_register_entities(&vpfe_dev->vpfe_ipipe,
- &vpfe_dev->v4l2_dev);
- if (ret)
- goto out_ipipeif_register;
-
- ret = vpfe_resizer_register_entities(&vpfe_dev->vpfe_resizer,
- &vpfe_dev->v4l2_dev);
- if (ret)
- goto out_ipipe_register;
-
- /* create links now, starting with external(i2c) entities */
- for (i = 0; i < vpfe_dev->num_ext_subdevs; i++)
- /*
- * if entity has no pads (ex: amplifier),
- * can't establish link
- */
- if (vpfe_dev->sd[i]->entity.num_pads) {
- ret = media_create_pad_link(&vpfe_dev->sd[i]->entity,
- 0, &vpfe_dev->vpfe_isif.subdev.entity,
- 0, flags);
- if (ret < 0)
- goto out_resizer_register;
- }
-
- ret = media_create_pad_link(&vpfe_dev->vpfe_isif.subdev.entity, 1,
- &vpfe_dev->vpfe_ipipeif.subdev.entity,
- 0, flags);
- if (ret < 0)
- goto out_resizer_register;
-
- ret = media_create_pad_link(&vpfe_dev->vpfe_ipipeif.subdev.entity, 1,
- &vpfe_dev->vpfe_ipipe.subdev.entity,
- 0, flags);
- if (ret < 0)
- goto out_resizer_register;
-
- ret = media_create_pad_link(&vpfe_dev->vpfe_ipipe.subdev.entity,
- 1, &vpfe_dev->vpfe_resizer.crop_resizer.subdev.entity,
- 0, flags);
- if (ret < 0)
- goto out_resizer_register;
-
- ret = media_create_pad_link(&vpfe_dev->vpfe_ipipeif.subdev.entity, 1,
- &vpfe_dev->vpfe_resizer.crop_resizer.subdev.entity,
- 0, flags);
- if (ret < 0)
- goto out_resizer_register;
-
- ret = v4l2_device_register_subdev_nodes(&vpfe_dev->v4l2_dev);
- if (ret < 0)
- goto out_resizer_register;
-
- return 0;
-
-out_resizer_register:
- vpfe_resizer_unregister_entities(&vpfe_dev->vpfe_resizer);
-out_ipipe_register:
- vpfe_ipipe_unregister_entities(&vpfe_dev->vpfe_ipipe);
-out_ipipeif_register:
- vpfe_ipipeif_unregister_entities(&vpfe_dev->vpfe_ipipeif);
-out_isif_register:
- vpfe_isif_unregister_entities(&vpfe_dev->vpfe_isif);
-
- return ret;
-}
-
-/*
- * vpfe_unregister_entities() - unregister all v4l2 subdevs and media entities
- * @vpfe_dev - ptr to vpfe capture device
- *
- * unregister all v4l2 subdevs and media entities
- */
-static void vpfe_unregister_entities(struct vpfe_device *vpfe_dev)
-{
- vpfe_isif_unregister_entities(&vpfe_dev->vpfe_isif);
- vpfe_ipipeif_unregister_entities(&vpfe_dev->vpfe_ipipeif);
- vpfe_ipipe_unregister_entities(&vpfe_dev->vpfe_ipipe);
- vpfe_resizer_unregister_entities(&vpfe_dev->vpfe_resizer);
-}
-
-/*
- * vpfe_cleanup_modules() - cleanup all non-i2c v4l2 subdevs
- * @vpfe_dev - ptr to vpfe capture device
- * @pdev - pointer to platform device
- *
- * cleanup all v4l2 subdevs
- */
-static void vpfe_cleanup_modules(struct vpfe_device *vpfe_dev,
- struct platform_device *pdev)
-{
- vpfe_isif_cleanup(&vpfe_dev->vpfe_isif, pdev);
- vpfe_ipipeif_cleanup(&vpfe_dev->vpfe_ipipeif, pdev);
- vpfe_ipipe_cleanup(&vpfe_dev->vpfe_ipipe, pdev);
- vpfe_resizer_cleanup(&vpfe_dev->vpfe_resizer, pdev);
-}
-
-/*
- * vpfe_initialize_modules() - initialize all non-i2c v4l2 subdevs
- * @vpfe_dev - ptr to vpfe capture device
- * @pdev - pointer to platform device
- *
- * initialize all v4l2 subdevs and media entities
- */
-static int vpfe_initialize_modules(struct vpfe_device *vpfe_dev,
- struct platform_device *pdev)
-{
- int ret;
-
- ret = vpfe_isif_init(&vpfe_dev->vpfe_isif, pdev);
- if (ret)
- return ret;
-
- ret = vpfe_ipipeif_init(&vpfe_dev->vpfe_ipipeif, pdev);
- if (ret)
- goto out_isif_init;
-
- ret = vpfe_ipipe_init(&vpfe_dev->vpfe_ipipe, pdev);
- if (ret)
- goto out_ipipeif_init;
-
- ret = vpfe_resizer_init(&vpfe_dev->vpfe_resizer, pdev);
- if (ret)
- goto out_ipipe_init;
-
- return 0;
-
-out_ipipe_init:
- vpfe_ipipe_cleanup(&vpfe_dev->vpfe_ipipe, pdev);
-out_ipipeif_init:
- vpfe_ipipeif_cleanup(&vpfe_dev->vpfe_ipipeif, pdev);
-out_isif_init:
- vpfe_isif_cleanup(&vpfe_dev->vpfe_isif, pdev);
-
- return ret;
-}
-
-/*
- * vpfe_probe() : vpfe probe function
- * @pdev: platform device pointer
- *
- * This function creates device entries by register itself to the V4L2 driver
- * and initializes fields of each device objects
- */
-static int vpfe_probe(struct platform_device *pdev)
-{
- struct vpfe_device *vpfe_dev;
- struct resource *res1;
- int ret = -ENOMEM;
-
- vpfe_dev = kzalloc(sizeof(*vpfe_dev), GFP_KERNEL);
- if (!vpfe_dev)
- return ret;
-
- if (pdev->dev.platform_data == NULL) {
- v4l2_err(pdev->dev.driver, "Unable to get vpfe config\n");
- ret = -ENOENT;
- goto probe_free_dev_mem;
- }
-
- vpfe_dev->cfg = pdev->dev.platform_data;
- if (vpfe_dev->cfg->card_name == NULL ||
- vpfe_dev->cfg->sub_devs == NULL) {
- v4l2_err(pdev->dev.driver, "null ptr in vpfe_cfg\n");
- ret = -ENOENT;
- goto probe_free_dev_mem;
- }
-
- /* Get VINT0 irq resource */
- res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (!res1) {
- v4l2_err(pdev->dev.driver,
- "Unable to get interrupt for VINT0\n");
- ret = -ENOENT;
- goto probe_free_dev_mem;
- }
- vpfe_dev->ccdc_irq0 = res1->start;
-
- /* Get VINT1 irq resource */
- res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
- if (!res1) {
- v4l2_err(pdev->dev.driver,
- "Unable to get interrupt for VINT1\n");
- ret = -ENOENT;
- goto probe_free_dev_mem;
- }
- vpfe_dev->ccdc_irq1 = res1->start;
-
- /* Get DMA irq resource */
- res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 2);
- if (!res1) {
- v4l2_err(pdev->dev.driver,
- "Unable to get interrupt for DMA\n");
- ret = -ENOENT;
- goto probe_free_dev_mem;
- }
- vpfe_dev->imp_dma_irq = res1->start;
-
- vpfe_dev->pdev = &pdev->dev;
-
- /* enable vpss clocks */
- ret = vpfe_enable_clock(vpfe_dev);
- if (ret)
- goto probe_free_dev_mem;
-
- ret = vpfe_initialize_modules(vpfe_dev, pdev);
- if (ret)
- goto probe_disable_clock;
-
- vpfe_dev->media_dev.dev = vpfe_dev->pdev;
- strscpy((char *)&vpfe_dev->media_dev.model, "davinci-media",
- sizeof(vpfe_dev->media_dev.model));
-
- ret = media_device_register(&vpfe_dev->media_dev);
- if (ret) {
- v4l2_err(pdev->dev.driver,
- "Unable to register media device.\n");
- goto probe_out_entities_cleanup;
- }
-
- vpfe_dev->v4l2_dev.mdev = &vpfe_dev->media_dev;
- ret = v4l2_device_register(&pdev->dev, &vpfe_dev->v4l2_dev);
- if (ret) {
- v4l2_err(pdev->dev.driver, "Unable to register v4l2 device.\n");
- goto probe_out_media_unregister;
- }
-
- v4l2_info(&vpfe_dev->v4l2_dev, "v4l2 device registered\n");
- /* set the driver data in platform device */
- platform_set_drvdata(pdev, vpfe_dev);
- /* register subdevs/entities */
- ret = vpfe_register_entities(vpfe_dev);
- if (ret)
- goto probe_out_v4l2_unregister;
-
- ret = vpfe_attach_irq(vpfe_dev);
- if (ret)
- goto probe_out_entities_unregister;
-
- return 0;
-
-probe_out_entities_unregister:
- vpfe_unregister_entities(vpfe_dev);
- kzfree(vpfe_dev->sd);
-probe_out_v4l2_unregister:
- v4l2_device_unregister(&vpfe_dev->v4l2_dev);
-probe_out_media_unregister:
- media_device_unregister(&vpfe_dev->media_dev);
-probe_out_entities_cleanup:
- vpfe_cleanup_modules(vpfe_dev, pdev);
-probe_disable_clock:
- vpfe_disable_clock(vpfe_dev);
-probe_free_dev_mem:
- kzfree(vpfe_dev);
-
- return ret;
-}
-
-/*
- * vpfe_remove : This function un-registers device from V4L2 driver
- */
-static int vpfe_remove(struct platform_device *pdev)
-{
- struct vpfe_device *vpfe_dev = platform_get_drvdata(pdev);
-
- v4l2_info(pdev->dev.driver, "%s\n", __func__);
-
- kzfree(vpfe_dev->sd);
- vpfe_detach_irq(vpfe_dev);
- vpfe_unregister_entities(vpfe_dev);
- vpfe_cleanup_modules(vpfe_dev, pdev);
- v4l2_device_unregister(&vpfe_dev->v4l2_dev);
- media_device_unregister(&vpfe_dev->media_dev);
- vpfe_disable_clock(vpfe_dev);
- kzfree(vpfe_dev);
-
- return 0;
-}
-
-static struct platform_driver vpfe_driver = {
- .driver = {
- .name = CAPTURE_DRV_NAME,
- },
- .probe = vpfe_probe,
- .remove = vpfe_remove,
-};
-
-module_platform_driver(vpfe_driver);
diff --git a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h
deleted file mode 100644
index fe4a421b5dba..000000000000
--- a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_MC_CAPTURE_H
-#define _DAVINCI_VPFE_MC_CAPTURE_H
-
-#include "dm365_ipipe.h"
-#include "dm365_ipipeif.h"
-#include "dm365_isif.h"
-#include "dm365_resizer.h"
-#include "vpfe_video.h"
-
-#define VPFE_MAJOR_RELEASE 0
-#define VPFE_MINOR_RELEASE 0
-#define VPFE_BUILD 1
-#define VPFE_CAPTURE_VERSION_CODE ((VPFE_MAJOR_RELEASE << 16) | \
- (VPFE_MINOR_RELEASE << 8) | \
- VPFE_BUILD)
-
-/* IPIPE hardware limits */
-#define IPIPE_MAX_OUTPUT_WIDTH_A 2176
-#define IPIPE_MAX_OUTPUT_WIDTH_B 640
-
-/* Based on max resolution supported. QXGA */
-#define IPIPE_MAX_OUTPUT_HEIGHT_A 1536
-/* Based on max resolution supported. VGA */
-#define IPIPE_MAX_OUTPUT_HEIGHT_B 480
-
-#define to_vpfe_device(ptr_module) \
- container_of(ptr_module, struct vpfe_device, vpfe_##ptr_module)
-#define to_device(ptr_module) \
- (to_vpfe_device(ptr_module)->dev)
-
-struct vpfe_device {
- /* external registered sub devices */
- struct v4l2_subdev **sd;
- /* number of registered external subdevs */
- unsigned int num_ext_subdevs;
- /* vpfe cfg */
- struct vpfe_config *cfg;
- /* clock ptrs for vpfe capture */
- struct clk **clks;
- /* V4l2 device */
- struct v4l2_device v4l2_dev;
- /* parent device */
- struct device *pdev;
- /* IRQ number for DMA transfer completion at the image processor */
- unsigned int imp_dma_irq;
- /* CCDC IRQs used when CCDC/ISIF output to SDRAM */
- unsigned int ccdc_irq0;
- unsigned int ccdc_irq1;
- /* media device */
- struct media_device media_dev;
- /* ccdc subdevice */
- struct vpfe_isif_device vpfe_isif;
- /* ipipeif subdevice */
- struct vpfe_ipipeif_device vpfe_ipipeif;
- /* ipipe subdevice */
- struct vpfe_ipipe_device vpfe_ipipe;
- /* resizer subdevice */
- struct vpfe_resizer_device vpfe_resizer;
-};
-
-/* File handle structure */
-struct vpfe_fh {
- struct v4l2_fh vfh;
- struct vpfe_video_device *video;
- /* Indicates whether this file handle is doing IO */
- u8 io_allowed;
-};
-
-void mbus_to_pix(const struct v4l2_mbus_framefmt *mbus,
- struct v4l2_pix_format *pix);
-
-#endif /* _DAVINCI_VPFE_MC_CAPTURE_H */
diff --git a/drivers/staging/media/davinci_vpfe/vpfe_video.c b/drivers/staging/media/davinci_vpfe/vpfe_video.c
deleted file mode 100644
index ab6bc452d9f6..000000000000
--- a/drivers/staging/media/davinci_vpfe/vpfe_video.c
+++ /dev/null
@@ -1,1646 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#include <linux/module.h>
-#include <linux/slab.h>
-
-#include <media/v4l2-ioctl.h>
-
-#include "vpfe.h"
-#include "vpfe_mc_capture.h"
-
-static int debug;
-
-/* get v4l2 subdev pointer to external subdev which is active */
-static struct media_entity *vpfe_get_input_entity
- (struct vpfe_video_device *video)
-{
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct media_pad *remote;
-
- remote = media_entity_remote_pad(&vpfe_dev->vpfe_isif.pads[0]);
- if (!remote) {
- pr_err("Invalid media connection to isif/ccdc\n");
- return NULL;
- }
- return remote->entity;
-}
-
-/* updates external subdev(sensor/decoder) which is active */
-static int vpfe_update_current_ext_subdev(struct vpfe_video_device *video)
-{
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_config *vpfe_cfg;
- struct v4l2_subdev *subdev;
- struct media_pad *remote;
- int i;
-
- remote = media_entity_remote_pad(&vpfe_dev->vpfe_isif.pads[0]);
- if (!remote) {
- pr_err("Invalid media connection to isif/ccdc\n");
- return -EINVAL;
- }
-
- subdev = media_entity_to_v4l2_subdev(remote->entity);
- vpfe_cfg = vpfe_dev->pdev->platform_data;
- for (i = 0; i < vpfe_cfg->num_subdevs; i++) {
- if (!strcmp(vpfe_cfg->sub_devs[i].module_name, subdev->name)) {
- video->current_ext_subdev = &vpfe_cfg->sub_devs[i];
- break;
- }
- }
-
- /* if user not linked decoder/sensor to isif/ccdc */
- if (i == vpfe_cfg->num_subdevs) {
- pr_err("Invalid media chain connection to isif/ccdc\n");
- return -EINVAL;
- }
- /* find the v4l2 subdev pointer */
- for (i = 0; i < vpfe_dev->num_ext_subdevs; i++) {
- if (!strcmp(video->current_ext_subdev->module_name,
- vpfe_dev->sd[i]->name))
- video->current_ext_subdev->subdev = vpfe_dev->sd[i];
- }
- return 0;
-}
-
-/* get the subdev which is connected to the output video node */
-static struct v4l2_subdev *
-vpfe_video_remote_subdev(struct vpfe_video_device *video, u32 *pad)
-{
- struct media_pad *remote = media_entity_remote_pad(&video->pad);
-
- if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
- return NULL;
- if (pad)
- *pad = remote->index;
- return media_entity_to_v4l2_subdev(remote->entity);
-}
-
-/* get the format set at output pad of the adjacent subdev */
-static int
-__vpfe_video_get_format(struct vpfe_video_device *video,
- struct v4l2_format *format)
-{
- struct v4l2_subdev_format fmt;
- struct v4l2_subdev *subdev;
- struct media_pad *remote;
- u32 pad;
- int ret;
-
- subdev = vpfe_video_remote_subdev(video, &pad);
- if (!subdev)
- return -EINVAL;
-
- fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
- remote = media_entity_remote_pad(&video->pad);
- fmt.pad = remote->index;
-
- ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt);
- if (ret == -ENOIOCTLCMD)
- return -EINVAL;
-
- format->type = video->type;
- /* convert mbus_format to v4l2_format */
- v4l2_fill_pix_format(&format->fmt.pix, &fmt.format);
- mbus_to_pix(&fmt.format, &format->fmt.pix);
-
- return 0;
-}
-
-/* make a note of pipeline details */
-static int vpfe_prepare_pipeline(struct vpfe_video_device *video)
-{
- struct media_graph graph;
- struct media_entity *entity = &video->video_dev.entity;
- struct media_device *mdev = entity->graph_obj.mdev;
- struct vpfe_pipeline *pipe = &video->pipe;
- struct vpfe_video_device *far_end = NULL;
- int ret;
-
- pipe->input_num = 0;
- pipe->output_num = 0;
-
- if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
- pipe->inputs[pipe->input_num++] = video;
- else
- pipe->outputs[pipe->output_num++] = video;
-
- mutex_lock(&mdev->graph_mutex);
- ret = media_graph_walk_init(&graph, mdev);
- if (ret) {
- mutex_unlock(&mdev->graph_mutex);
- return -ENOMEM;
- }
- media_graph_walk_start(&graph, entity);
- while ((entity = media_graph_walk_next(&graph))) {
- if (entity == &video->video_dev.entity)
- continue;
- if (!is_media_entity_v4l2_video_device(entity))
- continue;
- far_end = to_vpfe_video(media_entity_to_video_device(entity));
- if (far_end->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
- pipe->inputs[pipe->input_num++] = far_end;
- else
- pipe->outputs[pipe->output_num++] = far_end;
- }
- media_graph_walk_cleanup(&graph);
- mutex_unlock(&mdev->graph_mutex);
-
- return 0;
-}
-
-/* update pipe state selected by user */
-static int vpfe_update_pipe_state(struct vpfe_video_device *video)
-{
- struct vpfe_pipeline *pipe = &video->pipe;
- int ret;
-
- ret = vpfe_prepare_pipeline(video);
- if (ret)
- return ret;
-
- /*
- * Find out if there is any input video
- * if yes, it is single shot.
- */
- if (pipe->input_num == 0) {
- pipe->state = VPFE_PIPELINE_STREAM_CONTINUOUS;
- ret = vpfe_update_current_ext_subdev(video);
- if (ret) {
- pr_err("Invalid external subdev\n");
- return ret;
- }
- } else {
- pipe->state = VPFE_PIPELINE_STREAM_SINGLESHOT;
- }
- video->initialized = 1;
- video->skip_frame_count = 1;
- video->skip_frame_count_init = 1;
- return 0;
-}
-
-/* checks whether pipeline is ready for enabling */
-int vpfe_video_is_pipe_ready(struct vpfe_pipeline *pipe)
-{
- int i;
-
- for (i = 0; i < pipe->input_num; i++)
- if (!pipe->inputs[i]->started ||
- pipe->inputs[i]->state != VPFE_VIDEO_BUFFER_QUEUED)
- return 0;
- for (i = 0; i < pipe->output_num; i++)
- if (!pipe->outputs[i]->started ||
- pipe->outputs[i]->state != VPFE_VIDEO_BUFFER_QUEUED)
- return 0;
- return 1;
-}
-
-/*
- * Validate a pipeline by checking both ends of all links for format
- * discrepancies.
- *
- * Return 0 if all formats match, or -EPIPE if at least one link is found with
- * different formats on its two ends.
- */
-static int vpfe_video_validate_pipeline(struct vpfe_pipeline *pipe)
-{
- struct v4l2_subdev_format fmt_source;
- struct v4l2_subdev_format fmt_sink;
- struct v4l2_subdev *subdev;
- struct media_pad *pad;
- int ret;
-
- /*
- * Should not matter if it is output[0] or 1 as
- * the general ideas is to traverse backwards and
- * the fact that the out video node always has the
- * format of the connected pad.
- */
- subdev = vpfe_video_remote_subdev(pipe->outputs[0], NULL);
- if (!subdev)
- return -EPIPE;
-
- while (1) {
- /* Retrieve the sink format */
- pad = &subdev->entity.pads[0];
- if (!(pad->flags & MEDIA_PAD_FL_SINK))
- break;
-
- fmt_sink.which = V4L2_SUBDEV_FORMAT_ACTIVE;
- fmt_sink.pad = pad->index;
- ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL,
- &fmt_sink);
-
- if (ret < 0 && ret != -ENOIOCTLCMD)
- return -EPIPE;
-
- /* Retrieve the source format */
- pad = media_entity_remote_pad(pad);
- if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
- break;
-
- subdev = media_entity_to_v4l2_subdev(pad->entity);
-
- fmt_source.which = V4L2_SUBDEV_FORMAT_ACTIVE;
- fmt_source.pad = pad->index;
- ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt_source);
- if (ret < 0 && ret != -ENOIOCTLCMD)
- return -EPIPE;
-
- /* Check if the two ends match */
- if (fmt_source.format.code != fmt_sink.format.code ||
- fmt_source.format.width != fmt_sink.format.width ||
- fmt_source.format.height != fmt_sink.format.height)
- return -EPIPE;
- }
- return 0;
-}
-
-/*
- * vpfe_pipeline_enable() - Enable streaming on a pipeline
- * @vpfe_dev: vpfe device
- * @pipe: vpfe pipeline
- *
- * Walk the entities chain starting at the pipeline output video node and start
- * all modules in the chain in the given mode.
- *
- * Return 0 if successful, or the return value of the failed video::s_stream
- * operation otherwise.
- */
-static int vpfe_pipeline_enable(struct vpfe_pipeline *pipe)
-{
- struct media_entity *entity;
- struct v4l2_subdev *subdev;
- struct media_device *mdev;
- int ret;
-
- if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS)
- entity = vpfe_get_input_entity(pipe->outputs[0]);
- else
- entity = &pipe->inputs[0]->video_dev.entity;
-
- mdev = entity->graph_obj.mdev;
- mutex_lock(&mdev->graph_mutex);
- ret = media_graph_walk_init(&pipe->graph, mdev);
- if (ret)
- goto out;
- media_graph_walk_start(&pipe->graph, entity);
- while ((entity = media_graph_walk_next(&pipe->graph))) {
-
- if (!is_media_entity_v4l2_subdev(entity))
- continue;
- subdev = media_entity_to_v4l2_subdev(entity);
- ret = v4l2_subdev_call(subdev, video, s_stream, 1);
- if (ret < 0 && ret != -ENOIOCTLCMD)
- break;
- }
-out:
- if (ret)
- media_graph_walk_cleanup(&pipe->graph);
- mutex_unlock(&mdev->graph_mutex);
- return ret;
-}
-
-/*
- * vpfe_pipeline_disable() - Disable streaming on a pipeline
- * @vpfe_dev: vpfe device
- * @pipe: VPFE pipeline
- *
- * Walk the entities chain starting at the pipeline output video node and stop
- * all modules in the chain.
- *
- * Return 0 if all modules have been properly stopped, or -ETIMEDOUT if a module
- * can't be stopped.
- */
-static int vpfe_pipeline_disable(struct vpfe_pipeline *pipe)
-{
- struct media_entity *entity;
- struct v4l2_subdev *subdev;
- struct media_device *mdev;
- int ret = 0;
-
- if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS)
- entity = vpfe_get_input_entity(pipe->outputs[0]);
- else
- entity = &pipe->inputs[0]->video_dev.entity;
-
- mdev = entity->graph_obj.mdev;
- mutex_lock(&mdev->graph_mutex);
- media_graph_walk_start(&pipe->graph, entity);
-
- while ((entity = media_graph_walk_next(&pipe->graph))) {
-
- if (!is_media_entity_v4l2_subdev(entity))
- continue;
- subdev = media_entity_to_v4l2_subdev(entity);
- ret = v4l2_subdev_call(subdev, video, s_stream, 0);
- if (ret < 0 && ret != -ENOIOCTLCMD)
- break;
- }
- mutex_unlock(&mdev->graph_mutex);
-
- media_graph_walk_cleanup(&pipe->graph);
- return ret ? -ETIMEDOUT : 0;
-}
-
-/*
- * vpfe_pipeline_set_stream() - Enable/disable streaming on a pipeline
- * @vpfe_dev: VPFE device
- * @pipe: VPFE pipeline
- * @state: Stream state (stopped or active)
- *
- * Set the pipeline to the given stream state.
- *
- * Return 0 if successful, or the return value of the failed video::s_stream
- * operation otherwise.
- */
-static int vpfe_pipeline_set_stream(struct vpfe_pipeline *pipe,
- enum vpfe_pipeline_stream_state state)
-{
- if (state == VPFE_PIPELINE_STREAM_STOPPED)
- return vpfe_pipeline_disable(pipe);
-
- return vpfe_pipeline_enable(pipe);
-}
-
-static int all_videos_stopped(struct vpfe_video_device *video)
-{
- struct vpfe_pipeline *pipe = &video->pipe;
- int i;
-
- for (i = 0; i < pipe->input_num; i++)
- if (pipe->inputs[i]->started)
- return 0;
- for (i = 0; i < pipe->output_num; i++)
- if (pipe->outputs[i]->started)
- return 0;
- return 1;
-}
-
-/*
- * vpfe_open() - open video device
- * @file: file pointer
- *
- * initialize media pipeline state, allocate memory for file handle
- *
- * Return 0 if successful, or the return -ENODEV otherwise.
- */
-static int vpfe_open(struct file *file)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_fh *handle;
-
- /* Allocate memory for the file handle object */
- handle = kzalloc(sizeof(struct vpfe_fh), GFP_KERNEL);
-
- if (!handle)
- return -ENOMEM;
-
- v4l2_fh_init(&handle->vfh, &video->video_dev);
- v4l2_fh_add(&handle->vfh);
-
- mutex_lock(&video->lock);
- /* If decoder is not initialized. initialize it */
- if (!video->initialized && vpfe_update_pipe_state(video)) {
- mutex_unlock(&video->lock);
- v4l2_fh_del(&handle->vfh);
- v4l2_fh_exit(&handle->vfh);
- kfree(handle);
- return -ENODEV;
- }
- /* Increment device users counter */
- video->usrs++;
- /* Set io_allowed member to false */
- handle->io_allowed = 0;
- handle->video = video;
- file->private_data = &handle->vfh;
- mutex_unlock(&video->lock);
-
- return 0;
-}
-
-/* get the next buffer available from dma queue */
-static unsigned long
-vpfe_video_get_next_buffer(struct vpfe_video_device *video)
-{
- video->cur_frm = video->next_frm =
- list_entry(video->dma_queue.next,
- struct vpfe_cap_buffer, list);
-
- list_del(&video->next_frm->list);
- video->next_frm->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE;
- return vb2_dma_contig_plane_dma_addr(&video->next_frm->vb.vb2_buf, 0);
-}
-
-/* schedule the next buffer which is available on dma queue */
-void vpfe_video_schedule_next_buffer(struct vpfe_video_device *video)
-{
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- unsigned long addr;
-
- if (list_empty(&video->dma_queue))
- return;
-
- video->next_frm = list_entry(video->dma_queue.next,
- struct vpfe_cap_buffer, list);
-
- if (video->pipe.state == VPFE_PIPELINE_STREAM_SINGLESHOT)
- video->cur_frm = video->next_frm;
-
- list_del(&video->next_frm->list);
- video->next_frm->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE;
- addr = vb2_dma_contig_plane_dma_addr(&video->next_frm->vb.vb2_buf, 0);
- video->ops->queue(vpfe_dev, addr);
- video->state = VPFE_VIDEO_BUFFER_QUEUED;
-}
-
-/* schedule the buffer for capturing bottom field */
-void vpfe_video_schedule_bottom_field(struct vpfe_video_device *video)
-{
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- unsigned long addr;
-
- addr = vb2_dma_contig_plane_dma_addr(&video->cur_frm->vb.vb2_buf, 0);
- addr += video->field_off;
- video->ops->queue(vpfe_dev, addr);
-}
-
-/* make buffer available for dequeue */
-void vpfe_video_process_buffer_complete(struct vpfe_video_device *video)
-{
- struct vpfe_pipeline *pipe = &video->pipe;
-
- video->cur_frm->vb.vb2_buf.timestamp = ktime_get_ns();
- vb2_buffer_done(&video->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
- if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS)
- video->cur_frm = video->next_frm;
-}
-
-/* vpfe_stop_capture() - stop streaming */
-static void vpfe_stop_capture(struct vpfe_video_device *video)
-{
- struct vpfe_pipeline *pipe = &video->pipe;
-
- video->started = 0;
-
- if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
- return;
- if (all_videos_stopped(video))
- vpfe_pipeline_set_stream(pipe,
- VPFE_PIPELINE_STREAM_STOPPED);
-}
-
-/*
- * vpfe_release() - release video device
- * @file: file pointer
- *
- * deletes buffer queue, frees the buffers and the vpfe file handle
- *
- * Return 0
- */
-static int vpfe_release(struct file *file)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct v4l2_fh *vfh = file->private_data;
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_fh *fh = container_of(vfh, struct vpfe_fh, vfh);
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_release\n");
-
- /* Get the device lock */
- mutex_lock(&video->lock);
- /* if this instance is doing IO */
- if (fh->io_allowed) {
- if (video->started) {
- vpfe_stop_capture(video);
- /*
- * mark pipe state as stopped in vpfe_release(),
- * as app might call streamon() after streamoff()
- * in which case driver has to start streaming.
- */
- video->pipe.state = VPFE_PIPELINE_STREAM_STOPPED;
- vb2_streamoff(&video->buffer_queue,
- video->buffer_queue.type);
- }
- video->io_usrs = 0;
- /* Free buffers allocated */
- vb2_queue_release(&video->buffer_queue);
- }
- /* Decrement device users counter */
- video->usrs--;
- v4l2_fh_del(&fh->vfh);
- v4l2_fh_exit(&fh->vfh);
- /* If this is the last file handle */
- if (!video->usrs)
- video->initialized = 0;
- mutex_unlock(&video->lock);
- file->private_data = NULL;
- /* Free memory allocated to file handle object */
- v4l2_fh_del(vfh);
- kzfree(fh);
- return 0;
-}
-
-/*
- * vpfe_mmap() - It is used to map kernel space buffers
- * into user spaces
- */
-static int vpfe_mmap(struct file *file, struct vm_area_struct *vma)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_mmap\n");
- return vb2_mmap(&video->buffer_queue, vma);
-}
-
-/*
- * vpfe_poll() - It is used for select/poll system call
- */
-static __poll_t vpfe_poll(struct file *file, poll_table *wait)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_poll\n");
- if (video->started)
- return vb2_poll(&video->buffer_queue, file, wait);
- return 0;
-}
-
-/* vpfe capture driver file operations */
-static const struct v4l2_file_operations vpfe_fops = {
- .owner = THIS_MODULE,
- .open = vpfe_open,
- .release = vpfe_release,
- .unlocked_ioctl = video_ioctl2,
- .mmap = vpfe_mmap,
- .poll = vpfe_poll
-};
-
-/*
- * vpfe_querycap() - query capabilities of video device
- * @file: file pointer
- * @priv: void pointer
- * @cap: pointer to v4l2_capability structure
- *
- * fills v4l2 capabilities structure
- *
- * Return 0
- */
-static int vpfe_querycap(struct file *file, void *priv,
- struct v4l2_capability *cap)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querycap\n");
-
- cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT |
- V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS;
- strscpy(cap->driver, CAPTURE_DRV_NAME, sizeof(cap->driver));
- strscpy(cap->bus_info, "VPFE", sizeof(cap->bus_info));
- strscpy(cap->card, vpfe_dev->cfg->card_name, sizeof(cap->card));
-
- return 0;
-}
-
-/*
- * vpfe_g_fmt() - get the format which is active on video device
- * @file: file pointer
- * @priv: void pointer
- * @fmt: pointer to v4l2_format structure
- *
- * fills v4l2 format structure with active format
- *
- * Return 0
- */
-static int vpfe_g_fmt(struct file *file, void *priv,
- struct v4l2_format *fmt)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_fmt\n");
- /* Fill in the information about format */
- *fmt = video->fmt;
- return 0;
-}
-
-/*
- * vpfe_enum_fmt() - enum formats supported on media chain
- * @file: file pointer
- * @priv: void pointer
- * @fmt: pointer to v4l2_fmtdesc structure
- *
- * fills v4l2_fmtdesc structure with output format set on adjacent subdev,
- * only one format is enumearted as subdevs are already configured
- *
- * Return 0 if successful, error code otherwise
- */
-static int vpfe_enum_fmt(struct file *file, void *priv,
- struct v4l2_fmtdesc *fmt)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct v4l2_subdev_format sd_fmt;
- struct v4l2_mbus_framefmt mbus;
- struct v4l2_subdev *subdev;
- struct v4l2_format format;
- struct media_pad *remote;
- int ret;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_fmt\n");
-
- /*
- * since already subdev pad format is set,
- * only one pixel format is available
- */
- if (fmt->index > 0) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Invalid index\n");
- return -EINVAL;
- }
- /* get the remote pad */
- remote = media_entity_remote_pad(&video->pad);
- if (!remote) {
- v4l2_err(&vpfe_dev->v4l2_dev,
- "invalid remote pad for video node\n");
- return -EINVAL;
- }
- /* get the remote subdev */
- subdev = vpfe_video_remote_subdev(video, NULL);
- if (!subdev) {
- v4l2_err(&vpfe_dev->v4l2_dev,
- "invalid remote subdev for video node\n");
- return -EINVAL;
- }
- sd_fmt.pad = remote->index;
- sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
- /* get output format of remote subdev */
- ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &sd_fmt);
- if (ret) {
- v4l2_err(&vpfe_dev->v4l2_dev,
- "invalid remote subdev for video node\n");
- return ret;
- }
- /* convert to pix format */
- mbus.code = sd_fmt.format.code;
- mbus_to_pix(&mbus, &format.fmt.pix);
- /* copy the result */
- fmt->pixelformat = format.fmt.pix.pixelformat;
-
- return 0;
-}
-
-/*
- * vpfe_s_fmt() - set the format on video device
- * @file: file pointer
- * @priv: void pointer
- * @fmt: pointer to v4l2_format structure
- *
- * validate and set the format on video device
- *
- * Return 0 on success, error code otherwise
- */
-static int vpfe_s_fmt(struct file *file, void *priv,
- struct v4l2_format *fmt)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct v4l2_format format;
- int ret;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_fmt\n");
- /* If streaming is started, return error */
- if (video->started) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is started\n");
- return -EBUSY;
- }
- /* get adjacent subdev's output pad format */
- ret = __vpfe_video_get_format(video, &format);
- if (ret)
- return ret;
- *fmt = format;
- video->fmt = *fmt;
- return 0;
-}
-
-/*
- * vpfe_try_fmt() - try the format on video device
- * @file: file pointer
- * @priv: void pointer
- * @fmt: pointer to v4l2_format structure
- *
- * validate the format, update with correct format
- * based on output format set on adjacent subdev
- *
- * Return 0 on success, error code otherwise
- */
-static int vpfe_try_fmt(struct file *file, void *priv,
- struct v4l2_format *fmt)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct v4l2_format format;
- int ret;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_try_fmt\n");
- /* get adjacent subdev's output pad format */
- ret = __vpfe_video_get_format(video, &format);
- if (ret)
- return ret;
-
- *fmt = format;
- return 0;
-}
-
-/*
- * vpfe_enum_input() - enum inputs supported on media chain
- * @file: file pointer
- * @priv: void pointer
- * @fmt: pointer to v4l2_fmtdesc structure
- *
- * fills v4l2_input structure with input available on media chain,
- * only one input is enumearted as media chain is setup by this time
- *
- * Return 0 if successful, -EINVAL is media chain is invalid
- */
-static int vpfe_enum_input(struct file *file, void *priv,
- struct v4l2_input *inp)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_ext_subdev_info *sdinfo = video->current_ext_subdev;
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_input\n");
- /* enumerate from the subdev user has chosen through mc */
- if (inp->index < sdinfo->num_inputs) {
- memcpy(inp, &sdinfo->inputs[inp->index],
- sizeof(struct v4l2_input));
- return 0;
- }
- return -EINVAL;
-}
-
-/*
- * vpfe_g_input() - get index of the input which is active
- * @file: file pointer
- * @priv: void pointer
- * @index: pointer to unsigned int
- *
- * set index with input index which is active
- */
-static int vpfe_g_input(struct file *file, void *priv, unsigned int *index)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_input\n");
-
- *index = video->current_input;
- return 0;
-}
-
-/*
- * vpfe_s_input() - set input which is pointed by input index
- * @file: file pointer
- * @priv: void pointer
- * @index: pointer to unsigned int
- *
- * set input on external subdev
- *
- * Return 0 on success, error code otherwise
- */
-static int vpfe_s_input(struct file *file, void *priv, unsigned int index)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_ext_subdev_info *sdinfo;
- struct vpfe_route *route;
- struct v4l2_input *inps;
- u32 output;
- u32 input;
- int ret;
- int i;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_input\n");
-
- ret = mutex_lock_interruptible(&video->lock);
- if (ret)
- return ret;
- /*
- * If streaming is started return device busy
- * error
- */
- if (video->started) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is on\n");
- ret = -EBUSY;
- goto unlock_out;
- }
-
- sdinfo = video->current_ext_subdev;
- if (!sdinfo->registered) {
- ret = -EINVAL;
- goto unlock_out;
- }
- if (vpfe_dev->cfg->setup_input &&
- vpfe_dev->cfg->setup_input(sdinfo->grp_id) < 0) {
- ret = -EFAULT;
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev,
- "couldn't setup input for %s\n",
- sdinfo->module_name);
- goto unlock_out;
- }
- route = &sdinfo->routes[index];
- if (route && sdinfo->can_route) {
- input = route->input;
- output = route->output;
- ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev,
- sdinfo->grp_id, video,
- s_routing, input, output, 0);
- if (ret) {
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev,
- "s_input:error in setting input in decoder\n");
- ret = -EINVAL;
- goto unlock_out;
- }
- }
- /* set standards set by subdev in video device */
- for (i = 0; i < sdinfo->num_inputs; i++) {
- inps = &sdinfo->inputs[i];
- video->video_dev.tvnorms |= inps->std;
- }
- video->current_input = index;
-unlock_out:
- mutex_unlock(&video->lock);
- return ret;
-}
-
-/*
- * vpfe_querystd() - query std which is being input on external subdev
- * @file: file pointer
- * @priv: void pointer
- * @std_id: pointer to v4l2_std_id structure
- *
- * call external subdev through v4l2_device_call_until_err to
- * get the std that is being active.
- *
- * Return 0 on success, error code otherwise
- */
-static int vpfe_querystd(struct file *file, void *priv, v4l2_std_id *std_id)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_ext_subdev_info *sdinfo;
- int ret;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querystd\n");
-
- ret = mutex_lock_interruptible(&video->lock);
- sdinfo = video->current_ext_subdev;
- if (ret)
- return ret;
- /* Call querystd function of decoder device */
- ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id,
- video, querystd, std_id);
- mutex_unlock(&video->lock);
- return ret;
-}
-
-/*
- * vpfe_s_std() - set std on external subdev
- * @file: file pointer
- * @priv: void pointer
- * @std_id: pointer to v4l2_std_id structure
- *
- * set std pointed by std_id on external subdev by calling it using
- * v4l2_device_call_until_err
- *
- * Return 0 on success, error code otherwise
- */
-static int vpfe_s_std(struct file *file, void *priv, v4l2_std_id std_id)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_ext_subdev_info *sdinfo;
- int ret;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_std\n");
-
- /* Call decoder driver function to set the standard */
- ret = mutex_lock_interruptible(&video->lock);
- if (ret)
- return ret;
- sdinfo = video->current_ext_subdev;
- /* If streaming is started, return device busy error */
- if (video->started) {
- v4l2_err(&vpfe_dev->v4l2_dev, "streaming is started\n");
- ret = -EBUSY;
- goto unlock_out;
- }
- ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id,
- video, s_std, std_id);
- if (ret < 0) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Failed to set standard\n");
- video->stdid = V4L2_STD_UNKNOWN;
- goto unlock_out;
- }
- video->stdid = std_id;
-unlock_out:
- mutex_unlock(&video->lock);
- return ret;
-}
-
-static int vpfe_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_std\n");
- *tvnorm = video->stdid;
- return 0;
-}
-
-/*
- * vpfe_enum_dv_timings() - enumerate dv_timings which are supported by
- * to external subdev
- * @file: file pointer
- * @priv: void pointer
- * @timings: pointer to v4l2_enum_dv_timings structure
- *
- * enum dv_timings's which are supported by external subdev through
- * v4l2_subdev_call
- *
- * Return 0 on success, error code otherwise
- */
-static int
-vpfe_enum_dv_timings(struct file *file, void *fh,
- struct v4l2_enum_dv_timings *timings)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct v4l2_subdev *subdev = video->current_ext_subdev->subdev;
-
- timings->pad = 0;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_dv_timings\n");
- return v4l2_subdev_call(subdev, pad, enum_dv_timings, timings);
-}
-
-/*
- * vpfe_query_dv_timings() - query the dv_timings which is being input
- * to external subdev
- * @file: file pointer
- * @priv: void pointer
- * @timings: pointer to v4l2_dv_timings structure
- *
- * get dv_timings which is being input on external subdev through
- * v4l2_subdev_call
- *
- * Return 0 on success, error code otherwise
- */
-static int
-vpfe_query_dv_timings(struct file *file, void *fh,
- struct v4l2_dv_timings *timings)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct v4l2_subdev *subdev = video->current_ext_subdev->subdev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_query_dv_timings\n");
- return v4l2_subdev_call(subdev, video, query_dv_timings, timings);
-}
-
-/*
- * vpfe_s_dv_timings() - set dv_timings on external subdev
- * @file: file pointer
- * @priv: void pointer
- * @timings: pointer to v4l2_dv_timings structure
- *
- * set dv_timings pointed by timings on external subdev through
- * v4l2_device_call_until_err, this configures amplifier also
- *
- * Return 0 on success, error code otherwise
- */
-static int
-vpfe_s_dv_timings(struct file *file, void *fh,
- struct v4l2_dv_timings *timings)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_dv_timings\n");
-
- video->stdid = V4L2_STD_UNKNOWN;
- return v4l2_device_call_until_err(&vpfe_dev->v4l2_dev,
- video->current_ext_subdev->grp_id,
- video, s_dv_timings, timings);
-}
-
-/*
- * vpfe_g_dv_timings() - get dv_timings which is set on external subdev
- * @file: file pointer
- * @priv: void pointer
- * @timings: pointer to v4l2_dv_timings structure
- *
- * get dv_timings which is set on external subdev through
- * v4l2_subdev_call
- *
- * Return 0 on success, error code otherwise
- */
-static int
-vpfe_g_dv_timings(struct file *file, void *fh,
- struct v4l2_dv_timings *timings)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct v4l2_subdev *subdev = video->current_ext_subdev->subdev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_dv_timings\n");
- return v4l2_subdev_call(subdev, video, g_dv_timings, timings);
-}
-
-/*
- * Videobuf operations
- */
-/*
- * vpfe_buffer_queue_setup : Callback function for buffer setup.
- * @vq: vb2_queue ptr
- * @fmt: v4l2 format
- * @nbuffers: ptr to number of buffers requested by application
- * @nplanes:: contains number of distinct video planes needed to hold a frame
- * @sizes[]: contains the size (in bytes) of each plane.
- * @alloc_devs: ptr to allocation context
- *
- * This callback function is called when reqbuf() is called to adjust
- * the buffer nbuffers and buffer size
- */
-static int
-vpfe_buffer_queue_setup(struct vb2_queue *vq,
- unsigned int *nbuffers, unsigned int *nplanes,
- unsigned int sizes[], struct device *alloc_devs[])
-{
- struct vpfe_fh *fh = vb2_get_drv_priv(vq);
- struct vpfe_video_device *video = fh->video;
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- unsigned long size;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_queue_setup\n");
- size = video->fmt.fmt.pix.sizeimage;
-
- if (vq->num_buffers + *nbuffers < 3)
- *nbuffers = 3 - vq->num_buffers;
-
- *nplanes = 1;
- sizes[0] = size;
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev,
- "nbuffers=%d, size=%lu\n", *nbuffers, size);
- return 0;
-}
-
-/*
- * vpfe_buffer_prepare : callback function for buffer prepare
- * @vb: ptr to vb2_buffer
- *
- * This is the callback function for buffer prepare when vb2_qbuf()
- * function is called. The buffer is prepared and user space virtual address
- * or user address is converted into physical address
- */
-static int vpfe_buffer_prepare(struct vb2_buffer *vb)
-{
- struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue);
- struct vpfe_video_device *video = fh->video;
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- unsigned long addr;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_prepare\n");
-
- /* Initialize buffer */
- vb2_set_plane_payload(vb, 0, video->fmt.fmt.pix.sizeimage);
- if (vb2_plane_vaddr(vb, 0) &&
- vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0))
- return -EINVAL;
-
- addr = vb2_dma_contig_plane_dma_addr(vb, 0);
- /* Make sure user addresses are aligned to 32 bytes */
- if (!ALIGN(addr, 32))
- return -EINVAL;
-
- return 0;
-}
-
-static void vpfe_buffer_queue(struct vb2_buffer *vb)
-{
- struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
- /* Get the file handle object and device object */
- struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue);
- struct vpfe_video_device *video = fh->video;
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_pipeline *pipe = &video->pipe;
- struct vpfe_cap_buffer *buf = container_of(vbuf,
- struct vpfe_cap_buffer, vb);
- unsigned long flags;
- unsigned long empty;
- unsigned long addr;
-
- spin_lock_irqsave(&video->dma_queue_lock, flags);
- empty = list_empty(&video->dma_queue);
- /* add the buffer to the DMA queue */
- list_add_tail(&buf->list, &video->dma_queue);
- spin_unlock_irqrestore(&video->dma_queue_lock, flags);
- /* this case happens in case of single shot */
- if (empty && video->started && pipe->state ==
- VPFE_PIPELINE_STREAM_SINGLESHOT &&
- video->state == VPFE_VIDEO_BUFFER_NOT_QUEUED) {
- spin_lock(&video->dma_queue_lock);
- addr = vpfe_video_get_next_buffer(video);
- video->ops->queue(vpfe_dev, addr);
-
- video->state = VPFE_VIDEO_BUFFER_QUEUED;
- spin_unlock(&video->dma_queue_lock);
-
- /* enable h/w each time in single shot */
- if (vpfe_video_is_pipe_ready(pipe))
- vpfe_pipeline_set_stream(pipe,
- VPFE_PIPELINE_STREAM_SINGLESHOT);
- }
-}
-
-/* vpfe_start_capture() - start streaming on all the subdevs */
-static int vpfe_start_capture(struct vpfe_video_device *video)
-{
- struct vpfe_pipeline *pipe = &video->pipe;
- int ret = 0;
-
- video->started = 1;
- if (vpfe_video_is_pipe_ready(pipe))
- ret = vpfe_pipeline_set_stream(pipe, pipe->state);
-
- return ret;
-}
-
-static int vpfe_start_streaming(struct vb2_queue *vq, unsigned int count)
-{
- struct vpfe_fh *fh = vb2_get_drv_priv(vq);
- struct vpfe_video_device *video = fh->video;
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- unsigned long addr;
- int ret;
-
- ret = mutex_lock_interruptible(&video->lock);
- if (ret)
- goto streamoff;
-
- /* Get the next frame from the buffer queue */
- video->cur_frm = video->next_frm =
- list_entry(video->dma_queue.next, struct vpfe_cap_buffer, list);
- /* Remove buffer from the buffer queue */
- list_del(&video->cur_frm->list);
- /* Mark state of the current frame to active */
- video->cur_frm->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE;
- /* Initialize field_id and started member */
- video->field_id = 0;
- addr = vb2_dma_contig_plane_dma_addr(&video->cur_frm->vb.vb2_buf, 0);
- video->ops->queue(vpfe_dev, addr);
- video->state = VPFE_VIDEO_BUFFER_QUEUED;
-
- ret = vpfe_start_capture(video);
- if (ret) {
- struct vpfe_cap_buffer *buf, *tmp;
-
- vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
- VB2_BUF_STATE_QUEUED);
- list_for_each_entry_safe(buf, tmp, &video->dma_queue, list) {
- list_del(&buf->list);
- vb2_buffer_done(&buf->vb.vb2_buf,
- VB2_BUF_STATE_QUEUED);
- }
- goto unlock_out;
- }
-
- mutex_unlock(&video->lock);
-
- return ret;
-unlock_out:
- mutex_unlock(&video->lock);
-streamoff:
- ret = vb2_streamoff(&video->buffer_queue, video->buffer_queue.type);
- return 0;
-}
-
-static int vpfe_buffer_init(struct vb2_buffer *vb)
-{
- struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
- struct vpfe_cap_buffer *buf = container_of(vbuf,
- struct vpfe_cap_buffer, vb);
-
- INIT_LIST_HEAD(&buf->list);
- return 0;
-}
-
-/* abort streaming and wait for last buffer */
-static void vpfe_stop_streaming(struct vb2_queue *vq)
-{
- struct vpfe_fh *fh = vb2_get_drv_priv(vq);
- struct vpfe_video_device *video = fh->video;
-
- /* release all active buffers */
- if (video->cur_frm == video->next_frm) {
- vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
- VB2_BUF_STATE_ERROR);
- } else {
- if (video->cur_frm != NULL)
- vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
- VB2_BUF_STATE_ERROR);
- if (video->next_frm != NULL)
- vb2_buffer_done(&video->next_frm->vb.vb2_buf,
- VB2_BUF_STATE_ERROR);
- }
-
- while (!list_empty(&video->dma_queue)) {
- video->next_frm = list_entry(video->dma_queue.next,
- struct vpfe_cap_buffer, list);
- list_del(&video->next_frm->list);
- vb2_buffer_done(&video->next_frm->vb.vb2_buf,
- VB2_BUF_STATE_ERROR);
- }
-}
-
-static void vpfe_buf_cleanup(struct vb2_buffer *vb)
-{
- struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
- struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue);
- struct vpfe_video_device *video = fh->video;
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_cap_buffer *buf = container_of(vbuf,
- struct vpfe_cap_buffer, vb);
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buf_cleanup\n");
- if (vb->state == VB2_BUF_STATE_ACTIVE)
- list_del_init(&buf->list);
-}
-
-static const struct vb2_ops video_qops = {
- .queue_setup = vpfe_buffer_queue_setup,
- .buf_init = vpfe_buffer_init,
- .buf_prepare = vpfe_buffer_prepare,
- .start_streaming = vpfe_start_streaming,
- .stop_streaming = vpfe_stop_streaming,
- .buf_cleanup = vpfe_buf_cleanup,
- .buf_queue = vpfe_buffer_queue,
- .wait_prepare = vb2_ops_wait_prepare,
- .wait_finish = vb2_ops_wait_finish,
-};
-
-/*
- * vpfe_reqbufs() - supported REQBUF only once opening
- * the device.
- */
-static int vpfe_reqbufs(struct file *file, void *priv,
- struct v4l2_requestbuffers *req_buf)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_fh *fh = file->private_data;
- struct vb2_queue *q;
- int ret;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_reqbufs\n");
-
- if (req_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
- req_buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT){
- v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buffer type\n");
- return -EINVAL;
- }
-
- ret = mutex_lock_interruptible(&video->lock);
- if (ret)
- return ret;
-
- if (video->io_usrs != 0) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Only one IO user allowed\n");
- ret = -EBUSY;
- goto unlock_out;
- }
- video->memory = req_buf->memory;
-
- /* Initialize videobuf2 queue as per the buffer type */
- q = &video->buffer_queue;
- q->type = req_buf->type;
- q->io_modes = VB2_MMAP | VB2_USERPTR;
- q->drv_priv = fh;
- q->min_buffers_needed = 1;
- q->ops = &video_qops;
- q->mem_ops = &vb2_dma_contig_memops;
- q->buf_struct_size = sizeof(struct vpfe_cap_buffer);
- q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
- q->dev = vpfe_dev->pdev;
- q->lock = &video->lock;
-
- ret = vb2_queue_init(q);
- if (ret) {
- v4l2_err(&vpfe_dev->v4l2_dev, "vb2_queue_init() failed\n");
- goto unlock_out;
- }
-
- fh->io_allowed = 1;
- video->io_usrs = 1;
- INIT_LIST_HEAD(&video->dma_queue);
- ret = vb2_reqbufs(&video->buffer_queue, req_buf);
-
-unlock_out:
- mutex_unlock(&video->lock);
- return ret;
-}
-
-/*
- * vpfe_querybuf() - query buffers for exchange
- */
-static int vpfe_querybuf(struct file *file, void *priv,
- struct v4l2_buffer *buf)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querybuf\n");
-
- if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
- buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n");
- return -EINVAL;
- }
-
- if (video->memory != V4L2_MEMORY_MMAP) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Invalid memory\n");
- return -EINVAL;
- }
-
- /* Call vb2_querybuf to get information */
- return vb2_querybuf(&video->buffer_queue, buf);
-}
-
-/*
- * vpfe_qbuf() - queue buffers for capture or processing
- */
-static int vpfe_qbuf(struct file *file, void *priv,
- struct v4l2_buffer *p)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_fh *fh = file->private_data;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_qbuf\n");
-
- if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
- p->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n");
- return -EINVAL;
- }
- /*
- * If this file handle is not allowed to do IO,
- * return error
- */
- if (!fh->io_allowed) {
- v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n");
- return -EACCES;
- }
-
- return vb2_qbuf(&video->buffer_queue,
- video->video_dev.v4l2_dev->mdev, p);
-}
-
-/*
- * vpfe_dqbuf() - deque buffer which is done with processing
- */
-static int vpfe_dqbuf(struct file *file, void *priv,
- struct v4l2_buffer *buf)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_dqbuf\n");
-
- if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
- buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n");
- return -EINVAL;
- }
-
- return vb2_dqbuf(&video->buffer_queue,
- buf, (file->f_flags & O_NONBLOCK));
-}
-
-/*
- * vpfe_streamon() - start streaming
- * @file: file pointer
- * @priv: void pointer
- * @buf_type: enum v4l2_buf_type
- *
- * queue buffer onto hardware for capture/processing and
- * start all the subdevs which are in media chain
- *
- * Return 0 on success, error code otherwise
- */
-static int vpfe_streamon(struct file *file, void *priv,
- enum v4l2_buf_type buf_type)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_pipeline *pipe = &video->pipe;
- struct vpfe_fh *fh = file->private_data;
- int ret = -EINVAL;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamon\n");
-
- if (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
- buf_type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
- v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n");
- return ret;
- }
- /* If file handle is not allowed IO, return error */
- if (!fh->io_allowed) {
- v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n");
- return -EACCES;
- }
- /* If buffer queue is empty, return error */
- if (list_empty(&video->buffer_queue.queued_list)) {
- v4l2_err(&vpfe_dev->v4l2_dev, "buffer queue is empty\n");
- return -EIO;
- }
- /* Validate the pipeline */
- if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
- ret = vpfe_video_validate_pipeline(pipe);
- if (ret < 0)
- return ret;
- }
- /* Call vb2_streamon to start streaming */
- return vb2_streamon(&video->buffer_queue, buf_type);
-}
-
-/*
- * vpfe_streamoff() - stop streaming
- * @file: file pointer
- * @priv: void pointer
- * @buf_type: enum v4l2_buf_type
- *
- * stop all the subdevs which are in media chain
- *
- * Return 0 on success, error code otherwise
- */
-static int vpfe_streamoff(struct file *file, void *priv,
- enum v4l2_buf_type buf_type)
-{
- struct vpfe_video_device *video = video_drvdata(file);
- struct vpfe_device *vpfe_dev = video->vpfe_dev;
- struct vpfe_fh *fh = file->private_data;
- int ret = 0;
-
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamoff\n");
-
- if (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
- buf_type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "Invalid buf type\n");
- return -EINVAL;
- }
-
- /* If io is allowed for this file handle, return error */
- if (!fh->io_allowed) {
- v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "fh->io_allowed\n");
- return -EACCES;
- }
-
- /* If streaming is not started, return error */
- if (!video->started) {
- v4l2_err(&vpfe_dev->v4l2_dev, "device is not started\n");
- return -EINVAL;
- }
-
- ret = mutex_lock_interruptible(&video->lock);
- if (ret)
- return ret;
-
- vpfe_stop_capture(video);
- ret = vb2_streamoff(&video->buffer_queue, buf_type);
- mutex_unlock(&video->lock);
-
- return ret;
-}
-
-/* vpfe capture ioctl operations */
-static const struct v4l2_ioctl_ops vpfe_ioctl_ops = {
- .vidioc_querycap = vpfe_querycap,
- .vidioc_g_fmt_vid_cap = vpfe_g_fmt,
- .vidioc_s_fmt_vid_cap = vpfe_s_fmt,
- .vidioc_try_fmt_vid_cap = vpfe_try_fmt,
- .vidioc_enum_fmt_vid_cap = vpfe_enum_fmt,
- .vidioc_g_fmt_vid_out = vpfe_g_fmt,
- .vidioc_s_fmt_vid_out = vpfe_s_fmt,
- .vidioc_try_fmt_vid_out = vpfe_try_fmt,
- .vidioc_enum_fmt_vid_out = vpfe_enum_fmt,
- .vidioc_enum_input = vpfe_enum_input,
- .vidioc_g_input = vpfe_g_input,
- .vidioc_s_input = vpfe_s_input,
- .vidioc_querystd = vpfe_querystd,
- .vidioc_s_std = vpfe_s_std,
- .vidioc_g_std = vpfe_g_std,
- .vidioc_enum_dv_timings = vpfe_enum_dv_timings,
- .vidioc_query_dv_timings = vpfe_query_dv_timings,
- .vidioc_s_dv_timings = vpfe_s_dv_timings,
- .vidioc_g_dv_timings = vpfe_g_dv_timings,
- .vidioc_reqbufs = vpfe_reqbufs,
- .vidioc_querybuf = vpfe_querybuf,
- .vidioc_qbuf = vpfe_qbuf,
- .vidioc_dqbuf = vpfe_dqbuf,
- .vidioc_streamon = vpfe_streamon,
- .vidioc_streamoff = vpfe_streamoff,
-};
-
-/* VPFE video init function */
-int vpfe_video_init(struct vpfe_video_device *video, const char *name)
-{
- const char *direction;
- int ret;
-
- switch (video->type) {
- case V4L2_BUF_TYPE_VIDEO_CAPTURE:
- direction = "output";
- video->pad.flags = MEDIA_PAD_FL_SINK;
- video->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- break;
-
- case V4L2_BUF_TYPE_VIDEO_OUTPUT:
- direction = "input";
- video->pad.flags = MEDIA_PAD_FL_SOURCE;
- video->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
- break;
-
- default:
- return -EINVAL;
- }
- /* Initialize field of video device */
- mutex_init(&video->lock);
- video->video_dev.release = video_device_release;
- video->video_dev.fops = &vpfe_fops;
- video->video_dev.ioctl_ops = &vpfe_ioctl_ops;
- video->video_dev.minor = -1;
- video->video_dev.tvnorms = 0;
- video->video_dev.lock = &video->lock;
- snprintf(video->video_dev.name, sizeof(video->video_dev.name),
- "DAVINCI VIDEO %s %s", name, direction);
-
- spin_lock_init(&video->irqlock);
- spin_lock_init(&video->dma_queue_lock);
- ret = media_entity_pads_init(&video->video_dev.entity,
- 1, &video->pad);
- if (ret < 0)
- return ret;
-
- video_set_drvdata(&video->video_dev, video);
-
- return 0;
-}
-
-/* vpfe video device register function */
-int vpfe_video_register(struct vpfe_video_device *video,
- struct v4l2_device *vdev)
-{
- int ret;
-
- video->video_dev.v4l2_dev = vdev;
-
- if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
- video->video_dev.device_caps = V4L2_CAP_VIDEO_CAPTURE;
- else
- video->video_dev.device_caps = V4L2_CAP_VIDEO_OUTPUT;
- video->video_dev.device_caps |= V4L2_CAP_STREAMING;
- ret = video_register_device(&video->video_dev, VFL_TYPE_GRABBER, -1);
- if (ret < 0)
- pr_err("%s: could not register video device (%d)\n",
- __func__, ret);
- return ret;
-}
-
-/* vpfe video device unregister function */
-void vpfe_video_unregister(struct vpfe_video_device *video)
-{
- if (video_is_registered(&video->video_dev)) {
- video_unregister_device(&video->video_dev);
- media_entity_cleanup(&video->video_dev.entity);
- }
-}
diff --git a/drivers/staging/media/davinci_vpfe/vpfe_video.h b/drivers/staging/media/davinci_vpfe/vpfe_video.h
deleted file mode 100644
index 5d01c4883ab4..000000000000
--- a/drivers/staging/media/davinci_vpfe/vpfe_video.h
+++ /dev/null
@@ -1,150 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2012 Texas Instruments Inc
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation version 2.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * Contributors:
- * Manjunath Hadli <manjunath.hadli@ti.com>
- * Prabhakar Lad <prabhakar.lad@ti.com>
- */
-
-#ifndef _DAVINCI_VPFE_VIDEO_H
-#define _DAVINCI_VPFE_VIDEO_H
-
-#include <media/videobuf2-v4l2.h>
-#include <media/videobuf2-dma-contig.h>
-
-struct vpfe_device;
-
-/*
- * struct vpfe_video_operations - VPFE video operations
- * @queue: Resume streaming when a buffer is queued. Called on VIDIOC_QBUF
- * if there was no buffer previously queued.
- */
-struct vpfe_video_operations {
- int (*queue)(struct vpfe_device *vpfe_dev, unsigned long addr);
-};
-
-enum vpfe_pipeline_stream_state {
- VPFE_PIPELINE_STREAM_STOPPED = 0,
- VPFE_PIPELINE_STREAM_CONTINUOUS = 1,
- VPFE_PIPELINE_STREAM_SINGLESHOT = 2,
-};
-
-enum vpfe_video_state {
- /* indicates that buffer is not queued */
- VPFE_VIDEO_BUFFER_NOT_QUEUED = 0,
- /* indicates that buffer is queued */
- VPFE_VIDEO_BUFFER_QUEUED = 1,
-};
-
-struct vpfe_pipeline {
- /* media pipeline */
- struct media_pipeline *pipe;
- struct media_graph graph;
- /* state of the pipeline, continuous,
- * single-shot or stopped
- */
- enum vpfe_pipeline_stream_state state;
- /* number of active input video entities */
- unsigned int input_num;
- /* number of active output video entities */
- unsigned int output_num;
- /* input video nodes in case of single-shot mode */
- struct vpfe_video_device *inputs[10];
- /* capturing video nodes */
- struct vpfe_video_device *outputs[10];
-};
-
-#define to_vpfe_pipeline(__e) \
- container_of((__e)->pipe, struct vpfe_pipeline, pipe)
-
-#define to_vpfe_video(vdev) \
- container_of(vdev, struct vpfe_video_device, video_dev)
-
-struct vpfe_cap_buffer {
- struct vb2_v4l2_buffer vb;
- struct list_head list;
-};
-
-struct vpfe_video_device {
- /* vpfe device */
- struct vpfe_device *vpfe_dev;
- /* video dev */
- struct video_device video_dev;
- /* media pad of video entity */
- struct media_pad pad;
- /* video operations supported by video device */
- const struct vpfe_video_operations *ops;
- /* type of the video buffers used by user */
- enum v4l2_buf_type type;
- /* Indicates id of the field which is being captured */
- u32 field_id;
- /* pipeline for which video device is part of */
- struct vpfe_pipeline pipe;
- /* Indicates whether streaming started */
- u8 started;
- /* Indicates state of the stream */
- unsigned int state;
- /* current input at the sub device */
- int current_input;
- /*
- * This field keeps track of type of buffer exchange mechanism
- * user has selected
- */
- enum v4l2_memory memory;
- /* number of open instances of the channel */
- u32 usrs;
- /* flag to indicate whether decoder is initialized */
- u8 initialized;
- /* skip frame count */
- u8 skip_frame_count;
- /* skip frame count init value */
- u8 skip_frame_count_init;
- /* time per frame for skipping */
- struct v4l2_fract timeperframe;
- /* ptr to currently selected sub device */
- struct vpfe_ext_subdev_info *current_ext_subdev;
- /* Pointer pointing to current vpfe_cap_buffer */
- struct vpfe_cap_buffer *cur_frm;
- /* Pointer pointing to next vpfe_cap_buffer */
- struct vpfe_cap_buffer *next_frm;
- /* Used to store pixel format */
- struct v4l2_format fmt;
- struct vb2_queue buffer_queue;
- /* Queue of filled frames */
- struct list_head dma_queue;
- spinlock_t irqlock;
- /* IRQ lock for DMA queue */
- spinlock_t dma_queue_lock;
- /* lock used to serialize all video4linux ioctls */
- struct mutex lock;
- /* number of users performing IO */
- u32 io_usrs;
- /* Currently selected or default standard */
- v4l2_std_id stdid;
- /*
- * offset where second field starts from the starting of the
- * buffer for field separated YCbCr formats
- */
- u32 field_off;
-};
-
-int vpfe_video_is_pipe_ready(struct vpfe_pipeline *pipe);
-void vpfe_video_unregister(struct vpfe_video_device *video);
-int vpfe_video_register(struct vpfe_video_device *video,
- struct v4l2_device *vdev);
-int vpfe_video_init(struct vpfe_video_device *video, const char *name);
-void vpfe_video_process_buffer_complete(struct vpfe_video_device *video);
-void vpfe_video_schedule_bottom_field(struct vpfe_video_device *video);
-void vpfe_video_schedule_next_buffer(struct vpfe_video_device *video);
-
-#endif /* _DAVINCI_VPFE_VIDEO_H */
diff --git a/drivers/staging/media/hantro/Kconfig b/drivers/staging/media/hantro/Kconfig
index be133bbaa68a..de77fe6554e7 100644
--- a/drivers/staging/media/hantro/Kconfig
+++ b/drivers/staging/media/hantro/Kconfig
@@ -20,4 +20,4 @@ config VIDEO_HANTRO_ROCKCHIP
depends on ARCH_ROCKCHIP || COMPILE_TEST
default y
help
- Enable support for RK3288 and RK3399 SoCs.
+ Enable support for RK3288, RK3328, and RK3399 SoCs.
diff --git a/drivers/staging/media/hantro/Makefile b/drivers/staging/media/hantro/Makefile
index 1584acdbf4a3..5d6b0383d280 100644
--- a/drivers/staging/media/hantro/Makefile
+++ b/drivers/staging/media/hantro/Makefile
@@ -4,11 +4,16 @@ hantro-vpu-y += \
hantro_drv.o \
hantro_v4l2.o \
hantro_h1_jpeg_enc.o \
+ hantro_g1_h264_dec.o \
hantro_g1_mpeg2_dec.o \
+ hantro_g1_vp8_dec.o \
rk3399_vpu_hw_jpeg_enc.o \
rk3399_vpu_hw_mpeg2_dec.o \
+ rk3399_vpu_hw_vp8_dec.o \
hantro_jpeg.o \
- hantro_mpeg2.o
+ hantro_h264.o \
+ hantro_mpeg2.o \
+ hantro_vp8.o
hantro-vpu-$(CONFIG_VIDEO_HANTRO_ROCKCHIP) += \
rk3288_vpu_hw.o \
diff --git a/drivers/staging/media/hantro/hantro.h b/drivers/staging/media/hantro/hantro.h
index 62dcca9ff19c..f670bbde4159 100644
--- a/drivers/staging/media/hantro/hantro.h
+++ b/drivers/staging/media/hantro/hantro.h
@@ -20,11 +20,20 @@
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-dma-contig.h>
#include "hantro_hw.h"
+#define VP8_MB_DIM 16
+#define VP8_MB_WIDTH(w) DIV_ROUND_UP(w, VP8_MB_DIM)
+#define VP8_MB_HEIGHT(h) DIV_ROUND_UP(h, VP8_MB_DIM)
+
+#define H264_MB_DIM 16
+#define H264_MB_WIDTH(w) DIV_ROUND_UP(w, H264_MB_DIM)
+#define H264_MB_HEIGHT(h) DIV_ROUND_UP(h, H264_MB_DIM)
+
#define MPEG2_MB_DIM 16
#define MPEG2_MB_WIDTH(w) DIV_ROUND_UP(w, MPEG2_MB_DIM)
#define MPEG2_MB_HEIGHT(h) DIV_ROUND_UP(h, MPEG2_MB_DIM)
@@ -38,8 +47,9 @@ struct hantro_codec_ops;
#define HANTRO_JPEG_ENCODER BIT(0)
#define HANTRO_ENCODERS 0x0000ffff
-
#define HANTRO_MPEG2_DECODER BIT(16)
+#define HANTRO_VP8_DECODER BIT(17)
+#define HANTRO_H264_DECODER BIT(18)
#define HANTRO_DECODERS 0xffff0000
/**
@@ -96,22 +106,24 @@ struct hantro_variant {
* enum hantro_codec_mode - codec operating mode.
* @HANTRO_MODE_NONE: No operating mode. Used for RAW video formats.
* @HANTRO_MODE_JPEG_ENC: JPEG encoder.
+ * @HANTRO_MODE_H264_DEC: H264 decoder.
* @HANTRO_MODE_MPEG2_DEC: MPEG-2 decoder.
+ * @HANTRO_MODE_VP8_DEC: VP8 decoder.
*/
enum hantro_codec_mode {
HANTRO_MODE_NONE = -1,
HANTRO_MODE_JPEG_ENC,
+ HANTRO_MODE_H264_DEC,
HANTRO_MODE_MPEG2_DEC,
+ HANTRO_MODE_VP8_DEC,
};
/*
* struct hantro_ctrl - helper type to declare supported controls
- * @id: V4L2 control ID (V4L2_CID_xxx)
* @codec: codec id this control belong to (HANTRO_JPEG_ENCODER, etc.)
* @cfg: control configuration
*/
struct hantro_ctrl {
- unsigned int id;
unsigned int codec;
struct v4l2_ctrl_config cfg;
};
@@ -215,6 +227,7 @@ struct hantro_dev {
* @codec_ops: Set of operations related to codec mode.
* @jpeg_enc: JPEG-encoding context.
* @mpeg2_dec: MPEG-2-decoding context.
+ * @vp8_dec: VP8-decoding context.
*/
struct hantro_ctx {
struct hantro_dev *dev;
@@ -239,8 +252,10 @@ struct hantro_ctx {
/* Specific for particular codec modes. */
union {
+ struct hantro_h264_dec_hw_ctx h264_dec;
struct hantro_jpeg_enc_hw_ctx jpeg_enc;
struct hantro_mpeg2_dec_hw_ctx mpeg2_dec;
+ struct hantro_vp8_dec_hw_ctx vp8_dec;
};
};
@@ -265,6 +280,12 @@ struct hantro_fmt {
struct v4l2_frmsize_stepwise frmsize;
};
+struct hantro_reg {
+ u32 base;
+ u32 shift;
+ u32 mask;
+};
+
/* Logging helpers */
/**
@@ -343,9 +364,33 @@ static inline u32 vdpu_read(struct hantro_dev *vpu, u32 reg)
return val;
}
+static inline void hantro_reg_write(struct hantro_dev *vpu,
+ const struct hantro_reg *reg,
+ u32 val)
+{
+ u32 v;
+
+ v = vdpu_read(vpu, reg->base);
+ v &= ~(reg->mask << reg->shift);
+ v |= ((val & reg->mask) << reg->shift);
+ vdpu_write_relaxed(vpu, v, reg->base);
+}
+
bool hantro_is_encoder_ctx(const struct hantro_ctx *ctx);
void *hantro_get_ctrl(struct hantro_ctx *ctx, u32 id);
dma_addr_t hantro_get_ref(struct vb2_queue *q, u64 ts);
+static inline struct vb2_v4l2_buffer *
+hantro_get_src_buf(struct hantro_ctx *ctx)
+{
+ return v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+}
+
+static inline struct vb2_v4l2_buffer *
+hantro_get_dst_buf(struct hantro_ctx *ctx)
+{
+ return v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+}
+
#endif /* HANTRO_H_ */
diff --git a/drivers/staging/media/hantro/hantro_drv.c b/drivers/staging/media/hantro/hantro_drv.c
index 4a7afad2af92..6d9d41170832 100644
--- a/drivers/staging/media/hantro/hantro_drv.c
+++ b/drivers/staging/media/hantro/hantro_drv.c
@@ -111,8 +111,6 @@ static void hantro_job_finish(struct hantro_dev *vpu,
src->sequence = ctx->sequence_out++;
dst->sequence = ctx->sequence_cap++;
- v4l2_m2m_buf_copy_metadata(src, dst, true);
-
ret = ctx->buf_finish(ctx, &dst->vb2_buf, bytesused);
if (ret)
result = VB2_BUF_STATE_ERROR;
@@ -153,11 +151,37 @@ void hantro_watchdog(struct work_struct *work)
}
}
+void hantro_prepare_run(struct hantro_ctx *ctx)
+{
+ struct vb2_v4l2_buffer *src_buf;
+
+ src_buf = hantro_get_src_buf(ctx);
+ v4l2_ctrl_request_setup(src_buf->vb2_buf.req_obj.req,
+ &ctx->ctrl_handler);
+}
+
+void hantro_finish_run(struct hantro_ctx *ctx)
+{
+ struct vb2_v4l2_buffer *src_buf;
+
+ src_buf = hantro_get_src_buf(ctx);
+ v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req,
+ &ctx->ctrl_handler);
+
+ /* Kick the watchdog. */
+ schedule_delayed_work(&ctx->dev->watchdog_work,
+ msecs_to_jiffies(2000));
+}
+
static void device_run(void *priv)
{
struct hantro_ctx *ctx = priv;
+ struct vb2_v4l2_buffer *src, *dst;
int ret;
+ src = hantro_get_src_buf(ctx);
+ dst = hantro_get_dst_buf(ctx);
+
ret = clk_bulk_enable(ctx->dev->variant->num_clocks, ctx->dev->clocks);
if (ret)
goto err_cancel_job;
@@ -165,6 +189,8 @@ static void device_run(void *priv)
if (ret < 0)
goto err_cancel_job;
+ v4l2_m2m_buf_copy_metadata(src, dst, true);
+
ctx->codec_ops->run(ctx);
return;
@@ -262,28 +288,74 @@ static const struct v4l2_ctrl_ops hantro_ctrl_ops = {
.s_ctrl = hantro_s_ctrl,
};
-static struct hantro_ctrl controls[] = {
+static const struct hantro_ctrl controls[] = {
{
- .id = V4L2_CID_JPEG_COMPRESSION_QUALITY,
.codec = HANTRO_JPEG_ENCODER,
.cfg = {
+ .id = V4L2_CID_JPEG_COMPRESSION_QUALITY,
.min = 5,
.max = 100,
.step = 1,
.def = 50,
+ .ops = &hantro_ctrl_ops,
},
}, {
- .id = V4L2_CID_MPEG_VIDEO_MPEG2_SLICE_PARAMS,
.codec = HANTRO_MPEG2_DECODER,
.cfg = {
- .elem_size = sizeof(struct v4l2_ctrl_mpeg2_slice_params),
+ .id = V4L2_CID_MPEG_VIDEO_MPEG2_SLICE_PARAMS,
},
}, {
- .id = V4L2_CID_MPEG_VIDEO_MPEG2_QUANTIZATION,
.codec = HANTRO_MPEG2_DECODER,
.cfg = {
- .elem_size = sizeof(struct v4l2_ctrl_mpeg2_quantization),
+ .id = V4L2_CID_MPEG_VIDEO_MPEG2_QUANTIZATION,
+ },
+ }, {
+ .codec = HANTRO_VP8_DECODER,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_VP8_FRAME_HEADER,
+ },
+ }, {
+ .codec = HANTRO_H264_DECODER,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_DECODE_PARAMS,
},
+ }, {
+ .codec = HANTRO_H264_DECODER,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_SLICE_PARAMS,
+ },
+ }, {
+ .codec = HANTRO_H264_DECODER,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_SPS,
+ },
+ }, {
+ .codec = HANTRO_H264_DECODER,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_PPS,
+ },
+ }, {
+ .codec = HANTRO_H264_DECODER,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_SCALING_MATRIX,
+ },
+ }, {
+ .codec = HANTRO_H264_DECODER,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_DECODE_MODE,
+ .min = V4L2_MPEG_VIDEO_H264_DECODE_MODE_FRAME_BASED,
+ .def = V4L2_MPEG_VIDEO_H264_DECODE_MODE_FRAME_BASED,
+ .max = V4L2_MPEG_VIDEO_H264_DECODE_MODE_FRAME_BASED,
+ },
+ }, {
+ .codec = HANTRO_H264_DECODER,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_START_CODE,
+ .min = V4L2_MPEG_VIDEO_H264_START_CODE_ANNEX_B,
+ .def = V4L2_MPEG_VIDEO_H264_START_CODE_ANNEX_B,
+ .max = V4L2_MPEG_VIDEO_H264_START_CODE_ANNEX_B,
+ },
+ }, {
},
};
@@ -298,22 +370,12 @@ static int hantro_ctrls_setup(struct hantro_dev *vpu,
for (i = 0; i < num_ctrls; i++) {
if (!(allowed_codecs & controls[i].codec))
continue;
- if (!controls[i].cfg.elem_size) {
- v4l2_ctrl_new_std(&ctx->ctrl_handler,
- &hantro_ctrl_ops,
- controls[i].id, controls[i].cfg.min,
- controls[i].cfg.max,
- controls[i].cfg.step,
- controls[i].cfg.def);
- } else {
- controls[i].cfg.id = controls[i].id;
- v4l2_ctrl_new_custom(&ctx->ctrl_handler,
- &controls[i].cfg, NULL);
- }
+ v4l2_ctrl_new_custom(&ctx->ctrl_handler,
+ &controls[i].cfg, NULL);
if (ctx->ctrl_handler.error) {
vpu_err("Adding control (%d) failed %d\n",
- controls[i].id,
+ controls[i].cfg.id,
ctx->ctrl_handler.error);
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
return ctx->ctrl_handler.error;
@@ -419,6 +481,7 @@ static const struct v4l2_file_operations hantro_fops = {
static const struct of_device_id of_hantro_match[] = {
#ifdef CONFIG_VIDEO_HANTRO_ROCKCHIP
{ .compatible = "rockchip,rk3399-vpu", .data = &rk3399_vpu_variant, },
+ { .compatible = "rockchip,rk3328-vpu", .data = &rk3328_vpu_variant, },
{ .compatible = "rockchip,rk3288-vpu", .data = &rk3288_vpu_variant, },
#endif
{ /* sentinel */ }
@@ -724,6 +787,7 @@ static int hantro_probe(struct platform_device *pdev)
dev_err(vpu->dev, "Could not set DMA coherent mask.\n");
return ret;
}
+ vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32));
for (i = 0; i < vpu->variant->num_irqs; i++) {
const char *irq_name = vpu->variant->irqs[i].name;
diff --git a/drivers/staging/media/hantro/hantro_g1_h264_dec.c b/drivers/staging/media/hantro/hantro_g1_h264_dec.c
new file mode 100644
index 000000000000..7ab534936843
--- /dev/null
+++ b/drivers/staging/media/hantro/hantro_g1_h264_dec.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip RK3288 VPU codec driver
+ *
+ * Copyright (c) 2014 Rockchip Electronics Co., Ltd.
+ * Hertz Wong <hertz.wong@rock-chips.com>
+ * Herman Chen <herman.chen@rock-chips.com>
+ *
+ * Copyright (C) 2014 Google, Inc.
+ * Tomasz Figa <tfiga@chromium.org>
+ */
+
+#include <linux/types.h>
+#include <linux/sort.h>
+
+#include <media/v4l2-mem2mem.h>
+
+#include "hantro_g1_regs.h"
+#include "hantro_hw.h"
+#include "hantro_v4l2.h"
+
+static void set_params(struct hantro_ctx *ctx)
+{
+ const struct hantro_h264_dec_ctrls *ctrls = &ctx->h264_dec.ctrls;
+ const struct v4l2_ctrl_h264_decode_params *dec_param = ctrls->decode;
+ const struct v4l2_ctrl_h264_slice_params *slices = ctrls->slices;
+ const struct v4l2_ctrl_h264_sps *sps = ctrls->sps;
+ const struct v4l2_ctrl_h264_pps *pps = ctrls->pps;
+ struct vb2_v4l2_buffer *src_buf = hantro_get_src_buf(ctx);
+ struct hantro_dev *vpu = ctx->dev;
+ u32 reg;
+
+ /* Decoder control register 0. */
+ reg = G1_REG_DEC_CTRL0_DEC_AXI_WR_ID(0x0);
+ if (sps->flags & V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD)
+ reg |= G1_REG_DEC_CTRL0_SEQ_MBAFF_E;
+ reg |= G1_REG_DEC_CTRL0_PICORD_COUNT_E;
+ if (dec_param->nal_ref_idc)
+ reg |= G1_REG_DEC_CTRL0_WRITE_MVS_E;
+
+ if (!(sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) &&
+ (sps->flags & V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD ||
+ slices[0].flags & V4L2_H264_SLICE_FLAG_FIELD_PIC))
+ reg |= G1_REG_DEC_CTRL0_PIC_INTERLACE_E;
+ if (slices[0].flags & V4L2_H264_SLICE_FLAG_FIELD_PIC)
+ reg |= G1_REG_DEC_CTRL0_PIC_FIELDMODE_E;
+ if (!(slices[0].flags & V4L2_H264_SLICE_FLAG_BOTTOM_FIELD))
+ reg |= G1_REG_DEC_CTRL0_PIC_TOPFIELD_E;
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL0);
+
+ /* Decoder control register 1. */
+ reg = G1_REG_DEC_CTRL1_PIC_MB_WIDTH(sps->pic_width_in_mbs_minus1 + 1) |
+ G1_REG_DEC_CTRL1_PIC_MB_HEIGHT_P(sps->pic_height_in_map_units_minus1 + 1) |
+ G1_REG_DEC_CTRL1_REF_FRAMES(sps->max_num_ref_frames);
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL1);
+
+ /* Decoder control register 2. */
+ reg = G1_REG_DEC_CTRL2_CH_QP_OFFSET(pps->chroma_qp_index_offset) |
+ G1_REG_DEC_CTRL2_CH_QP_OFFSET2(pps->second_chroma_qp_index_offset);
+
+ /* always use the matrix sent from userspace */
+ reg |= G1_REG_DEC_CTRL2_TYPE1_QUANT_E;
+
+ if (slices[0].flags & V4L2_H264_SLICE_FLAG_FIELD_PIC)
+ reg |= G1_REG_DEC_CTRL2_FIELDPIC_FLAG_E;
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL2);
+
+ /* Decoder control register 3. */
+ reg = G1_REG_DEC_CTRL3_START_CODE_E |
+ G1_REG_DEC_CTRL3_INIT_QP(pps->pic_init_qp_minus26 + 26) |
+ G1_REG_DEC_CTRL3_STREAM_LEN(vb2_get_plane_payload(&src_buf->vb2_buf, 0));
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL3);
+
+ /* Decoder control register 4. */
+ reg = G1_REG_DEC_CTRL4_FRAMENUM_LEN(sps->log2_max_frame_num_minus4 + 4) |
+ G1_REG_DEC_CTRL4_FRAMENUM(slices[0].frame_num) |
+ G1_REG_DEC_CTRL4_WEIGHT_BIPR_IDC(pps->weighted_bipred_idc);
+ if (pps->flags & V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE)
+ reg |= G1_REG_DEC_CTRL4_CABAC_E;
+ if (sps->flags & V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE)
+ reg |= G1_REG_DEC_CTRL4_DIR_8X8_INFER_E;
+ if (sps->chroma_format_idc == 0)
+ reg |= G1_REG_DEC_CTRL4_BLACKWHITE_E;
+ if (pps->flags & V4L2_H264_PPS_FLAG_WEIGHTED_PRED)
+ reg |= G1_REG_DEC_CTRL4_WEIGHT_PRED_E;
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL4);
+
+ /* Decoder control register 5. */
+ reg = G1_REG_DEC_CTRL5_REFPIC_MK_LEN(slices[0].dec_ref_pic_marking_bit_size) |
+ G1_REG_DEC_CTRL5_IDR_PIC_ID(slices[0].idr_pic_id);
+ if (pps->flags & V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED)
+ reg |= G1_REG_DEC_CTRL5_CONST_INTRA_E;
+ if (pps->flags & V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT)
+ reg |= G1_REG_DEC_CTRL5_FILT_CTRL_PRES;
+ if (pps->flags & V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT)
+ reg |= G1_REG_DEC_CTRL5_RDPIC_CNT_PRES;
+ if (pps->flags & V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE)
+ reg |= G1_REG_DEC_CTRL5_8X8TRANS_FLAG_E;
+ if (dec_param->flags & V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC)
+ reg |= G1_REG_DEC_CTRL5_IDR_PIC_E;
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL5);
+
+ /* Decoder control register 6. */
+ reg = G1_REG_DEC_CTRL6_PPS_ID(slices[0].pic_parameter_set_id) |
+ G1_REG_DEC_CTRL6_REFIDX0_ACTIVE(pps->num_ref_idx_l0_default_active_minus1 + 1) |
+ G1_REG_DEC_CTRL6_REFIDX1_ACTIVE(pps->num_ref_idx_l1_default_active_minus1 + 1) |
+ G1_REG_DEC_CTRL6_POC_LENGTH(slices[0].pic_order_cnt_bit_size);
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL6);
+
+ /* Error concealment register. */
+ vdpu_write_relaxed(vpu, 0, G1_REG_ERR_CONC);
+
+ /* Prediction filter tap register. */
+ vdpu_write_relaxed(vpu,
+ G1_REG_PRED_FLT_PRED_BC_TAP_0_0(1) |
+ G1_REG_PRED_FLT_PRED_BC_TAP_0_1(-5 & 0x3ff) |
+ G1_REG_PRED_FLT_PRED_BC_TAP_0_2(20),
+ G1_REG_PRED_FLT);
+
+ /* Reference picture buffer control register. */
+ vdpu_write_relaxed(vpu, 0, G1_REG_REF_BUF_CTRL);
+
+ /* Reference picture buffer control register 2. */
+ vdpu_write_relaxed(vpu, G1_REG_REF_BUF_CTRL2_APF_THRESHOLD(8),
+ G1_REG_REF_BUF_CTRL2);
+}
+
+static void set_ref(struct hantro_ctx *ctx)
+{
+ struct v4l2_h264_dpb_entry *dpb = ctx->h264_dec.dpb;
+ const u8 *b0_reflist, *b1_reflist, *p_reflist;
+ struct hantro_dev *vpu = ctx->dev;
+ u32 dpb_longterm = 0;
+ u32 dpb_valid = 0;
+ int reg_num;
+ u32 reg;
+ int i;
+
+ /*
+ * Set up bit maps of valid and long term DPBs.
+ * NOTE: The bits are reversed, i.e. MSb is DPB 0.
+ */
+ for (i = 0; i < HANTRO_H264_DPB_SIZE; ++i) {
+ if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)
+ dpb_valid |= BIT(HANTRO_H264_DPB_SIZE - 1 - i);
+
+ if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)
+ dpb_longterm |= BIT(HANTRO_H264_DPB_SIZE - 1 - i);
+ }
+ vdpu_write_relaxed(vpu, dpb_valid << 16, G1_REG_VALID_REF);
+ vdpu_write_relaxed(vpu, dpb_longterm << 16, G1_REG_LT_REF);
+
+ /*
+ * Set up reference frame picture numbers.
+ *
+ * Each G1_REG_REF_PIC(x) register contains numbers of two
+ * subsequential reference pictures.
+ */
+ for (i = 0; i < HANTRO_H264_DPB_SIZE; i += 2) {
+ reg = 0;
+ if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)
+ reg |= G1_REG_REF_PIC_REFER0_NBR(dpb[i].pic_num);
+ else
+ reg |= G1_REG_REF_PIC_REFER0_NBR(dpb[i].frame_num);
+
+ if (dpb[i + 1].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)
+ reg |= G1_REG_REF_PIC_REFER1_NBR(dpb[i + 1].pic_num);
+ else
+ reg |= G1_REG_REF_PIC_REFER1_NBR(dpb[i + 1].frame_num);
+
+ vdpu_write_relaxed(vpu, reg, G1_REG_REF_PIC(i / 2));
+ }
+
+ b0_reflist = ctx->h264_dec.reflists.b0;
+ b1_reflist = ctx->h264_dec.reflists.b1;
+ p_reflist = ctx->h264_dec.reflists.p;
+
+ /*
+ * Each G1_REG_BD_REF_PIC(x) register contains three entries
+ * of each forward and backward picture list.
+ */
+ reg_num = 0;
+ for (i = 0; i < 15; i += 3) {
+ reg = G1_REG_BD_REF_PIC_BINIT_RLIST_F0(b0_reflist[i]) |
+ G1_REG_BD_REF_PIC_BINIT_RLIST_F1(b0_reflist[i + 1]) |
+ G1_REG_BD_REF_PIC_BINIT_RLIST_F2(b0_reflist[i + 2]) |
+ G1_REG_BD_REF_PIC_BINIT_RLIST_B0(b1_reflist[i]) |
+ G1_REG_BD_REF_PIC_BINIT_RLIST_B1(b1_reflist[i + 1]) |
+ G1_REG_BD_REF_PIC_BINIT_RLIST_B2(b1_reflist[i + 2]);
+ vdpu_write_relaxed(vpu, reg, G1_REG_BD_REF_PIC(reg_num++));
+ }
+
+ /*
+ * G1_REG_BD_P_REF_PIC register contains last entries (index 15)
+ * of forward and backward reference picture lists and first 4 entries
+ * of P forward picture list.
+ */
+ reg = G1_REG_BD_P_REF_PIC_BINIT_RLIST_F15(b0_reflist[15]) |
+ G1_REG_BD_P_REF_PIC_BINIT_RLIST_B15(b1_reflist[15]) |
+ G1_REG_BD_P_REF_PIC_PINIT_RLIST_F0(p_reflist[0]) |
+ G1_REG_BD_P_REF_PIC_PINIT_RLIST_F1(p_reflist[1]) |
+ G1_REG_BD_P_REF_PIC_PINIT_RLIST_F2(p_reflist[2]) |
+ G1_REG_BD_P_REF_PIC_PINIT_RLIST_F3(p_reflist[3]);
+ vdpu_write_relaxed(vpu, reg, G1_REG_BD_P_REF_PIC);
+
+ /*
+ * Each G1_REG_FWD_PIC(x) register contains six consecutive
+ * entries of P forward picture list, starting from index 4.
+ */
+ reg_num = 0;
+ for (i = 4; i < HANTRO_H264_DPB_SIZE; i += 6) {
+ reg = G1_REG_FWD_PIC_PINIT_RLIST_F0(p_reflist[i]) |
+ G1_REG_FWD_PIC_PINIT_RLIST_F1(p_reflist[i + 1]) |
+ G1_REG_FWD_PIC_PINIT_RLIST_F2(p_reflist[i + 2]) |
+ G1_REG_FWD_PIC_PINIT_RLIST_F3(p_reflist[i + 3]) |
+ G1_REG_FWD_PIC_PINIT_RLIST_F4(p_reflist[i + 4]) |
+ G1_REG_FWD_PIC_PINIT_RLIST_F5(p_reflist[i + 5]);
+ vdpu_write_relaxed(vpu, reg, G1_REG_FWD_PIC(reg_num++));
+ }
+
+ /* Set up addresses of DPB buffers. */
+ for (i = 0; i < HANTRO_H264_DPB_SIZE; i++) {
+ struct vb2_buffer *buf = hantro_h264_get_ref_buf(ctx, i);
+
+ vdpu_write_relaxed(vpu, vb2_dma_contig_plane_dma_addr(buf, 0),
+ G1_REG_ADDR_REF(i));
+ }
+}
+
+static void set_buffers(struct hantro_ctx *ctx)
+{
+ const struct hantro_h264_dec_ctrls *ctrls = &ctx->h264_dec.ctrls;
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ struct hantro_dev *vpu = ctx->dev;
+ dma_addr_t src_dma, dst_dma;
+
+ src_buf = hantro_get_src_buf(ctx);
+ dst_buf = hantro_get_dst_buf(ctx);
+
+ /* Source (stream) buffer. */
+ src_dma = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
+ vdpu_write_relaxed(vpu, src_dma, G1_REG_ADDR_STR);
+
+ /* Destination (decoded frame) buffer. */
+ dst_dma = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
+ vdpu_write_relaxed(vpu, dst_dma, G1_REG_ADDR_DST);
+
+ /* Higher profiles require DMV buffer appended to reference frames. */
+ if (ctrls->sps->profile_idc > 66) {
+ size_t pic_size = ctx->h264_dec.pic_size;
+ size_t mv_offset = round_up(pic_size, 8);
+
+ if (ctrls->slices[0].flags & V4L2_H264_SLICE_FLAG_BOTTOM_FIELD)
+ mv_offset += 32 * H264_MB_WIDTH(ctx->dst_fmt.width);
+
+ vdpu_write_relaxed(vpu, dst_dma + mv_offset,
+ G1_REG_ADDR_DIR_MV);
+ }
+
+ /* Auxiliary buffer prepared in hantro_g1_h264_dec_prepare_table(). */
+ vdpu_write_relaxed(vpu, ctx->h264_dec.priv.dma, G1_REG_ADDR_QTABLE);
+}
+
+void hantro_g1_h264_dec_run(struct hantro_ctx *ctx)
+{
+ struct hantro_dev *vpu = ctx->dev;
+
+ /* Prepare the H264 decoder context. */
+ if (hantro_h264_dec_prepare_run(ctx))
+ return;
+
+ /* Configure hardware registers. */
+ set_params(ctx);
+ set_ref(ctx);
+ set_buffers(ctx);
+
+ hantro_finish_run(ctx);
+
+ /* Start decoding! */
+ vdpu_write_relaxed(vpu,
+ G1_REG_CONFIG_DEC_AXI_RD_ID(0xffu) |
+ G1_REG_CONFIG_DEC_TIMEOUT_E |
+ G1_REG_CONFIG_DEC_OUT_ENDIAN |
+ G1_REG_CONFIG_DEC_STRENDIAN_E |
+ G1_REG_CONFIG_DEC_MAX_BURST(16) |
+ G1_REG_CONFIG_DEC_OUTSWAP32_E |
+ G1_REG_CONFIG_DEC_INSWAP32_E |
+ G1_REG_CONFIG_DEC_STRSWAP32_E |
+ G1_REG_CONFIG_DEC_CLK_GATE_E,
+ G1_REG_CONFIG);
+ vdpu_write(vpu, G1_REG_INTERRUPT_DEC_E, G1_REG_INTERRUPT);
+}
diff --git a/drivers/staging/media/hantro/hantro_g1_mpeg2_dec.c b/drivers/staging/media/hantro/hantro_g1_mpeg2_dec.c
index e592c1b66375..80f0e94f8afa 100644
--- a/drivers/staging/media/hantro/hantro_g1_mpeg2_dec.c
+++ b/drivers/staging/media/hantro/hantro_g1_mpeg2_dec.c
@@ -167,12 +167,11 @@ void hantro_g1_mpeg2_dec_run(struct hantro_ctx *ctx)
const struct v4l2_mpeg2_picture *picture;
u32 reg;
- src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
- dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ src_buf = hantro_get_src_buf(ctx);
+ dst_buf = hantro_get_dst_buf(ctx);
/* Apply request controls if any */
- v4l2_ctrl_request_setup(src_buf->vb2_buf.req_obj.req,
- &ctx->ctrl_handler);
+ hantro_prepare_run(ctx);
slice_params = hantro_get_ctrl(ctx,
V4L2_CID_MPEG_VIDEO_MPEG2_SLICE_PARAMS);
@@ -248,12 +247,7 @@ void hantro_g1_mpeg2_dec_run(struct hantro_ctx *ctx)
&dst_buf->vb2_buf,
sequence, picture, slice_params);
- /* Controls no longer in-use, we can complete them */
- v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req,
- &ctx->ctrl_handler);
-
- /* Kick the watchdog and start decoding */
- schedule_delayed_work(&vpu->watchdog_work, msecs_to_jiffies(2000));
+ hantro_finish_run(ctx);
reg = G1_REG_DEC_E(1);
vdpu_write(vpu, reg, G1_SWREG(1));
diff --git a/drivers/staging/media/hantro/hantro_g1_vp8_dec.c b/drivers/staging/media/hantro/hantro_g1_vp8_dec.c
new file mode 100644
index 000000000000..6d99c2be01cf
--- /dev/null
+++ b/drivers/staging/media/hantro/hantro_g1_vp8_dec.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hantro VP8 codec driver
+ *
+ * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
+ * ZhiChao Yu <zhichao.yu@rock-chips.com>
+ *
+ * Copyright (C) 2019 Google, Inc.
+ * Tomasz Figa <tfiga@chromium.org>
+ */
+
+#include <media/v4l2-mem2mem.h>
+#include <media/vp8-ctrls.h>
+
+#include "hantro_hw.h"
+#include "hantro.h"
+#include "hantro_g1_regs.h"
+
+/* DCT partition base address regs */
+static const struct hantro_reg vp8_dec_dct_base[8] = {
+ { G1_REG_ADDR_STR, 0, 0xffffffff },
+ { G1_REG_ADDR_REF(8), 0, 0xffffffff },
+ { G1_REG_ADDR_REF(9), 0, 0xffffffff },
+ { G1_REG_ADDR_REF(10), 0, 0xffffffff },
+ { G1_REG_ADDR_REF(11), 0, 0xffffffff },
+ { G1_REG_ADDR_REF(12), 0, 0xffffffff },
+ { G1_REG_ADDR_REF(14), 0, 0xffffffff },
+ { G1_REG_ADDR_REF(15), 0, 0xffffffff },
+};
+
+/* Loop filter level regs */
+static const struct hantro_reg vp8_dec_lf_level[4] = {
+ { G1_REG_REF_PIC(2), 18, 0x3f },
+ { G1_REG_REF_PIC(2), 12, 0x3f },
+ { G1_REG_REF_PIC(2), 6, 0x3f },
+ { G1_REG_REF_PIC(2), 0, 0x3f },
+};
+
+/* Macroblock loop filter level adjustment regs */
+static const struct hantro_reg vp8_dec_mb_adj[4] = {
+ { G1_REG_REF_PIC(0), 21, 0x7f },
+ { G1_REG_REF_PIC(0), 14, 0x7f },
+ { G1_REG_REF_PIC(0), 7, 0x7f },
+ { G1_REG_REF_PIC(0), 0, 0x7f },
+};
+
+/* Reference frame adjustment regs */
+static const struct hantro_reg vp8_dec_ref_adj[4] = {
+ { G1_REG_REF_PIC(1), 21, 0x7f },
+ { G1_REG_REF_PIC(1), 14, 0x7f },
+ { G1_REG_REF_PIC(1), 7, 0x7f },
+ { G1_REG_REF_PIC(1), 0, 0x7f },
+};
+
+/* Quantizer */
+static const struct hantro_reg vp8_dec_quant[4] = {
+ { G1_REG_REF_PIC(3), 11, 0x7ff },
+ { G1_REG_REF_PIC(3), 0, 0x7ff },
+ { G1_REG_BD_REF_PIC(4), 11, 0x7ff },
+ { G1_REG_BD_REF_PIC(4), 0, 0x7ff },
+};
+
+/* Quantizer delta regs */
+static const struct hantro_reg vp8_dec_quant_delta[5] = {
+ { G1_REG_REF_PIC(3), 27, 0x1f },
+ { G1_REG_REF_PIC(3), 22, 0x1f },
+ { G1_REG_BD_REF_PIC(4), 27, 0x1f },
+ { G1_REG_BD_REF_PIC(4), 22, 0x1f },
+ { G1_REG_BD_P_REF_PIC, 27, 0x1f },
+};
+
+/* DCT partition start bits regs */
+static const struct hantro_reg vp8_dec_dct_start_bits[8] = {
+ { G1_REG_DEC_CTRL2, 26, 0x3f }, { G1_REG_DEC_CTRL4, 26, 0x3f },
+ { G1_REG_DEC_CTRL4, 20, 0x3f }, { G1_REG_DEC_CTRL7, 24, 0x3f },
+ { G1_REG_DEC_CTRL7, 18, 0x3f }, { G1_REG_DEC_CTRL7, 12, 0x3f },
+ { G1_REG_DEC_CTRL7, 6, 0x3f }, { G1_REG_DEC_CTRL7, 0, 0x3f },
+};
+
+/* Precision filter tap regs */
+static const struct hantro_reg vp8_dec_pred_bc_tap[8][4] = {
+ {
+ { G1_REG_PRED_FLT, 22, 0x3ff },
+ { G1_REG_PRED_FLT, 12, 0x3ff },
+ { G1_REG_PRED_FLT, 2, 0x3ff },
+ { G1_REG_REF_PIC(4), 22, 0x3ff },
+ },
+ {
+ { G1_REG_REF_PIC(4), 12, 0x3ff },
+ { G1_REG_REF_PIC(4), 2, 0x3ff },
+ { G1_REG_REF_PIC(5), 22, 0x3ff },
+ { G1_REG_REF_PIC(5), 12, 0x3ff },
+ },
+ {
+ { G1_REG_REF_PIC(5), 2, 0x3ff },
+ { G1_REG_REF_PIC(6), 22, 0x3ff },
+ { G1_REG_REF_PIC(6), 12, 0x3ff },
+ { G1_REG_REF_PIC(6), 2, 0x3ff },
+ },
+ {
+ { G1_REG_REF_PIC(7), 22, 0x3ff },
+ { G1_REG_REF_PIC(7), 12, 0x3ff },
+ { G1_REG_REF_PIC(7), 2, 0x3ff },
+ { G1_REG_LT_REF, 22, 0x3ff },
+ },
+ {
+ { G1_REG_LT_REF, 12, 0x3ff },
+ { G1_REG_LT_REF, 2, 0x3ff },
+ { G1_REG_VALID_REF, 22, 0x3ff },
+ { G1_REG_VALID_REF, 12, 0x3ff },
+ },
+ {
+ { G1_REG_VALID_REF, 2, 0x3ff },
+ { G1_REG_BD_REF_PIC(0), 22, 0x3ff },
+ { G1_REG_BD_REF_PIC(0), 12, 0x3ff },
+ { G1_REG_BD_REF_PIC(0), 2, 0x3ff },
+ },
+ {
+ { G1_REG_BD_REF_PIC(1), 22, 0x3ff },
+ { G1_REG_BD_REF_PIC(1), 12, 0x3ff },
+ { G1_REG_BD_REF_PIC(1), 2, 0x3ff },
+ { G1_REG_BD_REF_PIC(2), 22, 0x3ff },
+ },
+ {
+ { G1_REG_BD_REF_PIC(2), 12, 0x3ff },
+ { G1_REG_BD_REF_PIC(2), 2, 0x3ff },
+ { G1_REG_BD_REF_PIC(3), 22, 0x3ff },
+ { G1_REG_BD_REF_PIC(3), 12, 0x3ff },
+ },
+};
+
+/*
+ * Set loop filters
+ */
+static void cfg_lf(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ const struct v4l2_vp8_segment_header *seg = &hdr->segment_header;
+ const struct v4l2_vp8_loopfilter_header *lf = &hdr->lf_header;
+ struct hantro_dev *vpu = ctx->dev;
+ unsigned int i;
+ u32 reg;
+
+ if (!(seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_ENABLED)) {
+ hantro_reg_write(vpu, &vp8_dec_lf_level[0], lf->level);
+ } else if (seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_DELTA_VALUE_MODE) {
+ for (i = 0; i < 4; i++) {
+ u32 lf_level = clamp(lf->level + seg->lf_update[i],
+ 0, 63);
+
+ hantro_reg_write(vpu, &vp8_dec_lf_level[i], lf_level);
+ }
+ } else {
+ for (i = 0; i < 4; i++)
+ hantro_reg_write(vpu, &vp8_dec_lf_level[i],
+ seg->lf_update[i]);
+ }
+
+ reg = G1_REG_REF_PIC_FILT_SHARPNESS(lf->sharpness_level);
+ if (lf->flags & V4L2_VP8_LF_FILTER_TYPE_SIMPLE)
+ reg |= G1_REG_REF_PIC_FILT_TYPE_E;
+ vdpu_write_relaxed(vpu, reg, G1_REG_REF_PIC(0));
+
+ if (lf->flags & V4L2_VP8_LF_HEADER_ADJ_ENABLE) {
+ for (i = 0; i < 4; i++) {
+ hantro_reg_write(vpu, &vp8_dec_mb_adj[i],
+ lf->mb_mode_delta[i]);
+ hantro_reg_write(vpu, &vp8_dec_ref_adj[i],
+ lf->ref_frm_delta[i]);
+ }
+ }
+}
+
+/*
+ * Set quantization parameters
+ */
+static void cfg_qp(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ const struct v4l2_vp8_quantization_header *q = &hdr->quant_header;
+ const struct v4l2_vp8_segment_header *seg = &hdr->segment_header;
+ struct hantro_dev *vpu = ctx->dev;
+ unsigned int i;
+
+ if (!(seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_ENABLED)) {
+ hantro_reg_write(vpu, &vp8_dec_quant[0], q->y_ac_qi);
+ } else if (seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_DELTA_VALUE_MODE) {
+ for (i = 0; i < 4; i++) {
+ u32 quant = clamp(q->y_ac_qi + seg->quant_update[i],
+ 0, 127);
+
+ hantro_reg_write(vpu, &vp8_dec_quant[i], quant);
+ }
+ } else {
+ for (i = 0; i < 4; i++)
+ hantro_reg_write(vpu, &vp8_dec_quant[i],
+ seg->quant_update[i]);
+ }
+
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[0], q->y_dc_delta);
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[1], q->y2_dc_delta);
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[2], q->y2_ac_delta);
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[3], q->uv_dc_delta);
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[4], q->uv_ac_delta);
+}
+
+/*
+ * set control partition and DCT partition regs
+ *
+ * VP8 frame stream data layout:
+ *
+ * first_part_size parttion_sizes[0]
+ * ^ ^
+ * src_dma | |
+ * ^ +--------+------+ +-----+-----+
+ * | | control part | | |
+ * +--------+----------------+------------------+-----------+-----+-----------+
+ * | tag 3B | extra 7B | hdr | mb_data | DCT sz | DCT part0 | ... | DCT partn |
+ * +--------+-----------------------------------+-----------+-----+-----------+
+ * | | | |
+ * v +----+---+ v
+ * mb_start | src_dma_end
+ * v
+ * DCT size part
+ * (num_dct-1)*3B
+ * Note:
+ * 1. only key-frames have extra 7-bytes
+ * 2. all offsets are base on src_dma
+ * 3. number of DCT parts is 1, 2, 4 or 8
+ * 4. the addresses set to the VPU must be 64-bits aligned
+ */
+static void cfg_parts(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct vb2_v4l2_buffer *vb2_src;
+ u32 first_part_offset = VP8_FRAME_IS_KEY_FRAME(hdr) ? 10 : 3;
+ u32 mb_size, mb_offset_bytes, mb_offset_bits, mb_start_bits;
+ u32 dct_size_part_size, dct_part_offset;
+ struct hantro_reg reg;
+ dma_addr_t src_dma;
+ u32 dct_part_total_len = 0;
+ u32 count = 0;
+ unsigned int i;
+
+ vb2_src = hantro_get_src_buf(ctx);
+ src_dma = vb2_dma_contig_plane_dma_addr(&vb2_src->vb2_buf, 0);
+
+ /*
+ * Calculate control partition mb data info
+ * @first_part_header_bits: bits offset of mb data from first
+ * part start pos
+ * @mb_offset_bits: bits offset of mb data from src_dma
+ * base addr
+ * @mb_offset_byte: bytes offset of mb data from src_dma
+ * base addr
+ * @mb_start_bits: bits offset of mb data from mb data
+ * 64bits alignment addr
+ */
+ mb_offset_bits = first_part_offset * 8 +
+ hdr->first_part_header_bits + 8;
+ mb_offset_bytes = mb_offset_bits / 8;
+ mb_start_bits = mb_offset_bits -
+ (mb_offset_bytes & (~DEC_8190_ALIGN_MASK)) * 8;
+ mb_size = hdr->first_part_size -
+ (mb_offset_bytes - first_part_offset) +
+ (mb_offset_bytes & DEC_8190_ALIGN_MASK);
+
+ /* Macroblock data aligned base addr */
+ vdpu_write_relaxed(vpu, (mb_offset_bytes & (~DEC_8190_ALIGN_MASK))
+ + src_dma, G1_REG_ADDR_REF(13));
+
+ /* Macroblock data start bits */
+ reg.base = G1_REG_DEC_CTRL2;
+ reg.mask = 0x3f;
+ reg.shift = 18;
+ hantro_reg_write(vpu, &reg, mb_start_bits);
+
+ /* Macroblock aligned data length */
+ reg.base = G1_REG_DEC_CTRL6;
+ reg.mask = 0x3fffff;
+ reg.shift = 0;
+ hantro_reg_write(vpu, &reg, mb_size + 1);
+
+ /*
+ * Calculate DCT partition info
+ * @dct_size_part_size: Containing sizes of DCT part, every DCT part
+ * has 3 bytes to store its size, except the last
+ * DCT part
+ * @dct_part_offset: bytes offset of DCT parts from src_dma base addr
+ * @dct_part_total_len: total size of all DCT parts
+ */
+ dct_size_part_size = (hdr->num_dct_parts - 1) * 3;
+ dct_part_offset = first_part_offset + hdr->first_part_size;
+ for (i = 0; i < hdr->num_dct_parts; i++)
+ dct_part_total_len += hdr->dct_part_sizes[i];
+ dct_part_total_len += dct_size_part_size;
+ dct_part_total_len += (dct_part_offset & DEC_8190_ALIGN_MASK);
+
+ /* Number of DCT partitions */
+ reg.base = G1_REG_DEC_CTRL6;
+ reg.mask = 0xf;
+ reg.shift = 24;
+ hantro_reg_write(vpu, &reg, hdr->num_dct_parts - 1);
+
+ /* DCT partition length */
+ vdpu_write_relaxed(vpu,
+ G1_REG_DEC_CTRL3_STREAM_LEN(dct_part_total_len),
+ G1_REG_DEC_CTRL3);
+
+ /* DCT partitions base address */
+ for (i = 0; i < hdr->num_dct_parts; i++) {
+ u32 byte_offset = dct_part_offset + dct_size_part_size + count;
+ u32 base_addr = byte_offset + src_dma;
+
+ hantro_reg_write(vpu, &vp8_dec_dct_base[i],
+ base_addr & (~DEC_8190_ALIGN_MASK));
+
+ hantro_reg_write(vpu, &vp8_dec_dct_start_bits[i],
+ (byte_offset & DEC_8190_ALIGN_MASK) * 8);
+
+ count += hdr->dct_part_sizes[i];
+ }
+}
+
+/*
+ * prediction filter taps
+ * normal 6-tap filters
+ */
+static void cfg_tap(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_reg reg;
+ u32 val = 0;
+ int i, j;
+
+ reg.base = G1_REG_BD_REF_PIC(3);
+ reg.mask = 0xf;
+
+ if ((hdr->version & 0x03) != 0)
+ return; /* Tap filter not used. */
+
+ for (i = 0; i < 8; i++) {
+ val = (hantro_vp8_dec_mc_filter[i][0] << 2) |
+ hantro_vp8_dec_mc_filter[i][5];
+
+ for (j = 0; j < 4; j++)
+ hantro_reg_write(vpu, &vp8_dec_pred_bc_tap[i][j],
+ hantro_vp8_dec_mc_filter[i][j + 1]);
+
+ switch (i) {
+ case 2:
+ reg.shift = 8;
+ break;
+ case 4:
+ reg.shift = 4;
+ break;
+ case 6:
+ reg.shift = 0;
+ break;
+ default:
+ continue;
+ }
+
+ hantro_reg_write(vpu, &reg, val);
+ }
+}
+
+static void cfg_ref(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ struct vb2_queue *cap_q = &ctx->fh.m2m_ctx->cap_q_ctx.q;
+ struct hantro_dev *vpu = ctx->dev;
+ struct vb2_v4l2_buffer *vb2_dst;
+ dma_addr_t ref;
+
+ vb2_dst = hantro_get_dst_buf(ctx);
+
+ ref = hantro_get_ref(cap_q, hdr->last_frame_ts);
+ if (!ref)
+ ref = vb2_dma_contig_plane_dma_addr(&vb2_dst->vb2_buf, 0);
+ vdpu_write_relaxed(vpu, ref, G1_REG_ADDR_REF(0));
+
+ ref = hantro_get_ref(cap_q, hdr->golden_frame_ts);
+ WARN_ON(!ref && hdr->golden_frame_ts);
+ if (!ref)
+ ref = vb2_dma_contig_plane_dma_addr(&vb2_dst->vb2_buf, 0);
+ if (hdr->flags & V4L2_VP8_FRAME_HEADER_FLAG_SIGN_BIAS_GOLDEN)
+ ref |= G1_REG_ADDR_REF_TOPC_E;
+ vdpu_write_relaxed(vpu, ref, G1_REG_ADDR_REF(4));
+
+ ref = hantro_get_ref(cap_q, hdr->alt_frame_ts);
+ WARN_ON(!ref && hdr->alt_frame_ts);
+ if (!ref)
+ ref = vb2_dma_contig_plane_dma_addr(&vb2_dst->vb2_buf, 0);
+ if (hdr->flags & V4L2_VP8_FRAME_HEADER_FLAG_SIGN_BIAS_ALT)
+ ref |= G1_REG_ADDR_REF_TOPC_E;
+ vdpu_write_relaxed(vpu, ref, G1_REG_ADDR_REF(5));
+}
+
+static void cfg_buffers(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ const struct v4l2_vp8_segment_header *seg = &hdr->segment_header;
+ struct hantro_dev *vpu = ctx->dev;
+ struct vb2_v4l2_buffer *vb2_dst;
+ dma_addr_t dst_dma;
+ u32 reg;
+
+ vb2_dst = hantro_get_dst_buf(ctx);
+
+ /* Set probability table buffer address */
+ vdpu_write_relaxed(vpu, ctx->vp8_dec.prob_tbl.dma,
+ G1_REG_ADDR_QTABLE);
+
+ /* Set segment map address */
+ reg = G1_REG_FWD_PIC1_SEGMENT_BASE(ctx->vp8_dec.segment_map.dma);
+ if (seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_ENABLED) {
+ reg |= G1_REG_FWD_PIC1_SEGMENT_E;
+ if (seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_UPDATE_MAP)
+ reg |= G1_REG_FWD_PIC1_SEGMENT_UPD_E;
+ }
+ vdpu_write_relaxed(vpu, reg, G1_REG_FWD_PIC(0));
+
+ dst_dma = vb2_dma_contig_plane_dma_addr(&vb2_dst->vb2_buf, 0);
+ vdpu_write_relaxed(vpu, dst_dma, G1_REG_ADDR_DST);
+}
+
+void hantro_g1_vp8_dec_run(struct hantro_ctx *ctx)
+{
+ const struct v4l2_ctrl_vp8_frame_header *hdr;
+ struct hantro_dev *vpu = ctx->dev;
+ size_t height = ctx->dst_fmt.height;
+ size_t width = ctx->dst_fmt.width;
+ u32 mb_width, mb_height;
+ u32 reg;
+
+ hantro_prepare_run(ctx);
+
+ hdr = hantro_get_ctrl(ctx, V4L2_CID_MPEG_VIDEO_VP8_FRAME_HEADER);
+ if (WARN_ON(!hdr))
+ return;
+
+ /* Reset segment_map buffer in keyframe */
+ if (VP8_FRAME_IS_KEY_FRAME(hdr) && ctx->vp8_dec.segment_map.cpu)
+ memset(ctx->vp8_dec.segment_map.cpu, 0,
+ ctx->vp8_dec.segment_map.size);
+
+ hantro_vp8_prob_update(ctx, hdr);
+
+ reg = G1_REG_CONFIG_DEC_TIMEOUT_E |
+ G1_REG_CONFIG_DEC_STRENDIAN_E |
+ G1_REG_CONFIG_DEC_INSWAP32_E |
+ G1_REG_CONFIG_DEC_STRSWAP32_E |
+ G1_REG_CONFIG_DEC_OUTSWAP32_E |
+ G1_REG_CONFIG_DEC_CLK_GATE_E |
+ G1_REG_CONFIG_DEC_IN_ENDIAN |
+ G1_REG_CONFIG_DEC_OUT_ENDIAN |
+ G1_REG_CONFIG_DEC_MAX_BURST(16);
+ vdpu_write_relaxed(vpu, reg, G1_REG_CONFIG);
+
+ reg = G1_REG_DEC_CTRL0_DEC_MODE(10);
+ if (!VP8_FRAME_IS_KEY_FRAME(hdr))
+ reg |= G1_REG_DEC_CTRL0_PIC_INTER_E;
+ if (!(hdr->flags & V4L2_VP8_FRAME_HEADER_FLAG_MB_NO_SKIP_COEFF))
+ reg |= G1_REG_DEC_CTRL0_SKIP_MODE;
+ if (hdr->lf_header.level == 0)
+ reg |= G1_REG_DEC_CTRL0_FILTERING_DIS;
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL0);
+
+ /* Frame dimensions */
+ mb_width = VP8_MB_WIDTH(width);
+ mb_height = VP8_MB_HEIGHT(height);
+ reg = G1_REG_DEC_CTRL1_PIC_MB_WIDTH(mb_width) |
+ G1_REG_DEC_CTRL1_PIC_MB_HEIGHT_P(mb_height) |
+ G1_REG_DEC_CTRL1_PIC_MB_W_EXT(mb_width >> 9) |
+ G1_REG_DEC_CTRL1_PIC_MB_H_EXT(mb_height >> 8);
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL1);
+
+ /* Boolean decoder */
+ reg = G1_REG_DEC_CTRL2_BOOLEAN_RANGE(hdr->coder_state.range)
+ | G1_REG_DEC_CTRL2_BOOLEAN_VALUE(hdr->coder_state.value);
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL2);
+
+ reg = 0;
+ if (hdr->version != 3)
+ reg |= G1_REG_DEC_CTRL4_VC1_HEIGHT_EXT;
+ if (hdr->version & 0x3)
+ reg |= G1_REG_DEC_CTRL4_BILIN_MC_E;
+ vdpu_write_relaxed(vpu, reg, G1_REG_DEC_CTRL4);
+
+ cfg_lf(ctx, hdr);
+ cfg_qp(ctx, hdr);
+ cfg_parts(ctx, hdr);
+ cfg_tap(ctx, hdr);
+ cfg_ref(ctx, hdr);
+ cfg_buffers(ctx, hdr);
+
+ hantro_finish_run(ctx);
+
+ vdpu_write(vpu, G1_REG_INTERRUPT_DEC_E, G1_REG_INTERRUPT);
+}
diff --git a/drivers/staging/media/hantro/hantro_h1_jpeg_enc.c b/drivers/staging/media/hantro/hantro_h1_jpeg_enc.c
index 0c1e3043dc7e..ecd34a7db190 100644
--- a/drivers/staging/media/hantro/hantro_h1_jpeg_enc.c
+++ b/drivers/staging/media/hantro/hantro_h1_jpeg_enc.c
@@ -84,8 +84,10 @@ void hantro_h1_jpeg_enc_run(struct hantro_ctx *ctx)
struct hantro_jpeg_ctx jpeg_ctx;
u32 reg;
- src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
- dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ src_buf = hantro_get_src_buf(ctx);
+ dst_buf = hantro_get_dst_buf(ctx);
+
+ hantro_prepare_run(ctx);
memset(&jpeg_ctx, 0, sizeof(jpeg_ctx));
jpeg_ctx.buffer = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
@@ -119,7 +121,8 @@ void hantro_h1_jpeg_enc_run(struct hantro_ctx *ctx)
| H1_REG_ENC_CTRL_ENC_MODE_JPEG
| H1_REG_ENC_PIC_INTRA
| H1_REG_ENC_CTRL_EN_BIT;
- /* Kick the watchdog and start encoding */
- schedule_delayed_work(&vpu->watchdog_work, msecs_to_jiffies(2000));
+
+ hantro_finish_run(ctx);
+
vepu_write(vpu, reg, H1_REG_ENC_CTRL);
}
diff --git a/drivers/staging/media/hantro/hantro_h264.c b/drivers/staging/media/hantro/hantro_h264.c
new file mode 100644
index 000000000000..0d758e0c0f99
--- /dev/null
+++ b/drivers/staging/media/hantro/hantro_h264.c
@@ -0,0 +1,646 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip RK3288 VPU codec driver
+ *
+ * Copyright (c) 2014 Rockchip Electronics Co., Ltd.
+ * Hertz Wong <hertz.wong@rock-chips.com>
+ * Herman Chen <herman.chen@rock-chips.com>
+ *
+ * Copyright (C) 2014 Google, Inc.
+ * Tomasz Figa <tfiga@chromium.org>
+ */
+
+#include <linux/types.h>
+#include <linux/sort.h>
+#include <media/v4l2-mem2mem.h>
+
+#include "hantro.h"
+#include "hantro_hw.h"
+
+/* Size with u32 units. */
+#define CABAC_INIT_BUFFER_SIZE (460 * 2)
+#define POC_BUFFER_SIZE 34
+#define SCALING_LIST_SIZE (6 * 16 + 6 * 64)
+
+#define POC_CMP(p0, p1) ((p0) < (p1) ? -1 : 1)
+
+/* Data structure describing auxiliary buffer format. */
+struct hantro_h264_dec_priv_tbl {
+ u32 cabac_table[CABAC_INIT_BUFFER_SIZE];
+ u32 poc[POC_BUFFER_SIZE];
+ u8 scaling_list[SCALING_LIST_SIZE];
+};
+
+/*
+ * Constant CABAC table.
+ * From drivers/media/platform/rk3288-vpu/rk3288_vpu_hw_h264d.c
+ * in https://chromium.googlesource.com/chromiumos/third_party/kernel,
+ * chromeos-3.14 branch.
+ */
+static const u32 h264_cabac_table[] = {
+ 0x14f10236, 0x034a14f1, 0x0236034a, 0xe47fe968, 0xfa35ff36, 0x07330000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x0029003f, 0x003f003f, 0xf7530456, 0x0061f948, 0x0d29033e, 0x000b0137,
+ 0x0045ef7f, 0xf3660052, 0xf94aeb6b, 0xe57fe17f, 0xe87fee5f, 0xe57feb72,
+ 0xe27fef7b, 0xf473f07a, 0xf573f43f, 0xfe44f154, 0xf368fd46, 0xf85df65a,
+ 0xe27fff4a, 0xfa61f95b, 0xec7ffc38, 0xfb52f94c, 0xea7df95d, 0xf557fd4d,
+ 0xfb47fc3f, 0xfc44f454, 0xf93ef941, 0x083d0538, 0xfe420140, 0x003dfe4e,
+ 0x01320734, 0x0a23002c, 0x0b26012d, 0x002e052c, 0x1f110133, 0x07321c13,
+ 0x10210e3e, 0xf36cf164, 0xf365f35b, 0xf45ef658, 0xf054f656, 0xf953f357,
+ 0xed5e0146, 0x0048fb4a, 0x123bf866, 0xf164005f, 0xfc4b0248, 0xf54bfd47,
+ 0x0f2ef345, 0x003e0041, 0x1525f148, 0x09391036, 0x003e0c48, 0x18000f09,
+ 0x08190d12, 0x0f090d13, 0x0a250c12, 0x061d1421, 0x0f1e042d, 0x013a003e,
+ 0x073d0c26, 0x0b2d0f27, 0x0b2a0d2c, 0x102d0c29, 0x0a311e22, 0x122a0a37,
+ 0x1133112e, 0x00591aed, 0x16ef1aef, 0x1ee71cec, 0x21e925e5, 0x21e928e4,
+ 0x26ef21f5, 0x28f129fa, 0x26012911, 0x1efa1b03, 0x1a1625f0, 0x23fc26f8,
+ 0x26fd2503, 0x26052a00, 0x23102716, 0x0e301b25, 0x153c0c44, 0x0261fd47,
+ 0xfa2afb32, 0xfd36fe3e, 0x003a013f, 0xfe48ff4a, 0xf75bfb43, 0xfb1bfd27,
+ 0xfe2c002e, 0xf040f844, 0xf64efa4d, 0xf656f45c, 0xf137f63c, 0xfa3efc41,
+ 0xf449f84c, 0xf950f758, 0xef6ef561, 0xec54f54f, 0xfa49fc4a, 0xf356f360,
+ 0xf561ed75, 0xf84efb21, 0xfc30fe35, 0xfd3ef347, 0xf64ff456, 0xf35af261,
+ 0x0000fa5d, 0xfa54f84f, 0x0042ff47, 0x003efe3c, 0xfe3bfb4b, 0xfd3efc3a,
+ 0xf742ff4f, 0x00470344, 0x0a2cf93e, 0x0f240e28, 0x101b0c1d, 0x012c1424,
+ 0x1220052a, 0x01300a3e, 0x112e0940, 0xf468f561, 0xf060f958, 0xf855f955,
+ 0xf755f358, 0x0442fd4d, 0xfd4cfa4c, 0x0a3aff4c, 0xff53f963, 0xf25f025f,
+ 0x004cfb4a, 0x0046f54b, 0x01440041, 0xf249033e, 0x043eff44, 0xf34b0b37,
+ 0x05400c46, 0x0f060613, 0x07100c0e, 0x120d0d0b, 0x0d0f0f10, 0x0c170d17,
+ 0x0f140e1a, 0x0e2c1128, 0x112f1811, 0x15151916, 0x1f1b161d, 0x13230e32,
+ 0x0a39073f, 0xfe4dfc52, 0xfd5e0945, 0xf46d24dd, 0x24de20e6, 0x25e22ce0,
+ 0x22ee22f1, 0x28f121f9, 0x23fb2100, 0x2602210d, 0x17230d3a, 0x1dfd1a00,
+ 0x161e1ff9, 0x23f122fd, 0x220324ff, 0x2205200b, 0x2305220c, 0x270b1e1d,
+ 0x221a1d27, 0x13421f15, 0x1f1f1932, 0xef78ec70, 0xee72f555, 0xf15cf259,
+ 0xe647f151, 0xf2500044, 0xf246e838, 0xe944e832, 0xf54a17f3, 0x1af328f1,
+ 0x31f22c03, 0x2d062c22, 0x21361352, 0xfd4bff17, 0x0122012b, 0x0036fe37,
+ 0x003d0140, 0x0044f75c, 0xf26af361, 0xf15af45a, 0xee58f649, 0xf74ff256,
+ 0xf649f646, 0xf645fb42, 0xf740fb3a, 0x023b15f6, 0x18f51cf8, 0x1cff1d03,
+ 0x1d092314, 0x1d240e43, 0x14f10236, 0x034a14f1, 0x0236034a, 0xe47fe968,
+ 0xfa35ff36, 0x07331721, 0x17021500, 0x01090031, 0xdb760539, 0xf34ef541,
+ 0x013e0c31, 0xfc491132, 0x1240092b, 0x1d001a43, 0x105a0968, 0xd27fec68,
+ 0x0143f34e, 0xf541013e, 0xfa56ef5f, 0xfa3d092d, 0xfd45fa51, 0xf5600637,
+ 0x0743fb56, 0x0258003a, 0xfd4cf65e, 0x05360445, 0xfd510058, 0xf943fb4a,
+ 0xfc4afb50, 0xf948013a, 0x0029003f, 0x003f003f, 0xf7530456, 0x0061f948,
+ 0x0d29033e, 0x002dfc4e, 0xfd60e57e, 0xe462e765, 0xe943e452, 0xec5ef053,
+ 0xea6eeb5b, 0xee66f35d, 0xe37ff95c, 0xfb59f960, 0xf36cfd2e, 0xff41ff39,
+ 0xf75dfd4a, 0xf75cf857, 0xe97e0536, 0x063c063b, 0x0645ff30, 0x0044fc45,
+ 0xf858fe55, 0xfa4eff4b, 0xf94d0236, 0x0532fd44, 0x0132062a, 0xfc51013f,
+ 0xfc460043, 0x0239fe4c, 0x0b230440, 0x013d0b23, 0x12190c18, 0x0d1d0d24,
+ 0xf65df949, 0xfe490d2e, 0x0931f964, 0x09350235, 0x0535fe3d, 0x00380038,
+ 0xf33ffb3c, 0xff3e0439, 0xfa450439, 0x0e270433, 0x0d440340, 0x013d093f,
+ 0x07321027, 0x052c0434, 0x0b30fb3c, 0xff3b003b, 0x1621052c, 0x0e2bff4e,
+ 0x003c0945, 0x0b1c0228, 0x032c0031, 0x002e022c, 0x0233002f, 0x0427023e,
+ 0x062e0036, 0x0336023a, 0x043f0633, 0x06390735, 0x06340637, 0x0b2d0e24,
+ 0x0835ff52, 0x0737fd4e, 0x0f2e161f, 0xff541907, 0x1ef91c03, 0x1c042000,
+ 0x22ff1e06, 0x1e062009, 0x1f131a1b, 0x1a1e2514, 0x1c221146, 0x0143053b,
+ 0x0943101e, 0x12201223, 0x161d181f, 0x1726122b, 0x14290b3f, 0x093b0940,
+ 0xff5efe59, 0xf76cfa4c, 0xfe2c002d, 0x0034fd40, 0xfe3bfc46, 0xfc4bf852,
+ 0xef66f74d, 0x0318002a, 0x00300037, 0xfa3bf947, 0xf453f557, 0xe277013a,
+ 0xfd1dff24, 0x0126022b, 0xfa37003a, 0x0040fd4a, 0xf65a0046, 0xfc1d051f,
+ 0x072a013b, 0xfe3afd48, 0xfd51f561, 0x003a0805, 0x0a0e0e12, 0x0d1b0228,
+ 0x003afd46, 0xfa4ff855, 0x0000f36a, 0xf06af657, 0xeb72ee6e, 0xf262ea6e,
+ 0xeb6aee67, 0xeb6be96c, 0xe670f660, 0xf45ffb5b, 0xf75dea5e, 0xfb560943,
+ 0xfc50f655, 0xff46073c, 0x093a053d, 0x0c320f32, 0x12311136, 0x0a29072e,
+ 0xff330731, 0x08340929, 0x062f0237, 0x0d290a2c, 0x06320535, 0x0d31043f,
+ 0x0640fe45, 0xfe3b0646, 0x0a2c091f, 0x0c2b0335, 0x0e220a26, 0xfd340d28,
+ 0x1120072c, 0x07260d32, 0x0a391a2b, 0x0e0b0b0e, 0x090b120b, 0x150917fe,
+ 0x20f120f1, 0x22eb27e9, 0x2adf29e1, 0x2ee426f4, 0x151d2de8, 0x35d330e6,
+ 0x41d52bed, 0x27f61e09, 0x121a141b, 0x0039f252, 0xfb4bed61, 0xdd7d1b00,
+ 0x1c001ffc, 0x1b062208, 0x1e0a1816, 0x21131620, 0x1a1f1529, 0x1a2c172f,
+ 0x10410e47, 0x083c063f, 0x11411518, 0x17141a17, 0x1b201c17, 0x1c181728,
+ 0x18201c1d, 0x172a1339, 0x1635163d, 0x0b560c28, 0x0b330e3b, 0xfc4ff947,
+ 0xfb45f746, 0xf842f644, 0xed49f445, 0xf046f143, 0xec3eed46, 0xf042ea41,
+ 0xec3f09fe, 0x1af721f7, 0x27f929fe, 0x2d033109, 0x2d1b243b, 0xfa42f923,
+ 0xf92af82d, 0xfb30f438, 0xfa3cfb3e, 0xf842f84c, 0xfb55fa51, 0xf64df951,
+ 0xef50ee49, 0xfc4af653, 0xf747f743, 0xff3df842, 0xf242003b, 0x023b15f3,
+ 0x21f227f9, 0x2efe3302, 0x3c063d11, 0x37222a3e, 0x14f10236, 0x034a14f1,
+ 0x0236034a, 0xe47fe968, 0xfa35ff36, 0x07331619, 0x22001000, 0xfe090429,
+ 0xe3760241, 0xfa47f34f, 0x05340932, 0xfd460a36, 0x1a221316, 0x28003902,
+ 0x29241a45, 0xd37ff165, 0xfc4cfa47, 0xf34f0534, 0x0645f35a, 0x0034082b,
+ 0xfe45fb52, 0xf660023b, 0x024bfd57, 0xfd640138, 0xfd4afa55, 0x003bfd51,
+ 0xf956fb5f, 0xff42ff4d, 0x0146fe56, 0xfb48003d, 0x0029003f, 0x003f003f,
+ 0xf7530456, 0x0061f948, 0x0d29033e, 0x0d0f0733, 0x0250d97f, 0xee5bef60,
+ 0xe651dd62, 0xe866e961, 0xe577e863, 0xeb6eee66, 0xdc7f0050, 0xfb59f95e,
+ 0xfc5c0027, 0x0041f154, 0xdd7ffe49, 0xf468f75b, 0xe17f0337, 0x07380737,
+ 0x083dfd35, 0x0044f94a, 0xf758f367, 0xf35bf759, 0xf25cf84c, 0xf457e96e,
+ 0xe869f64e, 0xec70ef63, 0xb27fba7f, 0xce7fd27f, 0xfc42fb4e, 0xfc47f848,
+ 0x023bff37, 0xf946fa4b, 0xf859de77, 0xfd4b2014, 0x1e16d47f, 0x0036fb3d,
+ 0x003aff3c, 0xfd3df843, 0xe754f24a, 0xfb410534, 0x0239003d, 0xf745f546,
+ 0x1237fc47, 0x003a073d, 0x09291219, 0x0920052b, 0x092f002c, 0x0033022e,
+ 0x1326fc42, 0x0f260c2a, 0x09220059, 0x042d0a1c, 0x0a1f21f5, 0x34d5120f,
+ 0x1c0023ea, 0x26e72200, 0x27ee20f4, 0x66a20000, 0x38f121fc, 0x1d0a25fb,
+ 0x33e327f7, 0x34de45c6, 0x43c12cfb, 0x200737e3, 0x20010000, 0x1b2421e7,
+ 0x22e224e4, 0x26e426e5, 0x22ee23f0, 0x22f220f8, 0x25fa2300, 0x1e0a1c12,
+ 0x1a191d29, 0x004b0248, 0x084d0e23, 0x121f1123, 0x151e112d, 0x142a122d,
+ 0x1b1a1036, 0x07421038, 0x0b490a43, 0xf674e970, 0xf147f93d, 0x0035fb42,
+ 0xf54df750, 0xf754f657, 0xde7feb65, 0xfd27fb35, 0xf93df54b, 0xf14def5b,
+ 0xe76be76f, 0xe47af54c, 0xf62cf634, 0xf639f73a, 0xf048f945, 0xfc45fb4a,
+ 0xf7560242, 0xf7220120, 0x0b1f0534, 0xfe37fe43, 0x0049f859, 0x03340704,
+ 0x0a081108, 0x10130325, 0xff3dfb49, 0xff46fc4e, 0x0000eb7e, 0xe97cec6e,
+ 0xe67ee77c, 0xef69e579, 0xe575ef66, 0xe675e574, 0xdf7af65f, 0xf264f85f,
+ 0xef6fe472, 0xfa59fe50, 0xfc52f755, 0xf851ff48, 0x05400143, 0x09380045,
+ 0x01450745, 0xf945fa43, 0xf04dfe40, 0x023dfa43, 0xfd400239, 0xfd41fd42,
+ 0x003e0933, 0xff42fe47, 0xfe4bff46, 0xf7480e3c, 0x1025002f, 0x12230b25,
+ 0x0c290a29, 0x02300c29, 0x0d29003b, 0x03321328, 0x03421232, 0x13fa12fa,
+ 0x0e001af4, 0x1ff021e7, 0x21ea25e4, 0x27e22ae2, 0x2fd62ddc, 0x31de29ef,
+ 0x200945b9, 0x3fc142c0, 0x4db636d9, 0x34dd29f6, 0x240028ff, 0x1e0e1c1a,
+ 0x17250c37, 0x0b4125df, 0x27dc28db, 0x26e22edf, 0x2ae228e8, 0x31e326f4,
+ 0x28f626fd, 0x2efb1f14, 0x1d1e192c, 0x0c300b31, 0x1a2d1616, 0x17161b15,
+ 0x21141a1c, 0x1e181b22, 0x122a1927, 0x12320c46, 0x15360e47, 0x0b531920,
+ 0x15311536, 0xfb55fa51, 0xf64df951, 0xef50ee49, 0xfc4af653, 0xf747f743,
+ 0xff3df842, 0xf242003b, 0x023b11f6, 0x20f32af7, 0x31fb3500, 0x4003440a,
+ 0x421b2f39, 0xfb470018, 0xff24fe2a, 0xfe34f739, 0xfa3ffc41, 0xfc43f952,
+ 0xfd51fd4c, 0xf948fa4e, 0xf448f244, 0xfd46fa4c, 0xfb42fb3e, 0x0039fc3d,
+ 0xf73c0136, 0x023a11f6, 0x20f32af7, 0x31fb3500, 0x4003440a, 0x421b2f39,
+ 0x14f10236, 0x034a14f1, 0x0236034a, 0xe47fe968, 0xfa35ff36, 0x07331d10,
+ 0x19000e00, 0xf633fd3e, 0xe5631a10, 0xfc55e866, 0x05390639, 0xef490e39,
+ 0x1428140a, 0x1d003600, 0x252a0c61, 0xe07fea75, 0xfe4afc55, 0xe8660539,
+ 0xfa5df258, 0xfa2c0437, 0xf559f167, 0xeb741339, 0x143a0454, 0x0660013f,
+ 0xfb55f36a, 0x053f064b, 0xfd5aff65, 0x0337fc4f, 0xfe4bf461, 0xf932013c,
+ 0x0029003f, 0x003f003f, 0xf7530456, 0x0061f948, 0x0d29033e, 0x0722f758,
+ 0xec7fdc7f, 0xef5bf25f, 0xe754e756, 0xf459ef5b, 0xe17ff24c, 0xee67f35a,
+ 0xdb7f0b50, 0x054c0254, 0x054efa37, 0x043df253, 0xdb7ffb4f, 0xf568f55b,
+ 0xe27f0041, 0xfe4f0048, 0xfc5cfa38, 0x0344f847, 0xf362fc56, 0xf458fb52,
+ 0xfd48fc43, 0xf848f059, 0xf745ff3b, 0x05420439, 0xfc47fe47, 0x023aff4a,
+ 0xfc2cff45, 0x003ef933, 0xfc2ffa2a, 0xfd29fa35, 0x084cf74e, 0xf5530934,
+ 0x0043fb5a, 0x0143f148, 0xfb4bf850, 0xeb53eb40, 0xf31fe740, 0xe35e094b,
+ 0x113ff84a, 0xfb23fe1b, 0x0d5b0341, 0xf945084d, 0xf642033e, 0xfd44ec51,
+ 0x001e0107, 0xfd17eb4a, 0x1042e97c, 0x11252cee, 0x32deea7f, 0x0427002a,
+ 0x07220b1d, 0x081f0625, 0x072a0328, 0x08210d2b, 0x0d24042f, 0x0337023a,
+ 0x063c082c, 0x0b2c0e2a, 0x07300438, 0x04340d25, 0x0931133a, 0x0a300c2d,
+ 0x00451421, 0x083f23ee, 0x21e71cfd, 0x180a1b00, 0x22f234d4, 0x27e81311,
+ 0x1f19241d, 0x1821220f, 0x1e141649, 0x1422131f, 0x1b2c1310, 0x0f240f24,
+ 0x151c1915, 0x1e141f0c, 0x1b10182a, 0x005d0e38, 0x0f391a26, 0xe87fe873,
+ 0xea52f73e, 0x0035003b, 0xf255f359, 0xf35ef55c, 0xe37feb64, 0xf239f443,
+ 0xf547f64d, 0xeb55f058, 0xe968f162, 0xdb7ff652, 0xf830f83d, 0xf842f946,
+ 0xf24bf64f, 0xf753f45c, 0xee6cfc4f, 0xea45f04b, 0xfe3a013a, 0xf34ef753,
+ 0xfc51f363, 0xf351fa26, 0xf33efa3a, 0xfe3bf049, 0xf64cf356, 0xf753f657,
+ 0x0000ea7f, 0xe77fe778, 0xe57fed72, 0xe975e776, 0xe675e871, 0xe476e178,
+ 0xdb7cf65e, 0xf166f663, 0xf36ace7f, 0xfb5c1139, 0xfb56f35e, 0xf45bfe4d,
+ 0x0047ff49, 0x0440f951, 0x05400f39, 0x01430044, 0xf6430144, 0x004d0240,
+ 0x0044fb4e, 0x0737053b, 0x02410e36, 0x0f2c053c, 0x0246fe4c, 0xee560c46,
+ 0x0540f446, 0x0b370538, 0x00450241, 0xfa4a0536, 0x0736fa4c, 0xf552fe4d,
+ 0xfe4d192a, 0x11f310f7, 0x11f41beb, 0x25e229d8, 0x2ad730d1, 0x27e02ed8,
+ 0x34cd2ed7, 0x34d92bed, 0x200b3dc9, 0x38d23ece, 0x51bd2dec, 0x23fe1c0f,
+ 0x22012701, 0x1e111426, 0x122d0f36, 0x004f24f0, 0x25f225ef, 0x2001220f,
+ 0x1d0f1819, 0x22161f10, 0x23121f1c, 0x2129241c, 0x1b2f153e, 0x121f131a,
+ 0x24181817, 0x1b10181e, 0x1f1d1629, 0x162a103c, 0x0f340e3c, 0x034ef07b,
+ 0x15351638, 0x193d1521, 0x1332113d, 0xfd4ef84a, 0xf748f648, 0xee4bf447,
+ 0xf53ffb46, 0xef4bf248, 0xf043f835, 0xf23bf734, 0xf54409fe, 0x1ef61ffc,
+ 0x21ff2107, 0x1f0c2517, 0x1f261440, 0xf747f925, 0xf82cf531, 0xf638f43b,
+ 0xf83ff743, 0xfa44f64f, 0xfd4ef84a, 0xf748f648, 0xee4bf447, 0xf53ffb46,
+ 0xef4bf248, 0xf043f835, 0xf23bf734, 0xf54409fe, 0x1ef61ffc, 0x21ff2107,
+ 0x1f0c2517, 0x1f261440
+};
+
+/*
+ * NOTE: The scaling lists are in zig-zag order, apply inverse scanning process
+ * to get the values in matrix order. In addition, the hardware requires bytes
+ * swapped within each subsequent 4 bytes. Both arrays below include both
+ * transformations.
+ */
+static const u32 zig_zag_4x4[] = {
+ 3, 2, 7, 11, 6, 1, 0, 5, 10, 15, 14, 9, 4, 8, 13, 12
+};
+
+static const u32 zig_zag_8x8[] = {
+ 3, 2, 11, 19, 10, 1, 0, 9, 18, 27, 35, 26, 17, 8, 7, 6,
+ 15, 16, 25, 34, 43, 51, 42, 33, 24, 23, 14, 5, 4, 13, 22, 31,
+ 32, 41, 50, 59, 58, 49, 40, 39, 30, 21, 12, 20, 29, 38, 47, 48,
+ 57, 56, 55, 46, 37, 28, 36, 45, 54, 63, 62, 53, 44, 52, 61, 60
+};
+
+static void
+reorder_scaling_list(struct hantro_ctx *ctx)
+{
+ const struct hantro_h264_dec_ctrls *ctrls = &ctx->h264_dec.ctrls;
+ const struct v4l2_ctrl_h264_scaling_matrix *scaling = ctrls->scaling;
+ const size_t num_list_4x4 = ARRAY_SIZE(scaling->scaling_list_4x4);
+ const size_t list_len_4x4 = ARRAY_SIZE(scaling->scaling_list_4x4[0]);
+ const size_t num_list_8x8 = ARRAY_SIZE(scaling->scaling_list_8x8);
+ const size_t list_len_8x8 = ARRAY_SIZE(scaling->scaling_list_8x8[0]);
+ struct hantro_h264_dec_priv_tbl *tbl = ctx->h264_dec.priv.cpu;
+ u8 *dst = tbl->scaling_list;
+ const u8 *src;
+ int i, j;
+
+ BUILD_BUG_ON(ARRAY_SIZE(zig_zag_4x4) != list_len_4x4);
+ BUILD_BUG_ON(ARRAY_SIZE(zig_zag_8x8) != list_len_8x8);
+ BUILD_BUG_ON(ARRAY_SIZE(tbl->scaling_list) !=
+ num_list_4x4 * list_len_4x4 +
+ num_list_8x8 * list_len_8x8);
+
+ src = &scaling->scaling_list_4x4[0][0];
+ for (i = 0; i < num_list_4x4; ++i) {
+ for (j = 0; j < list_len_4x4; ++j)
+ dst[zig_zag_4x4[j]] = src[j];
+ src += list_len_4x4;
+ dst += list_len_4x4;
+ }
+
+ src = &scaling->scaling_list_8x8[0][0];
+ for (i = 0; i < num_list_8x8; ++i) {
+ for (j = 0; j < list_len_8x8; ++j)
+ dst[zig_zag_8x8[j]] = src[j];
+ src += list_len_8x8;
+ dst += list_len_8x8;
+ }
+}
+
+static void prepare_table(struct hantro_ctx *ctx)
+{
+ const struct hantro_h264_dec_ctrls *ctrls = &ctx->h264_dec.ctrls;
+ const struct v4l2_ctrl_h264_decode_params *dec_param = ctrls->decode;
+ struct hantro_h264_dec_priv_tbl *tbl = ctx->h264_dec.priv.cpu;
+ const struct v4l2_h264_dpb_entry *dpb = ctx->h264_dec.dpb;
+ int i;
+
+ for (i = 0; i < HANTRO_H264_DPB_SIZE; ++i) {
+ tbl->poc[i * 2] = dpb[i].top_field_order_cnt;
+ tbl->poc[i * 2 + 1] = dpb[i].bottom_field_order_cnt;
+ }
+
+ tbl->poc[32] = dec_param->top_field_order_cnt;
+ tbl->poc[33] = dec_param->bottom_field_order_cnt;
+
+ reorder_scaling_list(ctx);
+}
+
+struct hantro_h264_reflist_builder {
+ const struct v4l2_h264_dpb_entry *dpb;
+ s32 pocs[HANTRO_H264_DPB_SIZE];
+ u8 unordered_reflist[HANTRO_H264_DPB_SIZE];
+ s32 curpoc;
+ u8 num_valid;
+};
+
+static s32 get_poc(enum v4l2_field field, s32 top_field_order_cnt,
+ s32 bottom_field_order_cnt)
+{
+ switch (field) {
+ case V4L2_FIELD_TOP:
+ return top_field_order_cnt;
+ case V4L2_FIELD_BOTTOM:
+ return bottom_field_order_cnt;
+ default:
+ break;
+ }
+
+ return min(top_field_order_cnt, bottom_field_order_cnt);
+}
+
+static void
+init_reflist_builder(struct hantro_ctx *ctx,
+ struct hantro_h264_reflist_builder *b)
+{
+ const struct v4l2_ctrl_h264_decode_params *dec_param;
+ struct vb2_v4l2_buffer *buf = hantro_get_dst_buf(ctx);
+ const struct v4l2_h264_dpb_entry *dpb = ctx->h264_dec.dpb;
+ struct vb2_queue *cap_q = &ctx->fh.m2m_ctx->cap_q_ctx.q;
+ unsigned int i;
+
+ dec_param = ctx->h264_dec.ctrls.decode;
+
+ memset(b, 0, sizeof(*b));
+ b->dpb = dpb;
+ b->curpoc = get_poc(buf->field, dec_param->top_field_order_cnt,
+ dec_param->bottom_field_order_cnt);
+
+ for (i = 0; i < ARRAY_SIZE(ctx->h264_dec.dpb); i++) {
+ int buf_idx;
+
+ if (!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE))
+ continue;
+
+ buf_idx = vb2_find_timestamp(cap_q, dpb[i].reference_ts, 0);
+ if (buf_idx < 0)
+ continue;
+
+ buf = to_vb2_v4l2_buffer(vb2_get_buffer(cap_q, buf_idx));
+ b->pocs[i] = get_poc(buf->field, dpb[i].top_field_order_cnt,
+ dpb[i].bottom_field_order_cnt);
+ b->unordered_reflist[b->num_valid] = i;
+ b->num_valid++;
+ }
+
+ for (i = b->num_valid; i < ARRAY_SIZE(ctx->h264_dec.dpb); i++)
+ b->unordered_reflist[i] = i;
+}
+
+static int p_ref_list_cmp(const void *ptra, const void *ptrb, const void *data)
+{
+ const struct hantro_h264_reflist_builder *builder = data;
+ const struct v4l2_h264_dpb_entry *a, *b;
+ u8 idxa, idxb;
+
+ idxa = *((u8 *)ptra);
+ idxb = *((u8 *)ptrb);
+ a = &builder->dpb[idxa];
+ b = &builder->dpb[idxb];
+
+ if ((a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) !=
+ (b->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)) {
+ /* Short term pics firt. */
+ if (!(a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM))
+ return -1;
+ else
+ return 1;
+ }
+
+ /*
+ * Short term pics in descending pic num order, long term ones in
+ * ascending order.
+ */
+ if (!(a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM))
+ return b->frame_num - a->frame_num;
+
+ return a->pic_num - b->pic_num;
+}
+
+static int b0_ref_list_cmp(const void *ptra, const void *ptrb, const void *data)
+{
+ const struct hantro_h264_reflist_builder *builder = data;
+ const struct v4l2_h264_dpb_entry *a, *b;
+ s32 poca, pocb;
+ u8 idxa, idxb;
+
+ idxa = *((u8 *)ptra);
+ idxb = *((u8 *)ptrb);
+ a = &builder->dpb[idxa];
+ b = &builder->dpb[idxb];
+
+ if ((a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) !=
+ (b->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)) {
+ /* Short term pics firt. */
+ if (!(a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM))
+ return -1;
+ else
+ return 1;
+ }
+
+ /* Long term pics in ascending pic num order. */
+ if (a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)
+ return a->pic_num - b->pic_num;
+
+ poca = builder->pocs[idxa];
+ pocb = builder->pocs[idxb];
+
+ /*
+ * Short term pics with POC < cur POC first in POC descending order
+ * followed by short term pics with POC > cur POC in POC ascending
+ * order.
+ */
+ if ((poca < builder->curpoc) != (pocb < builder->curpoc))
+ return POC_CMP(poca, pocb);
+ else if (poca < builder->curpoc)
+ return POC_CMP(pocb, poca);
+
+ return POC_CMP(poca, pocb);
+}
+
+static int b1_ref_list_cmp(const void *ptra, const void *ptrb, const void *data)
+{
+ const struct hantro_h264_reflist_builder *builder = data;
+ const struct v4l2_h264_dpb_entry *a, *b;
+ s32 poca, pocb;
+ u8 idxa, idxb;
+
+ idxa = *((u8 *)ptra);
+ idxb = *((u8 *)ptrb);
+ a = &builder->dpb[idxa];
+ b = &builder->dpb[idxb];
+
+ if ((a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) !=
+ (b->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)) {
+ /* Short term pics firt. */
+ if (!(a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM))
+ return -1;
+ else
+ return 1;
+ }
+
+ /* Long term pics in ascending pic num order. */
+ if (a->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)
+ return a->pic_num - b->pic_num;
+
+ poca = builder->pocs[idxa];
+ pocb = builder->pocs[idxb];
+
+ /*
+ * Short term pics with POC > cur POC first in POC ascending order
+ * followed by short term pics with POC > cur POC in POC descending
+ * order.
+ */
+ if ((poca < builder->curpoc) != (pocb < builder->curpoc))
+ return POC_CMP(pocb, poca);
+ else if (poca < builder->curpoc)
+ return POC_CMP(pocb, poca);
+
+ return POC_CMP(poca, pocb);
+}
+
+static void
+build_p_ref_list(const struct hantro_h264_reflist_builder *builder,
+ u8 *reflist)
+{
+ memcpy(reflist, builder->unordered_reflist,
+ sizeof(builder->unordered_reflist));
+ sort_r(reflist, builder->num_valid, sizeof(*reflist),
+ p_ref_list_cmp, NULL, builder);
+}
+
+static void
+build_b_ref_lists(const struct hantro_h264_reflist_builder *builder,
+ u8 *b0_reflist, u8 *b1_reflist)
+{
+ memcpy(b0_reflist, builder->unordered_reflist,
+ sizeof(builder->unordered_reflist));
+ sort_r(b0_reflist, builder->num_valid, sizeof(*b0_reflist),
+ b0_ref_list_cmp, NULL, builder);
+
+ memcpy(b1_reflist, builder->unordered_reflist,
+ sizeof(builder->unordered_reflist));
+ sort_r(b1_reflist, builder->num_valid, sizeof(*b1_reflist),
+ b1_ref_list_cmp, NULL, builder);
+
+ if (builder->num_valid > 1 &&
+ !memcmp(b1_reflist, b0_reflist, builder->num_valid))
+ swap(b1_reflist[0], b1_reflist[1]);
+}
+
+static bool dpb_entry_match(const struct v4l2_h264_dpb_entry *a,
+ const struct v4l2_h264_dpb_entry *b)
+{
+ return a->top_field_order_cnt == b->top_field_order_cnt &&
+ a->bottom_field_order_cnt == b->bottom_field_order_cnt;
+}
+
+static void update_dpb(struct hantro_ctx *ctx)
+{
+ const struct v4l2_ctrl_h264_decode_params *dec_param;
+ DECLARE_BITMAP(new, ARRAY_SIZE(dec_param->dpb)) = { 0, };
+ DECLARE_BITMAP(used, ARRAY_SIZE(dec_param->dpb)) = { 0, };
+ unsigned int i, j;
+
+ dec_param = ctx->h264_dec.ctrls.decode;
+
+ /* Disable all entries by default. */
+ for (i = 0; i < ARRAY_SIZE(ctx->h264_dec.dpb); i++)
+ ctx->h264_dec.dpb[i].flags &= ~V4L2_H264_DPB_ENTRY_FLAG_ACTIVE;
+
+ /* Try to match new DPB entries with existing ones by their POCs. */
+ for (i = 0; i < ARRAY_SIZE(dec_param->dpb); i++) {
+ const struct v4l2_h264_dpb_entry *ndpb = &dec_param->dpb[i];
+
+ if (!(ndpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE))
+ continue;
+
+ /*
+ * To cut off some comparisons, iterate only on target DPB
+ * entries which are not used yet.
+ */
+ for_each_clear_bit(j, used, ARRAY_SIZE(ctx->h264_dec.dpb)) {
+ struct v4l2_h264_dpb_entry *cdpb;
+
+ cdpb = &ctx->h264_dec.dpb[j];
+ if (cdpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE ||
+ !dpb_entry_match(cdpb, ndpb))
+ continue;
+
+ *cdpb = *ndpb;
+ set_bit(j, used);
+ break;
+ }
+
+ if (j == ARRAY_SIZE(ctx->h264_dec.dpb))
+ set_bit(i, new);
+ }
+
+ /* For entries that could not be matched, use remaining free slots. */
+ for_each_set_bit(i, new, ARRAY_SIZE(dec_param->dpb)) {
+ const struct v4l2_h264_dpb_entry *ndpb = &dec_param->dpb[i];
+ struct v4l2_h264_dpb_entry *cdpb;
+
+ /*
+ * Both arrays are of the same sizes, so there is no way
+ * we can end up with no space in target array, unless
+ * something is buggy.
+ */
+ j = find_first_zero_bit(used, ARRAY_SIZE(ctx->h264_dec.dpb));
+ if (WARN_ON(j >= ARRAY_SIZE(ctx->h264_dec.dpb)))
+ return;
+
+ cdpb = &ctx->h264_dec.dpb[j];
+ *cdpb = *ndpb;
+ set_bit(j, used);
+ }
+}
+
+struct vb2_buffer *hantro_h264_get_ref_buf(struct hantro_ctx *ctx,
+ unsigned int dpb_idx)
+{
+ struct vb2_queue *cap_q = &ctx->fh.m2m_ctx->cap_q_ctx.q;
+ struct v4l2_h264_dpb_entry *dpb = ctx->h264_dec.dpb;
+ struct vb2_buffer *buf;
+ int buf_idx = -1;
+
+ if (dpb[dpb_idx].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)
+ buf_idx = vb2_find_timestamp(cap_q,
+ dpb[dpb_idx].reference_ts, 0);
+
+ if (buf_idx >= 0) {
+ buf = vb2_get_buffer(cap_q, buf_idx);
+ } else {
+ struct vb2_v4l2_buffer *dst_buf;
+
+ /*
+ * If a DPB entry is unused or invalid, address of current
+ * destination buffer is returned.
+ */
+ dst_buf = hantro_get_dst_buf(ctx);
+ buf = &dst_buf->vb2_buf;
+ }
+
+ return buf;
+}
+
+int hantro_h264_dec_prepare_run(struct hantro_ctx *ctx)
+{
+ struct hantro_h264_dec_hw_ctx *h264_ctx = &ctx->h264_dec;
+ struct hantro_h264_dec_ctrls *ctrls = &h264_ctx->ctrls;
+ struct hantro_h264_reflist_builder reflist_builder;
+
+ hantro_prepare_run(ctx);
+
+ ctrls->scaling =
+ hantro_get_ctrl(ctx, V4L2_CID_MPEG_VIDEO_H264_SCALING_MATRIX);
+ if (WARN_ON(!ctrls->scaling))
+ return -EINVAL;
+
+ ctrls->decode =
+ hantro_get_ctrl(ctx, V4L2_CID_MPEG_VIDEO_H264_DECODE_PARAMS);
+ if (WARN_ON(!ctrls->decode))
+ return -EINVAL;
+
+ ctrls->slices =
+ hantro_get_ctrl(ctx, V4L2_CID_MPEG_VIDEO_H264_SLICE_PARAMS);
+ if (WARN_ON(!ctrls->slices))
+ return -EINVAL;
+
+ ctrls->sps =
+ hantro_get_ctrl(ctx, V4L2_CID_MPEG_VIDEO_H264_SPS);
+ if (WARN_ON(!ctrls->sps))
+ return -EINVAL;
+
+ ctrls->pps =
+ hantro_get_ctrl(ctx, V4L2_CID_MPEG_VIDEO_H264_PPS);
+ if (WARN_ON(!ctrls->pps))
+ return -EINVAL;
+
+ /* Update the DPB with new refs. */
+ update_dpb(ctx);
+
+ /* Prepare data in memory. */
+ prepare_table(ctx);
+
+ /* Build the P/B{0,1} ref lists. */
+ init_reflist_builder(ctx, &reflist_builder);
+ build_p_ref_list(&reflist_builder, h264_ctx->reflists.p);
+ build_b_ref_lists(&reflist_builder, h264_ctx->reflists.b0,
+ h264_ctx->reflists.b1);
+ return 0;
+}
+
+void hantro_h264_dec_exit(struct hantro_ctx *ctx)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_h264_dec_hw_ctx *h264_dec = &ctx->h264_dec;
+ struct hantro_aux_buf *priv = &h264_dec->priv;
+
+ dma_free_coherent(vpu->dev, priv->size, priv->cpu, priv->dma);
+}
+
+int hantro_h264_dec_init(struct hantro_ctx *ctx)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_h264_dec_hw_ctx *h264_dec = &ctx->h264_dec;
+ struct hantro_aux_buf *priv = &h264_dec->priv;
+ struct hantro_h264_dec_priv_tbl *tbl;
+ struct v4l2_pix_format_mplane pix_mp;
+
+ priv->cpu = dma_alloc_coherent(vpu->dev, sizeof(*tbl), &priv->dma,
+ GFP_KERNEL);
+ if (!priv->cpu)
+ return -ENOMEM;
+
+ priv->size = sizeof(*tbl);
+ tbl = priv->cpu;
+ memcpy(tbl->cabac_table, h264_cabac_table, sizeof(tbl->cabac_table));
+
+ v4l2_fill_pixfmt_mp(&pix_mp, ctx->dst_fmt.pixelformat,
+ ctx->dst_fmt.width, ctx->dst_fmt.height);
+ h264_dec->pic_size = pix_mp.plane_fmt[0].sizeimage;
+
+ return 0;
+}
diff --git a/drivers/staging/media/hantro/hantro_hw.h b/drivers/staging/media/hantro/hantro_hw.h
index 3c361c2e9b88..2fab655bf098 100644
--- a/drivers/staging/media/hantro/hantro_hw.h
+++ b/drivers/staging/media/hantro/hantro_hw.h
@@ -11,9 +11,13 @@
#include <linux/interrupt.h>
#include <linux/v4l2-controls.h>
+#include <media/h264-ctrls.h>
#include <media/mpeg2-ctrls.h>
+#include <media/vp8-ctrls.h>
#include <media/videobuf2-core.h>
+#define DEC_8190_ALIGN_MASK 0x07U
+
struct hantro_dev;
struct hantro_ctx;
struct hantro_buf;
@@ -39,6 +43,54 @@ struct hantro_jpeg_enc_hw_ctx {
struct hantro_aux_buf bounce_buffer;
};
+/* Max. number of entries in the DPB (HW limitation). */
+#define HANTRO_H264_DPB_SIZE 16
+
+/**
+ * struct hantro_h264_dec_ctrls
+ * @decode: Decode params
+ * @scaling: Scaling info
+ * @slice: Slice params
+ * @sps: SPS info
+ * @pps: PPS info
+ */
+struct hantro_h264_dec_ctrls {
+ const struct v4l2_ctrl_h264_decode_params *decode;
+ const struct v4l2_ctrl_h264_scaling_matrix *scaling;
+ const struct v4l2_ctrl_h264_slice_params *slices;
+ const struct v4l2_ctrl_h264_sps *sps;
+ const struct v4l2_ctrl_h264_pps *pps;
+};
+
+/**
+ * struct hantro_h264_dec_reflists
+ * @p: P reflist
+ * @b0: B0 reflist
+ * @b1: B1 reflist
+ */
+struct hantro_h264_dec_reflists {
+ u8 p[HANTRO_H264_DPB_SIZE];
+ u8 b0[HANTRO_H264_DPB_SIZE];
+ u8 b1[HANTRO_H264_DPB_SIZE];
+};
+
+/**
+ * struct hantro_h264_dec_hw_ctx
+ * @priv: Private auxiliary buffer for hardware.
+ * @dpb: DPB
+ * @reflists: P/B0/B1 reflists
+ * @ctrls: V4L2 controls attached to a run
+ * @pic_size: Size in bytes of decoded picture, this is needed
+ * to pass the location of motion vectors.
+ */
+struct hantro_h264_dec_hw_ctx {
+ struct hantro_aux_buf priv;
+ struct v4l2_h264_dpb_entry dpb[HANTRO_H264_DPB_SIZE];
+ struct hantro_h264_dec_reflists reflists;
+ struct hantro_h264_dec_ctrls ctrls;
+ size_t pic_size;
+};
+
/**
* struct hantro_mpeg2_dec_hw_ctx
* @qtable: Quantization table
@@ -48,6 +100,16 @@ struct hantro_mpeg2_dec_hw_ctx {
};
/**
+ * struct hantro_vp8d_hw_ctx
+ * @segment_map: Segment map buffer.
+ * @prob_tbl: Probability table buffer.
+ */
+struct hantro_vp8_dec_hw_ctx {
+ struct hantro_aux_buf segment_map;
+ struct hantro_aux_buf prob_tbl;
+};
+
+/**
* struct hantro_codec_ops - codec mode specific operations
*
* @init: If needed, can be used for initialization.
@@ -82,16 +144,27 @@ extern const struct hantro_variant rk3399_vpu_variant;
extern const struct hantro_variant rk3328_vpu_variant;
extern const struct hantro_variant rk3288_vpu_variant;
+extern const u32 hantro_vp8_dec_mc_filter[8][6];
+
void hantro_watchdog(struct work_struct *work);
void hantro_run(struct hantro_ctx *ctx);
void hantro_irq_done(struct hantro_dev *vpu, unsigned int bytesused,
enum vb2_buffer_state result);
+void hantro_prepare_run(struct hantro_ctx *ctx);
+void hantro_finish_run(struct hantro_ctx *ctx);
void hantro_h1_jpeg_enc_run(struct hantro_ctx *ctx);
void rk3399_vpu_jpeg_enc_run(struct hantro_ctx *ctx);
int hantro_jpeg_enc_init(struct hantro_ctx *ctx);
void hantro_jpeg_enc_exit(struct hantro_ctx *ctx);
+struct vb2_buffer *hantro_h264_get_ref_buf(struct hantro_ctx *ctx,
+ unsigned int dpb_idx);
+int hantro_h264_dec_prepare_run(struct hantro_ctx *ctx);
+void hantro_g1_h264_dec_run(struct hantro_ctx *ctx);
+int hantro_h264_dec_init(struct hantro_ctx *ctx);
+void hantro_h264_dec_exit(struct hantro_ctx *ctx);
+
void hantro_g1_mpeg2_dec_run(struct hantro_ctx *ctx);
void rk3399_vpu_mpeg2_dec_run(struct hantro_ctx *ctx);
void hantro_mpeg2_dec_copy_qtable(u8 *qtable,
@@ -99,4 +172,11 @@ void hantro_mpeg2_dec_copy_qtable(u8 *qtable,
int hantro_mpeg2_dec_init(struct hantro_ctx *ctx);
void hantro_mpeg2_dec_exit(struct hantro_ctx *ctx);
+void hantro_g1_vp8_dec_run(struct hantro_ctx *ctx);
+void rk3399_vpu_vp8_dec_run(struct hantro_ctx *ctx);
+int hantro_vp8_dec_init(struct hantro_ctx *ctx);
+void hantro_vp8_dec_exit(struct hantro_ctx *ctx);
+void hantro_vp8_prob_update(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr);
+
#endif /* HANTRO_HW_H_ */
diff --git a/drivers/staging/media/hantro/hantro_v4l2.c b/drivers/staging/media/hantro/hantro_v4l2.c
index 68f45ee66821..3dae52abb96c 100644
--- a/drivers/staging/media/hantro/hantro_v4l2.c
+++ b/drivers/staging/media/hantro/hantro_v4l2.c
@@ -239,6 +239,15 @@ static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f,
/* Fill remaining fields */
v4l2_fill_pixfmt_mp(pix_mp, fmt->fourcc, pix_mp->width,
pix_mp->height);
+ /*
+ * The H264 decoder needs extra space on the output buffers
+ * to store motion vectors. This is needed for reference
+ * frames.
+ */
+ if (ctx->vpu_src_fmt->fourcc == V4L2_PIX_FMT_H264_SLICE)
+ pix_mp->plane_fmt[0].sizeimage +=
+ 128 * DIV_ROUND_UP(pix_mp->width, 16) *
+ DIV_ROUND_UP(pix_mp->height, 16);
} else if (!pix_mp->plane_fmt[0].sizeimage) {
/*
* For coded formats the application can specify
@@ -344,6 +353,8 @@ hantro_update_requires_request(struct hantro_ctx *ctx, u32 fourcc)
ctx->fh.m2m_ctx->out_q_ctx.q.requires_requests = false;
break;
case V4L2_PIX_FMT_MPEG2_SLICE:
+ case V4L2_PIX_FMT_VP8_FRAME:
+ case V4L2_PIX_FMT_H264_SLICE:
ctx->fh.m2m_ctx->out_q_ctx.q.requires_requests = true;
break;
default:
diff --git a/drivers/staging/media/hantro/hantro_vp8.c b/drivers/staging/media/hantro/hantro_vp8.c
new file mode 100644
index 000000000000..0e02d147b189
--- /dev/null
+++ b/drivers/staging/media/hantro/hantro_vp8.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hantro VPU codec driver
+ *
+ * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
+ */
+
+#include "hantro.h"
+
+/*
+ * probs table with packed
+ */
+struct vp8_prob_tbl_packed {
+ u8 prob_mb_skip_false;
+ u8 prob_intra;
+ u8 prob_ref_last;
+ u8 prob_ref_golden;
+ u8 prob_segment[3];
+ u8 padding0;
+
+ u8 prob_luma_16x16_pred_mode[4];
+ u8 prob_chroma_pred_mode[3];
+ u8 padding1;
+
+ /* mv prob */
+ u8 prob_mv_context[2][19];
+ u8 padding2[2];
+
+ /* coeff probs */
+ u8 prob_coeffs[4][8][3][11];
+ u8 padding3[96];
+};
+
+/*
+ * filter taps taken to 7-bit precision,
+ * reference RFC6386#Page-16, filters[8][6]
+ */
+const u32 hantro_vp8_dec_mc_filter[8][6] = {
+ { 0, 0, 128, 0, 0, 0 },
+ { 0, -6, 123, 12, -1, 0 },
+ { 2, -11, 108, 36, -8, 1 },
+ { 0, -9, 93, 50, -6, 0 },
+ { 3, -16, 77, 77, -16, 3 },
+ { 0, -6, 50, 93, -9, 0 },
+ { 1, -8, 36, 108, -11, 2 },
+ { 0, -1, 12, 123, -6, 0 }
+};
+
+void hantro_vp8_prob_update(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ const struct v4l2_vp8_entropy_header *entropy = &hdr->entropy_header;
+ u32 i, j, k;
+ u8 *dst;
+
+ /* first probs */
+ dst = ctx->vp8_dec.prob_tbl.cpu;
+
+ dst[0] = hdr->prob_skip_false;
+ dst[1] = hdr->prob_intra;
+ dst[2] = hdr->prob_last;
+ dst[3] = hdr->prob_gf;
+ dst[4] = hdr->segment_header.segment_probs[0];
+ dst[5] = hdr->segment_header.segment_probs[1];
+ dst[6] = hdr->segment_header.segment_probs[2];
+ dst[7] = 0;
+
+ dst += 8;
+ dst[0] = entropy->y_mode_probs[0];
+ dst[1] = entropy->y_mode_probs[1];
+ dst[2] = entropy->y_mode_probs[2];
+ dst[3] = entropy->y_mode_probs[3];
+ dst[4] = entropy->uv_mode_probs[0];
+ dst[5] = entropy->uv_mode_probs[1];
+ dst[6] = entropy->uv_mode_probs[2];
+ dst[7] = 0; /*unused */
+
+ /* mv probs */
+ dst += 8;
+ dst[0] = entropy->mv_probs[0][0]; /* is short */
+ dst[1] = entropy->mv_probs[1][0];
+ dst[2] = entropy->mv_probs[0][1]; /* sign */
+ dst[3] = entropy->mv_probs[1][1];
+ dst[4] = entropy->mv_probs[0][8 + 9];
+ dst[5] = entropy->mv_probs[0][9 + 9];
+ dst[6] = entropy->mv_probs[1][8 + 9];
+ dst[7] = entropy->mv_probs[1][9 + 9];
+ dst += 8;
+ for (i = 0; i < 2; ++i) {
+ for (j = 0; j < 8; j += 4) {
+ dst[0] = entropy->mv_probs[i][j + 9 + 0];
+ dst[1] = entropy->mv_probs[i][j + 9 + 1];
+ dst[2] = entropy->mv_probs[i][j + 9 + 2];
+ dst[3] = entropy->mv_probs[i][j + 9 + 3];
+ dst += 4;
+ }
+ }
+ for (i = 0; i < 2; ++i) {
+ dst[0] = entropy->mv_probs[i][0 + 2];
+ dst[1] = entropy->mv_probs[i][1 + 2];
+ dst[2] = entropy->mv_probs[i][2 + 2];
+ dst[3] = entropy->mv_probs[i][3 + 2];
+ dst[4] = entropy->mv_probs[i][4 + 2];
+ dst[5] = entropy->mv_probs[i][5 + 2];
+ dst[6] = entropy->mv_probs[i][6 + 2];
+ dst[7] = 0; /*unused */
+ dst += 8;
+ }
+
+ /* coeff probs (header part) */
+ dst = ctx->vp8_dec.prob_tbl.cpu;
+ dst += (8 * 7);
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 8; ++j) {
+ for (k = 0; k < 3; ++k) {
+ dst[0] = entropy->coeff_probs[i][j][k][0];
+ dst[1] = entropy->coeff_probs[i][j][k][1];
+ dst[2] = entropy->coeff_probs[i][j][k][2];
+ dst[3] = entropy->coeff_probs[i][j][k][3];
+ dst += 4;
+ }
+ }
+ }
+
+ /* coeff probs (footer part) */
+ dst = ctx->vp8_dec.prob_tbl.cpu;
+ dst += (8 * 55);
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 8; ++j) {
+ for (k = 0; k < 3; ++k) {
+ dst[0] = entropy->coeff_probs[i][j][k][4];
+ dst[1] = entropy->coeff_probs[i][j][k][5];
+ dst[2] = entropy->coeff_probs[i][j][k][6];
+ dst[3] = entropy->coeff_probs[i][j][k][7];
+ dst[4] = entropy->coeff_probs[i][j][k][8];
+ dst[5] = entropy->coeff_probs[i][j][k][9];
+ dst[6] = entropy->coeff_probs[i][j][k][10];
+ dst[7] = 0; /*unused */
+ dst += 8;
+ }
+ }
+ }
+}
+
+int hantro_vp8_dec_init(struct hantro_ctx *ctx)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct hantro_aux_buf *aux_buf;
+ unsigned int mb_width, mb_height;
+ size_t segment_map_size;
+ int ret;
+
+ /* segment map table size calculation */
+ mb_width = DIV_ROUND_UP(ctx->dst_fmt.width, 16);
+ mb_height = DIV_ROUND_UP(ctx->dst_fmt.height, 16);
+ segment_map_size = round_up(DIV_ROUND_UP(mb_width * mb_height, 4), 64);
+
+ /*
+ * In context init the dma buffer for segment map must be allocated.
+ * And the data in segment map buffer must be set to all zero.
+ */
+ aux_buf = &ctx->vp8_dec.segment_map;
+ aux_buf->size = segment_map_size;
+ aux_buf->cpu = dma_alloc_coherent(vpu->dev, aux_buf->size,
+ &aux_buf->dma, GFP_KERNEL);
+ if (!aux_buf->cpu)
+ return -ENOMEM;
+
+ /*
+ * Allocate probability table buffer,
+ * total 1208 bytes, 4K page is far enough.
+ */
+ aux_buf = &ctx->vp8_dec.prob_tbl;
+ aux_buf->size = sizeof(struct vp8_prob_tbl_packed);
+ aux_buf->cpu = dma_alloc_coherent(vpu->dev, aux_buf->size,
+ &aux_buf->dma, GFP_KERNEL);
+ if (!aux_buf->cpu) {
+ ret = -ENOMEM;
+ goto err_free_seg_map;
+ }
+
+ return 0;
+
+err_free_seg_map:
+ dma_free_coherent(vpu->dev, ctx->vp8_dec.segment_map.size,
+ ctx->vp8_dec.segment_map.cpu,
+ ctx->vp8_dec.segment_map.dma);
+
+ return ret;
+}
+
+void hantro_vp8_dec_exit(struct hantro_ctx *ctx)
+{
+ struct hantro_vp8_dec_hw_ctx *vp8_dec = &ctx->vp8_dec;
+ struct hantro_dev *vpu = ctx->dev;
+
+ dma_free_coherent(vpu->dev, vp8_dec->segment_map.size,
+ vp8_dec->segment_map.cpu, vp8_dec->segment_map.dma);
+ dma_free_coherent(vpu->dev, vp8_dec->prob_tbl.size,
+ vp8_dec->prob_tbl.cpu, vp8_dec->prob_tbl.dma);
+}
diff --git a/drivers/staging/media/hantro/rk3288_vpu_hw.c b/drivers/staging/media/hantro/rk3288_vpu_hw.c
index bcacc4f51093..6bfcc47d1e58 100644
--- a/drivers/staging/media/hantro/rk3288_vpu_hw.c
+++ b/drivers/staging/media/hantro/rk3288_vpu_hw.c
@@ -62,6 +62,19 @@ static const struct hantro_fmt rk3288_vpu_dec_fmts[] = {
.codec_mode = HANTRO_MODE_NONE,
},
{
+ .fourcc = V4L2_PIX_FMT_H264_SLICE,
+ .codec_mode = HANTRO_MODE_H264_DEC,
+ .max_depth = 2,
+ .frmsize = {
+ .min_width = 48,
+ .max_width = 3840,
+ .step_width = H264_MB_DIM,
+ .min_height = 48,
+ .max_height = 2160,
+ .step_height = H264_MB_DIM,
+ },
+ },
+ {
.fourcc = V4L2_PIX_FMT_MPEG2_SLICE,
.codec_mode = HANTRO_MODE_MPEG2_DEC,
.max_depth = 2,
@@ -74,6 +87,19 @@ static const struct hantro_fmt rk3288_vpu_dec_fmts[] = {
.step_height = MPEG2_MB_DIM,
},
},
+ {
+ .fourcc = V4L2_PIX_FMT_VP8_FRAME,
+ .codec_mode = HANTRO_MODE_VP8_DEC,
+ .max_depth = 2,
+ .frmsize = {
+ .min_width = 48,
+ .max_width = 3840,
+ .step_width = VP8_MB_DIM,
+ .min_height = 48,
+ .max_height = 2160,
+ .step_height = VP8_MB_DIM,
+ },
+ },
};
static irqreturn_t rk3288_vepu_irq(int irq, void *dev_id)
@@ -149,12 +175,24 @@ static const struct hantro_codec_ops rk3288_vpu_codec_ops[] = {
.init = hantro_jpeg_enc_init,
.exit = hantro_jpeg_enc_exit,
},
+ [HANTRO_MODE_H264_DEC] = {
+ .run = hantro_g1_h264_dec_run,
+ .reset = rk3288_vpu_dec_reset,
+ .init = hantro_h264_dec_init,
+ .exit = hantro_h264_dec_exit,
+ },
[HANTRO_MODE_MPEG2_DEC] = {
.run = hantro_g1_mpeg2_dec_run,
.reset = rk3288_vpu_dec_reset,
.init = hantro_mpeg2_dec_init,
.exit = hantro_mpeg2_dec_exit,
},
+ [HANTRO_MODE_VP8_DEC] = {
+ .run = hantro_g1_vp8_dec_run,
+ .reset = rk3288_vpu_dec_reset,
+ .init = hantro_vp8_dec_init,
+ .exit = hantro_vp8_dec_exit,
+ },
};
/*
@@ -177,7 +215,8 @@ const struct hantro_variant rk3288_vpu_variant = {
.dec_offset = 0x400,
.dec_fmts = rk3288_vpu_dec_fmts,
.num_dec_fmts = ARRAY_SIZE(rk3288_vpu_dec_fmts),
- .codec = HANTRO_JPEG_ENCODER | HANTRO_MPEG2_DECODER,
+ .codec = HANTRO_JPEG_ENCODER | HANTRO_MPEG2_DECODER |
+ HANTRO_VP8_DECODER | HANTRO_H264_DECODER,
.codec_ops = rk3288_vpu_codec_ops,
.irqs = rk3288_irqs,
.num_irqs = ARRAY_SIZE(rk3288_irqs),
diff --git a/drivers/staging/media/hantro/rk3399_vpu_hw.c b/drivers/staging/media/hantro/rk3399_vpu_hw.c
index 5718f8063542..14d14bc6b12b 100644
--- a/drivers/staging/media/hantro/rk3399_vpu_hw.c
+++ b/drivers/staging/media/hantro/rk3399_vpu_hw.c
@@ -73,6 +73,19 @@ static const struct hantro_fmt rk3399_vpu_dec_fmts[] = {
.step_height = MPEG2_MB_DIM,
},
},
+ {
+ .fourcc = V4L2_PIX_FMT_VP8_FRAME,
+ .codec_mode = HANTRO_MODE_VP8_DEC,
+ .max_depth = 2,
+ .frmsize = {
+ .min_width = 48,
+ .max_width = 3840,
+ .step_width = VP8_MB_DIM,
+ .min_height = 48,
+ .max_height = 2160,
+ .step_height = VP8_MB_DIM,
+ },
+ },
};
static irqreturn_t rk3399_vepu_irq(int irq, void *dev_id)
@@ -154,6 +167,12 @@ static const struct hantro_codec_ops rk3399_vpu_codec_ops[] = {
.init = hantro_mpeg2_dec_init,
.exit = hantro_mpeg2_dec_exit,
},
+ [HANTRO_MODE_VP8_DEC] = {
+ .run = rk3399_vpu_vp8_dec_run,
+ .reset = rk3399_vpu_dec_reset,
+ .init = hantro_vp8_dec_init,
+ .exit = hantro_vp8_dec_exit,
+ },
};
/*
@@ -176,7 +195,8 @@ const struct hantro_variant rk3399_vpu_variant = {
.dec_offset = 0x400,
.dec_fmts = rk3399_vpu_dec_fmts,
.num_dec_fmts = ARRAY_SIZE(rk3399_vpu_dec_fmts),
- .codec = HANTRO_JPEG_ENCODER | HANTRO_MPEG2_DECODER,
+ .codec = HANTRO_JPEG_ENCODER | HANTRO_MPEG2_DECODER |
+ HANTRO_VP8_DECODER,
.codec_ops = rk3399_vpu_codec_ops,
.irqs = rk3399_irqs,
.num_irqs = ARRAY_SIZE(rk3399_irqs),
@@ -184,3 +204,20 @@ const struct hantro_variant rk3399_vpu_variant = {
.clk_names = rk3399_clk_names,
.num_clocks = ARRAY_SIZE(rk3399_clk_names)
};
+
+static const struct hantro_irq rk3328_irqs[] = {
+ { "vdpu", rk3399_vdpu_irq },
+};
+
+const struct hantro_variant rk3328_vpu_variant = {
+ .dec_offset = 0x400,
+ .dec_fmts = rk3399_vpu_dec_fmts,
+ .num_dec_fmts = ARRAY_SIZE(rk3399_vpu_dec_fmts),
+ .codec = HANTRO_MPEG2_DECODER | HANTRO_VP8_DECODER,
+ .codec_ops = rk3399_vpu_codec_ops,
+ .irqs = rk3328_irqs,
+ .num_irqs = ARRAY_SIZE(rk3328_irqs),
+ .init = rk3399_vpu_hw_init,
+ .clk_names = rk3399_clk_names,
+ .num_clocks = ARRAY_SIZE(rk3399_clk_names),
+};
diff --git a/drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c b/drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c
index ae66354d2d93..06162f569b5e 100644
--- a/drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c
+++ b/drivers/staging/media/hantro/rk3399_vpu_hw_jpeg_enc.c
@@ -113,14 +113,12 @@ void rk3399_vpu_jpeg_enc_run(struct hantro_ctx *ctx)
struct hantro_dev *vpu = ctx->dev;
struct vb2_v4l2_buffer *src_buf, *dst_buf;
struct hantro_jpeg_ctx jpeg_ctx;
- struct media_request *src_req;
u32 reg;
- src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
- dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ src_buf = hantro_get_src_buf(ctx);
+ dst_buf = hantro_get_dst_buf(ctx);
- src_req = src_buf->vb2_buf.req_obj.req;
- v4l2_ctrl_request_setup(src_req, &ctx->ctrl_handler);
+ hantro_prepare_run(ctx);
memset(&jpeg_ctx, 0, sizeof(jpeg_ctx));
jpeg_ctx.buffer = vb2_plane_vaddr(&dst_buf->vb2_buf, 0);
@@ -157,9 +155,7 @@ void rk3399_vpu_jpeg_enc_run(struct hantro_ctx *ctx)
| VEPU_REG_ENCODE_FORMAT_JPEG
| VEPU_REG_ENCODE_ENABLE;
- v4l2_ctrl_request_complete(src_req, &ctx->ctrl_handler);
-
/* Kick the watchdog and start encoding */
- schedule_delayed_work(&vpu->watchdog_work, msecs_to_jiffies(2000));
+ hantro_finish_run(ctx);
vepu_write(vpu, reg, VEPU_REG_ENCODE_START);
}
diff --git a/drivers/staging/media/hantro/rk3399_vpu_hw_mpeg2_dec.c b/drivers/staging/media/hantro/rk3399_vpu_hw_mpeg2_dec.c
index 8685bddfbcab..e7ba5c0441cc 100644
--- a/drivers/staging/media/hantro/rk3399_vpu_hw_mpeg2_dec.c
+++ b/drivers/staging/media/hantro/rk3399_vpu_hw_mpeg2_dec.c
@@ -169,12 +169,10 @@ void rk3399_vpu_mpeg2_dec_run(struct hantro_ctx *ctx)
const struct v4l2_mpeg2_picture *picture;
u32 reg;
- src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
- dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ src_buf = hantro_get_src_buf(ctx);
+ dst_buf = hantro_get_dst_buf(ctx);
- /* Apply request controls if any */
- v4l2_ctrl_request_setup(src_buf->vb2_buf.req_obj.req,
- &ctx->ctrl_handler);
+ hantro_prepare_run(ctx);
slice_params = hantro_get_ctrl(ctx,
V4L2_CID_MPEG_VIDEO_MPEG2_SLICE_PARAMS);
@@ -254,12 +252,8 @@ void rk3399_vpu_mpeg2_dec_run(struct hantro_ctx *ctx)
&dst_buf->vb2_buf,
sequence, picture, slice_params);
- /* Controls no longer in-use, we can complete them */
- v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req,
- &ctx->ctrl_handler);
-
/* Kick the watchdog and start decoding */
- schedule_delayed_work(&vpu->watchdog_work, msecs_to_jiffies(2000));
+ hantro_finish_run(ctx);
reg = vdpu_read(vpu, VDPU_SWREG(57)) | VDPU_REG_DEC_E(1);
vdpu_write(vpu, reg, VDPU_SWREG(57));
diff --git a/drivers/staging/media/hantro/rk3399_vpu_hw_vp8_dec.c b/drivers/staging/media/hantro/rk3399_vpu_hw_vp8_dec.c
new file mode 100644
index 000000000000..f17e32620b08
--- /dev/null
+++ b/drivers/staging/media/hantro/rk3399_vpu_hw_vp8_dec.c
@@ -0,0 +1,595 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip VPU codec vp8 decode driver
+ *
+ * Copyright (C) 2014 Rockchip Electronics Co., Ltd.
+ * ZhiChao Yu <zhichao.yu@rock-chips.com>
+ *
+ * Copyright (C) 2014 Google LLC.
+ * Tomasz Figa <tfiga@chromium.org>
+ *
+ * Copyright (C) 2015 Rockchip Electronics Co., Ltd.
+ * Alpha Lin <alpha.lin@rock-chips.com>
+ */
+
+#include <media/v4l2-mem2mem.h>
+#include <media/vp8-ctrls.h>
+
+#include "hantro_hw.h"
+#include "hantro.h"
+#include "hantro_g1_regs.h"
+
+#define VDPU_REG_DEC_CTRL0 0x0c8
+#define VDPU_REG_STREAM_LEN 0x0cc
+#define VDPU_REG_DEC_FORMAT 0x0d4
+#define VDPU_REG_DEC_CTRL0_DEC_MODE(x) (((x) & 0xf) << 0)
+#define VDPU_REG_DATA_ENDIAN 0x0d8
+#define VDPU_REG_CONFIG_DEC_STRENDIAN_E BIT(5)
+#define VDPU_REG_CONFIG_DEC_STRSWAP32_E BIT(4)
+#define VDPU_REG_CONFIG_DEC_OUTSWAP32_E BIT(3)
+#define VDPU_REG_CONFIG_DEC_INSWAP32_E BIT(2)
+#define VDPU_REG_CONFIG_DEC_OUT_ENDIAN BIT(1)
+#define VDPU_REG_CONFIG_DEC_IN_ENDIAN BIT(0)
+#define VDPU_REG_AXI_CTRL 0x0e0
+#define VDPU_REG_CONFIG_DEC_MAX_BURST(x) (((x) & 0x1f) << 16)
+#define VDPU_REG_EN_FLAGS 0x0e4
+#define VDPU_REG_DEC_CTRL0_PIC_INTER_E BIT(14)
+#define VDPU_REG_CONFIG_DEC_TIMEOUT_E BIT(5)
+#define VDPU_REG_CONFIG_DEC_CLK_GATE_E BIT(4)
+#define VDPU_REG_PRED_FLT 0x0ec
+#define VDPU_REG_ADDR_QTABLE 0x0f4
+#define VDPU_REG_ADDR_DST 0x0fc
+#define VDPU_REG_ADDR_STR 0x100
+#define VDPU_REG_VP8_PIC_MB_SIZE 0x1e0
+#define VDPU_REG_VP8_DCT_START_BIT 0x1e4
+#define VDPU_REG_DEC_CTRL4_VC1_HEIGHT_EXT BIT(13)
+#define VDPU_REG_DEC_CTRL4_BILIN_MC_E BIT(12)
+#define VDPU_REG_VP8_CTRL0 0x1e8
+#define VDPU_REG_VP8_DATA_VAL 0x1f0
+#define VDPU_REG_PRED_FLT7 0x1f4
+#define VDPU_REG_PRED_FLT8 0x1f8
+#define VDPU_REG_PRED_FLT9 0x1fc
+#define VDPU_REG_PRED_FLT10 0x200
+#define VDPU_REG_FILTER_LEVEL 0x204
+#define VDPU_REG_VP8_QUANTER0 0x208
+#define VDPU_REG_VP8_ADDR_REF0 0x20c
+#define VDPU_REG_FILTER_MB_ADJ 0x210
+#define VDPU_REG_REF_PIC_FILT_TYPE_E BIT(31)
+#define VDPU_REG_REF_PIC_FILT_SHARPNESS(x) (((x) & 0x7) << 28)
+#define VDPU_REG_FILTER_REF_ADJ 0x214
+#define VDPU_REG_VP8_ADDR_REF2_5(i) (0x218 + ((i) * 0x4))
+#define VDPU_REG_VP8_GREF_SIGN_BIAS BIT(0)
+#define VDPU_REG_VP8_AREF_SIGN_BIAS BIT(0)
+#define VDPU_REG_VP8_DCT_BASE(i) \
+ (0x230 + ((((i) < 5) ? (i) : ((i) + 1)) * 0x4))
+#define VDPU_REG_VP8_ADDR_CTRL_PART 0x244
+#define VDPU_REG_VP8_SEGMENT_VAL 0x254
+#define VDPU_REG_FWD_PIC1_SEGMENT_BASE(x) ((x) << 0)
+#define VDPU_REG_FWD_PIC1_SEGMENT_UPD_E BIT(1)
+#define VDPU_REG_FWD_PIC1_SEGMENT_E BIT(0)
+#define VDPU_REG_VP8_DCT_START_BIT2 0x258
+#define VDPU_REG_VP8_QUANTER1 0x25c
+#define VDPU_REG_VP8_QUANTER2 0x260
+#define VDPU_REG_PRED_FLT1 0x264
+#define VDPU_REG_PRED_FLT2 0x268
+#define VDPU_REG_PRED_FLT3 0x26c
+#define VDPU_REG_PRED_FLT4 0x270
+#define VDPU_REG_PRED_FLT5 0x274
+#define VDPU_REG_PRED_FLT6 0x278
+
+static const struct hantro_reg vp8_dec_dct_base[8] = {
+ { VDPU_REG_ADDR_STR, 0, 0xffffffff },
+ { VDPU_REG_VP8_DCT_BASE(0), 0, 0xffffffff },
+ { VDPU_REG_VP8_DCT_BASE(1), 0, 0xffffffff },
+ { VDPU_REG_VP8_DCT_BASE(2), 0, 0xffffffff },
+ { VDPU_REG_VP8_DCT_BASE(3), 0, 0xffffffff },
+ { VDPU_REG_VP8_DCT_BASE(4), 0, 0xffffffff },
+ { VDPU_REG_VP8_DCT_BASE(5), 0, 0xffffffff },
+ { VDPU_REG_VP8_DCT_BASE(6), 0, 0xffffffff },
+};
+
+static const struct hantro_reg vp8_dec_lf_level[4] = {
+ { VDPU_REG_FILTER_LEVEL, 18, 0x3f },
+ { VDPU_REG_FILTER_LEVEL, 12, 0x3f },
+ { VDPU_REG_FILTER_LEVEL, 6, 0x3f },
+ { VDPU_REG_FILTER_LEVEL, 0, 0x3f },
+};
+
+static const struct hantro_reg vp8_dec_mb_adj[4] = {
+ { VDPU_REG_FILTER_MB_ADJ, 21, 0x7f },
+ { VDPU_REG_FILTER_MB_ADJ, 14, 0x7f },
+ { VDPU_REG_FILTER_MB_ADJ, 7, 0x7f },
+ { VDPU_REG_FILTER_MB_ADJ, 0, 0x7f },
+};
+
+static const struct hantro_reg vp8_dec_ref_adj[4] = {
+ { VDPU_REG_FILTER_REF_ADJ, 21, 0x7f },
+ { VDPU_REG_FILTER_REF_ADJ, 14, 0x7f },
+ { VDPU_REG_FILTER_REF_ADJ, 7, 0x7f },
+ { VDPU_REG_FILTER_REF_ADJ, 0, 0x7f },
+};
+
+static const struct hantro_reg vp8_dec_quant[4] = {
+ { VDPU_REG_VP8_QUANTER0, 11, 0x7ff },
+ { VDPU_REG_VP8_QUANTER0, 0, 0x7ff },
+ { VDPU_REG_VP8_QUANTER1, 11, 0x7ff },
+ { VDPU_REG_VP8_QUANTER1, 0, 0x7ff },
+};
+
+static const struct hantro_reg vp8_dec_quant_delta[5] = {
+ { VDPU_REG_VP8_QUANTER0, 27, 0x1f },
+ { VDPU_REG_VP8_QUANTER0, 22, 0x1f },
+ { VDPU_REG_VP8_QUANTER1, 27, 0x1f },
+ { VDPU_REG_VP8_QUANTER1, 22, 0x1f },
+ { VDPU_REG_VP8_QUANTER2, 27, 0x1f },
+};
+
+static const struct hantro_reg vp8_dec_dct_start_bits[8] = {
+ { VDPU_REG_VP8_CTRL0, 26, 0x3f },
+ { VDPU_REG_VP8_DCT_START_BIT, 26, 0x3f },
+ { VDPU_REG_VP8_DCT_START_BIT, 20, 0x3f },
+ { VDPU_REG_VP8_DCT_START_BIT2, 24, 0x3f },
+ { VDPU_REG_VP8_DCT_START_BIT2, 18, 0x3f },
+ { VDPU_REG_VP8_DCT_START_BIT2, 12, 0x3f },
+ { VDPU_REG_VP8_DCT_START_BIT2, 6, 0x3f },
+ { VDPU_REG_VP8_DCT_START_BIT2, 0, 0x3f },
+};
+
+static const struct hantro_reg vp8_dec_pred_bc_tap[8][6] = {
+ {
+ { 0, 0, 0},
+ { VDPU_REG_PRED_FLT, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT1, 22, 0x3ff },
+ { 0, 0, 0},
+ }, {
+ { 0, 0, 0},
+ { VDPU_REG_PRED_FLT1, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT1, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT2, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT2, 12, 0x3ff },
+ { 0, 0, 0},
+ }, {
+ { VDPU_REG_PRED_FLT10, 10, 0x3 },
+ { VDPU_REG_PRED_FLT2, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT3, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT3, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT3, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT10, 8, 0x3},
+ }, {
+ { 0, 0, 0},
+ { VDPU_REG_PRED_FLT4, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT4, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT4, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT5, 22, 0x3ff },
+ { 0, 0, 0},
+ }, {
+ { VDPU_REG_PRED_FLT10, 6, 0x3 },
+ { VDPU_REG_PRED_FLT5, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT5, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT6, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT6, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT10, 4, 0x3 },
+ }, {
+ { 0, 0, 0},
+ { VDPU_REG_PRED_FLT6, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT7, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT7, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT7, 2, 0x3ff },
+ { 0, 0, 0},
+ }, {
+ { VDPU_REG_PRED_FLT10, 2, 0x3 },
+ { VDPU_REG_PRED_FLT8, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT8, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT8, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT9, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT10, 0, 0x3 },
+ }, {
+ { 0, 0, 0},
+ { VDPU_REG_PRED_FLT9, 12, 0x3ff },
+ { VDPU_REG_PRED_FLT9, 2, 0x3ff },
+ { VDPU_REG_PRED_FLT10, 22, 0x3ff },
+ { VDPU_REG_PRED_FLT10, 12, 0x3ff },
+ { 0, 0, 0},
+ },
+};
+
+static const struct hantro_reg vp8_dec_mb_start_bit = {
+ .base = VDPU_REG_VP8_CTRL0,
+ .shift = 18,
+ .mask = 0x3f
+};
+
+static const struct hantro_reg vp8_dec_mb_aligned_data_len = {
+ .base = VDPU_REG_VP8_DATA_VAL,
+ .shift = 0,
+ .mask = 0x3fffff
+};
+
+static const struct hantro_reg vp8_dec_num_dct_partitions = {
+ .base = VDPU_REG_VP8_DATA_VAL,
+ .shift = 24,
+ .mask = 0xf
+};
+
+static const struct hantro_reg vp8_dec_stream_len = {
+ .base = VDPU_REG_STREAM_LEN,
+ .shift = 0,
+ .mask = 0xffffff
+};
+
+static const struct hantro_reg vp8_dec_mb_width = {
+ .base = VDPU_REG_VP8_PIC_MB_SIZE,
+ .shift = 23,
+ .mask = 0x1ff
+};
+
+static const struct hantro_reg vp8_dec_mb_height = {
+ .base = VDPU_REG_VP8_PIC_MB_SIZE,
+ .shift = 11,
+ .mask = 0xff
+};
+
+static const struct hantro_reg vp8_dec_mb_width_ext = {
+ .base = VDPU_REG_VP8_PIC_MB_SIZE,
+ .shift = 3,
+ .mask = 0x7
+};
+
+static const struct hantro_reg vp8_dec_mb_height_ext = {
+ .base = VDPU_REG_VP8_PIC_MB_SIZE,
+ .shift = 0,
+ .mask = 0x7
+};
+
+static const struct hantro_reg vp8_dec_bool_range = {
+ .base = VDPU_REG_VP8_CTRL0,
+ .shift = 0,
+ .mask = 0xff
+};
+
+static const struct hantro_reg vp8_dec_bool_value = {
+ .base = VDPU_REG_VP8_CTRL0,
+ .shift = 8,
+ .mask = 0xff
+};
+
+static const struct hantro_reg vp8_dec_filter_disable = {
+ .base = VDPU_REG_DEC_CTRL0,
+ .shift = 8,
+ .mask = 1
+};
+
+static const struct hantro_reg vp8_dec_skip_mode = {
+ .base = VDPU_REG_DEC_CTRL0,
+ .shift = 9,
+ .mask = 1
+};
+
+static const struct hantro_reg vp8_dec_start_dec = {
+ .base = VDPU_REG_EN_FLAGS,
+ .shift = 0,
+ .mask = 1
+};
+
+static void cfg_lf(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ const struct v4l2_vp8_segment_header *seg = &hdr->segment_header;
+ const struct v4l2_vp8_loopfilter_header *lf = &hdr->lf_header;
+ struct hantro_dev *vpu = ctx->dev;
+ unsigned int i;
+ u32 reg;
+
+ if (!(seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_ENABLED)) {
+ hantro_reg_write(vpu, &vp8_dec_lf_level[0], lf->level);
+ } else if (seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_DELTA_VALUE_MODE) {
+ for (i = 0; i < 4; i++) {
+ u32 lf_level = clamp(lf->level + seg->lf_update[i],
+ 0, 63);
+
+ hantro_reg_write(vpu, &vp8_dec_lf_level[i], lf_level);
+ }
+ } else {
+ for (i = 0; i < 4; i++)
+ hantro_reg_write(vpu, &vp8_dec_lf_level[i],
+ seg->lf_update[i]);
+ }
+
+ reg = VDPU_REG_REF_PIC_FILT_SHARPNESS(lf->sharpness_level);
+ if (lf->flags & V4L2_VP8_LF_FILTER_TYPE_SIMPLE)
+ reg |= VDPU_REG_REF_PIC_FILT_TYPE_E;
+ vdpu_write_relaxed(vpu, reg, VDPU_REG_FILTER_MB_ADJ);
+
+ if (lf->flags & V4L2_VP8_LF_HEADER_ADJ_ENABLE) {
+ for (i = 0; i < 4; i++) {
+ hantro_reg_write(vpu, &vp8_dec_mb_adj[i],
+ lf->mb_mode_delta[i]);
+ hantro_reg_write(vpu, &vp8_dec_ref_adj[i],
+ lf->ref_frm_delta[i]);
+ }
+ }
+}
+
+static void cfg_qp(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ const struct v4l2_vp8_quantization_header *q = &hdr->quant_header;
+ const struct v4l2_vp8_segment_header *seg = &hdr->segment_header;
+ struct hantro_dev *vpu = ctx->dev;
+ unsigned int i;
+
+ if (!(seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_ENABLED)) {
+ hantro_reg_write(vpu, &vp8_dec_quant[0], q->y_ac_qi);
+ } else if (seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_DELTA_VALUE_MODE) {
+ for (i = 0; i < 4; i++) {
+ u32 quant = clamp(q->y_ac_qi + seg->quant_update[i],
+ 0, 127);
+
+ hantro_reg_write(vpu, &vp8_dec_quant[i], quant);
+ }
+ } else {
+ for (i = 0; i < 4; i++)
+ hantro_reg_write(vpu, &vp8_dec_quant[i],
+ seg->quant_update[i]);
+ }
+
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[0], q->y_dc_delta);
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[1], q->y2_dc_delta);
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[2], q->y2_ac_delta);
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[3], q->uv_dc_delta);
+ hantro_reg_write(vpu, &vp8_dec_quant_delta[4], q->uv_ac_delta);
+}
+
+static void cfg_parts(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct vb2_v4l2_buffer *vb2_src;
+ u32 first_part_offset = VP8_FRAME_IS_KEY_FRAME(hdr) ? 10 : 3;
+ u32 mb_size, mb_offset_bytes, mb_offset_bits, mb_start_bits;
+ u32 dct_size_part_size, dct_part_offset;
+ dma_addr_t src_dma;
+ u32 dct_part_total_len = 0;
+ u32 count = 0;
+ unsigned int i;
+
+ vb2_src = hantro_get_src_buf(ctx);
+ src_dma = vb2_dma_contig_plane_dma_addr(&vb2_src->vb2_buf, 0);
+
+ /*
+ * Calculate control partition mb data info
+ * @first_part_header_bits: bits offset of mb data from first
+ * part start pos
+ * @mb_offset_bits: bits offset of mb data from src_dma
+ * base addr
+ * @mb_offset_byte: bytes offset of mb data from src_dma
+ * base addr
+ * @mb_start_bits: bits offset of mb data from mb data
+ * 64bits alignment addr
+ */
+ mb_offset_bits = first_part_offset * 8 +
+ hdr->first_part_header_bits + 8;
+ mb_offset_bytes = mb_offset_bits / 8;
+ mb_start_bits = mb_offset_bits -
+ (mb_offset_bytes & (~DEC_8190_ALIGN_MASK)) * 8;
+ mb_size = hdr->first_part_size -
+ (mb_offset_bytes - first_part_offset) +
+ (mb_offset_bytes & DEC_8190_ALIGN_MASK);
+
+ /* Macroblock data aligned base addr */
+ vdpu_write_relaxed(vpu, (mb_offset_bytes & (~DEC_8190_ALIGN_MASK)) +
+ src_dma, VDPU_REG_VP8_ADDR_CTRL_PART);
+ hantro_reg_write(vpu, &vp8_dec_mb_start_bit, mb_start_bits);
+ hantro_reg_write(vpu, &vp8_dec_mb_aligned_data_len, mb_size);
+
+ /*
+ * Calculate DCT partition info
+ * @dct_size_part_size: Containing sizes of DCT part, every DCT part
+ * has 3 bytes to store its size, except the last
+ * DCT part
+ * @dct_part_offset: bytes offset of DCT parts from src_dma base addr
+ * @dct_part_total_len: total size of all DCT parts
+ */
+ dct_size_part_size = (hdr->num_dct_parts - 1) * 3;
+ dct_part_offset = first_part_offset + hdr->first_part_size;
+ for (i = 0; i < hdr->num_dct_parts; i++)
+ dct_part_total_len += hdr->dct_part_sizes[i];
+ dct_part_total_len += dct_size_part_size;
+ dct_part_total_len += (dct_part_offset & DEC_8190_ALIGN_MASK);
+
+ /* Number of DCT partitions */
+ hantro_reg_write(vpu, &vp8_dec_num_dct_partitions,
+ hdr->num_dct_parts - 1);
+
+ /* DCT partition length */
+ hantro_reg_write(vpu, &vp8_dec_stream_len, dct_part_total_len);
+
+ /* DCT partitions base address */
+ for (i = 0; i < hdr->num_dct_parts; i++) {
+ u32 byte_offset = dct_part_offset + dct_size_part_size + count;
+ u32 base_addr = byte_offset + src_dma;
+
+ hantro_reg_write(vpu, &vp8_dec_dct_base[i],
+ base_addr & (~DEC_8190_ALIGN_MASK));
+
+ hantro_reg_write(vpu, &vp8_dec_dct_start_bits[i],
+ (byte_offset & DEC_8190_ALIGN_MASK) * 8);
+
+ count += hdr->dct_part_sizes[i];
+ }
+}
+
+/*
+ * prediction filter taps
+ * normal 6-tap filters
+ */
+static void cfg_tap(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ int i, j;
+
+ if ((hdr->version & 0x03) != 0)
+ return; /* Tap filter not used. */
+
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 6; j++) {
+ if (vp8_dec_pred_bc_tap[i][j].base != 0)
+ hantro_reg_write(vpu,
+ &vp8_dec_pred_bc_tap[i][j],
+ hantro_vp8_dec_mc_filter[i][j]);
+ }
+ }
+}
+
+static void cfg_ref(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ struct hantro_dev *vpu = ctx->dev;
+ struct vb2_v4l2_buffer *vb2_dst;
+ struct vb2_queue *cap_q;
+ dma_addr_t ref;
+
+ cap_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ vb2_dst = hantro_get_dst_buf(ctx);
+
+ ref = hantro_get_ref(cap_q, hdr->last_frame_ts);
+ if (!ref)
+ ref = vb2_dma_contig_plane_dma_addr(&vb2_dst->vb2_buf, 0);
+ vdpu_write_relaxed(vpu, ref, VDPU_REG_VP8_ADDR_REF0);
+
+ ref = hantro_get_ref(cap_q, hdr->golden_frame_ts);
+ WARN_ON(!ref && hdr->golden_frame_ts);
+ if (!ref)
+ ref = vb2_dma_contig_plane_dma_addr(&vb2_dst->vb2_buf, 0);
+ if (hdr->flags & V4L2_VP8_FRAME_HEADER_FLAG_SIGN_BIAS_GOLDEN)
+ ref |= VDPU_REG_VP8_GREF_SIGN_BIAS;
+ vdpu_write_relaxed(vpu, ref, VDPU_REG_VP8_ADDR_REF2_5(2));
+
+ ref = hantro_get_ref(cap_q, hdr->alt_frame_ts);
+ WARN_ON(!ref && hdr->alt_frame_ts);
+ if (!ref)
+ ref = vb2_dma_contig_plane_dma_addr(&vb2_dst->vb2_buf, 0);
+ if (hdr->flags & V4L2_VP8_FRAME_HEADER_FLAG_SIGN_BIAS_ALT)
+ ref |= VDPU_REG_VP8_AREF_SIGN_BIAS;
+ vdpu_write_relaxed(vpu, ref, VDPU_REG_VP8_ADDR_REF2_5(3));
+}
+
+static void cfg_buffers(struct hantro_ctx *ctx,
+ const struct v4l2_ctrl_vp8_frame_header *hdr)
+{
+ const struct v4l2_vp8_segment_header *seg = &hdr->segment_header;
+ struct hantro_dev *vpu = ctx->dev;
+ struct vb2_v4l2_buffer *vb2_dst;
+ dma_addr_t dst_dma;
+ u32 reg;
+
+ vb2_dst = hantro_get_dst_buf(ctx);
+
+ /* Set probability table buffer address */
+ vdpu_write_relaxed(vpu, ctx->vp8_dec.prob_tbl.dma,
+ VDPU_REG_ADDR_QTABLE);
+
+ /* Set segment map address */
+ reg = VDPU_REG_FWD_PIC1_SEGMENT_BASE(ctx->vp8_dec.segment_map.dma);
+ if (seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_ENABLED) {
+ reg |= VDPU_REG_FWD_PIC1_SEGMENT_E;
+ if (seg->flags & V4L2_VP8_SEGMENT_HEADER_FLAG_UPDATE_MAP)
+ reg |= VDPU_REG_FWD_PIC1_SEGMENT_UPD_E;
+ }
+ vdpu_write_relaxed(vpu, reg, VDPU_REG_VP8_SEGMENT_VAL);
+
+ /* set output frame buffer address */
+ dst_dma = vb2_dma_contig_plane_dma_addr(&vb2_dst->vb2_buf, 0);
+ vdpu_write_relaxed(vpu, dst_dma, VDPU_REG_ADDR_DST);
+}
+
+void rk3399_vpu_vp8_dec_run(struct hantro_ctx *ctx)
+{
+ const struct v4l2_ctrl_vp8_frame_header *hdr;
+ struct hantro_dev *vpu = ctx->dev;
+ size_t height = ctx->dst_fmt.height;
+ size_t width = ctx->dst_fmt.width;
+ u32 mb_width, mb_height;
+ u32 reg;
+
+ hantro_prepare_run(ctx);
+
+ hdr = hantro_get_ctrl(ctx, V4L2_CID_MPEG_VIDEO_VP8_FRAME_HEADER);
+ if (WARN_ON(!hdr))
+ return;
+
+ /* Reset segment_map buffer in keyframe */
+ if (VP8_FRAME_IS_KEY_FRAME(hdr) && ctx->vp8_dec.segment_map.cpu)
+ memset(ctx->vp8_dec.segment_map.cpu, 0,
+ ctx->vp8_dec.segment_map.size);
+
+ hantro_vp8_prob_update(ctx, hdr);
+
+ /*
+ * Extensive testing shows that the hardware does not properly
+ * clear the internal state from previous a decoding run. This
+ * causes corruption in decoded frames for multi-instance use cases.
+ * A soft reset before programming the registers has been found
+ * to resolve those problems.
+ */
+ ctx->codec_ops->reset(ctx);
+
+ reg = VDPU_REG_CONFIG_DEC_TIMEOUT_E
+ | VDPU_REG_CONFIG_DEC_CLK_GATE_E;
+ if (!VP8_FRAME_IS_KEY_FRAME(hdr))
+ reg |= VDPU_REG_DEC_CTRL0_PIC_INTER_E;
+ vdpu_write_relaxed(vpu, reg, VDPU_REG_EN_FLAGS);
+
+ reg = VDPU_REG_CONFIG_DEC_STRENDIAN_E
+ | VDPU_REG_CONFIG_DEC_INSWAP32_E
+ | VDPU_REG_CONFIG_DEC_STRSWAP32_E
+ | VDPU_REG_CONFIG_DEC_OUTSWAP32_E
+ | VDPU_REG_CONFIG_DEC_IN_ENDIAN
+ | VDPU_REG_CONFIG_DEC_OUT_ENDIAN;
+ vdpu_write_relaxed(vpu, reg, VDPU_REG_DATA_ENDIAN);
+
+ reg = VDPU_REG_CONFIG_DEC_MAX_BURST(16);
+ vdpu_write_relaxed(vpu, reg, VDPU_REG_AXI_CTRL);
+
+ reg = VDPU_REG_DEC_CTRL0_DEC_MODE(10);
+ vdpu_write_relaxed(vpu, reg, VDPU_REG_DEC_FORMAT);
+
+ if (!(hdr->flags & V4L2_VP8_FRAME_HEADER_FLAG_MB_NO_SKIP_COEFF))
+ hantro_reg_write(vpu, &vp8_dec_skip_mode, 1);
+ if (hdr->lf_header.level == 0)
+ hantro_reg_write(vpu, &vp8_dec_filter_disable, 1);
+
+ /* Frame dimensions */
+ mb_width = VP8_MB_WIDTH(width);
+ mb_height = VP8_MB_HEIGHT(height);
+
+ hantro_reg_write(vpu, &vp8_dec_mb_width, mb_width);
+ hantro_reg_write(vpu, &vp8_dec_mb_height, mb_height);
+ hantro_reg_write(vpu, &vp8_dec_mb_width_ext, mb_width >> 9);
+ hantro_reg_write(vpu, &vp8_dec_mb_height_ext, mb_height >> 8);
+
+ /* Boolean decoder */
+ hantro_reg_write(vpu, &vp8_dec_bool_range, hdr->coder_state.range);
+ hantro_reg_write(vpu, &vp8_dec_bool_value, hdr->coder_state.value);
+
+ reg = vdpu_read(vpu, VDPU_REG_VP8_DCT_START_BIT);
+ if (hdr->version != 3)
+ reg |= VDPU_REG_DEC_CTRL4_VC1_HEIGHT_EXT;
+ if (hdr->version & 0x3)
+ reg |= VDPU_REG_DEC_CTRL4_BILIN_MC_E;
+ vdpu_write_relaxed(vpu, reg, VDPU_REG_VP8_DCT_START_BIT);
+
+ cfg_lf(ctx, hdr);
+ cfg_qp(ctx, hdr);
+ cfg_parts(ctx, hdr);
+ cfg_tap(ctx, hdr);
+ cfg_ref(ctx, hdr);
+ cfg_buffers(ctx, hdr);
+
+ hantro_finish_run(ctx);
+
+ hantro_reg_write(vpu, &vp8_dec_start_dec, 1);
+}
diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig
index 4c726345dc25..8f1ae50a4abd 100644
--- a/drivers/staging/media/imx/Kconfig
+++ b/drivers/staging/media/imx/Kconfig
@@ -7,6 +7,7 @@ config VIDEO_IMX_MEDIA
depends on HAS_DMA
select VIDEOBUF2_DMA_CONTIG
select V4L2_FWNODE
+ select V4L2_MEM2MEM_DEV
help
Say yes here to enable support for video4linux media controller
driver for the i.MX5/6 SOC.
@@ -22,11 +23,11 @@ config VIDEO_IMX_CSI
A video4linux camera sensor interface driver for i.MX5/6.
config VIDEO_IMX7_CSI
- tristate "i.MX7 Camera Sensor Interface driver"
+ tristate "i.MX6UL/L / i.MX7 Camera Sensor Interface driver"
depends on VIDEO_IMX_MEDIA && VIDEO_DEV && I2C
default y
help
Enable support for video4linux camera sensor interface driver for
- i.MX7.
+ i.MX6UL/L or i.MX7.
endmenu
endif
diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
index aa6c4b4ad37e..9bd9e873ba7c 100644
--- a/drivers/staging/media/imx/Makefile
+++ b/drivers/staging/media/imx/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
imx6-media-objs := imx-media-dev.o imx-media-internal-sd.o \
- imx-ic-common.o imx-ic-prp.o imx-ic-prpencvf.o imx-media-vdic.o
+ imx-ic-common.o imx-ic-prp.o imx-ic-prpencvf.o imx-media-vdic.o \
+ imx-media-csc-scaler.o
imx-media-common-objs := imx-media-capture.o imx-media-dev-common.o \
imx-media-of.o imx-media-utils.o
diff --git a/drivers/staging/media/imx/imx-media-csc-scaler.c b/drivers/staging/media/imx/imx-media-csc-scaler.c
new file mode 100644
index 000000000000..2b635ebf62d6
--- /dev/null
+++ b/drivers/staging/media/imx/imx-media-csc-scaler.c
@@ -0,0 +1,925 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * i.MX IPUv3 IC PP mem2mem CSC/Scaler driver
+ *
+ * Copyright (C) 2011 Pengutronix, Sascha Hauer
+ * Copyright (C) 2018 Pengutronix, Philipp Zabel
+ */
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <video/imx-ipu-v3.h>
+#include <video/imx-ipu-image-convert.h>
+
+#include <media/media-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "imx-media.h"
+
+#define fh_to_ctx(__fh) container_of(__fh, struct ipu_csc_scaler_ctx, fh)
+
+enum {
+ V4L2_M2M_SRC = 0,
+ V4L2_M2M_DST = 1,
+};
+
+struct ipu_csc_scaler_priv {
+ struct imx_media_video_dev vdev;
+
+ struct v4l2_m2m_dev *m2m_dev;
+ struct device *dev;
+
+ struct imx_media_dev *md;
+
+ struct mutex mutex; /* mem2mem device mutex */
+};
+
+#define vdev_to_priv(v) container_of(v, struct ipu_csc_scaler_priv, vdev)
+
+/* Per-queue, driver-specific private data */
+struct ipu_csc_scaler_q_data {
+ struct v4l2_pix_format cur_fmt;
+ struct v4l2_rect rect;
+};
+
+struct ipu_csc_scaler_ctx {
+ struct ipu_csc_scaler_priv *priv;
+
+ struct v4l2_fh fh;
+ struct ipu_csc_scaler_q_data q_data[2];
+ struct ipu_image_convert_ctx *icc;
+
+ struct v4l2_ctrl_handler ctrl_hdlr;
+ int rotate;
+ bool hflip;
+ bool vflip;
+ enum ipu_rotate_mode rot_mode;
+ unsigned int sequence;
+};
+
+static struct ipu_csc_scaler_q_data *get_q_data(struct ipu_csc_scaler_ctx *ctx,
+ enum v4l2_buf_type type)
+{
+ if (V4L2_TYPE_IS_OUTPUT(type))
+ return &ctx->q_data[V4L2_M2M_SRC];
+ else
+ return &ctx->q_data[V4L2_M2M_DST];
+}
+
+/*
+ * mem2mem callbacks
+ */
+
+static void job_abort(void *_ctx)
+{
+ struct ipu_csc_scaler_ctx *ctx = _ctx;
+
+ if (ctx->icc)
+ ipu_image_convert_abort(ctx->icc);
+}
+
+static void ipu_ic_pp_complete(struct ipu_image_convert_run *run, void *_ctx)
+{
+ struct ipu_csc_scaler_ctx *ctx = _ctx;
+ struct ipu_csc_scaler_priv *priv = ctx->priv;
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+
+ src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+ v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true);
+
+ src_buf->sequence = ctx->sequence++;
+ dst_buf->sequence = src_buf->sequence;
+
+ v4l2_m2m_buf_done(src_buf, run->status ? VB2_BUF_STATE_ERROR :
+ VB2_BUF_STATE_DONE);
+ v4l2_m2m_buf_done(dst_buf, run->status ? VB2_BUF_STATE_ERROR :
+ VB2_BUF_STATE_DONE);
+
+ v4l2_m2m_job_finish(priv->m2m_dev, ctx->fh.m2m_ctx);
+ kfree(run);
+}
+
+static void device_run(void *_ctx)
+{
+ struct ipu_csc_scaler_ctx *ctx = _ctx;
+ struct ipu_csc_scaler_priv *priv = ctx->priv;
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ struct ipu_image_convert_run *run;
+ int ret;
+
+ src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+ run = kzalloc(sizeof(*run), GFP_KERNEL);
+ if (!run)
+ goto err;
+
+ run->ctx = ctx->icc;
+ run->in_phys = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
+ run->out_phys = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
+
+ ret = ipu_image_convert_queue(run);
+ if (ret < 0) {
+ v4l2_err(ctx->priv->vdev.vfd->v4l2_dev,
+ "%s: failed to queue: %d\n", __func__, ret);
+ goto err;
+ }
+
+ return;
+
+err:
+ v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+ v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
+ v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR);
+ v4l2_m2m_job_finish(priv->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+/*
+ * Video ioctls
+ */
+static int ipu_csc_scaler_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, "imx-media-csc-scaler", sizeof(cap->driver));
+ strscpy(cap->card, "imx-media-csc-scaler", sizeof(cap->card));
+ strscpy(cap->bus_info, "platform:imx-media-csc-scaler",
+ sizeof(cap->bus_info));
+
+ return 0;
+}
+
+static int ipu_csc_scaler_enum_fmt(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ u32 fourcc;
+ int ret;
+
+ ret = imx_media_enum_format(&fourcc, f->index, CS_SEL_ANY);
+ if (ret)
+ return ret;
+
+ f->pixelformat = fourcc;
+
+ return 0;
+}
+
+static int ipu_csc_scaler_g_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct ipu_csc_scaler_ctx *ctx = fh_to_ctx(priv);
+ struct ipu_csc_scaler_q_data *q_data;
+
+ q_data = get_q_data(ctx, f->type);
+
+ f->fmt.pix = q_data->cur_fmt;
+
+ return 0;
+}
+
+static int ipu_csc_scaler_try_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct ipu_csc_scaler_ctx *ctx = fh_to_ctx(priv);
+ struct ipu_csc_scaler_q_data *q_data = get_q_data(ctx, f->type);
+ struct ipu_image test_in, test_out;
+ enum v4l2_field field;
+
+ field = f->fmt.pix.field;
+ if (field == V4L2_FIELD_ANY)
+ field = V4L2_FIELD_NONE;
+ else if (field != V4L2_FIELD_NONE)
+ return -EINVAL;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ struct ipu_csc_scaler_q_data *q_data_in =
+ get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+
+ test_out.pix = f->fmt.pix;
+ test_in.pix = q_data_in->cur_fmt;
+ } else {
+ struct ipu_csc_scaler_q_data *q_data_out =
+ get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+
+ test_in.pix = f->fmt.pix;
+ test_out.pix = q_data_out->cur_fmt;
+ }
+
+ ipu_image_convert_adjust(&test_in, &test_out, ctx->rot_mode);
+
+ f->fmt.pix = (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ?
+ test_out.pix : test_in.pix;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ f->fmt.pix.colorspace = q_data->cur_fmt.colorspace;
+ f->fmt.pix.ycbcr_enc = q_data->cur_fmt.ycbcr_enc;
+ f->fmt.pix.xfer_func = q_data->cur_fmt.xfer_func;
+ f->fmt.pix.quantization = q_data->cur_fmt.quantization;
+ } else if (f->fmt.pix.colorspace == V4L2_COLORSPACE_DEFAULT) {
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+ f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ f->fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+ f->fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT;
+ }
+
+ return 0;
+}
+
+static int ipu_csc_scaler_s_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct ipu_csc_scaler_q_data *q_data;
+ struct ipu_csc_scaler_ctx *ctx = fh_to_ctx(priv);
+ struct vb2_queue *vq;
+ int ret;
+
+ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+ if (vb2_is_busy(vq)) {
+ v4l2_err(ctx->priv->vdev.vfd->v4l2_dev, "%s: queue busy\n",
+ __func__);
+ return -EBUSY;
+ }
+
+ q_data = get_q_data(ctx, f->type);
+
+ ret = ipu_csc_scaler_try_fmt(file, priv, f);
+ if (ret < 0)
+ return ret;
+
+ q_data->cur_fmt.width = f->fmt.pix.width;
+ q_data->cur_fmt.height = f->fmt.pix.height;
+ q_data->cur_fmt.pixelformat = f->fmt.pix.pixelformat;
+ q_data->cur_fmt.field = f->fmt.pix.field;
+ q_data->cur_fmt.bytesperline = f->fmt.pix.bytesperline;
+ q_data->cur_fmt.sizeimage = f->fmt.pix.sizeimage;
+
+ /* Reset cropping/composing rectangle */
+ q_data->rect.left = 0;
+ q_data->rect.top = 0;
+ q_data->rect.width = q_data->cur_fmt.width;
+ q_data->rect.height = q_data->cur_fmt.height;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ /* Set colorimetry on the output queue */
+ q_data->cur_fmt.colorspace = f->fmt.pix.colorspace;
+ q_data->cur_fmt.ycbcr_enc = f->fmt.pix.ycbcr_enc;
+ q_data->cur_fmt.xfer_func = f->fmt.pix.xfer_func;
+ q_data->cur_fmt.quantization = f->fmt.pix.quantization;
+ /* Propagate colorimetry to the capture queue */
+ q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ q_data->cur_fmt.colorspace = f->fmt.pix.colorspace;
+ q_data->cur_fmt.ycbcr_enc = f->fmt.pix.ycbcr_enc;
+ q_data->cur_fmt.xfer_func = f->fmt.pix.xfer_func;
+ q_data->cur_fmt.quantization = f->fmt.pix.quantization;
+ }
+
+ /*
+ * TODO: Setting colorimetry on the capture queue is currently not
+ * supported by the V4L2 API
+ */
+
+ return 0;
+}
+
+static int ipu_csc_scaler_g_selection(struct file *file, void *priv,
+ struct v4l2_selection *s)
+{
+ struct ipu_csc_scaler_ctx *ctx = fh_to_ctx(priv);
+ struct ipu_csc_scaler_q_data *q_data;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+ q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (s->target == V4L2_SEL_TGT_CROP ||
+ s->target == V4L2_SEL_TGT_COMPOSE) {
+ s->r = q_data->rect;
+ } else {
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = q_data->cur_fmt.width;
+ s->r.height = q_data->cur_fmt.height;
+ }
+
+ return 0;
+}
+
+static int ipu_csc_scaler_s_selection(struct file *file, void *priv,
+ struct v4l2_selection *s)
+{
+ struct ipu_csc_scaler_ctx *ctx = fh_to_ctx(priv);
+ struct ipu_csc_scaler_q_data *q_data;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP:
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+ s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+
+ q_data = get_q_data(ctx, s->type);
+
+ /* The input's frame width to the IC must be a multiple of 8 pixels
+ * When performing resizing the frame width must be multiple of burst
+ * size - 8 or 16 pixels as defined by CB#_BURST_16 parameter.
+ */
+ if (s->flags & V4L2_SEL_FLAG_GE)
+ s->r.width = round_up(s->r.width, 8);
+ if (s->flags & V4L2_SEL_FLAG_LE)
+ s->r.width = round_down(s->r.width, 8);
+ s->r.width = clamp_t(unsigned int, s->r.width, 8,
+ round_down(q_data->cur_fmt.width, 8));
+ s->r.height = clamp_t(unsigned int, s->r.height, 1,
+ q_data->cur_fmt.height);
+ s->r.left = clamp_t(unsigned int, s->r.left, 0,
+ q_data->cur_fmt.width - s->r.width);
+ s->r.top = clamp_t(unsigned int, s->r.top, 0,
+ q_data->cur_fmt.height - s->r.height);
+
+ /* V4L2_SEL_FLAG_KEEP_CONFIG is only valid for subdevices */
+ q_data->rect = s->r;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops ipu_csc_scaler_ioctl_ops = {
+ .vidioc_querycap = ipu_csc_scaler_querycap,
+
+ .vidioc_enum_fmt_vid_cap = ipu_csc_scaler_enum_fmt,
+ .vidioc_g_fmt_vid_cap = ipu_csc_scaler_g_fmt,
+ .vidioc_try_fmt_vid_cap = ipu_csc_scaler_try_fmt,
+ .vidioc_s_fmt_vid_cap = ipu_csc_scaler_s_fmt,
+
+ .vidioc_enum_fmt_vid_out = ipu_csc_scaler_enum_fmt,
+ .vidioc_g_fmt_vid_out = ipu_csc_scaler_g_fmt,
+ .vidioc_try_fmt_vid_out = ipu_csc_scaler_try_fmt,
+ .vidioc_s_fmt_vid_out = ipu_csc_scaler_s_fmt,
+
+ .vidioc_g_selection = ipu_csc_scaler_g_selection,
+ .vidioc_s_selection = ipu_csc_scaler_s_selection,
+
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/*
+ * Queue operations
+ */
+
+static int ipu_csc_scaler_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers,
+ unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct ipu_csc_scaler_ctx *ctx = vb2_get_drv_priv(vq);
+ struct ipu_csc_scaler_q_data *q_data;
+ unsigned int size, count = *nbuffers;
+
+ q_data = get_q_data(ctx, vq->type);
+
+ size = q_data->cur_fmt.sizeimage;
+
+ *nbuffers = count;
+
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ dev_dbg(ctx->priv->dev, "get %d buffer(s) of size %d each.\n",
+ count, size);
+
+ return 0;
+}
+
+static int ipu_csc_scaler_buf_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct ipu_csc_scaler_ctx *ctx = vb2_get_drv_priv(vq);
+ struct ipu_csc_scaler_q_data *q_data;
+ unsigned long size;
+
+ dev_dbg(ctx->priv->dev, "type: %d\n", vq->type);
+
+ if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+ if (vbuf->field == V4L2_FIELD_ANY)
+ vbuf->field = V4L2_FIELD_NONE;
+ if (vbuf->field != V4L2_FIELD_NONE) {
+ dev_dbg(ctx->priv->dev, "%s: field isn't supported\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+
+ q_data = get_q_data(ctx, vq->type);
+ size = q_data->cur_fmt.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ dev_dbg(ctx->priv->dev,
+ "%s: data will not fit into plane (%lu < %lu)\n",
+ __func__, vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+
+ return 0;
+}
+
+static void ipu_csc_scaler_buf_queue(struct vb2_buffer *vb)
+{
+ struct ipu_csc_scaler_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, to_vb2_v4l2_buffer(vb));
+}
+
+static void ipu_image_from_q_data(struct ipu_image *im,
+ struct ipu_csc_scaler_q_data *q_data)
+{
+ struct v4l2_pix_format *fmt = &q_data->cur_fmt;
+
+ im->pix = *fmt;
+ if (fmt->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT)
+ im->pix.ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+ if (fmt->quantization == V4L2_QUANTIZATION_DEFAULT)
+ im->pix.ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+ im->rect = q_data->rect;
+}
+
+static int ipu_csc_scaler_start_streaming(struct vb2_queue *q,
+ unsigned int count)
+{
+ const enum ipu_ic_task ic_task = IC_TASK_POST_PROCESSOR;
+ struct ipu_csc_scaler_ctx *ctx = vb2_get_drv_priv(q);
+ struct ipu_csc_scaler_priv *priv = ctx->priv;
+ struct ipu_soc *ipu = priv->md->ipu[0];
+ struct ipu_csc_scaler_q_data *q_data;
+ struct vb2_queue *other_q;
+ struct ipu_image in, out;
+
+ other_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
+ (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ?
+ V4L2_BUF_TYPE_VIDEO_OUTPUT :
+ V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ if (!vb2_is_streaming(other_q))
+ return 0;
+
+ if (ctx->icc) {
+ v4l2_warn(ctx->priv->vdev.vfd->v4l2_dev, "removing old ICC\n");
+ ipu_image_convert_unprepare(ctx->icc);
+ }
+
+ q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
+ ipu_image_from_q_data(&in, q_data);
+
+ q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ ipu_image_from_q_data(&out, q_data);
+
+ ctx->icc = ipu_image_convert_prepare(ipu, ic_task, &in, &out,
+ ctx->rot_mode,
+ ipu_ic_pp_complete, ctx);
+ if (IS_ERR(ctx->icc)) {
+ struct vb2_v4l2_buffer *buf;
+ int ret = PTR_ERR(ctx->icc);
+
+ ctx->icc = NULL;
+ v4l2_err(ctx->priv->vdev.vfd->v4l2_dev, "%s: error %d\n",
+ __func__, ret);
+ while ((buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
+ while ((buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void ipu_csc_scaler_stop_streaming(struct vb2_queue *q)
+{
+ struct ipu_csc_scaler_ctx *ctx = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *buf;
+
+ if (ctx->icc) {
+ ipu_image_convert_unprepare(ctx->icc);
+ ctx->icc = NULL;
+ }
+
+ ctx->sequence = 0;
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ while ((buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
+ } else {
+ while ((buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
+ }
+}
+
+static const struct vb2_ops ipu_csc_scaler_qops = {
+ .queue_setup = ipu_csc_scaler_queue_setup,
+ .buf_prepare = ipu_csc_scaler_buf_prepare,
+ .buf_queue = ipu_csc_scaler_buf_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = ipu_csc_scaler_start_streaming,
+ .stop_streaming = ipu_csc_scaler_stop_streaming,
+};
+
+static int ipu_csc_scaler_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct ipu_csc_scaler_ctx *ctx = priv;
+ int ret;
+
+ memset(src_vq, 0, sizeof(*src_vq));
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ src_vq->drv_priv = ctx;
+ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ src_vq->ops = &ipu_csc_scaler_qops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->lock = &ctx->priv->mutex;
+ src_vq->dev = ctx->priv->dev;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ memset(dst_vq, 0, sizeof(*dst_vq));
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ dst_vq->drv_priv = ctx;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->ops = &ipu_csc_scaler_qops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &ctx->priv->mutex;
+ dst_vq->dev = ctx->priv->dev;
+
+ return vb2_queue_init(dst_vq);
+}
+
+static int ipu_csc_scaler_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct ipu_csc_scaler_ctx *ctx = container_of(ctrl->handler,
+ struct ipu_csc_scaler_ctx,
+ ctrl_hdlr);
+ enum ipu_rotate_mode rot_mode;
+ int rotate;
+ bool hflip, vflip;
+ int ret = 0;
+
+ rotate = ctx->rotate;
+ hflip = ctx->hflip;
+ vflip = ctx->vflip;
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ hflip = ctrl->val;
+ break;
+ case V4L2_CID_VFLIP:
+ vflip = ctrl->val;
+ break;
+ case V4L2_CID_ROTATE:
+ rotate = ctrl->val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = ipu_degrees_to_rot_mode(&rot_mode, rotate, hflip, vflip);
+ if (ret)
+ return ret;
+
+ if (rot_mode != ctx->rot_mode) {
+ struct v4l2_pix_format *in_fmt, *out_fmt;
+ struct ipu_image test_in, test_out;
+
+ in_fmt = &ctx->q_data[V4L2_M2M_SRC].cur_fmt;
+ out_fmt = &ctx->q_data[V4L2_M2M_DST].cur_fmt;
+
+ test_in.pix = *in_fmt;
+ test_out.pix = *out_fmt;
+
+ if (ipu_rot_mode_is_irt(rot_mode) !=
+ ipu_rot_mode_is_irt(ctx->rot_mode)) {
+ /* Switch width & height to keep aspect ratio intact */
+ test_out.pix.width = out_fmt->height;
+ test_out.pix.height = out_fmt->width;
+ }
+
+ ipu_image_convert_adjust(&test_in, &test_out, ctx->rot_mode);
+
+ /* Check if output format needs to be changed */
+ if (test_in.pix.width != in_fmt->width ||
+ test_in.pix.height != in_fmt->height ||
+ test_in.pix.bytesperline != in_fmt->bytesperline ||
+ test_in.pix.sizeimage != in_fmt->sizeimage) {
+ struct vb2_queue *out_q;
+
+ out_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT);
+ if (vb2_is_busy(out_q))
+ return -EBUSY;
+ }
+
+ /* Check if capture format needs to be changed */
+ if (test_out.pix.width != out_fmt->width ||
+ test_out.pix.height != out_fmt->height ||
+ test_out.pix.bytesperline != out_fmt->bytesperline ||
+ test_out.pix.sizeimage != out_fmt->sizeimage) {
+ struct vb2_queue *cap_q;
+
+ cap_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ if (vb2_is_busy(cap_q))
+ return -EBUSY;
+ }
+
+ *in_fmt = test_in.pix;
+ *out_fmt = test_out.pix;
+
+ ctx->rot_mode = rot_mode;
+ ctx->rotate = rotate;
+ ctx->hflip = hflip;
+ ctx->vflip = vflip;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops ipu_csc_scaler_ctrl_ops = {
+ .s_ctrl = ipu_csc_scaler_s_ctrl,
+};
+
+static int ipu_csc_scaler_init_controls(struct ipu_csc_scaler_ctx *ctx)
+{
+ struct v4l2_ctrl_handler *hdlr = &ctx->ctrl_hdlr;
+
+ v4l2_ctrl_handler_init(hdlr, 3);
+
+ v4l2_ctrl_new_std(hdlr, &ipu_csc_scaler_ctrl_ops, V4L2_CID_HFLIP,
+ 0, 1, 1, 0);
+ v4l2_ctrl_new_std(hdlr, &ipu_csc_scaler_ctrl_ops, V4L2_CID_VFLIP,
+ 0, 1, 1, 0);
+ v4l2_ctrl_new_std(hdlr, &ipu_csc_scaler_ctrl_ops, V4L2_CID_ROTATE,
+ 0, 270, 90, 0);
+
+ if (hdlr->error) {
+ v4l2_ctrl_handler_free(hdlr);
+ return hdlr->error;
+ }
+
+ v4l2_ctrl_handler_setup(hdlr);
+ return 0;
+}
+
+#define DEFAULT_WIDTH 720
+#define DEFAULT_HEIGHT 576
+static const struct ipu_csc_scaler_q_data ipu_csc_scaler_q_data_default = {
+ .cur_fmt = {
+ .width = DEFAULT_WIDTH,
+ .height = DEFAULT_HEIGHT,
+ .pixelformat = V4L2_PIX_FMT_YUV420,
+ .field = V4L2_FIELD_NONE,
+ .bytesperline = DEFAULT_WIDTH,
+ .sizeimage = DEFAULT_WIDTH * DEFAULT_HEIGHT * 3 / 2,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ },
+ .rect = {
+ .width = DEFAULT_WIDTH,
+ .height = DEFAULT_HEIGHT,
+ },
+};
+
+/*
+ * File operations
+ */
+static int ipu_csc_scaler_open(struct file *file)
+{
+ struct ipu_csc_scaler_priv *priv = video_drvdata(file);
+ struct ipu_csc_scaler_ctx *ctx = NULL;
+ int ret;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->rot_mode = IPU_ROTATE_NONE;
+
+ v4l2_fh_init(&ctx->fh, video_devdata(file));
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh);
+ ctx->priv = priv;
+
+ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(priv->m2m_dev, ctx,
+ &ipu_csc_scaler_queue_init);
+ if (IS_ERR(ctx->fh.m2m_ctx)) {
+ ret = PTR_ERR(ctx->fh.m2m_ctx);
+ goto err_ctx;
+ }
+
+ ret = ipu_csc_scaler_init_controls(ctx);
+ if (ret)
+ goto err_ctrls;
+
+ ctx->fh.ctrl_handler = &ctx->ctrl_hdlr;
+
+ ctx->q_data[V4L2_M2M_SRC] = ipu_csc_scaler_q_data_default;
+ ctx->q_data[V4L2_M2M_DST] = ipu_csc_scaler_q_data_default;
+
+ dev_dbg(priv->dev, "Created instance %p, m2m_ctx: %p\n", ctx,
+ ctx->fh.m2m_ctx);
+
+ return 0;
+
+err_ctrls:
+ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+err_ctx:
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+ return ret;
+}
+
+static int ipu_csc_scaler_release(struct file *file)
+{
+ struct ipu_csc_scaler_priv *priv = video_drvdata(file);
+ struct ipu_csc_scaler_ctx *ctx = fh_to_ctx(file->private_data);
+
+ dev_dbg(priv->dev, "Releasing instance %p\n", ctx);
+
+ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations ipu_csc_scaler_fops = {
+ .owner = THIS_MODULE,
+ .open = ipu_csc_scaler_open,
+ .release = ipu_csc_scaler_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+static struct v4l2_m2m_ops m2m_ops = {
+ .device_run = device_run,
+ .job_abort = job_abort,
+};
+
+static void ipu_csc_scaler_video_device_release(struct video_device *vdev)
+{
+ struct ipu_csc_scaler_priv *priv = video_get_drvdata(vdev);
+
+ v4l2_m2m_release(priv->m2m_dev);
+ video_device_release(vdev);
+ kfree(priv);
+}
+
+static const struct video_device ipu_csc_scaler_videodev_template = {
+ .name = "ipu_ic_pp csc/scaler",
+ .fops = &ipu_csc_scaler_fops,
+ .ioctl_ops = &ipu_csc_scaler_ioctl_ops,
+ .minor = -1,
+ .release = ipu_csc_scaler_video_device_release,
+ .vfl_dir = VFL_DIR_M2M,
+ .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING,
+};
+
+int imx_media_csc_scaler_device_register(struct imx_media_video_dev *vdev)
+{
+ struct ipu_csc_scaler_priv *priv = vdev_to_priv(vdev);
+ struct video_device *vfd = vdev->vfd;
+ int ret;
+
+ vfd->v4l2_dev = &priv->md->v4l2_dev;
+
+ ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1);
+ if (ret) {
+ v4l2_err(vfd->v4l2_dev, "Failed to register video device\n");
+ return ret;
+ }
+
+ v4l2_info(vfd->v4l2_dev, "Registered %s as /dev/%s\n", vfd->name,
+ video_device_node_name(vfd));
+
+ return 0;
+}
+
+void imx_media_csc_scaler_device_unregister(struct imx_media_video_dev *vdev)
+{
+ struct ipu_csc_scaler_priv *priv = vdev_to_priv(vdev);
+ struct video_device *vfd = priv->vdev.vfd;
+
+ mutex_lock(&priv->mutex);
+
+ video_unregister_device(vfd);
+
+ mutex_unlock(&priv->mutex);
+}
+
+struct imx_media_video_dev *
+imx_media_csc_scaler_device_init(struct imx_media_dev *md)
+{
+ struct ipu_csc_scaler_priv *priv;
+ struct video_device *vfd;
+ int ret;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ priv->md = md;
+ priv->dev = md->md.dev;
+
+ mutex_init(&priv->mutex);
+
+ vfd = video_device_alloc();
+ if (!vfd) {
+ ret = -ENOMEM;
+ goto err_vfd;
+ }
+
+ *vfd = ipu_csc_scaler_videodev_template;
+ vfd->lock = &priv->mutex;
+ priv->vdev.vfd = vfd;
+
+ INIT_LIST_HEAD(&priv->vdev.list);
+
+ video_set_drvdata(vfd, priv);
+
+ priv->m2m_dev = v4l2_m2m_init(&m2m_ops);
+ if (IS_ERR(priv->m2m_dev)) {
+ ret = PTR_ERR(priv->m2m_dev);
+ v4l2_err(&md->v4l2_dev, "Failed to init mem2mem device: %d\n",
+ ret);
+ goto err_m2m;
+ }
+
+ return &priv->vdev;
+
+err_m2m:
+ video_set_drvdata(vfd, NULL);
+err_vfd:
+ kfree(priv);
+ return ERR_PTR(ret);
+}
+
+MODULE_DESCRIPTION("i.MX IPUv3 mem2mem scaler/CSC driver");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/imx/imx-media-dev.c b/drivers/staging/media/imx/imx-media-dev.c
index 6ac371f6e971..2c3c2adca683 100644
--- a/drivers/staging/media/imx/imx-media-dev.c
+++ b/drivers/staging/media/imx/imx-media-dev.c
@@ -38,9 +38,34 @@ static int imx_media_subdev_bound(struct v4l2_async_notifier *notifier,
}
/* async subdev complete notifier */
+static int imx6_media_probe_complete(struct v4l2_async_notifier *notifier)
+{
+ struct imx_media_dev *imxmd = notifier2dev(notifier);
+ int ret;
+
+ /* call the imx5/6/7 common probe completion handler */
+ ret = imx_media_probe_complete(notifier);
+ if (ret)
+ return ret;
+
+ mutex_lock(&imxmd->mutex);
+
+ imxmd->m2m_vdev = imx_media_csc_scaler_device_init(imxmd);
+ if (IS_ERR(imxmd->m2m_vdev)) {
+ ret = PTR_ERR(imxmd->m2m_vdev);
+ goto unlock;
+ }
+
+ ret = imx_media_csc_scaler_device_register(imxmd->m2m_vdev);
+unlock:
+ mutex_unlock(&imxmd->mutex);
+ return ret;
+}
+
+/* async subdev complete notifier */
static const struct v4l2_async_notifier_operations imx_media_notifier_ops = {
.bound = imx_media_subdev_bound,
- .complete = imx_media_probe_complete,
+ .complete = imx6_media_probe_complete,
};
static int imx_media_probe(struct platform_device *pdev)
@@ -85,6 +110,7 @@ static int imx_media_remove(struct platform_device *pdev)
v4l2_async_notifier_unregister(&imxmd->notifier);
imx_media_unregister_ipu_internal_subdevs(imxmd);
v4l2_async_notifier_cleanup(&imxmd->notifier);
+ imx_media_csc_scaler_device_unregister(imxmd->m2m_vdev);
media_device_unregister(&imxmd->md);
v4l2_device_unregister(&imxmd->v4l2_dev);
media_device_cleanup(&imxmd->md);
diff --git a/drivers/staging/media/imx/imx-media-internal-sd.c b/drivers/staging/media/imx/imx-media-internal-sd.c
index cb1e4cdd5079..d4237e1a4241 100644
--- a/drivers/staging/media/imx/imx-media-internal-sd.c
+++ b/drivers/staging/media/imx/imx-media-internal-sd.c
@@ -210,6 +210,10 @@ int imx_media_register_ipu_internal_subdevs(struct imx_media_dev *imxmd,
mutex_lock(&imxmd->mutex);
+ /* record this IPU */
+ if (!imxmd->ipu[ipu_id])
+ imxmd->ipu[ipu_id] = ipu;
+
/* register the synchronous subdevs */
for (i = 0; i < NUM_IPU_SUBDEVS; i++) {
intsd = &int_subdev[i];
diff --git a/drivers/staging/media/imx/imx-media-utils.c b/drivers/staging/media/imx/imx-media-utils.c
index 9088c4b720a3..4cc6a7462ae2 100644
--- a/drivers/staging/media/imx/imx-media-utils.c
+++ b/drivers/staging/media/imx/imx-media-utils.c
@@ -841,7 +841,7 @@ find_pipeline_entity(struct media_entity *start, u32 grp_id,
if (sd->grp_id & grp_id)
return &sd->entity;
} else if (buftype && is_media_entity_v4l2_video_device(start)) {
- vfd = media_entity_to_video_device(pad->entity);
+ vfd = media_entity_to_video_device(start);
if (buftype == vfd->queue->type)
return &vfd->entity;
}
diff --git a/drivers/staging/media/imx/imx-media.h b/drivers/staging/media/imx/imx-media.h
index 4d124a86b358..11861191324a 100644
--- a/drivers/staging/media/imx/imx-media.h
+++ b/drivers/staging/media/imx/imx-media.h
@@ -136,9 +136,15 @@ struct imx_media_dev {
/* master video device list */
struct list_head vdev_list;
+ /* IPUs this media driver control, valid after subdevs bound */
+ struct ipu_soc *ipu[2];
+
/* for async subdev registration */
struct v4l2_async_notifier notifier;
+ /* IC scaler/CSC mem2mem video device */
+ struct imx_media_video_dev *m2m_vdev;
+
/* the IPU internal subdev's registered synchronously */
struct v4l2_subdev *sync_sd[2][NUM_IPU_SUBDEVS];
};
@@ -269,6 +275,12 @@ struct imx_media_buffer *
imx_media_capture_device_next_buf(struct imx_media_video_dev *vdev);
void imx_media_capture_device_error(struct imx_media_video_dev *vdev);
+/* imx-media-csc-scaler.c */
+struct imx_media_video_dev *
+imx_media_csc_scaler_device_init(struct imx_media_dev *dev);
+int imx_media_csc_scaler_device_register(struct imx_media_video_dev *vdev);
+void imx_media_csc_scaler_device_unregister(struct imx_media_video_dev *vdev);
+
/* subdev group ids */
#define IMX_MEDIA_GRP_ID_CSI2 BIT(8)
#define IMX_MEDIA_GRP_ID_CSI BIT(9)
diff --git a/drivers/staging/media/imx/imx6-mipi-csi2.c b/drivers/staging/media/imx/imx6-mipi-csi2.c
index f29e28df36ed..bfa4b254c4e4 100644
--- a/drivers/staging/media/imx/imx6-mipi-csi2.c
+++ b/drivers/staging/media/imx/imx6-mipi-csi2.c
@@ -243,7 +243,7 @@ static int __maybe_unused csi2_dphy_wait_ulp(struct csi2_dev *csi2)
}
/* Waits for low-power LP-11 state on data and clock lanes. */
-static int csi2_dphy_wait_stopstate(struct csi2_dev *csi2)
+static void csi2_dphy_wait_stopstate(struct csi2_dev *csi2)
{
u32 mask, reg;
int ret;
@@ -254,11 +254,9 @@ static int csi2_dphy_wait_stopstate(struct csi2_dev *csi2)
ret = readl_poll_timeout(csi2->base + CSI2_PHY_STATE, reg,
(reg & mask) == mask, 0, 500000);
if (ret) {
- v4l2_err(&csi2->sd, "LP-11 timeout, phy_state = 0x%08x\n", reg);
- return ret;
+ v4l2_warn(&csi2->sd, "LP-11 wait timeout, likely a sensor driver bug, expect capture failures.\n");
+ v4l2_warn(&csi2->sd, "phy_state = 0x%08x\n", reg);
}
-
- return 0;
}
/* Wait for active clock on the clock lane. */
@@ -316,9 +314,7 @@ static int csi2_start(struct csi2_dev *csi2)
csi2_enable(csi2, true);
/* Step 5 */
- ret = csi2_dphy_wait_stopstate(csi2);
- if (ret)
- goto err_assert_reset;
+ csi2_dphy_wait_stopstate(csi2);
/* Step 6 */
ret = v4l2_subdev_call(csi2->src_sd, video, s_stream, 1);
diff --git a/drivers/staging/media/imx/imx7-media-csi.c b/drivers/staging/media/imx/imx7-media-csi.c
index d7d38dd9f168..bfd6b5fbf484 100644
--- a/drivers/staging/media/imx/imx7-media-csi.c
+++ b/drivers/staging/media/imx/imx7-media-csi.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * V4L2 Capture CSI Subdev for Freescale i.MX7 SOC
+ * V4L2 Capture CSI Subdev for Freescale i.MX6UL/L / i.MX7 SOC
*
* Copyright (c) 2019 Linaro Ltd
*
@@ -765,6 +765,7 @@ static int imx7_csi_configure(struct imx7_csi *csi)
struct v4l2_pix_format *out_pix = &vdev->fmt.fmt.pix;
__u32 in_code = csi->format_mbus[IMX7_CSI_PAD_SINK].code;
u32 cr1, cr18;
+ int width = out_pix->width;
if (out_pix->field == V4L2_FIELD_INTERLACED) {
imx7_csi_deinterlace_enable(csi, true);
@@ -774,15 +775,27 @@ static int imx7_csi_configure(struct imx7_csi *csi)
imx7_csi_buf_stride_set(csi, 0);
}
- imx7_csi_set_imagpara(csi, out_pix->width, out_pix->height);
+ cr18 = imx7_csi_reg_read(csi, CSI_CSICR18);
+
+ if (!csi->is_csi2) {
+ if (out_pix->pixelformat == V4L2_PIX_FMT_UYVY ||
+ out_pix->pixelformat == V4L2_PIX_FMT_YUYV)
+ width *= 2;
+
+ imx7_csi_set_imagpara(csi, width, out_pix->height);
+
+ cr18 |= (BIT_BASEADDR_SWITCH_EN | BIT_BASEADDR_SWITCH_SEL |
+ BIT_BASEADDR_CHG_ERR_EN);
+ imx7_csi_reg_write(csi, cr18, CSI_CSICR18);
- if (!csi->is_csi2)
return 0;
+ }
+
+ imx7_csi_set_imagpara(csi, width, out_pix->height);
cr1 = imx7_csi_reg_read(csi, CSI_CSICR1);
cr1 &= ~BIT_GCLK_MODE;
- cr18 = imx7_csi_reg_read(csi, CSI_CSICR18);
cr18 &= BIT_MIPI_DATA_FORMAT_MASK;
cr18 |= BIT_DATA_FROM_MIPI;
@@ -817,11 +830,9 @@ static void imx7_csi_enable(struct imx7_csi *csi)
{
imx7_csi_sw_reset(csi);
- if (csi->is_csi2) {
- imx7_csi_dmareq_rff_enable(csi);
- imx7_csi_hw_enable_irq(csi);
- imx7_csi_hw_enable(csi);
- }
+ imx7_csi_dmareq_rff_enable(csi);
+ imx7_csi_hw_enable_irq(csi);
+ imx7_csi_hw_enable(csi);
}
static void imx7_csi_disable(struct imx7_csi *csi)
@@ -1300,6 +1311,7 @@ static int imx7_csi_remove(struct platform_device *pdev)
static const struct of_device_id imx7_csi_of_match[] = {
{ .compatible = "fsl,imx7-csi" },
+ { .compatible = "fsl,imx6ul-csi" },
{ },
};
MODULE_DEVICE_TABLE(of, imx7_csi_of_match);
diff --git a/drivers/staging/media/ipu3/ipu3-tables.h b/drivers/staging/media/ipu3/ipu3-tables.h
index a1bf3286f380..9f719c48b432 100644
--- a/drivers/staging/media/ipu3/ipu3-tables.h
+++ b/drivers/staging/media/ipu3/ipu3-tables.h
@@ -4,6 +4,8 @@
#ifndef __IPU3_TABLES_H
#define __IPU3_TABLES_H
+#include <linux/bitops.h>
+
#include "ipu3-abi.h"
#define IMGU_BDS_GRANULARITY 32 /* Downscaling granularity */
@@ -12,7 +14,7 @@
#define IMGU_SCALER_DOWNSCALE_4TAPS_LEN 128
#define IMGU_SCALER_DOWNSCALE_2TAPS_LEN 64
-#define IMGU_SCALER_FP ((u32)1 << 31) /* 1.0 in fixed point */
+#define IMGU_SCALER_FP BIT(31) /* 1.0 in fixed point */
#define IMGU_XNR3_VMEM_LUT_LEN 16
diff --git a/drivers/staging/media/ipu3/ipu3.c b/drivers/staging/media/ipu3/ipu3.c
index a7372395a101..06a61f31ca50 100644
--- a/drivers/staging/media/ipu3/ipu3.c
+++ b/drivers/staging/media/ipu3/ipu3.c
@@ -778,8 +778,7 @@ out:
static int __maybe_unused imgu_resume(struct device *dev)
{
- struct pci_dev *pci_dev = to_pci_dev(dev);
- struct imgu_device *imgu = pci_get_drvdata(pci_dev);
+ struct imgu_device *imgu = dev_get_drvdata(dev);
int r = 0;
unsigned int pipe;
diff --git a/drivers/staging/media/omap4iss/iss_video.c b/drivers/staging/media/omap4iss/iss_video.c
index c307707480f7..54144dc9f509 100644
--- a/drivers/staging/media/omap4iss/iss_video.c
+++ b/drivers/staging/media/omap4iss/iss_video.c
@@ -31,61 +31,61 @@
static struct iss_format_info formats[] = {
{ MEDIA_BUS_FMT_Y8_1X8, MEDIA_BUS_FMT_Y8_1X8,
MEDIA_BUS_FMT_Y8_1X8, MEDIA_BUS_FMT_Y8_1X8,
- V4L2_PIX_FMT_GREY, 8, "Greyscale 8 bpp", },
+ V4L2_PIX_FMT_GREY, 8, },
{ MEDIA_BUS_FMT_Y10_1X10, MEDIA_BUS_FMT_Y10_1X10,
MEDIA_BUS_FMT_Y10_1X10, MEDIA_BUS_FMT_Y8_1X8,
- V4L2_PIX_FMT_Y10, 10, "Greyscale 10 bpp", },
+ V4L2_PIX_FMT_Y10, 10, },
{ MEDIA_BUS_FMT_Y12_1X12, MEDIA_BUS_FMT_Y10_1X10,
MEDIA_BUS_FMT_Y12_1X12, MEDIA_BUS_FMT_Y8_1X8,
- V4L2_PIX_FMT_Y12, 12, "Greyscale 12 bpp", },
+ V4L2_PIX_FMT_Y12, 12, },
{ MEDIA_BUS_FMT_SBGGR8_1X8, MEDIA_BUS_FMT_SBGGR8_1X8,
MEDIA_BUS_FMT_SBGGR8_1X8, MEDIA_BUS_FMT_SBGGR8_1X8,
- V4L2_PIX_FMT_SBGGR8, 8, "BGGR Bayer 8 bpp", },
+ V4L2_PIX_FMT_SBGGR8, 8, },
{ MEDIA_BUS_FMT_SGBRG8_1X8, MEDIA_BUS_FMT_SGBRG8_1X8,
MEDIA_BUS_FMT_SGBRG8_1X8, MEDIA_BUS_FMT_SGBRG8_1X8,
- V4L2_PIX_FMT_SGBRG8, 8, "GBRG Bayer 8 bpp", },
+ V4L2_PIX_FMT_SGBRG8, 8, },
{ MEDIA_BUS_FMT_SGRBG8_1X8, MEDIA_BUS_FMT_SGRBG8_1X8,
MEDIA_BUS_FMT_SGRBG8_1X8, MEDIA_BUS_FMT_SGRBG8_1X8,
- V4L2_PIX_FMT_SGRBG8, 8, "GRBG Bayer 8 bpp", },
+ V4L2_PIX_FMT_SGRBG8, 8, },
{ MEDIA_BUS_FMT_SRGGB8_1X8, MEDIA_BUS_FMT_SRGGB8_1X8,
MEDIA_BUS_FMT_SRGGB8_1X8, MEDIA_BUS_FMT_SRGGB8_1X8,
- V4L2_PIX_FMT_SRGGB8, 8, "RGGB Bayer 8 bpp", },
+ V4L2_PIX_FMT_SRGGB8, 8, },
{ MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8,
MEDIA_BUS_FMT_SGRBG10_1X10, 0,
- V4L2_PIX_FMT_SGRBG10DPCM8, 8, "GRBG Bayer 10 bpp DPCM8", },
+ V4L2_PIX_FMT_SGRBG10DPCM8, 8, },
{ MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SBGGR8_1X8,
- V4L2_PIX_FMT_SBGGR10, 10, "BGGR Bayer 10 bpp", },
+ V4L2_PIX_FMT_SBGGR10, 10, },
{ MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10,
MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SGBRG8_1X8,
- V4L2_PIX_FMT_SGBRG10, 10, "GBRG Bayer 10 bpp", },
+ V4L2_PIX_FMT_SGBRG10, 10, },
{ MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SGRBG8_1X8,
- V4L2_PIX_FMT_SGRBG10, 10, "GRBG Bayer 10 bpp", },
+ V4L2_PIX_FMT_SGRBG10, 10, },
{ MEDIA_BUS_FMT_SRGGB10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10,
MEDIA_BUS_FMT_SRGGB10_1X10, MEDIA_BUS_FMT_SRGGB8_1X8,
- V4L2_PIX_FMT_SRGGB10, 10, "RGGB Bayer 10 bpp", },
+ V4L2_PIX_FMT_SRGGB10, 10, },
{ MEDIA_BUS_FMT_SBGGR12_1X12, MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_SBGGR12_1X12, MEDIA_BUS_FMT_SBGGR8_1X8,
- V4L2_PIX_FMT_SBGGR12, 12, "BGGR Bayer 12 bpp", },
+ V4L2_PIX_FMT_SBGGR12, 12, },
{ MEDIA_BUS_FMT_SGBRG12_1X12, MEDIA_BUS_FMT_SGBRG10_1X10,
MEDIA_BUS_FMT_SGBRG12_1X12, MEDIA_BUS_FMT_SGBRG8_1X8,
- V4L2_PIX_FMT_SGBRG12, 12, "GBRG Bayer 12 bpp", },
+ V4L2_PIX_FMT_SGBRG12, 12, },
{ MEDIA_BUS_FMT_SGRBG12_1X12, MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SGRBG12_1X12, MEDIA_BUS_FMT_SGRBG8_1X8,
- V4L2_PIX_FMT_SGRBG12, 12, "GRBG Bayer 12 bpp", },
+ V4L2_PIX_FMT_SGRBG12, 12, },
{ MEDIA_BUS_FMT_SRGGB12_1X12, MEDIA_BUS_FMT_SRGGB10_1X10,
MEDIA_BUS_FMT_SRGGB12_1X12, MEDIA_BUS_FMT_SRGGB8_1X8,
- V4L2_PIX_FMT_SRGGB12, 12, "RGGB Bayer 12 bpp", },
+ V4L2_PIX_FMT_SRGGB12, 12, },
{ MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_UYVY8_1X16,
MEDIA_BUS_FMT_UYVY8_1X16, 0,
- V4L2_PIX_FMT_UYVY, 16, "YUV 4:2:2 (UYVY)", },
+ V4L2_PIX_FMT_UYVY, 16, },
{ MEDIA_BUS_FMT_YUYV8_1X16, MEDIA_BUS_FMT_YUYV8_1X16,
MEDIA_BUS_FMT_YUYV8_1X16, 0,
- V4L2_PIX_FMT_YUYV, 16, "YUV 4:2:2 (YUYV)", },
+ V4L2_PIX_FMT_YUYV, 16, },
{ MEDIA_BUS_FMT_YUYV8_1_5X8, MEDIA_BUS_FMT_YUYV8_1_5X8,
MEDIA_BUS_FMT_YUYV8_1_5X8, 0,
- V4L2_PIX_FMT_NV12, 8, "YUV 4:2:0 (NV12)", },
+ V4L2_PIX_FMT_NV12, 8, },
};
const struct iss_format_info *
@@ -563,8 +563,6 @@ iss_video_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f)
if (index == 0) {
f->pixelformat = info->pixelformat;
- strscpy(f->description, info->description,
- sizeof(f->description));
return 0;
}
diff --git a/drivers/staging/media/omap4iss/iss_video.h b/drivers/staging/media/omap4iss/iss_video.h
index f22489edb562..8b3dd92021e1 100644
--- a/drivers/staging/media/omap4iss/iss_video.h
+++ b/drivers/staging/media/omap4iss/iss_video.h
@@ -36,7 +36,6 @@ struct v4l2_pix_format;
* shifted to be 8 bits per pixel. =0 if format is not shiftable.
* @pixelformat: V4L2 pixel format FCC identifier
* @bpp: Bits per pixel
- * @description: Human-readable format description
*/
struct iss_format_info {
u32 code;
@@ -45,7 +44,6 @@ struct iss_format_info {
u32 flavor;
u32 pixelformat;
unsigned int bpp;
- const char *description;
};
enum iss_pipeline_stream_state {
diff --git a/drivers/staging/media/soc_camera/soc_camera.c b/drivers/staging/media/soc_camera/soc_camera.c
index a6232dcd59bc..7b9448e3c9ba 100644
--- a/drivers/staging/media/soc_camera/soc_camera.c
+++ b/drivers/staging/media/soc_camera/soc_camera.c
@@ -869,8 +869,6 @@ static int soc_camera_enum_fmt_vid_cap(struct file *file, void *priv,
format = icd->user_formats[f->index].host_fmt;
- if (format->name)
- strscpy(f->description, format->name, sizeof(f->description));
f->pixelformat = format->fourcc;
return 0;
}
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.c b/drivers/staging/media/sunxi/cedrus/cedrus.c
index 370937edfc14..2d3ea8b74dfd 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.c
@@ -29,47 +29,72 @@
static const struct cedrus_control cedrus_controls[] = {
{
- .id = V4L2_CID_MPEG_VIDEO_MPEG2_SLICE_PARAMS,
- .elem_size = sizeof(struct v4l2_ctrl_mpeg2_slice_params),
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG2_SLICE_PARAMS,
+ },
.codec = CEDRUS_CODEC_MPEG2,
.required = true,
},
{
- .id = V4L2_CID_MPEG_VIDEO_MPEG2_QUANTIZATION,
- .elem_size = sizeof(struct v4l2_ctrl_mpeg2_quantization),
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG2_QUANTIZATION,
+ },
.codec = CEDRUS_CODEC_MPEG2,
.required = false,
},
{
- .id = V4L2_CID_MPEG_VIDEO_H264_DECODE_PARAMS,
- .elem_size = sizeof(struct v4l2_ctrl_h264_decode_params),
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_DECODE_PARAMS,
+ },
.codec = CEDRUS_CODEC_H264,
.required = true,
},
{
- .id = V4L2_CID_MPEG_VIDEO_H264_SLICE_PARAMS,
- .elem_size = sizeof(struct v4l2_ctrl_h264_slice_params),
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_SLICE_PARAMS,
+ },
.codec = CEDRUS_CODEC_H264,
.required = true,
},
{
- .id = V4L2_CID_MPEG_VIDEO_H264_SPS,
- .elem_size = sizeof(struct v4l2_ctrl_h264_sps),
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_SPS,
+ },
.codec = CEDRUS_CODEC_H264,
.required = true,
},
{
- .id = V4L2_CID_MPEG_VIDEO_H264_PPS,
- .elem_size = sizeof(struct v4l2_ctrl_h264_pps),
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_PPS,
+ },
.codec = CEDRUS_CODEC_H264,
.required = true,
},
{
- .id = V4L2_CID_MPEG_VIDEO_H264_SCALING_MATRIX,
- .elem_size = sizeof(struct v4l2_ctrl_h264_scaling_matrix),
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_SCALING_MATRIX,
+ },
.codec = CEDRUS_CODEC_H264,
.required = true,
},
+ {
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_DECODE_MODE,
+ .max = V4L2_MPEG_VIDEO_H264_DECODE_MODE_SLICE_BASED,
+ .def = V4L2_MPEG_VIDEO_H264_DECODE_MODE_SLICE_BASED,
+ },
+ .codec = CEDRUS_CODEC_H264,
+ .required = false,
+ },
+ {
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_START_CODE,
+ .max = V4L2_MPEG_VIDEO_H264_START_CODE_NONE,
+ .def = V4L2_MPEG_VIDEO_H264_START_CODE_NONE,
+ },
+ .codec = CEDRUS_CODEC_H264,
+ .required = false,
+ },
};
#define CEDRUS_CONTROLS_COUNT ARRAY_SIZE(cedrus_controls)
@@ -106,12 +131,8 @@ static int cedrus_init_ctrls(struct cedrus_dev *dev, struct cedrus_ctx *ctx)
return -ENOMEM;
for (i = 0; i < CEDRUS_CONTROLS_COUNT; i++) {
- struct v4l2_ctrl_config cfg = {};
-
- cfg.elem_size = cedrus_controls[i].elem_size;
- cfg.id = cedrus_controls[i].id;
-
- ctrl = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+ ctrl = v4l2_ctrl_new_custom(hdl, &cedrus_controls[i].cfg,
+ NULL);
if (hdl->error) {
v4l2_err(&dev->v4l2_dev,
"Failed to create new custom control\n");
@@ -178,7 +199,7 @@ static int cedrus_request_validate(struct media_request *req)
continue;
ctrl_test = v4l2_ctrl_request_hdl_ctrl_find(hdl,
- cedrus_controls[i].id);
+ cedrus_controls[i].cfg.id);
if (!ctrl_test) {
v4l2_info(&ctx->dev->v4l2_dev,
"Missing required codec control\n");
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus.h b/drivers/staging/media/sunxi/cedrus/cedrus.h
index 3f476d0fd981..2f017a651848 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus.h
@@ -49,8 +49,7 @@ enum cedrus_h264_pic_type {
};
struct cedrus_control {
- u32 id;
- u32 elem_size;
+ struct v4l2_ctrl_config cfg;
enum cedrus_codec codec;
unsigned char required:1;
};
@@ -100,8 +99,6 @@ struct cedrus_ctx {
struct v4l2_ctrl_handler hdl;
struct v4l2_ctrl **ctrls;
- struct vb2_buffer *dst_bufs[VIDEO_MAX_FRAME];
-
union {
struct {
void *mv_col_buf;
@@ -187,7 +184,7 @@ static inline dma_addr_t cedrus_dst_buf_addr(struct cedrus_ctx *ctx,
if (index < 0)
return 0;
- buf = ctx->dst_bufs[index];
+ buf = ctx->fh.m2m_ctx->cap_q_ctx.q.bufs[index];
return buf ? cedrus_buf_addr(buf, &ctx->dst_fmt, plane) : 0;
}
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_dec.c b/drivers/staging/media/sunxi/cedrus/cedrus_dec.c
index bdad87eb9d79..56ca4c9ad01c 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_dec.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_dec.c
@@ -46,7 +46,7 @@ void cedrus_device_run(void *priv)
V4L2_CID_MPEG_VIDEO_MPEG2_QUANTIZATION);
break;
- case V4L2_PIX_FMT_H264_SLICE_RAW:
+ case V4L2_PIX_FMT_H264_SLICE:
run.h264.decode_params = cedrus_find_control_data(ctx,
V4L2_CID_MPEG_VIDEO_H264_DECODE_PARAMS);
run.h264.pps = cedrus_find_control_data(ctx,
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
index a30bb283f69f..d6a782703c9b 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_h264.c
@@ -118,7 +118,7 @@ static void cedrus_write_frame_list(struct cedrus_ctx *ctx,
if (buf_idx < 0)
continue;
- cedrus_buf = vb2_to_cedrus_buffer(ctx->dst_bufs[buf_idx]);
+ cedrus_buf = vb2_to_cedrus_buffer(cap_q->bufs[buf_idx]);
position = cedrus_buf->codec.h264.position;
used_dpbs |= BIT(position);
@@ -193,7 +193,7 @@ static void _cedrus_write_ref_list(struct cedrus_ctx *ctx,
if (buf_idx < 0)
continue;
- ref_buf = to_vb2_v4l2_buffer(ctx->dst_bufs[buf_idx]);
+ ref_buf = to_vb2_v4l2_buffer(cap_q->bufs[buf_idx]);
cedrus_buf = vb2_v4l2_to_cedrus_buffer(ref_buf);
position = cedrus_buf->codec.h264.position;
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
index 9ce3a65903c5..a942cd9bed57 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c
@@ -79,9 +79,6 @@ void cedrus_dst_format_set(struct cedrus_dev *dev,
reg = VE_PRIMARY_OUT_FMT_NV12;
cedrus_write(dev, VE_PRIMARY_OUT_FMT, reg);
- reg = VE_CHROMA_BUF_LEN_SDRT(chroma_size / 2);
- cedrus_write(dev, VE_CHROMA_BUF_LEN, reg);
-
reg = chroma_size / 2;
cedrus_write(dev, VE_PRIMARY_CHROMA_BUF_LEN, reg);
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
index 3e9931416e45..ddd29788d685 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_regs.h
@@ -110,7 +110,7 @@
#define VE_DEC_MPEG_MBADDR (VE_ENGINE_DEC_MPEG + 0x10)
#define VE_DEC_MPEG_MBADDR_X(w) (((w) << 8) & GENMASK(15, 8))
-#define VE_DEC_MPEG_MBADDR_Y(h) (((h) << 0) & GENMASK(0, 7))
+#define VE_DEC_MPEG_MBADDR_Y(h) (((h) << 0) & GENMASK(7, 0))
#define VE_DEC_MPEG_CTRL (VE_ENGINE_DEC_MPEG + 0x14)
diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
index e2b530b1a956..eeee3efd247b 100644
--- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c
+++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c
@@ -38,7 +38,7 @@ static struct cedrus_format cedrus_formats[] = {
.directions = CEDRUS_DECODE_SRC,
},
{
- .pixelformat = V4L2_PIX_FMT_H264_SLICE_RAW,
+ .pixelformat = V4L2_PIX_FMT_H264_SLICE,
.directions = CEDRUS_DECODE_SRC,
},
{
@@ -104,7 +104,7 @@ static void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt)
switch (pix_fmt->pixelformat) {
case V4L2_PIX_FMT_MPEG2_SLICE:
- case V4L2_PIX_FMT_H264_SLICE_RAW:
+ case V4L2_PIX_FMT_H264_SLICE:
/* Zero bytes per line for encoded source. */
bytesperline = 0;
@@ -411,26 +411,6 @@ static void cedrus_queue_cleanup(struct vb2_queue *vq, u32 state)
}
}
-static int cedrus_buf_init(struct vb2_buffer *vb)
-{
- struct vb2_queue *vq = vb->vb2_queue;
- struct cedrus_ctx *ctx = vb2_get_drv_priv(vq);
-
- if (!V4L2_TYPE_IS_OUTPUT(vq->type))
- ctx->dst_bufs[vb->index] = vb;
-
- return 0;
-}
-
-static void cedrus_buf_cleanup(struct vb2_buffer *vb)
-{
- struct vb2_queue *vq = vb->vb2_queue;
- struct cedrus_ctx *ctx = vb2_get_drv_priv(vq);
-
- if (!V4L2_TYPE_IS_OUTPUT(vq->type))
- ctx->dst_bufs[vb->index] = NULL;
-}
-
static int cedrus_buf_out_validate(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
@@ -469,7 +449,7 @@ static int cedrus_start_streaming(struct vb2_queue *vq, unsigned int count)
ctx->current_codec = CEDRUS_CODEC_MPEG2;
break;
- case V4L2_PIX_FMT_H264_SLICE_RAW:
+ case V4L2_PIX_FMT_H264_SLICE:
ctx->current_codec = CEDRUS_CODEC_H264;
break;
@@ -517,8 +497,6 @@ static void cedrus_buf_request_complete(struct vb2_buffer *vb)
static struct vb2_ops cedrus_qops = {
.queue_setup = cedrus_queue_setup,
.buf_prepare = cedrus_buf_prepare,
- .buf_init = cedrus_buf_init,
- .buf_cleanup = cedrus_buf_cleanup,
.buf_queue = cedrus_buf_queue,
.buf_out_validate = cedrus_buf_out_validate,
.buf_request_complete = cedrus_buf_request_complete,
diff --git a/drivers/staging/media/tegra-vde/Kconfig b/drivers/staging/media/tegra-vde/Kconfig
index 2e7f644ae591..ba49ea50b8c0 100644
--- a/drivers/staging/media/tegra-vde/Kconfig
+++ b/drivers/staging/media/tegra-vde/Kconfig
@@ -3,7 +3,7 @@ config TEGRA_VDE
tristate "NVIDIA Tegra Video Decoder Engine driver"
depends on ARCH_TEGRA || COMPILE_TEST
select DMA_SHARED_BUFFER
- select IOMMU_IOVA if IOMMU_SUPPORT
+ select IOMMU_IOVA if (IOMMU_SUPPORT || COMPILE_TEST)
select SRAM
help
Say Y here to enable support for the NVIDIA Tegra video decoder
diff --git a/drivers/staging/octeon-usb/octeon-hcd.c b/drivers/staging/octeon-usb/octeon-hcd.c
index cd2b777073c4..a5321cc692c5 100644
--- a/drivers/staging/octeon-usb/octeon-hcd.c
+++ b/drivers/staging/octeon-usb/octeon-hcd.c
@@ -3512,7 +3512,7 @@ static const struct hc_driver octeon_hc_driver = {
.product_desc = "Octeon Host Controller",
.hcd_priv_size = sizeof(struct octeon_hcd),
.irq = octeon_usb_irq,
- .flags = HCD_MEMORY | HCD_USB2,
+ .flags = HCD_MEMORY | HCD_DMA | HCD_USB2,
.start = octeon_usb_start,
.stop = octeon_usb_stop,
.urb_enqueue = octeon_usb_urb_enqueue,
diff --git a/drivers/staging/uwb/Kconfig b/drivers/staging/uwb/Kconfig
new file mode 100644
index 000000000000..259e053e1e09
--- /dev/null
+++ b/drivers/staging/uwb/Kconfig
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# UWB device configuration
+#
+
+menuconfig UWB
+ tristate "Ultra Wideband devices"
+ default n
+ select GENERIC_NET_UTILS
+ help
+ UWB is a high-bandwidth, low-power, point-to-point radio
+ technology using a wide spectrum (3.1-10.6GHz). It is
+ optimized for in-room use (480Mbps at 2 meters, 110Mbps at
+ 10m). It serves as the transport layer for other protocols,
+ such as Wireless USB (WUSB).
+
+ The topology is peer to peer; however, higher level
+ protocols (such as WUSB) might impose a master/slave
+ relationship.
+
+ Say Y here if your computer has UWB radio controllers (USB or PCI)
+ based. You will need to enable the radio controllers
+ below. It is ok to select all of them, no harm done.
+
+ For more help check the UWB and WUSB related files in
+ <file:Documentation/usb/>.
+
+ To compile the UWB stack as a module, choose M here.
+
+if UWB
+
+config UWB_HWA
+ tristate "UWB Radio Control driver for WUSB-compliant USB dongles (HWA)"
+ depends on USB
+ help
+ This driver enables the radio controller for HWA USB
+ devices. HWA stands for Host Wire Adapter, and it is a UWB
+ Radio Controller connected to your system via USB. Most of
+ them come with a Wireless USB host controller also.
+
+ To compile this driver select Y (built in) or M (module). It
+ is safe to select any even if you do not have the hardware.
+
+config UWB_WHCI
+ tristate "UWB Radio Control driver for WHCI-compliant cards"
+ depends on PCI
+ help
+ This driver enables the radio controller for WHCI cards.
+
+ WHCI is a specification developed by Intel
+ (http://www.intel.com/technology/comms/wusb/whci.htm) much
+ in the spirit of USB's EHCI, but for UWB and Wireless USB
+ radio/host controllers connected via memory mapping (eg:
+ PCI). Most of these cards come also with a Wireless USB host
+ controller.
+
+ To compile this driver select Y (built in) or M (module). It
+ is safe to select any even if you do not have the hardware.
+
+config UWB_I1480U
+ tristate "Support for Intel Wireless UWB Link 1480 HWA"
+ depends on UWB_HWA
+ select FW_LOADER
+ help
+ This driver enables support for the i1480 when connected via
+ USB. It consists of a firmware uploader that will enable it
+ to behave as an HWA device.
+
+ To compile this driver select Y (built in) or M (module). It
+ is safe to select any even if you do not have the hardware.
+
+endif # UWB
diff --git a/drivers/staging/uwb/Makefile b/drivers/staging/uwb/Makefile
new file mode 100644
index 000000000000..32f4de7afbd6
--- /dev/null
+++ b/drivers/staging/uwb/Makefile
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_UWB) += uwb.o
+obj-$(CONFIG_UWB_WHCI) += umc.o whci.o whc-rc.o
+obj-$(CONFIG_UWB_HWA) += hwa-rc.o
+obj-$(CONFIG_UWB_I1480U) += i1480/
+
+uwb-objs := \
+ address.o \
+ allocator.o \
+ beacon.o \
+ driver.o \
+ drp.o \
+ drp-avail.o \
+ drp-ie.o \
+ est.o \
+ ie.o \
+ ie-rcv.o \
+ lc-dev.o \
+ lc-rc.o \
+ neh.o \
+ pal.o \
+ radio.o \
+ reset.o \
+ rsv.o \
+ scan.o \
+ uwb-debug.o \
+ uwbd.o
+
+umc-objs := \
+ umc-bus.o \
+ umc-dev.o \
+ umc-drv.o
diff --git a/drivers/staging/uwb/TODO b/drivers/staging/uwb/TODO
new file mode 100644
index 000000000000..abae57000534
--- /dev/null
+++ b/drivers/staging/uwb/TODO
@@ -0,0 +1,8 @@
+TODO: Remove in late 2019 unless there are users
+
+There seems to not be any real wireless USB devices anywhere in the wild
+anymore. It turned out to be a failed technology :(
+
+This will be removed from the tree if no one objects.
+
+Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/staging/uwb/address.c b/drivers/staging/uwb/address.c
new file mode 100644
index 000000000000..857d5cd56a95
--- /dev/null
+++ b/drivers/staging/uwb/address.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Address management
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ */
+
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/random.h>
+#include <linux/etherdevice.h>
+
+#include "uwb-internal.h"
+
+
+/** Device Address Management command */
+struct uwb_rc_cmd_dev_addr_mgmt {
+ struct uwb_rccb rccb;
+ u8 bmOperationType;
+ u8 baAddr[6];
+} __attribute__((packed));
+
+
+/**
+ * Low level command for setting/getting UWB radio's addresses
+ *
+ * @hwarc: HWA Radio Control interface instance
+ * @bmOperationType:
+ * Set/get, MAC/DEV (see WUSB1.0[8.6.2.2])
+ * @baAddr: address buffer--assumed to have enough data to hold
+ * the address type requested.
+ * @reply: Pointer to reply buffer (can be stack allocated)
+ * @returns: 0 if ok, < 0 errno code on error.
+ *
+ * @cmd has to be allocated because USB cannot grok USB or vmalloc
+ * buffers depending on your combination of host architecture.
+ */
+static
+int uwb_rc_dev_addr_mgmt(struct uwb_rc *rc,
+ u8 bmOperationType, const u8 *baAddr,
+ struct uwb_rc_evt_dev_addr_mgmt *reply)
+{
+ int result;
+ struct uwb_rc_cmd_dev_addr_mgmt *cmd;
+
+ result = -ENOMEM;
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+ if (cmd == NULL)
+ goto error_kzalloc;
+ cmd->rccb.bCommandType = UWB_RC_CET_GENERAL;
+ cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_DEV_ADDR_MGMT);
+ cmd->bmOperationType = bmOperationType;
+ if (baAddr) {
+ size_t size = 0;
+ switch (bmOperationType >> 1) {
+ case 0: size = 2; break;
+ case 1: size = 6; break;
+ default: BUG();
+ }
+ memcpy(cmd->baAddr, baAddr, size);
+ }
+ reply->rceb.bEventType = UWB_RC_CET_GENERAL;
+ reply->rceb.wEvent = UWB_RC_CMD_DEV_ADDR_MGMT;
+ result = uwb_rc_cmd(rc, "DEV-ADDR-MGMT",
+ &cmd->rccb, sizeof(*cmd),
+ &reply->rceb, sizeof(*reply));
+ if (result < 0)
+ goto error_cmd;
+ if (result < sizeof(*reply)) {
+ dev_err(&rc->uwb_dev.dev,
+ "DEV-ADDR-MGMT: not enough data replied: "
+ "%d vs %zu bytes needed\n", result, sizeof(*reply));
+ result = -ENOMSG;
+ } else if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(&rc->uwb_dev.dev,
+ "DEV-ADDR-MGMT: command execution failed: %s (%d)\n",
+ uwb_rc_strerror(reply->bResultCode),
+ reply->bResultCode);
+ result = -EIO;
+ } else
+ result = 0;
+error_cmd:
+ kfree(cmd);
+error_kzalloc:
+ return result;
+}
+
+
+/**
+ * Set the UWB RC MAC or device address.
+ *
+ * @rc: UWB Radio Controller
+ * @_addr: Pointer to address to write [assumed to be either a
+ * 'struct uwb_mac_addr *' or a 'struct uwb_dev_addr *'].
+ * @type: Type of address to set (UWB_ADDR_DEV or UWB_ADDR_MAC).
+ * @returns: 0 if ok, < 0 errno code on error.
+ *
+ * Some anal retentivity here: even if both 'struct
+ * uwb_{dev,mac}_addr' have the actual byte array in the same offset
+ * and I could just pass _addr to hwarc_cmd_dev_addr_mgmt(), I prefer
+ * to use some syntatic sugar in case someday we decide to change the
+ * format of the structs. The compiler will optimize it out anyway.
+ */
+static int uwb_rc_addr_set(struct uwb_rc *rc,
+ const void *_addr, enum uwb_addr_type type)
+{
+ int result;
+ u8 bmOperationType = 0x1; /* Set address */
+ const struct uwb_dev_addr *dev_addr = _addr;
+ const struct uwb_mac_addr *mac_addr = _addr;
+ struct uwb_rc_evt_dev_addr_mgmt reply;
+ const u8 *baAddr;
+
+ result = -EINVAL;
+ switch (type) {
+ case UWB_ADDR_DEV:
+ baAddr = dev_addr->data;
+ break;
+ case UWB_ADDR_MAC:
+ baAddr = mac_addr->data;
+ bmOperationType |= 0x2;
+ break;
+ default:
+ return result;
+ }
+ return uwb_rc_dev_addr_mgmt(rc, bmOperationType, baAddr, &reply);
+}
+
+
+/**
+ * Get the UWB radio's MAC or device address.
+ *
+ * @rc: UWB Radio Controller
+ * @_addr: Where to write the address data [assumed to be either a
+ * 'struct uwb_mac_addr *' or a 'struct uwb_dev_addr *'].
+ * @type: Type of address to get (UWB_ADDR_DEV or UWB_ADDR_MAC).
+ * @returns: 0 if ok (and *_addr set), < 0 errno code on error.
+ *
+ * See comment in uwb_rc_addr_set() about anal retentivity in the
+ * type handling of the address variables.
+ */
+static int uwb_rc_addr_get(struct uwb_rc *rc,
+ void *_addr, enum uwb_addr_type type)
+{
+ int result;
+ u8 bmOperationType = 0x0; /* Get address */
+ struct uwb_rc_evt_dev_addr_mgmt evt;
+ struct uwb_dev_addr *dev_addr = _addr;
+ struct uwb_mac_addr *mac_addr = _addr;
+ u8 *baAddr;
+
+ result = -EINVAL;
+ switch (type) {
+ case UWB_ADDR_DEV:
+ baAddr = dev_addr->data;
+ break;
+ case UWB_ADDR_MAC:
+ bmOperationType |= 0x2;
+ baAddr = mac_addr->data;
+ break;
+ default:
+ return result;
+ }
+ result = uwb_rc_dev_addr_mgmt(rc, bmOperationType, baAddr, &evt);
+ if (result == 0)
+ switch (type) {
+ case UWB_ADDR_DEV:
+ memcpy(&dev_addr->data, evt.baAddr,
+ sizeof(dev_addr->data));
+ break;
+ case UWB_ADDR_MAC:
+ memcpy(&mac_addr->data, evt.baAddr,
+ sizeof(mac_addr->data));
+ break;
+ default: /* shut gcc up */
+ BUG();
+ }
+ return result;
+}
+
+
+/** Get @rc's MAC address to @addr */
+int uwb_rc_mac_addr_get(struct uwb_rc *rc,
+ struct uwb_mac_addr *addr) {
+ return uwb_rc_addr_get(rc, addr, UWB_ADDR_MAC);
+}
+EXPORT_SYMBOL_GPL(uwb_rc_mac_addr_get);
+
+
+/** Get @rc's device address to @addr */
+int uwb_rc_dev_addr_get(struct uwb_rc *rc,
+ struct uwb_dev_addr *addr) {
+ return uwb_rc_addr_get(rc, addr, UWB_ADDR_DEV);
+}
+EXPORT_SYMBOL_GPL(uwb_rc_dev_addr_get);
+
+
+/** Set @rc's address to @addr */
+int uwb_rc_mac_addr_set(struct uwb_rc *rc,
+ const struct uwb_mac_addr *addr)
+{
+ int result = -EINVAL;
+ mutex_lock(&rc->uwb_dev.mutex);
+ result = uwb_rc_addr_set(rc, addr, UWB_ADDR_MAC);
+ mutex_unlock(&rc->uwb_dev.mutex);
+ return result;
+}
+
+
+/** Set @rc's address to @addr */
+int uwb_rc_dev_addr_set(struct uwb_rc *rc,
+ const struct uwb_dev_addr *addr)
+{
+ int result = -EINVAL;
+ mutex_lock(&rc->uwb_dev.mutex);
+ result = uwb_rc_addr_set(rc, addr, UWB_ADDR_DEV);
+ rc->uwb_dev.dev_addr = *addr;
+ mutex_unlock(&rc->uwb_dev.mutex);
+ return result;
+}
+
+/* Returns !0 if given address is already assigned to device. */
+int __uwb_mac_addr_assigned_check(struct device *dev, void *_addr)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_mac_addr *addr = _addr;
+
+ if (!uwb_mac_addr_cmp(addr, &uwb_dev->mac_addr))
+ return !0;
+ return 0;
+}
+
+/* Returns !0 if given address is already assigned to device. */
+int __uwb_dev_addr_assigned_check(struct device *dev, void *_addr)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_dev_addr *addr = _addr;
+ if (!uwb_dev_addr_cmp(addr, &uwb_dev->dev_addr))
+ return !0;
+ return 0;
+}
+
+/**
+ * uwb_dev_addr_assign - assigned a generated DevAddr to a radio controller
+ * @rc: the (local) radio controller device requiring a new DevAddr
+ *
+ * A new DevAddr is required when:
+ * - first setting up a radio controller
+ * - if the hardware reports a DevAddr conflict
+ *
+ * The DevAddr is randomly generated in the generated DevAddr range
+ * [0x100, 0xfeff]. The number of devices in a beacon group is limited
+ * by mMaxBPLength (96) so this address space will never be exhausted.
+ *
+ * [ECMA-368] 17.1.1, 17.16.
+ */
+int uwb_rc_dev_addr_assign(struct uwb_rc *rc)
+{
+ struct uwb_dev_addr new_addr;
+
+ do {
+ get_random_bytes(new_addr.data, sizeof(new_addr.data));
+ } while (new_addr.data[0] == 0x00 || new_addr.data[0] == 0xff
+ || __uwb_dev_addr_assigned(rc, &new_addr));
+
+ return uwb_rc_dev_addr_set(rc, &new_addr);
+}
+
+/**
+ * uwbd_evt_handle_rc_dev_addr_conflict - handle a DEV_ADDR_CONFLICT event
+ * @evt: the DEV_ADDR_CONFLICT notification from the radio controller
+ *
+ * A new (non-conflicting) DevAddr is assigned to the radio controller.
+ *
+ * [ECMA-368] 17.1.1.1.
+ */
+int uwbd_evt_handle_rc_dev_addr_conflict(struct uwb_event *evt)
+{
+ struct uwb_rc *rc = evt->rc;
+
+ return uwb_rc_dev_addr_assign(rc);
+}
+
+/*
+ * Print the 48-bit EUI MAC address of the radio controller when
+ * reading /sys/class/uwb_rc/XX/mac_address
+ */
+static ssize_t uwb_rc_mac_addr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_rc *rc = uwb_dev->rc;
+ struct uwb_mac_addr addr;
+ ssize_t result;
+
+ mutex_lock(&rc->uwb_dev.mutex);
+ result = uwb_rc_addr_get(rc, &addr, UWB_ADDR_MAC);
+ mutex_unlock(&rc->uwb_dev.mutex);
+ if (result >= 0) {
+ result = uwb_mac_addr_print(buf, UWB_ADDR_STRSIZE, &addr);
+ buf[result++] = '\n';
+ }
+ return result;
+}
+
+/*
+ * Parse a 48 bit address written to /sys/class/uwb_rc/XX/mac_address
+ * and if correct, set it.
+ */
+static ssize_t uwb_rc_mac_addr_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_rc *rc = uwb_dev->rc;
+ struct uwb_mac_addr addr;
+ ssize_t result;
+
+ if (!mac_pton(buf, addr.data))
+ return -EINVAL;
+ if (is_multicast_ether_addr(addr.data)) {
+ dev_err(&rc->uwb_dev.dev, "refusing to set multicast "
+ "MAC address %s\n", buf);
+ return -EINVAL;
+ }
+ result = uwb_rc_mac_addr_set(rc, &addr);
+ if (result == 0)
+ rc->uwb_dev.mac_addr = addr;
+
+ return result < 0 ? result : size;
+}
+DEVICE_ATTR(mac_address, S_IRUGO | S_IWUSR, uwb_rc_mac_addr_show, uwb_rc_mac_addr_store);
+
+/** Print @addr to @buf, @return bytes written */
+size_t __uwb_addr_print(char *buf, size_t buf_size, const unsigned char *addr,
+ int type)
+{
+ size_t result;
+ if (type)
+ result = scnprintf(buf, buf_size, "%pM", addr);
+ else
+ result = scnprintf(buf, buf_size, "%02x:%02x",
+ addr[1], addr[0]);
+ return result;
+}
+EXPORT_SYMBOL_GPL(__uwb_addr_print);
diff --git a/drivers/staging/uwb/allocator.c b/drivers/staging/uwb/allocator.c
new file mode 100644
index 000000000000..1f429fba20b7
--- /dev/null
+++ b/drivers/staging/uwb/allocator.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UWB reservation management.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include "uwb.h"
+
+#include "uwb-internal.h"
+
+static void uwb_rsv_fill_column_alloc(struct uwb_rsv_alloc_info *ai)
+{
+ int col, mas, safe_mas, unsafe_mas;
+ unsigned char *bm = ai->bm;
+ struct uwb_rsv_col_info *ci = ai->ci;
+ unsigned char c;
+
+ for (col = ci->csi.start_col; col < UWB_NUM_ZONES; col += ci->csi.interval) {
+
+ safe_mas = ci->csi.safe_mas_per_col;
+ unsafe_mas = ci->csi.unsafe_mas_per_col;
+
+ for (mas = 0; mas < UWB_MAS_PER_ZONE; mas++ ) {
+ if (bm[col * UWB_MAS_PER_ZONE + mas] == 0) {
+
+ if (safe_mas > 0) {
+ safe_mas--;
+ c = UWB_RSV_MAS_SAFE;
+ } else if (unsafe_mas > 0) {
+ unsafe_mas--;
+ c = UWB_RSV_MAS_UNSAFE;
+ } else {
+ break;
+ }
+ bm[col * UWB_MAS_PER_ZONE + mas] = c;
+ }
+ }
+ }
+}
+
+static void uwb_rsv_fill_row_alloc(struct uwb_rsv_alloc_info *ai)
+{
+ int mas, col, rows;
+ unsigned char *bm = ai->bm;
+ struct uwb_rsv_row_info *ri = &ai->ri;
+ unsigned char c;
+
+ rows = 1;
+ c = UWB_RSV_MAS_SAFE;
+ for (mas = UWB_MAS_PER_ZONE - 1; mas >= 0; mas--) {
+ if (ri->avail[mas] == 1) {
+
+ if (rows > ri->used_rows) {
+ break;
+ } else if (rows > 7) {
+ c = UWB_RSV_MAS_UNSAFE;
+ }
+
+ for (col = 0; col < UWB_NUM_ZONES; col++) {
+ if (bm[col * UWB_NUM_ZONES + mas] != UWB_RSV_MAS_NOT_AVAIL) {
+ bm[col * UWB_NUM_ZONES + mas] = c;
+ if(c == UWB_RSV_MAS_SAFE)
+ ai->safe_allocated_mases++;
+ else
+ ai->unsafe_allocated_mases++;
+ }
+ }
+ rows++;
+ }
+ }
+ ai->total_allocated_mases = ai->safe_allocated_mases + ai->unsafe_allocated_mases;
+}
+
+/*
+ * Find the best column set for a given availability, interval, num safe mas and
+ * num unsafe mas.
+ *
+ * The different sets are tried in order as shown below, depending on the interval.
+ *
+ * interval = 16
+ * deep = 0
+ * set 1 -> { 8 }
+ * deep = 1
+ * set 1 -> { 4 }
+ * set 2 -> { 12 }
+ * deep = 2
+ * set 1 -> { 2 }
+ * set 2 -> { 6 }
+ * set 3 -> { 10 }
+ * set 4 -> { 14 }
+ * deep = 3
+ * set 1 -> { 1 }
+ * set 2 -> { 3 }
+ * set 3 -> { 5 }
+ * set 4 -> { 7 }
+ * set 5 -> { 9 }
+ * set 6 -> { 11 }
+ * set 7 -> { 13 }
+ * set 8 -> { 15 }
+ *
+ * interval = 8
+ * deep = 0
+ * set 1 -> { 4 12 }
+ * deep = 1
+ * set 1 -> { 2 10 }
+ * set 2 -> { 6 14 }
+ * deep = 2
+ * set 1 -> { 1 9 }
+ * set 2 -> { 3 11 }
+ * set 3 -> { 5 13 }
+ * set 4 -> { 7 15 }
+ *
+ * interval = 4
+ * deep = 0
+ * set 1 -> { 2 6 10 14 }
+ * deep = 1
+ * set 1 -> { 1 5 9 13 }
+ * set 2 -> { 3 7 11 15 }
+ *
+ * interval = 2
+ * deep = 0
+ * set 1 -> { 1 3 5 7 9 11 13 15 }
+ */
+static int uwb_rsv_find_best_column_set(struct uwb_rsv_alloc_info *ai, int interval,
+ int num_safe_mas, int num_unsafe_mas)
+{
+ struct uwb_rsv_col_info *ci = ai->ci;
+ struct uwb_rsv_col_set_info *csi = &ci->csi;
+ struct uwb_rsv_col_set_info tmp_csi;
+ int deep, set, col, start_col_deep, col_start_set;
+ int start_col, max_mas_in_set, lowest_max_mas_in_deep;
+ int n_mas;
+ int found = UWB_RSV_ALLOC_NOT_FOUND;
+
+ tmp_csi.start_col = 0;
+ start_col_deep = interval;
+ n_mas = num_unsafe_mas + num_safe_mas;
+
+ for (deep = 0; ((interval >> deep) & 0x1) == 0; deep++) {
+ start_col_deep /= 2;
+ col_start_set = 0;
+ lowest_max_mas_in_deep = UWB_MAS_PER_ZONE;
+
+ for (set = 1; set <= (1 << deep); set++) {
+ max_mas_in_set = 0;
+ start_col = start_col_deep + col_start_set;
+ for (col = start_col; col < UWB_NUM_ZONES; col += interval) {
+
+ if (ci[col].max_avail_safe >= num_safe_mas &&
+ ci[col].max_avail_unsafe >= n_mas) {
+ if (ci[col].highest_mas[n_mas] > max_mas_in_set)
+ max_mas_in_set = ci[col].highest_mas[n_mas];
+ } else {
+ max_mas_in_set = 0;
+ break;
+ }
+ }
+ if ((lowest_max_mas_in_deep > max_mas_in_set) && max_mas_in_set) {
+ lowest_max_mas_in_deep = max_mas_in_set;
+
+ tmp_csi.start_col = start_col;
+ }
+ col_start_set += (interval >> deep);
+ }
+
+ if (lowest_max_mas_in_deep < 8) {
+ csi->start_col = tmp_csi.start_col;
+ found = UWB_RSV_ALLOC_FOUND;
+ break;
+ } else if ((lowest_max_mas_in_deep > 8) &&
+ (lowest_max_mas_in_deep != UWB_MAS_PER_ZONE) &&
+ (found == UWB_RSV_ALLOC_NOT_FOUND)) {
+ csi->start_col = tmp_csi.start_col;
+ found = UWB_RSV_ALLOC_FOUND;
+ }
+ }
+
+ if (found == UWB_RSV_ALLOC_FOUND) {
+ csi->interval = interval;
+ csi->safe_mas_per_col = num_safe_mas;
+ csi->unsafe_mas_per_col = num_unsafe_mas;
+
+ ai->safe_allocated_mases = (UWB_NUM_ZONES / interval) * num_safe_mas;
+ ai->unsafe_allocated_mases = (UWB_NUM_ZONES / interval) * num_unsafe_mas;
+ ai->total_allocated_mases = ai->safe_allocated_mases + ai->unsafe_allocated_mases;
+ ai->interval = interval;
+ }
+ return found;
+}
+
+static void get_row_descriptors(struct uwb_rsv_alloc_info *ai)
+{
+ unsigned char *bm = ai->bm;
+ struct uwb_rsv_row_info *ri = &ai->ri;
+ int col, mas;
+
+ ri->free_rows = 16;
+ for (mas = 0; mas < UWB_MAS_PER_ZONE; mas ++) {
+ ri->avail[mas] = 1;
+ for (col = 1; col < UWB_NUM_ZONES; col++) {
+ if (bm[col * UWB_NUM_ZONES + mas] == UWB_RSV_MAS_NOT_AVAIL) {
+ ri->free_rows--;
+ ri->avail[mas]=0;
+ break;
+ }
+ }
+ }
+}
+
+static void uwb_rsv_fill_column_info(unsigned char *bm, int column, struct uwb_rsv_col_info *rci)
+{
+ int mas;
+ int block_count = 0, start_block = 0;
+ int previous_avail = 0;
+ int available = 0;
+ int safe_mas_in_row[UWB_MAS_PER_ZONE] = {
+ 8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 2, 1,
+ };
+
+ rci->max_avail_safe = 0;
+
+ for (mas = 0; mas < UWB_MAS_PER_ZONE; mas ++) {
+ if (!bm[column * UWB_NUM_ZONES + mas]) {
+ available++;
+ rci->max_avail_unsafe = available;
+
+ rci->highest_mas[available] = mas;
+
+ if (previous_avail) {
+ block_count++;
+ if ((block_count > safe_mas_in_row[start_block]) &&
+ (!rci->max_avail_safe))
+ rci->max_avail_safe = available - 1;
+ } else {
+ previous_avail = 1;
+ start_block = mas;
+ block_count = 1;
+ }
+ } else {
+ previous_avail = 0;
+ }
+ }
+ if (!rci->max_avail_safe)
+ rci->max_avail_safe = rci->max_avail_unsafe;
+}
+
+static void get_column_descriptors(struct uwb_rsv_alloc_info *ai)
+{
+ unsigned char *bm = ai->bm;
+ struct uwb_rsv_col_info *ci = ai->ci;
+ int col;
+
+ for (col = 1; col < UWB_NUM_ZONES; col++) {
+ uwb_rsv_fill_column_info(bm, col, &ci[col]);
+ }
+}
+
+static int uwb_rsv_find_best_row_alloc(struct uwb_rsv_alloc_info *ai)
+{
+ int n_rows;
+ int max_rows = ai->max_mas / UWB_USABLE_MAS_PER_ROW;
+ int min_rows = ai->min_mas / UWB_USABLE_MAS_PER_ROW;
+ if (ai->min_mas % UWB_USABLE_MAS_PER_ROW)
+ min_rows++;
+ for (n_rows = max_rows; n_rows >= min_rows; n_rows--) {
+ if (n_rows <= ai->ri.free_rows) {
+ ai->ri.used_rows = n_rows;
+ ai->interval = 1; /* row reservation */
+ uwb_rsv_fill_row_alloc(ai);
+ return UWB_RSV_ALLOC_FOUND;
+ }
+ }
+ return UWB_RSV_ALLOC_NOT_FOUND;
+}
+
+static int uwb_rsv_find_best_col_alloc(struct uwb_rsv_alloc_info *ai, int interval)
+{
+ int n_safe, n_unsafe, n_mas;
+ int n_column = UWB_NUM_ZONES / interval;
+ int max_per_zone = ai->max_mas / n_column;
+ int min_per_zone = ai->min_mas / n_column;
+
+ if (ai->min_mas % n_column)
+ min_per_zone++;
+
+ if (min_per_zone > UWB_MAS_PER_ZONE) {
+ return UWB_RSV_ALLOC_NOT_FOUND;
+ }
+
+ if (max_per_zone > UWB_MAS_PER_ZONE) {
+ max_per_zone = UWB_MAS_PER_ZONE;
+ }
+
+ for (n_mas = max_per_zone; n_mas >= min_per_zone; n_mas--) {
+ if (uwb_rsv_find_best_column_set(ai, interval, 0, n_mas) == UWB_RSV_ALLOC_NOT_FOUND)
+ continue;
+ for (n_safe = n_mas; n_safe >= 0; n_safe--) {
+ n_unsafe = n_mas - n_safe;
+ if (uwb_rsv_find_best_column_set(ai, interval, n_safe, n_unsafe) == UWB_RSV_ALLOC_FOUND) {
+ uwb_rsv_fill_column_alloc(ai);
+ return UWB_RSV_ALLOC_FOUND;
+ }
+ }
+ }
+ return UWB_RSV_ALLOC_NOT_FOUND;
+}
+
+int uwb_rsv_find_best_allocation(struct uwb_rsv *rsv, struct uwb_mas_bm *available,
+ struct uwb_mas_bm *result)
+{
+ struct uwb_rsv_alloc_info *ai;
+ int interval;
+ int bit_index;
+
+ ai = kzalloc(sizeof(struct uwb_rsv_alloc_info), GFP_KERNEL);
+ if (!ai)
+ return UWB_RSV_ALLOC_NOT_FOUND;
+ ai->min_mas = rsv->min_mas;
+ ai->max_mas = rsv->max_mas;
+ ai->max_interval = rsv->max_interval;
+
+
+ /* fill the not available vector from the available bm */
+ for_each_clear_bit(bit_index, available->bm, UWB_NUM_MAS)
+ ai->bm[bit_index] = UWB_RSV_MAS_NOT_AVAIL;
+
+ if (ai->max_interval == 1) {
+ get_row_descriptors(ai);
+ if (uwb_rsv_find_best_row_alloc(ai) == UWB_RSV_ALLOC_FOUND)
+ goto alloc_found;
+ else
+ goto alloc_not_found;
+ }
+
+ get_column_descriptors(ai);
+
+ for (interval = 16; interval >= 2; interval>>=1) {
+ if (interval > ai->max_interval)
+ continue;
+ if (uwb_rsv_find_best_col_alloc(ai, interval) == UWB_RSV_ALLOC_FOUND)
+ goto alloc_found;
+ }
+
+ /* try row reservation if no column is found */
+ get_row_descriptors(ai);
+ if (uwb_rsv_find_best_row_alloc(ai) == UWB_RSV_ALLOC_FOUND)
+ goto alloc_found;
+ else
+ goto alloc_not_found;
+
+ alloc_found:
+ bitmap_zero(result->bm, UWB_NUM_MAS);
+ bitmap_zero(result->unsafe_bm, UWB_NUM_MAS);
+ /* fill the safe and unsafe bitmaps */
+ for (bit_index = 0; bit_index < UWB_NUM_MAS; bit_index++) {
+ if (ai->bm[bit_index] == UWB_RSV_MAS_SAFE)
+ set_bit(bit_index, result->bm);
+ else if (ai->bm[bit_index] == UWB_RSV_MAS_UNSAFE)
+ set_bit(bit_index, result->unsafe_bm);
+ }
+ bitmap_or(result->bm, result->bm, result->unsafe_bm, UWB_NUM_MAS);
+
+ result->safe = ai->safe_allocated_mases;
+ result->unsafe = ai->unsafe_allocated_mases;
+
+ kfree(ai);
+ return UWB_RSV_ALLOC_FOUND;
+
+ alloc_not_found:
+ kfree(ai);
+ return UWB_RSV_ALLOC_NOT_FOUND;
+}
diff --git a/drivers/staging/uwb/beacon.c b/drivers/staging/uwb/beacon.c
new file mode 100644
index 000000000000..c483c19c5ef8
--- /dev/null
+++ b/drivers/staging/uwb/beacon.c
@@ -0,0 +1,595 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Beacon management
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kdev_t.h>
+#include <linux/slab.h>
+
+#include "uwb-internal.h"
+
+/* Start Beaconing command structure */
+struct uwb_rc_cmd_start_beacon {
+ struct uwb_rccb rccb;
+ __le16 wBPSTOffset;
+ u8 bChannelNumber;
+} __attribute__((packed));
+
+
+static int uwb_rc_start_beacon(struct uwb_rc *rc, u16 bpst_offset, u8 channel)
+{
+ int result;
+ struct uwb_rc_cmd_start_beacon *cmd;
+ struct uwb_rc_evt_confirm reply;
+
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+ if (cmd == NULL)
+ return -ENOMEM;
+ cmd->rccb.bCommandType = UWB_RC_CET_GENERAL;
+ cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_START_BEACON);
+ cmd->wBPSTOffset = cpu_to_le16(bpst_offset);
+ cmd->bChannelNumber = channel;
+ reply.rceb.bEventType = UWB_RC_CET_GENERAL;
+ reply.rceb.wEvent = UWB_RC_CMD_START_BEACON;
+ result = uwb_rc_cmd(rc, "START-BEACON", &cmd->rccb, sizeof(*cmd),
+ &reply.rceb, sizeof(reply));
+ if (result < 0)
+ goto error_cmd;
+ if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(&rc->uwb_dev.dev,
+ "START-BEACON: command execution failed: %s (%d)\n",
+ uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
+ result = -EIO;
+ }
+error_cmd:
+ kfree(cmd);
+ return result;
+}
+
+static int uwb_rc_stop_beacon(struct uwb_rc *rc)
+{
+ int result;
+ struct uwb_rccb *cmd;
+ struct uwb_rc_evt_confirm reply;
+
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+ if (cmd == NULL)
+ return -ENOMEM;
+ cmd->bCommandType = UWB_RC_CET_GENERAL;
+ cmd->wCommand = cpu_to_le16(UWB_RC_CMD_STOP_BEACON);
+ reply.rceb.bEventType = UWB_RC_CET_GENERAL;
+ reply.rceb.wEvent = UWB_RC_CMD_STOP_BEACON;
+ result = uwb_rc_cmd(rc, "STOP-BEACON", cmd, sizeof(*cmd),
+ &reply.rceb, sizeof(reply));
+ if (result < 0)
+ goto error_cmd;
+ if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(&rc->uwb_dev.dev,
+ "STOP-BEACON: command execution failed: %s (%d)\n",
+ uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
+ result = -EIO;
+ }
+error_cmd:
+ kfree(cmd);
+ return result;
+}
+
+/*
+ * Start/stop beacons
+ *
+ * @rc: UWB Radio Controller to operate on
+ * @channel: UWB channel on which to beacon (WUSB[table
+ * 5-12]). If -1, stop beaconing.
+ * @bpst_offset: Beacon Period Start Time offset; FIXME-do zero
+ *
+ * According to WHCI 0.95 [4.13.6] the driver will only receive the RCEB
+ * of a SET IE command after the device sent the first beacon that includes
+ * the IEs specified in the SET IE command. So, after we start beaconing we
+ * check if there is anything in the IE cache and call the SET IE command
+ * if needed.
+ */
+int uwb_rc_beacon(struct uwb_rc *rc, int channel, unsigned bpst_offset)
+{
+ int result;
+ struct device *dev = &rc->uwb_dev.dev;
+
+ dev_dbg(dev, "%s: channel = %d\n", __func__, channel);
+ if (channel < 0)
+ channel = -1;
+ if (channel == -1)
+ result = uwb_rc_stop_beacon(rc);
+ else {
+ /* channel >= 0...dah */
+ result = uwb_rc_start_beacon(rc, bpst_offset, channel);
+ if (result < 0) {
+ dev_err(dev, "Cannot start beaconing: %d\n", result);
+ return result;
+ }
+ if (le16_to_cpu(rc->ies->wIELength) > 0) {
+ result = uwb_rc_set_ie(rc, rc->ies);
+ if (result < 0) {
+ dev_err(dev, "Cannot set new IE on device: "
+ "%d\n", result);
+ result = uwb_rc_stop_beacon(rc);
+ channel = -1;
+ bpst_offset = 0;
+ }
+ }
+ }
+
+ if (result >= 0)
+ rc->beaconing = channel;
+ return result;
+}
+
+/*
+ * Beacon cache
+ *
+ * The purpose of this is to speed up the lookup of becon information
+ * when a new beacon arrives. The UWB Daemon uses it also to keep a
+ * tab of which devices are in radio distance and which not. When a
+ * device's beacon stays present for more than a certain amount of
+ * time, it is considered a new, usable device. When a beacon ceases
+ * to be received for a certain amount of time, it is considered that
+ * the device is gone.
+ *
+ * FIXME: use an allocator for the entries
+ * FIXME: use something faster for search than a list
+ */
+
+void uwb_bce_kfree(struct kref *_bce)
+{
+ struct uwb_beca_e *bce = container_of(_bce, struct uwb_beca_e, refcnt);
+
+ kfree(bce->be);
+ kfree(bce);
+}
+
+
+/* Find a beacon by dev addr in the cache */
+static
+struct uwb_beca_e *__uwb_beca_find_bydev(struct uwb_rc *rc,
+ const struct uwb_dev_addr *dev_addr)
+{
+ struct uwb_beca_e *bce, *next;
+ list_for_each_entry_safe(bce, next, &rc->uwb_beca.list, node) {
+ if (!memcmp(&bce->dev_addr, dev_addr, sizeof(bce->dev_addr)))
+ goto out;
+ }
+ bce = NULL;
+out:
+ return bce;
+}
+
+/* Find a beacon by dev addr in the cache */
+static
+struct uwb_beca_e *__uwb_beca_find_bymac(struct uwb_rc *rc,
+ const struct uwb_mac_addr *mac_addr)
+{
+ struct uwb_beca_e *bce, *next;
+ list_for_each_entry_safe(bce, next, &rc->uwb_beca.list, node) {
+ if (!memcmp(bce->mac_addr, mac_addr->data,
+ sizeof(struct uwb_mac_addr)))
+ goto out;
+ }
+ bce = NULL;
+out:
+ return bce;
+}
+
+/**
+ * uwb_dev_get_by_devaddr - get a UWB device with a specific DevAddr
+ * @rc: the radio controller that saw the device
+ * @devaddr: DevAddr of the UWB device to find
+ *
+ * There may be more than one matching device (in the case of a
+ * DevAddr conflict), but only the first one is returned.
+ */
+struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc,
+ const struct uwb_dev_addr *devaddr)
+{
+ struct uwb_dev *found = NULL;
+ struct uwb_beca_e *bce;
+
+ mutex_lock(&rc->uwb_beca.mutex);
+ bce = __uwb_beca_find_bydev(rc, devaddr);
+ if (bce)
+ found = uwb_dev_try_get(rc, bce->uwb_dev);
+ mutex_unlock(&rc->uwb_beca.mutex);
+
+ return found;
+}
+
+/**
+ * uwb_dev_get_by_macaddr - get a UWB device with a specific EUI-48
+ * @rc: the radio controller that saw the device
+ * @devaddr: EUI-48 of the UWB device to find
+ */
+struct uwb_dev *uwb_dev_get_by_macaddr(struct uwb_rc *rc,
+ const struct uwb_mac_addr *macaddr)
+{
+ struct uwb_dev *found = NULL;
+ struct uwb_beca_e *bce;
+
+ mutex_lock(&rc->uwb_beca.mutex);
+ bce = __uwb_beca_find_bymac(rc, macaddr);
+ if (bce)
+ found = uwb_dev_try_get(rc, bce->uwb_dev);
+ mutex_unlock(&rc->uwb_beca.mutex);
+
+ return found;
+}
+
+/* Initialize a beacon cache entry */
+static void uwb_beca_e_init(struct uwb_beca_e *bce)
+{
+ mutex_init(&bce->mutex);
+ kref_init(&bce->refcnt);
+ stats_init(&bce->lqe_stats);
+ stats_init(&bce->rssi_stats);
+}
+
+/*
+ * Add a beacon to the cache
+ *
+ * @be: Beacon event information
+ * @bf: Beacon frame (part of b, really)
+ * @ts_jiffies: Timestamp (in jiffies) when the beacon was received
+ */
+static
+struct uwb_beca_e *__uwb_beca_add(struct uwb_rc *rc,
+ struct uwb_rc_evt_beacon *be,
+ struct uwb_beacon_frame *bf,
+ unsigned long ts_jiffies)
+{
+ struct uwb_beca_e *bce;
+
+ bce = kzalloc(sizeof(*bce), GFP_KERNEL);
+ if (bce == NULL)
+ return NULL;
+ uwb_beca_e_init(bce);
+ bce->ts_jiffies = ts_jiffies;
+ bce->uwb_dev = NULL;
+ list_add(&bce->node, &rc->uwb_beca.list);
+ return bce;
+}
+
+/*
+ * Wipe out beacon entries that became stale
+ *
+ * Remove associated devicest too.
+ */
+void uwb_beca_purge(struct uwb_rc *rc)
+{
+ struct uwb_beca_e *bce, *next;
+ unsigned long expires;
+
+ mutex_lock(&rc->uwb_beca.mutex);
+ list_for_each_entry_safe(bce, next, &rc->uwb_beca.list, node) {
+ expires = bce->ts_jiffies + msecs_to_jiffies(beacon_timeout_ms);
+ if (time_after(jiffies, expires)) {
+ uwbd_dev_offair(bce);
+ }
+ }
+ mutex_unlock(&rc->uwb_beca.mutex);
+}
+
+/* Clean up the whole beacon cache. Called on shutdown */
+void uwb_beca_release(struct uwb_rc *rc)
+{
+ struct uwb_beca_e *bce, *next;
+
+ mutex_lock(&rc->uwb_beca.mutex);
+ list_for_each_entry_safe(bce, next, &rc->uwb_beca.list, node) {
+ list_del(&bce->node);
+ uwb_bce_put(bce);
+ }
+ mutex_unlock(&rc->uwb_beca.mutex);
+}
+
+static void uwb_beacon_print(struct uwb_rc *rc, struct uwb_rc_evt_beacon *be,
+ struct uwb_beacon_frame *bf)
+{
+ char macbuf[UWB_ADDR_STRSIZE];
+ char devbuf[UWB_ADDR_STRSIZE];
+ char dstbuf[UWB_ADDR_STRSIZE];
+
+ uwb_mac_addr_print(macbuf, sizeof(macbuf), &bf->Device_Identifier);
+ uwb_dev_addr_print(devbuf, sizeof(devbuf), &bf->hdr.SrcAddr);
+ uwb_dev_addr_print(dstbuf, sizeof(dstbuf), &bf->hdr.DestAddr);
+ dev_info(&rc->uwb_dev.dev,
+ "BEACON from %s to %s (ch%u offset %u slot %u MAC %s)\n",
+ devbuf, dstbuf, be->bChannelNumber, be->wBPSTOffset,
+ bf->Beacon_Slot_Number, macbuf);
+}
+
+/*
+ * @bce: beacon cache entry, referenced
+ */
+ssize_t uwb_bce_print_IEs(struct uwb_dev *uwb_dev, struct uwb_beca_e *bce,
+ char *buf, size_t size)
+{
+ ssize_t result = 0;
+ struct uwb_rc_evt_beacon *be;
+ struct uwb_beacon_frame *bf;
+ int ies_len;
+ struct uwb_ie_hdr *ies;
+
+ mutex_lock(&bce->mutex);
+
+ be = bce->be;
+ if (be) {
+ bf = (struct uwb_beacon_frame *)bce->be->BeaconInfo;
+ ies_len = be->wBeaconInfoLength - sizeof(struct uwb_beacon_frame);
+ ies = (struct uwb_ie_hdr *)bf->IEData;
+
+ result = uwb_ie_dump_hex(ies, ies_len, buf, size);
+ }
+
+ mutex_unlock(&bce->mutex);
+
+ return result;
+}
+
+/*
+ * Verify that the beacon event, frame and IEs are ok
+ */
+static int uwb_verify_beacon(struct uwb_rc *rc, struct uwb_event *evt,
+ struct uwb_rc_evt_beacon *be)
+{
+ int result = -EINVAL;
+ struct uwb_beacon_frame *bf;
+ struct device *dev = &rc->uwb_dev.dev;
+
+ /* Is there enough data to decode a beacon frame? */
+ if (evt->notif.size < sizeof(*be) + sizeof(*bf)) {
+ dev_err(dev, "BEACON event: Not enough data to decode "
+ "(%zu vs %zu bytes needed)\n", evt->notif.size,
+ sizeof(*be) + sizeof(*bf));
+ goto error;
+ }
+ /* FIXME: make sure beacon frame IEs are fine and that the whole thing
+ * is consistent */
+ result = 0;
+error:
+ return result;
+}
+
+/*
+ * Handle UWB_RC_EVT_BEACON events
+ *
+ * We check the beacon cache to see how the received beacon fares. If
+ * is there already we refresh the timestamp. If not we create a new
+ * entry.
+ *
+ * According to the WHCI and WUSB specs, only one beacon frame is
+ * allowed per notification block, so we don't bother about scanning
+ * for more.
+ */
+int uwbd_evt_handle_rc_beacon(struct uwb_event *evt)
+{
+ int result = -EINVAL;
+ struct uwb_rc *rc;
+ struct uwb_rc_evt_beacon *be;
+ struct uwb_beacon_frame *bf;
+ struct uwb_beca_e *bce;
+
+ rc = evt->rc;
+ be = container_of(evt->notif.rceb, struct uwb_rc_evt_beacon, rceb);
+ result = uwb_verify_beacon(rc, evt, be);
+ if (result < 0)
+ return result;
+
+ /* FIXME: handle alien beacons. */
+ if (be->bBeaconType == UWB_RC_BEACON_TYPE_OL_ALIEN ||
+ be->bBeaconType == UWB_RC_BEACON_TYPE_NOL_ALIEN) {
+ return -ENOSYS;
+ }
+
+ bf = (struct uwb_beacon_frame *) be->BeaconInfo;
+
+ /*
+ * Drop beacons from devices with a NULL EUI-48 -- they cannot
+ * be uniquely identified.
+ *
+ * It's expected that these will all be WUSB devices and they
+ * have a WUSB specific connection method so ignoring them
+ * here shouldn't be a problem.
+ */
+ if (uwb_mac_addr_bcast(&bf->Device_Identifier))
+ return 0;
+
+ mutex_lock(&rc->uwb_beca.mutex);
+ bce = __uwb_beca_find_bymac(rc, &bf->Device_Identifier);
+ if (bce == NULL) {
+ /* Not in there, a new device is pinging */
+ uwb_beacon_print(evt->rc, be, bf);
+ bce = __uwb_beca_add(rc, be, bf, evt->ts_jiffies);
+ if (bce == NULL) {
+ mutex_unlock(&rc->uwb_beca.mutex);
+ return -ENOMEM;
+ }
+ }
+ mutex_unlock(&rc->uwb_beca.mutex);
+
+ mutex_lock(&bce->mutex);
+ /* purge old beacon data */
+ kfree(bce->be);
+
+ /* Update commonly used fields */
+ bce->ts_jiffies = evt->ts_jiffies;
+ bce->be = be;
+ bce->dev_addr = bf->hdr.SrcAddr;
+ bce->mac_addr = &bf->Device_Identifier;
+ be->wBPSTOffset = le16_to_cpu(be->wBPSTOffset);
+ be->wBeaconInfoLength = le16_to_cpu(be->wBeaconInfoLength);
+ stats_add_sample(&bce->lqe_stats, be->bLQI - 7);
+ stats_add_sample(&bce->rssi_stats, be->bRSSI + 18);
+
+ /*
+ * This might be a beacon from a new device.
+ */
+ if (bce->uwb_dev == NULL)
+ uwbd_dev_onair(evt->rc, bce);
+
+ mutex_unlock(&bce->mutex);
+
+ return 1; /* we keep the event data */
+}
+
+/*
+ * Handle UWB_RC_EVT_BEACON_SIZE events
+ *
+ * XXXXX
+ */
+int uwbd_evt_handle_rc_beacon_size(struct uwb_event *evt)
+{
+ int result = -EINVAL;
+ struct device *dev = &evt->rc->uwb_dev.dev;
+ struct uwb_rc_evt_beacon_size *bs;
+
+ /* Is there enough data to decode the event? */
+ if (evt->notif.size < sizeof(*bs)) {
+ dev_err(dev, "BEACON SIZE notification: Not enough data to "
+ "decode (%zu vs %zu bytes needed)\n",
+ evt->notif.size, sizeof(*bs));
+ goto error;
+ }
+ bs = container_of(evt->notif.rceb, struct uwb_rc_evt_beacon_size, rceb);
+ if (0)
+ dev_info(dev, "Beacon size changed to %u bytes "
+ "(FIXME: action?)\n", le16_to_cpu(bs->wNewBeaconSize));
+ else {
+ /* temporary hack until we do something with this message... */
+ static unsigned count;
+ if (++count % 1000 == 0)
+ dev_info(dev, "Beacon size changed %u times "
+ "(FIXME: action?)\n", count);
+ }
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * uwbd_evt_handle_rc_bp_slot_change - handle a BP_SLOT_CHANGE event
+ * @evt: the BP_SLOT_CHANGE notification from the radio controller
+ *
+ * If the event indicates that no beacon period slots were available
+ * then radio controller has transitioned to a non-beaconing state.
+ * Otherwise, simply save the current beacon slot.
+ */
+int uwbd_evt_handle_rc_bp_slot_change(struct uwb_event *evt)
+{
+ struct uwb_rc *rc = evt->rc;
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_rc_evt_bp_slot_change *bpsc;
+
+ if (evt->notif.size < sizeof(*bpsc)) {
+ dev_err(dev, "BP SLOT CHANGE event: Not enough data\n");
+ return -EINVAL;
+ }
+ bpsc = container_of(evt->notif.rceb, struct uwb_rc_evt_bp_slot_change, rceb);
+
+ if (uwb_rc_evt_bp_slot_change_no_slot(bpsc)) {
+ dev_err(dev, "stopped beaconing: No free slots in BP\n");
+ mutex_lock(&rc->uwb_dev.mutex);
+ rc->beaconing = -1;
+ mutex_unlock(&rc->uwb_dev.mutex);
+ } else
+ rc->uwb_dev.beacon_slot = uwb_rc_evt_bp_slot_change_slot_num(bpsc);
+
+ return 0;
+}
+
+/**
+ * Handle UWB_RC_EVT_BPOIE_CHANGE events
+ *
+ * XXXXX
+ */
+struct uwb_ie_bpo {
+ struct uwb_ie_hdr hdr;
+ u8 bp_length;
+ u8 data[];
+} __attribute__((packed));
+
+int uwbd_evt_handle_rc_bpoie_change(struct uwb_event *evt)
+{
+ int result = -EINVAL;
+ struct device *dev = &evt->rc->uwb_dev.dev;
+ struct uwb_rc_evt_bpoie_change *bpoiec;
+ struct uwb_ie_bpo *bpoie;
+ static unsigned count; /* FIXME: this is a temp hack */
+ size_t iesize;
+
+ /* Is there enough data to decode it? */
+ if (evt->notif.size < sizeof(*bpoiec)) {
+ dev_err(dev, "BPOIEC notification: Not enough data to "
+ "decode (%zu vs %zu bytes needed)\n",
+ evt->notif.size, sizeof(*bpoiec));
+ goto error;
+ }
+ bpoiec = container_of(evt->notif.rceb, struct uwb_rc_evt_bpoie_change, rceb);
+ iesize = le16_to_cpu(bpoiec->wBPOIELength);
+ if (iesize < sizeof(*bpoie)) {
+ dev_err(dev, "BPOIEC notification: Not enough IE data to "
+ "decode (%zu vs %zu bytes needed)\n",
+ iesize, sizeof(*bpoie));
+ goto error;
+ }
+ if (++count % 1000 == 0) /* Lame placeholder */
+ dev_info(dev, "BPOIE: %u changes received\n", count);
+ /*
+ * FIXME: At this point we should go over all the IEs in the
+ * bpoiec->BPOIE array and act on each.
+ */
+ result = 0;
+error:
+ return result;
+}
+
+/*
+ * Print beaconing state.
+ */
+static ssize_t uwb_rc_beacon_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_rc *rc = uwb_dev->rc;
+ ssize_t result;
+
+ mutex_lock(&rc->uwb_dev.mutex);
+ result = sprintf(buf, "%d\n", rc->beaconing);
+ mutex_unlock(&rc->uwb_dev.mutex);
+ return result;
+}
+
+/*
+ * Start beaconing on the specified channel, or stop beaconing.
+ */
+static ssize_t uwb_rc_beacon_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_rc *rc = uwb_dev->rc;
+ int channel;
+ ssize_t result = -EINVAL;
+
+ result = sscanf(buf, "%d", &channel);
+ if (result >= 1)
+ result = uwb_radio_force_channel(rc, channel);
+
+ return result < 0 ? result : size;
+}
+DEVICE_ATTR(beacon, S_IRUGO | S_IWUSR, uwb_rc_beacon_show, uwb_rc_beacon_store);
diff --git a/drivers/staging/uwb/driver.c b/drivers/staging/uwb/driver.c
new file mode 100644
index 000000000000..5755c2e49ffc
--- /dev/null
+++ b/drivers/staging/uwb/driver.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Driver initialization, etc
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ *
+ * Life cycle: FIXME: explain
+ *
+ * UWB radio controller:
+ *
+ * 1. alloc a uwb_rc, zero it
+ * 2. call uwb_rc_init() on it to set it up + ops (won't do any
+ * kind of allocation)
+ * 3. register (now it is owned by the UWB stack--deregister before
+ * freeing/destroying).
+ * 4. It lives on it's own now (UWB stack handles)--when it
+ * disconnects, call unregister()
+ * 5. free it.
+ *
+ * Make sure you have a reference to the uwb_rc before calling
+ * any of the UWB API functions.
+ *
+ * TODO:
+ *
+ * 1. Locking and life cycle management is crappy still. All entry
+ * points to the UWB HCD API assume you have a reference on the
+ * uwb_rc structure and that it won't go away. They mutex lock it
+ * before doing anything.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kdev_t.h>
+#include <linux/random.h>
+
+#include "uwb-internal.h"
+
+
+/* UWB stack attributes (or 'global' constants) */
+
+
+/**
+ * If a beacon disappears for longer than this, then we consider the
+ * device who was represented by that beacon to be gone.
+ *
+ * ECMA-368[17.2.3, last para] establishes that a device must not
+ * consider a device to be its neighbour if he doesn't receive a beacon
+ * for more than mMaxLostBeacons. mMaxLostBeacons is defined in
+ * ECMA-368[17.16] as 3; because we can get only one beacon per
+ * superframe, that'd be 3 * 65ms = 195 ~ 200 ms. Let's give it time
+ * for jitter and stuff and make it 500 ms.
+ */
+unsigned long beacon_timeout_ms = 500;
+
+static
+ssize_t beacon_timeout_ms_show(struct class *class,
+ struct class_attribute *attr,
+ char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%lu\n", beacon_timeout_ms);
+}
+
+static
+ssize_t beacon_timeout_ms_store(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t size)
+{
+ unsigned long bt;
+ ssize_t result;
+ result = sscanf(buf, "%lu", &bt);
+ if (result != 1)
+ return -EINVAL;
+ beacon_timeout_ms = bt;
+ return size;
+}
+static CLASS_ATTR_RW(beacon_timeout_ms);
+
+static struct attribute *uwb_class_attrs[] = {
+ &class_attr_beacon_timeout_ms.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(uwb_class);
+
+/** Device model classes */
+struct class uwb_rc_class = {
+ .name = "uwb_rc",
+ .class_groups = uwb_class_groups,
+};
+
+
+static int __init uwb_subsys_init(void)
+{
+ int result = 0;
+
+ result = uwb_est_create();
+ if (result < 0) {
+ printk(KERN_ERR "uwb: Can't initialize EST subsystem\n");
+ goto error_est_init;
+ }
+
+ result = class_register(&uwb_rc_class);
+ if (result < 0)
+ goto error_uwb_rc_class_register;
+
+ /* Register the UWB bus */
+ result = bus_register(&uwb_bus_type);
+ if (result) {
+ pr_err("%s - registering bus driver failed\n", __func__);
+ goto exit_bus;
+ }
+
+ uwb_dbg_init();
+ return 0;
+
+exit_bus:
+ class_unregister(&uwb_rc_class);
+error_uwb_rc_class_register:
+ uwb_est_destroy();
+error_est_init:
+ return result;
+}
+module_init(uwb_subsys_init);
+
+static void __exit uwb_subsys_exit(void)
+{
+ uwb_dbg_exit();
+ bus_unregister(&uwb_bus_type);
+ class_unregister(&uwb_rc_class);
+ uwb_est_destroy();
+ return;
+}
+module_exit(uwb_subsys_exit);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Ultra Wide Band core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/uwb/drp-avail.c b/drivers/staging/uwb/drp-avail.c
new file mode 100644
index 000000000000..02392ab82a7d
--- /dev/null
+++ b/drivers/staging/uwb/drp-avail.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * DRP availability management
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Reinette Chatre <reinette.chatre@intel.com>
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ *
+ * Manage DRP Availability (the MAS available for DRP
+ * reservations). Thus:
+ *
+ * - Handle DRP Availability Change notifications
+ *
+ * - Allow the reservation manager to indicate MAS reserved/released
+ * by local (owned by/targeted at the radio controller)
+ * reservations.
+ *
+ * - Based on the two sources above, generate a DRP Availability IE to
+ * be included in the beacon.
+ *
+ * See also the documentation for struct uwb_drp_avail.
+ */
+
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/bitmap.h>
+#include "uwb-internal.h"
+
+/**
+ * uwb_drp_avail_init - initialize an RC's MAS availability
+ *
+ * All MAS are available initially. The RC will inform use which
+ * slots are used for the BP (it may change in size).
+ */
+void uwb_drp_avail_init(struct uwb_rc *rc)
+{
+ bitmap_fill(rc->drp_avail.global, UWB_NUM_MAS);
+ bitmap_fill(rc->drp_avail.local, UWB_NUM_MAS);
+ bitmap_fill(rc->drp_avail.pending, UWB_NUM_MAS);
+}
+
+/*
+ * Determine MAS available for new local reservations.
+ *
+ * avail = global & local & pending
+ */
+void uwb_drp_available(struct uwb_rc *rc, struct uwb_mas_bm *avail)
+{
+ bitmap_and(avail->bm, rc->drp_avail.global, rc->drp_avail.local, UWB_NUM_MAS);
+ bitmap_and(avail->bm, avail->bm, rc->drp_avail.pending, UWB_NUM_MAS);
+}
+
+/**
+ * uwb_drp_avail_reserve_pending - reserve MAS for a new reservation
+ * @rc: the radio controller
+ * @mas: the MAS to reserve
+ *
+ * Returns 0 on success, or -EBUSY if the MAS requested aren't available.
+ */
+int uwb_drp_avail_reserve_pending(struct uwb_rc *rc, struct uwb_mas_bm *mas)
+{
+ struct uwb_mas_bm avail;
+
+ uwb_drp_available(rc, &avail);
+ if (!bitmap_subset(mas->bm, avail.bm, UWB_NUM_MAS))
+ return -EBUSY;
+
+ bitmap_andnot(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS);
+ return 0;
+}
+
+/**
+ * uwb_drp_avail_reserve - reserve MAS for an established reservation
+ * @rc: the radio controller
+ * @mas: the MAS to reserve
+ */
+void uwb_drp_avail_reserve(struct uwb_rc *rc, struct uwb_mas_bm *mas)
+{
+ bitmap_or(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS);
+ bitmap_andnot(rc->drp_avail.local, rc->drp_avail.local, mas->bm, UWB_NUM_MAS);
+ rc->drp_avail.ie_valid = false;
+}
+
+/**
+ * uwb_drp_avail_release - release MAS from a pending or established reservation
+ * @rc: the radio controller
+ * @mas: the MAS to release
+ */
+void uwb_drp_avail_release(struct uwb_rc *rc, struct uwb_mas_bm *mas)
+{
+ bitmap_or(rc->drp_avail.local, rc->drp_avail.local, mas->bm, UWB_NUM_MAS);
+ bitmap_or(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS);
+ rc->drp_avail.ie_valid = false;
+ uwb_rsv_handle_drp_avail_change(rc);
+}
+
+/**
+ * uwb_drp_avail_ie_update - update the DRP Availability IE
+ * @rc: the radio controller
+ *
+ * avail = global & local
+ */
+void uwb_drp_avail_ie_update(struct uwb_rc *rc)
+{
+ struct uwb_mas_bm avail;
+
+ bitmap_and(avail.bm, rc->drp_avail.global, rc->drp_avail.local, UWB_NUM_MAS);
+
+ rc->drp_avail.ie.hdr.element_id = UWB_IE_DRP_AVAILABILITY;
+ rc->drp_avail.ie.hdr.length = UWB_NUM_MAS / 8;
+ uwb_mas_bm_copy_le(rc->drp_avail.ie.bmp, &avail);
+ rc->drp_avail.ie_valid = true;
+}
+
+/**
+ * Create an unsigned long from a buffer containing a byte stream.
+ *
+ * @array: pointer to buffer
+ * @itr: index of buffer from where we start
+ * @len: the buffer's remaining size may not be exact multiple of
+ * sizeof(unsigned long), @len is the length of buffer that needs
+ * to be converted. This will be sizeof(unsigned long) or smaller
+ * (BUG if not). If it is smaller then we will pad the remaining
+ * space of the result with zeroes.
+ */
+static
+unsigned long get_val(u8 *array, size_t itr, size_t len)
+{
+ unsigned long val = 0;
+ size_t top = itr + len;
+
+ BUG_ON(len > sizeof(val));
+
+ while (itr < top) {
+ val <<= 8;
+ val |= array[top - 1];
+ top--;
+ }
+ val <<= 8 * (sizeof(val) - len); /* padding */
+ return val;
+}
+
+/**
+ * Initialize bitmap from data buffer.
+ *
+ * The bitmap to be converted could come from a IE, for example a
+ * DRP Availability IE.
+ * From ECMA-368 1.0 [16.8.7]: "
+ * octets: 1 1 N * (0 to 32)
+ * Element ID Length (=N) DRP Availability Bitmap
+ *
+ * The DRP Availability Bitmap field is up to 256 bits long, one
+ * bit for each MAS in the superframe, where the least-significant
+ * bit of the field corresponds to the first MAS in the superframe
+ * and successive bits correspond to successive MASs."
+ *
+ * The DRP Availability bitmap is in octets from 0 to 32, so octet
+ * 32 contains bits for MAS 1-8, etc. If the bitmap is smaller than 32
+ * octets, the bits in octets not included at the end of the bitmap are
+ * treated as zero. In this case (when the bitmap is smaller than 32
+ * octets) the MAS represented range from MAS 1 to MAS (size of bitmap)
+ * with the last octet still containing bits for MAS 1-8, etc.
+ *
+ * For example:
+ * F00F0102 03040506 0708090A 0B0C0D0E 0F010203
+ * ^^^^
+ * ||||
+ * ||||
+ * |||\LSB of byte is MAS 9
+ * ||\MSB of byte is MAS 16
+ * |\LSB of first byte is MAS 1
+ * \ MSB of byte is MAS 8
+ *
+ * An example of this encoding can be found in ECMA-368 Annex-D [Table D.11]
+ *
+ * The resulting bitmap will have the following mapping:
+ * bit position 0 == MAS 1
+ * bit position 1 == MAS 2
+ * ...
+ * bit position (UWB_NUM_MAS - 1) == MAS UWB_NUM_MAS
+ *
+ * @bmp_itr: pointer to bitmap (can be declared with DECLARE_BITMAP)
+ * @buffer: pointer to buffer containing bitmap data in big endian
+ * format (MSB first)
+ * @buffer_size:number of bytes with which bitmap should be initialized
+ */
+static
+void buffer_to_bmp(unsigned long *bmp_itr, void *_buffer,
+ size_t buffer_size)
+{
+ u8 *buffer = _buffer;
+ size_t itr, len;
+ unsigned long val;
+
+ itr = 0;
+ while (itr < buffer_size) {
+ len = buffer_size - itr >= sizeof(val) ?
+ sizeof(val) : buffer_size - itr;
+ val = get_val(buffer, itr, len);
+ bmp_itr[itr / sizeof(val)] = val;
+ itr += sizeof(val);
+ }
+}
+
+
+/**
+ * Extract DRP Availability bitmap from the notification.
+ *
+ * The notification that comes in contains a bitmap of (UWB_NUM_MAS / 8) bytes
+ * We convert that to our internal representation.
+ */
+static
+int uwbd_evt_get_drp_avail(struct uwb_event *evt, unsigned long *bmp)
+{
+ struct device *dev = &evt->rc->uwb_dev.dev;
+ struct uwb_rc_evt_drp_avail *drp_evt;
+ int result = -EINVAL;
+
+ /* Is there enough data to decode the event? */
+ if (evt->notif.size < sizeof(*drp_evt)) {
+ dev_err(dev, "DRP Availability Change: Not enough "
+ "data to decode event [%zu bytes, %zu "
+ "needed]\n", evt->notif.size, sizeof(*drp_evt));
+ goto error;
+ }
+ drp_evt = container_of(evt->notif.rceb, struct uwb_rc_evt_drp_avail, rceb);
+ buffer_to_bmp(bmp, drp_evt->bmp, UWB_NUM_MAS/8);
+ result = 0;
+error:
+ return result;
+}
+
+
+/**
+ * Process an incoming DRP Availability notification.
+ *
+ * @evt: Event information (packs the actual event data, which
+ * radio controller it came to, etc).
+ *
+ * @returns: 0 on success (so uwbd() frees the event buffer), < 0
+ * on error.
+ *
+ * According to ECMA-368 1.0 [16.8.7], bits set to ONE indicate that
+ * the MAS slot is available, bits set to ZERO indicate that the slot
+ * is busy.
+ *
+ * So we clear available slots, we set used slots :)
+ *
+ * The notification only marks non-availability based on the BP and
+ * received DRP IEs that are not for this radio controller. A copy of
+ * this bitmap is needed to generate the real availability (which
+ * includes local and pending reservations).
+ *
+ * The DRP Availability IE that this radio controller emits will need
+ * to be updated.
+ */
+int uwbd_evt_handle_rc_drp_avail(struct uwb_event *evt)
+{
+ int result;
+ struct uwb_rc *rc = evt->rc;
+ DECLARE_BITMAP(bmp, UWB_NUM_MAS);
+
+ result = uwbd_evt_get_drp_avail(evt, bmp);
+ if (result < 0)
+ return result;
+
+ mutex_lock(&rc->rsvs_mutex);
+ bitmap_copy(rc->drp_avail.global, bmp, UWB_NUM_MAS);
+ rc->drp_avail.ie_valid = false;
+ uwb_rsv_handle_drp_avail_change(rc);
+ mutex_unlock(&rc->rsvs_mutex);
+
+ uwb_rsv_sched_update(rc);
+
+ return 0;
+}
diff --git a/drivers/staging/uwb/drp-ie.c b/drivers/staging/uwb/drp-ie.c
new file mode 100644
index 000000000000..b2a862cf76de
--- /dev/null
+++ b/drivers/staging/uwb/drp-ie.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UWB DRP IE management.
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/random.h>
+#include <linux/slab.h>
+
+#include "uwb.h"
+#include "uwb-internal.h"
+
+
+/*
+ * Return the reason code for a reservations's DRP IE.
+ */
+static int uwb_rsv_reason_code(struct uwb_rsv *rsv)
+{
+ static const int reason_codes[] = {
+ [UWB_RSV_STATE_O_INITIATED] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_O_PENDING] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_O_MODIFIED] = UWB_DRP_REASON_MODIFIED,
+ [UWB_RSV_STATE_O_ESTABLISHED] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_O_TO_BE_MOVED] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_O_MOVE_COMBINING] = UWB_DRP_REASON_MODIFIED,
+ [UWB_RSV_STATE_O_MOVE_REDUCING] = UWB_DRP_REASON_MODIFIED,
+ [UWB_RSV_STATE_O_MOVE_EXPANDING] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_T_ACCEPTED] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_T_CONFLICT] = UWB_DRP_REASON_CONFLICT,
+ [UWB_RSV_STATE_T_PENDING] = UWB_DRP_REASON_PENDING,
+ [UWB_RSV_STATE_T_DENIED] = UWB_DRP_REASON_DENIED,
+ [UWB_RSV_STATE_T_RESIZED] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_T_EXPANDING_ACCEPTED] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_T_EXPANDING_CONFLICT] = UWB_DRP_REASON_CONFLICT,
+ [UWB_RSV_STATE_T_EXPANDING_PENDING] = UWB_DRP_REASON_PENDING,
+ [UWB_RSV_STATE_T_EXPANDING_DENIED] = UWB_DRP_REASON_DENIED,
+ };
+
+ return reason_codes[rsv->state];
+}
+
+/*
+ * Return the reason code for a reservations's companion DRP IE .
+ */
+static int uwb_rsv_companion_reason_code(struct uwb_rsv *rsv)
+{
+ static const int companion_reason_codes[] = {
+ [UWB_RSV_STATE_O_MOVE_EXPANDING] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_T_EXPANDING_ACCEPTED] = UWB_DRP_REASON_ACCEPTED,
+ [UWB_RSV_STATE_T_EXPANDING_CONFLICT] = UWB_DRP_REASON_CONFLICT,
+ [UWB_RSV_STATE_T_EXPANDING_PENDING] = UWB_DRP_REASON_PENDING,
+ [UWB_RSV_STATE_T_EXPANDING_DENIED] = UWB_DRP_REASON_DENIED,
+ };
+
+ return companion_reason_codes[rsv->state];
+}
+
+/*
+ * Return the status bit for a reservations's DRP IE.
+ */
+int uwb_rsv_status(struct uwb_rsv *rsv)
+{
+ static const int statuses[] = {
+ [UWB_RSV_STATE_O_INITIATED] = 0,
+ [UWB_RSV_STATE_O_PENDING] = 0,
+ [UWB_RSV_STATE_O_MODIFIED] = 1,
+ [UWB_RSV_STATE_O_ESTABLISHED] = 1,
+ [UWB_RSV_STATE_O_TO_BE_MOVED] = 0,
+ [UWB_RSV_STATE_O_MOVE_COMBINING] = 1,
+ [UWB_RSV_STATE_O_MOVE_REDUCING] = 1,
+ [UWB_RSV_STATE_O_MOVE_EXPANDING] = 1,
+ [UWB_RSV_STATE_T_ACCEPTED] = 1,
+ [UWB_RSV_STATE_T_CONFLICT] = 0,
+ [UWB_RSV_STATE_T_PENDING] = 0,
+ [UWB_RSV_STATE_T_DENIED] = 0,
+ [UWB_RSV_STATE_T_RESIZED] = 1,
+ [UWB_RSV_STATE_T_EXPANDING_ACCEPTED] = 1,
+ [UWB_RSV_STATE_T_EXPANDING_CONFLICT] = 1,
+ [UWB_RSV_STATE_T_EXPANDING_PENDING] = 1,
+ [UWB_RSV_STATE_T_EXPANDING_DENIED] = 1,
+
+ };
+
+ return statuses[rsv->state];
+}
+
+/*
+ * Return the status bit for a reservations's companion DRP IE .
+ */
+int uwb_rsv_companion_status(struct uwb_rsv *rsv)
+{
+ static const int companion_statuses[] = {
+ [UWB_RSV_STATE_O_MOVE_EXPANDING] = 0,
+ [UWB_RSV_STATE_T_EXPANDING_ACCEPTED] = 1,
+ [UWB_RSV_STATE_T_EXPANDING_CONFLICT] = 0,
+ [UWB_RSV_STATE_T_EXPANDING_PENDING] = 0,
+ [UWB_RSV_STATE_T_EXPANDING_DENIED] = 0,
+ };
+
+ return companion_statuses[rsv->state];
+}
+
+/*
+ * Allocate a DRP IE.
+ *
+ * To save having to free/allocate a DRP IE when its MAS changes,
+ * enough memory is allocated for the maxiumum number of DRP
+ * allocation fields. This gives an overhead per reservation of up to
+ * (UWB_NUM_ZONES - 1) * 4 = 60 octets.
+ */
+static struct uwb_ie_drp *uwb_drp_ie_alloc(void)
+{
+ struct uwb_ie_drp *drp_ie;
+
+ drp_ie = kzalloc(struct_size(drp_ie, allocs, UWB_NUM_ZONES),
+ GFP_KERNEL);
+ if (drp_ie)
+ drp_ie->hdr.element_id = UWB_IE_DRP;
+ return drp_ie;
+}
+
+
+/*
+ * Fill a DRP IE's allocation fields from a MAS bitmap.
+ */
+static void uwb_drp_ie_from_bm(struct uwb_ie_drp *drp_ie,
+ struct uwb_mas_bm *mas)
+{
+ int z, i, num_fields = 0, next = 0;
+ struct uwb_drp_alloc *zones;
+ __le16 current_bmp;
+ DECLARE_BITMAP(tmp_bmp, UWB_NUM_MAS);
+ DECLARE_BITMAP(tmp_mas_bm, UWB_MAS_PER_ZONE);
+
+ zones = drp_ie->allocs;
+
+ bitmap_copy(tmp_bmp, mas->bm, UWB_NUM_MAS);
+
+ /* Determine unique MAS bitmaps in zones from bitmap. */
+ for (z = 0; z < UWB_NUM_ZONES; z++) {
+ bitmap_copy(tmp_mas_bm, tmp_bmp, UWB_MAS_PER_ZONE);
+ if (bitmap_weight(tmp_mas_bm, UWB_MAS_PER_ZONE) > 0) {
+ bool found = false;
+ current_bmp = (__le16) *tmp_mas_bm;
+ for (i = 0; i < next; i++) {
+ if (current_bmp == zones[i].mas_bm) {
+ zones[i].zone_bm |= 1 << z;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ num_fields++;
+ zones[next].zone_bm = 1 << z;
+ zones[next].mas_bm = current_bmp;
+ next++;
+ }
+ }
+ bitmap_shift_right(tmp_bmp, tmp_bmp, UWB_MAS_PER_ZONE, UWB_NUM_MAS);
+ }
+
+ /* Store in format ready for transmission (le16). */
+ for (i = 0; i < num_fields; i++) {
+ drp_ie->allocs[i].zone_bm = cpu_to_le16(zones[i].zone_bm);
+ drp_ie->allocs[i].mas_bm = cpu_to_le16(zones[i].mas_bm);
+ }
+
+ drp_ie->hdr.length = sizeof(struct uwb_ie_drp) - sizeof(struct uwb_ie_hdr)
+ + num_fields * sizeof(struct uwb_drp_alloc);
+}
+
+/**
+ * uwb_drp_ie_update - update a reservation's DRP IE
+ * @rsv: the reservation
+ */
+int uwb_drp_ie_update(struct uwb_rsv *rsv)
+{
+ struct uwb_ie_drp *drp_ie;
+ struct uwb_rsv_move *mv;
+ int unsafe;
+
+ if (rsv->state == UWB_RSV_STATE_NONE) {
+ kfree(rsv->drp_ie);
+ rsv->drp_ie = NULL;
+ return 0;
+ }
+
+ unsafe = rsv->mas.unsafe ? 1 : 0;
+
+ if (rsv->drp_ie == NULL) {
+ rsv->drp_ie = uwb_drp_ie_alloc();
+ if (rsv->drp_ie == NULL)
+ return -ENOMEM;
+ }
+ drp_ie = rsv->drp_ie;
+
+ uwb_ie_drp_set_unsafe(drp_ie, unsafe);
+ uwb_ie_drp_set_tiebreaker(drp_ie, rsv->tiebreaker);
+ uwb_ie_drp_set_owner(drp_ie, uwb_rsv_is_owner(rsv));
+ uwb_ie_drp_set_status(drp_ie, uwb_rsv_status(rsv));
+ uwb_ie_drp_set_reason_code(drp_ie, uwb_rsv_reason_code(rsv));
+ uwb_ie_drp_set_stream_index(drp_ie, rsv->stream);
+ uwb_ie_drp_set_type(drp_ie, rsv->type);
+
+ if (uwb_rsv_is_owner(rsv)) {
+ switch (rsv->target.type) {
+ case UWB_RSV_TARGET_DEV:
+ drp_ie->dev_addr = rsv->target.dev->dev_addr;
+ break;
+ case UWB_RSV_TARGET_DEVADDR:
+ drp_ie->dev_addr = rsv->target.devaddr;
+ break;
+ }
+ } else
+ drp_ie->dev_addr = rsv->owner->dev_addr;
+
+ uwb_drp_ie_from_bm(drp_ie, &rsv->mas);
+
+ if (uwb_rsv_has_two_drp_ies(rsv)) {
+ mv = &rsv->mv;
+ if (mv->companion_drp_ie == NULL) {
+ mv->companion_drp_ie = uwb_drp_ie_alloc();
+ if (mv->companion_drp_ie == NULL)
+ return -ENOMEM;
+ }
+ drp_ie = mv->companion_drp_ie;
+
+ /* keep all the same configuration of the main drp_ie */
+ memcpy(drp_ie, rsv->drp_ie, sizeof(struct uwb_ie_drp));
+
+
+ /* FIXME: handle properly the unsafe bit */
+ uwb_ie_drp_set_unsafe(drp_ie, 1);
+ uwb_ie_drp_set_status(drp_ie, uwb_rsv_companion_status(rsv));
+ uwb_ie_drp_set_reason_code(drp_ie, uwb_rsv_companion_reason_code(rsv));
+
+ uwb_drp_ie_from_bm(drp_ie, &mv->companion_mas);
+ }
+
+ rsv->ie_valid = true;
+ return 0;
+}
+
+/*
+ * Set MAS bits from given MAS bitmap in a single zone of large bitmap.
+ *
+ * We are given a zone id and the MAS bitmap of bits that need to be set in
+ * this zone. Note that this zone may already have bits set and this only
+ * adds settings - we cannot simply assign the MAS bitmap contents to the
+ * zone contents. We iterate over the the bits (MAS) in the zone and set the
+ * bits that are set in the given MAS bitmap.
+ */
+static
+void uwb_drp_ie_single_zone_to_bm(struct uwb_mas_bm *bm, u8 zone, u16 mas_bm)
+{
+ int mas;
+ u16 mas_mask;
+
+ for (mas = 0; mas < UWB_MAS_PER_ZONE; mas++) {
+ mas_mask = 1 << mas;
+ if (mas_bm & mas_mask)
+ set_bit(zone * UWB_NUM_ZONES + mas, bm->bm);
+ }
+}
+
+/**
+ * uwb_drp_ie_zones_to_bm - convert DRP allocation fields to a bitmap
+ * @mas: MAS bitmap that will be populated to correspond to the
+ * allocation fields in the DRP IE
+ * @drp_ie: the DRP IE that contains the allocation fields.
+ *
+ * The input format is an array of MAS allocation fields (16 bit Zone
+ * bitmap, 16 bit MAS bitmap) as described in [ECMA-368] section
+ * 16.8.6. The output is a full 256 bit MAS bitmap.
+ *
+ * We go over all the allocation fields, for each allocation field we
+ * know which zones are impacted. We iterate over all the zones
+ * impacted and call a function that will set the correct MAS bits in
+ * each zone.
+ */
+void uwb_drp_ie_to_bm(struct uwb_mas_bm *bm, const struct uwb_ie_drp *drp_ie)
+{
+ int numallocs = (drp_ie->hdr.length - 4) / 4;
+ const struct uwb_drp_alloc *alloc;
+ int cnt;
+ u16 zone_bm, mas_bm;
+ u8 zone;
+ u16 zone_mask;
+
+ bitmap_zero(bm->bm, UWB_NUM_MAS);
+
+ for (cnt = 0; cnt < numallocs; cnt++) {
+ alloc = &drp_ie->allocs[cnt];
+ zone_bm = le16_to_cpu(alloc->zone_bm);
+ mas_bm = le16_to_cpu(alloc->mas_bm);
+ for (zone = 0; zone < UWB_NUM_ZONES; zone++) {
+ zone_mask = 1 << zone;
+ if (zone_bm & zone_mask)
+ uwb_drp_ie_single_zone_to_bm(bm, zone, mas_bm);
+ }
+ }
+}
+
diff --git a/drivers/staging/uwb/drp.c b/drivers/staging/uwb/drp.c
new file mode 100644
index 000000000000..869987bede7b
--- /dev/null
+++ b/drivers/staging/uwb/drp.c
@@ -0,0 +1,842 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Dynamic Reservation Protocol handling
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include "uwb-internal.h"
+
+
+/* DRP Conflict Actions ([ECMA-368 2nd Edition] 17.4.6) */
+enum uwb_drp_conflict_action {
+ /* Reservation is maintained, no action needed */
+ UWB_DRP_CONFLICT_MANTAIN = 0,
+
+ /* the device shall not transmit frames in conflicting MASs in
+ * the following superframe. If the device is the reservation
+ * target, it shall also set the Reason Code in its DRP IE to
+ * Conflict in its beacon in the following superframe.
+ */
+ UWB_DRP_CONFLICT_ACT1,
+
+ /* the device shall not set the Reservation Status bit to ONE
+ * and shall not transmit frames in conflicting MASs. If the
+ * device is the reservation target, it shall also set the
+ * Reason Code in its DRP IE to Conflict.
+ */
+ UWB_DRP_CONFLICT_ACT2,
+
+ /* the device shall not transmit frames in conflicting MASs in
+ * the following superframe. It shall remove the conflicting
+ * MASs from the reservation or set the Reservation Status to
+ * ZERO in its beacon in the following superframe. If the
+ * device is the reservation target, it shall also set the
+ * Reason Code in its DRP IE to Conflict.
+ */
+ UWB_DRP_CONFLICT_ACT3,
+};
+
+
+static void uwb_rc_set_drp_cmd_done(struct uwb_rc *rc, void *arg,
+ struct uwb_rceb *reply, ssize_t reply_size)
+{
+ struct uwb_rc_evt_set_drp_ie *r = (struct uwb_rc_evt_set_drp_ie *)reply;
+ unsigned long flags;
+
+ if (r != NULL) {
+ if (r->bResultCode != UWB_RC_RES_SUCCESS)
+ dev_err(&rc->uwb_dev.dev, "SET-DRP-IE failed: %s (%d)\n",
+ uwb_rc_strerror(r->bResultCode), r->bResultCode);
+ } else
+ dev_err(&rc->uwb_dev.dev, "SET-DRP-IE: timeout\n");
+
+ spin_lock_irqsave(&rc->rsvs_lock, flags);
+ if (rc->set_drp_ie_pending > 1) {
+ rc->set_drp_ie_pending = 0;
+ uwb_rsv_queue_update(rc);
+ } else {
+ rc->set_drp_ie_pending = 0;
+ }
+ spin_unlock_irqrestore(&rc->rsvs_lock, flags);
+}
+
+/**
+ * Construct and send the SET DRP IE
+ *
+ * @rc: UWB Host controller
+ * @returns: >= 0 number of bytes still available in the beacon
+ * < 0 errno code on error.
+ *
+ * See WUSB[8.6.2.7]: The host must set all the DRP IEs that it wants the
+ * device to include in its beacon at the same time. We thus have to
+ * traverse all reservations and include the DRP IEs of all PENDING
+ * and NEGOTIATED reservations in a SET DRP command for transmission.
+ *
+ * A DRP Availability IE is appended.
+ *
+ * rc->rsvs_mutex is held
+ *
+ * FIXME We currently ignore the returned value indicating the remaining space
+ * in beacon. This could be used to deny reservation requests earlier if
+ * determined that they would cause the beacon space to be exceeded.
+ */
+int uwb_rc_send_all_drp_ie(struct uwb_rc *rc)
+{
+ int result;
+ struct uwb_rc_cmd_set_drp_ie *cmd;
+ struct uwb_rsv *rsv;
+ struct uwb_rsv_move *mv;
+ int num_bytes = 0;
+ u8 *IEDataptr;
+
+ result = -ENOMEM;
+ /* First traverse all reservations to determine memory needed. */
+ list_for_each_entry(rsv, &rc->reservations, rc_node) {
+ if (rsv->drp_ie != NULL) {
+ num_bytes += rsv->drp_ie->hdr.length + 2;
+ if (uwb_rsv_has_two_drp_ies(rsv) &&
+ (rsv->mv.companion_drp_ie != NULL)) {
+ mv = &rsv->mv;
+ num_bytes +=
+ mv->companion_drp_ie->hdr.length + 2;
+ }
+ }
+ }
+ num_bytes += sizeof(rc->drp_avail.ie);
+ cmd = kzalloc(sizeof(*cmd) + num_bytes, GFP_KERNEL);
+ if (cmd == NULL)
+ goto error;
+ cmd->rccb.bCommandType = UWB_RC_CET_GENERAL;
+ cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SET_DRP_IE);
+ cmd->wIELength = num_bytes;
+ IEDataptr = (u8 *)&cmd->IEData[0];
+
+ /* FIXME: DRV avail IE is not always needed */
+ /* put DRP avail IE first */
+ memcpy(IEDataptr, &rc->drp_avail.ie, sizeof(rc->drp_avail.ie));
+ IEDataptr += sizeof(struct uwb_ie_drp_avail);
+
+ /* Next traverse all reservations to place IEs in allocated memory. */
+ list_for_each_entry(rsv, &rc->reservations, rc_node) {
+ if (rsv->drp_ie != NULL) {
+ memcpy(IEDataptr, rsv->drp_ie,
+ rsv->drp_ie->hdr.length + 2);
+ IEDataptr += rsv->drp_ie->hdr.length + 2;
+
+ if (uwb_rsv_has_two_drp_ies(rsv) &&
+ (rsv->mv.companion_drp_ie != NULL)) {
+ mv = &rsv->mv;
+ memcpy(IEDataptr, mv->companion_drp_ie,
+ mv->companion_drp_ie->hdr.length + 2);
+ IEDataptr +=
+ mv->companion_drp_ie->hdr.length + 2;
+ }
+ }
+ }
+
+ result = uwb_rc_cmd_async(rc, "SET-DRP-IE",
+ &cmd->rccb, sizeof(*cmd) + num_bytes,
+ UWB_RC_CET_GENERAL, UWB_RC_CMD_SET_DRP_IE,
+ uwb_rc_set_drp_cmd_done, NULL);
+
+ rc->set_drp_ie_pending = 1;
+
+ kfree(cmd);
+error:
+ return result;
+}
+
+/*
+ * Evaluate the action to perform using conflict resolution rules
+ *
+ * Return a uwb_drp_conflict_action.
+ */
+static int evaluate_conflict_action(struct uwb_ie_drp *ext_drp_ie, int ext_beacon_slot,
+ struct uwb_rsv *rsv, int our_status)
+{
+ int our_tie_breaker = rsv->tiebreaker;
+ int our_type = rsv->type;
+ int our_beacon_slot = rsv->rc->uwb_dev.beacon_slot;
+
+ int ext_tie_breaker = uwb_ie_drp_tiebreaker(ext_drp_ie);
+ int ext_status = uwb_ie_drp_status(ext_drp_ie);
+ int ext_type = uwb_ie_drp_type(ext_drp_ie);
+
+
+ /* [ECMA-368 2nd Edition] 17.4.6 */
+ if (ext_type == UWB_DRP_TYPE_PCA && our_type == UWB_DRP_TYPE_PCA) {
+ return UWB_DRP_CONFLICT_MANTAIN;
+ }
+
+ /* [ECMA-368 2nd Edition] 17.4.6-1 */
+ if (our_type == UWB_DRP_TYPE_ALIEN_BP) {
+ return UWB_DRP_CONFLICT_MANTAIN;
+ }
+
+ /* [ECMA-368 2nd Edition] 17.4.6-2 */
+ if (ext_type == UWB_DRP_TYPE_ALIEN_BP) {
+ /* here we know our_type != UWB_DRP_TYPE_ALIEN_BP */
+ return UWB_DRP_CONFLICT_ACT1;
+ }
+
+ /* [ECMA-368 2nd Edition] 17.4.6-3 */
+ if (our_status == 0 && ext_status == 1) {
+ return UWB_DRP_CONFLICT_ACT2;
+ }
+
+ /* [ECMA-368 2nd Edition] 17.4.6-4 */
+ if (our_status == 1 && ext_status == 0) {
+ return UWB_DRP_CONFLICT_MANTAIN;
+ }
+
+ /* [ECMA-368 2nd Edition] 17.4.6-5a */
+ if (our_tie_breaker == ext_tie_breaker &&
+ our_beacon_slot < ext_beacon_slot) {
+ return UWB_DRP_CONFLICT_MANTAIN;
+ }
+
+ /* [ECMA-368 2nd Edition] 17.4.6-5b */
+ if (our_tie_breaker != ext_tie_breaker &&
+ our_beacon_slot > ext_beacon_slot) {
+ return UWB_DRP_CONFLICT_MANTAIN;
+ }
+
+ if (our_status == 0) {
+ if (our_tie_breaker == ext_tie_breaker) {
+ /* [ECMA-368 2nd Edition] 17.4.6-6a */
+ if (our_beacon_slot > ext_beacon_slot) {
+ return UWB_DRP_CONFLICT_ACT2;
+ }
+ } else {
+ /* [ECMA-368 2nd Edition] 17.4.6-6b */
+ if (our_beacon_slot < ext_beacon_slot) {
+ return UWB_DRP_CONFLICT_ACT2;
+ }
+ }
+ } else {
+ if (our_tie_breaker == ext_tie_breaker) {
+ /* [ECMA-368 2nd Edition] 17.4.6-7a */
+ if (our_beacon_slot > ext_beacon_slot) {
+ return UWB_DRP_CONFLICT_ACT3;
+ }
+ } else {
+ /* [ECMA-368 2nd Edition] 17.4.6-7b */
+ if (our_beacon_slot < ext_beacon_slot) {
+ return UWB_DRP_CONFLICT_ACT3;
+ }
+ }
+ }
+ return UWB_DRP_CONFLICT_MANTAIN;
+}
+
+static void handle_conflict_normal(struct uwb_ie_drp *drp_ie,
+ int ext_beacon_slot,
+ struct uwb_rsv *rsv,
+ struct uwb_mas_bm *conflicting_mas)
+{
+ struct uwb_rc *rc = rsv->rc;
+ struct uwb_rsv_move *mv = &rsv->mv;
+ struct uwb_drp_backoff_win *bow = &rc->bow;
+ int action;
+
+ action = evaluate_conflict_action(drp_ie, ext_beacon_slot, rsv, uwb_rsv_status(rsv));
+
+ if (uwb_rsv_is_owner(rsv)) {
+ switch(action) {
+ case UWB_DRP_CONFLICT_ACT2:
+ /* try move */
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_TO_BE_MOVED);
+ if (bow->can_reserve_extra_mases == false)
+ uwb_rsv_backoff_win_increment(rc);
+
+ break;
+ case UWB_DRP_CONFLICT_ACT3:
+ uwb_rsv_backoff_win_increment(rc);
+ /* drop some mases with reason modified */
+ /* put in the companion the mases to be dropped */
+ bitmap_and(mv->companion_mas.bm, rsv->mas.bm, conflicting_mas->bm, UWB_NUM_MAS);
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MODIFIED);
+ default:
+ break;
+ }
+ } else {
+ switch(action) {
+ case UWB_DRP_CONFLICT_ACT2:
+ case UWB_DRP_CONFLICT_ACT3:
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_CONFLICT);
+ default:
+ break;
+ }
+
+ }
+
+}
+
+static void handle_conflict_expanding(struct uwb_ie_drp *drp_ie, int ext_beacon_slot,
+ struct uwb_rsv *rsv, bool companion_only,
+ struct uwb_mas_bm *conflicting_mas)
+{
+ struct uwb_rc *rc = rsv->rc;
+ struct uwb_drp_backoff_win *bow = &rc->bow;
+ struct uwb_rsv_move *mv = &rsv->mv;
+ int action;
+
+ if (companion_only) {
+ /* status of companion is 0 at this point */
+ action = evaluate_conflict_action(drp_ie, ext_beacon_slot, rsv, 0);
+ if (uwb_rsv_is_owner(rsv)) {
+ switch(action) {
+ case UWB_DRP_CONFLICT_ACT2:
+ case UWB_DRP_CONFLICT_ACT3:
+ uwb_rsv_set_state(rsv,
+ UWB_RSV_STATE_O_ESTABLISHED);
+ rsv->needs_release_companion_mas = false;
+ if (bow->can_reserve_extra_mases == false)
+ uwb_rsv_backoff_win_increment(rc);
+ uwb_drp_avail_release(rsv->rc,
+ &rsv->mv.companion_mas);
+ }
+ } else { /* rsv is target */
+ switch(action) {
+ case UWB_DRP_CONFLICT_ACT2:
+ case UWB_DRP_CONFLICT_ACT3:
+ uwb_rsv_set_state(rsv,
+ UWB_RSV_STATE_T_EXPANDING_CONFLICT);
+ /* send_drp_avail_ie = true; */
+ }
+ }
+ } else { /* also base part of the reservation is conflicting */
+ if (uwb_rsv_is_owner(rsv)) {
+ uwb_rsv_backoff_win_increment(rc);
+ /* remove companion part */
+ uwb_drp_avail_release(rsv->rc, &rsv->mv.companion_mas);
+
+ /* drop some mases with reason modified */
+
+ /* put in the companion the mases to be dropped */
+ bitmap_andnot(mv->companion_mas.bm, rsv->mas.bm,
+ conflicting_mas->bm, UWB_NUM_MAS);
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MODIFIED);
+ } else { /* it is a target rsv */
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_CONFLICT);
+ /* send_drp_avail_ie = true; */
+ }
+ }
+}
+
+static void uwb_drp_handle_conflict_rsv(struct uwb_rc *rc, struct uwb_rsv *rsv,
+ struct uwb_rc_evt_drp *drp_evt,
+ struct uwb_ie_drp *drp_ie,
+ struct uwb_mas_bm *conflicting_mas)
+{
+ struct uwb_rsv_move *mv;
+
+ /* check if the conflicting reservation has two drp_ies */
+ if (uwb_rsv_has_two_drp_ies(rsv)) {
+ mv = &rsv->mv;
+ if (bitmap_intersects(rsv->mas.bm, conflicting_mas->bm,
+ UWB_NUM_MAS)) {
+ handle_conflict_expanding(drp_ie,
+ drp_evt->beacon_slot_number,
+ rsv, false, conflicting_mas);
+ } else {
+ if (bitmap_intersects(mv->companion_mas.bm,
+ conflicting_mas->bm, UWB_NUM_MAS)) {
+ handle_conflict_expanding(
+ drp_ie, drp_evt->beacon_slot_number,
+ rsv, true, conflicting_mas);
+ }
+ }
+ } else if (bitmap_intersects(rsv->mas.bm, conflicting_mas->bm,
+ UWB_NUM_MAS)) {
+ handle_conflict_normal(drp_ie, drp_evt->beacon_slot_number,
+ rsv, conflicting_mas);
+ }
+}
+
+static void uwb_drp_handle_all_conflict_rsv(struct uwb_rc *rc,
+ struct uwb_rc_evt_drp *drp_evt,
+ struct uwb_ie_drp *drp_ie,
+ struct uwb_mas_bm *conflicting_mas)
+{
+ struct uwb_rsv *rsv;
+
+ list_for_each_entry(rsv, &rc->reservations, rc_node) {
+ uwb_drp_handle_conflict_rsv(rc, rsv, drp_evt, drp_ie,
+ conflicting_mas);
+ }
+}
+
+static void uwb_drp_process_target_accepted(struct uwb_rc *rc,
+ struct uwb_rsv *rsv, struct uwb_rc_evt_drp *drp_evt,
+ struct uwb_ie_drp *drp_ie, struct uwb_mas_bm *mas)
+{
+ struct uwb_rsv_move *mv = &rsv->mv;
+ int status;
+
+ status = uwb_ie_drp_status(drp_ie);
+
+ if (rsv->state == UWB_RSV_STATE_T_CONFLICT) {
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_CONFLICT);
+ return;
+ }
+
+ if (rsv->state == UWB_RSV_STATE_T_EXPANDING_ACCEPTED) {
+ /* drp_ie is companion */
+ if (!bitmap_equal(rsv->mas.bm, mas->bm, UWB_NUM_MAS)) {
+ /* stroke companion */
+ uwb_rsv_set_state(rsv,
+ UWB_RSV_STATE_T_EXPANDING_ACCEPTED);
+ }
+ } else {
+ if (!bitmap_equal(rsv->mas.bm, mas->bm, UWB_NUM_MAS)) {
+ if (uwb_drp_avail_reserve_pending(rc, mas) == -EBUSY) {
+ /* FIXME: there is a conflict, find
+ * the conflicting reservations and
+ * take a sensible action. Consider
+ * that in drp_ie there is the
+ * "neighbour" */
+ uwb_drp_handle_all_conflict_rsv(rc, drp_evt,
+ drp_ie, mas);
+ } else {
+ /* accept the extra reservation */
+ bitmap_copy(mv->companion_mas.bm, mas->bm,
+ UWB_NUM_MAS);
+ uwb_rsv_set_state(rsv,
+ UWB_RSV_STATE_T_EXPANDING_ACCEPTED);
+ }
+ } else {
+ if (status) {
+ uwb_rsv_set_state(rsv,
+ UWB_RSV_STATE_T_ACCEPTED);
+ }
+ }
+
+ }
+}
+
+/*
+ * Based on the DRP IE, transition a target reservation to a new
+ * state.
+ */
+static void uwb_drp_process_target(struct uwb_rc *rc, struct uwb_rsv *rsv,
+ struct uwb_ie_drp *drp_ie, struct uwb_rc_evt_drp *drp_evt)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_rsv_move *mv = &rsv->mv;
+ int status;
+ enum uwb_drp_reason reason_code;
+ struct uwb_mas_bm mas;
+
+ status = uwb_ie_drp_status(drp_ie);
+ reason_code = uwb_ie_drp_reason_code(drp_ie);
+ uwb_drp_ie_to_bm(&mas, drp_ie);
+
+ switch (reason_code) {
+ case UWB_DRP_REASON_ACCEPTED:
+ uwb_drp_process_target_accepted(rc, rsv, drp_evt, drp_ie, &mas);
+ break;
+
+ case UWB_DRP_REASON_MODIFIED:
+ /* check to see if we have already modified the reservation */
+ if (bitmap_equal(rsv->mas.bm, mas.bm, UWB_NUM_MAS)) {
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_ACCEPTED);
+ break;
+ }
+
+ /* find if the owner wants to expand or reduce */
+ if (bitmap_subset(mas.bm, rsv->mas.bm, UWB_NUM_MAS)) {
+ /* owner is reducing */
+ bitmap_andnot(mv->companion_mas.bm, rsv->mas.bm, mas.bm,
+ UWB_NUM_MAS);
+ uwb_drp_avail_release(rsv->rc, &mv->companion_mas);
+ }
+
+ bitmap_copy(rsv->mas.bm, mas.bm, UWB_NUM_MAS);
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_RESIZED);
+ break;
+ default:
+ dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n",
+ reason_code, status);
+ }
+}
+
+static void uwb_drp_process_owner_accepted(struct uwb_rsv *rsv,
+ struct uwb_mas_bm *mas)
+{
+ struct uwb_rsv_move *mv = &rsv->mv;
+
+ switch (rsv->state) {
+ case UWB_RSV_STATE_O_PENDING:
+ case UWB_RSV_STATE_O_INITIATED:
+ case UWB_RSV_STATE_O_ESTABLISHED:
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED);
+ break;
+ case UWB_RSV_STATE_O_MODIFIED:
+ if (bitmap_equal(mas->bm, rsv->mas.bm, UWB_NUM_MAS))
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED);
+ else
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MODIFIED);
+ break;
+
+ case UWB_RSV_STATE_O_MOVE_REDUCING: /* shouldn' t be a problem */
+ if (bitmap_equal(mas->bm, rsv->mas.bm, UWB_NUM_MAS))
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED);
+ else
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MOVE_REDUCING);
+ break;
+ case UWB_RSV_STATE_O_MOVE_EXPANDING:
+ if (bitmap_equal(mas->bm, mv->companion_mas.bm, UWB_NUM_MAS)) {
+ /* Companion reservation accepted */
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MOVE_COMBINING);
+ } else {
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MOVE_EXPANDING);
+ }
+ break;
+ case UWB_RSV_STATE_O_MOVE_COMBINING:
+ if (bitmap_equal(mas->bm, rsv->mas.bm, UWB_NUM_MAS))
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MOVE_REDUCING);
+ else
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MOVE_COMBINING);
+ break;
+ default:
+ break;
+ }
+}
+/*
+ * Based on the DRP IE, transition an owner reservation to a new
+ * state.
+ */
+static void uwb_drp_process_owner(struct uwb_rc *rc, struct uwb_rsv *rsv,
+ struct uwb_dev *src, struct uwb_ie_drp *drp_ie,
+ struct uwb_rc_evt_drp *drp_evt)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ int status;
+ enum uwb_drp_reason reason_code;
+ struct uwb_mas_bm mas;
+
+ status = uwb_ie_drp_status(drp_ie);
+ reason_code = uwb_ie_drp_reason_code(drp_ie);
+ uwb_drp_ie_to_bm(&mas, drp_ie);
+
+ if (status) {
+ switch (reason_code) {
+ case UWB_DRP_REASON_ACCEPTED:
+ uwb_drp_process_owner_accepted(rsv, &mas);
+ break;
+ default:
+ dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n",
+ reason_code, status);
+ }
+ } else {
+ switch (reason_code) {
+ case UWB_DRP_REASON_PENDING:
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_PENDING);
+ break;
+ case UWB_DRP_REASON_DENIED:
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
+ break;
+ case UWB_DRP_REASON_CONFLICT:
+ /* resolve the conflict */
+ bitmap_complement(mas.bm, src->last_availability_bm,
+ UWB_NUM_MAS);
+ uwb_drp_handle_conflict_rsv(rc, rsv, drp_evt, drp_ie, &mas);
+ break;
+ default:
+ dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n",
+ reason_code, status);
+ }
+ }
+}
+
+static void uwb_cnflt_alien_stroke_timer(struct uwb_cnflt_alien *cnflt)
+{
+ unsigned timeout_us = UWB_MAX_LOST_BEACONS * UWB_SUPERFRAME_LENGTH_US;
+ mod_timer(&cnflt->timer, jiffies + usecs_to_jiffies(timeout_us));
+}
+
+static void uwb_cnflt_update_work(struct work_struct *work)
+{
+ struct uwb_cnflt_alien *cnflt = container_of(work,
+ struct uwb_cnflt_alien,
+ cnflt_update_work);
+ struct uwb_cnflt_alien *c;
+ struct uwb_rc *rc = cnflt->rc;
+
+ unsigned long delay_us = UWB_MAS_LENGTH_US * UWB_MAS_PER_ZONE;
+
+ mutex_lock(&rc->rsvs_mutex);
+
+ list_del(&cnflt->rc_node);
+
+ /* update rc global conflicting alien bitmap */
+ bitmap_zero(rc->cnflt_alien_bitmap.bm, UWB_NUM_MAS);
+
+ list_for_each_entry(c, &rc->cnflt_alien_list, rc_node) {
+ bitmap_or(rc->cnflt_alien_bitmap.bm, rc->cnflt_alien_bitmap.bm,
+ c->mas.bm, UWB_NUM_MAS);
+ }
+
+ queue_delayed_work(rc->rsv_workq, &rc->rsv_alien_bp_work,
+ usecs_to_jiffies(delay_us));
+
+ kfree(cnflt);
+ mutex_unlock(&rc->rsvs_mutex);
+}
+
+static void uwb_cnflt_timer(struct timer_list *t)
+{
+ struct uwb_cnflt_alien *cnflt = from_timer(cnflt, t, timer);
+
+ queue_work(cnflt->rc->rsv_workq, &cnflt->cnflt_update_work);
+}
+
+/*
+ * We have received an DRP_IE of type Alien BP and we need to make
+ * sure we do not transmit in conflicting MASs.
+ */
+static void uwb_drp_handle_alien_drp(struct uwb_rc *rc, struct uwb_ie_drp *drp_ie)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_mas_bm mas;
+ struct uwb_cnflt_alien *cnflt;
+ unsigned long delay_us = UWB_MAS_LENGTH_US * UWB_MAS_PER_ZONE;
+
+ uwb_drp_ie_to_bm(&mas, drp_ie);
+
+ list_for_each_entry(cnflt, &rc->cnflt_alien_list, rc_node) {
+ if (bitmap_equal(cnflt->mas.bm, mas.bm, UWB_NUM_MAS)) {
+ /* Existing alien BP reservation conflicting
+ * bitmap, just reset the timer */
+ uwb_cnflt_alien_stroke_timer(cnflt);
+ return;
+ }
+ }
+
+ /* New alien BP reservation conflicting bitmap */
+
+ /* alloc and initialize new uwb_cnflt_alien */
+ cnflt = kzalloc(sizeof(struct uwb_cnflt_alien), GFP_KERNEL);
+ if (!cnflt) {
+ dev_err(dev, "failed to alloc uwb_cnflt_alien struct\n");
+ return;
+ }
+
+ INIT_LIST_HEAD(&cnflt->rc_node);
+ timer_setup(&cnflt->timer, uwb_cnflt_timer, 0);
+
+ cnflt->rc = rc;
+ INIT_WORK(&cnflt->cnflt_update_work, uwb_cnflt_update_work);
+
+ bitmap_copy(cnflt->mas.bm, mas.bm, UWB_NUM_MAS);
+
+ list_add_tail(&cnflt->rc_node, &rc->cnflt_alien_list);
+
+ /* update rc global conflicting alien bitmap */
+ bitmap_or(rc->cnflt_alien_bitmap.bm, rc->cnflt_alien_bitmap.bm, mas.bm, UWB_NUM_MAS);
+
+ queue_delayed_work(rc->rsv_workq, &rc->rsv_alien_bp_work, usecs_to_jiffies(delay_us));
+
+ /* start the timer */
+ uwb_cnflt_alien_stroke_timer(cnflt);
+}
+
+static void uwb_drp_process_not_involved(struct uwb_rc *rc,
+ struct uwb_rc_evt_drp *drp_evt,
+ struct uwb_ie_drp *drp_ie)
+{
+ struct uwb_mas_bm mas;
+
+ uwb_drp_ie_to_bm(&mas, drp_ie);
+ uwb_drp_handle_all_conflict_rsv(rc, drp_evt, drp_ie, &mas);
+}
+
+static void uwb_drp_process_involved(struct uwb_rc *rc, struct uwb_dev *src,
+ struct uwb_rc_evt_drp *drp_evt,
+ struct uwb_ie_drp *drp_ie)
+{
+ struct uwb_rsv *rsv;
+
+ rsv = uwb_rsv_find(rc, src, drp_ie);
+ if (!rsv) {
+ /*
+ * No reservation? It's either for a recently
+ * terminated reservation; or the DRP IE couldn't be
+ * processed (e.g., an invalid IE or out of memory).
+ */
+ return;
+ }
+
+ /*
+ * Do nothing with DRP IEs for reservations that have been
+ * terminated.
+ */
+ if (rsv->state == UWB_RSV_STATE_NONE) {
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
+ return;
+ }
+
+ if (uwb_ie_drp_owner(drp_ie))
+ uwb_drp_process_target(rc, rsv, drp_ie, drp_evt);
+ else
+ uwb_drp_process_owner(rc, rsv, src, drp_ie, drp_evt);
+
+}
+
+
+static bool uwb_drp_involves_us(struct uwb_rc *rc, struct uwb_ie_drp *drp_ie)
+{
+ return uwb_dev_addr_cmp(&rc->uwb_dev.dev_addr, &drp_ie->dev_addr) == 0;
+}
+
+/*
+ * Process a received DRP IE.
+ */
+static void uwb_drp_process(struct uwb_rc *rc, struct uwb_rc_evt_drp *drp_evt,
+ struct uwb_dev *src, struct uwb_ie_drp *drp_ie)
+{
+ if (uwb_ie_drp_type(drp_ie) == UWB_DRP_TYPE_ALIEN_BP)
+ uwb_drp_handle_alien_drp(rc, drp_ie);
+ else if (uwb_drp_involves_us(rc, drp_ie))
+ uwb_drp_process_involved(rc, src, drp_evt, drp_ie);
+ else
+ uwb_drp_process_not_involved(rc, drp_evt, drp_ie);
+}
+
+/*
+ * Process a received DRP Availability IE
+ */
+static void uwb_drp_availability_process(struct uwb_rc *rc, struct uwb_dev *src,
+ struct uwb_ie_drp_avail *drp_availability_ie)
+{
+ bitmap_copy(src->last_availability_bm,
+ drp_availability_ie->bmp, UWB_NUM_MAS);
+}
+
+/*
+ * Process all the DRP IEs (both DRP IEs and the DRP Availability IE)
+ * from a device.
+ */
+static
+void uwb_drp_process_all(struct uwb_rc *rc, struct uwb_rc_evt_drp *drp_evt,
+ size_t ielen, struct uwb_dev *src_dev)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_ie_hdr *ie_hdr;
+ void *ptr;
+
+ ptr = drp_evt->ie_data;
+ for (;;) {
+ ie_hdr = uwb_ie_next(&ptr, &ielen);
+ if (!ie_hdr)
+ break;
+
+ switch (ie_hdr->element_id) {
+ case UWB_IE_DRP_AVAILABILITY:
+ uwb_drp_availability_process(rc, src_dev, (struct uwb_ie_drp_avail *)ie_hdr);
+ break;
+ case UWB_IE_DRP:
+ uwb_drp_process(rc, drp_evt, src_dev, (struct uwb_ie_drp *)ie_hdr);
+ break;
+ default:
+ dev_warn(dev, "unexpected IE in DRP notification\n");
+ break;
+ }
+ }
+
+ if (ielen > 0)
+ dev_warn(dev, "%d octets remaining in DRP notification\n",
+ (int)ielen);
+}
+
+/**
+ * uwbd_evt_handle_rc_drp - handle a DRP_IE event
+ * @evt: the DRP_IE event from the radio controller
+ *
+ * This processes DRP notifications from the radio controller, either
+ * initiating a new reservation or transitioning an existing
+ * reservation into a different state.
+ *
+ * DRP notifications can occur for three different reasons:
+ *
+ * - UWB_DRP_NOTIF_DRP_IE_RECVD: one or more DRP IEs with the RC as
+ * the target or source have been received.
+ *
+ * These DRP IEs could be new or for an existing reservation.
+ *
+ * If the DRP IE for an existing reservation ceases to be to
+ * received for at least mMaxLostBeacons, the reservation should be
+ * considered to be terminated. Note that the TERMINATE reason (see
+ * below) may not always be signalled (e.g., the remote device has
+ * two or more reservations established with the RC).
+ *
+ * - UWB_DRP_NOTIF_CONFLICT: DRP IEs from any device in the beacon
+ * group conflict with the RC's reservations.
+ *
+ * - UWB_DRP_NOTIF_TERMINATE: DRP IEs are no longer being received
+ * from a device (i.e., it's terminated all reservations).
+ *
+ * Only the software state of the reservations is changed; the setting
+ * of the radio controller's DRP IEs is done after all the events in
+ * an event buffer are processed. This saves waiting multiple times
+ * for the SET_DRP_IE command to complete.
+ */
+int uwbd_evt_handle_rc_drp(struct uwb_event *evt)
+{
+ struct device *dev = &evt->rc->uwb_dev.dev;
+ struct uwb_rc *rc = evt->rc;
+ struct uwb_rc_evt_drp *drp_evt;
+ size_t ielength, bytes_left;
+ struct uwb_dev_addr src_addr;
+ struct uwb_dev *src_dev;
+
+ /* Is there enough data to decode the event (and any IEs in
+ its payload)? */
+ if (evt->notif.size < sizeof(*drp_evt)) {
+ dev_err(dev, "DRP event: Not enough data to decode event "
+ "[%zu bytes left, %zu needed]\n",
+ evt->notif.size, sizeof(*drp_evt));
+ return 0;
+ }
+ bytes_left = evt->notif.size - sizeof(*drp_evt);
+ drp_evt = container_of(evt->notif.rceb, struct uwb_rc_evt_drp, rceb);
+ ielength = le16_to_cpu(drp_evt->ie_length);
+ if (bytes_left != ielength) {
+ dev_err(dev, "DRP event: Not enough data in payload [%zu"
+ "bytes left, %zu declared in the event]\n",
+ bytes_left, ielength);
+ return 0;
+ }
+
+ memcpy(src_addr.data, &drp_evt->src_addr, sizeof(src_addr));
+ src_dev = uwb_dev_get_by_devaddr(rc, &src_addr);
+ if (!src_dev) {
+ /*
+ * A DRP notification from an unrecognized device.
+ *
+ * This is probably from a WUSB device that doesn't
+ * have an EUI-48 and therefore doesn't show up in the
+ * UWB device database. It's safe to simply ignore
+ * these.
+ */
+ return 0;
+ }
+
+ mutex_lock(&rc->rsvs_mutex);
+
+ /* We do not distinguish from the reason */
+ uwb_drp_process_all(rc, drp_evt, ielength, src_dev);
+
+ mutex_unlock(&rc->rsvs_mutex);
+
+ uwb_dev_put(src_dev);
+ return 0;
+}
diff --git a/drivers/staging/uwb/est.c b/drivers/staging/uwb/est.c
new file mode 100644
index 000000000000..d4141ffdd775
--- /dev/null
+++ b/drivers/staging/uwb/est.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band Radio Control
+ * Event Size Tables management
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ *
+ * Infrastructure, code and data tables for guessing the size of
+ * events received on the notification endpoints of UWB radio
+ * controllers.
+ *
+ * You define a table of events and for each, its size and how to get
+ * the extra size.
+ *
+ * ENTRY POINTS:
+ *
+ * uwb_est_{init/destroy}(): To initialize/release the EST subsystem.
+ *
+ * uwb_est_[u]register(): To un/register event size tables
+ * uwb_est_grow()
+ *
+ * uwb_est_find_size(): Get the size of an event
+ * uwb_est_get_size()
+ */
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+
+#include "uwb-internal.h"
+
+struct uwb_est {
+ u16 type_event_high;
+ u16 vendor, product;
+ u8 entries;
+ const struct uwb_est_entry *entry;
+};
+
+static struct uwb_est *uwb_est;
+static u8 uwb_est_size;
+static u8 uwb_est_used;
+static DEFINE_RWLOCK(uwb_est_lock);
+
+/**
+ * WUSB Standard Event Size Table, HWA-RC interface
+ *
+ * Sizes for events and notifications type 0 (general), high nibble 0.
+ */
+static
+struct uwb_est_entry uwb_est_00_00xx[] = {
+ [UWB_RC_EVT_IE_RCV] = {
+ .size = sizeof(struct uwb_rc_evt_ie_rcv),
+ .offset = 1 + offsetof(struct uwb_rc_evt_ie_rcv, wIELength),
+ },
+ [UWB_RC_EVT_BEACON] = {
+ .size = sizeof(struct uwb_rc_evt_beacon),
+ .offset = 1 + offsetof(struct uwb_rc_evt_beacon, wBeaconInfoLength),
+ },
+ [UWB_RC_EVT_BEACON_SIZE] = {
+ .size = sizeof(struct uwb_rc_evt_beacon_size),
+ },
+ [UWB_RC_EVT_BPOIE_CHANGE] = {
+ .size = sizeof(struct uwb_rc_evt_bpoie_change),
+ .offset = 1 + offsetof(struct uwb_rc_evt_bpoie_change,
+ wBPOIELength),
+ },
+ [UWB_RC_EVT_BP_SLOT_CHANGE] = {
+ .size = sizeof(struct uwb_rc_evt_bp_slot_change),
+ },
+ [UWB_RC_EVT_BP_SWITCH_IE_RCV] = {
+ .size = sizeof(struct uwb_rc_evt_bp_switch_ie_rcv),
+ .offset = 1 + offsetof(struct uwb_rc_evt_bp_switch_ie_rcv, wIELength),
+ },
+ [UWB_RC_EVT_DEV_ADDR_CONFLICT] = {
+ .size = sizeof(struct uwb_rc_evt_dev_addr_conflict),
+ },
+ [UWB_RC_EVT_DRP_AVAIL] = {
+ .size = sizeof(struct uwb_rc_evt_drp_avail)
+ },
+ [UWB_RC_EVT_DRP] = {
+ .size = sizeof(struct uwb_rc_evt_drp),
+ .offset = 1 + offsetof(struct uwb_rc_evt_drp, ie_length),
+ },
+ [UWB_RC_EVT_BP_SWITCH_STATUS] = {
+ .size = sizeof(struct uwb_rc_evt_bp_switch_status),
+ },
+ [UWB_RC_EVT_CMD_FRAME_RCV] = {
+ .size = sizeof(struct uwb_rc_evt_cmd_frame_rcv),
+ .offset = 1 + offsetof(struct uwb_rc_evt_cmd_frame_rcv, dataLength),
+ },
+ [UWB_RC_EVT_CHANNEL_CHANGE_IE_RCV] = {
+ .size = sizeof(struct uwb_rc_evt_channel_change_ie_rcv),
+ .offset = 1 + offsetof(struct uwb_rc_evt_channel_change_ie_rcv, wIELength),
+ },
+ [UWB_RC_CMD_CHANNEL_CHANGE] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_DEV_ADDR_MGMT] = {
+ .size = sizeof(struct uwb_rc_evt_dev_addr_mgmt) },
+ [UWB_RC_CMD_GET_IE] = {
+ .size = sizeof(struct uwb_rc_evt_get_ie),
+ .offset = 1 + offsetof(struct uwb_rc_evt_get_ie, wIELength),
+ },
+ [UWB_RC_CMD_RESET] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_SCAN] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_SET_BEACON_FILTER] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_SET_DRP_IE] = {
+ .size = sizeof(struct uwb_rc_evt_set_drp_ie),
+ },
+ [UWB_RC_CMD_SET_IE] = {
+ .size = sizeof(struct uwb_rc_evt_set_ie),
+ },
+ [UWB_RC_CMD_SET_NOTIFICATION_FILTER] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_SET_TX_POWER] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_SLEEP] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_START_BEACON] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_STOP_BEACON] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_BP_MERGE] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_SEND_COMMAND_FRAME] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+ [UWB_RC_CMD_SET_ASIE_NOTIF] = {
+ .size = sizeof(struct uwb_rc_evt_confirm),
+ },
+};
+
+static
+struct uwb_est_entry uwb_est_01_00xx[] = {
+ [UWB_RC_DAA_ENERGY_DETECTED] = {
+ .size = sizeof(struct uwb_rc_evt_daa_energy_detected),
+ },
+ [UWB_RC_SET_DAA_ENERGY_MASK] = {
+ .size = sizeof(struct uwb_rc_evt_set_daa_energy_mask),
+ },
+ [UWB_RC_SET_NOTIFICATION_FILTER_EX] = {
+ .size = sizeof(struct uwb_rc_evt_set_notification_filter_ex),
+ },
+};
+
+/**
+ * Initialize the EST subsystem
+ *
+ * Register the standard tables also.
+ *
+ * FIXME: tag init
+ */
+int uwb_est_create(void)
+{
+ int result;
+
+ uwb_est_size = 2;
+ uwb_est_used = 0;
+ uwb_est = kcalloc(uwb_est_size, sizeof(uwb_est[0]), GFP_KERNEL);
+ if (uwb_est == NULL)
+ return -ENOMEM;
+
+ result = uwb_est_register(UWB_RC_CET_GENERAL, 0, 0xffff, 0xffff,
+ uwb_est_00_00xx, ARRAY_SIZE(uwb_est_00_00xx));
+ if (result < 0)
+ goto out;
+ result = uwb_est_register(UWB_RC_CET_EX_TYPE_1, 0, 0xffff, 0xffff,
+ uwb_est_01_00xx, ARRAY_SIZE(uwb_est_01_00xx));
+out:
+ return result;
+}
+
+
+/** Clean it up */
+void uwb_est_destroy(void)
+{
+ kfree(uwb_est);
+ uwb_est = NULL;
+ uwb_est_size = uwb_est_used = 0;
+}
+
+
+/**
+ * Double the capacity of the EST table
+ *
+ * @returns 0 if ok, < 0 errno no error.
+ */
+static
+int uwb_est_grow(void)
+{
+ size_t actual_size = uwb_est_size * sizeof(uwb_est[0]);
+ void *new = kmalloc_array(2, actual_size, GFP_ATOMIC);
+ if (new == NULL)
+ return -ENOMEM;
+ memcpy(new, uwb_est, actual_size);
+ memset(new + actual_size, 0, actual_size);
+ kfree(uwb_est);
+ uwb_est = new;
+ uwb_est_size *= 2;
+ return 0;
+}
+
+
+/**
+ * Register an event size table
+ *
+ * Makes room for it if the table is full, and then inserts it in the
+ * right position (entries are sorted by type, event_high, vendor and
+ * then product).
+ *
+ * @vendor: vendor code for matching against the device (0x0000 and
+ * 0xffff mean any); use 0x0000 to force all to match without
+ * checking possible vendor specific ones, 0xfffff to match
+ * after checking vendor specific ones.
+ *
+ * @product: product code from that vendor; same matching rules, use
+ * 0x0000 for not allowing vendor specific matches, 0xffff
+ * for allowing.
+ *
+ * This arragement just makes the tables sort differenty. Because the
+ * table is sorted by growing type-event_high-vendor-product, a zero
+ * vendor will match before than a 0x456a vendor, that will match
+ * before a 0xfffff vendor.
+ *
+ * @returns 0 if ok, < 0 errno on error (-ENOENT if not found).
+ */
+/* FIXME: add bus type to vendor/product code */
+int uwb_est_register(u8 type, u8 event_high, u16 vendor, u16 product,
+ const struct uwb_est_entry *entry, size_t entries)
+{
+ unsigned long flags;
+ unsigned itr;
+ int result = 0;
+
+ write_lock_irqsave(&uwb_est_lock, flags);
+ if (uwb_est_used == uwb_est_size) {
+ result = uwb_est_grow();
+ if (result < 0)
+ goto out;
+ }
+ /* Find the right spot to insert it in */
+ for (itr = 0; itr < uwb_est_used; itr++)
+ if (uwb_est[itr].type_event_high < type
+ && uwb_est[itr].vendor < vendor
+ && uwb_est[itr].product < product)
+ break;
+
+ /* Shift others to make room for the new one? */
+ if (itr < uwb_est_used)
+ memmove(&uwb_est[itr+1], &uwb_est[itr], uwb_est_used - itr);
+ uwb_est[itr].type_event_high = type << 8 | event_high;
+ uwb_est[itr].vendor = vendor;
+ uwb_est[itr].product = product;
+ uwb_est[itr].entry = entry;
+ uwb_est[itr].entries = entries;
+ uwb_est_used++;
+out:
+ write_unlock_irqrestore(&uwb_est_lock, flags);
+ return result;
+}
+EXPORT_SYMBOL_GPL(uwb_est_register);
+
+
+/**
+ * Unregister an event size table
+ *
+ * This just removes the specified entry and moves the ones after it
+ * to fill in the gap. This is needed to keep the list sorted; no
+ * reallocation is done to reduce the size of the table.
+ *
+ * We unregister by all the data we used to register instead of by
+ * pointer to the @entry array because we might have used the same
+ * table for a bunch of IDs (for example).
+ *
+ * @returns 0 if ok, < 0 errno on error (-ENOENT if not found).
+ */
+int uwb_est_unregister(u8 type, u8 event_high, u16 vendor, u16 product,
+ const struct uwb_est_entry *entry, size_t entries)
+{
+ unsigned long flags;
+ unsigned itr;
+ struct uwb_est est_cmp = {
+ .type_event_high = type << 8 | event_high,
+ .vendor = vendor,
+ .product = product,
+ .entry = entry,
+ .entries = entries
+ };
+ write_lock_irqsave(&uwb_est_lock, flags);
+ for (itr = 0; itr < uwb_est_used; itr++)
+ if (!memcmp(&uwb_est[itr], &est_cmp, sizeof(est_cmp)))
+ goto found;
+ write_unlock_irqrestore(&uwb_est_lock, flags);
+ return -ENOENT;
+
+found:
+ if (itr < uwb_est_used - 1) /* Not last one? move ones above */
+ memmove(&uwb_est[itr], &uwb_est[itr+1], uwb_est_used - itr - 1);
+ uwb_est_used--;
+ write_unlock_irqrestore(&uwb_est_lock, flags);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(uwb_est_unregister);
+
+
+/**
+ * Get the size of an event from a table
+ *
+ * @rceb: pointer to the buffer with the event
+ * @rceb_size: size of the area pointed to by @rceb in bytes.
+ * @returns: > 0 Size of the event
+ * -ENOSPC An area big enough was not provided to look
+ * ahead into the event's guts and guess the size.
+ * -EINVAL Unknown event code (wEvent).
+ *
+ * This will look at the received RCEB and guess what is the total
+ * size. For variable sized events, it will look further ahead into
+ * their length field to see how much data should be read.
+ *
+ * Note this size is *not* final--the neh (Notification/Event Handle)
+ * might specificy an extra size to add.
+ */
+static
+ssize_t uwb_est_get_size(struct uwb_rc *uwb_rc, struct uwb_est *est,
+ u8 event_low, const struct uwb_rceb *rceb,
+ size_t rceb_size)
+{
+ unsigned offset;
+ ssize_t size;
+ struct device *dev = &uwb_rc->uwb_dev.dev;
+ const struct uwb_est_entry *entry;
+
+ size = -ENOENT;
+ if (event_low >= est->entries) { /* in range? */
+ dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: event %u out of range\n",
+ est, est->type_event_high, est->vendor, est->product,
+ est->entries, event_low);
+ goto out;
+ }
+ size = -ENOENT;
+ entry = &est->entry[event_low];
+ if (entry->size == 0 && entry->offset == 0) { /* unknown? */
+ dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: event %u unknown\n",
+ est, est->type_event_high, est->vendor, est->product,
+ est->entries, event_low);
+ goto out;
+ }
+ offset = entry->offset; /* extra fries with that? */
+ if (offset == 0)
+ size = entry->size;
+ else {
+ /* Ops, got an extra size field at 'offset'--read it */
+ const void *ptr = rceb;
+ size_t type_size = 0;
+ offset--;
+ size = -ENOSPC; /* enough data for more? */
+ switch (entry->type) {
+ case UWB_EST_16: type_size = sizeof(__le16); break;
+ case UWB_EST_8: type_size = sizeof(u8); break;
+ default: BUG();
+ }
+ if (offset + type_size > rceb_size) {
+ dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: "
+ "not enough data to read extra size\n",
+ est, est->type_event_high, est->vendor,
+ est->product, est->entries);
+ goto out;
+ }
+ size = entry->size;
+ ptr += offset;
+ switch (entry->type) {
+ case UWB_EST_16: size += le16_to_cpu(*(__le16 *)ptr); break;
+ case UWB_EST_8: size += *(u8 *)ptr; break;
+ default: BUG();
+ }
+ }
+out:
+ return size;
+}
+
+
+/**
+ * Guesses the size of a WA event
+ *
+ * @rceb: pointer to the buffer with the event
+ * @rceb_size: size of the area pointed to by @rceb in bytes.
+ * @returns: > 0 Size of the event
+ * -ENOSPC An area big enough was not provided to look
+ * ahead into the event's guts and guess the size.
+ * -EINVAL Unknown event code (wEvent).
+ *
+ * This will look at the received RCEB and guess what is the total
+ * size by checking all the tables registered with
+ * uwb_est_register(). For variable sized events, it will look further
+ * ahead into their length field to see how much data should be read.
+ *
+ * Note this size is *not* final--the neh (Notification/Event Handle)
+ * might specificy an extra size to add or replace.
+ */
+ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb,
+ size_t rceb_size)
+{
+ /* FIXME: add vendor/product data */
+ ssize_t size;
+ struct device *dev = &rc->uwb_dev.dev;
+ unsigned long flags;
+ unsigned itr;
+ u16 type_event_high, event;
+
+ read_lock_irqsave(&uwb_est_lock, flags);
+ size = -ENOSPC;
+ if (rceb_size < sizeof(*rceb))
+ goto out;
+ event = le16_to_cpu(rceb->wEvent);
+ type_event_high = rceb->bEventType << 8 | (event & 0xff00) >> 8;
+ for (itr = 0; itr < uwb_est_used; itr++) {
+ if (uwb_est[itr].type_event_high != type_event_high)
+ continue;
+ size = uwb_est_get_size(rc, &uwb_est[itr],
+ event & 0x00ff, rceb, rceb_size);
+ /* try more tables that might handle the same type */
+ if (size != -ENOENT)
+ goto out;
+ }
+ dev_dbg(dev,
+ "event 0x%02x/%04x/%02x: no handlers available; RCEB %4ph\n",
+ (unsigned) rceb->bEventType,
+ (unsigned) le16_to_cpu(rceb->wEvent),
+ (unsigned) rceb->bEventContext,
+ rceb);
+ size = -ENOENT;
+out:
+ read_unlock_irqrestore(&uwb_est_lock, flags);
+ return size;
+}
+EXPORT_SYMBOL_GPL(uwb_est_find_size);
diff --git a/drivers/staging/uwb/hwa-rc.c b/drivers/staging/uwb/hwa-rc.c
new file mode 100644
index 000000000000..b6effad749d7
--- /dev/null
+++ b/drivers/staging/uwb/hwa-rc.c
@@ -0,0 +1,929 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * WUSB Host Wire Adapter: Radio Control Interface (WUSB[8.6])
+ * Radio Control command/event transport
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * Initialize the Radio Control interface Driver.
+ *
+ * For each device probed, creates an 'struct hwarc' which contains
+ * just the representation of the UWB Radio Controller, and the logic
+ * for reading notifications and passing them to the UWB Core.
+ *
+ * So we initialize all of those, register the UWB Radio Controller
+ * and setup the notification/event handle to pipe the notifications
+ * to the UWB management Daemon.
+ *
+ * Command and event filtering.
+ *
+ * This is the driver for the Radio Control Interface described in WUSB
+ * 1.0. The core UWB module assumes that all drivers are compliant to the
+ * WHCI 0.95 specification. We thus create a filter that parses all
+ * incoming messages from the (WUSB 1.0) device and manipulate them to
+ * conform to the WHCI 0.95 specification. Similarly, outgoing messages
+ * are parsed and manipulated to conform to the WUSB 1.0 compliant messages
+ * that the device expects. Only a few messages are affected:
+ * Affected events:
+ * UWB_RC_EVT_BEACON
+ * UWB_RC_EVT_BP_SLOT_CHANGE
+ * UWB_RC_EVT_DRP_AVAIL
+ * UWB_RC_EVT_DRP
+ * Affected commands:
+ * UWB_RC_CMD_SCAN
+ * UWB_RC_CMD_SET_DRP_IE
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include "../wusbcore/include/wusb.h"
+#include "../wusbcore/include/wusb-wa.h"
+#include "uwb.h"
+
+#include "uwb-internal.h"
+
+/* The device uses commands and events from the WHCI specification, although
+ * reporting itself as WUSB compliant. */
+#define WUSB_QUIRK_WHCI_CMD_EVT 0x01
+
+/**
+ * Descriptor for an instance of the UWB Radio Control Driver that
+ * attaches to the RCI interface of the Host Wired Adapter.
+ *
+ * Unless there is a lock specific to the 'data members', all access
+ * is protected by uwb_rc->mutex.
+ *
+ * The NEEP (Notification/Event EndPoint) URB (@neep_urb) writes to
+ * @rd_buffer. Note there is no locking because it is perfectly (heh!)
+ * serialized--probe() submits an URB, callback is called, processes
+ * the data (synchronously), submits another URB, and so on. There is
+ * no concurrent access to the buffer.
+ */
+struct hwarc {
+ struct usb_device *usb_dev;
+ struct usb_interface *usb_iface;
+ struct uwb_rc *uwb_rc; /* UWB host controller */
+ struct urb *neep_urb; /* Notification endpoint handling */
+ struct edc neep_edc;
+ void *rd_buffer; /* NEEP read buffer */
+};
+
+
+/* Beacon received notification (WUSB 1.0 [8.6.3.2]) */
+struct uwb_rc_evt_beacon_WUSB_0100 {
+ struct uwb_rceb rceb;
+ u8 bChannelNumber;
+ __le16 wBPSTOffset;
+ u8 bLQI;
+ u8 bRSSI;
+ __le16 wBeaconInfoLength;
+ u8 BeaconInfo[];
+} __attribute__((packed));
+
+/**
+ * Filter WUSB 1.0 BEACON RCV notification to be WHCI 0.95
+ *
+ * @header: the incoming event
+ * @buf_size: size of buffer containing incoming event
+ * @new_size: size of event after filtering completed
+ *
+ * The WHCI 0.95 spec has a "Beacon Type" field. This value is unknown at
+ * the time we receive the beacon from WUSB so we just set it to
+ * UWB_RC_BEACON_TYPE_NEIGHBOR as a default.
+ * The solution below allocates memory upon receipt of every beacon from a
+ * WUSB device. This will deteriorate performance. What is the right way to
+ * do this?
+ */
+static
+int hwarc_filter_evt_beacon_WUSB_0100(struct uwb_rc *rc,
+ struct uwb_rceb **header,
+ const size_t buf_size,
+ size_t *new_size)
+{
+ struct uwb_rc_evt_beacon_WUSB_0100 *be;
+ struct uwb_rc_evt_beacon *newbe;
+ size_t bytes_left, ielength;
+ struct device *dev = &rc->uwb_dev.dev;
+
+ be = container_of(*header, struct uwb_rc_evt_beacon_WUSB_0100, rceb);
+ bytes_left = buf_size;
+ if (bytes_left < sizeof(*be)) {
+ dev_err(dev, "Beacon Received Notification: Not enough data "
+ "to decode for filtering (%zu vs %zu bytes needed)\n",
+ bytes_left, sizeof(*be));
+ return -EINVAL;
+ }
+ bytes_left -= sizeof(*be);
+ ielength = le16_to_cpu(be->wBeaconInfoLength);
+ if (bytes_left < ielength) {
+ dev_err(dev, "Beacon Received Notification: Not enough data "
+ "to decode IEs (%zu vs %zu bytes needed)\n",
+ bytes_left, ielength);
+ return -EINVAL;
+ }
+ newbe = kzalloc(sizeof(*newbe) + ielength, GFP_ATOMIC);
+ if (newbe == NULL)
+ return -ENOMEM;
+ newbe->rceb = be->rceb;
+ newbe->bChannelNumber = be->bChannelNumber;
+ newbe->bBeaconType = UWB_RC_BEACON_TYPE_NEIGHBOR;
+ newbe->wBPSTOffset = be->wBPSTOffset;
+ newbe->bLQI = be->bLQI;
+ newbe->bRSSI = be->bRSSI;
+ newbe->wBeaconInfoLength = be->wBeaconInfoLength;
+ memcpy(newbe->BeaconInfo, be->BeaconInfo, ielength);
+ *header = &newbe->rceb;
+ *new_size = sizeof(*newbe) + ielength;
+ return 1; /* calling function will free memory */
+}
+
+
+/* DRP Availability change notification (WUSB 1.0 [8.6.3.8]) */
+struct uwb_rc_evt_drp_avail_WUSB_0100 {
+ struct uwb_rceb rceb;
+ __le16 wIELength;
+ u8 IEData[];
+} __attribute__((packed));
+
+/**
+ * Filter WUSB 1.0 DRP AVAILABILITY CHANGE notification to be WHCI 0.95
+ *
+ * @header: the incoming event
+ * @buf_size: size of buffer containing incoming event
+ * @new_size: size of event after filtering completed
+ */
+static
+int hwarc_filter_evt_drp_avail_WUSB_0100(struct uwb_rc *rc,
+ struct uwb_rceb **header,
+ const size_t buf_size,
+ size_t *new_size)
+{
+ struct uwb_rc_evt_drp_avail_WUSB_0100 *da;
+ struct uwb_rc_evt_drp_avail *newda;
+ struct uwb_ie_hdr *ie_hdr;
+ size_t bytes_left, ielength;
+ struct device *dev = &rc->uwb_dev.dev;
+
+
+ da = container_of(*header, struct uwb_rc_evt_drp_avail_WUSB_0100, rceb);
+ bytes_left = buf_size;
+ if (bytes_left < sizeof(*da)) {
+ dev_err(dev, "Not enough data to decode DRP Avail "
+ "Notification for filtering. Expected %zu, "
+ "received %zu.\n", (size_t)sizeof(*da), bytes_left);
+ return -EINVAL;
+ }
+ bytes_left -= sizeof(*da);
+ ielength = le16_to_cpu(da->wIELength);
+ if (bytes_left < ielength) {
+ dev_err(dev, "DRP Avail Notification filter: IE length "
+ "[%zu bytes] does not match actual length "
+ "[%zu bytes].\n", ielength, bytes_left);
+ return -EINVAL;
+ }
+ if (ielength < sizeof(*ie_hdr)) {
+ dev_err(dev, "DRP Avail Notification filter: Not enough "
+ "data to decode IE [%zu bytes, %zu needed]\n",
+ ielength, sizeof(*ie_hdr));
+ return -EINVAL;
+ }
+ ie_hdr = (void *) da->IEData;
+ if (ie_hdr->length > 32) {
+ dev_err(dev, "DRP Availability Change event has unexpected "
+ "length for filtering. Expected < 32 bytes, "
+ "got %zu bytes.\n", (size_t)ie_hdr->length);
+ return -EINVAL;
+ }
+ newda = kzalloc(sizeof(*newda), GFP_ATOMIC);
+ if (newda == NULL)
+ return -ENOMEM;
+ newda->rceb = da->rceb;
+ memcpy(newda->bmp, (u8 *) ie_hdr + sizeof(*ie_hdr), ie_hdr->length);
+ *header = &newda->rceb;
+ *new_size = sizeof(*newda);
+ return 1; /* calling function will free memory */
+}
+
+
+/* DRP notification (WUSB 1.0 [8.6.3.9]) */
+struct uwb_rc_evt_drp_WUSB_0100 {
+ struct uwb_rceb rceb;
+ struct uwb_dev_addr wSrcAddr;
+ u8 bExplicit;
+ __le16 wIELength;
+ u8 IEData[];
+} __attribute__((packed));
+
+/**
+ * Filter WUSB 1.0 DRP Notification to be WHCI 0.95
+ *
+ * @header: the incoming event
+ * @buf_size: size of buffer containing incoming event
+ * @new_size: size of event after filtering completed
+ *
+ * It is hard to manage DRP reservations without having a Reason code.
+ * Unfortunately there is none in the WUSB spec. We just set the default to
+ * DRP IE RECEIVED.
+ * We do not currently use the bBeaconSlotNumber value, so we set this to
+ * zero for now.
+ */
+static
+int hwarc_filter_evt_drp_WUSB_0100(struct uwb_rc *rc,
+ struct uwb_rceb **header,
+ const size_t buf_size,
+ size_t *new_size)
+{
+ struct uwb_rc_evt_drp_WUSB_0100 *drpev;
+ struct uwb_rc_evt_drp *newdrpev;
+ size_t bytes_left, ielength;
+ struct device *dev = &rc->uwb_dev.dev;
+
+ drpev = container_of(*header, struct uwb_rc_evt_drp_WUSB_0100, rceb);
+ bytes_left = buf_size;
+ if (bytes_left < sizeof(*drpev)) {
+ dev_err(dev, "Not enough data to decode DRP Notification "
+ "for filtering. Expected %zu, received %zu.\n",
+ (size_t)sizeof(*drpev), bytes_left);
+ return -EINVAL;
+ }
+ ielength = le16_to_cpu(drpev->wIELength);
+ bytes_left -= sizeof(*drpev);
+ if (bytes_left < ielength) {
+ dev_err(dev, "DRP Notification filter: header length [%zu "
+ "bytes] does not match actual length [%zu "
+ "bytes].\n", ielength, bytes_left);
+ return -EINVAL;
+ }
+ newdrpev = kzalloc(sizeof(*newdrpev) + ielength, GFP_ATOMIC);
+ if (newdrpev == NULL)
+ return -ENOMEM;
+ newdrpev->rceb = drpev->rceb;
+ newdrpev->src_addr = drpev->wSrcAddr;
+ newdrpev->reason = UWB_DRP_NOTIF_DRP_IE_RCVD;
+ newdrpev->beacon_slot_number = 0;
+ newdrpev->ie_length = drpev->wIELength;
+ memcpy(newdrpev->ie_data, drpev->IEData, ielength);
+ *header = &newdrpev->rceb;
+ *new_size = sizeof(*newdrpev) + ielength;
+ return 1; /* calling function will free memory */
+}
+
+
+/* Scan Command (WUSB 1.0 [8.6.2.5]) */
+struct uwb_rc_cmd_scan_WUSB_0100 {
+ struct uwb_rccb rccb;
+ u8 bChannelNumber;
+ u8 bScanState;
+} __attribute__((packed));
+
+/**
+ * Filter WHCI 0.95 SCAN command to be WUSB 1.0 SCAN command
+ *
+ * @header: command sent to device (compliant to WHCI 0.95)
+ * @size: size of command sent to device
+ *
+ * We only reduce the size by two bytes because the WUSB 1.0 scan command
+ * does not have the last field (wStarttime). Also, make sure we don't send
+ * the device an unexpected scan type.
+ */
+static
+int hwarc_filter_cmd_scan_WUSB_0100(struct uwb_rc *rc,
+ struct uwb_rccb **header,
+ size_t *size)
+{
+ struct uwb_rc_cmd_scan *sc;
+
+ sc = container_of(*header, struct uwb_rc_cmd_scan, rccb);
+
+ if (sc->bScanState == UWB_SCAN_ONLY_STARTTIME)
+ sc->bScanState = UWB_SCAN_ONLY;
+ /* Don't send the last two bytes. */
+ *size -= 2;
+ return 0;
+}
+
+
+/* SET DRP IE command (WUSB 1.0 [8.6.2.7]) */
+struct uwb_rc_cmd_set_drp_ie_WUSB_0100 {
+ struct uwb_rccb rccb;
+ u8 bExplicit;
+ __le16 wIELength;
+ struct uwb_ie_drp IEData[];
+} __attribute__((packed));
+
+/**
+ * Filter WHCI 0.95 SET DRP IE command to be WUSB 1.0 SET DRP IE command
+ *
+ * @header: command sent to device (compliant to WHCI 0.95)
+ * @size: size of command sent to device
+ *
+ * WUSB has an extra bExplicit field - we assume always explicit
+ * negotiation so this field is set. The command expected by the device is
+ * thus larger than the one prepared by the driver so we need to
+ * reallocate memory to accommodate this.
+ * We trust the driver to send us the correct data so no checking is done
+ * on incoming data - evn though it is variable length.
+ */
+static
+int hwarc_filter_cmd_set_drp_ie_WUSB_0100(struct uwb_rc *rc,
+ struct uwb_rccb **header,
+ size_t *size)
+{
+ struct uwb_rc_cmd_set_drp_ie *orgcmd;
+ struct uwb_rc_cmd_set_drp_ie_WUSB_0100 *cmd;
+ size_t ielength;
+
+ orgcmd = container_of(*header, struct uwb_rc_cmd_set_drp_ie, rccb);
+ ielength = le16_to_cpu(orgcmd->wIELength);
+ cmd = kzalloc(sizeof(*cmd) + ielength, GFP_KERNEL);
+ if (cmd == NULL)
+ return -ENOMEM;
+ cmd->rccb = orgcmd->rccb;
+ cmd->bExplicit = 0;
+ cmd->wIELength = orgcmd->wIELength;
+ memcpy(cmd->IEData, orgcmd->IEData, ielength);
+ *header = &cmd->rccb;
+ *size = sizeof(*cmd) + ielength;
+ return 1; /* calling function will free memory */
+}
+
+
+/**
+ * Filter data from WHCI driver to WUSB device
+ *
+ * @header: WHCI 0.95 compliant command from driver
+ * @size: length of command
+ *
+ * The routine managing commands to the device (uwb_rc_cmd()) will call the
+ * filtering function pointer (if it exists) before it passes any data to
+ * the device. At this time the command has been formatted according to
+ * WHCI 0.95 and is ready to be sent to the device.
+ *
+ * The filter function will be provided with the current command and its
+ * length. The function will manipulate the command if necessary and
+ * potentially reallocate memory for a command that needed more memory that
+ * the given command. If new memory was created the function will return 1
+ * to indicate to the calling function that the memory need to be freed
+ * when not needed any more. The size will contain the new length of the
+ * command.
+ * If memory has not been allocated we rely on the original mechanisms to
+ * free the memory of the command - even when we reduce the value of size.
+ */
+static
+int hwarc_filter_cmd_WUSB_0100(struct uwb_rc *rc, struct uwb_rccb **header,
+ size_t *size)
+{
+ int result;
+ struct uwb_rccb *rccb = *header;
+ int cmd = le16_to_cpu(rccb->wCommand);
+ switch (cmd) {
+ case UWB_RC_CMD_SCAN:
+ result = hwarc_filter_cmd_scan_WUSB_0100(rc, header, size);
+ break;
+ case UWB_RC_CMD_SET_DRP_IE:
+ result = hwarc_filter_cmd_set_drp_ie_WUSB_0100(rc, header, size);
+ break;
+ default:
+ result = -ENOANO;
+ break;
+ }
+ return result;
+}
+
+
+/**
+ * Filter data from WHCI driver to WUSB device
+ *
+ * @header: WHCI 0.95 compliant command from driver
+ * @size: length of command
+ *
+ * Filter commands based on which protocol the device supports. The WUSB
+ * errata should be the same as WHCI 0.95 so we do not filter that here -
+ * only WUSB 1.0.
+ */
+static
+int hwarc_filter_cmd(struct uwb_rc *rc, struct uwb_rccb **header,
+ size_t *size)
+{
+ int result = -ENOANO;
+ if (rc->version == 0x0100)
+ result = hwarc_filter_cmd_WUSB_0100(rc, header, size);
+ return result;
+}
+
+
+/**
+ * Compute return value as sum of incoming value and value at given offset
+ *
+ * @rceb: event for which we compute the size, it contains a variable
+ * length field.
+ * @core_size: size of the "non variable" part of the event
+ * @offset: place in event where the length of the variable part is stored
+ * @buf_size: total length of buffer in which event arrived - we need to make
+ * sure we read the offset in memory that is still part of the event
+ */
+static
+ssize_t hwarc_get_event_size(struct uwb_rc *rc, const struct uwb_rceb *rceb,
+ size_t core_size, size_t offset,
+ const size_t buf_size)
+{
+ ssize_t size = -ENOSPC;
+ const void *ptr = rceb;
+ size_t type_size = sizeof(__le16);
+ struct device *dev = &rc->uwb_dev.dev;
+
+ if (offset + type_size >= buf_size) {
+ dev_err(dev, "Not enough data to read extra size of event "
+ "0x%02x/%04x/%02x, only got %zu bytes.\n",
+ rceb->bEventType, le16_to_cpu(rceb->wEvent),
+ rceb->bEventContext, buf_size);
+ goto out;
+ }
+ ptr += offset;
+ size = core_size + le16_to_cpu(*(__le16 *)ptr);
+out:
+ return size;
+}
+
+
+/* Beacon slot change notification (WUSB 1.0 [8.6.3.5]) */
+struct uwb_rc_evt_bp_slot_change_WUSB_0100 {
+ struct uwb_rceb rceb;
+ u8 bSlotNumber;
+} __attribute__((packed));
+
+
+/**
+ * Filter data from WUSB device to WHCI driver
+ *
+ * @header: incoming event
+ * @buf_size: size of buffer in which event arrived
+ * @_event_size: actual size of event in the buffer
+ * @new_size: size of event after filtered
+ *
+ * We don't know how the buffer is constructed - there may be more than one
+ * event in it so buffer length does not determine event length. We first
+ * determine the expected size of the incoming event. This value is passed
+ * back only if the actual filtering succeeded (so we know the computed
+ * expected size is correct). This value will be zero if
+ * the event did not need any filtering.
+ *
+ * WHCI interprets the BP Slot Change event's data differently than
+ * WUSB. The event sizes are exactly the same. The data field
+ * indicates the new beacon slot in which a RC is transmitting its
+ * beacon. The maximum value of this is 96 (wMacBPLength ECMA-368
+ * 17.16 (Table 117)). We thus know that the WUSB value will not set
+ * the bit bNoSlot, so we don't really do anything (placeholder).
+ */
+static
+int hwarc_filter_event_WUSB_0100(struct uwb_rc *rc, struct uwb_rceb **header,
+ const size_t buf_size, size_t *_real_size,
+ size_t *_new_size)
+{
+ int result = -ENOANO;
+ struct uwb_rceb *rceb = *header;
+ int event = le16_to_cpu(rceb->wEvent);
+ ssize_t event_size;
+ size_t core_size, offset;
+
+ if (rceb->bEventType != UWB_RC_CET_GENERAL)
+ goto out;
+ switch (event) {
+ case UWB_RC_EVT_BEACON:
+ core_size = sizeof(struct uwb_rc_evt_beacon_WUSB_0100);
+ offset = offsetof(struct uwb_rc_evt_beacon_WUSB_0100,
+ wBeaconInfoLength);
+ event_size = hwarc_get_event_size(rc, rceb, core_size,
+ offset, buf_size);
+ if (event_size < 0)
+ goto out;
+ *_real_size = event_size;
+ result = hwarc_filter_evt_beacon_WUSB_0100(rc, header,
+ buf_size, _new_size);
+ break;
+ case UWB_RC_EVT_BP_SLOT_CHANGE:
+ *_new_size = *_real_size =
+ sizeof(struct uwb_rc_evt_bp_slot_change_WUSB_0100);
+ result = 0;
+ break;
+
+ case UWB_RC_EVT_DRP_AVAIL:
+ core_size = sizeof(struct uwb_rc_evt_drp_avail_WUSB_0100);
+ offset = offsetof(struct uwb_rc_evt_drp_avail_WUSB_0100,
+ wIELength);
+ event_size = hwarc_get_event_size(rc, rceb, core_size,
+ offset, buf_size);
+ if (event_size < 0)
+ goto out;
+ *_real_size = event_size;
+ result = hwarc_filter_evt_drp_avail_WUSB_0100(
+ rc, header, buf_size, _new_size);
+ break;
+
+ case UWB_RC_EVT_DRP:
+ core_size = sizeof(struct uwb_rc_evt_drp_WUSB_0100);
+ offset = offsetof(struct uwb_rc_evt_drp_WUSB_0100, wIELength);
+ event_size = hwarc_get_event_size(rc, rceb, core_size,
+ offset, buf_size);
+ if (event_size < 0)
+ goto out;
+ *_real_size = event_size;
+ result = hwarc_filter_evt_drp_WUSB_0100(rc, header,
+ buf_size, _new_size);
+ break;
+
+ default:
+ break;
+ }
+out:
+ return result;
+}
+
+/**
+ * Filter data from WUSB device to WHCI driver
+ *
+ * @header: incoming event
+ * @buf_size: size of buffer in which event arrived
+ * @_event_size: actual size of event in the buffer
+ * @_new_size: size of event after filtered
+ *
+ * Filter events based on which protocol the device supports. The WUSB
+ * errata should be the same as WHCI 0.95 so we do not filter that here -
+ * only WUSB 1.0.
+ *
+ * If we don't handle it, we return -ENOANO (why the weird error code?
+ * well, so if I get it, I can pinpoint in the code that raised
+ * it...after all, not too many places use the higher error codes).
+ */
+static
+int hwarc_filter_event(struct uwb_rc *rc, struct uwb_rceb **header,
+ const size_t buf_size, size_t *_real_size,
+ size_t *_new_size)
+{
+ int result = -ENOANO;
+ if (rc->version == 0x0100)
+ result = hwarc_filter_event_WUSB_0100(
+ rc, header, buf_size, _real_size, _new_size);
+ return result;
+}
+
+
+/**
+ * Execute an UWB RC command on HWA
+ *
+ * @rc: Instance of a Radio Controller that is a HWA
+ * @cmd: Buffer containing the RCCB and payload to execute
+ * @cmd_size: Size of the command buffer.
+ *
+ * NOTE: rc's mutex has to be locked
+ */
+static
+int hwarc_cmd(struct uwb_rc *uwb_rc, const struct uwb_rccb *cmd, size_t cmd_size)
+{
+ struct hwarc *hwarc = uwb_rc->priv;
+ return usb_control_msg(
+ hwarc->usb_dev, usb_sndctrlpipe(hwarc->usb_dev, 0),
+ WA_EXEC_RC_CMD, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, hwarc->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ (void *) cmd, cmd_size, 100 /* FIXME: this is totally arbitrary */);
+}
+
+static
+int hwarc_reset(struct uwb_rc *uwb_rc)
+{
+ struct hwarc *hwarc = uwb_rc->priv;
+ int result;
+
+ /* device lock must be held when calling usb_reset_device. */
+ result = usb_lock_device_for_reset(hwarc->usb_dev, NULL);
+ if (result >= 0) {
+ result = usb_reset_device(hwarc->usb_dev);
+ usb_unlock_device(hwarc->usb_dev);
+ }
+
+ return result;
+}
+
+/**
+ * Callback for the notification and event endpoint
+ *
+ * Check's that everything is fine and then passes the read data to
+ * the notification/event handling mechanism (neh).
+ */
+static
+void hwarc_neep_cb(struct urb *urb)
+{
+ struct hwarc *hwarc = urb->context;
+ struct usb_interface *usb_iface = hwarc->usb_iface;
+ struct device *dev = &usb_iface->dev;
+ int result;
+
+ switch (result = urb->status) {
+ case 0:
+ uwb_rc_neh_grok(hwarc->uwb_rc, urb->transfer_buffer,
+ urb->actual_length);
+ break;
+ case -ECONNRESET: /* Not an error, but a controlled situation; */
+ case -ENOENT: /* (we killed the URB)...so, no broadcast */
+ goto out;
+ case -ESHUTDOWN: /* going away! */
+ goto out;
+ default: /* On general errors, retry unless it gets ugly */
+ if (edc_inc(&hwarc->neep_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME))
+ goto error_exceeded;
+ dev_err(dev, "NEEP: URB error %d\n", urb->status);
+ }
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result < 0 && result != -ENODEV && result != -EPERM) {
+ /* ignoring unrecoverable errors */
+ dev_err(dev, "NEEP: Can't resubmit URB (%d) resetting device\n",
+ result);
+ goto error;
+ }
+out:
+ return;
+
+error_exceeded:
+ dev_err(dev, "NEEP: URB max acceptable errors "
+ "exceeded, resetting device\n");
+error:
+ uwb_rc_neh_error(hwarc->uwb_rc, result);
+ uwb_rc_reset_all(hwarc->uwb_rc);
+ return;
+}
+
+static void hwarc_init(struct hwarc *hwarc)
+{
+ edc_init(&hwarc->neep_edc);
+}
+
+/**
+ * Initialize the notification/event endpoint stuff
+ *
+ * Note this is effectively a parallel thread; it knows that
+ * hwarc->uwb_rc always exists because the existence of a 'hwarc'
+ * means that there is a reverence on the hwarc->uwb_rc (see
+ * _probe()), and thus _neep_cb() can execute safely.
+ */
+static int hwarc_neep_init(struct uwb_rc *rc)
+{
+ struct hwarc *hwarc = rc->priv;
+ struct usb_interface *iface = hwarc->usb_iface;
+ struct usb_device *usb_dev = interface_to_usbdev(iface);
+ struct device *dev = &iface->dev;
+ int result;
+ struct usb_endpoint_descriptor *epd;
+
+ epd = &iface->cur_altsetting->endpoint[0].desc;
+ hwarc->rd_buffer = (void *) __get_free_page(GFP_KERNEL);
+ if (hwarc->rd_buffer == NULL) {
+ dev_err(dev, "Unable to allocate notification's read buffer\n");
+ goto error_rd_buffer;
+ }
+ hwarc->neep_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (hwarc->neep_urb == NULL)
+ goto error_urb_alloc;
+ usb_fill_int_urb(hwarc->neep_urb, usb_dev,
+ usb_rcvintpipe(usb_dev, epd->bEndpointAddress),
+ hwarc->rd_buffer, PAGE_SIZE,
+ hwarc_neep_cb, hwarc, epd->bInterval);
+ result = usb_submit_urb(hwarc->neep_urb, GFP_ATOMIC);
+ if (result < 0) {
+ dev_err(dev, "Cannot submit notification URB: %d\n", result);
+ goto error_neep_submit;
+ }
+ return 0;
+
+error_neep_submit:
+ usb_free_urb(hwarc->neep_urb);
+ hwarc->neep_urb = NULL;
+error_urb_alloc:
+ free_page((unsigned long)hwarc->rd_buffer);
+ hwarc->rd_buffer = NULL;
+error_rd_buffer:
+ return -ENOMEM;
+}
+
+
+/** Clean up all the notification endpoint resources */
+static void hwarc_neep_release(struct uwb_rc *rc)
+{
+ struct hwarc *hwarc = rc->priv;
+
+ usb_kill_urb(hwarc->neep_urb);
+ usb_free_urb(hwarc->neep_urb);
+ hwarc->neep_urb = NULL;
+
+ free_page((unsigned long)hwarc->rd_buffer);
+ hwarc->rd_buffer = NULL;
+}
+
+/**
+ * Get the version from class-specific descriptor
+ *
+ * NOTE: this descriptor comes with the big bundled configuration
+ * descriptor that includes the interfaces' and endpoints', so
+ * we just look for it in the cached copy kept by the USB stack.
+ *
+ * NOTE2: We convert LE fields to CPU order.
+ */
+static int hwarc_get_version(struct uwb_rc *rc)
+{
+ int result;
+
+ struct hwarc *hwarc = rc->priv;
+ struct uwb_rc_control_intf_class_desc *descr;
+ struct device *dev = &rc->uwb_dev.dev;
+ struct usb_device *usb_dev = hwarc->usb_dev;
+ char *itr;
+ struct usb_descriptor_header *hdr;
+ size_t itr_size, actconfig_idx;
+ u16 version;
+
+ actconfig_idx = (usb_dev->actconfig - usb_dev->config) /
+ sizeof(usb_dev->config[0]);
+ itr = usb_dev->rawdescriptors[actconfig_idx];
+ itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
+ while (itr_size >= sizeof(*hdr)) {
+ hdr = (struct usb_descriptor_header *) itr;
+ dev_dbg(dev, "Extra device descriptor: "
+ "type %02x/%u bytes @ %zu (%zu left)\n",
+ hdr->bDescriptorType, hdr->bLength,
+ (itr - usb_dev->rawdescriptors[actconfig_idx]),
+ itr_size);
+ if (hdr->bDescriptorType == USB_DT_CS_RADIO_CONTROL)
+ goto found;
+ itr += hdr->bLength;
+ itr_size -= hdr->bLength;
+ }
+ dev_err(dev, "cannot find Radio Control Interface Class descriptor\n");
+ return -ENODEV;
+
+found:
+ result = -EINVAL;
+ if (hdr->bLength > itr_size) { /* is it available? */
+ dev_err(dev, "incomplete Radio Control Interface Class "
+ "descriptor (%zu bytes left, %u needed)\n",
+ itr_size, hdr->bLength);
+ goto error;
+ }
+ if (hdr->bLength < sizeof(*descr)) {
+ dev_err(dev, "short Radio Control Interface Class "
+ "descriptor\n");
+ goto error;
+ }
+ descr = (struct uwb_rc_control_intf_class_desc *) hdr;
+ /* Make LE fields CPU order */
+ version = __le16_to_cpu(descr->bcdRCIVersion);
+ if (version != 0x0100) {
+ dev_err(dev, "Device reports protocol version 0x%04x. We "
+ "do not support that. \n", version);
+ result = -EINVAL;
+ goto error;
+ }
+ rc->version = version;
+ dev_dbg(dev, "Device supports WUSB protocol version 0x%04x \n", rc->version);
+ result = 0;
+error:
+ return result;
+}
+
+/*
+ * By creating a 'uwb_rc', we have a reference on it -- that reference
+ * is the one we drop when we disconnect.
+ *
+ * No need to switch altsettings; according to WUSB1.0[8.6.1.1], there
+ * is only one altsetting allowed.
+ */
+static int hwarc_probe(struct usb_interface *iface,
+ const struct usb_device_id *id)
+{
+ int result;
+ struct uwb_rc *uwb_rc;
+ struct hwarc *hwarc;
+ struct device *dev = &iface->dev;
+
+ if (iface->cur_altsetting->desc.bNumEndpoints < 1)
+ return -ENODEV;
+ if (!usb_endpoint_xfer_int(&iface->cur_altsetting->endpoint[0].desc))
+ return -ENODEV;
+
+ result = -ENOMEM;
+ uwb_rc = uwb_rc_alloc();
+ if (uwb_rc == NULL) {
+ dev_err(dev, "unable to allocate RC instance\n");
+ goto error_rc_alloc;
+ }
+ hwarc = kzalloc(sizeof(*hwarc), GFP_KERNEL);
+ if (hwarc == NULL) {
+ dev_err(dev, "unable to allocate HWA RC instance\n");
+ goto error_alloc;
+ }
+ hwarc_init(hwarc);
+ hwarc->usb_dev = usb_get_dev(interface_to_usbdev(iface));
+ hwarc->usb_iface = usb_get_intf(iface);
+ hwarc->uwb_rc = uwb_rc;
+
+ uwb_rc->owner = THIS_MODULE;
+ uwb_rc->start = hwarc_neep_init;
+ uwb_rc->stop = hwarc_neep_release;
+ uwb_rc->cmd = hwarc_cmd;
+ uwb_rc->reset = hwarc_reset;
+ if (id->driver_info & WUSB_QUIRK_WHCI_CMD_EVT) {
+ uwb_rc->filter_cmd = NULL;
+ uwb_rc->filter_event = NULL;
+ } else {
+ uwb_rc->filter_cmd = hwarc_filter_cmd;
+ uwb_rc->filter_event = hwarc_filter_event;
+ }
+
+ result = uwb_rc_add(uwb_rc, dev, hwarc);
+ if (result < 0)
+ goto error_rc_add;
+ result = hwarc_get_version(uwb_rc);
+ if (result < 0) {
+ dev_err(dev, "cannot retrieve version of RC \n");
+ goto error_get_version;
+ }
+ usb_set_intfdata(iface, hwarc);
+ return 0;
+
+error_get_version:
+ uwb_rc_rm(uwb_rc);
+error_rc_add:
+ usb_put_intf(iface);
+ usb_put_dev(hwarc->usb_dev);
+ kfree(hwarc);
+error_alloc:
+ uwb_rc_put(uwb_rc);
+error_rc_alloc:
+ return result;
+}
+
+static void hwarc_disconnect(struct usb_interface *iface)
+{
+ struct hwarc *hwarc = usb_get_intfdata(iface);
+ struct uwb_rc *uwb_rc = hwarc->uwb_rc;
+
+ usb_set_intfdata(hwarc->usb_iface, NULL);
+ uwb_rc_rm(uwb_rc);
+ usb_put_intf(hwarc->usb_iface);
+ usb_put_dev(hwarc->usb_dev);
+ kfree(hwarc);
+ uwb_rc_put(uwb_rc); /* when creating the device, refcount = 1 */
+}
+
+static int hwarc_pre_reset(struct usb_interface *iface)
+{
+ struct hwarc *hwarc = usb_get_intfdata(iface);
+ struct uwb_rc *uwb_rc = hwarc->uwb_rc;
+
+ uwb_rc_pre_reset(uwb_rc);
+ return 0;
+}
+
+static int hwarc_post_reset(struct usb_interface *iface)
+{
+ struct hwarc *hwarc = usb_get_intfdata(iface);
+ struct uwb_rc *uwb_rc = hwarc->uwb_rc;
+
+ return uwb_rc_post_reset(uwb_rc);
+}
+
+/** USB device ID's that we handle */
+static const struct usb_device_id hwarc_id_table[] = {
+ /* D-Link DUB-1210 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3d02, 0xe0, 0x01, 0x02),
+ .driver_info = WUSB_QUIRK_WHCI_CMD_EVT },
+ /* Intel i1480 (using firmware 1.3PA2-20070828) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x8086, 0x0c3b, 0xe0, 0x01, 0x02),
+ .driver_info = WUSB_QUIRK_WHCI_CMD_EVT },
+ /* Alereon 5310 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5310, 0xe0, 0x01, 0x02),
+ .driver_info = WUSB_QUIRK_WHCI_CMD_EVT },
+ /* Alereon 5611 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5611, 0xe0, 0x01, 0x02),
+ .driver_info = WUSB_QUIRK_WHCI_CMD_EVT },
+ /* Generic match for the Radio Control interface */
+ { USB_INTERFACE_INFO(0xe0, 0x01, 0x02), },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, hwarc_id_table);
+
+static struct usb_driver hwarc_driver = {
+ .name = "hwa-rc",
+ .id_table = hwarc_id_table,
+ .probe = hwarc_probe,
+ .disconnect = hwarc_disconnect,
+ .pre_reset = hwarc_pre_reset,
+ .post_reset = hwarc_post_reset,
+};
+
+module_usb_driver(hwarc_driver);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Host Wireless Adapter Radio Control Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/uwb/i1480/Makefile b/drivers/staging/uwb/i1480/Makefile
new file mode 100644
index 000000000000..d26fb9b845ae
--- /dev/null
+++ b/drivers/staging/uwb/i1480/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_UWB_I1480U) += dfu/ i1480-est.o
diff --git a/drivers/staging/uwb/i1480/dfu/Makefile b/drivers/staging/uwb/i1480/dfu/Makefile
new file mode 100644
index 000000000000..4739fdac5922
--- /dev/null
+++ b/drivers/staging/uwb/i1480/dfu/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_UWB_I1480U) += i1480-dfu-usb.o
+
+i1480-dfu-usb-objs := \
+ dfu.o \
+ mac.o \
+ phy.o \
+ usb.o
+
+
diff --git a/drivers/staging/uwb/i1480/dfu/dfu.c b/drivers/staging/uwb/i1480/dfu/dfu.c
new file mode 100644
index 000000000000..9d51ce8faad1
--- /dev/null
+++ b/drivers/staging/uwb/i1480/dfu/dfu.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Wireless UWB Link 1480
+ * Main driver
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * Common code for firmware upload used by the USB and PCI version;
+ * i1480_fw_upload() takes a device descriptor and uses the function
+ * pointers it provides to upload firmware and prepare the PHY.
+ *
+ * As well, provides common functions used by the rest of the code.
+ */
+#include "i1480-dfu.h"
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/device.h>
+#include <linux/random.h>
+#include <linux/export.h>
+#include "../../uwb.h"
+
+/*
+ * i1480_rceb_check - Check RCEB for expected field values
+ * @i1480: pointer to device for which RCEB is being checked
+ * @rceb: RCEB being checked
+ * @cmd: which command the RCEB is related to
+ * @context: expected context
+ * @expected_type: expected event type
+ * @expected_event: expected event
+ *
+ * If @cmd is NULL, do not print error messages, but still return an error
+ * code.
+ *
+ * Return 0 if @rceb matches the expected values, -EINVAL otherwise.
+ */
+int i1480_rceb_check(const struct i1480 *i1480, const struct uwb_rceb *rceb,
+ const char *cmd, u8 context, u8 expected_type,
+ unsigned expected_event)
+{
+ int result = 0;
+ struct device *dev = i1480->dev;
+ if (rceb->bEventContext != context) {
+ if (cmd)
+ dev_err(dev, "%s: unexpected context id 0x%02x "
+ "(expected 0x%02x)\n", cmd,
+ rceb->bEventContext, context);
+ result = -EINVAL;
+ }
+ if (rceb->bEventType != expected_type) {
+ if (cmd)
+ dev_err(dev, "%s: unexpected event type 0x%02x "
+ "(expected 0x%02x)\n", cmd,
+ rceb->bEventType, expected_type);
+ result = -EINVAL;
+ }
+ if (le16_to_cpu(rceb->wEvent) != expected_event) {
+ if (cmd)
+ dev_err(dev, "%s: unexpected event 0x%04x "
+ "(expected 0x%04x)\n", cmd,
+ le16_to_cpu(rceb->wEvent), expected_event);
+ result = -EINVAL;
+ }
+ return result;
+}
+EXPORT_SYMBOL_GPL(i1480_rceb_check);
+
+
+/*
+ * Execute a Radio Control Command
+ *
+ * Command data has to be in i1480->cmd_buf.
+ *
+ * @returns size of the reply data filled in i1480->evt_buf or < 0 errno
+ * code on error.
+ */
+ssize_t i1480_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size,
+ size_t reply_size)
+{
+ ssize_t result;
+ struct uwb_rceb *reply = i1480->evt_buf;
+ struct uwb_rccb *cmd = i1480->cmd_buf;
+ u16 expected_event = reply->wEvent;
+ u8 expected_type = reply->bEventType;
+ u8 context;
+
+ init_completion(&i1480->evt_complete);
+ i1480->evt_result = -EINPROGRESS;
+ do {
+ get_random_bytes(&context, 1);
+ } while (context == 0x00 || context == 0xff);
+ cmd->bCommandContext = context;
+ result = i1480->cmd(i1480, cmd_name, cmd_size);
+ if (result < 0)
+ goto error;
+ /* wait for the callback to report a event was received */
+ result = wait_for_completion_interruptible_timeout(
+ &i1480->evt_complete, HZ);
+ if (result == 0) {
+ result = -ETIMEDOUT;
+ goto error;
+ }
+ if (result < 0)
+ goto error;
+ result = i1480->evt_result;
+ if (result < 0) {
+ dev_err(i1480->dev, "%s: command reply reception failed: %zd\n",
+ cmd_name, result);
+ goto error;
+ }
+ /*
+ * Firmware versions >= 1.4.12224 for IOGear GUWA100U generate a
+ * spurious notification after firmware is downloaded. So check whether
+ * the receibed RCEB is such notification before assuming that the
+ * command has failed.
+ */
+ if (i1480_rceb_check(i1480, i1480->evt_buf, NULL,
+ 0, 0xfd, 0x0022) == 0) {
+ /* Now wait for the actual RCEB for this command. */
+ result = i1480->wait_init_done(i1480);
+ if (result < 0)
+ goto error;
+ result = i1480->evt_result;
+ }
+ if (result != reply_size) {
+ dev_err(i1480->dev, "%s returned only %zu bytes, %zu expected\n",
+ cmd_name, result, reply_size);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Verify we got the right event in response */
+ result = i1480_rceb_check(i1480, i1480->evt_buf, cmd_name, context,
+ expected_type, expected_event);
+error:
+ return result;
+}
+EXPORT_SYMBOL_GPL(i1480_cmd);
+
+
+static
+int i1480_print_state(struct i1480 *i1480)
+{
+ int result;
+ u32 *buf = (u32 *) i1480->cmd_buf;
+
+ result = i1480->read(i1480, 0x80080000, 2 * sizeof(*buf));
+ if (result < 0) {
+ dev_err(i1480->dev, "cannot read U & L states: %d\n", result);
+ goto error;
+ }
+ dev_info(i1480->dev, "state U 0x%08x, L 0x%08x\n", buf[0], buf[1]);
+error:
+ return result;
+}
+
+
+/*
+ * PCI probe, firmware uploader
+ *
+ * _mac_fw_upload() will call rc_setup(), which needs an rc_release().
+ */
+int i1480_fw_upload(struct i1480 *i1480)
+{
+ int result;
+
+ result = i1480_pre_fw_upload(i1480); /* PHY pre fw */
+ if (result < 0 && result != -ENOENT) {
+ i1480_print_state(i1480);
+ goto error;
+ }
+ result = i1480_mac_fw_upload(i1480); /* MAC fw */
+ if (result < 0) {
+ if (result == -ENOENT)
+ dev_err(i1480->dev, "Cannot locate MAC FW file '%s'\n",
+ i1480->mac_fw_name);
+ else
+ i1480_print_state(i1480);
+ goto error;
+ }
+ result = i1480_phy_fw_upload(i1480); /* PHY fw */
+ if (result < 0 && result != -ENOENT) {
+ i1480_print_state(i1480);
+ goto error_rc_release;
+ }
+ /*
+ * FIXME: find some reliable way to check whether firmware is running
+ * properly. Maybe use some standard request that has no side effects?
+ */
+ dev_info(i1480->dev, "firmware uploaded successfully\n");
+error_rc_release:
+ if (i1480->rc_release)
+ i1480->rc_release(i1480);
+ result = 0;
+error:
+ return result;
+}
+EXPORT_SYMBOL_GPL(i1480_fw_upload);
diff --git a/drivers/staging/uwb/i1480/dfu/i1480-dfu.h b/drivers/staging/uwb/i1480/dfu/i1480-dfu.h
new file mode 100644
index 000000000000..b21d058ecc23
--- /dev/null
+++ b/drivers/staging/uwb/i1480/dfu/i1480-dfu.h
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * i1480 Device Firmware Upload
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This driver is the firmware uploader for the Intel Wireless UWB
+ * Link 1480 device (both in the USB and PCI incarnations).
+ *
+ * The process is quite simple: we stop the device, write the firmware
+ * to its memory and then restart it. Wait for the device to let us
+ * know it is done booting firmware. Ready.
+ *
+ * We might have to upload before or after a phy firmware (which might
+ * be done in two methods, using a normal firmware image or through
+ * the MPI port).
+ *
+ * Because USB and PCI use common methods, we just make ops out of the
+ * common operations (read, write, wait_init_done and cmd) and
+ * implement them in usb.c and pci.c.
+ *
+ * The flow is (some parts omitted):
+ *
+ * i1480_{usb,pci}_probe() On enumerate/discovery
+ * i1480_fw_upload()
+ * i1480_pre_fw_upload()
+ * __mac_fw_upload()
+ * fw_hdrs_load()
+ * mac_fw_hdrs_push()
+ * i1480->write() [i1480_{usb,pci}_write()]
+ * i1480_fw_cmp()
+ * i1480->read() [i1480_{usb,pci}_read()]
+ * i1480_mac_fw_upload()
+ * __mac_fw_upload()
+ * i1480->setup(()
+ * i1480->wait_init_done()
+ * i1480_cmd_reset()
+ * i1480->cmd() [i1480_{usb,pci}_cmd()]
+ * ...
+ * i1480_phy_fw_upload()
+ * request_firmware()
+ * i1480_mpi_write()
+ * i1480->cmd() [i1480_{usb,pci}_cmd()]
+ *
+ * Once the probe function enumerates the device and uploads the
+ * firmware, we just exit with -ENODEV, as we don't really want to
+ * attach to the device.
+ */
+#ifndef __i1480_DFU_H__
+#define __i1480_DFU_H__
+
+#include <linux/types.h>
+#include <linux/completion.h>
+#include "../../include/spec.h"
+
+#define i1480_FW_UPLOAD_MODE_MASK (cpu_to_le32(0x00000018))
+
+#if i1480_FW > 0x00000302
+#define i1480_RCEB_EXTENDED
+#endif
+
+struct uwb_rccb;
+struct uwb_rceb;
+
+/*
+ * Common firmware upload handlers
+ *
+ * Normally you embed this struct in another one specific to your hw.
+ *
+ * @write Write to device's memory from buffer.
+ * @read Read from device's memory to i1480->evt_buf.
+ * @setup Setup device after basic firmware is uploaded
+ * @wait_init_done
+ * Wait for the device to send a notification saying init
+ * is done.
+ * @cmd FOP for issuing the command to the hardware. The
+ * command data is contained in i1480->cmd_buf and the size
+ * is supplied as an argument. The command replied is put
+ * in i1480->evt_buf and the size in i1480->evt_result (or if
+ * an error, a < 0 errno code).
+ *
+ * @cmd_buf Memory buffer used to send commands to the device.
+ * Allocated by the upper layers i1480_fw_upload().
+ * Size has to be @buf_size.
+ * @evt_buf Memory buffer used to place the async notifications
+ * received by the hw. Allocated by the upper layers
+ * i1480_fw_upload().
+ * Size has to be @buf_size.
+ * @cmd_complete
+ * Low level driver uses this to notify code waiting afor
+ * an event that the event has arrived and data is in
+ * i1480->evt_buf (and size/result in i1480->evt_result).
+ * @hw_rev
+ * Use this value to activate dfu code to support new revisions
+ * of hardware. i1480_init() sets this to a default value.
+ * It should be updated by the USB and PCI code.
+ */
+struct i1480 {
+ struct device *dev;
+
+ int (*write)(struct i1480 *, u32 addr, const void *, size_t);
+ int (*read)(struct i1480 *, u32 addr, size_t);
+ int (*rc_setup)(struct i1480 *);
+ void (*rc_release)(struct i1480 *);
+ int (*wait_init_done)(struct i1480 *);
+ int (*cmd)(struct i1480 *, const char *cmd_name, size_t cmd_size);
+ const char *pre_fw_name;
+ const char *mac_fw_name;
+ const char *mac_fw_name_deprecate; /* FIXME: Will go away */
+ const char *phy_fw_name;
+ u8 hw_rev;
+
+ size_t buf_size; /* size of both evt_buf and cmd_buf */
+ void *evt_buf, *cmd_buf;
+ ssize_t evt_result;
+ struct completion evt_complete;
+};
+
+static inline
+void i1480_init(struct i1480 *i1480)
+{
+ i1480->hw_rev = 1;
+ init_completion(&i1480->evt_complete);
+}
+
+extern int i1480_fw_upload(struct i1480 *);
+extern int i1480_pre_fw_upload(struct i1480 *);
+extern int i1480_mac_fw_upload(struct i1480 *);
+extern int i1480_phy_fw_upload(struct i1480 *);
+extern ssize_t i1480_cmd(struct i1480 *, const char *, size_t, size_t);
+extern int i1480_rceb_check(const struct i1480 *,
+ const struct uwb_rceb *, const char *, u8,
+ u8, unsigned);
+
+enum {
+ /* Vendor specific command type */
+ i1480_CET_VS1 = 0xfd,
+ /* i1480 commands */
+ i1480_CMD_SET_IP_MAS = 0x000e,
+ i1480_CMD_GET_MAC_PHY_INFO = 0x0003,
+ i1480_CMD_MPI_WRITE = 0x000f,
+ i1480_CMD_MPI_READ = 0x0010,
+ /* i1480 events */
+#if i1480_FW > 0x00000302
+ i1480_EVT_CONFIRM = 0x0002,
+ i1480_EVT_RM_INIT_DONE = 0x0101,
+ i1480_EVT_DEV_ADD = 0x0103,
+ i1480_EVT_DEV_RM = 0x0104,
+ i1480_EVT_DEV_ID_CHANGE = 0x0105,
+ i1480_EVT_GET_MAC_PHY_INFO = i1480_CMD_GET_MAC_PHY_INFO,
+#else
+ i1480_EVT_CONFIRM = 0x0002,
+ i1480_EVT_RM_INIT_DONE = 0x0101,
+ i1480_EVT_DEV_ADD = 0x0103,
+ i1480_EVT_DEV_RM = 0x0104,
+ i1480_EVT_DEV_ID_CHANGE = 0x0105,
+ i1480_EVT_GET_MAC_PHY_INFO = i1480_EVT_CONFIRM,
+#endif
+};
+
+
+struct i1480_evt_confirm {
+ struct uwb_rceb rceb;
+#ifdef i1480_RCEB_EXTENDED
+ __le16 wParamLength;
+#endif
+ u8 bResultCode;
+} __attribute__((packed));
+
+
+struct i1480_rceb {
+ struct uwb_rceb rceb;
+#ifdef i1480_RCEB_EXTENDED
+ __le16 wParamLength;
+#endif
+} __attribute__((packed));
+
+
+/**
+ * Get MAC & PHY Information confirm event structure
+ *
+ * Confirm event returned by the command.
+ */
+struct i1480_evt_confirm_GMPI {
+#if i1480_FW > 0x00000302
+ struct uwb_rceb rceb;
+ __le16 wParamLength;
+ __le16 status;
+ u8 mac_addr[6]; /* EUI-64 bit IEEE address [still 8 bytes?] */
+ u8 dev_addr[2];
+ __le16 mac_fw_rev; /* major = v >> 8; minor = v & 0xff */
+ u8 hw_rev;
+ u8 phy_vendor;
+ u8 phy_rev; /* major v = >> 8; minor = v & 0xff */
+ __le16 mac_caps;
+ u8 phy_caps[3];
+ u8 key_stores;
+ __le16 mcast_addr_stores;
+ u8 sec_mode_supported;
+#else
+ struct uwb_rceb rceb;
+ u8 status;
+ u8 mac_addr[8]; /* EUI-64 bit IEEE address [still 8 bytes?] */
+ u8 dev_addr[2];
+ __le16 mac_fw_rev; /* major = v >> 8; minor = v & 0xff */
+ __le16 phy_fw_rev; /* major v = >> 8; minor = v & 0xff */
+ __le16 mac_caps;
+ u8 phy_caps;
+ u8 key_stores;
+ __le16 mcast_addr_stores;
+ u8 sec_mode_supported;
+#endif
+} __attribute__((packed));
+
+
+struct i1480_cmd_mpi_write {
+ struct uwb_rccb rccb;
+ __le16 size;
+ u8 data[];
+};
+
+
+struct i1480_cmd_mpi_read {
+ struct uwb_rccb rccb;
+ __le16 size;
+ struct {
+ u8 page, offset;
+ } __attribute__((packed)) data[];
+} __attribute__((packed));
+
+
+struct i1480_evt_mpi_read {
+ struct uwb_rceb rceb;
+#ifdef i1480_RCEB_EXTENDED
+ __le16 wParamLength;
+#endif
+ u8 bResultCode;
+ __le16 size;
+ struct {
+ u8 page, offset, value;
+ } __attribute__((packed)) data[];
+} __attribute__((packed));
+
+
+#endif /* #ifndef __i1480_DFU_H__ */
diff --git a/drivers/staging/uwb/i1480/dfu/mac.c b/drivers/staging/uwb/i1480/dfu/mac.c
new file mode 100644
index 000000000000..6e4d6c9cecf5
--- /dev/null
+++ b/drivers/staging/uwb/i1480/dfu/mac.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Wireless UWB Link 1480
+ * MAC Firmware upload implementation
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * Implementation of the code for parsing the firmware file (extract
+ * the headers and binary code chunks) in the fw_*() functions. The
+ * code to upload pre and mac firmwares is the same, so it uses a
+ * common entry point in __mac_fw_upload(), which uses the i1480
+ * function pointers to push the firmware to the device.
+ */
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include "../../uwb.h"
+#include "i1480-dfu.h"
+
+/*
+ * Descriptor for a continuous segment of MAC fw data
+ */
+struct fw_hdr {
+ unsigned long address;
+ size_t length;
+ const u32 *bin;
+ struct fw_hdr *next;
+};
+
+
+/* Free a chain of firmware headers */
+static
+void fw_hdrs_free(struct fw_hdr *hdr)
+{
+ struct fw_hdr *next;
+
+ while (hdr) {
+ next = hdr->next;
+ kfree(hdr);
+ hdr = next;
+ }
+}
+
+
+/* Fill a firmware header descriptor from a memory buffer */
+static
+int fw_hdr_load(struct i1480 *i1480, struct fw_hdr *hdr, unsigned hdr_cnt,
+ const char *_data, const u32 *data_itr, const u32 *data_top)
+{
+ size_t hdr_offset = (const char *) data_itr - _data;
+ size_t remaining_size = (void *) data_top - (void *) data_itr;
+ if (data_itr + 2 > data_top) {
+ dev_err(i1480->dev, "fw hdr #%u/%zu: EOF reached in header at "
+ "offset %zu, limit %zu\n",
+ hdr_cnt, hdr_offset,
+ (const char *) data_itr + 2 - _data,
+ (const char *) data_top - _data);
+ return -EINVAL;
+ }
+ hdr->next = NULL;
+ hdr->address = le32_to_cpu(*data_itr++);
+ hdr->length = le32_to_cpu(*data_itr++);
+ hdr->bin = data_itr;
+ if (hdr->length > remaining_size) {
+ dev_err(i1480->dev, "fw hdr #%u/%zu: EOF reached in data; "
+ "chunk too long (%zu bytes), only %zu left\n",
+ hdr_cnt, hdr_offset, hdr->length, remaining_size);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+
+/**
+ * Get a buffer where the firmware is supposed to be and create a
+ * chain of headers linking them together.
+ *
+ * @phdr: where to place the pointer to the first header (headers link
+ * to the next via the @hdr->next ptr); need to free the whole
+ * chain when done.
+ *
+ * @_data: Pointer to the data buffer.
+ *
+ * @_data_size: Size of the data buffer (bytes); data size has to be a
+ * multiple of 4. Function will fail if not.
+ *
+ * Goes over the whole binary blob; reads the first chunk and creates
+ * a fw hdr from it (which points to where the data is in @_data and
+ * the length of the chunk); then goes on to the next chunk until
+ * done. Each header is linked to the next.
+ */
+static
+int fw_hdrs_load(struct i1480 *i1480, struct fw_hdr **phdr,
+ const char *_data, size_t data_size)
+{
+ int result;
+ unsigned hdr_cnt = 0;
+ u32 *data = (u32 *) _data, *data_itr, *data_top;
+ struct fw_hdr *hdr, **prev_hdr = phdr;
+
+ result = -EINVAL;
+ /* Check size is ok and pointer is aligned */
+ if (data_size % sizeof(u32) != 0)
+ goto error;
+ if ((unsigned long) _data % sizeof(u16) != 0)
+ goto error;
+ *phdr = NULL;
+ data_itr = data;
+ data_top = (u32 *) (_data + data_size);
+ while (data_itr < data_top) {
+ result = -ENOMEM;
+ hdr = kmalloc(sizeof(*hdr), GFP_KERNEL);
+ if (hdr == NULL) {
+ dev_err(i1480->dev, "Cannot allocate fw header "
+ "for chunk #%u\n", hdr_cnt);
+ goto error_alloc;
+ }
+ result = fw_hdr_load(i1480, hdr, hdr_cnt,
+ _data, data_itr, data_top);
+ if (result < 0)
+ goto error_load;
+ data_itr += 2 + hdr->length;
+ *prev_hdr = hdr;
+ prev_hdr = &hdr->next;
+ hdr_cnt++;
+ };
+ *prev_hdr = NULL;
+ return 0;
+
+error_load:
+ kfree(hdr);
+error_alloc:
+ fw_hdrs_free(*phdr);
+error:
+ return result;
+}
+
+
+/**
+ * Compares a chunk of fw with one in the devices's memory
+ *
+ * @i1480: Device instance
+ * @hdr: Pointer to the firmware chunk
+ * @returns: 0 if equal, < 0 errno on error. If > 0, it is the offset
+ * where the difference was found (plus one).
+ *
+ * Kind of dirty and simplistic, but does the trick in both the PCI
+ * and USB version. We do a quick[er] memcmp(), and if it fails, we do
+ * a byte-by-byte to find the offset.
+ */
+static
+ssize_t i1480_fw_cmp(struct i1480 *i1480, struct fw_hdr *hdr)
+{
+ ssize_t result = 0;
+ u32 src_itr = 0, cnt;
+ size_t size = hdr->length*sizeof(hdr->bin[0]);
+ size_t chunk_size;
+ u8 *bin = (u8 *) hdr->bin;
+
+ while (size > 0) {
+ chunk_size = size < i1480->buf_size ? size : i1480->buf_size;
+ result = i1480->read(i1480, hdr->address + src_itr, chunk_size);
+ if (result < 0) {
+ dev_err(i1480->dev, "error reading for verification: "
+ "%zd\n", result);
+ goto error;
+ }
+ if (memcmp(i1480->cmd_buf, bin + src_itr, result)) {
+ u8 *buf = i1480->cmd_buf;
+ for (cnt = 0; cnt < result; cnt++)
+ if (bin[src_itr + cnt] != buf[cnt]) {
+ dev_err(i1480->dev, "byte failed at "
+ "src_itr %u cnt %u [0x%02x "
+ "vs 0x%02x]\n", src_itr, cnt,
+ bin[src_itr + cnt], buf[cnt]);
+ result = src_itr + cnt + 1;
+ goto cmp_failed;
+ }
+ }
+ src_itr += result;
+ size -= result;
+ }
+ result = 0;
+error:
+cmp_failed:
+ return result;
+}
+
+
+/**
+ * Writes firmware headers to the device.
+ *
+ * @prd: PRD instance
+ * @hdr: Processed firmware
+ * @returns: 0 if ok, < 0 errno on error.
+ */
+static
+int mac_fw_hdrs_push(struct i1480 *i1480, struct fw_hdr *hdr,
+ const char *fw_name, const char *fw_tag)
+{
+ struct device *dev = i1480->dev;
+ ssize_t result = 0;
+ struct fw_hdr *hdr_itr;
+ int verif_retry_count;
+
+ /* Now, header by header, push them to the hw */
+ for (hdr_itr = hdr; hdr_itr != NULL; hdr_itr = hdr_itr->next) {
+ verif_retry_count = 0;
+retry:
+ dev_dbg(dev, "fw chunk (%zu @ 0x%08lx)\n",
+ hdr_itr->length * sizeof(hdr_itr->bin[0]),
+ hdr_itr->address);
+ result = i1480->write(i1480, hdr_itr->address, hdr_itr->bin,
+ hdr_itr->length*sizeof(hdr_itr->bin[0]));
+ if (result < 0) {
+ dev_err(dev, "%s fw '%s': write failed (%zuB @ 0x%lx):"
+ " %zd\n", fw_tag, fw_name,
+ hdr_itr->length * sizeof(hdr_itr->bin[0]),
+ hdr_itr->address, result);
+ break;
+ }
+ result = i1480_fw_cmp(i1480, hdr_itr);
+ if (result < 0) {
+ dev_err(dev, "%s fw '%s': verification read "
+ "failed (%zuB @ 0x%lx): %zd\n",
+ fw_tag, fw_name,
+ hdr_itr->length * sizeof(hdr_itr->bin[0]),
+ hdr_itr->address, result);
+ break;
+ }
+ if (result > 0) { /* Offset where it failed + 1 */
+ result--;
+ dev_err(dev, "%s fw '%s': WARNING: verification "
+ "failed at 0x%lx: retrying\n",
+ fw_tag, fw_name, hdr_itr->address + result);
+ if (++verif_retry_count < 3)
+ goto retry; /* write this block again! */
+ dev_err(dev, "%s fw '%s': verification failed at 0x%lx: "
+ "tried %d times\n", fw_tag, fw_name,
+ hdr_itr->address + result, verif_retry_count);
+ result = -EINVAL;
+ break;
+ }
+ }
+ return result;
+}
+
+
+/** Puts the device in firmware upload mode.*/
+static
+int mac_fw_upload_enable(struct i1480 *i1480)
+{
+ int result;
+ u32 reg = 0x800000c0;
+ u32 *buffer = (u32 *)i1480->cmd_buf;
+
+ if (i1480->hw_rev > 1)
+ reg = 0x8000d0d4;
+ result = i1480->read(i1480, reg, sizeof(u32));
+ if (result < 0)
+ goto error_cmd;
+ *buffer &= ~i1480_FW_UPLOAD_MODE_MASK;
+ result = i1480->write(i1480, reg, buffer, sizeof(u32));
+ if (result < 0)
+ goto error_cmd;
+ return 0;
+error_cmd:
+ dev_err(i1480->dev, "can't enable fw upload mode: %d\n", result);
+ return result;
+}
+
+
+/** Gets the device out of firmware upload mode. */
+static
+int mac_fw_upload_disable(struct i1480 *i1480)
+{
+ int result;
+ u32 reg = 0x800000c0;
+ u32 *buffer = (u32 *)i1480->cmd_buf;
+
+ if (i1480->hw_rev > 1)
+ reg = 0x8000d0d4;
+ result = i1480->read(i1480, reg, sizeof(u32));
+ if (result < 0)
+ goto error_cmd;
+ *buffer |= i1480_FW_UPLOAD_MODE_MASK;
+ result = i1480->write(i1480, reg, buffer, sizeof(u32));
+ if (result < 0)
+ goto error_cmd;
+ return 0;
+error_cmd:
+ dev_err(i1480->dev, "can't disable fw upload mode: %d\n", result);
+ return result;
+}
+
+
+
+/**
+ * Generic function for uploading a MAC firmware.
+ *
+ * @i1480: Device instance
+ * @fw_name: Name of firmware file to upload.
+ * @fw_tag: Name of the firmware type (for messages)
+ * [eg: MAC, PRE]
+ * @do_wait: Wait for device to emit initialization done message (0
+ * for PRE fws, 1 for MAC fws).
+ * @returns: 0 if ok, < 0 errno on error.
+ */
+static
+int __mac_fw_upload(struct i1480 *i1480, const char *fw_name,
+ const char *fw_tag)
+{
+ int result;
+ const struct firmware *fw;
+ struct fw_hdr *fw_hdrs;
+
+ result = request_firmware(&fw, fw_name, i1480->dev);
+ if (result < 0) /* Up to caller to complain on -ENOENT */
+ goto out;
+ result = fw_hdrs_load(i1480, &fw_hdrs, fw->data, fw->size);
+ if (result < 0) {
+ dev_err(i1480->dev, "%s fw '%s': failed to parse firmware "
+ "file: %d\n", fw_tag, fw_name, result);
+ goto out_release;
+ }
+ result = mac_fw_upload_enable(i1480);
+ if (result < 0)
+ goto out_hdrs_release;
+ result = mac_fw_hdrs_push(i1480, fw_hdrs, fw_name, fw_tag);
+ mac_fw_upload_disable(i1480);
+out_hdrs_release:
+ if (result >= 0)
+ dev_info(i1480->dev, "%s fw '%s': uploaded\n", fw_tag, fw_name);
+ else
+ dev_err(i1480->dev, "%s fw '%s': failed to upload (%d), "
+ "power cycle device\n", fw_tag, fw_name, result);
+ fw_hdrs_free(fw_hdrs);
+out_release:
+ release_firmware(fw);
+out:
+ return result;
+}
+
+
+/**
+ * Upload a pre-PHY firmware
+ *
+ */
+int i1480_pre_fw_upload(struct i1480 *i1480)
+{
+ int result;
+ result = __mac_fw_upload(i1480, i1480->pre_fw_name, "PRE");
+ if (result == 0)
+ msleep(400);
+ return result;
+}
+
+
+/**
+ * Reset a the MAC and PHY
+ *
+ * @i1480: Device's instance
+ * @returns: 0 if ok, < 0 errno code on error
+ *
+ * We put the command on kmalloc'ed memory as some arches cannot do
+ * USB from the stack. The reply event is copied from an stage buffer,
+ * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details.
+ *
+ * We issue the reset to make sure the UWB controller reinits the PHY;
+ * this way we can now if the PHY init went ok.
+ */
+static
+int i1480_cmd_reset(struct i1480 *i1480)
+{
+ int result;
+ struct uwb_rccb *cmd = (void *) i1480->cmd_buf;
+ struct i1480_evt_reset {
+ struct uwb_rceb rceb;
+ u8 bResultCode;
+ } __attribute__((packed)) *reply = (void *) i1480->evt_buf;
+
+ result = -ENOMEM;
+ cmd->bCommandType = UWB_RC_CET_GENERAL;
+ cmd->wCommand = cpu_to_le16(UWB_RC_CMD_RESET);
+ reply->rceb.bEventType = UWB_RC_CET_GENERAL;
+ reply->rceb.wEvent = UWB_RC_CMD_RESET;
+ result = i1480_cmd(i1480, "RESET", sizeof(*cmd), sizeof(*reply));
+ if (result < 0)
+ goto out;
+ if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(i1480->dev, "RESET: command execution failed: %u\n",
+ reply->bResultCode);
+ result = -EIO;
+ }
+out:
+ return result;
+
+}
+
+
+/* Wait for the MAC FW to start running */
+static
+int i1480_fw_is_running_q(struct i1480 *i1480)
+{
+ int cnt = 0;
+ int result;
+ u32 *val = (u32 *) i1480->cmd_buf;
+
+ for (cnt = 0; cnt < 10; cnt++) {
+ msleep(100);
+ result = i1480->read(i1480, 0x80080000, 4);
+ if (result < 0) {
+ dev_err(i1480->dev, "Can't read 0x8008000: %d\n", result);
+ goto out;
+ }
+ if (*val == 0x55555555UL) /* fw running? cool */
+ goto out;
+ }
+ dev_err(i1480->dev, "Timed out waiting for fw to start\n");
+ result = -ETIMEDOUT;
+out:
+ return result;
+
+}
+
+
+/**
+ * Upload MAC firmware, wait for it to start
+ *
+ * @i1480: Device instance
+ * @fw_name: Name of the file that contains the firmware
+ *
+ * This has to be called after the pre fw has been uploaded (if
+ * there is any).
+ */
+int i1480_mac_fw_upload(struct i1480 *i1480)
+{
+ int result = 0, deprecated_name = 0;
+ struct i1480_rceb *rcebe = (void *) i1480->evt_buf;
+
+ result = __mac_fw_upload(i1480, i1480->mac_fw_name, "MAC");
+ if (result == -ENOENT) {
+ result = __mac_fw_upload(i1480, i1480->mac_fw_name_deprecate,
+ "MAC");
+ deprecated_name = 1;
+ }
+ if (result < 0)
+ return result;
+ if (deprecated_name == 1)
+ dev_warn(i1480->dev,
+ "WARNING: firmware file name %s is deprecated, "
+ "please rename to %s\n",
+ i1480->mac_fw_name_deprecate, i1480->mac_fw_name);
+ result = i1480_fw_is_running_q(i1480);
+ if (result < 0)
+ goto error_fw_not_running;
+ result = i1480->rc_setup ? i1480->rc_setup(i1480) : 0;
+ if (result < 0) {
+ dev_err(i1480->dev, "Cannot setup after MAC fw upload: %d\n",
+ result);
+ goto error_setup;
+ }
+ result = i1480->wait_init_done(i1480); /* wait init'on */
+ if (result < 0) {
+ dev_err(i1480->dev, "MAC fw '%s': Initialization timed out "
+ "(%d)\n", i1480->mac_fw_name, result);
+ goto error_init_timeout;
+ }
+ /* verify we got the right initialization done event */
+ if (i1480->evt_result != sizeof(*rcebe)) {
+ dev_err(i1480->dev, "MAC fw '%s': initialization event returns "
+ "wrong size (%zu bytes vs %zu needed)\n",
+ i1480->mac_fw_name, i1480->evt_result, sizeof(*rcebe));
+ goto error_size;
+ }
+ result = -EIO;
+ if (i1480_rceb_check(i1480, &rcebe->rceb, NULL, 0, i1480_CET_VS1,
+ i1480_EVT_RM_INIT_DONE) < 0) {
+ dev_err(i1480->dev, "wrong initialization event 0x%02x/%04x/%02x "
+ "received; expected 0x%02x/%04x/00\n",
+ rcebe->rceb.bEventType, le16_to_cpu(rcebe->rceb.wEvent),
+ rcebe->rceb.bEventContext, i1480_CET_VS1,
+ i1480_EVT_RM_INIT_DONE);
+ goto error_init_timeout;
+ }
+ result = i1480_cmd_reset(i1480);
+ if (result < 0)
+ dev_err(i1480->dev, "MAC fw '%s': MBOA reset failed (%d)\n",
+ i1480->mac_fw_name, result);
+error_fw_not_running:
+error_init_timeout:
+error_size:
+error_setup:
+ return result;
+}
diff --git a/drivers/staging/uwb/i1480/dfu/phy.c b/drivers/staging/uwb/i1480/dfu/phy.c
new file mode 100644
index 000000000000..13512c7dda0b
--- /dev/null
+++ b/drivers/staging/uwb/i1480/dfu/phy.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Wireless UWB Link 1480
+ * PHY parameters upload
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * Code for uploading the PHY parameters to the PHY through the UWB
+ * Radio Control interface.
+ *
+ * We just send the data through the MPI interface using HWA-like
+ * commands and then reset the PHY to make sure it is ok.
+ */
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include "../../../wusbcore/include/wusb.h"
+#include "i1480-dfu.h"
+
+
+/**
+ * Write a value array to an address of the MPI interface
+ *
+ * @i1480: Device descriptor
+ * @data: Data array to write
+ * @size: Size of the data array
+ * @returns: 0 if ok, < 0 errno code on error.
+ *
+ * The data array is organized into pairs:
+ *
+ * ADDRESS VALUE
+ *
+ * ADDRESS is BE 16 bit unsigned, VALUE 8 bit unsigned. Size thus has
+ * to be a multiple of three.
+ */
+static
+int i1480_mpi_write(struct i1480 *i1480, const void *data, size_t size)
+{
+ int result;
+ struct i1480_cmd_mpi_write *cmd = i1480->cmd_buf;
+ struct i1480_evt_confirm *reply = i1480->evt_buf;
+
+ BUG_ON(size > 480);
+ result = -ENOMEM;
+ cmd->rccb.bCommandType = i1480_CET_VS1;
+ cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_WRITE);
+ cmd->size = cpu_to_le16(size);
+ memcpy(cmd->data, data, size);
+ reply->rceb.bEventType = i1480_CET_VS1;
+ reply->rceb.wEvent = i1480_CMD_MPI_WRITE;
+ result = i1480_cmd(i1480, "MPI-WRITE", sizeof(*cmd) + size, sizeof(*reply));
+ if (result < 0)
+ goto out;
+ if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(i1480->dev, "MPI-WRITE: command execution failed: %d\n",
+ reply->bResultCode);
+ result = -EIO;
+ }
+out:
+ return result;
+}
+
+
+/**
+ * Read a value array to from an address of the MPI interface
+ *
+ * @i1480: Device descriptor
+ * @data: where to place the read array
+ * @srcaddr: Where to read from
+ * @size: Size of the data read array
+ * @returns: 0 if ok, < 0 errno code on error.
+ *
+ * The command data array is organized into pairs ADDR0 ADDR1..., and
+ * the returned data in ADDR0 VALUE0 ADDR1 VALUE1...
+ *
+ * We generate the command array to be a sequential read and then
+ * rearrange the result.
+ *
+ * We use the i1480->cmd_buf for the command, i1480->evt_buf for the reply.
+ *
+ * As the reply has to fit in 512 bytes (i1480->evt_buffer), the max amount
+ * of values we can read is (512 - sizeof(*reply)) / 3
+ */
+static
+int i1480_mpi_read(struct i1480 *i1480, u8 *data, u16 srcaddr, size_t size)
+{
+ int result;
+ struct i1480_cmd_mpi_read *cmd = i1480->cmd_buf;
+ struct i1480_evt_mpi_read *reply = i1480->evt_buf;
+ unsigned cnt;
+
+ memset(i1480->cmd_buf, 0x69, 512);
+ memset(i1480->evt_buf, 0x69, 512);
+
+ BUG_ON(size > (i1480->buf_size - sizeof(*reply)) / 3);
+ result = -ENOMEM;
+ cmd->rccb.bCommandType = i1480_CET_VS1;
+ cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_READ);
+ cmd->size = cpu_to_le16(3*size);
+ for (cnt = 0; cnt < size; cnt++) {
+ cmd->data[cnt].page = (srcaddr + cnt) >> 8;
+ cmd->data[cnt].offset = (srcaddr + cnt) & 0xff;
+ }
+ reply->rceb.bEventType = i1480_CET_VS1;
+ reply->rceb.wEvent = i1480_CMD_MPI_READ;
+ result = i1480_cmd(i1480, "MPI-READ", sizeof(*cmd) + 2*size,
+ sizeof(*reply) + 3*size);
+ if (result < 0)
+ goto out;
+ if (reply->bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(i1480->dev, "MPI-READ: command execution failed: %d\n",
+ reply->bResultCode);
+ result = -EIO;
+ goto out;
+ }
+ for (cnt = 0; cnt < size; cnt++) {
+ if (reply->data[cnt].page != (srcaddr + cnt) >> 8)
+ dev_err(i1480->dev, "MPI-READ: page inconsistency at "
+ "index %u: expected 0x%02x, got 0x%02x\n", cnt,
+ (srcaddr + cnt) >> 8, reply->data[cnt].page);
+ if (reply->data[cnt].offset != ((srcaddr + cnt) & 0x00ff))
+ dev_err(i1480->dev, "MPI-READ: offset inconsistency at "
+ "index %u: expected 0x%02x, got 0x%02x\n", cnt,
+ (srcaddr + cnt) & 0x00ff,
+ reply->data[cnt].offset);
+ data[cnt] = reply->data[cnt].value;
+ }
+ result = 0;
+out:
+ return result;
+}
+
+
+/**
+ * Upload a PHY firmware, wait for it to start
+ *
+ * @i1480: Device instance
+ * @fw_name: Name of the file that contains the firmware
+ *
+ * We assume the MAC fw is up and running. This means we can use the
+ * MPI interface to write the PHY firmware. Once done, we issue an
+ * MBOA Reset, which will force the MAC to reset and reinitialize the
+ * PHY. If that works, we are ready to go.
+ *
+ * Max packet size for the MPI write is 512, so the max buffer is 480
+ * (which gives us 160 byte triads of MSB, LSB and VAL for the data).
+ */
+int i1480_phy_fw_upload(struct i1480 *i1480)
+{
+ int result;
+ const struct firmware *fw;
+ const char *data_itr, *data_top;
+ const size_t MAX_BLK_SIZE = 480; /* 160 triads */
+ size_t data_size;
+ u8 phy_stat;
+
+ result = request_firmware(&fw, i1480->phy_fw_name, i1480->dev);
+ if (result < 0)
+ goto out;
+ /* Loop writing data in chunks as big as possible until done. */
+ for (data_itr = fw->data, data_top = data_itr + fw->size;
+ data_itr < data_top; data_itr += MAX_BLK_SIZE) {
+ data_size = min(MAX_BLK_SIZE, (size_t) (data_top - data_itr));
+ result = i1480_mpi_write(i1480, data_itr, data_size);
+ if (result < 0)
+ goto error_mpi_write;
+ }
+ /* Read MPI page 0, offset 6; if 0, PHY was initialized correctly. */
+ result = i1480_mpi_read(i1480, &phy_stat, 0x0006, 1);
+ if (result < 0) {
+ dev_err(i1480->dev, "PHY: can't get status: %d\n", result);
+ goto error_mpi_status;
+ }
+ if (phy_stat != 0) {
+ result = -ENODEV;
+ dev_info(i1480->dev, "error, PHY not ready: %u\n", phy_stat);
+ goto error_phy_status;
+ }
+ dev_info(i1480->dev, "PHY fw '%s': uploaded\n", i1480->phy_fw_name);
+error_phy_status:
+error_mpi_status:
+error_mpi_write:
+ release_firmware(fw);
+ if (result < 0)
+ dev_err(i1480->dev, "PHY fw '%s': failed to upload (%d), "
+ "power cycle device\n", i1480->phy_fw_name, result);
+out:
+ return result;
+}
diff --git a/drivers/staging/uwb/i1480/dfu/usb.c b/drivers/staging/uwb/i1480/dfu/usb.c
new file mode 100644
index 000000000000..d41086bdd783
--- /dev/null
+++ b/drivers/staging/uwb/i1480/dfu/usb.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Wireless UWB Link 1480
+ * USB SKU firmware upload implementation
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This driver will prepare the i1480 device to behave as a real
+ * Wireless USB HWA adaptor by uploading the firmware.
+ *
+ * When the device is connected or driver is loaded, i1480_usb_probe()
+ * is called--this will allocate and initialize the device structure,
+ * fill in the pointers to the common functions (read, write,
+ * wait_init_done and cmd for HWA command execution) and once that is
+ * done, call the common firmware uploading routine. Then clean up and
+ * return -ENODEV, as we don't attach to the device.
+ *
+ * The rest are the basic ops we implement that the fw upload code
+ * uses to do its job. All the ops in the common code are i1480->NAME,
+ * the functions are i1480_usb_NAME().
+ */
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include "../../uwb.h"
+#include "../../../wusbcore/include/wusb.h"
+#include "../../../wusbcore/include/wusb-wa.h"
+#include "i1480-dfu.h"
+
+struct i1480_usb {
+ struct i1480 i1480;
+ struct usb_device *usb_dev;
+ struct usb_interface *usb_iface;
+ struct urb *neep_urb; /* URB for reading from EP1 */
+};
+
+
+static
+void i1480_usb_init(struct i1480_usb *i1480_usb)
+{
+ i1480_init(&i1480_usb->i1480);
+}
+
+
+static
+int i1480_usb_create(struct i1480_usb *i1480_usb, struct usb_interface *iface)
+{
+ struct usb_device *usb_dev = interface_to_usbdev(iface);
+ int result = -ENOMEM;
+
+ i1480_usb->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */
+ i1480_usb->usb_iface = usb_get_intf(iface);
+ usb_set_intfdata(iface, i1480_usb); /* Bind the driver to iface0 */
+ i1480_usb->neep_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (i1480_usb->neep_urb == NULL)
+ goto error;
+ return 0;
+
+error:
+ usb_set_intfdata(iface, NULL);
+ usb_put_intf(iface);
+ usb_put_dev(usb_dev);
+ return result;
+}
+
+
+static
+void i1480_usb_destroy(struct i1480_usb *i1480_usb)
+{
+ usb_kill_urb(i1480_usb->neep_urb);
+ usb_free_urb(i1480_usb->neep_urb);
+ usb_set_intfdata(i1480_usb->usb_iface, NULL);
+ usb_put_intf(i1480_usb->usb_iface);
+ usb_put_dev(i1480_usb->usb_dev);
+}
+
+
+/**
+ * Write a buffer to a memory address in the i1480 device
+ *
+ * @i1480: i1480 instance
+ * @memory_address:
+ * Address where to write the data buffer to.
+ * @buffer: Buffer to the data
+ * @size: Size of the buffer [has to be < 512].
+ * @returns: 0 if ok, < 0 errno code on error.
+ *
+ * Data buffers to USB cannot be on the stack or in vmalloc'ed areas,
+ * so we copy it to the local i1480 buffer before proceeding. In any
+ * case, we have a max size we can send.
+ */
+static
+int i1480_usb_write(struct i1480 *i1480, u32 memory_address,
+ const void *buffer, size_t size)
+{
+ int result = 0;
+ struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480);
+ size_t buffer_size, itr = 0;
+
+ BUG_ON(size & 0x3); /* Needs to be a multiple of 4 */
+ while (size > 0) {
+ buffer_size = size < i1480->buf_size ? size : i1480->buf_size;
+ memcpy(i1480->cmd_buf, buffer + itr, buffer_size);
+ result = usb_control_msg(
+ i1480_usb->usb_dev, usb_sndctrlpipe(i1480_usb->usb_dev, 0),
+ 0xf0, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ memory_address, (memory_address >> 16),
+ i1480->cmd_buf, buffer_size, 100 /* FIXME: arbitrary */);
+ if (result < 0)
+ break;
+ itr += result;
+ memory_address += result;
+ size -= result;
+ }
+ return result;
+}
+
+
+/**
+ * Read a block [max size 512] of the device's memory to @i1480's buffer.
+ *
+ * @i1480: i1480 instance
+ * @memory_address:
+ * Address where to read from.
+ * @size: Size to read. Smaller than or equal to 512.
+ * @returns: >= 0 number of bytes written if ok, < 0 errno code on error.
+ *
+ * NOTE: if the memory address or block is incorrect, you might get a
+ * stall or a different memory read. Caller has to verify the
+ * memory address and size passed back in the @neh structure.
+ */
+static
+int i1480_usb_read(struct i1480 *i1480, u32 addr, size_t size)
+{
+ ssize_t result = 0, bytes = 0;
+ size_t itr, read_size = i1480->buf_size;
+ struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480);
+
+ BUG_ON(size > i1480->buf_size);
+ BUG_ON(size & 0x3); /* Needs to be a multiple of 4 */
+ BUG_ON(read_size > 512);
+
+ if (addr >= 0x8000d200 && addr < 0x8000d400) /* Yeah, HW quirk */
+ read_size = 4;
+
+ for (itr = 0; itr < size; itr += read_size) {
+ size_t itr_addr = addr + itr;
+ size_t itr_size = min(read_size, size - itr);
+ result = usb_control_msg(
+ i1480_usb->usb_dev, usb_rcvctrlpipe(i1480_usb->usb_dev, 0),
+ 0xf0, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ itr_addr, (itr_addr >> 16),
+ i1480->cmd_buf + itr, itr_size,
+ 100 /* FIXME: arbitrary */);
+ if (result < 0) {
+ dev_err(i1480->dev, "%s: USB read error: %zd\n",
+ __func__, result);
+ goto out;
+ }
+ if (result != itr_size) {
+ result = -EIO;
+ dev_err(i1480->dev,
+ "%s: partial read got only %zu bytes vs %zu expected\n",
+ __func__, result, itr_size);
+ goto out;
+ }
+ bytes += result;
+ }
+ result = bytes;
+out:
+ return result;
+}
+
+
+/**
+ * Callback for reads on the notification/event endpoint
+ *
+ * Just enables the completion read handler.
+ */
+static
+void i1480_usb_neep_cb(struct urb *urb)
+{
+ struct i1480 *i1480 = urb->context;
+ struct device *dev = i1480->dev;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ECONNRESET: /* Not an error, but a controlled situation; */
+ case -ENOENT: /* (we killed the URB)...so, no broadcast */
+ dev_dbg(dev, "NEEP: reset/noent %d\n", urb->status);
+ break;
+ case -ESHUTDOWN: /* going away! */
+ dev_dbg(dev, "NEEP: down %d\n", urb->status);
+ break;
+ default:
+ dev_err(dev, "NEEP: unknown status %d\n", urb->status);
+ break;
+ }
+ i1480->evt_result = urb->actual_length;
+ complete(&i1480->evt_complete);
+ return;
+}
+
+
+/**
+ * Wait for the MAC FW to initialize
+ *
+ * MAC FW sends a 0xfd/0101/00 notification to EP1 when done
+ * initializing. Get that notification into i1480->evt_buf; upper layer
+ * will verify it.
+ *
+ * Set i1480->evt_result with the result of getting the event or its
+ * size (if successful).
+ *
+ * Delivers the data directly to i1480->evt_buf
+ */
+static
+int i1480_usb_wait_init_done(struct i1480 *i1480)
+{
+ int result;
+ struct device *dev = i1480->dev;
+ struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480);
+ struct usb_endpoint_descriptor *epd;
+
+ init_completion(&i1480->evt_complete);
+ i1480->evt_result = -EINPROGRESS;
+ epd = &i1480_usb->usb_iface->cur_altsetting->endpoint[0].desc;
+ usb_fill_int_urb(i1480_usb->neep_urb, i1480_usb->usb_dev,
+ usb_rcvintpipe(i1480_usb->usb_dev, epd->bEndpointAddress),
+ i1480->evt_buf, i1480->buf_size,
+ i1480_usb_neep_cb, i1480, epd->bInterval);
+ result = usb_submit_urb(i1480_usb->neep_urb, GFP_KERNEL);
+ if (result < 0) {
+ dev_err(dev, "init done: cannot submit NEEP read: %d\n",
+ result);
+ goto error_submit;
+ }
+ /* Wait for the USB callback to get the data */
+ result = wait_for_completion_interruptible_timeout(
+ &i1480->evt_complete, HZ);
+ if (result <= 0) {
+ result = result == 0 ? -ETIMEDOUT : result;
+ goto error_wait;
+ }
+ usb_kill_urb(i1480_usb->neep_urb);
+ return 0;
+
+error_wait:
+ usb_kill_urb(i1480_usb->neep_urb);
+error_submit:
+ i1480->evt_result = result;
+ return result;
+}
+
+
+/**
+ * Generic function for issuing commands to the i1480
+ *
+ * @i1480: i1480 instance
+ * @cmd_name: Name of the command (for error messages)
+ * @cmd: Pointer to command buffer
+ * @cmd_size: Size of the command buffer
+ * @reply: Buffer for the reply event
+ * @reply_size: Expected size back (including RCEB); the reply buffer
+ * is assumed to be as big as this.
+ * @returns: >= 0 size of the returned event data if ok,
+ * < 0 errno code on error.
+ *
+ * Arms the NE handle, issues the command to the device and checks the
+ * basics of the reply event.
+ */
+static
+int i1480_usb_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size)
+{
+ int result;
+ struct device *dev = i1480->dev;
+ struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480);
+ struct usb_endpoint_descriptor *epd;
+ struct uwb_rccb *cmd = i1480->cmd_buf;
+ u8 iface_no;
+
+ /* Post a read on the notification & event endpoint */
+ iface_no = i1480_usb->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ epd = &i1480_usb->usb_iface->cur_altsetting->endpoint[0].desc;
+ usb_fill_int_urb(
+ i1480_usb->neep_urb, i1480_usb->usb_dev,
+ usb_rcvintpipe(i1480_usb->usb_dev, epd->bEndpointAddress),
+ i1480->evt_buf, i1480->buf_size,
+ i1480_usb_neep_cb, i1480, epd->bInterval);
+ result = usb_submit_urb(i1480_usb->neep_urb, GFP_KERNEL);
+ if (result < 0) {
+ dev_err(dev, "%s: cannot submit NEEP read: %d\n",
+ cmd_name, result);
+ goto error_submit_ep1;
+ }
+ /* Now post the command on EP0 */
+ result = usb_control_msg(
+ i1480_usb->usb_dev, usb_sndctrlpipe(i1480_usb->usb_dev, 0),
+ WA_EXEC_RC_CMD,
+ USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS,
+ 0, iface_no,
+ cmd, cmd_size,
+ 100 /* FIXME: this is totally arbitrary */);
+ if (result < 0) {
+ dev_err(dev, "%s: control request failed: %d\n",
+ cmd_name, result);
+ goto error_submit_ep0;
+ }
+ return result;
+
+error_submit_ep0:
+ usb_kill_urb(i1480_usb->neep_urb);
+error_submit_ep1:
+ return result;
+}
+
+
+/*
+ * Probe a i1480 device for uploading firmware.
+ *
+ * We attach only to interface #0, which is the radio control interface.
+ */
+static
+int i1480_usb_probe(struct usb_interface *iface, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(iface);
+ struct i1480_usb *i1480_usb;
+ struct i1480 *i1480;
+ struct device *dev = &iface->dev;
+ int result;
+
+ result = -ENODEV;
+ if (iface->cur_altsetting->desc.bInterfaceNumber != 0) {
+ dev_dbg(dev, "not attaching to iface %d\n",
+ iface->cur_altsetting->desc.bInterfaceNumber);
+ goto error;
+ }
+ if (iface->num_altsetting > 1 &&
+ le16_to_cpu(udev->descriptor.idProduct) == 0xbabe) {
+ /* Need altsetting #1 [HW QUIRK] or EP1 won't work */
+ result = usb_set_interface(interface_to_usbdev(iface), 0, 1);
+ if (result < 0)
+ dev_warn(dev,
+ "can't set altsetting 1 on iface 0: %d\n",
+ result);
+ }
+
+ if (iface->cur_altsetting->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ result = -ENOMEM;
+ i1480_usb = kzalloc(sizeof(*i1480_usb), GFP_KERNEL);
+ if (i1480_usb == NULL) {
+ dev_err(dev, "Unable to allocate instance\n");
+ goto error;
+ }
+ i1480_usb_init(i1480_usb);
+
+ i1480 = &i1480_usb->i1480;
+ i1480->buf_size = 512;
+ i1480->cmd_buf = kmalloc_array(2, i1480->buf_size, GFP_KERNEL);
+ if (i1480->cmd_buf == NULL) {
+ dev_err(dev, "Cannot allocate transfer buffers\n");
+ result = -ENOMEM;
+ goto error_buf_alloc;
+ }
+ i1480->evt_buf = i1480->cmd_buf + i1480->buf_size;
+
+ result = i1480_usb_create(i1480_usb, iface);
+ if (result < 0) {
+ dev_err(dev, "Cannot create instance: %d\n", result);
+ goto error_create;
+ }
+
+ /* setup the fops and upload the firmware */
+ i1480->pre_fw_name = "i1480-pre-phy-0.0.bin";
+ i1480->mac_fw_name = "i1480-usb-0.0.bin";
+ i1480->mac_fw_name_deprecate = "ptc-0.0.bin";
+ i1480->phy_fw_name = "i1480-phy-0.0.bin";
+ i1480->dev = &iface->dev;
+ i1480->write = i1480_usb_write;
+ i1480->read = i1480_usb_read;
+ i1480->rc_setup = NULL;
+ i1480->wait_init_done = i1480_usb_wait_init_done;
+ i1480->cmd = i1480_usb_cmd;
+
+ result = i1480_fw_upload(&i1480_usb->i1480); /* the real thing */
+ if (result >= 0) {
+ usb_reset_device(i1480_usb->usb_dev);
+ result = -ENODEV; /* we don't want to bind to the iface */
+ }
+ i1480_usb_destroy(i1480_usb);
+error_create:
+ kfree(i1480->cmd_buf);
+error_buf_alloc:
+ kfree(i1480_usb);
+error:
+ return result;
+}
+
+MODULE_FIRMWARE("i1480-pre-phy-0.0.bin");
+MODULE_FIRMWARE("i1480-usb-0.0.bin");
+MODULE_FIRMWARE("i1480-phy-0.0.bin");
+
+#define i1480_USB_DEV(v, p) \
+{ \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE \
+ | USB_DEVICE_ID_MATCH_DEV_INFO \
+ | USB_DEVICE_ID_MATCH_INT_INFO, \
+ .idVendor = (v), \
+ .idProduct = (p), \
+ .bDeviceClass = 0xff, \
+ .bDeviceSubClass = 0xff, \
+ .bDeviceProtocol = 0xff, \
+ .bInterfaceClass = 0xff, \
+ .bInterfaceSubClass = 0xff, \
+ .bInterfaceProtocol = 0xff, \
+}
+
+
+/** USB device ID's that we handle */
+static const struct usb_device_id i1480_usb_id_table[] = {
+ i1480_USB_DEV(0x8086, 0xdf3b),
+ i1480_USB_DEV(0x15a9, 0x0005),
+ i1480_USB_DEV(0x07d1, 0x3802),
+ i1480_USB_DEV(0x050d, 0x305a),
+ i1480_USB_DEV(0x3495, 0x3007),
+ {},
+};
+MODULE_DEVICE_TABLE(usb, i1480_usb_id_table);
+
+
+static struct usb_driver i1480_dfu_driver = {
+ .name = "i1480-dfu-usb",
+ .id_table = i1480_usb_id_table,
+ .probe = i1480_usb_probe,
+ .disconnect = NULL,
+};
+
+module_usb_driver(i1480_dfu_driver);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Intel Wireless UWB Link 1480 firmware uploader for USB");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/uwb/i1480/i1480-est.c b/drivers/staging/uwb/i1480/i1480-est.c
new file mode 100644
index 000000000000..106e0a44b138
--- /dev/null
+++ b/drivers/staging/uwb/i1480/i1480-est.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Wireless UWB Link 1480
+ * Event Size tables for Wired Adaptors
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include "../uwb.h"
+#include "dfu/i1480-dfu.h"
+
+
+/** Event size table for wEvents 0x00XX */
+static struct uwb_est_entry i1480_est_fd00[] = {
+ /* Anybody expecting this response has to use
+ * neh->extra_size to specify the real size that will
+ * come back. */
+ [i1480_EVT_CONFIRM] = { .size = sizeof(struct i1480_evt_confirm) },
+ [i1480_CMD_SET_IP_MAS] = { .size = sizeof(struct i1480_evt_confirm) },
+#ifdef i1480_RCEB_EXTENDED
+ [0x09] = {
+ .size = sizeof(struct i1480_rceb),
+ .offset = 1 + offsetof(struct i1480_rceb, wParamLength),
+ },
+#endif
+};
+
+/** Event size table for wEvents 0x01XX */
+static struct uwb_est_entry i1480_est_fd01[] = {
+ [0xff & i1480_EVT_RM_INIT_DONE] = { .size = sizeof(struct i1480_rceb) },
+ [0xff & i1480_EVT_DEV_ADD] = { .size = sizeof(struct i1480_rceb) + 9 },
+ [0xff & i1480_EVT_DEV_RM] = { .size = sizeof(struct i1480_rceb) + 9 },
+ [0xff & i1480_EVT_DEV_ID_CHANGE] = {
+ .size = sizeof(struct i1480_rceb) + 2 },
+};
+
+static int __init i1480_est_init(void)
+{
+ int result = uwb_est_register(i1480_CET_VS1, 0x00, 0x8086, 0x0c3b,
+ i1480_est_fd00,
+ ARRAY_SIZE(i1480_est_fd00));
+ if (result < 0) {
+ printk(KERN_ERR "Can't register EST table fd00: %d\n", result);
+ return result;
+ }
+ result = uwb_est_register(i1480_CET_VS1, 0x01, 0x8086, 0x0c3b,
+ i1480_est_fd01, ARRAY_SIZE(i1480_est_fd01));
+ if (result < 0) {
+ printk(KERN_ERR "Can't register EST table fd01: %d\n", result);
+ return result;
+ }
+ return 0;
+}
+module_init(i1480_est_init);
+
+static void __exit i1480_est_exit(void)
+{
+ uwb_est_unregister(i1480_CET_VS1, 0x00, 0x8086, 0x0c3b,
+ i1480_est_fd00, ARRAY_SIZE(i1480_est_fd00));
+ uwb_est_unregister(i1480_CET_VS1, 0x01, 0x8086, 0x0c3b,
+ i1480_est_fd01, ARRAY_SIZE(i1480_est_fd01));
+}
+module_exit(i1480_est_exit);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("i1480's Vendor Specific Event Size Tables");
+MODULE_LICENSE("GPL");
+
+/**
+ * USB device ID's that we handle
+ *
+ * [so we are loaded when this kind device is connected]
+ */
+static struct usb_device_id __used i1480_est_id_table[] = {
+ { USB_DEVICE(0x8086, 0xdf3b), },
+ { USB_DEVICE(0x8086, 0x0c3b), },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, i1480_est_id_table);
diff --git a/drivers/staging/uwb/ie-rcv.c b/drivers/staging/uwb/ie-rcv.c
new file mode 100644
index 000000000000..51a4706e0dd3
--- /dev/null
+++ b/drivers/staging/uwb/ie-rcv.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * IE Received notification handling.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/bitmap.h>
+#include "uwb-internal.h"
+
+/*
+ * Process an incoming IE Received notification.
+ */
+int uwbd_evt_handle_rc_ie_rcv(struct uwb_event *evt)
+{
+ int result = -EINVAL;
+ struct device *dev = &evt->rc->uwb_dev.dev;
+ struct uwb_rc_evt_ie_rcv *iercv;
+
+ /* Is there enough data to decode it? */
+ if (evt->notif.size < sizeof(*iercv)) {
+ dev_err(dev, "IE Received notification: Not enough data to "
+ "decode (%zu vs %zu bytes needed)\n",
+ evt->notif.size, sizeof(*iercv));
+ goto error;
+ }
+ iercv = container_of(evt->notif.rceb, struct uwb_rc_evt_ie_rcv, rceb);
+
+ dev_dbg(dev, "IE received, element ID=%d\n", iercv->IEData[0]);
+
+ if (iercv->IEData[0] == UWB_RELINQUISH_REQUEST_IE) {
+ dev_warn(dev, "unhandled Relinquish Request IE\n");
+ }
+
+ return 0;
+error:
+ return result;
+}
diff --git a/drivers/staging/uwb/ie.c b/drivers/staging/uwb/ie.c
new file mode 100644
index 000000000000..b11678dd0380
--- /dev/null
+++ b/drivers/staging/uwb/ie.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Information Element Handling
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Reinette Chatre <reinette.chatre@intel.com>
+ *
+ * FIXME: docs
+ */
+
+#include <linux/slab.h>
+#include <linux/export.h>
+#include "uwb-internal.h"
+
+/**
+ * uwb_ie_next - get the next IE in a buffer
+ * @ptr: start of the buffer containing the IE data
+ * @len: length of the buffer
+ *
+ * Both @ptr and @len are updated so subsequent calls to uwb_ie_next()
+ * will get the next IE.
+ *
+ * NULL is returned (and @ptr and @len will not be updated) if there
+ * are no more IEs in the buffer or the buffer is too short.
+ */
+struct uwb_ie_hdr *uwb_ie_next(void **ptr, size_t *len)
+{
+ struct uwb_ie_hdr *hdr;
+ size_t ie_len;
+
+ if (*len < sizeof(struct uwb_ie_hdr))
+ return NULL;
+
+ hdr = *ptr;
+ ie_len = sizeof(struct uwb_ie_hdr) + hdr->length;
+
+ if (*len < ie_len)
+ return NULL;
+
+ *ptr += ie_len;
+ *len -= ie_len;
+
+ return hdr;
+}
+EXPORT_SYMBOL_GPL(uwb_ie_next);
+
+/**
+ * uwb_ie_dump_hex - print IEs to a character buffer
+ * @ies: the IEs to print.
+ * @len: length of all the IEs.
+ * @buf: the destination buffer.
+ * @size: size of @buf.
+ *
+ * Returns the number of characters written.
+ */
+int uwb_ie_dump_hex(const struct uwb_ie_hdr *ies, size_t len,
+ char *buf, size_t size)
+{
+ void *ptr;
+ const struct uwb_ie_hdr *ie;
+ int r = 0;
+ u8 *d;
+
+ ptr = (void *)ies;
+ for (;;) {
+ ie = uwb_ie_next(&ptr, &len);
+ if (!ie)
+ break;
+
+ r += scnprintf(buf + r, size - r, "%02x %02x",
+ (unsigned)ie->element_id,
+ (unsigned)ie->length);
+ d = (uint8_t *)ie + sizeof(struct uwb_ie_hdr);
+ while (d != ptr && r < size)
+ r += scnprintf(buf + r, size - r, " %02x", (unsigned)*d++);
+ if (r < size)
+ buf[r++] = '\n';
+ };
+
+ return r;
+}
+
+/**
+ * Get the IEs that a radio controller is sending in its beacon
+ *
+ * @uwb_rc: UWB Radio Controller
+ * @returns: Size read from the system
+ *
+ * We don't need to lock the uwb_rc's mutex because we don't modify
+ * anything. Once done with the iedata buffer, call
+ * uwb_rc_ie_release(iedata). Don't call kfree on it.
+ */
+static
+ssize_t uwb_rc_get_ie(struct uwb_rc *uwb_rc, struct uwb_rc_evt_get_ie **pget_ie)
+{
+ ssize_t result;
+ struct device *dev = &uwb_rc->uwb_dev.dev;
+ struct uwb_rccb *cmd = NULL;
+ struct uwb_rceb *reply = NULL;
+ struct uwb_rc_evt_get_ie *get_ie;
+
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+ if (cmd == NULL)
+ return -ENOMEM;
+
+ cmd->bCommandType = UWB_RC_CET_GENERAL;
+ cmd->wCommand = cpu_to_le16(UWB_RC_CMD_GET_IE);
+ result = uwb_rc_vcmd(uwb_rc, "GET_IE", cmd, sizeof(*cmd),
+ UWB_RC_CET_GENERAL, UWB_RC_CMD_GET_IE,
+ &reply);
+ kfree(cmd);
+ if (result < 0)
+ return result;
+
+ get_ie = container_of(reply, struct uwb_rc_evt_get_ie, rceb);
+ if (result < sizeof(*get_ie)) {
+ dev_err(dev, "not enough data returned for decoding GET IE "
+ "(%zu bytes received vs %zu needed)\n",
+ result, sizeof(*get_ie));
+ return -EINVAL;
+ } else if (result < sizeof(*get_ie) + le16_to_cpu(get_ie->wIELength)) {
+ dev_err(dev, "not enough data returned for decoding GET IE "
+ "payload (%zu bytes received vs %zu needed)\n", result,
+ sizeof(*get_ie) + le16_to_cpu(get_ie->wIELength));
+ return -EINVAL;
+ }
+
+ *pget_ie = get_ie;
+ return result;
+}
+
+
+/**
+ * Replace all IEs currently being transmitted by a device
+ *
+ * @cmd: pointer to the SET-IE command with the IEs to set
+ * @size: size of @buf
+ */
+int uwb_rc_set_ie(struct uwb_rc *rc, struct uwb_rc_cmd_set_ie *cmd)
+{
+ int result;
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_rc_evt_set_ie reply;
+
+ reply.rceb.bEventType = UWB_RC_CET_GENERAL;
+ reply.rceb.wEvent = UWB_RC_CMD_SET_IE;
+ result = uwb_rc_cmd(rc, "SET-IE", &cmd->rccb,
+ sizeof(*cmd) + le16_to_cpu(cmd->wIELength),
+ &reply.rceb, sizeof(reply));
+ if (result < 0)
+ goto error_cmd;
+ else if (result != sizeof(reply)) {
+ dev_err(dev, "SET-IE: not enough data to decode reply "
+ "(%d bytes received vs %zu needed)\n",
+ result, sizeof(reply));
+ result = -EIO;
+ } else if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(dev, "SET-IE: command execution failed: %s (%d)\n",
+ uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
+ result = -EIO;
+ } else
+ result = 0;
+error_cmd:
+ return result;
+}
+
+/* Cleanup the whole IE management subsystem */
+void uwb_rc_ie_init(struct uwb_rc *uwb_rc)
+{
+ mutex_init(&uwb_rc->ies_mutex);
+}
+
+
+/**
+ * uwb_rc_ie_setup - setup a radio controller's IE manager
+ * @uwb_rc: the radio controller.
+ *
+ * The current set of IEs are obtained from the hardware with a GET-IE
+ * command (since the radio controller is not yet beaconing this will
+ * be just the hardware's MAC and PHY Capability IEs).
+ *
+ * Returns 0 on success; -ve on an error.
+ */
+int uwb_rc_ie_setup(struct uwb_rc *uwb_rc)
+{
+ struct uwb_rc_evt_get_ie *ie_info = NULL;
+ int capacity;
+
+ capacity = uwb_rc_get_ie(uwb_rc, &ie_info);
+ if (capacity < 0)
+ return capacity;
+
+ mutex_lock(&uwb_rc->ies_mutex);
+
+ uwb_rc->ies = (struct uwb_rc_cmd_set_ie *)ie_info;
+ uwb_rc->ies->rccb.bCommandType = UWB_RC_CET_GENERAL;
+ uwb_rc->ies->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SET_IE);
+ uwb_rc->ies_capacity = capacity;
+
+ mutex_unlock(&uwb_rc->ies_mutex);
+
+ return 0;
+}
+
+
+/* Cleanup the whole IE management subsystem */
+void uwb_rc_ie_release(struct uwb_rc *uwb_rc)
+{
+ kfree(uwb_rc->ies);
+ uwb_rc->ies = NULL;
+ uwb_rc->ies_capacity = 0;
+}
+
+
+static int uwb_rc_ie_add_one(struct uwb_rc *rc, const struct uwb_ie_hdr *new_ie)
+{
+ struct uwb_rc_cmd_set_ie *new_ies;
+ void *ptr, *prev_ie;
+ struct uwb_ie_hdr *ie;
+ size_t length, new_ie_len, new_capacity, size, prev_size;
+
+ length = le16_to_cpu(rc->ies->wIELength);
+ new_ie_len = sizeof(struct uwb_ie_hdr) + new_ie->length;
+ new_capacity = sizeof(struct uwb_rc_cmd_set_ie) + length + new_ie_len;
+
+ if (new_capacity > rc->ies_capacity) {
+ new_ies = krealloc(rc->ies, new_capacity, GFP_KERNEL);
+ if (!new_ies)
+ return -ENOMEM;
+ rc->ies = new_ies;
+ }
+
+ ptr = rc->ies->IEData;
+ size = length;
+ for (;;) {
+ prev_ie = ptr;
+ prev_size = size;
+ ie = uwb_ie_next(&ptr, &size);
+ if (!ie || ie->element_id > new_ie->element_id)
+ break;
+ }
+
+ memmove(prev_ie + new_ie_len, prev_ie, prev_size);
+ memcpy(prev_ie, new_ie, new_ie_len);
+ rc->ies->wIELength = cpu_to_le16(length + new_ie_len);
+
+ return 0;
+}
+
+/**
+ * uwb_rc_ie_add - add new IEs to the radio controller's beacon
+ * @uwb_rc: the radio controller.
+ * @ies: the buffer containing the new IE or IEs to be added to
+ * the device's beacon.
+ * @size: length of all the IEs.
+ *
+ * According to WHCI 0.95 [4.13.6] the driver will only receive the RCEB
+ * after the device sent the first beacon that includes the IEs specified
+ * in the SET IE command. We thus cannot send this command if the device is
+ * not beaconing. Instead, a SET IE command will be sent later right after
+ * we start beaconing.
+ *
+ * Setting an IE on the device will overwrite all current IEs in device. So
+ * we take the current IEs being transmitted by the device, insert the
+ * new one, and call SET IE with all the IEs needed.
+ *
+ * Returns 0 on success; or -ENOMEM.
+ */
+int uwb_rc_ie_add(struct uwb_rc *uwb_rc,
+ const struct uwb_ie_hdr *ies, size_t size)
+{
+ int result = 0;
+ void *ptr;
+ const struct uwb_ie_hdr *ie;
+
+ mutex_lock(&uwb_rc->ies_mutex);
+
+ ptr = (void *)ies;
+ for (;;) {
+ ie = uwb_ie_next(&ptr, &size);
+ if (!ie)
+ break;
+
+ result = uwb_rc_ie_add_one(uwb_rc, ie);
+ if (result < 0)
+ break;
+ }
+ if (result >= 0) {
+ if (size == 0) {
+ if (uwb_rc->beaconing != -1)
+ result = uwb_rc_set_ie(uwb_rc, uwb_rc->ies);
+ } else
+ result = -EINVAL;
+ }
+
+ mutex_unlock(&uwb_rc->ies_mutex);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_ie_add);
+
+
+/*
+ * Remove an IE from internal cache
+ *
+ * We are dealing with our internal IE cache so no need to verify that the
+ * IEs are valid (it has been done already).
+ *
+ * Should be called with ies_mutex held
+ *
+ * We do not break out once an IE is found in the cache. It is currently
+ * possible to have more than one IE with the same ID included in the
+ * beacon. We don't reallocate, we just mark the size smaller.
+ */
+static
+void uwb_rc_ie_cache_rm(struct uwb_rc *uwb_rc, enum uwb_ie to_remove)
+{
+ struct uwb_ie_hdr *ie;
+ size_t len = le16_to_cpu(uwb_rc->ies->wIELength);
+ void *ptr;
+ size_t size;
+
+ ptr = uwb_rc->ies->IEData;
+ size = len;
+ for (;;) {
+ ie = uwb_ie_next(&ptr, &size);
+ if (!ie)
+ break;
+ if (ie->element_id == to_remove) {
+ len -= sizeof(struct uwb_ie_hdr) + ie->length;
+ memmove(ie, ptr, size);
+ ptr = ie;
+ }
+ }
+ uwb_rc->ies->wIELength = cpu_to_le16(len);
+}
+
+
+/**
+ * uwb_rc_ie_rm - remove an IE from the radio controller's beacon
+ * @uwb_rc: the radio controller.
+ * @element_id: the element ID of the IE to remove.
+ *
+ * Only IEs previously added with uwb_rc_ie_add() may be removed.
+ *
+ * Returns 0 on success; or -ve the SET-IE command to the radio
+ * controller failed.
+ */
+int uwb_rc_ie_rm(struct uwb_rc *uwb_rc, enum uwb_ie element_id)
+{
+ int result = 0;
+
+ mutex_lock(&uwb_rc->ies_mutex);
+
+ uwb_rc_ie_cache_rm(uwb_rc, element_id);
+
+ if (uwb_rc->beaconing != -1)
+ result = uwb_rc_set_ie(uwb_rc, uwb_rc->ies);
+
+ mutex_unlock(&uwb_rc->ies_mutex);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_ie_rm);
diff --git a/drivers/staging/uwb/include/debug-cmd.h b/drivers/staging/uwb/include/debug-cmd.h
new file mode 100644
index 000000000000..f97db6c3bcc0
--- /dev/null
+++ b/drivers/staging/uwb/include/debug-cmd.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Ultra Wide Band
+ * Debug interface commands
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#ifndef __LINUX__UWB__DEBUG_CMD_H__
+#define __LINUX__UWB__DEBUG_CMD_H__
+
+#include <linux/types.h>
+
+/*
+ * Debug interface commands
+ *
+ * UWB_DBG_CMD_RSV_ESTABLISH: Establish a new unicast reservation.
+ *
+ * UWB_DBG_CMD_RSV_TERMINATE: Terminate the Nth reservation.
+ */
+
+enum uwb_dbg_cmd_type {
+ UWB_DBG_CMD_RSV_ESTABLISH = 1,
+ UWB_DBG_CMD_RSV_TERMINATE = 2,
+ UWB_DBG_CMD_IE_ADD = 3,
+ UWB_DBG_CMD_IE_RM = 4,
+ UWB_DBG_CMD_RADIO_START = 5,
+ UWB_DBG_CMD_RADIO_STOP = 6,
+};
+
+struct uwb_dbg_cmd_rsv_establish {
+ __u8 target[6];
+ __u8 type;
+ __u16 max_mas;
+ __u16 min_mas;
+ __u8 max_interval;
+};
+
+struct uwb_dbg_cmd_rsv_terminate {
+ int index;
+};
+
+struct uwb_dbg_cmd_ie {
+ __u8 data[128];
+ int len;
+};
+
+struct uwb_dbg_cmd {
+ __u32 type;
+ union {
+ struct uwb_dbg_cmd_rsv_establish rsv_establish;
+ struct uwb_dbg_cmd_rsv_terminate rsv_terminate;
+ struct uwb_dbg_cmd_ie ie_add;
+ struct uwb_dbg_cmd_ie ie_rm;
+ };
+};
+
+#endif /* #ifndef __LINUX__UWB__DEBUG_CMD_H__ */
diff --git a/drivers/staging/uwb/include/spec.h b/drivers/staging/uwb/include/spec.h
new file mode 100644
index 000000000000..5f75caf7b4ba
--- /dev/null
+++ b/drivers/staging/uwb/include/spec.h
@@ -0,0 +1,767 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Ultra Wide Band
+ * UWB Standard definitions
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * All these definitions are based on the ECMA-368 standard.
+ *
+ * Note all definitions are Little Endian in the wire, and we will
+ * convert them to host order before operating on the bitfields (that
+ * yes, we use extensively).
+ */
+
+#ifndef __LINUX__UWB_SPEC_H__
+#define __LINUX__UWB_SPEC_H__
+
+#include <linux/types.h>
+#include <linux/bitmap.h>
+#include <linux/if_ether.h>
+
+#define i1480_FW 0x00000303
+/* #define i1480_FW 0x00000302 */
+
+/**
+ * Number of Medium Access Slots in a superframe.
+ *
+ * UWB divides time in SuperFrames, each one divided in 256 pieces, or
+ * Medium Access Slots. See MBOA MAC[5.4.5] for details. The MAS is the
+ * basic bandwidth allocation unit in UWB.
+ */
+enum { UWB_NUM_MAS = 256 };
+
+/**
+ * Number of Zones in superframe.
+ *
+ * UWB divides the superframe into zones with numbering starting from BPST.
+ * See MBOA MAC[16.8.6]
+ */
+enum { UWB_NUM_ZONES = 16 };
+
+/*
+ * Number of MAS in a zone.
+ */
+#define UWB_MAS_PER_ZONE (UWB_NUM_MAS / UWB_NUM_ZONES)
+
+/*
+ * Number of MAS required before a row can be considered available.
+ */
+#define UWB_USABLE_MAS_PER_ROW (UWB_NUM_ZONES - 1)
+
+/*
+ * Number of streams per DRP reservation between a pair of devices.
+ *
+ * [ECMA-368] section 16.8.6.
+ */
+enum { UWB_NUM_STREAMS = 8 };
+
+/*
+ * mMasLength
+ *
+ * The length of a MAS in microseconds.
+ *
+ * [ECMA-368] section 17.16.
+ */
+enum { UWB_MAS_LENGTH_US = 256 };
+
+/*
+ * mBeaconSlotLength
+ *
+ * The length of the beacon slot in microseconds.
+ *
+ * [ECMA-368] section 17.16
+ */
+enum { UWB_BEACON_SLOT_LENGTH_US = 85 };
+
+/*
+ * mMaxLostBeacons
+ *
+ * The number beacons missing in consecutive superframes before a
+ * device can be considered as unreachable.
+ *
+ * [ECMA-368] section 17.16
+ */
+enum { UWB_MAX_LOST_BEACONS = 3 };
+
+/*
+ * mDRPBackOffWinMin
+ *
+ * The minimum number of superframes to wait before trying to reserve
+ * extra MAS.
+ *
+ * [ECMA-368] section 17.16
+ */
+enum { UWB_DRP_BACKOFF_WIN_MIN = 2 };
+
+/*
+ * mDRPBackOffWinMax
+ *
+ * The maximum number of superframes to wait before trying to reserve
+ * extra MAS.
+ *
+ * [ECMA-368] section 17.16
+ */
+enum { UWB_DRP_BACKOFF_WIN_MAX = 16 };
+
+/*
+ * Length of a superframe in microseconds.
+ */
+#define UWB_SUPERFRAME_LENGTH_US (UWB_MAS_LENGTH_US * UWB_NUM_MAS)
+
+/**
+ * UWB MAC address
+ *
+ * It is *imperative* that this struct is exactly 6 packed bytes (as
+ * it is also used to define headers sent down and up the wire/radio).
+ */
+struct uwb_mac_addr {
+ u8 data[ETH_ALEN];
+} __attribute__((packed));
+
+
+/**
+ * UWB device address
+ *
+ * It is *imperative* that this struct is exactly 6 packed bytes (as
+ * it is also used to define headers sent down and up the wire/radio).
+ */
+struct uwb_dev_addr {
+ u8 data[2];
+} __attribute__((packed));
+
+
+/**
+ * Types of UWB addresses
+ *
+ * Order matters (by size).
+ */
+enum uwb_addr_type {
+ UWB_ADDR_DEV = 0,
+ UWB_ADDR_MAC = 1,
+};
+
+
+/** Size of a char buffer for printing a MAC/device address */
+enum { UWB_ADDR_STRSIZE = 32 };
+
+
+/** UWB WiMedia protocol IDs. */
+enum uwb_prid {
+ UWB_PRID_WLP_RESERVED = 0x0000,
+ UWB_PRID_WLP = 0x0001,
+ UWB_PRID_WUSB_BOT = 0x0010,
+ UWB_PRID_WUSB = 0x0010,
+ UWB_PRID_WUSB_TOP = 0x001F,
+};
+
+
+/** PHY Rate (MBOA MAC[7.8.12, Table 61]) */
+enum uwb_phy_rate {
+ UWB_PHY_RATE_53 = 0,
+ UWB_PHY_RATE_80,
+ UWB_PHY_RATE_106,
+ UWB_PHY_RATE_160,
+ UWB_PHY_RATE_200,
+ UWB_PHY_RATE_320,
+ UWB_PHY_RATE_400,
+ UWB_PHY_RATE_480,
+ UWB_PHY_RATE_INVALID
+};
+
+
+/**
+ * Different ways to scan (MBOA MAC[6.2.2, Table 8], WUSB[Table 8-78])
+ */
+enum uwb_scan_type {
+ UWB_SCAN_ONLY = 0,
+ UWB_SCAN_OUTSIDE_BP,
+ UWB_SCAN_WHILE_INACTIVE,
+ UWB_SCAN_DISABLED,
+ UWB_SCAN_ONLY_STARTTIME,
+ UWB_SCAN_TOP
+};
+
+
+/** ACK Policy types (MBOA MAC[7.2.1.3]) */
+enum uwb_ack_pol {
+ UWB_ACK_NO = 0,
+ UWB_ACK_INM = 1,
+ UWB_ACK_B = 2,
+ UWB_ACK_B_REQ = 3,
+};
+
+
+/** DRP reservation types ([ECMA-368 table 106) */
+enum uwb_drp_type {
+ UWB_DRP_TYPE_ALIEN_BP = 0,
+ UWB_DRP_TYPE_HARD,
+ UWB_DRP_TYPE_SOFT,
+ UWB_DRP_TYPE_PRIVATE,
+ UWB_DRP_TYPE_PCA,
+};
+
+
+/** DRP Reason Codes ([ECMA-368] table 107) */
+enum uwb_drp_reason {
+ UWB_DRP_REASON_ACCEPTED = 0,
+ UWB_DRP_REASON_CONFLICT,
+ UWB_DRP_REASON_PENDING,
+ UWB_DRP_REASON_DENIED,
+ UWB_DRP_REASON_MODIFIED,
+};
+
+/** Relinquish Request Reason Codes ([ECMA-368] table 113) */
+enum uwb_relinquish_req_reason {
+ UWB_RELINQUISH_REQ_REASON_NON_SPECIFIC = 0,
+ UWB_RELINQUISH_REQ_REASON_OVER_ALLOCATION,
+};
+
+/**
+ * DRP Notification Reason Codes (WHCI 0.95 [3.1.4.9])
+ */
+enum uwb_drp_notif_reason {
+ UWB_DRP_NOTIF_DRP_IE_RCVD = 0,
+ UWB_DRP_NOTIF_CONFLICT,
+ UWB_DRP_NOTIF_TERMINATE,
+};
+
+
+/** Allocation of MAS slots in a DRP request MBOA MAC[7.8.7] */
+struct uwb_drp_alloc {
+ __le16 zone_bm;
+ __le16 mas_bm;
+} __attribute__((packed));
+
+
+/** General MAC Header format (ECMA-368[16.2]) */
+struct uwb_mac_frame_hdr {
+ __le16 Frame_Control;
+ struct uwb_dev_addr DestAddr;
+ struct uwb_dev_addr SrcAddr;
+ __le16 Sequence_Control;
+ __le16 Access_Information;
+} __attribute__((packed));
+
+
+/**
+ * uwb_beacon_frame - a beacon frame including MAC headers
+ *
+ * [ECMA] section 16.3.
+ */
+struct uwb_beacon_frame {
+ struct uwb_mac_frame_hdr hdr;
+ struct uwb_mac_addr Device_Identifier; /* may be a NULL EUI-48 */
+ u8 Beacon_Slot_Number;
+ u8 Device_Control;
+ u8 IEData[];
+} __attribute__((packed));
+
+
+/** Information Element codes (MBOA MAC[T54]) */
+enum uwb_ie {
+ UWB_PCA_AVAILABILITY = 2,
+ UWB_IE_DRP_AVAILABILITY = 8,
+ UWB_IE_DRP = 9,
+ UWB_BP_SWITCH_IE = 11,
+ UWB_MAC_CAPABILITIES_IE = 12,
+ UWB_PHY_CAPABILITIES_IE = 13,
+ UWB_APP_SPEC_PROBE_IE = 15,
+ UWB_IDENTIFICATION_IE = 19,
+ UWB_MASTER_KEY_ID_IE = 20,
+ UWB_RELINQUISH_REQUEST_IE = 21,
+ UWB_IE_WLP = 250, /* WiMedia Logical Link Control Protocol WLP 0.99 */
+ UWB_APP_SPEC_IE = 255,
+};
+
+
+/**
+ * Header common to all Information Elements (IEs)
+ */
+struct uwb_ie_hdr {
+ u8 element_id; /* enum uwb_ie */
+ u8 length;
+} __attribute__((packed));
+
+
+/** Dynamic Reservation Protocol IE (MBOA MAC[7.8.6]) */
+struct uwb_ie_drp {
+ struct uwb_ie_hdr hdr;
+ __le16 drp_control;
+ struct uwb_dev_addr dev_addr;
+ struct uwb_drp_alloc allocs[];
+} __attribute__((packed));
+
+static inline int uwb_ie_drp_type(struct uwb_ie_drp *ie)
+{
+ return (le16_to_cpu(ie->drp_control) >> 0) & 0x7;
+}
+
+static inline int uwb_ie_drp_stream_index(struct uwb_ie_drp *ie)
+{
+ return (le16_to_cpu(ie->drp_control) >> 3) & 0x7;
+}
+
+static inline int uwb_ie_drp_reason_code(struct uwb_ie_drp *ie)
+{
+ return (le16_to_cpu(ie->drp_control) >> 6) & 0x7;
+}
+
+static inline int uwb_ie_drp_status(struct uwb_ie_drp *ie)
+{
+ return (le16_to_cpu(ie->drp_control) >> 9) & 0x1;
+}
+
+static inline int uwb_ie_drp_owner(struct uwb_ie_drp *ie)
+{
+ return (le16_to_cpu(ie->drp_control) >> 10) & 0x1;
+}
+
+static inline int uwb_ie_drp_tiebreaker(struct uwb_ie_drp *ie)
+{
+ return (le16_to_cpu(ie->drp_control) >> 11) & 0x1;
+}
+
+static inline int uwb_ie_drp_unsafe(struct uwb_ie_drp *ie)
+{
+ return (le16_to_cpu(ie->drp_control) >> 12) & 0x1;
+}
+
+static inline void uwb_ie_drp_set_type(struct uwb_ie_drp *ie, enum uwb_drp_type type)
+{
+ u16 drp_control = le16_to_cpu(ie->drp_control);
+ drp_control = (drp_control & ~(0x7 << 0)) | (type << 0);
+ ie->drp_control = cpu_to_le16(drp_control);
+}
+
+static inline void uwb_ie_drp_set_stream_index(struct uwb_ie_drp *ie, int stream_index)
+{
+ u16 drp_control = le16_to_cpu(ie->drp_control);
+ drp_control = (drp_control & ~(0x7 << 3)) | (stream_index << 3);
+ ie->drp_control = cpu_to_le16(drp_control);
+}
+
+static inline void uwb_ie_drp_set_reason_code(struct uwb_ie_drp *ie,
+ enum uwb_drp_reason reason_code)
+{
+ u16 drp_control = le16_to_cpu(ie->drp_control);
+ drp_control = (ie->drp_control & ~(0x7 << 6)) | (reason_code << 6);
+ ie->drp_control = cpu_to_le16(drp_control);
+}
+
+static inline void uwb_ie_drp_set_status(struct uwb_ie_drp *ie, int status)
+{
+ u16 drp_control = le16_to_cpu(ie->drp_control);
+ drp_control = (drp_control & ~(0x1 << 9)) | (status << 9);
+ ie->drp_control = cpu_to_le16(drp_control);
+}
+
+static inline void uwb_ie_drp_set_owner(struct uwb_ie_drp *ie, int owner)
+{
+ u16 drp_control = le16_to_cpu(ie->drp_control);
+ drp_control = (drp_control & ~(0x1 << 10)) | (owner << 10);
+ ie->drp_control = cpu_to_le16(drp_control);
+}
+
+static inline void uwb_ie_drp_set_tiebreaker(struct uwb_ie_drp *ie, int tiebreaker)
+{
+ u16 drp_control = le16_to_cpu(ie->drp_control);
+ drp_control = (drp_control & ~(0x1 << 11)) | (tiebreaker << 11);
+ ie->drp_control = cpu_to_le16(drp_control);
+}
+
+static inline void uwb_ie_drp_set_unsafe(struct uwb_ie_drp *ie, int unsafe)
+{
+ u16 drp_control = le16_to_cpu(ie->drp_control);
+ drp_control = (drp_control & ~(0x1 << 12)) | (unsafe << 12);
+ ie->drp_control = cpu_to_le16(drp_control);
+}
+
+/** Dynamic Reservation Protocol IE (MBOA MAC[7.8.7]) */
+struct uwb_ie_drp_avail {
+ struct uwb_ie_hdr hdr;
+ DECLARE_BITMAP(bmp, UWB_NUM_MAS);
+} __attribute__((packed));
+
+/* Relinqish Request IE ([ECMA-368] section 16.8.19). */
+struct uwb_relinquish_request_ie {
+ struct uwb_ie_hdr hdr;
+ __le16 relinquish_req_control;
+ struct uwb_dev_addr dev_addr;
+ struct uwb_drp_alloc allocs[];
+} __attribute__((packed));
+
+static inline int uwb_ie_relinquish_req_reason_code(struct uwb_relinquish_request_ie *ie)
+{
+ return (le16_to_cpu(ie->relinquish_req_control) >> 0) & 0xf;
+}
+
+static inline void uwb_ie_relinquish_req_set_reason_code(struct uwb_relinquish_request_ie *ie,
+ int reason_code)
+{
+ u16 ctrl = le16_to_cpu(ie->relinquish_req_control);
+ ctrl = (ctrl & ~(0xf << 0)) | (reason_code << 0);
+ ie->relinquish_req_control = cpu_to_le16(ctrl);
+}
+
+/**
+ * The Vendor ID is set to an OUI that indicates the vendor of the device.
+ * ECMA-368 [16.8.10]
+ */
+struct uwb_vendor_id {
+ u8 data[3];
+} __attribute__((packed));
+
+/**
+ * The device type ID
+ * FIXME: clarify what this means
+ * ECMA-368 [16.8.10]
+ */
+struct uwb_device_type_id {
+ u8 data[3];
+} __attribute__((packed));
+
+
+/**
+ * UWB device information types
+ * ECMA-368 [16.8.10]
+ */
+enum uwb_dev_info_type {
+ UWB_DEV_INFO_VENDOR_ID = 0,
+ UWB_DEV_INFO_VENDOR_TYPE,
+ UWB_DEV_INFO_NAME,
+};
+
+/**
+ * UWB device information found in Identification IE
+ * ECMA-368 [16.8.10]
+ */
+struct uwb_dev_info {
+ u8 type; /* enum uwb_dev_info_type */
+ u8 length;
+ u8 data[];
+} __attribute__((packed));
+
+/**
+ * UWB Identification IE
+ * ECMA-368 [16.8.10]
+ */
+struct uwb_identification_ie {
+ struct uwb_ie_hdr hdr;
+ struct uwb_dev_info info[];
+} __attribute__((packed));
+
+/*
+ * UWB Radio Controller
+ *
+ * These definitions are common to the Radio Control layers as
+ * exported by the WUSB1.0 HWA and WHCI interfaces.
+ */
+
+/** Radio Control Command Block (WUSB1.0[Table 8-65] and WHCI 0.95) */
+struct uwb_rccb {
+ u8 bCommandType; /* enum hwa_cet */
+ __le16 wCommand; /* Command code */
+ u8 bCommandContext; /* Context ID */
+} __attribute__((packed));
+
+
+/** Radio Control Event Block (WUSB[table 8-66], WHCI 0.95) */
+struct uwb_rceb {
+ u8 bEventType; /* enum hwa_cet */
+ __le16 wEvent; /* Event code */
+ u8 bEventContext; /* Context ID */
+} __attribute__((packed));
+
+
+enum {
+ UWB_RC_CET_GENERAL = 0, /* General Command/Event type */
+ UWB_RC_CET_EX_TYPE_1 = 1, /* Extended Type 1 Command/Event type */
+};
+
+/* Commands to the radio controller */
+enum uwb_rc_cmd {
+ UWB_RC_CMD_CHANNEL_CHANGE = 16,
+ UWB_RC_CMD_DEV_ADDR_MGMT = 17, /* Device Address Management */
+ UWB_RC_CMD_GET_IE = 18, /* GET Information Elements */
+ UWB_RC_CMD_RESET = 19,
+ UWB_RC_CMD_SCAN = 20, /* Scan management */
+ UWB_RC_CMD_SET_BEACON_FILTER = 21,
+ UWB_RC_CMD_SET_DRP_IE = 22, /* Dynamic Reservation Protocol IEs */
+ UWB_RC_CMD_SET_IE = 23, /* Information Element management */
+ UWB_RC_CMD_SET_NOTIFICATION_FILTER = 24,
+ UWB_RC_CMD_SET_TX_POWER = 25,
+ UWB_RC_CMD_SLEEP = 26,
+ UWB_RC_CMD_START_BEACON = 27,
+ UWB_RC_CMD_STOP_BEACON = 28,
+ UWB_RC_CMD_BP_MERGE = 29,
+ UWB_RC_CMD_SEND_COMMAND_FRAME = 30,
+ UWB_RC_CMD_SET_ASIE_NOTIF = 31,
+};
+
+/* Notifications from the radio controller */
+enum uwb_rc_evt {
+ UWB_RC_EVT_IE_RCV = 0,
+ UWB_RC_EVT_BEACON = 1,
+ UWB_RC_EVT_BEACON_SIZE = 2,
+ UWB_RC_EVT_BPOIE_CHANGE = 3,
+ UWB_RC_EVT_BP_SLOT_CHANGE = 4,
+ UWB_RC_EVT_BP_SWITCH_IE_RCV = 5,
+ UWB_RC_EVT_DEV_ADDR_CONFLICT = 6,
+ UWB_RC_EVT_DRP_AVAIL = 7,
+ UWB_RC_EVT_DRP = 8,
+ UWB_RC_EVT_BP_SWITCH_STATUS = 9,
+ UWB_RC_EVT_CMD_FRAME_RCV = 10,
+ UWB_RC_EVT_CHANNEL_CHANGE_IE_RCV = 11,
+ /* Events (command responses) use the same code as the command */
+ UWB_RC_EVT_UNKNOWN_CMD_RCV = 65535,
+};
+
+enum uwb_rc_extended_type_1_cmd {
+ UWB_RC_SET_DAA_ENERGY_MASK = 32,
+ UWB_RC_SET_NOTIFICATION_FILTER_EX = 33,
+};
+
+enum uwb_rc_extended_type_1_evt {
+ UWB_RC_DAA_ENERGY_DETECTED = 0,
+};
+
+/* Radio Control Result Code. [WHCI] table 3-3. */
+enum {
+ UWB_RC_RES_SUCCESS = 0,
+ UWB_RC_RES_FAIL,
+ UWB_RC_RES_FAIL_HARDWARE,
+ UWB_RC_RES_FAIL_NO_SLOTS,
+ UWB_RC_RES_FAIL_BEACON_TOO_LARGE,
+ UWB_RC_RES_FAIL_INVALID_PARAMETER,
+ UWB_RC_RES_FAIL_UNSUPPORTED_PWR_LEVEL,
+ UWB_RC_RES_FAIL_INVALID_IE_DATA,
+ UWB_RC_RES_FAIL_BEACON_SIZE_EXCEEDED,
+ UWB_RC_RES_FAIL_CANCELLED,
+ UWB_RC_RES_FAIL_INVALID_STATE,
+ UWB_RC_RES_FAIL_INVALID_SIZE,
+ UWB_RC_RES_FAIL_ACK_NOT_RECEIVED,
+ UWB_RC_RES_FAIL_NO_MORE_ASIE_NOTIF,
+ UWB_RC_RES_FAIL_TIME_OUT = 255,
+};
+
+/* Confirm event. [WHCI] section 3.1.3.1 etc. */
+struct uwb_rc_evt_confirm {
+ struct uwb_rceb rceb;
+ u8 bResultCode;
+} __attribute__((packed));
+
+/* Device Address Management event. [WHCI] section 3.1.3.2. */
+struct uwb_rc_evt_dev_addr_mgmt {
+ struct uwb_rceb rceb;
+ u8 baAddr[ETH_ALEN];
+ u8 bResultCode;
+} __attribute__((packed));
+
+
+/* Get IE Event. [WHCI] section 3.1.3.3. */
+struct uwb_rc_evt_get_ie {
+ struct uwb_rceb rceb;
+ __le16 wIELength;
+ u8 IEData[];
+} __attribute__((packed));
+
+/* Set DRP IE Event. [WHCI] section 3.1.3.7. */
+struct uwb_rc_evt_set_drp_ie {
+ struct uwb_rceb rceb;
+ __le16 wRemainingSpace;
+ u8 bResultCode;
+} __attribute__((packed));
+
+/* Set IE Event. [WHCI] section 3.1.3.8. */
+struct uwb_rc_evt_set_ie {
+ struct uwb_rceb rceb;
+ __le16 RemainingSpace;
+ u8 bResultCode;
+} __attribute__((packed));
+
+/* Scan command. [WHCI] 3.1.3.5. */
+struct uwb_rc_cmd_scan {
+ struct uwb_rccb rccb;
+ u8 bChannelNumber;
+ u8 bScanState;
+ __le16 wStartTime;
+} __attribute__((packed));
+
+/* Set DRP IE command. [WHCI] section 3.1.3.7. */
+struct uwb_rc_cmd_set_drp_ie {
+ struct uwb_rccb rccb;
+ __le16 wIELength;
+ struct uwb_ie_drp IEData[];
+} __attribute__((packed));
+
+/* Set IE command. [WHCI] section 3.1.3.8. */
+struct uwb_rc_cmd_set_ie {
+ struct uwb_rccb rccb;
+ __le16 wIELength;
+ u8 IEData[];
+} __attribute__((packed));
+
+/* Set DAA Energy Mask event. [WHCI 0.96] section 3.1.3.17. */
+struct uwb_rc_evt_set_daa_energy_mask {
+ struct uwb_rceb rceb;
+ __le16 wLength;
+ u8 result;
+} __attribute__((packed));
+
+/* Set Notification Filter Extended event. [WHCI 0.96] section 3.1.3.18. */
+struct uwb_rc_evt_set_notification_filter_ex {
+ struct uwb_rceb rceb;
+ __le16 wLength;
+ u8 result;
+} __attribute__((packed));
+
+/* IE Received notification. [WHCI] section 3.1.4.1. */
+struct uwb_rc_evt_ie_rcv {
+ struct uwb_rceb rceb;
+ struct uwb_dev_addr SrcAddr;
+ __le16 wIELength;
+ u8 IEData[];
+} __attribute__((packed));
+
+/* Type of the received beacon. [WHCI] section 3.1.4.2. */
+enum uwb_rc_beacon_type {
+ UWB_RC_BEACON_TYPE_SCAN = 0,
+ UWB_RC_BEACON_TYPE_NEIGHBOR,
+ UWB_RC_BEACON_TYPE_OL_ALIEN,
+ UWB_RC_BEACON_TYPE_NOL_ALIEN,
+};
+
+/* Beacon received notification. [WHCI] 3.1.4.2. */
+struct uwb_rc_evt_beacon {
+ struct uwb_rceb rceb;
+ u8 bChannelNumber;
+ u8 bBeaconType;
+ __le16 wBPSTOffset;
+ u8 bLQI;
+ u8 bRSSI;
+ __le16 wBeaconInfoLength;
+ u8 BeaconInfo[];
+} __attribute__((packed));
+
+
+/* Beacon Size Change notification. [WHCI] section 3.1.4.3 */
+struct uwb_rc_evt_beacon_size {
+ struct uwb_rceb rceb;
+ __le16 wNewBeaconSize;
+} __attribute__((packed));
+
+
+/* BPOIE Change notification. [WHCI] section 3.1.4.4. */
+struct uwb_rc_evt_bpoie_change {
+ struct uwb_rceb rceb;
+ __le16 wBPOIELength;
+ u8 BPOIE[];
+} __attribute__((packed));
+
+
+/* Beacon Slot Change notification. [WHCI] section 3.1.4.5. */
+struct uwb_rc_evt_bp_slot_change {
+ struct uwb_rceb rceb;
+ u8 slot_info;
+} __attribute__((packed));
+
+static inline int uwb_rc_evt_bp_slot_change_slot_num(
+ const struct uwb_rc_evt_bp_slot_change *evt)
+{
+ return evt->slot_info & 0x7f;
+}
+
+static inline int uwb_rc_evt_bp_slot_change_no_slot(
+ const struct uwb_rc_evt_bp_slot_change *evt)
+{
+ return (evt->slot_info & 0x80) >> 7;
+}
+
+/* BP Switch IE Received notification. [WHCI] section 3.1.4.6. */
+struct uwb_rc_evt_bp_switch_ie_rcv {
+ struct uwb_rceb rceb;
+ struct uwb_dev_addr wSrcAddr;
+ __le16 wIELength;
+ u8 IEData[];
+} __attribute__((packed));
+
+/* DevAddr Conflict notification. [WHCI] section 3.1.4.7. */
+struct uwb_rc_evt_dev_addr_conflict {
+ struct uwb_rceb rceb;
+} __attribute__((packed));
+
+/* DRP notification. [WHCI] section 3.1.4.9. */
+struct uwb_rc_evt_drp {
+ struct uwb_rceb rceb;
+ struct uwb_dev_addr src_addr;
+ u8 reason;
+ u8 beacon_slot_number;
+ __le16 ie_length;
+ u8 ie_data[];
+} __attribute__((packed));
+
+static inline enum uwb_drp_notif_reason uwb_rc_evt_drp_reason(struct uwb_rc_evt_drp *evt)
+{
+ return evt->reason & 0x0f;
+}
+
+
+/* DRP Availability Change notification. [WHCI] section 3.1.4.8. */
+struct uwb_rc_evt_drp_avail {
+ struct uwb_rceb rceb;
+ DECLARE_BITMAP(bmp, UWB_NUM_MAS);
+} __attribute__((packed));
+
+/* BP switch status notification. [WHCI] section 3.1.4.10. */
+struct uwb_rc_evt_bp_switch_status {
+ struct uwb_rceb rceb;
+ u8 status;
+ u8 slot_offset;
+ __le16 bpst_offset;
+ u8 move_countdown;
+} __attribute__((packed));
+
+/* Command Frame Received notification. [WHCI] section 3.1.4.11. */
+struct uwb_rc_evt_cmd_frame_rcv {
+ struct uwb_rceb rceb;
+ __le16 receive_time;
+ struct uwb_dev_addr wSrcAddr;
+ struct uwb_dev_addr wDstAddr;
+ __le16 control;
+ __le16 reserved;
+ __le16 dataLength;
+ u8 data[];
+} __attribute__((packed));
+
+/* Channel Change IE Received notification. [WHCI] section 3.1.4.12. */
+struct uwb_rc_evt_channel_change_ie_rcv {
+ struct uwb_rceb rceb;
+ struct uwb_dev_addr wSrcAddr;
+ __le16 wIELength;
+ u8 IEData[];
+} __attribute__((packed));
+
+/* DAA Energy Detected notification. [WHCI 0.96] section 3.1.4.14. */
+struct uwb_rc_evt_daa_energy_detected {
+ struct uwb_rceb rceb;
+ __le16 wLength;
+ u8 bandID;
+ u8 reserved;
+ u8 toneBmp[16];
+} __attribute__((packed));
+
+
+/**
+ * Radio Control Interface Class Descriptor
+ *
+ * WUSB 1.0 [8.6.1.2]
+ */
+struct uwb_rc_control_intf_class_desc {
+ u8 bLength;
+ u8 bDescriptorType;
+ __le16 bcdRCIVersion;
+} __attribute__((packed));
+
+#endif /* #ifndef __LINUX__UWB_SPEC_H__ */
diff --git a/drivers/staging/uwb/include/umc.h b/drivers/staging/uwb/include/umc.h
new file mode 100644
index 000000000000..ddbceb39ad15
--- /dev/null
+++ b/drivers/staging/uwb/include/umc.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UWB Multi-interface Controller support.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * UMC (UWB Multi-interface Controller) capabilities (e.g., radio
+ * controller, host controller) are presented as devices on the "umc"
+ * bus.
+ *
+ * The radio controller is not strictly a UMC capability but it's
+ * useful to present it as such.
+ *
+ * References:
+ *
+ * [WHCI] Wireless Host Controller Interface Specification for
+ * Certified Wireless Universal Serial Bus, revision 0.95.
+ *
+ * How this works is kind of convoluted but simple. The whci.ko driver
+ * loads when WHCI devices are detected. These WHCI devices expose
+ * many devices in the same PCI function (they couldn't have reused
+ * functions, no), so for each PCI function that exposes these many
+ * devices, whci ceates a umc_dev [whci_probe() -> whci_add_cap()]
+ * with umc_device_create() and adds it to the bus with
+ * umc_device_register().
+ *
+ * umc_device_register() calls device_register() which will push the
+ * bus management code to load your UMC driver's somehting_probe()
+ * that you have registered for that capability code.
+ *
+ * Now when the WHCI device is removed, whci_remove() will go over
+ * each umc_dev assigned to each of the PCI function's capabilities
+ * and through whci_del_cap() call umc_device_unregister() each
+ * created umc_dev. Of course, if you are bound to the device, your
+ * driver's something_remove() will be called.
+ */
+
+#ifndef _LINUX_UWB_UMC_H_
+#define _LINUX_UWB_UMC_H_
+
+#include <linux/device.h>
+#include <linux/pci.h>
+
+/*
+ * UMC capability IDs.
+ *
+ * 0x00 is reserved so use it for the radio controller device.
+ *
+ * [WHCI] table 2-8
+ */
+#define UMC_CAP_ID_WHCI_RC 0x00 /* radio controller */
+#define UMC_CAP_ID_WHCI_WUSB_HC 0x01 /* WUSB host controller */
+
+/**
+ * struct umc_dev - UMC capability device
+ *
+ * @version: version of the specification this capability conforms to.
+ * @cap_id: capability ID.
+ * @bar: PCI Bar (64 bit) where the resource lies
+ * @resource: register space resource.
+ * @irq: interrupt line.
+ */
+struct umc_dev {
+ u16 version;
+ u8 cap_id;
+ u8 bar;
+ struct resource resource;
+ unsigned irq;
+ struct device dev;
+};
+
+#define to_umc_dev(d) container_of(d, struct umc_dev, dev)
+
+/**
+ * struct umc_driver - UMC capability driver
+ * @cap_id: supported capability ID.
+ * @match: driver specific capability matching function.
+ * @match_data: driver specific data for match() (e.g., a
+ * table of pci_device_id's if umc_match_pci_id() is used).
+ */
+struct umc_driver {
+ char *name;
+ u8 cap_id;
+ int (*match)(struct umc_driver *, struct umc_dev *);
+ const void *match_data;
+
+ int (*probe)(struct umc_dev *);
+ void (*remove)(struct umc_dev *);
+ int (*pre_reset)(struct umc_dev *);
+ int (*post_reset)(struct umc_dev *);
+
+ struct device_driver driver;
+};
+
+#define to_umc_driver(d) container_of(d, struct umc_driver, driver)
+
+extern struct bus_type umc_bus_type;
+
+struct umc_dev *umc_device_create(struct device *parent, int n);
+int __must_check umc_device_register(struct umc_dev *umc);
+void umc_device_unregister(struct umc_dev *umc);
+
+int __must_check __umc_driver_register(struct umc_driver *umc_drv,
+ struct module *mod,
+ const char *mod_name);
+
+/**
+ * umc_driver_register - register a UMC capabiltity driver.
+ * @umc_drv: pointer to the driver.
+ */
+#define umc_driver_register(umc_drv) \
+ __umc_driver_register(umc_drv, THIS_MODULE, KBUILD_MODNAME)
+
+void umc_driver_unregister(struct umc_driver *umc_drv);
+
+/*
+ * Utility function you can use to match (umc_driver->match) against a
+ * null-terminated array of 'struct pci_device_id' in
+ * umc_driver->match_data.
+ */
+int umc_match_pci_id(struct umc_driver *umc_drv, struct umc_dev *umc);
+
+/**
+ * umc_parent_pci_dev - return the UMC's parent PCI device or NULL if none
+ * @umc_dev: UMC device whose parent PCI device we are looking for
+ *
+ * DIRTY!!! DON'T RELY ON THIS
+ *
+ * FIXME: This is as dirty as it gets, but we need some way to check
+ * the correct type of umc_dev->parent (so that for example, we can
+ * cast to pci_dev). Casting to pci_dev is necessary because at some
+ * point we need to request resources from the device. Mapping is
+ * easily over come (ioremap and stuff are bus agnostic), but hooking
+ * up to some error handlers (such as pci error handlers) might need
+ * this.
+ *
+ * THIS might (probably will) be removed in the future, so don't count
+ * on it.
+ */
+static inline struct pci_dev *umc_parent_pci_dev(struct umc_dev *umc_dev)
+{
+ struct pci_dev *pci_dev = NULL;
+ if (dev_is_pci(umc_dev->dev.parent))
+ pci_dev = to_pci_dev(umc_dev->dev.parent);
+ return pci_dev;
+}
+
+/**
+ * umc_dev_get() - reference a UMC device.
+ * @umc_dev: Pointer to UMC device.
+ *
+ * NOTE: we are assuming in this whole scheme that the parent device
+ * is referenced at _probe() time and unreferenced at _remove()
+ * time by the parent's subsystem.
+ */
+static inline struct umc_dev *umc_dev_get(struct umc_dev *umc_dev)
+{
+ get_device(&umc_dev->dev);
+ return umc_dev;
+}
+
+/**
+ * umc_dev_put() - unreference a UMC device.
+ * @umc_dev: Pointer to UMC device.
+ */
+static inline void umc_dev_put(struct umc_dev *umc_dev)
+{
+ put_device(&umc_dev->dev);
+}
+
+/**
+ * umc_set_drvdata - set UMC device's driver data.
+ * @umc_dev: Pointer to UMC device.
+ * @data: Data to set.
+ */
+static inline void umc_set_drvdata(struct umc_dev *umc_dev, void *data)
+{
+ dev_set_drvdata(&umc_dev->dev, data);
+}
+
+/**
+ * umc_get_drvdata - recover UMC device's driver data.
+ * @umc_dev: Pointer to UMC device.
+ */
+static inline void *umc_get_drvdata(struct umc_dev *umc_dev)
+{
+ return dev_get_drvdata(&umc_dev->dev);
+}
+
+int umc_controller_reset(struct umc_dev *umc);
+
+#endif /* #ifndef _LINUX_UWB_UMC_H_ */
diff --git a/drivers/staging/uwb/include/whci.h b/drivers/staging/uwb/include/whci.h
new file mode 100644
index 000000000000..1a5c2cc2b008
--- /dev/null
+++ b/drivers/staging/uwb/include/whci.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Wireless Host Controller Interface for Ultra-Wide-Band and Wireless USB
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * References:
+ * [WHCI] Wireless Host Controller Interface Specification for
+ * Certified Wireless Universal Serial Bus, revision 0.95.
+ */
+#ifndef _LINUX_UWB_WHCI_H_
+#define _LINUX_UWB_WHCI_H_
+
+#include <linux/pci.h>
+
+/*
+ * UWB interface capability registers (offsets from UWBBASE)
+ *
+ * [WHCI] section 2.2
+ */
+#define UWBCAPINFO 0x00 /* == UWBCAPDATA(0) */
+# define UWBCAPINFO_TO_N_CAPS(c) (((c) >> 0) & 0xFull)
+#define UWBCAPDATA(n) (8*(n))
+# define UWBCAPDATA_TO_VERSION(c) (((c) >> 32) & 0xFFFFull)
+# define UWBCAPDATA_TO_OFFSET(c) (((c) >> 18) & 0x3FFFull)
+# define UWBCAPDATA_TO_BAR(c) (((c) >> 16) & 0x3ull)
+# define UWBCAPDATA_TO_SIZE(c) ((((c) >> 8) & 0xFFull) * sizeof(u32))
+# define UWBCAPDATA_TO_CAP_ID(c) (((c) >> 0) & 0xFFull)
+
+/* Size of the WHCI capability data (including the RC capability) for
+ a device with n capabilities. */
+#define UWBCAPDATA_SIZE(n) (8 + 8*(n))
+
+
+/*
+ * URC registers (offsets from URCBASE)
+ *
+ * [WHCI] section 2.3
+ */
+#define URCCMD 0x00
+# define URCCMD_RESET (1 << 31) /* UMC Hardware reset */
+# define URCCMD_RS (1 << 30) /* Run/Stop */
+# define URCCMD_EARV (1 << 29) /* Event Address Register Valid */
+# define URCCMD_ACTIVE (1 << 15) /* Command is active */
+# define URCCMD_IWR (1 << 14) /* Interrupt When Ready */
+# define URCCMD_SIZE_MASK 0x00000fff /* Command size mask */
+#define URCSTS 0x04
+# define URCSTS_EPS (1 << 17) /* Event Processing Status */
+# define URCSTS_HALTED (1 << 16) /* RC halted */
+# define URCSTS_HSE (1 << 10) /* Host System Error...fried */
+# define URCSTS_ER (1 << 9) /* Event Ready */
+# define URCSTS_RCI (1 << 8) /* Ready for Command Interrupt */
+# define URCSTS_INT_MASK 0x00000700 /* URC interrupt sources */
+# define URCSTS_ISI 0x000000ff /* Interrupt Source Identification */
+#define URCINTR 0x08
+# define URCINTR_EN_ALL 0x000007ff /* Enable all interrupt sources */
+#define URCCMDADDR 0x10
+#define URCEVTADDR 0x18
+# define URCEVTADDR_OFFSET_MASK 0xfff /* Event pointer offset mask */
+
+
+/** Write 32 bit @value to little endian register at @addr */
+static inline
+void le_writel(u32 value, void __iomem *addr)
+{
+ iowrite32(value, addr);
+}
+
+
+/** Read from 32 bit little endian register at @addr */
+static inline
+u32 le_readl(void __iomem *addr)
+{
+ return ioread32(addr);
+}
+
+
+/** Write 64 bit @value to little endian register at @addr */
+static inline
+void le_writeq(u64 value, void __iomem *addr)
+{
+ iowrite32(value, addr);
+ iowrite32(value >> 32, addr + 4);
+}
+
+
+/** Read from 64 bit little endian register at @addr */
+static inline
+u64 le_readq(void __iomem *addr)
+{
+ u64 value;
+ value = ioread32(addr);
+ value |= (u64)ioread32(addr + 4) << 32;
+ return value;
+}
+
+extern int whci_wait_for(struct device *dev, u32 __iomem *reg,
+ u32 mask, u32 result,
+ unsigned long max_ms, const char *tag);
+
+#endif /* #ifndef _LINUX_UWB_WHCI_H_ */
diff --git a/drivers/staging/uwb/lc-dev.c b/drivers/staging/uwb/lc-dev.c
new file mode 100644
index 000000000000..3e5c07fd6b10
--- /dev/null
+++ b/drivers/staging/uwb/lc-dev.c
@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Life cycle of devices
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/err.h>
+#include <linux/kdev_t.h>
+#include <linux/random.h>
+#include <linux/stat.h>
+#include "uwb-internal.h"
+
+/* We initialize addresses to 0xff (invalid, as it is bcast) */
+static inline void uwb_dev_addr_init(struct uwb_dev_addr *addr)
+{
+ memset(&addr->data, 0xff, sizeof(addr->data));
+}
+
+static inline void uwb_mac_addr_init(struct uwb_mac_addr *addr)
+{
+ memset(&addr->data, 0xff, sizeof(addr->data));
+}
+
+/*
+ * Add callback @new to be called when an event occurs in @rc.
+ */
+int uwb_notifs_register(struct uwb_rc *rc, struct uwb_notifs_handler *new)
+{
+ if (mutex_lock_interruptible(&rc->notifs_chain.mutex))
+ return -ERESTARTSYS;
+ list_add(&new->list_node, &rc->notifs_chain.list);
+ mutex_unlock(&rc->notifs_chain.mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(uwb_notifs_register);
+
+/*
+ * Remove event handler (callback)
+ */
+int uwb_notifs_deregister(struct uwb_rc *rc, struct uwb_notifs_handler *entry)
+{
+ if (mutex_lock_interruptible(&rc->notifs_chain.mutex))
+ return -ERESTARTSYS;
+ list_del(&entry->list_node);
+ mutex_unlock(&rc->notifs_chain.mutex);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(uwb_notifs_deregister);
+
+/*
+ * Notify all event handlers of a given event on @rc
+ *
+ * We are called with a valid reference to the device, or NULL if the
+ * event is not for a particular event (e.g., a BG join event).
+ */
+void uwb_notify(struct uwb_rc *rc, struct uwb_dev *uwb_dev, enum uwb_notifs event)
+{
+ struct uwb_notifs_handler *handler;
+ if (mutex_lock_interruptible(&rc->notifs_chain.mutex))
+ return;
+ if (!list_empty(&rc->notifs_chain.list)) {
+ list_for_each_entry(handler, &rc->notifs_chain.list, list_node) {
+ handler->cb(handler->data, uwb_dev, event);
+ }
+ }
+ mutex_unlock(&rc->notifs_chain.mutex);
+}
+
+/*
+ * Release the backing device of a uwb_dev that has been dynamically allocated.
+ */
+static void uwb_dev_sys_release(struct device *dev)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+
+ uwb_bce_put(uwb_dev->bce);
+ memset(uwb_dev, 0x69, sizeof(*uwb_dev));
+ kfree(uwb_dev);
+}
+
+/*
+ * Initialize a UWB device instance
+ *
+ * Alloc, zero and call this function.
+ */
+void uwb_dev_init(struct uwb_dev *uwb_dev)
+{
+ mutex_init(&uwb_dev->mutex);
+ device_initialize(&uwb_dev->dev);
+ uwb_dev->dev.release = uwb_dev_sys_release;
+ uwb_dev_addr_init(&uwb_dev->dev_addr);
+ uwb_mac_addr_init(&uwb_dev->mac_addr);
+ bitmap_fill(uwb_dev->streams, UWB_NUM_GLOBAL_STREAMS);
+}
+
+static ssize_t uwb_dev_EUI_48_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ char addr[UWB_ADDR_STRSIZE];
+
+ uwb_mac_addr_print(addr, sizeof(addr), &uwb_dev->mac_addr);
+ return sprintf(buf, "%s\n", addr);
+}
+static DEVICE_ATTR(EUI_48, S_IRUGO, uwb_dev_EUI_48_show, NULL);
+
+static ssize_t uwb_dev_DevAddr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ char addr[UWB_ADDR_STRSIZE];
+
+ uwb_dev_addr_print(addr, sizeof(addr), &uwb_dev->dev_addr);
+ return sprintf(buf, "%s\n", addr);
+}
+static DEVICE_ATTR(DevAddr, S_IRUGO, uwb_dev_DevAddr_show, NULL);
+
+/*
+ * Show the BPST of this device.
+ *
+ * Calculated from the receive time of the device's beacon and it's
+ * slot number.
+ */
+static ssize_t uwb_dev_BPST_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_beca_e *bce;
+ struct uwb_beacon_frame *bf;
+ u16 bpst;
+
+ bce = uwb_dev->bce;
+ mutex_lock(&bce->mutex);
+ bf = (struct uwb_beacon_frame *)bce->be->BeaconInfo;
+ bpst = bce->be->wBPSTOffset
+ - (u16)(bf->Beacon_Slot_Number * UWB_BEACON_SLOT_LENGTH_US);
+ mutex_unlock(&bce->mutex);
+
+ return sprintf(buf, "%d\n", bpst);
+}
+static DEVICE_ATTR(BPST, S_IRUGO, uwb_dev_BPST_show, NULL);
+
+/*
+ * Show the IEs a device is beaconing
+ *
+ * We need to access the beacon cache, so we just lock it really
+ * quick, print the IEs and unlock.
+ *
+ * We have a reference on the cache entry, so that should be
+ * quite safe.
+ */
+static ssize_t uwb_dev_IEs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+
+ return uwb_bce_print_IEs(uwb_dev, uwb_dev->bce, buf, PAGE_SIZE);
+}
+static DEVICE_ATTR(IEs, S_IRUGO | S_IWUSR, uwb_dev_IEs_show, NULL);
+
+static ssize_t uwb_dev_LQE_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_beca_e *bce = uwb_dev->bce;
+ size_t result;
+
+ mutex_lock(&bce->mutex);
+ result = stats_show(&uwb_dev->bce->lqe_stats, buf);
+ mutex_unlock(&bce->mutex);
+ return result;
+}
+
+static ssize_t uwb_dev_LQE_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_beca_e *bce = uwb_dev->bce;
+ ssize_t result;
+
+ mutex_lock(&bce->mutex);
+ result = stats_store(&uwb_dev->bce->lqe_stats, buf, size);
+ mutex_unlock(&bce->mutex);
+ return result;
+}
+static DEVICE_ATTR(LQE, S_IRUGO | S_IWUSR, uwb_dev_LQE_show, uwb_dev_LQE_store);
+
+static ssize_t uwb_dev_RSSI_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_beca_e *bce = uwb_dev->bce;
+ size_t result;
+
+ mutex_lock(&bce->mutex);
+ result = stats_show(&uwb_dev->bce->rssi_stats, buf);
+ mutex_unlock(&bce->mutex);
+ return result;
+}
+
+static ssize_t uwb_dev_RSSI_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_beca_e *bce = uwb_dev->bce;
+ ssize_t result;
+
+ mutex_lock(&bce->mutex);
+ result = stats_store(&uwb_dev->bce->rssi_stats, buf, size);
+ mutex_unlock(&bce->mutex);
+ return result;
+}
+static DEVICE_ATTR(RSSI, S_IRUGO | S_IWUSR, uwb_dev_RSSI_show, uwb_dev_RSSI_store);
+
+
+static struct attribute *uwb_dev_attrs[] = {
+ &dev_attr_EUI_48.attr,
+ &dev_attr_DevAddr.attr,
+ &dev_attr_BPST.attr,
+ &dev_attr_IEs.attr,
+ &dev_attr_LQE.attr,
+ &dev_attr_RSSI.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(uwb_dev);
+
+/* UWB bus type. */
+struct bus_type uwb_bus_type = {
+ .name = "uwb",
+ .dev_groups = uwb_dev_groups,
+};
+
+/**
+ * Device SYSFS registration
+ */
+static int __uwb_dev_sys_add(struct uwb_dev *uwb_dev, struct device *parent_dev)
+{
+ struct device *dev;
+
+ dev = &uwb_dev->dev;
+ dev->parent = parent_dev;
+ dev_set_drvdata(dev, uwb_dev);
+
+ return device_add(dev);
+}
+
+
+static void __uwb_dev_sys_rm(struct uwb_dev *uwb_dev)
+{
+ dev_set_drvdata(&uwb_dev->dev, NULL);
+ device_del(&uwb_dev->dev);
+}
+
+
+/**
+ * Register and initialize a new UWB device
+ *
+ * Did you call uwb_dev_init() on it?
+ *
+ * @parent_rc: is the parent radio controller who has the link to the
+ * device. When registering the UWB device that is a UWB
+ * Radio Controller, we point back to it.
+ *
+ * If registering the device that is part of a radio, caller has set
+ * rc->uwb_dev->dev. Otherwise it is to be left NULL--a new one will
+ * be allocated.
+ */
+int uwb_dev_add(struct uwb_dev *uwb_dev, struct device *parent_dev,
+ struct uwb_rc *parent_rc)
+{
+ int result;
+ struct device *dev;
+
+ BUG_ON(uwb_dev == NULL);
+ BUG_ON(parent_dev == NULL);
+ BUG_ON(parent_rc == NULL);
+
+ mutex_lock(&uwb_dev->mutex);
+ dev = &uwb_dev->dev;
+ uwb_dev->rc = parent_rc;
+ result = __uwb_dev_sys_add(uwb_dev, parent_dev);
+ if (result < 0)
+ printk(KERN_ERR "UWB: unable to register dev %s with sysfs: %d\n",
+ dev_name(dev), result);
+ mutex_unlock(&uwb_dev->mutex);
+ return result;
+}
+
+
+void uwb_dev_rm(struct uwb_dev *uwb_dev)
+{
+ mutex_lock(&uwb_dev->mutex);
+ __uwb_dev_sys_rm(uwb_dev);
+ mutex_unlock(&uwb_dev->mutex);
+}
+
+
+static
+int __uwb_dev_try_get(struct device *dev, void *__target_uwb_dev)
+{
+ struct uwb_dev *target_uwb_dev = __target_uwb_dev;
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ if (uwb_dev == target_uwb_dev) {
+ uwb_dev_get(uwb_dev);
+ return 1;
+ } else
+ return 0;
+}
+
+
+/**
+ * Given a UWB device descriptor, validate and refcount it
+ *
+ * @returns NULL if the device does not exist or is quiescing; the ptr to
+ * it otherwise.
+ */
+struct uwb_dev *uwb_dev_try_get(struct uwb_rc *rc, struct uwb_dev *uwb_dev)
+{
+ if (uwb_dev_for_each(rc, __uwb_dev_try_get, uwb_dev))
+ return uwb_dev;
+ else
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(uwb_dev_try_get);
+
+
+/**
+ * Remove a device from the system [grunt for other functions]
+ */
+int __uwb_dev_offair(struct uwb_dev *uwb_dev, struct uwb_rc *rc)
+{
+ struct device *dev = &uwb_dev->dev;
+ char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE];
+
+ uwb_mac_addr_print(macbuf, sizeof(macbuf), &uwb_dev->mac_addr);
+ uwb_dev_addr_print(devbuf, sizeof(devbuf), &uwb_dev->dev_addr);
+ dev_info(dev, "uwb device (mac %s dev %s) disconnected from %s %s\n",
+ macbuf, devbuf,
+ uwb_dev->dev.bus->name,
+ rc ? dev_name(&(rc->uwb_dev.dev)) : "");
+ uwb_dev_rm(uwb_dev);
+ list_del(&uwb_dev->bce->node);
+ uwb_bce_put(uwb_dev->bce);
+ uwb_dev_put(uwb_dev); /* for the creation in _onair() */
+
+ return 0;
+}
+
+
+/**
+ * A device went off the air, clean up after it!
+ *
+ * This is called by the UWB Daemon (through the beacon purge function
+ * uwb_bcn_cache_purge) when it is detected that a device has been in
+ * radio silence for a while.
+ *
+ * If this device is actually a local radio controller we don't need
+ * to go through the offair process, as it is not registered as that.
+ *
+ * NOTE: uwb_bcn_cache.mutex is held!
+ */
+void uwbd_dev_offair(struct uwb_beca_e *bce)
+{
+ struct uwb_dev *uwb_dev;
+
+ uwb_dev = bce->uwb_dev;
+ if (uwb_dev) {
+ uwb_notify(uwb_dev->rc, uwb_dev, UWB_NOTIF_OFFAIR);
+ __uwb_dev_offair(uwb_dev, uwb_dev->rc);
+ }
+}
+
+
+/**
+ * A device went on the air, start it up!
+ *
+ * This is called by the UWB Daemon when it is detected that a device
+ * has popped up in the radio range of the radio controller.
+ *
+ * It will just create the freaking device, register the beacon and
+ * stuff and yatla, done.
+ *
+ *
+ * NOTE: uwb_beca.mutex is held, bce->mutex is held
+ */
+void uwbd_dev_onair(struct uwb_rc *rc, struct uwb_beca_e *bce)
+{
+ int result;
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_dev *uwb_dev;
+ char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE];
+
+ uwb_mac_addr_print(macbuf, sizeof(macbuf), bce->mac_addr);
+ uwb_dev_addr_print(devbuf, sizeof(devbuf), &bce->dev_addr);
+ uwb_dev = kzalloc(sizeof(struct uwb_dev), GFP_KERNEL);
+ if (uwb_dev == NULL) {
+ dev_err(dev, "new device %s: Cannot allocate memory\n",
+ macbuf);
+ return;
+ }
+ uwb_dev_init(uwb_dev); /* This sets refcnt to one, we own it */
+ uwb_dev->dev.bus = &uwb_bus_type;
+ uwb_dev->mac_addr = *bce->mac_addr;
+ uwb_dev->dev_addr = bce->dev_addr;
+ dev_set_name(&uwb_dev->dev, "%s", macbuf);
+
+ /* plug the beacon cache */
+ bce->uwb_dev = uwb_dev;
+ uwb_dev->bce = bce;
+ uwb_bce_get(bce); /* released in uwb_dev_sys_release() */
+
+ result = uwb_dev_add(uwb_dev, &rc->uwb_dev.dev, rc);
+ if (result < 0) {
+ dev_err(dev, "new device %s: cannot instantiate device\n",
+ macbuf);
+ goto error_dev_add;
+ }
+
+ dev_info(dev, "uwb device (mac %s dev %s) connected to %s %s\n",
+ macbuf, devbuf, uwb_dev->dev.bus->name,
+ dev_name(&(rc->uwb_dev.dev)));
+ uwb_notify(rc, uwb_dev, UWB_NOTIF_ONAIR);
+ return;
+
+error_dev_add:
+ bce->uwb_dev = NULL;
+ uwb_bce_put(bce);
+ kfree(uwb_dev);
+ return;
+}
+
+/**
+ * Iterate over the list of UWB devices, calling a @function on each
+ *
+ * See docs for bus_for_each()....
+ *
+ * @rc: radio controller for the devices.
+ * @function: function to call.
+ * @priv: data to pass to @function.
+ * @returns: 0 if no invocation of function() returned a value
+ * different to zero. That value otherwise.
+ */
+int uwb_dev_for_each(struct uwb_rc *rc, uwb_dev_for_each_f function, void *priv)
+{
+ return device_for_each_child(&rc->uwb_dev.dev, priv, function);
+}
+EXPORT_SYMBOL_GPL(uwb_dev_for_each);
diff --git a/drivers/staging/uwb/lc-rc.c b/drivers/staging/uwb/lc-rc.c
new file mode 100644
index 000000000000..ee31b221cdc2
--- /dev/null
+++ b/drivers/staging/uwb/lc-rc.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Life cycle of radio controllers
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ *
+ * A UWB radio controller is also a UWB device, so it embeds one...
+ *
+ * List of RCs comes from the 'struct class uwb_rc_class'.
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/random.h>
+#include <linux/kdev_t.h>
+#include <linux/etherdevice.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+
+#include "uwb-internal.h"
+
+static int uwb_rc_index_match(struct device *dev, const void *data)
+{
+ const int *index = data;
+ struct uwb_rc *rc = dev_get_drvdata(dev);
+
+ if (rc->index == *index)
+ return 1;
+ return 0;
+}
+
+static struct uwb_rc *uwb_rc_find_by_index(int index)
+{
+ struct device *dev;
+ struct uwb_rc *rc = NULL;
+
+ dev = class_find_device(&uwb_rc_class, NULL, &index, uwb_rc_index_match);
+ if (dev) {
+ rc = dev_get_drvdata(dev);
+ put_device(dev);
+ }
+
+ return rc;
+}
+
+static int uwb_rc_new_index(void)
+{
+ int index = 0;
+
+ for (;;) {
+ if (!uwb_rc_find_by_index(index))
+ return index;
+ if (++index < 0)
+ index = 0;
+ }
+}
+
+/**
+ * Release the backing device of a uwb_rc that has been dynamically allocated.
+ */
+static void uwb_rc_sys_release(struct device *dev)
+{
+ struct uwb_dev *uwb_dev = container_of(dev, struct uwb_dev, dev);
+ struct uwb_rc *rc = container_of(uwb_dev, struct uwb_rc, uwb_dev);
+
+ uwb_rc_ie_release(rc);
+ kfree(rc);
+}
+
+
+void uwb_rc_init(struct uwb_rc *rc)
+{
+ struct uwb_dev *uwb_dev = &rc->uwb_dev;
+
+ uwb_dev_init(uwb_dev);
+ rc->uwb_dev.dev.class = &uwb_rc_class;
+ rc->uwb_dev.dev.release = uwb_rc_sys_release;
+ uwb_rc_neh_create(rc);
+ rc->beaconing = -1;
+ rc->scan_type = UWB_SCAN_DISABLED;
+ INIT_LIST_HEAD(&rc->notifs_chain.list);
+ mutex_init(&rc->notifs_chain.mutex);
+ INIT_LIST_HEAD(&rc->uwb_beca.list);
+ mutex_init(&rc->uwb_beca.mutex);
+ uwb_drp_avail_init(rc);
+ uwb_rc_ie_init(rc);
+ uwb_rsv_init(rc);
+ uwb_rc_pal_init(rc);
+}
+EXPORT_SYMBOL_GPL(uwb_rc_init);
+
+
+struct uwb_rc *uwb_rc_alloc(void)
+{
+ struct uwb_rc *rc;
+ rc = kzalloc(sizeof(*rc), GFP_KERNEL);
+ if (rc == NULL)
+ return NULL;
+ uwb_rc_init(rc);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_alloc);
+
+/*
+ * Show the ASIE that is broadcast in the UWB beacon by this uwb_rc device.
+ */
+static ssize_t ASIE_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_rc *rc = uwb_dev->rc;
+ struct uwb_ie_hdr *ie;
+ void *ptr;
+ size_t len;
+ int result = 0;
+
+ /* init empty buffer. */
+ result = scnprintf(buf, PAGE_SIZE, "\n");
+ mutex_lock(&rc->ies_mutex);
+ /* walk IEData looking for an ASIE. */
+ ptr = rc->ies->IEData;
+ len = le16_to_cpu(rc->ies->wIELength);
+ for (;;) {
+ ie = uwb_ie_next(&ptr, &len);
+ if (!ie)
+ break;
+ if (ie->element_id == UWB_APP_SPEC_IE) {
+ result = uwb_ie_dump_hex(ie,
+ ie->length + sizeof(struct uwb_ie_hdr),
+ buf, PAGE_SIZE);
+ break;
+ }
+ }
+ mutex_unlock(&rc->ies_mutex);
+
+ return result;
+}
+
+/*
+ * Update the ASIE that is broadcast in the UWB beacon by this uwb_rc device.
+ */
+static ssize_t ASIE_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_rc *rc = uwb_dev->rc;
+ char ie_buf[255];
+ int result, ie_len = 0;
+ const char *cur_ptr = buf;
+ struct uwb_ie_hdr *ie;
+
+ /* empty string means clear the ASIE. */
+ if (strlen(buf) <= 1) {
+ uwb_rc_ie_rm(rc, UWB_APP_SPEC_IE);
+ return size;
+ }
+
+ /* if non-empty string, convert string of hex chars to binary. */
+ while (ie_len < sizeof(ie_buf)) {
+ int char_count;
+
+ if (sscanf(cur_ptr, " %02hhX %n",
+ &(ie_buf[ie_len]), &char_count) > 0) {
+ ++ie_len;
+ /* skip chars read from cur_ptr. */
+ cur_ptr += char_count;
+ } else {
+ break;
+ }
+ }
+
+ /* validate IE length and type. */
+ if (ie_len < sizeof(struct uwb_ie_hdr)) {
+ dev_err(dev, "%s: Invalid ASIE size %d.\n", __func__, ie_len);
+ return -EINVAL;
+ }
+
+ ie = (struct uwb_ie_hdr *)ie_buf;
+ if (ie->element_id != UWB_APP_SPEC_IE) {
+ dev_err(dev, "%s: Invalid IE element type size = 0x%02X.\n",
+ __func__, ie->element_id);
+ return -EINVAL;
+ }
+
+ /* bounds check length field from user. */
+ if (ie->length > (ie_len - sizeof(struct uwb_ie_hdr)))
+ ie->length = ie_len - sizeof(struct uwb_ie_hdr);
+
+ /*
+ * Valid ASIE received. Remove current ASIE then add the new one using
+ * uwb_rc_ie_add.
+ */
+ uwb_rc_ie_rm(rc, UWB_APP_SPEC_IE);
+
+ result = uwb_rc_ie_add(rc, ie, ie->length + sizeof(struct uwb_ie_hdr));
+
+ return result >= 0 ? size : result;
+}
+static DEVICE_ATTR_RW(ASIE);
+
+static struct attribute *rc_attrs[] = {
+ &dev_attr_mac_address.attr,
+ &dev_attr_scan.attr,
+ &dev_attr_beacon.attr,
+ &dev_attr_ASIE.attr,
+ NULL,
+};
+
+static const struct attribute_group rc_attr_group = {
+ .attrs = rc_attrs,
+};
+
+/*
+ * Registration of sysfs specific stuff
+ */
+static int uwb_rc_sys_add(struct uwb_rc *rc)
+{
+ return sysfs_create_group(&rc->uwb_dev.dev.kobj, &rc_attr_group);
+}
+
+
+static void __uwb_rc_sys_rm(struct uwb_rc *rc)
+{
+ sysfs_remove_group(&rc->uwb_dev.dev.kobj, &rc_attr_group);
+}
+
+/**
+ * uwb_rc_mac_addr_setup - get an RC's EUI-48 address or set it
+ * @rc: the radio controller.
+ *
+ * If the EUI-48 address is 00:00:00:00:00:00 or FF:FF:FF:FF:FF:FF
+ * then a random locally administered EUI-48 is generated and set on
+ * the device. The probability of address collisions is sufficiently
+ * unlikely (1/2^40 = 9.1e-13) that they're not checked for.
+ */
+static
+int uwb_rc_mac_addr_setup(struct uwb_rc *rc)
+{
+ int result;
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_dev *uwb_dev = &rc->uwb_dev;
+ char devname[UWB_ADDR_STRSIZE];
+ struct uwb_mac_addr addr;
+
+ result = uwb_rc_mac_addr_get(rc, &addr);
+ if (result < 0) {
+ dev_err(dev, "cannot retrieve UWB EUI-48 address: %d\n", result);
+ return result;
+ }
+
+ if (uwb_mac_addr_unset(&addr) || uwb_mac_addr_bcast(&addr)) {
+ addr.data[0] = 0x02; /* locally administered and unicast */
+ get_random_bytes(&addr.data[1], sizeof(addr.data)-1);
+
+ result = uwb_rc_mac_addr_set(rc, &addr);
+ if (result < 0) {
+ uwb_mac_addr_print(devname, sizeof(devname), &addr);
+ dev_err(dev, "cannot set EUI-48 address %s: %d\n",
+ devname, result);
+ return result;
+ }
+ }
+ uwb_dev->mac_addr = addr;
+ return 0;
+}
+
+
+
+static int uwb_rc_setup(struct uwb_rc *rc)
+{
+ int result;
+ struct device *dev = &rc->uwb_dev.dev;
+
+ result = uwb_radio_setup(rc);
+ if (result < 0) {
+ dev_err(dev, "cannot setup UWB radio: %d\n", result);
+ goto error;
+ }
+ result = uwb_rc_mac_addr_setup(rc);
+ if (result < 0) {
+ dev_err(dev, "cannot setup UWB MAC address: %d\n", result);
+ goto error;
+ }
+ result = uwb_rc_dev_addr_assign(rc);
+ if (result < 0) {
+ dev_err(dev, "cannot assign UWB DevAddr: %d\n", result);
+ goto error;
+ }
+ result = uwb_rc_ie_setup(rc);
+ if (result < 0) {
+ dev_err(dev, "cannot setup IE subsystem: %d\n", result);
+ goto error_ie_setup;
+ }
+ result = uwb_rsv_setup(rc);
+ if (result < 0) {
+ dev_err(dev, "cannot setup reservation subsystem: %d\n", result);
+ goto error_rsv_setup;
+ }
+ uwb_dbg_add_rc(rc);
+ return 0;
+
+error_rsv_setup:
+ uwb_rc_ie_release(rc);
+error_ie_setup:
+error:
+ return result;
+}
+
+
+/**
+ * Register a new UWB radio controller
+ *
+ * Did you call uwb_rc_init() on your rc?
+ *
+ * We assume that this is being called with a > 0 refcount on
+ * it [through ops->{get|put}_device(). We'll take our own, though.
+ *
+ * @parent_dev is our real device, the one that provides the actual UWB device
+ */
+int uwb_rc_add(struct uwb_rc *rc, struct device *parent_dev, void *priv)
+{
+ int result;
+ struct device *dev;
+ char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE];
+
+ rc->index = uwb_rc_new_index();
+
+ dev = &rc->uwb_dev.dev;
+ dev_set_name(dev, "uwb%d", rc->index);
+
+ rc->priv = priv;
+
+ init_waitqueue_head(&rc->uwbd.wq);
+ INIT_LIST_HEAD(&rc->uwbd.event_list);
+ spin_lock_init(&rc->uwbd.event_list_lock);
+
+ uwbd_start(rc);
+
+ result = rc->start(rc);
+ if (result < 0)
+ goto error_rc_start;
+
+ result = uwb_rc_setup(rc);
+ if (result < 0) {
+ dev_err(dev, "cannot setup UWB radio controller: %d\n", result);
+ goto error_rc_setup;
+ }
+
+ result = uwb_dev_add(&rc->uwb_dev, parent_dev, rc);
+ if (result < 0 && result != -EADDRNOTAVAIL)
+ goto error_dev_add;
+
+ result = uwb_rc_sys_add(rc);
+ if (result < 0) {
+ dev_err(parent_dev, "cannot register UWB radio controller "
+ "dev attributes: %d\n", result);
+ goto error_sys_add;
+ }
+
+ uwb_mac_addr_print(macbuf, sizeof(macbuf), &rc->uwb_dev.mac_addr);
+ uwb_dev_addr_print(devbuf, sizeof(devbuf), &rc->uwb_dev.dev_addr);
+ dev_info(dev,
+ "new uwb radio controller (mac %s dev %s) on %s %s\n",
+ macbuf, devbuf, parent_dev->bus->name, dev_name(parent_dev));
+ rc->ready = 1;
+ return 0;
+
+error_sys_add:
+ uwb_dev_rm(&rc->uwb_dev);
+error_dev_add:
+error_rc_setup:
+ rc->stop(rc);
+error_rc_start:
+ uwbd_stop(rc);
+ return result;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_add);
+
+
+static int uwb_dev_offair_helper(struct device *dev, void *priv)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+
+ return __uwb_dev_offair(uwb_dev, uwb_dev->rc);
+}
+
+/*
+ * Remove a Radio Controller; stop beaconing/scanning, disconnect all children
+ */
+void uwb_rc_rm(struct uwb_rc *rc)
+{
+ rc->ready = 0;
+
+ uwb_dbg_del_rc(rc);
+ uwb_rsv_remove_all(rc);
+ uwb_radio_shutdown(rc);
+
+ rc->stop(rc);
+
+ uwbd_stop(rc);
+ uwb_rc_neh_destroy(rc);
+
+ uwb_dev_lock(&rc->uwb_dev);
+ rc->priv = NULL;
+ rc->cmd = NULL;
+ uwb_dev_unlock(&rc->uwb_dev);
+ mutex_lock(&rc->uwb_beca.mutex);
+ uwb_dev_for_each(rc, uwb_dev_offair_helper, NULL);
+ __uwb_rc_sys_rm(rc);
+ mutex_unlock(&rc->uwb_beca.mutex);
+ uwb_rsv_cleanup(rc);
+ uwb_beca_release(rc);
+ uwb_dev_rm(&rc->uwb_dev);
+}
+EXPORT_SYMBOL_GPL(uwb_rc_rm);
+
+static int find_rc_try_get(struct device *dev, const void *data)
+{
+ const struct uwb_rc *target_rc = data;
+ struct uwb_rc *rc = dev_get_drvdata(dev);
+
+ if (rc == NULL) {
+ WARN_ON(1);
+ return 0;
+ }
+ if (rc == target_rc) {
+ if (rc->ready == 0)
+ return 0;
+ else
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Given a radio controller descriptor, validate and refcount it
+ *
+ * @returns NULL if the rc does not exist or is quiescing; the ptr to
+ * it otherwise.
+ */
+struct uwb_rc *__uwb_rc_try_get(struct uwb_rc *target_rc)
+{
+ struct device *dev;
+ struct uwb_rc *rc = NULL;
+
+ dev = class_find_device(&uwb_rc_class, NULL, target_rc,
+ find_rc_try_get);
+ if (dev) {
+ rc = dev_get_drvdata(dev);
+ __uwb_rc_get(rc);
+ put_device(dev);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(__uwb_rc_try_get);
+
+/*
+ * RC get for external refcount acquirers...
+ *
+ * Increments the refcount of the device and it's backend modules
+ */
+static inline struct uwb_rc *uwb_rc_get(struct uwb_rc *rc)
+{
+ if (rc->ready == 0)
+ return NULL;
+ uwb_dev_get(&rc->uwb_dev);
+ return rc;
+}
+
+static int find_rc_grandpa(struct device *dev, const void *data)
+{
+ const struct device *grandpa_dev = data;
+ struct uwb_rc *rc = dev_get_drvdata(dev);
+
+ if (rc->uwb_dev.dev.parent->parent == grandpa_dev) {
+ rc = uwb_rc_get(rc);
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Locate and refcount a radio controller given a common grand-parent
+ *
+ * @grandpa_dev Pointer to the 'grandparent' device structure.
+ * @returns NULL If the rc does not exist or is quiescing; the ptr to
+ * it otherwise, properly referenced.
+ *
+ * The Radio Control interface (or the UWB Radio Controller) is always
+ * an interface of a device. The parent is the interface, the
+ * grandparent is the device that encapsulates the interface.
+ *
+ * There is no need to lock around as the "grandpa" would be
+ * refcounted by the target, and to remove the referemes, the
+ * uwb_rc_class->sem would have to be taken--we hold it, ergo we
+ * should be safe.
+ */
+struct uwb_rc *uwb_rc_get_by_grandpa(const struct device *grandpa_dev)
+{
+ struct device *dev;
+ struct uwb_rc *rc = NULL;
+
+ dev = class_find_device(&uwb_rc_class, NULL, grandpa_dev,
+ find_rc_grandpa);
+ if (dev) {
+ rc = dev_get_drvdata(dev);
+ put_device(dev);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_get_by_grandpa);
+
+/**
+ * Find a radio controller by device address
+ *
+ * @returns the pointer to the radio controller, properly referenced
+ */
+static int find_rc_dev(struct device *dev, const void *data)
+{
+ const struct uwb_dev_addr *addr = data;
+ struct uwb_rc *rc = dev_get_drvdata(dev);
+
+ if (rc == NULL) {
+ WARN_ON(1);
+ return 0;
+ }
+ if (!uwb_dev_addr_cmp(&rc->uwb_dev.dev_addr, addr)) {
+ rc = uwb_rc_get(rc);
+ return 1;
+ }
+ return 0;
+}
+
+struct uwb_rc *uwb_rc_get_by_dev(const struct uwb_dev_addr *addr)
+{
+ struct device *dev;
+ struct uwb_rc *rc = NULL;
+
+ dev = class_find_device(&uwb_rc_class, NULL, addr, find_rc_dev);
+ if (dev) {
+ rc = dev_get_drvdata(dev);
+ put_device(dev);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_get_by_dev);
+
+/**
+ * Drop a reference on a radio controller
+ *
+ * This is the version that should be done by entities external to the
+ * UWB Radio Control stack (ie: clients of the API).
+ */
+void uwb_rc_put(struct uwb_rc *rc)
+{
+ __uwb_rc_put(rc);
+}
+EXPORT_SYMBOL_GPL(uwb_rc_put);
diff --git a/drivers/staging/uwb/neh.c b/drivers/staging/uwb/neh.c
new file mode 100644
index 000000000000..1695584be412
--- /dev/null
+++ b/drivers/staging/uwb/neh.c
@@ -0,0 +1,606 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * WUSB Wire Adapter: Radio Control Interface (WUSB[8])
+ * Notification and Event Handling
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * The RC interface of the Host Wire Adapter (USB dongle) or WHCI PCI
+ * card delivers a stream of notifications and events to the
+ * notification end event endpoint or area. This code takes care of
+ * getting a buffer with that data, breaking it up in separate
+ * notifications and events and then deliver those.
+ *
+ * Events are answers to commands and they carry a context ID that
+ * associates them to the command. Notifications are that,
+ * notifications, they come out of the blue and have a context ID of
+ * zero. Think of the context ID kind of like a handler. The
+ * uwb_rc_neh_* code deals with managing context IDs.
+ *
+ * This is why you require a handle to operate on a UWB host. When you
+ * open a handle a context ID is assigned to you.
+ *
+ * So, as it is done is:
+ *
+ * 1. Add an event handler [uwb_rc_neh_add()] (assigns a ctx id)
+ * 2. Issue command [rc->cmd(rc, ...)]
+ * 3. Arm the timeout timer [uwb_rc_neh_arm()]
+ * 4, Release the reference to the neh [uwb_rc_neh_put()]
+ * 5. Wait for the callback
+ * 6. Command result (RCEB) is passed to the callback
+ *
+ * If (2) fails, you should remove the handle [uwb_rc_neh_rm()]
+ * instead of arming the timer.
+ *
+ * Handles are for using in *serialized* code, single thread.
+ *
+ * When the notification/event comes, the IRQ handler/endpoint
+ * callback passes the data read to uwb_rc_neh_grok() which will break
+ * it up in a discrete series of events, look up who is listening for
+ * them and execute the pertinent callbacks.
+ *
+ * If the reader detects an error while reading the data stream, call
+ * uwb_rc_neh_error().
+ *
+ * CONSTRAINTS/ASSUMPTIONS:
+ *
+ * - Most notifications/events are small (less thank .5k), copying
+ * around is ok.
+ *
+ * - Notifications/events are ALWAYS smaller than PAGE_SIZE
+ *
+ * - Notifications/events always come in a single piece (ie: a buffer
+ * will always contain entire notifications/events).
+ *
+ * - we cannot know in advance how long each event is (because they
+ * lack a length field in their header--smart move by the standards
+ * body, btw). So we need a facility to get the event size given the
+ * header. This is what the EST code does (notif/Event Size
+ * Tables), check nest.c--as well, you can associate the size to
+ * the handle [w/ neh->extra_size()].
+ *
+ * - Most notifications/events are fixed size; only a few are variable
+ * size (NEST takes care of that).
+ *
+ * - Listeners of events expect them, so they usually provide a
+ * buffer, as they know the size. Listeners to notifications don't,
+ * so we allocate their buffers dynamically.
+ */
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/export.h>
+
+#include "uwb-internal.h"
+
+/*
+ * UWB Radio Controller Notification/Event Handle
+ *
+ * Represents an entity waiting for an event coming from the UWB Radio
+ * Controller with a given context id (context) and type (evt_type and
+ * evt). On reception of the notification/event, the callback (cb) is
+ * called with the event.
+ *
+ * If the timer expires before the event is received, the callback is
+ * called with -ETIMEDOUT as the event size.
+ */
+struct uwb_rc_neh {
+ struct kref kref;
+
+ struct uwb_rc *rc;
+ u8 evt_type;
+ __le16 evt;
+ u8 context;
+ u8 completed;
+ uwb_rc_cmd_cb_f cb;
+ void *arg;
+
+ struct timer_list timer;
+ struct list_head list_node;
+};
+
+static void uwb_rc_neh_timer(struct timer_list *t);
+
+static void uwb_rc_neh_release(struct kref *kref)
+{
+ struct uwb_rc_neh *neh = container_of(kref, struct uwb_rc_neh, kref);
+
+ kfree(neh);
+}
+
+static void uwb_rc_neh_get(struct uwb_rc_neh *neh)
+{
+ kref_get(&neh->kref);
+}
+
+/**
+ * uwb_rc_neh_put - release reference to a neh
+ * @neh: the neh
+ */
+void uwb_rc_neh_put(struct uwb_rc_neh *neh)
+{
+ kref_put(&neh->kref, uwb_rc_neh_release);
+}
+
+
+/**
+ * Assigns @neh a context id from @rc's pool
+ *
+ * @rc: UWB Radio Controller descriptor; @rc->neh_lock taken
+ * @neh: Notification/Event Handle
+ * @returns 0 if context id was assigned ok; < 0 errno on error (if
+ * all the context IDs are taken).
+ *
+ * (assumes @wa is locked).
+ *
+ * NOTE: WUSB spec reserves context ids 0x00 for notifications and
+ * 0xff is invalid, so they must not be used. Initialization
+ * fills up those two in the bitmap so they are not allocated.
+ *
+ * We spread the allocation around to reduce the possibility of two
+ * consecutive opened @neh's getting the same context ID assigned (to
+ * avoid surprises with late events that timed out long time ago). So
+ * first we search from where @rc->ctx_roll is, if not found, we
+ * search from zero.
+ */
+static
+int __uwb_rc_ctx_get(struct uwb_rc *rc, struct uwb_rc_neh *neh)
+{
+ int result;
+ result = find_next_zero_bit(rc->ctx_bm, UWB_RC_CTX_MAX,
+ rc->ctx_roll++);
+ if (result < UWB_RC_CTX_MAX)
+ goto found;
+ result = find_first_zero_bit(rc->ctx_bm, UWB_RC_CTX_MAX);
+ if (result < UWB_RC_CTX_MAX)
+ goto found;
+ return -ENFILE;
+found:
+ set_bit(result, rc->ctx_bm);
+ neh->context = result;
+ return 0;
+}
+
+
+/** Releases @neh's context ID back to @rc (@rc->neh_lock is locked). */
+static
+void __uwb_rc_ctx_put(struct uwb_rc *rc, struct uwb_rc_neh *neh)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ if (neh->context == 0)
+ return;
+ if (test_bit(neh->context, rc->ctx_bm) == 0) {
+ dev_err(dev, "context %u not set in bitmap\n",
+ neh->context);
+ WARN_ON(1);
+ }
+ clear_bit(neh->context, rc->ctx_bm);
+ neh->context = 0;
+}
+
+/**
+ * uwb_rc_neh_add - add a neh for a radio controller command
+ * @rc: the radio controller
+ * @cmd: the radio controller command
+ * @expected_type: the type of the expected response event
+ * @expected_event: the expected event ID
+ * @cb: callback for when the event is received
+ * @arg: argument for the callback
+ *
+ * Creates a neh and adds it to the list of those waiting for an
+ * event. A context ID will be assigned to the command.
+ */
+struct uwb_rc_neh *uwb_rc_neh_add(struct uwb_rc *rc, struct uwb_rccb *cmd,
+ u8 expected_type, u16 expected_event,
+ uwb_rc_cmd_cb_f cb, void *arg)
+{
+ int result;
+ unsigned long flags;
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_rc_neh *neh;
+
+ neh = kzalloc(sizeof(*neh), GFP_KERNEL);
+ if (neh == NULL) {
+ result = -ENOMEM;
+ goto error_kzalloc;
+ }
+
+ kref_init(&neh->kref);
+ INIT_LIST_HEAD(&neh->list_node);
+ timer_setup(&neh->timer, uwb_rc_neh_timer, 0);
+
+ neh->rc = rc;
+ neh->evt_type = expected_type;
+ neh->evt = cpu_to_le16(expected_event);
+ neh->cb = cb;
+ neh->arg = arg;
+
+ spin_lock_irqsave(&rc->neh_lock, flags);
+ result = __uwb_rc_ctx_get(rc, neh);
+ if (result >= 0) {
+ cmd->bCommandContext = neh->context;
+ list_add_tail(&neh->list_node, &rc->neh_list);
+ uwb_rc_neh_get(neh);
+ }
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+ if (result < 0)
+ goto error_ctx_get;
+
+ return neh;
+
+error_ctx_get:
+ kfree(neh);
+error_kzalloc:
+ dev_err(dev, "cannot open handle to radio controller: %d\n", result);
+ return ERR_PTR(result);
+}
+
+static void __uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh)
+{
+ __uwb_rc_ctx_put(rc, neh);
+ list_del(&neh->list_node);
+}
+
+/**
+ * uwb_rc_neh_rm - remove a neh.
+ * @rc: the radio controller
+ * @neh: the neh to remove
+ *
+ * Remove an active neh immediately instead of waiting for the event
+ * (or a time out).
+ */
+void uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rc->neh_lock, flags);
+ __uwb_rc_neh_rm(rc, neh);
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+
+ del_timer_sync(&neh->timer);
+ uwb_rc_neh_put(neh);
+}
+
+/**
+ * uwb_rc_neh_arm - arm an event handler timeout timer
+ *
+ * @rc: UWB Radio Controller
+ * @neh: Notification/event handler for @rc
+ *
+ * The timer is only armed if the neh is active.
+ */
+void uwb_rc_neh_arm(struct uwb_rc *rc, struct uwb_rc_neh *neh)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rc->neh_lock, flags);
+ if (neh->context)
+ mod_timer(&neh->timer,
+ jiffies + msecs_to_jiffies(UWB_RC_CMD_TIMEOUT_MS));
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+}
+
+static void uwb_rc_neh_cb(struct uwb_rc_neh *neh, struct uwb_rceb *rceb, size_t size)
+{
+ (*neh->cb)(neh->rc, neh->arg, rceb, size);
+ uwb_rc_neh_put(neh);
+}
+
+static bool uwb_rc_neh_match(struct uwb_rc_neh *neh, const struct uwb_rceb *rceb)
+{
+ return neh->evt_type == rceb->bEventType
+ && neh->evt == rceb->wEvent
+ && neh->context == rceb->bEventContext;
+}
+
+/**
+ * Find the handle waiting for a RC Radio Control Event
+ *
+ * @rc: UWB Radio Controller
+ * @rceb: Pointer to the RCEB buffer
+ * @event_size: Pointer to the size of the RCEB buffer. Might be
+ * adjusted to take into account the @neh->extra_size
+ * settings.
+ *
+ * If the listener has no buffer (NULL buffer), one is allocated for
+ * the right size (the amount of data received). @neh->ptr will point
+ * to the event payload, which always starts with a 'struct
+ * uwb_rceb'. kfree() it when done.
+ */
+static
+struct uwb_rc_neh *uwb_rc_neh_lookup(struct uwb_rc *rc,
+ const struct uwb_rceb *rceb)
+{
+ struct uwb_rc_neh *neh = NULL, *h;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rc->neh_lock, flags);
+
+ list_for_each_entry(h, &rc->neh_list, list_node) {
+ if (uwb_rc_neh_match(h, rceb)) {
+ neh = h;
+ break;
+ }
+ }
+
+ if (neh)
+ __uwb_rc_neh_rm(rc, neh);
+
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+
+ return neh;
+}
+
+
+/*
+ * Process notifications coming from the radio control interface
+ *
+ * @rc: UWB Radio Control Interface descriptor
+ * @neh: Notification/Event Handler @neh->ptr points to
+ * @uwb_evt->buffer.
+ *
+ * This function is called by the event/notif handling subsystem when
+ * notifications arrive (hwarc_probe() arms a notification/event handle
+ * that calls back this function for every received notification; this
+ * function then will rearm itself).
+ *
+ * Notification data buffers are dynamically allocated by the NEH
+ * handling code in neh.c [uwb_rc_neh_lookup()]. What is actually
+ * allocated is space to contain the notification data.
+ *
+ * Buffers are prefixed with a Radio Control Event Block (RCEB) as
+ * defined by the WUSB Wired-Adapter Radio Control interface. We
+ * just use it for the notification code.
+ *
+ * On each case statement we just transcode endianess of the different
+ * fields. We declare a pointer to a RCI definition of an event, and
+ * then to a UWB definition of the same event (which are the same,
+ * remember). Event if we use different pointers
+ */
+static
+void uwb_rc_notif(struct uwb_rc *rc, struct uwb_rceb *rceb, ssize_t size)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_event *uwb_evt;
+
+ if (size == -ESHUTDOWN)
+ return;
+ if (size < 0) {
+ dev_err(dev, "ignoring event with error code %zu\n",
+ size);
+ return;
+ }
+
+ uwb_evt = kzalloc(sizeof(*uwb_evt), GFP_ATOMIC);
+ if (unlikely(uwb_evt == NULL)) {
+ dev_err(dev, "no memory to queue event 0x%02x/%04x/%02x\n",
+ rceb->bEventType, le16_to_cpu(rceb->wEvent),
+ rceb->bEventContext);
+ return;
+ }
+ uwb_evt->rc = __uwb_rc_get(rc); /* will be put by uwbd's uwbd_event_handle() */
+ uwb_evt->ts_jiffies = jiffies;
+ uwb_evt->type = UWB_EVT_TYPE_NOTIF;
+ uwb_evt->notif.size = size;
+ uwb_evt->notif.rceb = rceb;
+
+ uwbd_event_queue(uwb_evt);
+}
+
+static void uwb_rc_neh_grok_event(struct uwb_rc *rc, struct uwb_rceb *rceb, size_t size)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_rc_neh *neh;
+ struct uwb_rceb *notif;
+ unsigned long flags;
+
+ if (rceb->bEventContext == 0) {
+ notif = kmalloc(size, GFP_ATOMIC);
+ if (notif) {
+ memcpy(notif, rceb, size);
+ uwb_rc_notif(rc, notif, size);
+ } else
+ dev_err(dev, "event 0x%02x/%04x/%02x (%zu bytes): no memory\n",
+ rceb->bEventType, le16_to_cpu(rceb->wEvent),
+ rceb->bEventContext, size);
+ } else {
+ neh = uwb_rc_neh_lookup(rc, rceb);
+ if (neh) {
+ spin_lock_irqsave(&rc->neh_lock, flags);
+ /* to guard against a timeout */
+ neh->completed = 1;
+ del_timer(&neh->timer);
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+ uwb_rc_neh_cb(neh, rceb, size);
+ } else
+ dev_warn(dev, "event 0x%02x/%04x/%02x (%zu bytes): nobody cared\n",
+ rceb->bEventType, le16_to_cpu(rceb->wEvent),
+ rceb->bEventContext, size);
+ }
+}
+
+/**
+ * Given a buffer with one or more UWB RC events/notifications, break
+ * them up and dispatch them.
+ *
+ * @rc: UWB Radio Controller
+ * @buf: Buffer with the stream of notifications/events
+ * @buf_size: Amount of data in the buffer
+ *
+ * Note each notification/event starts always with a 'struct
+ * uwb_rceb', so the minimum size if 4 bytes.
+ *
+ * The device may pass us events formatted differently than expected.
+ * These are first filtered, potentially creating a new event in a new
+ * memory location. If a new event is created by the filter it is also
+ * freed here.
+ *
+ * For each notif/event, tries to guess the size looking at the EST
+ * tables, then looks for a neh that is waiting for that event and if
+ * found, copies the payload to the neh's buffer and calls it back. If
+ * not, the data is ignored.
+ *
+ * Note that if we can't find a size description in the EST tables, we
+ * still might find a size in the 'neh' handle in uwb_rc_neh_lookup().
+ *
+ * Assumptions:
+ *
+ * @rc->neh_lock is NOT taken
+ *
+ * We keep track of various sizes here:
+ * size: contains the size of the buffer that is processed for the
+ * incoming event. this buffer may contain events that are not
+ * formatted as WHCI.
+ * real_size: the actual space taken by this event in the buffer.
+ * We need to keep track of the real size of an event to be able to
+ * advance the buffer correctly.
+ * event_size: the size of the event as expected by the core layer
+ * [OR] the size of the event after filtering. if the filtering
+ * created a new event in a new memory location then this is
+ * effectively the size of a new event buffer
+ */
+void uwb_rc_neh_grok(struct uwb_rc *rc, void *buf, size_t buf_size)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ void *itr;
+ struct uwb_rceb *rceb;
+ size_t size, real_size, event_size;
+ int needtofree;
+
+ itr = buf;
+ size = buf_size;
+ while (size > 0) {
+ if (size < sizeof(*rceb)) {
+ dev_err(dev, "not enough data in event buffer to "
+ "process incoming events (%zu left, minimum is "
+ "%zu)\n", size, sizeof(*rceb));
+ break;
+ }
+
+ rceb = itr;
+ if (rc->filter_event) {
+ needtofree = rc->filter_event(rc, &rceb, size,
+ &real_size, &event_size);
+ if (needtofree < 0 && needtofree != -ENOANO) {
+ dev_err(dev, "BUG: Unable to filter event "
+ "(0x%02x/%04x/%02x) from "
+ "device. \n", rceb->bEventType,
+ le16_to_cpu(rceb->wEvent),
+ rceb->bEventContext);
+ break;
+ }
+ } else
+ needtofree = -ENOANO;
+ /* do real processing if there was no filtering or the
+ * filtering didn't act */
+ if (needtofree == -ENOANO) {
+ ssize_t ret = uwb_est_find_size(rc, rceb, size);
+ if (ret < 0)
+ break;
+ if (ret > size) {
+ dev_err(dev, "BUG: hw sent incomplete event "
+ "0x%02x/%04x/%02x (%zd bytes), only got "
+ "%zu bytes. We don't handle that.\n",
+ rceb->bEventType, le16_to_cpu(rceb->wEvent),
+ rceb->bEventContext, ret, size);
+ break;
+ }
+ real_size = event_size = ret;
+ }
+ uwb_rc_neh_grok_event(rc, rceb, event_size);
+
+ if (needtofree == 1)
+ kfree(rceb);
+
+ itr += real_size;
+ size -= real_size;
+ }
+}
+EXPORT_SYMBOL_GPL(uwb_rc_neh_grok);
+
+
+/**
+ * The entity that reads from the device notification/event channel has
+ * detected an error.
+ *
+ * @rc: UWB Radio Controller
+ * @error: Errno error code
+ *
+ */
+void uwb_rc_neh_error(struct uwb_rc *rc, int error)
+{
+ struct uwb_rc_neh *neh;
+ unsigned long flags;
+
+ for (;;) {
+ spin_lock_irqsave(&rc->neh_lock, flags);
+ if (list_empty(&rc->neh_list)) {
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+ break;
+ }
+ neh = list_first_entry(&rc->neh_list, struct uwb_rc_neh, list_node);
+ __uwb_rc_neh_rm(rc, neh);
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+
+ del_timer_sync(&neh->timer);
+ uwb_rc_neh_cb(neh, NULL, error);
+ }
+}
+EXPORT_SYMBOL_GPL(uwb_rc_neh_error);
+
+
+static void uwb_rc_neh_timer(struct timer_list *t)
+{
+ struct uwb_rc_neh *neh = from_timer(neh, t, timer);
+ struct uwb_rc *rc = neh->rc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rc->neh_lock, flags);
+ if (neh->completed) {
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+ return;
+ }
+ if (neh->context)
+ __uwb_rc_neh_rm(rc, neh);
+ else
+ neh = NULL;
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+
+ if (neh)
+ uwb_rc_neh_cb(neh, NULL, -ETIMEDOUT);
+}
+
+/** Initializes the @rc's neh subsystem
+ */
+void uwb_rc_neh_create(struct uwb_rc *rc)
+{
+ spin_lock_init(&rc->neh_lock);
+ INIT_LIST_HEAD(&rc->neh_list);
+ set_bit(0, rc->ctx_bm); /* 0 is reserved (see [WUSB] table 8-65) */
+ set_bit(0xff, rc->ctx_bm); /* and 0xff is invalid */
+ rc->ctx_roll = 1;
+}
+
+
+/** Release's the @rc's neh subsystem */
+void uwb_rc_neh_destroy(struct uwb_rc *rc)
+{
+ unsigned long flags;
+ struct uwb_rc_neh *neh;
+
+ for (;;) {
+ spin_lock_irqsave(&rc->neh_lock, flags);
+ if (list_empty(&rc->neh_list)) {
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+ break;
+ }
+ neh = list_first_entry(&rc->neh_list, struct uwb_rc_neh, list_node);
+ __uwb_rc_neh_rm(rc, neh);
+ spin_unlock_irqrestore(&rc->neh_lock, flags);
+
+ del_timer_sync(&neh->timer);
+ uwb_rc_neh_put(neh);
+ }
+}
diff --git a/drivers/staging/uwb/pal.c b/drivers/staging/uwb/pal.c
new file mode 100644
index 000000000000..a541e646a603
--- /dev/null
+++ b/drivers/staging/uwb/pal.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UWB PAL support.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/export.h>
+
+#include "uwb.h"
+#include "uwb-internal.h"
+
+/**
+ * uwb_pal_init - initialize a UWB PAL
+ * @pal: the PAL to initialize
+ */
+void uwb_pal_init(struct uwb_pal *pal)
+{
+ INIT_LIST_HEAD(&pal->node);
+}
+EXPORT_SYMBOL_GPL(uwb_pal_init);
+
+/**
+ * uwb_pal_register - register a UWB PAL
+ * @pal: the PAL
+ *
+ * The PAL must be initialized with uwb_pal_init().
+ */
+int uwb_pal_register(struct uwb_pal *pal)
+{
+ struct uwb_rc *rc = pal->rc;
+ int ret;
+
+ if (pal->device) {
+ /* create a link to the uwb_rc in the PAL device's directory. */
+ ret = sysfs_create_link(&pal->device->kobj,
+ &rc->uwb_dev.dev.kobj, "uwb_rc");
+ if (ret < 0)
+ return ret;
+ /* create a link to the PAL in the UWB device's directory. */
+ ret = sysfs_create_link(&rc->uwb_dev.dev.kobj,
+ &pal->device->kobj, pal->name);
+ if (ret < 0) {
+ sysfs_remove_link(&pal->device->kobj, "uwb_rc");
+ return ret;
+ }
+ }
+
+ pal->debugfs_dir = uwb_dbg_create_pal_dir(pal);
+
+ mutex_lock(&rc->uwb_dev.mutex);
+ list_add(&pal->node, &rc->pals);
+ mutex_unlock(&rc->uwb_dev.mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(uwb_pal_register);
+
+static int find_rc(struct device *dev, const void *data)
+{
+ const struct uwb_rc *target_rc = data;
+ struct uwb_rc *rc = dev_get_drvdata(dev);
+
+ if (rc == NULL) {
+ WARN_ON(1);
+ return 0;
+ }
+ if (rc == target_rc) {
+ if (rc->ready == 0)
+ return 0;
+ else
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Given a radio controller descriptor see if it is registered.
+ *
+ * @returns false if the rc does not exist or is quiescing; true otherwise.
+ */
+static bool uwb_rc_class_device_exists(struct uwb_rc *target_rc)
+{
+ struct device *dev;
+
+ dev = class_find_device(&uwb_rc_class, NULL, target_rc, find_rc);
+
+ put_device(dev);
+
+ return (dev != NULL);
+}
+
+/**
+ * uwb_pal_unregister - unregister a UWB PAL
+ * @pal: the PAL
+ */
+void uwb_pal_unregister(struct uwb_pal *pal)
+{
+ struct uwb_rc *rc = pal->rc;
+
+ uwb_radio_stop(pal);
+
+ mutex_lock(&rc->uwb_dev.mutex);
+ list_del(&pal->node);
+ mutex_unlock(&rc->uwb_dev.mutex);
+
+ debugfs_remove(pal->debugfs_dir);
+
+ if (pal->device) {
+ /* remove link to the PAL in the UWB device's directory. */
+ if (uwb_rc_class_device_exists(rc))
+ sysfs_remove_link(&rc->uwb_dev.dev.kobj, pal->name);
+
+ /* remove link to uwb_rc in the PAL device's directory. */
+ sysfs_remove_link(&pal->device->kobj, "uwb_rc");
+ }
+}
+EXPORT_SYMBOL_GPL(uwb_pal_unregister);
+
+/**
+ * uwb_rc_pal_init - initialize the PAL related parts of a radio controller
+ * @rc: the radio controller
+ */
+void uwb_rc_pal_init(struct uwb_rc *rc)
+{
+ INIT_LIST_HEAD(&rc->pals);
+}
diff --git a/drivers/staging/uwb/radio.c b/drivers/staging/uwb/radio.c
new file mode 100644
index 000000000000..6afb75ce1b5f
--- /dev/null
+++ b/drivers/staging/uwb/radio.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UWB radio (channel) management.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/export.h>
+
+#include "uwb.h"
+#include "uwb-internal.h"
+
+
+static int uwb_radio_select_channel(struct uwb_rc *rc)
+{
+ /*
+ * Default to channel 9 (BG1, TFC1) unless the user has
+ * selected a specific channel or there are no active PALs.
+ */
+ if (rc->active_pals == 0)
+ return -1;
+ if (rc->beaconing_forced)
+ return rc->beaconing_forced;
+ return 9;
+}
+
+
+/*
+ * Notify all active PALs that the channel has changed.
+ */
+static void uwb_radio_channel_changed(struct uwb_rc *rc, int channel)
+{
+ struct uwb_pal *pal;
+
+ list_for_each_entry(pal, &rc->pals, node) {
+ if (pal->channel && channel != pal->channel) {
+ pal->channel = channel;
+ if (pal->channel_changed)
+ pal->channel_changed(pal, pal->channel);
+ }
+ }
+}
+
+/*
+ * Change to a new channel and notify any active PALs of the new
+ * channel.
+ *
+ * When stopping the radio, PALs need to be notified first so they can
+ * terminate any active reservations.
+ */
+static int uwb_radio_change_channel(struct uwb_rc *rc, int channel)
+{
+ int ret = 0;
+ struct device *dev = &rc->uwb_dev.dev;
+
+ dev_dbg(dev, "%s: channel = %d, rc->beaconing = %d\n", __func__,
+ channel, rc->beaconing);
+
+ if (channel == -1)
+ uwb_radio_channel_changed(rc, channel);
+
+ if (channel != rc->beaconing) {
+ if (rc->beaconing != -1 && channel != -1) {
+ /*
+ * FIXME: should signal the channel change
+ * with a Channel Change IE.
+ */
+ ret = uwb_radio_change_channel(rc, -1);
+ if (ret < 0)
+ return ret;
+ }
+ ret = uwb_rc_beacon(rc, channel, 0);
+ }
+
+ if (channel != -1)
+ uwb_radio_channel_changed(rc, rc->beaconing);
+
+ return ret;
+}
+
+/**
+ * uwb_radio_start - request that the radio be started
+ * @pal: the PAL making the request.
+ *
+ * If the radio is not already active, a suitable channel is selected
+ * and beacons are started.
+ */
+int uwb_radio_start(struct uwb_pal *pal)
+{
+ struct uwb_rc *rc = pal->rc;
+ int ret = 0;
+
+ mutex_lock(&rc->uwb_dev.mutex);
+
+ if (!pal->channel) {
+ pal->channel = -1;
+ rc->active_pals++;
+ ret = uwb_radio_change_channel(rc, uwb_radio_select_channel(rc));
+ }
+
+ mutex_unlock(&rc->uwb_dev.mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(uwb_radio_start);
+
+/**
+ * uwb_radio_stop - request that the radio be stopped.
+ * @pal: the PAL making the request.
+ *
+ * Stops the radio if no other PAL is making use of it.
+ */
+void uwb_radio_stop(struct uwb_pal *pal)
+{
+ struct uwb_rc *rc = pal->rc;
+
+ mutex_lock(&rc->uwb_dev.mutex);
+
+ if (pal->channel) {
+ rc->active_pals--;
+ uwb_radio_change_channel(rc, uwb_radio_select_channel(rc));
+ pal->channel = 0;
+ }
+
+ mutex_unlock(&rc->uwb_dev.mutex);
+}
+EXPORT_SYMBOL_GPL(uwb_radio_stop);
+
+/*
+ * uwb_radio_force_channel - force a specific channel to be used
+ * @rc: the radio controller.
+ * @channel: the channel to use; -1 to force the radio to stop; 0 to
+ * use the default channel selection algorithm.
+ */
+int uwb_radio_force_channel(struct uwb_rc *rc, int channel)
+{
+ int ret = 0;
+
+ mutex_lock(&rc->uwb_dev.mutex);
+
+ rc->beaconing_forced = channel;
+ ret = uwb_radio_change_channel(rc, uwb_radio_select_channel(rc));
+
+ mutex_unlock(&rc->uwb_dev.mutex);
+ return ret;
+}
+
+/*
+ * uwb_radio_setup - setup the radio manager
+ * @rc: the radio controller.
+ *
+ * The radio controller is reset to ensure it's in a known state
+ * before it's used.
+ */
+int uwb_radio_setup(struct uwb_rc *rc)
+{
+ return uwb_rc_reset(rc);
+}
+
+/*
+ * uwb_radio_reset_state - reset any radio manager state
+ * @rc: the radio controller.
+ *
+ * All internal radio manager state is reset to values corresponding
+ * to a reset radio controller.
+ */
+void uwb_radio_reset_state(struct uwb_rc *rc)
+{
+ struct uwb_pal *pal;
+
+ mutex_lock(&rc->uwb_dev.mutex);
+
+ list_for_each_entry(pal, &rc->pals, node) {
+ if (pal->channel) {
+ pal->channel = -1;
+ if (pal->channel_changed)
+ pal->channel_changed(pal, -1);
+ }
+ }
+
+ rc->beaconing = -1;
+ rc->scanning = -1;
+
+ mutex_unlock(&rc->uwb_dev.mutex);
+}
+
+/*
+ * uwb_radio_shutdown - shutdown the radio manager
+ * @rc: the radio controller.
+ *
+ * The radio controller is reset.
+ */
+void uwb_radio_shutdown(struct uwb_rc *rc)
+{
+ uwb_radio_reset_state(rc);
+ uwb_rc_reset(rc);
+}
diff --git a/drivers/staging/uwb/reset.c b/drivers/staging/uwb/reset.c
new file mode 100644
index 000000000000..8fc7c14d903e
--- /dev/null
+++ b/drivers/staging/uwb/reset.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * UWB basic command support and radio reset
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME:
+ *
+ * - docs
+ *
+ * - Now we are serializing (using the uwb_dev->mutex) the command
+ * execution; it should be parallelized as much as possible some
+ * day.
+ */
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+
+#include "uwb-internal.h"
+
+/**
+ * Command result codes (WUSB1.0[T8-69])
+ */
+static
+const char *__strerror[] = {
+ "success",
+ "failure",
+ "hardware failure",
+ "no more slots",
+ "beacon is too large",
+ "invalid parameter",
+ "unsupported power level",
+ "time out (wa) or invalid ie data (whci)",
+ "beacon size exceeded",
+ "cancelled",
+ "invalid state",
+ "invalid size",
+ "ack not received",
+ "no more asie notification",
+};
+
+
+/** Return a string matching the given error code */
+const char *uwb_rc_strerror(unsigned code)
+{
+ if (code == 255)
+ return "time out";
+ if (code >= ARRAY_SIZE(__strerror))
+ return "unknown error";
+ return __strerror[code];
+}
+
+int uwb_rc_cmd_async(struct uwb_rc *rc, const char *cmd_name,
+ struct uwb_rccb *cmd, size_t cmd_size,
+ u8 expected_type, u16 expected_event,
+ uwb_rc_cmd_cb_f cb, void *arg)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_rc_neh *neh;
+ int needtofree = 0;
+ int result;
+
+ uwb_dev_lock(&rc->uwb_dev); /* Protect against rc->priv being removed */
+ if (rc->priv == NULL) {
+ uwb_dev_unlock(&rc->uwb_dev);
+ return -ESHUTDOWN;
+ }
+
+ if (rc->filter_cmd) {
+ needtofree = rc->filter_cmd(rc, &cmd, &cmd_size);
+ if (needtofree < 0 && needtofree != -ENOANO) {
+ dev_err(dev, "%s: filter error: %d\n",
+ cmd_name, needtofree);
+ uwb_dev_unlock(&rc->uwb_dev);
+ return needtofree;
+ }
+ }
+
+ neh = uwb_rc_neh_add(rc, cmd, expected_type, expected_event, cb, arg);
+ if (IS_ERR(neh)) {
+ result = PTR_ERR(neh);
+ uwb_dev_unlock(&rc->uwb_dev);
+ goto out;
+ }
+
+ result = rc->cmd(rc, cmd, cmd_size);
+ uwb_dev_unlock(&rc->uwb_dev);
+ if (result < 0)
+ uwb_rc_neh_rm(rc, neh);
+ else
+ uwb_rc_neh_arm(rc, neh);
+ uwb_rc_neh_put(neh);
+out:
+ if (needtofree == 1)
+ kfree(cmd);
+ return result < 0 ? result : 0;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_cmd_async);
+
+struct uwb_rc_cmd_done_params {
+ struct completion completion;
+ struct uwb_rceb *reply;
+ ssize_t reply_size;
+};
+
+static void uwb_rc_cmd_done(struct uwb_rc *rc, void *arg,
+ struct uwb_rceb *reply, ssize_t reply_size)
+{
+ struct uwb_rc_cmd_done_params *p = (struct uwb_rc_cmd_done_params *)arg;
+
+ if (reply_size > 0) {
+ if (p->reply)
+ reply_size = min(p->reply_size, reply_size);
+ else
+ p->reply = kmalloc(reply_size, GFP_ATOMIC);
+
+ if (p->reply)
+ memcpy(p->reply, reply, reply_size);
+ else
+ reply_size = -ENOMEM;
+ }
+ p->reply_size = reply_size;
+ complete(&p->completion);
+}
+
+
+/**
+ * Generic function for issuing commands to the Radio Control Interface
+ *
+ * @rc: UWB Radio Control descriptor
+ * @cmd_name: Name of the command being issued (for error messages)
+ * @cmd: Pointer to rccb structure containing the command;
+ * normally you embed this structure as the first member of
+ * the full command structure.
+ * @cmd_size: Size of the whole command buffer pointed to by @cmd.
+ * @reply: Pointer to where to store the reply
+ * @reply_size: @reply's size
+ * @expected_type: Expected type in the return event
+ * @expected_event: Expected event code in the return event
+ * @preply: Here a pointer to where the event data is received will
+ * be stored. Once done with the data, free with kfree().
+ *
+ * This function is generic; it works for commands that return a fixed
+ * and known size or for commands that return a variable amount of data.
+ *
+ * If a buffer is provided, that is used, although it could be chopped
+ * to the maximum size of the buffer. If the buffer is NULL, then one
+ * be allocated in *preply with the whole contents of the reply.
+ *
+ * @rc needs to be referenced
+ */
+static
+ssize_t __uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name,
+ struct uwb_rccb *cmd, size_t cmd_size,
+ struct uwb_rceb *reply, size_t reply_size,
+ u8 expected_type, u16 expected_event,
+ struct uwb_rceb **preply)
+{
+ ssize_t result = 0;
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_rc_cmd_done_params params;
+
+ init_completion(&params.completion);
+ params.reply = reply;
+ params.reply_size = reply_size;
+
+ result = uwb_rc_cmd_async(rc, cmd_name, cmd, cmd_size,
+ expected_type, expected_event,
+ uwb_rc_cmd_done, &params);
+ if (result)
+ return result;
+
+ wait_for_completion(&params.completion);
+
+ if (preply)
+ *preply = params.reply;
+
+ if (params.reply_size < 0)
+ dev_err(dev, "%s: confirmation event 0x%02x/%04x/%02x "
+ "reception failed: %d\n", cmd_name,
+ expected_type, expected_event, cmd->bCommandContext,
+ (int)params.reply_size);
+ return params.reply_size;
+}
+
+
+/**
+ * Generic function for issuing commands to the Radio Control Interface
+ *
+ * @rc: UWB Radio Control descriptor
+ * @cmd_name: Name of the command being issued (for error messages)
+ * @cmd: Pointer to rccb structure containing the command;
+ * normally you embed this structure as the first member of
+ * the full command structure.
+ * @cmd_size: Size of the whole command buffer pointed to by @cmd.
+ * @reply: Pointer to the beginning of the confirmation event
+ * buffer. Normally bigger than an 'struct hwarc_rceb'.
+ * You need to fill out reply->bEventType and reply->wEvent (in
+ * cpu order) as the function will use them to verify the
+ * confirmation event.
+ * @reply_size: Size of the reply buffer
+ *
+ * The function checks that the length returned in the reply is at
+ * least as big as @reply_size; if not, it will be deemed an error and
+ * -EIO returned.
+ *
+ * @rc needs to be referenced
+ */
+ssize_t uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name,
+ struct uwb_rccb *cmd, size_t cmd_size,
+ struct uwb_rceb *reply, size_t reply_size)
+{
+ struct device *dev = &rc->uwb_dev.dev;
+ ssize_t result;
+
+ result = __uwb_rc_cmd(rc, cmd_name,
+ cmd, cmd_size, reply, reply_size,
+ reply->bEventType, reply->wEvent, NULL);
+
+ if (result > 0 && result < reply_size) {
+ dev_err(dev, "%s: not enough data returned for decoding reply "
+ "(%zu bytes received vs at least %zu needed)\n",
+ cmd_name, result, reply_size);
+ result = -EIO;
+ }
+ return result;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_cmd);
+
+
+/**
+ * Generic function for issuing commands to the Radio Control
+ * Interface that return an unknown amount of data
+ *
+ * @rc: UWB Radio Control descriptor
+ * @cmd_name: Name of the command being issued (for error messages)
+ * @cmd: Pointer to rccb structure containing the command;
+ * normally you embed this structure as the first member of
+ * the full command structure.
+ * @cmd_size: Size of the whole command buffer pointed to by @cmd.
+ * @expected_type: Expected type in the return event
+ * @expected_event: Expected event code in the return event
+ * @preply: Here a pointer to where the event data is received will
+ * be stored. Once done with the data, free with kfree().
+ *
+ * The function checks that the length returned in the reply is at
+ * least as big as a 'struct uwb_rceb *'; if not, it will be deemed an
+ * error and -EIO returned.
+ *
+ * @rc needs to be referenced
+ */
+ssize_t uwb_rc_vcmd(struct uwb_rc *rc, const char *cmd_name,
+ struct uwb_rccb *cmd, size_t cmd_size,
+ u8 expected_type, u16 expected_event,
+ struct uwb_rceb **preply)
+{
+ return __uwb_rc_cmd(rc, cmd_name, cmd, cmd_size, NULL, 0,
+ expected_type, expected_event, preply);
+}
+EXPORT_SYMBOL_GPL(uwb_rc_vcmd);
+
+
+/**
+ * Reset a UWB Host Controller (and all radio settings)
+ *
+ * @rc: Host Controller descriptor
+ * @returns: 0 if ok, < 0 errno code on error
+ *
+ * We put the command on kmalloc'ed memory as some arches cannot do
+ * USB from the stack. The reply event is copied from an stage buffer,
+ * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details.
+ */
+int uwb_rc_reset(struct uwb_rc *rc)
+{
+ int result = -ENOMEM;
+ struct uwb_rc_evt_confirm reply;
+ struct uwb_rccb *cmd;
+ size_t cmd_size = sizeof(*cmd);
+
+ mutex_lock(&rc->uwb_dev.mutex);
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+ if (cmd == NULL)
+ goto error_kzalloc;
+ cmd->bCommandType = UWB_RC_CET_GENERAL;
+ cmd->wCommand = cpu_to_le16(UWB_RC_CMD_RESET);
+ reply.rceb.bEventType = UWB_RC_CET_GENERAL;
+ reply.rceb.wEvent = UWB_RC_CMD_RESET;
+ result = uwb_rc_cmd(rc, "RESET", cmd, cmd_size,
+ &reply.rceb, sizeof(reply));
+ if (result < 0)
+ goto error_cmd;
+ if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(&rc->uwb_dev.dev,
+ "RESET: command execution failed: %s (%d)\n",
+ uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
+ result = -EIO;
+ }
+error_cmd:
+ kfree(cmd);
+error_kzalloc:
+ mutex_unlock(&rc->uwb_dev.mutex);
+ return result;
+}
+
+int uwbd_msg_handle_reset(struct uwb_event *evt)
+{
+ struct uwb_rc *rc = evt->rc;
+ int ret;
+
+ dev_info(&rc->uwb_dev.dev, "resetting radio controller\n");
+ ret = rc->reset(rc);
+ if (ret < 0) {
+ dev_err(&rc->uwb_dev.dev, "failed to reset hardware: %d\n", ret);
+ goto error;
+ }
+ return 0;
+error:
+ /* Nothing can be done except try the reset again. Wait a bit
+ to avoid reset loops during probe() or remove(). */
+ msleep(1000);
+ uwb_rc_reset_all(rc);
+ return ret;
+}
+
+/**
+ * uwb_rc_reset_all - request a reset of the radio controller and PALs
+ * @rc: the radio controller of the hardware device to be reset.
+ *
+ * The full hardware reset of the radio controller and all the PALs
+ * will be scheduled.
+ */
+void uwb_rc_reset_all(struct uwb_rc *rc)
+{
+ struct uwb_event *evt;
+
+ evt = kzalloc(sizeof(struct uwb_event), GFP_ATOMIC);
+ if (unlikely(evt == NULL))
+ return;
+
+ evt->rc = __uwb_rc_get(rc); /* will be put by uwbd's uwbd_event_handle() */
+ evt->ts_jiffies = jiffies;
+ evt->type = UWB_EVT_TYPE_MSG;
+ evt->message = UWB_EVT_MSG_RESET;
+
+ uwbd_event_queue(evt);
+}
+EXPORT_SYMBOL_GPL(uwb_rc_reset_all);
+
+void uwb_rc_pre_reset(struct uwb_rc *rc)
+{
+ rc->stop(rc);
+ uwbd_flush(rc);
+
+ uwb_radio_reset_state(rc);
+ uwb_rsv_remove_all(rc);
+}
+EXPORT_SYMBOL_GPL(uwb_rc_pre_reset);
+
+int uwb_rc_post_reset(struct uwb_rc *rc)
+{
+ int ret;
+
+ ret = rc->start(rc);
+ if (ret)
+ goto out;
+ ret = uwb_rc_mac_addr_set(rc, &rc->uwb_dev.mac_addr);
+ if (ret)
+ goto out;
+ ret = uwb_rc_dev_addr_set(rc, &rc->uwb_dev.dev_addr);
+ if (ret)
+ goto out;
+out:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(uwb_rc_post_reset);
diff --git a/drivers/staging/uwb/rsv.c b/drivers/staging/uwb/rsv.c
new file mode 100644
index 000000000000..f45a04ff7275
--- /dev/null
+++ b/drivers/staging/uwb/rsv.c
@@ -0,0 +1,1000 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UWB reservation management.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/export.h>
+
+#include "uwb.h"
+#include "uwb-internal.h"
+
+static void uwb_rsv_timer(struct timer_list *t);
+
+static const char *rsv_states[] = {
+ [UWB_RSV_STATE_NONE] = "none ",
+ [UWB_RSV_STATE_O_INITIATED] = "o initiated ",
+ [UWB_RSV_STATE_O_PENDING] = "o pending ",
+ [UWB_RSV_STATE_O_MODIFIED] = "o modified ",
+ [UWB_RSV_STATE_O_ESTABLISHED] = "o established ",
+ [UWB_RSV_STATE_O_TO_BE_MOVED] = "o to be moved ",
+ [UWB_RSV_STATE_O_MOVE_EXPANDING] = "o move expanding",
+ [UWB_RSV_STATE_O_MOVE_COMBINING] = "o move combining",
+ [UWB_RSV_STATE_O_MOVE_REDUCING] = "o move reducing ",
+ [UWB_RSV_STATE_T_ACCEPTED] = "t accepted ",
+ [UWB_RSV_STATE_T_CONFLICT] = "t conflict ",
+ [UWB_RSV_STATE_T_PENDING] = "t pending ",
+ [UWB_RSV_STATE_T_DENIED] = "t denied ",
+ [UWB_RSV_STATE_T_RESIZED] = "t resized ",
+ [UWB_RSV_STATE_T_EXPANDING_ACCEPTED] = "t expanding acc ",
+ [UWB_RSV_STATE_T_EXPANDING_CONFLICT] = "t expanding conf",
+ [UWB_RSV_STATE_T_EXPANDING_PENDING] = "t expanding pend",
+ [UWB_RSV_STATE_T_EXPANDING_DENIED] = "t expanding den ",
+};
+
+static const char *rsv_types[] = {
+ [UWB_DRP_TYPE_ALIEN_BP] = "alien-bp",
+ [UWB_DRP_TYPE_HARD] = "hard",
+ [UWB_DRP_TYPE_SOFT] = "soft",
+ [UWB_DRP_TYPE_PRIVATE] = "private",
+ [UWB_DRP_TYPE_PCA] = "pca",
+};
+
+bool uwb_rsv_has_two_drp_ies(struct uwb_rsv *rsv)
+{
+ static const bool has_two_drp_ies[] = {
+ [UWB_RSV_STATE_O_INITIATED] = false,
+ [UWB_RSV_STATE_O_PENDING] = false,
+ [UWB_RSV_STATE_O_MODIFIED] = false,
+ [UWB_RSV_STATE_O_ESTABLISHED] = false,
+ [UWB_RSV_STATE_O_TO_BE_MOVED] = false,
+ [UWB_RSV_STATE_O_MOVE_COMBINING] = false,
+ [UWB_RSV_STATE_O_MOVE_REDUCING] = false,
+ [UWB_RSV_STATE_O_MOVE_EXPANDING] = true,
+ [UWB_RSV_STATE_T_ACCEPTED] = false,
+ [UWB_RSV_STATE_T_CONFLICT] = false,
+ [UWB_RSV_STATE_T_PENDING] = false,
+ [UWB_RSV_STATE_T_DENIED] = false,
+ [UWB_RSV_STATE_T_RESIZED] = false,
+ [UWB_RSV_STATE_T_EXPANDING_ACCEPTED] = true,
+ [UWB_RSV_STATE_T_EXPANDING_CONFLICT] = true,
+ [UWB_RSV_STATE_T_EXPANDING_PENDING] = true,
+ [UWB_RSV_STATE_T_EXPANDING_DENIED] = true,
+ };
+
+ return has_two_drp_ies[rsv->state];
+}
+
+/**
+ * uwb_rsv_state_str - return a string for a reservation state
+ * @state: the reservation state.
+ */
+const char *uwb_rsv_state_str(enum uwb_rsv_state state)
+{
+ if (state < UWB_RSV_STATE_NONE || state >= UWB_RSV_STATE_LAST)
+ return "unknown";
+ return rsv_states[state];
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_state_str);
+
+/**
+ * uwb_rsv_type_str - return a string for a reservation type
+ * @type: the reservation type
+ */
+const char *uwb_rsv_type_str(enum uwb_drp_type type)
+{
+ if (type < UWB_DRP_TYPE_ALIEN_BP || type > UWB_DRP_TYPE_PCA)
+ return "invalid";
+ return rsv_types[type];
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_type_str);
+
+void uwb_rsv_dump(char *text, struct uwb_rsv *rsv)
+{
+ struct device *dev = &rsv->rc->uwb_dev.dev;
+ struct uwb_dev_addr devaddr;
+ char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE];
+
+ uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr);
+ if (rsv->target.type == UWB_RSV_TARGET_DEV)
+ devaddr = rsv->target.dev->dev_addr;
+ else
+ devaddr = rsv->target.devaddr;
+ uwb_dev_addr_print(target, sizeof(target), &devaddr);
+
+ dev_dbg(dev, "rsv %s %s -> %s: %s\n",
+ text, owner, target, uwb_rsv_state_str(rsv->state));
+}
+
+static void uwb_rsv_release(struct kref *kref)
+{
+ struct uwb_rsv *rsv = container_of(kref, struct uwb_rsv, kref);
+
+ kfree(rsv);
+}
+
+void uwb_rsv_get(struct uwb_rsv *rsv)
+{
+ kref_get(&rsv->kref);
+}
+
+void uwb_rsv_put(struct uwb_rsv *rsv)
+{
+ kref_put(&rsv->kref, uwb_rsv_release);
+}
+
+/*
+ * Get a free stream index for a reservation.
+ *
+ * If the target is a DevAddr (e.g., a WUSB cluster reservation) then
+ * the stream is allocated from a pool of per-RC stream indexes,
+ * otherwise a unique stream index for the target is selected.
+ */
+static int uwb_rsv_get_stream(struct uwb_rsv *rsv)
+{
+ struct uwb_rc *rc = rsv->rc;
+ struct device *dev = &rc->uwb_dev.dev;
+ unsigned long *streams_bm;
+ int stream;
+
+ switch (rsv->target.type) {
+ case UWB_RSV_TARGET_DEV:
+ streams_bm = rsv->target.dev->streams;
+ break;
+ case UWB_RSV_TARGET_DEVADDR:
+ streams_bm = rc->uwb_dev.streams;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ stream = find_first_zero_bit(streams_bm, UWB_NUM_STREAMS);
+ if (stream >= UWB_NUM_STREAMS) {
+ dev_err(dev, "%s: no available stream found\n", __func__);
+ return -EBUSY;
+ }
+
+ rsv->stream = stream;
+ set_bit(stream, streams_bm);
+
+ dev_dbg(dev, "get stream %d\n", rsv->stream);
+
+ return 0;
+}
+
+static void uwb_rsv_put_stream(struct uwb_rsv *rsv)
+{
+ struct uwb_rc *rc = rsv->rc;
+ struct device *dev = &rc->uwb_dev.dev;
+ unsigned long *streams_bm;
+
+ switch (rsv->target.type) {
+ case UWB_RSV_TARGET_DEV:
+ streams_bm = rsv->target.dev->streams;
+ break;
+ case UWB_RSV_TARGET_DEVADDR:
+ streams_bm = rc->uwb_dev.streams;
+ break;
+ default:
+ return;
+ }
+
+ clear_bit(rsv->stream, streams_bm);
+
+ dev_dbg(dev, "put stream %d\n", rsv->stream);
+}
+
+void uwb_rsv_backoff_win_timer(struct timer_list *t)
+{
+ struct uwb_drp_backoff_win *bow = from_timer(bow, t, timer);
+ struct uwb_rc *rc = container_of(bow, struct uwb_rc, bow);
+ struct device *dev = &rc->uwb_dev.dev;
+
+ bow->can_reserve_extra_mases = true;
+ if (bow->total_expired <= 4) {
+ bow->total_expired++;
+ } else {
+ /* after 4 backoff window has expired we can exit from
+ * the backoff procedure */
+ bow->total_expired = 0;
+ bow->window = UWB_DRP_BACKOFF_WIN_MIN >> 1;
+ }
+ dev_dbg(dev, "backoff_win_timer total_expired=%d, n=%d\n", bow->total_expired, bow->n);
+
+ /* try to relocate all the "to be moved" relocations */
+ uwb_rsv_handle_drp_avail_change(rc);
+}
+
+void uwb_rsv_backoff_win_increment(struct uwb_rc *rc)
+{
+ struct uwb_drp_backoff_win *bow = &rc->bow;
+ struct device *dev = &rc->uwb_dev.dev;
+ unsigned timeout_us;
+
+ dev_dbg(dev, "backoff_win_increment: window=%d\n", bow->window);
+
+ bow->can_reserve_extra_mases = false;
+
+ if((bow->window << 1) == UWB_DRP_BACKOFF_WIN_MAX)
+ return;
+
+ bow->window <<= 1;
+ bow->n = prandom_u32() & (bow->window - 1);
+ dev_dbg(dev, "new_window=%d, n=%d\n", bow->window, bow->n);
+
+ /* reset the timer associated variables */
+ timeout_us = bow->n * UWB_SUPERFRAME_LENGTH_US;
+ bow->total_expired = 0;
+ mod_timer(&bow->timer, jiffies + usecs_to_jiffies(timeout_us));
+}
+
+static void uwb_rsv_stroke_timer(struct uwb_rsv *rsv)
+{
+ int sframes = UWB_MAX_LOST_BEACONS;
+
+ /*
+ * Multicast reservations can become established within 1
+ * super frame and should not be terminated if no response is
+ * received.
+ */
+ if (rsv->state == UWB_RSV_STATE_NONE) {
+ sframes = 0;
+ } else if (rsv->is_multicast) {
+ if (rsv->state == UWB_RSV_STATE_O_INITIATED
+ || rsv->state == UWB_RSV_STATE_O_MOVE_EXPANDING
+ || rsv->state == UWB_RSV_STATE_O_MOVE_COMBINING
+ || rsv->state == UWB_RSV_STATE_O_MOVE_REDUCING)
+ sframes = 1;
+ if (rsv->state == UWB_RSV_STATE_O_ESTABLISHED)
+ sframes = 0;
+
+ }
+
+ if (sframes > 0) {
+ /*
+ * Add an additional 2 superframes to account for the
+ * time to send the SET DRP IE command.
+ */
+ unsigned timeout_us = (sframes + 2) * UWB_SUPERFRAME_LENGTH_US;
+ mod_timer(&rsv->timer, jiffies + usecs_to_jiffies(timeout_us));
+ } else
+ del_timer(&rsv->timer);
+}
+
+/*
+ * Update a reservations state, and schedule an update of the
+ * transmitted DRP IEs.
+ */
+static void uwb_rsv_state_update(struct uwb_rsv *rsv,
+ enum uwb_rsv_state new_state)
+{
+ rsv->state = new_state;
+ rsv->ie_valid = false;
+
+ uwb_rsv_dump("SU", rsv);
+
+ uwb_rsv_stroke_timer(rsv);
+ uwb_rsv_sched_update(rsv->rc);
+}
+
+static void uwb_rsv_callback(struct uwb_rsv *rsv)
+{
+ if (rsv->callback)
+ rsv->callback(rsv);
+}
+
+void uwb_rsv_set_state(struct uwb_rsv *rsv, enum uwb_rsv_state new_state)
+{
+ struct uwb_rsv_move *mv = &rsv->mv;
+
+ if (rsv->state == new_state) {
+ switch (rsv->state) {
+ case UWB_RSV_STATE_O_ESTABLISHED:
+ case UWB_RSV_STATE_O_MOVE_EXPANDING:
+ case UWB_RSV_STATE_O_MOVE_COMBINING:
+ case UWB_RSV_STATE_O_MOVE_REDUCING:
+ case UWB_RSV_STATE_T_ACCEPTED:
+ case UWB_RSV_STATE_T_EXPANDING_ACCEPTED:
+ case UWB_RSV_STATE_T_RESIZED:
+ case UWB_RSV_STATE_NONE:
+ uwb_rsv_stroke_timer(rsv);
+ break;
+ default:
+ /* Expecting a state transition so leave timer
+ as-is. */
+ break;
+ }
+ return;
+ }
+
+ uwb_rsv_dump("SC", rsv);
+
+ switch (new_state) {
+ case UWB_RSV_STATE_NONE:
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_NONE);
+ uwb_rsv_remove(rsv);
+ uwb_rsv_callback(rsv);
+ break;
+ case UWB_RSV_STATE_O_INITIATED:
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_INITIATED);
+ break;
+ case UWB_RSV_STATE_O_PENDING:
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_PENDING);
+ break;
+ case UWB_RSV_STATE_O_MODIFIED:
+ /* in the companion there are the MASes to drop */
+ bitmap_andnot(rsv->mas.bm, rsv->mas.bm, mv->companion_mas.bm, UWB_NUM_MAS);
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_MODIFIED);
+ break;
+ case UWB_RSV_STATE_O_ESTABLISHED:
+ if (rsv->state == UWB_RSV_STATE_O_MODIFIED
+ || rsv->state == UWB_RSV_STATE_O_MOVE_REDUCING) {
+ uwb_drp_avail_release(rsv->rc, &mv->companion_mas);
+ rsv->needs_release_companion_mas = false;
+ }
+ uwb_drp_avail_reserve(rsv->rc, &rsv->mas);
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_ESTABLISHED);
+ uwb_rsv_callback(rsv);
+ break;
+ case UWB_RSV_STATE_O_MOVE_EXPANDING:
+ rsv->needs_release_companion_mas = true;
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_MOVE_EXPANDING);
+ break;
+ case UWB_RSV_STATE_O_MOVE_COMBINING:
+ rsv->needs_release_companion_mas = false;
+ uwb_drp_avail_reserve(rsv->rc, &mv->companion_mas);
+ bitmap_or(rsv->mas.bm, rsv->mas.bm, mv->companion_mas.bm, UWB_NUM_MAS);
+ rsv->mas.safe += mv->companion_mas.safe;
+ rsv->mas.unsafe += mv->companion_mas.unsafe;
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_MOVE_COMBINING);
+ break;
+ case UWB_RSV_STATE_O_MOVE_REDUCING:
+ bitmap_andnot(mv->companion_mas.bm, rsv->mas.bm, mv->final_mas.bm, UWB_NUM_MAS);
+ rsv->needs_release_companion_mas = true;
+ rsv->mas.safe = mv->final_mas.safe;
+ rsv->mas.unsafe = mv->final_mas.unsafe;
+ bitmap_copy(rsv->mas.bm, mv->final_mas.bm, UWB_NUM_MAS);
+ bitmap_copy(rsv->mas.unsafe_bm, mv->final_mas.unsafe_bm, UWB_NUM_MAS);
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_MOVE_REDUCING);
+ break;
+ case UWB_RSV_STATE_T_ACCEPTED:
+ case UWB_RSV_STATE_T_RESIZED:
+ rsv->needs_release_companion_mas = false;
+ uwb_drp_avail_reserve(rsv->rc, &rsv->mas);
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_ACCEPTED);
+ uwb_rsv_callback(rsv);
+ break;
+ case UWB_RSV_STATE_T_DENIED:
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_DENIED);
+ break;
+ case UWB_RSV_STATE_T_CONFLICT:
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_CONFLICT);
+ break;
+ case UWB_RSV_STATE_T_PENDING:
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_PENDING);
+ break;
+ case UWB_RSV_STATE_T_EXPANDING_ACCEPTED:
+ rsv->needs_release_companion_mas = true;
+ uwb_drp_avail_reserve(rsv->rc, &mv->companion_mas);
+ uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_EXPANDING_ACCEPTED);
+ break;
+ default:
+ dev_err(&rsv->rc->uwb_dev.dev, "unhandled state: %s (%d)\n",
+ uwb_rsv_state_str(new_state), new_state);
+ }
+}
+
+static void uwb_rsv_handle_timeout_work(struct work_struct *work)
+{
+ struct uwb_rsv *rsv = container_of(work, struct uwb_rsv,
+ handle_timeout_work);
+ struct uwb_rc *rc = rsv->rc;
+
+ mutex_lock(&rc->rsvs_mutex);
+
+ uwb_rsv_dump("TO", rsv);
+
+ switch (rsv->state) {
+ case UWB_RSV_STATE_O_INITIATED:
+ if (rsv->is_multicast) {
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED);
+ goto unlock;
+ }
+ break;
+ case UWB_RSV_STATE_O_MOVE_EXPANDING:
+ if (rsv->is_multicast) {
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MOVE_COMBINING);
+ goto unlock;
+ }
+ break;
+ case UWB_RSV_STATE_O_MOVE_COMBINING:
+ if (rsv->is_multicast) {
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MOVE_REDUCING);
+ goto unlock;
+ }
+ break;
+ case UWB_RSV_STATE_O_MOVE_REDUCING:
+ if (rsv->is_multicast) {
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED);
+ goto unlock;
+ }
+ break;
+ case UWB_RSV_STATE_O_ESTABLISHED:
+ if (rsv->is_multicast)
+ goto unlock;
+ break;
+ case UWB_RSV_STATE_T_EXPANDING_ACCEPTED:
+ /*
+ * The time out could be for the main or of the
+ * companion DRP, assume it's for the companion and
+ * drop that first. A further time out is required to
+ * drop the main.
+ */
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_ACCEPTED);
+ uwb_drp_avail_release(rsv->rc, &rsv->mv.companion_mas);
+ goto unlock;
+ case UWB_RSV_STATE_NONE:
+ goto unlock;
+ default:
+ break;
+ }
+
+ uwb_rsv_remove(rsv);
+
+unlock:
+ mutex_unlock(&rc->rsvs_mutex);
+}
+
+static struct uwb_rsv *uwb_rsv_alloc(struct uwb_rc *rc)
+{
+ struct uwb_rsv *rsv;
+
+ rsv = kzalloc(sizeof(struct uwb_rsv), GFP_KERNEL);
+ if (!rsv)
+ return NULL;
+
+ INIT_LIST_HEAD(&rsv->rc_node);
+ INIT_LIST_HEAD(&rsv->pal_node);
+ kref_init(&rsv->kref);
+ timer_setup(&rsv->timer, uwb_rsv_timer, 0);
+
+ rsv->rc = rc;
+ INIT_WORK(&rsv->handle_timeout_work, uwb_rsv_handle_timeout_work);
+
+ return rsv;
+}
+
+/**
+ * uwb_rsv_create - allocate and initialize a UWB reservation structure
+ * @rc: the radio controller
+ * @cb: callback to use when the reservation completes or terminates
+ * @pal_priv: data private to the PAL to be passed in the callback
+ *
+ * The callback is called when the state of the reservation changes from:
+ *
+ * - pending to accepted
+ * - pending to denined
+ * - accepted to terminated
+ * - pending to terminated
+ */
+struct uwb_rsv *uwb_rsv_create(struct uwb_rc *rc, uwb_rsv_cb_f cb, void *pal_priv)
+{
+ struct uwb_rsv *rsv;
+
+ rsv = uwb_rsv_alloc(rc);
+ if (!rsv)
+ return NULL;
+
+ rsv->callback = cb;
+ rsv->pal_priv = pal_priv;
+
+ return rsv;
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_create);
+
+void uwb_rsv_remove(struct uwb_rsv *rsv)
+{
+ uwb_rsv_dump("RM", rsv);
+
+ if (rsv->state != UWB_RSV_STATE_NONE)
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
+
+ if (rsv->needs_release_companion_mas)
+ uwb_drp_avail_release(rsv->rc, &rsv->mv.companion_mas);
+ uwb_drp_avail_release(rsv->rc, &rsv->mas);
+
+ if (uwb_rsv_is_owner(rsv))
+ uwb_rsv_put_stream(rsv);
+
+ uwb_dev_put(rsv->owner);
+ if (rsv->target.type == UWB_RSV_TARGET_DEV)
+ uwb_dev_put(rsv->target.dev);
+
+ list_del_init(&rsv->rc_node);
+ uwb_rsv_put(rsv);
+}
+
+/**
+ * uwb_rsv_destroy - free a UWB reservation structure
+ * @rsv: the reservation to free
+ *
+ * The reservation must already be terminated.
+ */
+void uwb_rsv_destroy(struct uwb_rsv *rsv)
+{
+ uwb_rsv_put(rsv);
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_destroy);
+
+/**
+ * usb_rsv_establish - start a reservation establishment
+ * @rsv: the reservation
+ *
+ * The PAL should fill in @rsv's owner, target, type, max_mas,
+ * min_mas, max_interval and is_multicast fields. If the target is a
+ * uwb_dev it must be referenced.
+ *
+ * The reservation's callback will be called when the reservation is
+ * accepted, denied or times out.
+ */
+int uwb_rsv_establish(struct uwb_rsv *rsv)
+{
+ struct uwb_rc *rc = rsv->rc;
+ struct uwb_mas_bm available;
+ struct device *dev = &rc->uwb_dev.dev;
+ int ret;
+
+ mutex_lock(&rc->rsvs_mutex);
+ ret = uwb_rsv_get_stream(rsv);
+ if (ret) {
+ dev_err(dev, "%s: uwb_rsv_get_stream failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ rsv->tiebreaker = prandom_u32() & 1;
+ /* get available mas bitmap */
+ uwb_drp_available(rc, &available);
+
+ ret = uwb_rsv_find_best_allocation(rsv, &available, &rsv->mas);
+ if (ret == UWB_RSV_ALLOC_NOT_FOUND) {
+ ret = -EBUSY;
+ uwb_rsv_put_stream(rsv);
+ dev_err(dev, "%s: uwb_rsv_find_best_allocation failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ ret = uwb_drp_avail_reserve_pending(rc, &rsv->mas);
+ if (ret != 0) {
+ uwb_rsv_put_stream(rsv);
+ dev_err(dev, "%s: uwb_drp_avail_reserve_pending failed: %d\n",
+ __func__, ret);
+ goto out;
+ }
+
+ uwb_rsv_get(rsv);
+ list_add_tail(&rsv->rc_node, &rc->reservations);
+ rsv->owner = &rc->uwb_dev;
+ uwb_dev_get(rsv->owner);
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_INITIATED);
+out:
+ mutex_unlock(&rc->rsvs_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_establish);
+
+/**
+ * uwb_rsv_modify - modify an already established reservation
+ * @rsv: the reservation to modify
+ * @max_mas: new maximum MAS to reserve
+ * @min_mas: new minimum MAS to reserve
+ * @max_interval: new max_interval to use
+ *
+ * FIXME: implement this once there are PALs that use it.
+ */
+int uwb_rsv_modify(struct uwb_rsv *rsv, int max_mas, int min_mas, int max_interval)
+{
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_modify);
+
+/*
+ * move an already established reservation (rc->rsvs_mutex must to be
+ * taken when tis function is called)
+ */
+int uwb_rsv_try_move(struct uwb_rsv *rsv, struct uwb_mas_bm *available)
+{
+ struct uwb_rc *rc = rsv->rc;
+ struct uwb_drp_backoff_win *bow = &rc->bow;
+ struct device *dev = &rc->uwb_dev.dev;
+ struct uwb_rsv_move *mv;
+ int ret = 0;
+
+ if (bow->can_reserve_extra_mases == false)
+ return -EBUSY;
+
+ mv = &rsv->mv;
+
+ if (uwb_rsv_find_best_allocation(rsv, available, &mv->final_mas) == UWB_RSV_ALLOC_FOUND) {
+
+ if (!bitmap_equal(rsv->mas.bm, mv->final_mas.bm, UWB_NUM_MAS)) {
+ /* We want to move the reservation */
+ bitmap_andnot(mv->companion_mas.bm, mv->final_mas.bm, rsv->mas.bm, UWB_NUM_MAS);
+ uwb_drp_avail_reserve_pending(rc, &mv->companion_mas);
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_MOVE_EXPANDING);
+ }
+ } else {
+ dev_dbg(dev, "new allocation not found\n");
+ }
+
+ return ret;
+}
+
+/* It will try to move every reservation in state O_ESTABLISHED giving
+ * to the MAS allocator algorithm an availability that is the real one
+ * plus the allocation already established from the reservation. */
+void uwb_rsv_handle_drp_avail_change(struct uwb_rc *rc)
+{
+ struct uwb_drp_backoff_win *bow = &rc->bow;
+ struct uwb_rsv *rsv;
+ struct uwb_mas_bm mas;
+
+ if (bow->can_reserve_extra_mases == false)
+ return;
+
+ list_for_each_entry(rsv, &rc->reservations, rc_node) {
+ if (rsv->state == UWB_RSV_STATE_O_ESTABLISHED ||
+ rsv->state == UWB_RSV_STATE_O_TO_BE_MOVED) {
+ uwb_drp_available(rc, &mas);
+ bitmap_or(mas.bm, mas.bm, rsv->mas.bm, UWB_NUM_MAS);
+ uwb_rsv_try_move(rsv, &mas);
+ }
+ }
+
+}
+
+/**
+ * uwb_rsv_terminate - terminate an established reservation
+ * @rsv: the reservation to terminate
+ *
+ * A reservation is terminated by removing the DRP IE from the beacon,
+ * the other end will consider the reservation to be terminated when
+ * it does not see the DRP IE for at least mMaxLostBeacons.
+ *
+ * If applicable, the reference to the target uwb_dev will be released.
+ */
+void uwb_rsv_terminate(struct uwb_rsv *rsv)
+{
+ struct uwb_rc *rc = rsv->rc;
+
+ mutex_lock(&rc->rsvs_mutex);
+
+ if (rsv->state != UWB_RSV_STATE_NONE)
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
+
+ mutex_unlock(&rc->rsvs_mutex);
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_terminate);
+
+/**
+ * uwb_rsv_accept - accept a new reservation from a peer
+ * @rsv: the reservation
+ * @cb: call back for reservation changes
+ * @pal_priv: data to be passed in the above call back
+ *
+ * Reservation requests from peers are denied unless a PAL accepts it
+ * by calling this function.
+ *
+ * The PAL call uwb_rsv_destroy() for all accepted reservations before
+ * calling uwb_pal_unregister().
+ */
+void uwb_rsv_accept(struct uwb_rsv *rsv, uwb_rsv_cb_f cb, void *pal_priv)
+{
+ uwb_rsv_get(rsv);
+
+ rsv->callback = cb;
+ rsv->pal_priv = pal_priv;
+ rsv->state = UWB_RSV_STATE_T_ACCEPTED;
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_accept);
+
+/*
+ * Is a received DRP IE for this reservation?
+ */
+static bool uwb_rsv_match(struct uwb_rsv *rsv, struct uwb_dev *src,
+ struct uwb_ie_drp *drp_ie)
+{
+ struct uwb_dev_addr *rsv_src;
+ int stream;
+
+ stream = uwb_ie_drp_stream_index(drp_ie);
+
+ if (rsv->stream != stream)
+ return false;
+
+ switch (rsv->target.type) {
+ case UWB_RSV_TARGET_DEVADDR:
+ return rsv->stream == stream;
+ case UWB_RSV_TARGET_DEV:
+ if (uwb_ie_drp_owner(drp_ie))
+ rsv_src = &rsv->owner->dev_addr;
+ else
+ rsv_src = &rsv->target.dev->dev_addr;
+ return uwb_dev_addr_cmp(&src->dev_addr, rsv_src) == 0;
+ }
+ return false;
+}
+
+static struct uwb_rsv *uwb_rsv_new_target(struct uwb_rc *rc,
+ struct uwb_dev *src,
+ struct uwb_ie_drp *drp_ie)
+{
+ struct uwb_rsv *rsv;
+ struct uwb_pal *pal;
+ enum uwb_rsv_state state;
+
+ rsv = uwb_rsv_alloc(rc);
+ if (!rsv)
+ return NULL;
+
+ rsv->rc = rc;
+ rsv->owner = src;
+ uwb_dev_get(rsv->owner);
+ rsv->target.type = UWB_RSV_TARGET_DEV;
+ rsv->target.dev = &rc->uwb_dev;
+ uwb_dev_get(&rc->uwb_dev);
+ rsv->type = uwb_ie_drp_type(drp_ie);
+ rsv->stream = uwb_ie_drp_stream_index(drp_ie);
+ uwb_drp_ie_to_bm(&rsv->mas, drp_ie);
+
+ /*
+ * See if any PALs are interested in this reservation. If not,
+ * deny the request.
+ */
+ rsv->state = UWB_RSV_STATE_T_DENIED;
+ mutex_lock(&rc->uwb_dev.mutex);
+ list_for_each_entry(pal, &rc->pals, node) {
+ if (pal->new_rsv)
+ pal->new_rsv(pal, rsv);
+ if (rsv->state == UWB_RSV_STATE_T_ACCEPTED)
+ break;
+ }
+ mutex_unlock(&rc->uwb_dev.mutex);
+
+ list_add_tail(&rsv->rc_node, &rc->reservations);
+ state = rsv->state;
+ rsv->state = UWB_RSV_STATE_NONE;
+
+ /* FIXME: do something sensible here */
+ if (state == UWB_RSV_STATE_T_ACCEPTED
+ && uwb_drp_avail_reserve_pending(rc, &rsv->mas) == -EBUSY) {
+ /* FIXME: do something sensible here */
+ } else {
+ uwb_rsv_set_state(rsv, state);
+ }
+
+ return rsv;
+}
+
+/**
+ * uwb_rsv_get_usable_mas - get the bitmap of the usable MAS of a reservations
+ * @rsv: the reservation.
+ * @mas: returns the available MAS.
+ *
+ * The usable MAS of a reservation may be less than the negotiated MAS
+ * if alien BPs are present.
+ */
+void uwb_rsv_get_usable_mas(struct uwb_rsv *rsv, struct uwb_mas_bm *mas)
+{
+ bitmap_zero(mas->bm, UWB_NUM_MAS);
+ bitmap_andnot(mas->bm, rsv->mas.bm, rsv->rc->cnflt_alien_bitmap.bm, UWB_NUM_MAS);
+}
+EXPORT_SYMBOL_GPL(uwb_rsv_get_usable_mas);
+
+/**
+ * uwb_rsv_find - find a reservation for a received DRP IE.
+ * @rc: the radio controller
+ * @src: source of the DRP IE
+ * @drp_ie: the DRP IE
+ *
+ * If the reservation cannot be found and the DRP IE is from a peer
+ * attempting to establish a new reservation, create a new reservation
+ * and add it to the list.
+ */
+struct uwb_rsv *uwb_rsv_find(struct uwb_rc *rc, struct uwb_dev *src,
+ struct uwb_ie_drp *drp_ie)
+{
+ struct uwb_rsv *rsv;
+
+ list_for_each_entry(rsv, &rc->reservations, rc_node) {
+ if (uwb_rsv_match(rsv, src, drp_ie))
+ return rsv;
+ }
+
+ if (uwb_ie_drp_owner(drp_ie))
+ return uwb_rsv_new_target(rc, src, drp_ie);
+
+ return NULL;
+}
+
+/*
+ * Go through all the reservations and check for timeouts and (if
+ * necessary) update their DRP IEs.
+ *
+ * FIXME: look at building the SET_DRP_IE command here rather than
+ * having to rescan the list in uwb_rc_send_all_drp_ie().
+ */
+static bool uwb_rsv_update_all(struct uwb_rc *rc)
+{
+ struct uwb_rsv *rsv, *t;
+ bool ie_updated = false;
+
+ list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) {
+ if (!rsv->ie_valid) {
+ uwb_drp_ie_update(rsv);
+ ie_updated = true;
+ }
+ }
+
+ return ie_updated;
+}
+
+void uwb_rsv_queue_update(struct uwb_rc *rc)
+{
+ unsigned long delay_us = UWB_MAS_LENGTH_US * UWB_MAS_PER_ZONE;
+
+ queue_delayed_work(rc->rsv_workq, &rc->rsv_update_work, usecs_to_jiffies(delay_us));
+}
+
+/**
+ * uwb_rsv_sched_update - schedule an update of the DRP IEs
+ * @rc: the radio controller.
+ *
+ * To improve performance and ensure correctness with [ECMA-368] the
+ * number of SET-DRP-IE commands that are done are limited.
+ *
+ * DRP IEs update come from two sources: DRP events from the hardware
+ * which all occur at the beginning of the superframe ('syncronous'
+ * events) and reservation establishment/termination requests from
+ * PALs or timers ('asynchronous' events).
+ *
+ * A delayed work ensures that all the synchronous events result in
+ * one SET-DRP-IE command.
+ *
+ * Additional logic (the set_drp_ie_pending and rsv_updated_postponed
+ * flags) will prevent an asynchrous event starting a SET-DRP-IE
+ * command if one is currently awaiting a response.
+ *
+ * FIXME: this does leave a window where an asynchrous event can delay
+ * the SET-DRP-IE for a synchronous event by one superframe.
+ */
+void uwb_rsv_sched_update(struct uwb_rc *rc)
+{
+ spin_lock_irq(&rc->rsvs_lock);
+ if (!delayed_work_pending(&rc->rsv_update_work)) {
+ if (rc->set_drp_ie_pending > 0) {
+ rc->set_drp_ie_pending++;
+ goto unlock;
+ }
+ uwb_rsv_queue_update(rc);
+ }
+unlock:
+ spin_unlock_irq(&rc->rsvs_lock);
+}
+
+/*
+ * Update DRP IEs and, if necessary, the DRP Availability IE and send
+ * the updated IEs to the radio controller.
+ */
+static void uwb_rsv_update_work(struct work_struct *work)
+{
+ struct uwb_rc *rc = container_of(work, struct uwb_rc,
+ rsv_update_work.work);
+ bool ie_updated;
+
+ mutex_lock(&rc->rsvs_mutex);
+
+ ie_updated = uwb_rsv_update_all(rc);
+
+ if (!rc->drp_avail.ie_valid) {
+ uwb_drp_avail_ie_update(rc);
+ ie_updated = true;
+ }
+
+ if (ie_updated && (rc->set_drp_ie_pending == 0))
+ uwb_rc_send_all_drp_ie(rc);
+
+ mutex_unlock(&rc->rsvs_mutex);
+}
+
+static void uwb_rsv_alien_bp_work(struct work_struct *work)
+{
+ struct uwb_rc *rc = container_of(work, struct uwb_rc,
+ rsv_alien_bp_work.work);
+ struct uwb_rsv *rsv;
+
+ mutex_lock(&rc->rsvs_mutex);
+
+ list_for_each_entry(rsv, &rc->reservations, rc_node) {
+ if (rsv->type != UWB_DRP_TYPE_ALIEN_BP) {
+ uwb_rsv_callback(rsv);
+ }
+ }
+
+ mutex_unlock(&rc->rsvs_mutex);
+}
+
+static void uwb_rsv_timer(struct timer_list *t)
+{
+ struct uwb_rsv *rsv = from_timer(rsv, t, timer);
+
+ queue_work(rsv->rc->rsv_workq, &rsv->handle_timeout_work);
+}
+
+/**
+ * uwb_rsv_remove_all - remove all reservations
+ * @rc: the radio controller
+ *
+ * A DRP IE update is not done.
+ */
+void uwb_rsv_remove_all(struct uwb_rc *rc)
+{
+ struct uwb_rsv *rsv, *t;
+
+ mutex_lock(&rc->rsvs_mutex);
+ list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) {
+ if (rsv->state != UWB_RSV_STATE_NONE)
+ uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE);
+ del_timer_sync(&rsv->timer);
+ }
+ /* Cancel any postponed update. */
+ rc->set_drp_ie_pending = 0;
+ mutex_unlock(&rc->rsvs_mutex);
+
+ cancel_delayed_work_sync(&rc->rsv_update_work);
+ flush_workqueue(rc->rsv_workq);
+
+ mutex_lock(&rc->rsvs_mutex);
+ list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) {
+ uwb_rsv_remove(rsv);
+ }
+ mutex_unlock(&rc->rsvs_mutex);
+}
+
+void uwb_rsv_init(struct uwb_rc *rc)
+{
+ INIT_LIST_HEAD(&rc->reservations);
+ INIT_LIST_HEAD(&rc->cnflt_alien_list);
+ mutex_init(&rc->rsvs_mutex);
+ spin_lock_init(&rc->rsvs_lock);
+ INIT_DELAYED_WORK(&rc->rsv_update_work, uwb_rsv_update_work);
+ INIT_DELAYED_WORK(&rc->rsv_alien_bp_work, uwb_rsv_alien_bp_work);
+ rc->bow.can_reserve_extra_mases = true;
+ rc->bow.total_expired = 0;
+ rc->bow.window = UWB_DRP_BACKOFF_WIN_MIN >> 1;
+ timer_setup(&rc->bow.timer, uwb_rsv_backoff_win_timer, 0);
+
+ bitmap_complement(rc->uwb_dev.streams, rc->uwb_dev.streams, UWB_NUM_STREAMS);
+}
+
+int uwb_rsv_setup(struct uwb_rc *rc)
+{
+ char name[16];
+
+ snprintf(name, sizeof(name), "%s_rsvd", dev_name(&rc->uwb_dev.dev));
+ rc->rsv_workq = create_singlethread_workqueue(name);
+ if (rc->rsv_workq == NULL)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void uwb_rsv_cleanup(struct uwb_rc *rc)
+{
+ uwb_rsv_remove_all(rc);
+ destroy_workqueue(rc->rsv_workq);
+}
diff --git a/drivers/staging/uwb/scan.c b/drivers/staging/uwb/scan.c
new file mode 100644
index 000000000000..ffc3f452302d
--- /dev/null
+++ b/drivers/staging/uwb/scan.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Scanning management
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ * FIXME: there are issues here on how BEACON and SCAN on USB RCI deal
+ * with each other. Currently seems that START_BEACON while
+ * SCAN_ONLY will cancel the scan, so we need to update the
+ * state here. Clarification request sent by email on
+ * 10/05/2005.
+ * 10/28/2005 No clear answer heard--maybe we'll hack the API
+ * so that when we start beaconing, if the HC is
+ * scanning in a mode not compatible with beaconing
+ * we just fail.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include "uwb-internal.h"
+
+
+/**
+ * Start/stop scanning in a radio controller
+ *
+ * @rc: UWB Radio Controller
+ * @channel: Channel to scan; encodings in WUSB1.0[Table 5.12]
+ * @type: Type of scanning to do.
+ * @bpst_offset: value at which to start scanning (if type ==
+ * UWB_SCAN_ONLY_STARTTIME)
+ * @returns: 0 if ok, < 0 errno code on error
+ *
+ * We put the command on kmalloc'ed memory as some arches cannot do
+ * USB from the stack. The reply event is copied from an stage buffer,
+ * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details.
+ */
+int uwb_rc_scan(struct uwb_rc *rc,
+ unsigned channel, enum uwb_scan_type type,
+ unsigned bpst_offset)
+{
+ int result;
+ struct uwb_rc_cmd_scan *cmd;
+ struct uwb_rc_evt_confirm reply;
+
+ result = -ENOMEM;
+ cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+ if (cmd == NULL)
+ goto error_kzalloc;
+ mutex_lock(&rc->uwb_dev.mutex);
+ cmd->rccb.bCommandType = UWB_RC_CET_GENERAL;
+ cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SCAN);
+ cmd->bChannelNumber = channel;
+ cmd->bScanState = type;
+ cmd->wStartTime = cpu_to_le16(bpst_offset);
+ reply.rceb.bEventType = UWB_RC_CET_GENERAL;
+ reply.rceb.wEvent = UWB_RC_CMD_SCAN;
+ result = uwb_rc_cmd(rc, "SCAN", &cmd->rccb, sizeof(*cmd),
+ &reply.rceb, sizeof(reply));
+ if (result < 0)
+ goto error_cmd;
+ if (reply.bResultCode != UWB_RC_RES_SUCCESS) {
+ dev_err(&rc->uwb_dev.dev,
+ "SCAN: command execution failed: %s (%d)\n",
+ uwb_rc_strerror(reply.bResultCode), reply.bResultCode);
+ result = -EIO;
+ goto error_cmd;
+ }
+ rc->scanning = channel;
+ rc->scan_type = type;
+error_cmd:
+ mutex_unlock(&rc->uwb_dev.mutex);
+ kfree(cmd);
+error_kzalloc:
+ return result;
+}
+
+/*
+ * Print scanning state
+ */
+static ssize_t uwb_rc_scan_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_rc *rc = uwb_dev->rc;
+ ssize_t result;
+
+ mutex_lock(&rc->uwb_dev.mutex);
+ result = sprintf(buf, "%d %d\n", rc->scanning, rc->scan_type);
+ mutex_unlock(&rc->uwb_dev.mutex);
+ return result;
+}
+
+/*
+ *
+ */
+static ssize_t uwb_rc_scan_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct uwb_dev *uwb_dev = to_uwb_dev(dev);
+ struct uwb_rc *rc = uwb_dev->rc;
+ unsigned channel;
+ unsigned type;
+ unsigned bpst_offset = 0;
+ ssize_t result = -EINVAL;
+
+ result = sscanf(buf, "%u %u %u\n", &channel, &type, &bpst_offset);
+ if (result >= 2 && type < UWB_SCAN_TOP)
+ result = uwb_rc_scan(rc, channel, type, bpst_offset);
+
+ return result < 0 ? result : size;
+}
+
+/** Radio Control sysfs interface (declaration) */
+DEVICE_ATTR(scan, S_IRUGO | S_IWUSR, uwb_rc_scan_show, uwb_rc_scan_store);
diff --git a/drivers/staging/uwb/umc-bus.c b/drivers/staging/uwb/umc-bus.c
new file mode 100644
index 000000000000..8b931f66a720
--- /dev/null
+++ b/drivers/staging/uwb/umc-bus.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Bus for UWB Multi-interface Controller capabilities.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/sysfs.h>
+#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include "include/umc.h"
+
+static int umc_bus_pre_reset_helper(struct device *dev, void *data)
+{
+ int ret = 0;
+
+ if (dev->driver) {
+ struct umc_dev *umc = to_umc_dev(dev);
+ struct umc_driver *umc_drv = to_umc_driver(dev->driver);
+
+ if (umc_drv->pre_reset)
+ ret = umc_drv->pre_reset(umc);
+ else
+ device_release_driver(dev);
+ }
+ return ret;
+}
+
+static int umc_bus_post_reset_helper(struct device *dev, void *data)
+{
+ int ret = 0;
+
+ if (dev->driver) {
+ struct umc_dev *umc = to_umc_dev(dev);
+ struct umc_driver *umc_drv = to_umc_driver(dev->driver);
+
+ if (umc_drv->post_reset)
+ ret = umc_drv->post_reset(umc);
+ } else
+ ret = device_attach(dev);
+
+ return ret;
+}
+
+/**
+ * umc_controller_reset - reset the whole UMC controller
+ * @umc: the UMC device for the radio controller.
+ *
+ * Drivers or all capabilities of the controller will have their
+ * pre_reset methods called or be unbound from their device. Then all
+ * post_reset methods will be called or the drivers will be rebound.
+ *
+ * Radio controllers must provide pre_reset and post_reset methods and
+ * reset the hardware in their start method.
+ *
+ * If this is called while a probe() or remove() is in progress it
+ * will return -EAGAIN and not perform the reset.
+ */
+int umc_controller_reset(struct umc_dev *umc)
+{
+ struct device *parent = umc->dev.parent;
+ int ret = 0;
+
+ if (!device_trylock(parent))
+ return -EAGAIN;
+ ret = device_for_each_child(parent, parent, umc_bus_pre_reset_helper);
+ if (ret >= 0)
+ ret = device_for_each_child(parent, parent, umc_bus_post_reset_helper);
+ device_unlock(parent);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(umc_controller_reset);
+
+/**
+ * umc_match_pci_id - match a UMC driver to a UMC device's parent PCI device.
+ * @umc_drv: umc driver with match_data pointing to a zero-terminated
+ * table of pci_device_id's.
+ * @umc: umc device whose parent is to be matched.
+ */
+int umc_match_pci_id(struct umc_driver *umc_drv, struct umc_dev *umc)
+{
+ const struct pci_device_id *id_table = umc_drv->match_data;
+ struct pci_dev *pci;
+
+ if (!dev_is_pci(umc->dev.parent))
+ return 0;
+
+ pci = to_pci_dev(umc->dev.parent);
+ return pci_match_id(id_table, pci) != NULL;
+}
+EXPORT_SYMBOL_GPL(umc_match_pci_id);
+
+static int umc_bus_rescan_helper(struct device *dev, void *data)
+{
+ int ret = 0;
+
+ if (!dev->driver)
+ ret = device_attach(dev);
+
+ return ret;
+}
+
+static void umc_bus_rescan(struct device *parent)
+{
+ int err;
+
+ /*
+ * We can't use bus_rescan_devices() here as it deadlocks when
+ * it tries to retake the dev->parent semaphore.
+ */
+ err = device_for_each_child(parent, NULL, umc_bus_rescan_helper);
+ if (err < 0)
+ printk(KERN_WARNING "%s: rescan of bus failed: %d\n",
+ KBUILD_MODNAME, err);
+}
+
+static int umc_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct umc_dev *umc = to_umc_dev(dev);
+ struct umc_driver *umc_driver = to_umc_driver(drv);
+
+ if (umc->cap_id == umc_driver->cap_id) {
+ if (umc_driver->match)
+ return umc_driver->match(umc_driver, umc);
+ else
+ return 1;
+ }
+ return 0;
+}
+
+static int umc_device_probe(struct device *dev)
+{
+ struct umc_dev *umc;
+ struct umc_driver *umc_driver;
+ int err;
+
+ umc_driver = to_umc_driver(dev->driver);
+ umc = to_umc_dev(dev);
+
+ get_device(dev);
+ err = umc_driver->probe(umc);
+ if (err)
+ put_device(dev);
+ else
+ umc_bus_rescan(dev->parent);
+
+ return err;
+}
+
+static int umc_device_remove(struct device *dev)
+{
+ struct umc_dev *umc;
+ struct umc_driver *umc_driver;
+
+ umc_driver = to_umc_driver(dev->driver);
+ umc = to_umc_dev(dev);
+
+ umc_driver->remove(umc);
+ put_device(dev);
+ return 0;
+}
+
+static ssize_t capability_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct umc_dev *umc = to_umc_dev(dev);
+
+ return sprintf(buf, "0x%02x\n", umc->cap_id);
+}
+static DEVICE_ATTR_RO(capability_id);
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct umc_dev *umc = to_umc_dev(dev);
+
+ return sprintf(buf, "0x%04x\n", umc->version);
+}
+static DEVICE_ATTR_RO(version);
+
+static struct attribute *umc_dev_attrs[] = {
+ &dev_attr_capability_id.attr,
+ &dev_attr_version.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(umc_dev);
+
+struct bus_type umc_bus_type = {
+ .name = "umc",
+ .match = umc_bus_match,
+ .probe = umc_device_probe,
+ .remove = umc_device_remove,
+ .dev_groups = umc_dev_groups,
+};
+EXPORT_SYMBOL_GPL(umc_bus_type);
+
+static int __init umc_bus_init(void)
+{
+ return bus_register(&umc_bus_type);
+}
+module_init(umc_bus_init);
+
+static void __exit umc_bus_exit(void)
+{
+ bus_unregister(&umc_bus_type);
+}
+module_exit(umc_bus_exit);
+
+MODULE_DESCRIPTION("UWB Multi-interface Controller capability bus");
+MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/uwb/umc-dev.c b/drivers/staging/uwb/umc-dev.c
new file mode 100644
index 000000000000..0c71caae00be
--- /dev/null
+++ b/drivers/staging/uwb/umc-dev.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UWB Multi-interface Controller device management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include "include/umc.h"
+
+static void umc_device_release(struct device *dev)
+{
+ struct umc_dev *umc = to_umc_dev(dev);
+
+ kfree(umc);
+}
+
+/**
+ * umc_device_create - allocate a child UMC device
+ * @parent: parent of the new UMC device.
+ * @n: index of the new device.
+ *
+ * The new UMC device will have a bus ID of the parent with '-n'
+ * appended.
+ */
+struct umc_dev *umc_device_create(struct device *parent, int n)
+{
+ struct umc_dev *umc;
+
+ umc = kzalloc(sizeof(struct umc_dev), GFP_KERNEL);
+ if (umc) {
+ dev_set_name(&umc->dev, "%s-%d", dev_name(parent), n);
+ umc->dev.parent = parent;
+ umc->dev.bus = &umc_bus_type;
+ umc->dev.release = umc_device_release;
+
+ umc->dev.dma_mask = parent->dma_mask;
+ }
+ return umc;
+}
+EXPORT_SYMBOL_GPL(umc_device_create);
+
+/**
+ * umc_device_register - register a UMC device
+ * @umc: pointer to the UMC device
+ *
+ * The memory resource for the UMC device is acquired and the device
+ * registered with the system.
+ */
+int umc_device_register(struct umc_dev *umc)
+{
+ int err;
+
+ err = request_resource(umc->resource.parent, &umc->resource);
+ if (err < 0) {
+ dev_err(&umc->dev, "can't allocate resource range %pR: %d\n",
+ &umc->resource, err);
+ goto error_request_resource;
+ }
+
+ err = device_register(&umc->dev);
+ if (err < 0)
+ goto error_device_register;
+ return 0;
+
+error_device_register:
+ put_device(&umc->dev);
+ release_resource(&umc->resource);
+error_request_resource:
+ return err;
+}
+EXPORT_SYMBOL_GPL(umc_device_register);
+
+/**
+ * umc_device_unregister - unregister a UMC device
+ * @umc: pointer to the UMC device
+ *
+ * First we unregister the device, make sure the driver can do it's
+ * resource release thing and then we try to release any left over
+ * resources. We take a ref to the device, to make sure it doesn't
+ * disappear under our feet.
+ */
+void umc_device_unregister(struct umc_dev *umc)
+{
+ struct device *dev;
+ if (!umc)
+ return;
+ dev = get_device(&umc->dev);
+ device_unregister(&umc->dev);
+ release_resource(&umc->resource);
+ put_device(dev);
+}
+EXPORT_SYMBOL_GPL(umc_device_unregister);
diff --git a/drivers/staging/uwb/umc-drv.c b/drivers/staging/uwb/umc-drv.c
new file mode 100644
index 000000000000..ed3bd220e8c2
--- /dev/null
+++ b/drivers/staging/uwb/umc-drv.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UWB Multi-interface Controller driver management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include "include/umc.h"
+
+int __umc_driver_register(struct umc_driver *umc_drv, struct module *module,
+ const char *mod_name)
+{
+ umc_drv->driver.name = umc_drv->name;
+ umc_drv->driver.owner = module;
+ umc_drv->driver.mod_name = mod_name;
+ umc_drv->driver.bus = &umc_bus_type;
+
+ return driver_register(&umc_drv->driver);
+}
+EXPORT_SYMBOL_GPL(__umc_driver_register);
+
+/**
+ * umc_driver_register - unregister a UMC capabiltity driver.
+ * @umc_drv: pointer to the driver.
+ */
+void umc_driver_unregister(struct umc_driver *umc_drv)
+{
+ driver_unregister(&umc_drv->driver);
+}
+EXPORT_SYMBOL_GPL(umc_driver_unregister);
diff --git a/drivers/staging/uwb/uwb-debug.c b/drivers/staging/uwb/uwb-debug.c
new file mode 100644
index 000000000000..dd14df219ef8
--- /dev/null
+++ b/drivers/staging/uwb/uwb-debug.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Debug support
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ *
+ * FIXME: doc
+ */
+
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/notifier.h>
+#include <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/seq_file.h>
+
+#include "include/debug-cmd.h"
+#include "uwb-internal.h"
+
+/*
+ * Debug interface
+ *
+ * Per radio controller debugfs files (in uwb/uwbN/):
+ *
+ * command: Flexible command interface (see <linux/uwb/debug-cmd.h>).
+ *
+ * reservations: information on reservations.
+ *
+ * accept: Set to true (Y or 1) to accept reservation requests from
+ * peers.
+ *
+ * drp_avail: DRP availability information.
+ */
+
+struct uwb_dbg {
+ struct uwb_pal pal;
+
+ bool accept;
+ struct list_head rsvs;
+
+ struct dentry *root_d;
+ struct dentry *command_f;
+ struct dentry *reservations_f;
+ struct dentry *accept_f;
+ struct dentry *drp_avail_f;
+ spinlock_t list_lock;
+};
+
+static struct dentry *root_dir;
+
+static void uwb_dbg_rsv_cb(struct uwb_rsv *rsv)
+{
+ struct uwb_dbg *dbg = rsv->pal_priv;
+
+ uwb_rsv_dump("debug", rsv);
+
+ if (rsv->state == UWB_RSV_STATE_NONE) {
+ spin_lock(&dbg->list_lock);
+ list_del(&rsv->pal_node);
+ spin_unlock(&dbg->list_lock);
+ uwb_rsv_destroy(rsv);
+ }
+}
+
+static int cmd_rsv_establish(struct uwb_rc *rc,
+ struct uwb_dbg_cmd_rsv_establish *cmd)
+{
+ struct uwb_mac_addr macaddr;
+ struct uwb_rsv *rsv;
+ struct uwb_dev *target;
+ int ret;
+
+ memcpy(&macaddr, cmd->target, sizeof(macaddr));
+ target = uwb_dev_get_by_macaddr(rc, &macaddr);
+ if (target == NULL)
+ return -ENODEV;
+
+ rsv = uwb_rsv_create(rc, uwb_dbg_rsv_cb, rc->dbg);
+ if (rsv == NULL) {
+ uwb_dev_put(target);
+ return -ENOMEM;
+ }
+
+ rsv->target.type = UWB_RSV_TARGET_DEV;
+ rsv->target.dev = target;
+ rsv->type = cmd->type;
+ rsv->max_mas = cmd->max_mas;
+ rsv->min_mas = cmd->min_mas;
+ rsv->max_interval = cmd->max_interval;
+
+ ret = uwb_rsv_establish(rsv);
+ if (ret)
+ uwb_rsv_destroy(rsv);
+ else {
+ spin_lock(&(rc->dbg)->list_lock);
+ list_add_tail(&rsv->pal_node, &rc->dbg->rsvs);
+ spin_unlock(&(rc->dbg)->list_lock);
+ }
+ return ret;
+}
+
+static int cmd_rsv_terminate(struct uwb_rc *rc,
+ struct uwb_dbg_cmd_rsv_terminate *cmd)
+{
+ struct uwb_rsv *rsv, *found = NULL;
+ int i = 0;
+
+ spin_lock(&(rc->dbg)->list_lock);
+
+ list_for_each_entry(rsv, &rc->dbg->rsvs, pal_node) {
+ if (i == cmd->index) {
+ found = rsv;
+ uwb_rsv_get(found);
+ break;
+ }
+ i++;
+ }
+
+ spin_unlock(&(rc->dbg)->list_lock);
+
+ if (!found)
+ return -EINVAL;
+
+ uwb_rsv_terminate(found);
+ uwb_rsv_put(found);
+
+ return 0;
+}
+
+static int cmd_ie_add(struct uwb_rc *rc, struct uwb_dbg_cmd_ie *ie_to_add)
+{
+ return uwb_rc_ie_add(rc,
+ (const struct uwb_ie_hdr *) ie_to_add->data,
+ ie_to_add->len);
+}
+
+static int cmd_ie_rm(struct uwb_rc *rc, struct uwb_dbg_cmd_ie *ie_to_rm)
+{
+ return uwb_rc_ie_rm(rc, ie_to_rm->data[0]);
+}
+
+static ssize_t command_write(struct file *file, const char __user *buf,
+ size_t len, loff_t *off)
+{
+ struct uwb_rc *rc = file->private_data;
+ struct uwb_dbg_cmd cmd;
+ int ret = 0;
+
+ if (len != sizeof(struct uwb_dbg_cmd))
+ return -EINVAL;
+
+ if (copy_from_user(&cmd, buf, len) != 0)
+ return -EFAULT;
+
+ switch (cmd.type) {
+ case UWB_DBG_CMD_RSV_ESTABLISH:
+ ret = cmd_rsv_establish(rc, &cmd.rsv_establish);
+ break;
+ case UWB_DBG_CMD_RSV_TERMINATE:
+ ret = cmd_rsv_terminate(rc, &cmd.rsv_terminate);
+ break;
+ case UWB_DBG_CMD_IE_ADD:
+ ret = cmd_ie_add(rc, &cmd.ie_add);
+ break;
+ case UWB_DBG_CMD_IE_RM:
+ ret = cmd_ie_rm(rc, &cmd.ie_rm);
+ break;
+ case UWB_DBG_CMD_RADIO_START:
+ ret = uwb_radio_start(&rc->dbg->pal);
+ break;
+ case UWB_DBG_CMD_RADIO_STOP:
+ uwb_radio_stop(&rc->dbg->pal);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret < 0 ? ret : len;
+}
+
+static const struct file_operations command_fops = {
+ .open = simple_open,
+ .write = command_write,
+ .read = NULL,
+ .llseek = no_llseek,
+ .owner = THIS_MODULE,
+};
+
+static int reservations_show(struct seq_file *s, void *p)
+{
+ struct uwb_rc *rc = s->private;
+ struct uwb_rsv *rsv;
+
+ mutex_lock(&rc->rsvs_mutex);
+
+ list_for_each_entry(rsv, &rc->reservations, rc_node) {
+ struct uwb_dev_addr devaddr;
+ char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE];
+ bool is_owner;
+
+ uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr);
+ if (rsv->target.type == UWB_RSV_TARGET_DEV) {
+ devaddr = rsv->target.dev->dev_addr;
+ is_owner = &rc->uwb_dev == rsv->owner;
+ } else {
+ devaddr = rsv->target.devaddr;
+ is_owner = true;
+ }
+ uwb_dev_addr_print(target, sizeof(target), &devaddr);
+
+ seq_printf(s, "%c %s -> %s: %s\n",
+ is_owner ? 'O' : 'T',
+ owner, target, uwb_rsv_state_str(rsv->state));
+ seq_printf(s, " stream: %d type: %s\n",
+ rsv->stream, uwb_rsv_type_str(rsv->type));
+ seq_printf(s, " %*pb\n", UWB_NUM_MAS, rsv->mas.bm);
+ }
+
+ mutex_unlock(&rc->rsvs_mutex);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(reservations);
+
+static int drp_avail_show(struct seq_file *s, void *p)
+{
+ struct uwb_rc *rc = s->private;
+
+ seq_printf(s, "global: %*pb\n", UWB_NUM_MAS, rc->drp_avail.global);
+ seq_printf(s, "local: %*pb\n", UWB_NUM_MAS, rc->drp_avail.local);
+ seq_printf(s, "pending: %*pb\n", UWB_NUM_MAS, rc->drp_avail.pending);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(drp_avail);
+
+static void uwb_dbg_channel_changed(struct uwb_pal *pal, int channel)
+{
+ struct device *dev = &pal->rc->uwb_dev.dev;
+
+ if (channel > 0)
+ dev_info(dev, "debug: channel %d started\n", channel);
+ else
+ dev_info(dev, "debug: channel stopped\n");
+}
+
+static void uwb_dbg_new_rsv(struct uwb_pal *pal, struct uwb_rsv *rsv)
+{
+ struct uwb_dbg *dbg = container_of(pal, struct uwb_dbg, pal);
+
+ if (dbg->accept) {
+ spin_lock(&dbg->list_lock);
+ list_add_tail(&rsv->pal_node, &dbg->rsvs);
+ spin_unlock(&dbg->list_lock);
+ uwb_rsv_accept(rsv, uwb_dbg_rsv_cb, dbg);
+ }
+}
+
+/**
+ * uwb_dbg_add_rc - add a debug interface for a radio controller
+ * @rc: the radio controller
+ */
+void uwb_dbg_add_rc(struct uwb_rc *rc)
+{
+ rc->dbg = kzalloc(sizeof(struct uwb_dbg), GFP_KERNEL);
+ if (rc->dbg == NULL)
+ return;
+
+ INIT_LIST_HEAD(&rc->dbg->rsvs);
+ spin_lock_init(&(rc->dbg)->list_lock);
+
+ uwb_pal_init(&rc->dbg->pal);
+ rc->dbg->pal.rc = rc;
+ rc->dbg->pal.channel_changed = uwb_dbg_channel_changed;
+ rc->dbg->pal.new_rsv = uwb_dbg_new_rsv;
+ uwb_pal_register(&rc->dbg->pal);
+
+ if (root_dir) {
+ rc->dbg->root_d = debugfs_create_dir(dev_name(&rc->uwb_dev.dev),
+ root_dir);
+ rc->dbg->command_f = debugfs_create_file("command", 0200,
+ rc->dbg->root_d, rc,
+ &command_fops);
+ rc->dbg->reservations_f = debugfs_create_file("reservations", 0444,
+ rc->dbg->root_d, rc,
+ &reservations_fops);
+ rc->dbg->accept_f = debugfs_create_bool("accept", 0644,
+ rc->dbg->root_d,
+ &rc->dbg->accept);
+ rc->dbg->drp_avail_f = debugfs_create_file("drp_avail", 0444,
+ rc->dbg->root_d, rc,
+ &drp_avail_fops);
+ }
+}
+
+/**
+ * uwb_dbg_del_rc - remove a radio controller's debug interface
+ * @rc: the radio controller
+ */
+void uwb_dbg_del_rc(struct uwb_rc *rc)
+{
+ struct uwb_rsv *rsv, *t;
+
+ if (rc->dbg == NULL)
+ return;
+
+ list_for_each_entry_safe(rsv, t, &rc->dbg->rsvs, pal_node) {
+ uwb_rsv_terminate(rsv);
+ }
+
+ uwb_pal_unregister(&rc->dbg->pal);
+
+ if (root_dir) {
+ debugfs_remove(rc->dbg->drp_avail_f);
+ debugfs_remove(rc->dbg->accept_f);
+ debugfs_remove(rc->dbg->reservations_f);
+ debugfs_remove(rc->dbg->command_f);
+ debugfs_remove(rc->dbg->root_d);
+ }
+}
+
+/**
+ * uwb_dbg_exit - initialize the debug interface sub-module
+ */
+void uwb_dbg_init(void)
+{
+ root_dir = debugfs_create_dir("uwb", NULL);
+}
+
+/**
+ * uwb_dbg_exit - clean-up the debug interface sub-module
+ */
+void uwb_dbg_exit(void)
+{
+ debugfs_remove(root_dir);
+}
+
+/**
+ * uwb_dbg_create_pal_dir - create a debugfs directory for a PAL
+ * @pal: The PAL.
+ */
+struct dentry *uwb_dbg_create_pal_dir(struct uwb_pal *pal)
+{
+ struct uwb_rc *rc = pal->rc;
+
+ if (root_dir && rc->dbg && rc->dbg->root_d && pal->name)
+ return debugfs_create_dir(pal->name, rc->dbg->root_d);
+ return NULL;
+}
diff --git a/drivers/staging/uwb/uwb-internal.h b/drivers/staging/uwb/uwb-internal.h
new file mode 100644
index 000000000000..4c2fdac7f610
--- /dev/null
+++ b/drivers/staging/uwb/uwb-internal.h
@@ -0,0 +1,366 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Ultra Wide Band
+ * UWB internal API
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This contains most of the internal API for UWB. This is stuff used
+ * across the stack that of course, is of no interest to the rest.
+ *
+ * Some parts might end up going public (like uwb_rc_*())...
+ */
+
+#ifndef __UWB_INTERNAL_H__
+#define __UWB_INTERNAL_H__
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include "uwb.h"
+
+struct uwb_beca_e;
+
+/* General device API */
+extern void uwb_dev_init(struct uwb_dev *uwb_dev);
+extern int __uwb_dev_offair(struct uwb_dev *, struct uwb_rc *);
+extern int uwb_dev_add(struct uwb_dev *uwb_dev, struct device *parent_dev,
+ struct uwb_rc *parent_rc);
+extern void uwb_dev_rm(struct uwb_dev *uwb_dev);
+extern void uwbd_dev_onair(struct uwb_rc *, struct uwb_beca_e *);
+extern void uwbd_dev_offair(struct uwb_beca_e *);
+void uwb_notify(struct uwb_rc *rc, struct uwb_dev *uwb_dev, enum uwb_notifs event);
+
+/* General UWB Radio Controller Internal API */
+extern struct uwb_rc *__uwb_rc_try_get(struct uwb_rc *);
+static inline struct uwb_rc *__uwb_rc_get(struct uwb_rc *rc)
+{
+ uwb_dev_get(&rc->uwb_dev);
+ return rc;
+}
+
+static inline void __uwb_rc_put(struct uwb_rc *rc)
+{
+ if (rc)
+ uwb_dev_put(&rc->uwb_dev);
+}
+
+extern int uwb_rc_reset(struct uwb_rc *rc);
+extern int uwb_rc_beacon(struct uwb_rc *rc,
+ int channel, unsigned bpst_offset);
+extern int uwb_rc_scan(struct uwb_rc *rc,
+ unsigned channel, enum uwb_scan_type type,
+ unsigned bpst_offset);
+extern int uwb_rc_send_all_drp_ie(struct uwb_rc *rc);
+
+void uwb_rc_ie_init(struct uwb_rc *);
+int uwb_rc_ie_setup(struct uwb_rc *);
+void uwb_rc_ie_release(struct uwb_rc *);
+int uwb_ie_dump_hex(const struct uwb_ie_hdr *ies, size_t len,
+ char *buf, size_t size);
+int uwb_rc_set_ie(struct uwb_rc *, struct uwb_rc_cmd_set_ie *);
+
+
+extern const char *uwb_rc_strerror(unsigned code);
+
+/*
+ * Time to wait for a response to an RC command.
+ *
+ * Some commands can take a long time to response. e.g., START_BEACON
+ * may scan for several superframes before joining an existing beacon
+ * group and this can take around 600 ms.
+ */
+#define UWB_RC_CMD_TIMEOUT_MS 1000 /* ms */
+
+/*
+ * Notification/Event Handlers
+ */
+
+struct uwb_rc_neh;
+
+extern int uwb_rc_cmd_async(struct uwb_rc *rc, const char *cmd_name,
+ struct uwb_rccb *cmd, size_t cmd_size,
+ u8 expected_type, u16 expected_event,
+ uwb_rc_cmd_cb_f cb, void *arg);
+
+
+void uwb_rc_neh_create(struct uwb_rc *rc);
+void uwb_rc_neh_destroy(struct uwb_rc *rc);
+
+struct uwb_rc_neh *uwb_rc_neh_add(struct uwb_rc *rc, struct uwb_rccb *cmd,
+ u8 expected_type, u16 expected_event,
+ uwb_rc_cmd_cb_f cb, void *arg);
+void uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh);
+void uwb_rc_neh_arm(struct uwb_rc *rc, struct uwb_rc_neh *neh);
+void uwb_rc_neh_put(struct uwb_rc_neh *neh);
+
+/* Event size tables */
+extern int uwb_est_create(void);
+extern void uwb_est_destroy(void);
+
+/*
+ * UWB conflicting alien reservations
+ */
+struct uwb_cnflt_alien {
+ struct uwb_rc *rc;
+ struct list_head rc_node;
+ struct uwb_mas_bm mas;
+ struct timer_list timer;
+ struct work_struct cnflt_update_work;
+};
+
+enum uwb_uwb_rsv_alloc_result {
+ UWB_RSV_ALLOC_FOUND = 0,
+ UWB_RSV_ALLOC_NOT_FOUND,
+};
+
+enum uwb_rsv_mas_status {
+ UWB_RSV_MAS_NOT_AVAIL = 1,
+ UWB_RSV_MAS_SAFE,
+ UWB_RSV_MAS_UNSAFE,
+};
+
+struct uwb_rsv_col_set_info {
+ unsigned char start_col;
+ unsigned char interval;
+ unsigned char safe_mas_per_col;
+ unsigned char unsafe_mas_per_col;
+};
+
+struct uwb_rsv_col_info {
+ unsigned char max_avail_safe;
+ unsigned char max_avail_unsafe;
+ unsigned char highest_mas[UWB_MAS_PER_ZONE];
+ struct uwb_rsv_col_set_info csi;
+};
+
+struct uwb_rsv_row_info {
+ unsigned char avail[UWB_MAS_PER_ZONE];
+ unsigned char free_rows;
+ unsigned char used_rows;
+};
+
+/*
+ * UWB find allocation
+ */
+struct uwb_rsv_alloc_info {
+ unsigned char bm[UWB_MAS_PER_ZONE * UWB_NUM_ZONES];
+ struct uwb_rsv_col_info ci[UWB_NUM_ZONES];
+ struct uwb_rsv_row_info ri;
+ struct uwb_mas_bm *not_available;
+ struct uwb_mas_bm *result;
+ int min_mas;
+ int max_mas;
+ int max_interval;
+ int total_allocated_mases;
+ int safe_allocated_mases;
+ int unsafe_allocated_mases;
+ int interval;
+};
+
+int uwb_rsv_find_best_allocation(struct uwb_rsv *rsv,
+ struct uwb_mas_bm *available,
+ struct uwb_mas_bm *result);
+void uwb_rsv_handle_drp_avail_change(struct uwb_rc *rc);
+/*
+ * UWB Events & management daemon
+ */
+
+/**
+ * enum uwb_event_type - types of UWB management daemon events
+ *
+ * The UWB management daemon (uwbd) can receive two types of events:
+ * UWB_EVT_TYPE_NOTIF - notification from the radio controller.
+ * UWB_EVT_TYPE_MSG - a simple message.
+ */
+enum uwb_event_type {
+ UWB_EVT_TYPE_NOTIF,
+ UWB_EVT_TYPE_MSG,
+};
+
+/**
+ * struct uwb_event_notif - an event for a radio controller notification
+ * @size: Size of the buffer (ie: Guaranteed to contain at least
+ * a full 'struct uwb_rceb')
+ * @rceb: Pointer to a kmalloced() event payload
+ */
+struct uwb_event_notif {
+ size_t size;
+ struct uwb_rceb *rceb;
+};
+
+/**
+ * enum uwb_event_message - an event for a message for asynchronous processing
+ *
+ * UWB_EVT_MSG_RESET - reset the radio controller and all PAL hardware.
+ */
+enum uwb_event_message {
+ UWB_EVT_MSG_RESET,
+};
+
+/**
+ * UWB Event
+ * @rc: Radio controller that emitted the event (referenced)
+ * @ts_jiffies: Timestamp, when was it received
+ * @type: This event's type.
+ */
+struct uwb_event {
+ struct list_head list_node;
+ struct uwb_rc *rc;
+ unsigned long ts_jiffies;
+ enum uwb_event_type type;
+ union {
+ struct uwb_event_notif notif;
+ enum uwb_event_message message;
+ };
+};
+
+extern void uwbd_start(struct uwb_rc *rc);
+extern void uwbd_stop(struct uwb_rc *rc);
+extern struct uwb_event *uwb_event_alloc(size_t, gfp_t gfp_mask);
+extern void uwbd_event_queue(struct uwb_event *);
+void uwbd_flush(struct uwb_rc *rc);
+
+/* UWB event handlers */
+extern int uwbd_evt_handle_rc_ie_rcv(struct uwb_event *);
+extern int uwbd_evt_handle_rc_beacon(struct uwb_event *);
+extern int uwbd_evt_handle_rc_beacon_size(struct uwb_event *);
+extern int uwbd_evt_handle_rc_bpoie_change(struct uwb_event *);
+extern int uwbd_evt_handle_rc_bp_slot_change(struct uwb_event *);
+extern int uwbd_evt_handle_rc_drp(struct uwb_event *);
+extern int uwbd_evt_handle_rc_drp_avail(struct uwb_event *);
+
+int uwbd_msg_handle_reset(struct uwb_event *evt);
+
+
+/*
+ * Address management
+ */
+int uwb_rc_dev_addr_assign(struct uwb_rc *rc);
+int uwbd_evt_handle_rc_dev_addr_conflict(struct uwb_event *evt);
+
+/*
+ * UWB Beacon Cache
+ *
+ * Each beacon we received is kept in a cache--when we receive that
+ * beacon consistently, that means there is a new device that we have
+ * to add to the system.
+ */
+
+extern unsigned long beacon_timeout_ms;
+
+/**
+ * Beacon cache entry
+ *
+ * @jiffies_refresh: last time a beacon was received that refreshed
+ * this cache entry.
+ * @uwb_dev: device connected to this beacon. This pointer is not
+ * safe, you need to get it with uwb_dev_try_get()
+ *
+ * @hits: how many time we have seen this beacon since last time we
+ * cleared it
+ */
+struct uwb_beca_e {
+ struct mutex mutex;
+ struct kref refcnt;
+ struct list_head node;
+ struct uwb_mac_addr *mac_addr;
+ struct uwb_dev_addr dev_addr;
+ u8 hits;
+ unsigned long ts_jiffies;
+ struct uwb_dev *uwb_dev;
+ struct uwb_rc_evt_beacon *be;
+ struct stats lqe_stats, rssi_stats; /* radio statistics */
+};
+struct uwb_beacon_frame;
+extern ssize_t uwb_bce_print_IEs(struct uwb_dev *, struct uwb_beca_e *,
+ char *, size_t);
+
+extern void uwb_bce_kfree(struct kref *_bce);
+static inline void uwb_bce_get(struct uwb_beca_e *bce)
+{
+ kref_get(&bce->refcnt);
+}
+static inline void uwb_bce_put(struct uwb_beca_e *bce)
+{
+ kref_put(&bce->refcnt, uwb_bce_kfree);
+}
+extern void uwb_beca_purge(struct uwb_rc *rc);
+extern void uwb_beca_release(struct uwb_rc *rc);
+
+struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc,
+ const struct uwb_dev_addr *devaddr);
+struct uwb_dev *uwb_dev_get_by_macaddr(struct uwb_rc *rc,
+ const struct uwb_mac_addr *macaddr);
+
+int uwb_radio_setup(struct uwb_rc *rc);
+void uwb_radio_reset_state(struct uwb_rc *rc);
+void uwb_radio_shutdown(struct uwb_rc *rc);
+int uwb_radio_force_channel(struct uwb_rc *rc, int channel);
+
+/* -- UWB Sysfs representation */
+extern struct class uwb_rc_class;
+extern struct bus_type uwb_bus_type;
+extern struct device_attribute dev_attr_mac_address;
+extern struct device_attribute dev_attr_beacon;
+extern struct device_attribute dev_attr_scan;
+
+/* -- DRP Bandwidth allocator: bandwidth allocations, reservations, DRP */
+void uwb_rsv_init(struct uwb_rc *rc);
+int uwb_rsv_setup(struct uwb_rc *rc);
+void uwb_rsv_cleanup(struct uwb_rc *rc);
+void uwb_rsv_remove_all(struct uwb_rc *rc);
+void uwb_rsv_get(struct uwb_rsv *rsv);
+void uwb_rsv_put(struct uwb_rsv *rsv);
+bool uwb_rsv_has_two_drp_ies(struct uwb_rsv *rsv);
+void uwb_rsv_dump(char *text, struct uwb_rsv *rsv);
+int uwb_rsv_try_move(struct uwb_rsv *rsv, struct uwb_mas_bm *available);
+void uwb_rsv_backoff_win_timer(struct timer_list *t);
+void uwb_rsv_backoff_win_increment(struct uwb_rc *rc);
+int uwb_rsv_status(struct uwb_rsv *rsv);
+int uwb_rsv_companion_status(struct uwb_rsv *rsv);
+
+void uwb_rsv_set_state(struct uwb_rsv *rsv, enum uwb_rsv_state new_state);
+void uwb_rsv_remove(struct uwb_rsv *rsv);
+struct uwb_rsv *uwb_rsv_find(struct uwb_rc *rc, struct uwb_dev *src,
+ struct uwb_ie_drp *drp_ie);
+void uwb_rsv_sched_update(struct uwb_rc *rc);
+void uwb_rsv_queue_update(struct uwb_rc *rc);
+
+int uwb_drp_ie_update(struct uwb_rsv *rsv);
+void uwb_drp_ie_to_bm(struct uwb_mas_bm *bm, const struct uwb_ie_drp *drp_ie);
+
+void uwb_drp_avail_init(struct uwb_rc *rc);
+void uwb_drp_available(struct uwb_rc *rc, struct uwb_mas_bm *avail);
+int uwb_drp_avail_reserve_pending(struct uwb_rc *rc, struct uwb_mas_bm *mas);
+void uwb_drp_avail_reserve(struct uwb_rc *rc, struct uwb_mas_bm *mas);
+void uwb_drp_avail_release(struct uwb_rc *rc, struct uwb_mas_bm *mas);
+void uwb_drp_avail_ie_update(struct uwb_rc *rc);
+
+/* -- PAL support */
+void uwb_rc_pal_init(struct uwb_rc *rc);
+
+/* -- Misc */
+
+extern ssize_t uwb_mac_frame_hdr_print(char *, size_t,
+ const struct uwb_mac_frame_hdr *);
+
+/* -- Debug interface */
+void uwb_dbg_init(void);
+void uwb_dbg_exit(void);
+void uwb_dbg_add_rc(struct uwb_rc *rc);
+void uwb_dbg_del_rc(struct uwb_rc *rc);
+struct dentry *uwb_dbg_create_pal_dir(struct uwb_pal *pal);
+
+static inline void uwb_dev_lock(struct uwb_dev *uwb_dev)
+{
+ device_lock(&uwb_dev->dev);
+}
+
+static inline void uwb_dev_unlock(struct uwb_dev *uwb_dev)
+{
+ device_unlock(&uwb_dev->dev);
+}
+
+#endif /* #ifndef __UWB_INTERNAL_H__ */
diff --git a/drivers/staging/uwb/uwb.h b/drivers/staging/uwb/uwb.h
new file mode 100644
index 000000000000..6a59706ba3a0
--- /dev/null
+++ b/drivers/staging/uwb/uwb.h
@@ -0,0 +1,817 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Ultra Wide Band
+ * UWB API
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: doc: overview of the API, different parts and pointers
+ */
+
+#ifndef __LINUX__UWB_H__
+#define __LINUX__UWB_H__
+
+#include <linux/limits.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <asm/page.h>
+#include "include/spec.h"
+
+struct uwb_dev;
+struct uwb_beca_e;
+struct uwb_rc;
+struct uwb_rsv;
+struct uwb_dbg;
+
+/**
+ * struct uwb_dev - a UWB Device
+ * @rc: UWB Radio Controller that discovered the device (kind of its
+ * parent).
+ * @bce: a beacon cache entry for this device; or NULL if the device
+ * is a local radio controller.
+ * @mac_addr: the EUI-48 address of this device.
+ * @dev_addr: the current DevAddr used by this device.
+ * @beacon_slot: the slot number the beacon is using.
+ * @streams: bitmap of streams allocated to reservations targeted at
+ * this device. For an RC, this is the streams allocated for
+ * reservations targeted at DevAddrs.
+ *
+ * A UWB device may either by a neighbor or part of a local radio
+ * controller.
+ */
+struct uwb_dev {
+ struct mutex mutex;
+ struct list_head list_node;
+ struct device dev;
+ struct uwb_rc *rc; /* radio controller */
+ struct uwb_beca_e *bce; /* Beacon Cache Entry */
+
+ struct uwb_mac_addr mac_addr;
+ struct uwb_dev_addr dev_addr;
+ int beacon_slot;
+ DECLARE_BITMAP(streams, UWB_NUM_STREAMS);
+ DECLARE_BITMAP(last_availability_bm, UWB_NUM_MAS);
+};
+#define to_uwb_dev(d) container_of(d, struct uwb_dev, dev)
+
+/**
+ * UWB HWA/WHCI Radio Control {Command|Event} Block context IDs
+ *
+ * RC[CE]Bs have a 'context ID' field that matches the command with
+ * the event received to confirm it.
+ *
+ * Maximum number of context IDs
+ */
+enum { UWB_RC_CTX_MAX = 256 };
+
+
+/** Notification chain head for UWB generated events to listeners */
+struct uwb_notifs_chain {
+ struct list_head list;
+ struct mutex mutex;
+};
+
+/* Beacon cache list */
+struct uwb_beca {
+ struct list_head list;
+ size_t entries;
+ struct mutex mutex;
+};
+
+/* Event handling thread. */
+struct uwbd {
+ int pid;
+ struct task_struct *task;
+ wait_queue_head_t wq;
+ struct list_head event_list;
+ spinlock_t event_list_lock;
+};
+
+/**
+ * struct uwb_mas_bm - a bitmap of all MAS in a superframe
+ * @bm: a bitmap of length #UWB_NUM_MAS
+ */
+struct uwb_mas_bm {
+ DECLARE_BITMAP(bm, UWB_NUM_MAS);
+ DECLARE_BITMAP(unsafe_bm, UWB_NUM_MAS);
+ int safe;
+ int unsafe;
+};
+
+/**
+ * uwb_rsv_state - UWB Reservation state.
+ *
+ * NONE - reservation is not active (no DRP IE being transmitted).
+ *
+ * Owner reservation states:
+ *
+ * INITIATED - owner has sent an initial DRP request.
+ * PENDING - target responded with pending Reason Code.
+ * MODIFIED - reservation manager is modifying an established
+ * reservation with a different MAS allocation.
+ * ESTABLISHED - the reservation has been successfully negotiated.
+ *
+ * Target reservation states:
+ *
+ * DENIED - request is denied.
+ * ACCEPTED - request is accepted.
+ * PENDING - PAL has yet to make a decision to whether to accept or
+ * deny.
+ *
+ * FIXME: further target states TBD.
+ */
+enum uwb_rsv_state {
+ UWB_RSV_STATE_NONE = 0,
+ UWB_RSV_STATE_O_INITIATED,
+ UWB_RSV_STATE_O_PENDING,
+ UWB_RSV_STATE_O_MODIFIED,
+ UWB_RSV_STATE_O_ESTABLISHED,
+ UWB_RSV_STATE_O_TO_BE_MOVED,
+ UWB_RSV_STATE_O_MOVE_EXPANDING,
+ UWB_RSV_STATE_O_MOVE_COMBINING,
+ UWB_RSV_STATE_O_MOVE_REDUCING,
+ UWB_RSV_STATE_T_ACCEPTED,
+ UWB_RSV_STATE_T_DENIED,
+ UWB_RSV_STATE_T_CONFLICT,
+ UWB_RSV_STATE_T_PENDING,
+ UWB_RSV_STATE_T_EXPANDING_ACCEPTED,
+ UWB_RSV_STATE_T_EXPANDING_CONFLICT,
+ UWB_RSV_STATE_T_EXPANDING_PENDING,
+ UWB_RSV_STATE_T_EXPANDING_DENIED,
+ UWB_RSV_STATE_T_RESIZED,
+
+ UWB_RSV_STATE_LAST,
+};
+
+enum uwb_rsv_target_type {
+ UWB_RSV_TARGET_DEV,
+ UWB_RSV_TARGET_DEVADDR,
+};
+
+/**
+ * struct uwb_rsv_target - the target of a reservation.
+ *
+ * Reservations unicast and targeted at a single device
+ * (UWB_RSV_TARGET_DEV); or (e.g., in the case of WUSB) targeted at a
+ * specific (private) DevAddr (UWB_RSV_TARGET_DEVADDR).
+ */
+struct uwb_rsv_target {
+ enum uwb_rsv_target_type type;
+ union {
+ struct uwb_dev *dev;
+ struct uwb_dev_addr devaddr;
+ };
+};
+
+struct uwb_rsv_move {
+ struct uwb_mas_bm final_mas;
+ struct uwb_ie_drp *companion_drp_ie;
+ struct uwb_mas_bm companion_mas;
+};
+
+/*
+ * Number of streams reserved for reservations targeted at DevAddrs.
+ */
+#define UWB_NUM_GLOBAL_STREAMS 1
+
+typedef void (*uwb_rsv_cb_f)(struct uwb_rsv *rsv);
+
+/**
+ * struct uwb_rsv - a DRP reservation
+ *
+ * Data structure management:
+ *
+ * @rc: the radio controller this reservation is for
+ * (as target or owner)
+ * @rc_node: a list node for the RC
+ * @pal_node: a list node for the PAL
+ *
+ * Owner and target parameters:
+ *
+ * @owner: the UWB device owning this reservation
+ * @target: the target UWB device
+ * @type: reservation type
+ *
+ * Owner parameters:
+ *
+ * @max_mas: maxiumum number of MAS
+ * @min_mas: minimum number of MAS
+ * @sparsity: owner selected sparsity
+ * @is_multicast: true iff multicast
+ *
+ * @callback: callback function when the reservation completes
+ * @pal_priv: private data for the PAL making the reservation
+ *
+ * Reservation status:
+ *
+ * @status: negotiation status
+ * @stream: stream index allocated for this reservation
+ * @tiebreaker: conflict tiebreaker for this reservation
+ * @mas: reserved MAS
+ * @drp_ie: the DRP IE
+ * @ie_valid: true iff the DRP IE matches the reservation parameters
+ *
+ * DRP reservations are uniquely identified by the owner, target and
+ * stream index. However, when using a DevAddr as a target (e.g., for
+ * a WUSB cluster reservation) the responses may be received from
+ * devices with different DevAddrs. In this case, reservations are
+ * uniquely identified by just the stream index. A number of stream
+ * indexes (UWB_NUM_GLOBAL_STREAMS) are reserved for this.
+ */
+struct uwb_rsv {
+ struct uwb_rc *rc;
+ struct list_head rc_node;
+ struct list_head pal_node;
+ struct kref kref;
+
+ struct uwb_dev *owner;
+ struct uwb_rsv_target target;
+ enum uwb_drp_type type;
+ int max_mas;
+ int min_mas;
+ int max_interval;
+ bool is_multicast;
+
+ uwb_rsv_cb_f callback;
+ void *pal_priv;
+
+ enum uwb_rsv_state state;
+ bool needs_release_companion_mas;
+ u8 stream;
+ u8 tiebreaker;
+ struct uwb_mas_bm mas;
+ struct uwb_ie_drp *drp_ie;
+ struct uwb_rsv_move mv;
+ bool ie_valid;
+ struct timer_list timer;
+ struct work_struct handle_timeout_work;
+};
+
+static const
+struct uwb_mas_bm uwb_mas_bm_zero = { .bm = { 0 } };
+
+static inline void uwb_mas_bm_copy_le(void *dst, const struct uwb_mas_bm *mas)
+{
+ bitmap_copy_le(dst, mas->bm, UWB_NUM_MAS);
+}
+
+/**
+ * struct uwb_drp_avail - a radio controller's view of MAS usage
+ * @global: MAS unused by neighbors (excluding reservations targeted
+ * or owned by the local radio controller) or the beaon period
+ * @local: MAS unused by local established reservations
+ * @pending: MAS unused by local pending reservations
+ * @ie: DRP Availability IE to be included in the beacon
+ * @ie_valid: true iff @ie is valid and does not need to regenerated from
+ * @global and @local
+ *
+ * Each radio controller maintains a view of MAS usage or
+ * availability. MAS available for a new reservation are determined
+ * from the intersection of @global, @local, and @pending.
+ *
+ * The radio controller must transmit a DRP Availability IE that's the
+ * intersection of @global and @local.
+ *
+ * A set bit indicates the MAS is unused and available.
+ *
+ * rc->rsvs_mutex should be held before accessing this data structure.
+ *
+ * [ECMA-368] section 17.4.3.
+ */
+struct uwb_drp_avail {
+ DECLARE_BITMAP(global, UWB_NUM_MAS);
+ DECLARE_BITMAP(local, UWB_NUM_MAS);
+ DECLARE_BITMAP(pending, UWB_NUM_MAS);
+ struct uwb_ie_drp_avail ie;
+ bool ie_valid;
+};
+
+struct uwb_drp_backoff_win {
+ u8 window;
+ u8 n;
+ int total_expired;
+ struct timer_list timer;
+ bool can_reserve_extra_mases;
+};
+
+const char *uwb_rsv_state_str(enum uwb_rsv_state state);
+const char *uwb_rsv_type_str(enum uwb_drp_type type);
+
+struct uwb_rsv *uwb_rsv_create(struct uwb_rc *rc, uwb_rsv_cb_f cb,
+ void *pal_priv);
+void uwb_rsv_destroy(struct uwb_rsv *rsv);
+
+int uwb_rsv_establish(struct uwb_rsv *rsv);
+int uwb_rsv_modify(struct uwb_rsv *rsv,
+ int max_mas, int min_mas, int sparsity);
+void uwb_rsv_terminate(struct uwb_rsv *rsv);
+
+void uwb_rsv_accept(struct uwb_rsv *rsv, uwb_rsv_cb_f cb, void *pal_priv);
+
+void uwb_rsv_get_usable_mas(struct uwb_rsv *orig_rsv, struct uwb_mas_bm *mas);
+
+/**
+ * Radio Control Interface instance
+ *
+ *
+ * Life cycle rules: those of the UWB Device.
+ *
+ * @index: an index number for this radio controller, as used in the
+ * device name.
+ * @version: version of protocol supported by this device
+ * @priv: Backend implementation; rw with uwb_dev.dev.sem taken.
+ * @cmd: Backend implementation to execute commands; rw and call
+ * only with uwb_dev.dev.sem taken.
+ * @reset: Hardware reset of radio controller and any PAL controllers.
+ * @filter: Backend implementation to manipulate data to and from device
+ * to be compliant to specification assumed by driver (WHCI
+ * 0.95).
+ *
+ * uwb_dev.dev.mutex is used to execute commands and update
+ * the corresponding structures; can't use a spinlock
+ * because rc->cmd() can sleep.
+ * @ies: This is a dynamically allocated array cacheing the
+ * IEs (settable by the host) that the beacon of this
+ * radio controller is currently sending.
+ *
+ * In reality, we store here the full command we set to
+ * the radio controller (which is basically a command
+ * prefix followed by all the IEs the beacon currently
+ * contains). This way we don't have to realloc and
+ * memcpy when setting it.
+ *
+ * We set this up in uwb_rc_ie_setup(), where we alloc
+ * this struct, call get_ie() [so we know which IEs are
+ * currently being sent, if any].
+ *
+ * @ies_capacity:Amount of space (in bytes) allocated in @ies. The
+ * amount used is given by sizeof(*ies) plus ies->wIELength
+ * (which is a little endian quantity all the time).
+ * @ies_mutex: protect the IE cache
+ * @dbg: information for the debug interface
+ */
+struct uwb_rc {
+ struct uwb_dev uwb_dev;
+ int index;
+ u16 version;
+
+ struct module *owner;
+ void *priv;
+ int (*start)(struct uwb_rc *rc);
+ void (*stop)(struct uwb_rc *rc);
+ int (*cmd)(struct uwb_rc *, const struct uwb_rccb *, size_t);
+ int (*reset)(struct uwb_rc *rc);
+ int (*filter_cmd)(struct uwb_rc *, struct uwb_rccb **, size_t *);
+ int (*filter_event)(struct uwb_rc *, struct uwb_rceb **, const size_t,
+ size_t *, size_t *);
+
+ spinlock_t neh_lock; /* protects neh_* and ctx_* */
+ struct list_head neh_list; /* Open NE handles */
+ unsigned long ctx_bm[UWB_RC_CTX_MAX / 8 / sizeof(unsigned long)];
+ u8 ctx_roll;
+
+ int beaconing; /* Beaconing state [channel number] */
+ int beaconing_forced;
+ int scanning;
+ enum uwb_scan_type scan_type:3;
+ unsigned ready:1;
+ struct uwb_notifs_chain notifs_chain;
+ struct uwb_beca uwb_beca;
+
+ struct uwbd uwbd;
+
+ struct uwb_drp_backoff_win bow;
+ struct uwb_drp_avail drp_avail;
+ struct list_head reservations;
+ struct list_head cnflt_alien_list;
+ struct uwb_mas_bm cnflt_alien_bitmap;
+ struct mutex rsvs_mutex;
+ spinlock_t rsvs_lock;
+ struct workqueue_struct *rsv_workq;
+
+ struct delayed_work rsv_update_work;
+ struct delayed_work rsv_alien_bp_work;
+ int set_drp_ie_pending;
+ struct mutex ies_mutex;
+ struct uwb_rc_cmd_set_ie *ies;
+ size_t ies_capacity;
+
+ struct list_head pals;
+ int active_pals;
+
+ struct uwb_dbg *dbg;
+};
+
+
+/**
+ * struct uwb_pal - a UWB PAL
+ * @name: descriptive name for this PAL (wusbhc, wlp, etc.).
+ * @device: a device for the PAL. Used to link the PAL and the radio
+ * controller in sysfs.
+ * @rc: the radio controller the PAL uses.
+ * @channel_changed: called when the channel used by the radio changes.
+ * A channel of -1 means the channel has been stopped.
+ * @new_rsv: called when a peer requests a reservation (may be NULL if
+ * the PAL cannot accept reservation requests).
+ * @channel: channel being used by the PAL; 0 if the PAL isn't using
+ * the radio; -1 if the PAL wishes to use the radio but
+ * cannot.
+ * @debugfs_dir: a debugfs directory which the PAL can use for its own
+ * debugfs files.
+ *
+ * A Protocol Adaptation Layer (PAL) is a user of the WiMedia UWB
+ * radio platform (e.g., WUSB, WLP or Bluetooth UWB AMP).
+ *
+ * The PALs using a radio controller must register themselves to
+ * permit the UWB stack to coordinate usage of the radio between the
+ * various PALs or to allow PALs to response to certain requests from
+ * peers.
+ *
+ * A struct uwb_pal should be embedded in a containing structure
+ * belonging to the PAL and initialized with uwb_pal_init()). Fields
+ * should be set appropriately by the PAL before registering the PAL
+ * with uwb_pal_register().
+ */
+struct uwb_pal {
+ struct list_head node;
+ const char *name;
+ struct device *device;
+ struct uwb_rc *rc;
+
+ void (*channel_changed)(struct uwb_pal *pal, int channel);
+ void (*new_rsv)(struct uwb_pal *pal, struct uwb_rsv *rsv);
+
+ int channel;
+ struct dentry *debugfs_dir;
+};
+
+void uwb_pal_init(struct uwb_pal *pal);
+int uwb_pal_register(struct uwb_pal *pal);
+void uwb_pal_unregister(struct uwb_pal *pal);
+
+int uwb_radio_start(struct uwb_pal *pal);
+void uwb_radio_stop(struct uwb_pal *pal);
+
+/*
+ * General public API
+ *
+ * This API can be used by UWB device drivers or by those implementing
+ * UWB Radio Controllers
+ */
+struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc,
+ const struct uwb_dev_addr *devaddr);
+struct uwb_dev *uwb_dev_get_by_rc(struct uwb_dev *, struct uwb_rc *);
+static inline void uwb_dev_get(struct uwb_dev *uwb_dev)
+{
+ get_device(&uwb_dev->dev);
+}
+static inline void uwb_dev_put(struct uwb_dev *uwb_dev)
+{
+ put_device(&uwb_dev->dev);
+}
+struct uwb_dev *uwb_dev_try_get(struct uwb_rc *rc, struct uwb_dev *uwb_dev);
+
+/**
+ * Callback function for 'uwb_{dev,rc}_foreach()'.
+ *
+ * @dev: Linux device instance
+ * 'uwb_dev = container_of(dev, struct uwb_dev, dev)'
+ * @priv: Data passed by the caller to 'uwb_{dev,rc}_foreach()'.
+ *
+ * @returns: 0 to continue the iterations, any other val to stop
+ * iterating and return the value to the caller of
+ * _foreach().
+ */
+typedef int (*uwb_dev_for_each_f)(struct device *dev, void *priv);
+int uwb_dev_for_each(struct uwb_rc *rc, uwb_dev_for_each_f func, void *priv);
+
+struct uwb_rc *uwb_rc_alloc(void);
+struct uwb_rc *uwb_rc_get_by_dev(const struct uwb_dev_addr *);
+struct uwb_rc *uwb_rc_get_by_grandpa(const struct device *);
+void uwb_rc_put(struct uwb_rc *rc);
+
+typedef void (*uwb_rc_cmd_cb_f)(struct uwb_rc *rc, void *arg,
+ struct uwb_rceb *reply, ssize_t reply_size);
+
+int uwb_rc_cmd_async(struct uwb_rc *rc, const char *cmd_name,
+ struct uwb_rccb *cmd, size_t cmd_size,
+ u8 expected_type, u16 expected_event,
+ uwb_rc_cmd_cb_f cb, void *arg);
+ssize_t uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name,
+ struct uwb_rccb *cmd, size_t cmd_size,
+ struct uwb_rceb *reply, size_t reply_size);
+ssize_t uwb_rc_vcmd(struct uwb_rc *rc, const char *cmd_name,
+ struct uwb_rccb *cmd, size_t cmd_size,
+ u8 expected_type, u16 expected_event,
+ struct uwb_rceb **preply);
+
+size_t __uwb_addr_print(char *, size_t, const unsigned char *, int);
+
+int uwb_rc_dev_addr_set(struct uwb_rc *, const struct uwb_dev_addr *);
+int uwb_rc_dev_addr_get(struct uwb_rc *, struct uwb_dev_addr *);
+int uwb_rc_mac_addr_set(struct uwb_rc *, const struct uwb_mac_addr *);
+int uwb_rc_mac_addr_get(struct uwb_rc *, struct uwb_mac_addr *);
+int __uwb_mac_addr_assigned_check(struct device *, void *);
+int __uwb_dev_addr_assigned_check(struct device *, void *);
+
+/* Print in @buf a pretty repr of @addr */
+static inline size_t uwb_dev_addr_print(char *buf, size_t buf_size,
+ const struct uwb_dev_addr *addr)
+{
+ return __uwb_addr_print(buf, buf_size, addr->data, 0);
+}
+
+/* Print in @buf a pretty repr of @addr */
+static inline size_t uwb_mac_addr_print(char *buf, size_t buf_size,
+ const struct uwb_mac_addr *addr)
+{
+ return __uwb_addr_print(buf, buf_size, addr->data, 1);
+}
+
+/* @returns 0 if device addresses @addr2 and @addr1 are equal */
+static inline int uwb_dev_addr_cmp(const struct uwb_dev_addr *addr1,
+ const struct uwb_dev_addr *addr2)
+{
+ return memcmp(addr1, addr2, sizeof(*addr1));
+}
+
+/* @returns 0 if MAC addresses @addr2 and @addr1 are equal */
+static inline int uwb_mac_addr_cmp(const struct uwb_mac_addr *addr1,
+ const struct uwb_mac_addr *addr2)
+{
+ return memcmp(addr1, addr2, sizeof(*addr1));
+}
+
+/* @returns !0 if a MAC @addr is a broadcast address */
+static inline int uwb_mac_addr_bcast(const struct uwb_mac_addr *addr)
+{
+ struct uwb_mac_addr bcast = {
+ .data = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }
+ };
+ return !uwb_mac_addr_cmp(addr, &bcast);
+}
+
+/* @returns !0 if a MAC @addr is all zeroes*/
+static inline int uwb_mac_addr_unset(const struct uwb_mac_addr *addr)
+{
+ struct uwb_mac_addr unset = {
+ .data = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+ };
+ return !uwb_mac_addr_cmp(addr, &unset);
+}
+
+/* @returns !0 if the address is in use. */
+static inline unsigned __uwb_dev_addr_assigned(struct uwb_rc *rc,
+ struct uwb_dev_addr *addr)
+{
+ return uwb_dev_for_each(rc, __uwb_dev_addr_assigned_check, addr);
+}
+
+/*
+ * UWB Radio Controller API
+ *
+ * This API is used (in addition to the general API) to implement UWB
+ * Radio Controllers.
+ */
+void uwb_rc_init(struct uwb_rc *);
+int uwb_rc_add(struct uwb_rc *, struct device *dev, void *rc_priv);
+void uwb_rc_rm(struct uwb_rc *);
+void uwb_rc_neh_grok(struct uwb_rc *, void *, size_t);
+void uwb_rc_neh_error(struct uwb_rc *, int);
+void uwb_rc_reset_all(struct uwb_rc *rc);
+void uwb_rc_pre_reset(struct uwb_rc *rc);
+int uwb_rc_post_reset(struct uwb_rc *rc);
+
+/**
+ * uwb_rsv_is_owner - is the owner of this reservation the RC?
+ * @rsv: the reservation
+ */
+static inline bool uwb_rsv_is_owner(struct uwb_rsv *rsv)
+{
+ return rsv->owner == &rsv->rc->uwb_dev;
+}
+
+/**
+ * enum uwb_notifs - UWB events that can be passed to any listeners
+ * @UWB_NOTIF_ONAIR: a new neighbour has joined the beacon group.
+ * @UWB_NOTIF_OFFAIR: a neighbour has left the beacon group.
+ *
+ * Higher layers can register callback functions with the radio
+ * controller using uwb_notifs_register(). The radio controller
+ * maintains a list of all registered handlers and will notify all
+ * nodes when an event occurs.
+ */
+enum uwb_notifs {
+ UWB_NOTIF_ONAIR,
+ UWB_NOTIF_OFFAIR,
+};
+
+/* Callback function registered with UWB */
+struct uwb_notifs_handler {
+ struct list_head list_node;
+ void (*cb)(void *, struct uwb_dev *, enum uwb_notifs);
+ void *data;
+};
+
+int uwb_notifs_register(struct uwb_rc *, struct uwb_notifs_handler *);
+int uwb_notifs_deregister(struct uwb_rc *, struct uwb_notifs_handler *);
+
+
+/**
+ * UWB radio controller Event Size Entry (for creating entry tables)
+ *
+ * WUSB and WHCI define events and notifications, and they might have
+ * fixed or variable size.
+ *
+ * Each event/notification has a size which is not necessarily known
+ * in advance based on the event code. As well, vendor specific
+ * events/notifications will have a size impossible to determine
+ * unless we know about the device's specific details.
+ *
+ * It was way too smart of the spec writers not to think that it would
+ * be impossible for a generic driver to skip over vendor specific
+ * events/notifications if there are no LENGTH fields in the HEADER of
+ * each message...the transaction size cannot be counted on as the
+ * spec does not forbid to pack more than one event in a single
+ * transaction.
+ *
+ * Thus, we guess sizes with tables (or for events, when you know the
+ * size ahead of time you can use uwb_rc_neh_extra_size*()). We
+ * register tables with the known events and their sizes, and then we
+ * traverse those tables. For those with variable length, we provide a
+ * way to lookup the size inside the event/notification's
+ * payload. This allows device-specific event size tables to be
+ * registered.
+ *
+ * @size: Size of the payload
+ *
+ * @offset: if != 0, at offset @offset-1 starts a field with a length
+ * that has to be added to @size. The format of the field is
+ * given by @type.
+ *
+ * @type: Type and length of the offset field. Most common is LE 16
+ * bits (that's why that is zero); others are there mostly to
+ * cover for bugs and weirdos.
+ */
+struct uwb_est_entry {
+ size_t size;
+ unsigned offset;
+ enum { UWB_EST_16 = 0, UWB_EST_8 = 1 } type;
+};
+
+int uwb_est_register(u8 type, u8 code_high, u16 vendor, u16 product,
+ const struct uwb_est_entry *, size_t entries);
+int uwb_est_unregister(u8 type, u8 code_high, u16 vendor, u16 product,
+ const struct uwb_est_entry *, size_t entries);
+ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb,
+ size_t len);
+
+/* -- Misc */
+
+enum {
+ EDC_MAX_ERRORS = 10,
+ EDC_ERROR_TIMEFRAME = HZ,
+};
+
+/* error density counter */
+struct edc {
+ unsigned long timestart;
+ u16 errorcount;
+};
+
+static inline
+void edc_init(struct edc *edc)
+{
+ edc->timestart = jiffies;
+}
+
+/* Called when an error occurred.
+ * This is way to determine if the number of acceptable errors per time
+ * period has been exceeded. It is not accurate as there are cases in which
+ * this scheme will not work, for example if there are periodic occurrences
+ * of errors that straddle updates to the start time. This scheme is
+ * sufficient for our usage.
+ *
+ * @returns 1 if maximum acceptable errors per timeframe has been exceeded.
+ */
+static inline int edc_inc(struct edc *err_hist, u16 max_err, u16 timeframe)
+{
+ unsigned long now;
+
+ now = jiffies;
+ if (now - err_hist->timestart > timeframe) {
+ err_hist->errorcount = 1;
+ err_hist->timestart = now;
+ } else if (++err_hist->errorcount > max_err) {
+ err_hist->errorcount = 0;
+ err_hist->timestart = now;
+ return 1;
+ }
+ return 0;
+}
+
+
+/* Information Element handling */
+
+struct uwb_ie_hdr *uwb_ie_next(void **ptr, size_t *len);
+int uwb_rc_ie_add(struct uwb_rc *uwb_rc, const struct uwb_ie_hdr *ies, size_t size);
+int uwb_rc_ie_rm(struct uwb_rc *uwb_rc, enum uwb_ie element_id);
+
+/*
+ * Transmission statistics
+ *
+ * UWB uses LQI and RSSI (one byte values) for reporting radio signal
+ * strength and line quality indication. We do quick and dirty
+ * averages of those. They are signed values, btw.
+ *
+ * For 8 bit quantities, we keep the min, the max, an accumulator
+ * (@sigma) and a # of samples. When @samples gets to 255, we compute
+ * the average (@sigma / @samples), place it in @sigma and reset
+ * @samples to 1 (so we use it as the first sample).
+ *
+ * Now, statistically speaking, probably I am kicking the kidneys of
+ * some books I have in my shelves collecting dust, but I just want to
+ * get an approx, not the Nobel.
+ *
+ * LOCKING: there is no locking per se, but we try to keep a lockless
+ * schema. Only _add_samples() modifies the values--as long as you
+ * have other locking on top that makes sure that no two calls of
+ * _add_sample() happen at the same time, then we are fine. Now, for
+ * resetting the values we just set @samples to 0 and that makes the
+ * next _add_sample() to start with defaults. Reading the values in
+ * _show() currently can race, so you need to make sure the calls are
+ * under the same lock that protects calls to _add_sample(). FIXME:
+ * currently unlocked (It is not ultraprecise but does the trick. Bite
+ * me).
+ */
+struct stats {
+ s8 min, max;
+ s16 sigma;
+ atomic_t samples;
+};
+
+static inline
+void stats_init(struct stats *stats)
+{
+ atomic_set(&stats->samples, 0);
+ wmb();
+}
+
+static inline
+void stats_add_sample(struct stats *stats, s8 sample)
+{
+ s8 min, max;
+ s16 sigma;
+ unsigned samples = atomic_read(&stats->samples);
+ if (samples == 0) { /* it was zero before, so we initialize */
+ min = 127;
+ max = -128;
+ sigma = 0;
+ } else {
+ min = stats->min;
+ max = stats->max;
+ sigma = stats->sigma;
+ }
+
+ if (sample < min) /* compute new values */
+ min = sample;
+ else if (sample > max)
+ max = sample;
+ sigma += sample;
+
+ stats->min = min; /* commit */
+ stats->max = max;
+ stats->sigma = sigma;
+ if (atomic_add_return(1, &stats->samples) > 255) {
+ /* wrapped around! reset */
+ stats->sigma = sigma / 256;
+ atomic_set(&stats->samples, 1);
+ }
+}
+
+static inline ssize_t stats_show(struct stats *stats, char *buf)
+{
+ int min, max, avg;
+ int samples = atomic_read(&stats->samples);
+ if (samples == 0)
+ min = max = avg = 0;
+ else {
+ min = stats->min;
+ max = stats->max;
+ avg = stats->sigma / samples;
+ }
+ return scnprintf(buf, PAGE_SIZE, "%d %d %d\n", min, max, avg);
+}
+
+static inline ssize_t stats_store(struct stats *stats, const char *buf,
+ size_t size)
+{
+ stats_init(stats);
+ return size;
+}
+
+#endif /* #ifndef __LINUX__UWB_H__ */
diff --git a/drivers/staging/uwb/uwbd.c b/drivers/staging/uwb/uwbd.c
new file mode 100644
index 000000000000..dc5c743d4f5f
--- /dev/null
+++ b/drivers/staging/uwb/uwbd.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ultra Wide Band
+ * Neighborhood Management Daemon
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This daemon takes care of maintaing information that describes the
+ * UWB neighborhood that the radios in this machine can see. It also
+ * keeps a tab of which devices are visible, makes sure each HC sits
+ * on a different channel to avoid interfering, etc.
+ *
+ * Different drivers (radio controller, device, any API in general)
+ * communicate with this daemon through an event queue. Daemon wakes
+ * up, takes a list of events and handles them one by one; handling
+ * function is extracted from a table based on the event's type and
+ * subtype. Events are freed only if the handling function says so.
+ *
+ * . Lock protecting the event list has to be an spinlock and locked
+ * with IRQSAVE because it might be called from an interrupt
+ * context (ie: when events arrive and the notification drops
+ * down from the ISR).
+ *
+ * . UWB radio controller drivers queue events to the daemon using
+ * uwbd_event_queue(). They just get the event, chew it to make it
+ * look like UWBD likes it and pass it in a buffer allocated with
+ * uwb_event_alloc().
+ *
+ * EVENTS
+ *
+ * Events have a type, a subtype, a length, some other stuff and the
+ * data blob, which depends on the event. The header is 'struct
+ * uwb_event'; for payloads, see 'struct uwbd_evt_*'.
+ *
+ * EVENT HANDLER TABLES
+ *
+ * To find a handling function for an event, the type is used to index
+ * a subtype-table in the type-table. The subtype-table is indexed
+ * with the subtype to get the function that handles the event. Start
+ * with the main type-table 'uwbd_evt_type_handler'.
+ *
+ * DEVICES
+ *
+ * Devices are created when a bunch of beacons have been received and
+ * it is stablished that the device has stable radio presence. CREATED
+ * only, not configured. Devices are ONLY configured when an
+ * Application-Specific IE Probe is receieved, in which the device
+ * declares which Protocol ID it groks. Then the device is CONFIGURED
+ * (and the driver->probe() stuff of the device model is invoked).
+ *
+ * Devices are considered disconnected when a certain number of
+ * beacons are not received in an amount of time.
+ *
+ * Handler functions are called normally uwbd_evt_handle_*().
+ */
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/freezer.h>
+
+#include "uwb-internal.h"
+
+/*
+ * UWBD Event handler function signature
+ *
+ * Return !0 if the event needs not to be freed (ie the handler
+ * takes/took care of it). 0 means the daemon code will free the
+ * event.
+ *
+ * @evt->rc is already referenced and guaranteed to exist. See
+ * uwb_evt_handle().
+ */
+typedef int (*uwbd_evt_handler_f)(struct uwb_event *);
+
+/**
+ * Properties of a UWBD event
+ *
+ * @handler: the function that will handle this event
+ * @name: text name of event
+ */
+struct uwbd_event {
+ uwbd_evt_handler_f handler;
+ const char *name;
+};
+
+/* Table of handlers for and properties of the UWBD Radio Control Events */
+static struct uwbd_event uwbd_urc_events[] = {
+ [UWB_RC_EVT_IE_RCV] = {
+ .handler = uwbd_evt_handle_rc_ie_rcv,
+ .name = "IE_RECEIVED"
+ },
+ [UWB_RC_EVT_BEACON] = {
+ .handler = uwbd_evt_handle_rc_beacon,
+ .name = "BEACON_RECEIVED"
+ },
+ [UWB_RC_EVT_BEACON_SIZE] = {
+ .handler = uwbd_evt_handle_rc_beacon_size,
+ .name = "BEACON_SIZE_CHANGE"
+ },
+ [UWB_RC_EVT_BPOIE_CHANGE] = {
+ .handler = uwbd_evt_handle_rc_bpoie_change,
+ .name = "BPOIE_CHANGE"
+ },
+ [UWB_RC_EVT_BP_SLOT_CHANGE] = {
+ .handler = uwbd_evt_handle_rc_bp_slot_change,
+ .name = "BP_SLOT_CHANGE"
+ },
+ [UWB_RC_EVT_DRP_AVAIL] = {
+ .handler = uwbd_evt_handle_rc_drp_avail,
+ .name = "DRP_AVAILABILITY_CHANGE"
+ },
+ [UWB_RC_EVT_DRP] = {
+ .handler = uwbd_evt_handle_rc_drp,
+ .name = "DRP"
+ },
+ [UWB_RC_EVT_DEV_ADDR_CONFLICT] = {
+ .handler = uwbd_evt_handle_rc_dev_addr_conflict,
+ .name = "DEV_ADDR_CONFLICT",
+ },
+};
+
+
+
+struct uwbd_evt_type_handler {
+ const char *name;
+ struct uwbd_event *uwbd_events;
+ size_t size;
+};
+
+/* Table of handlers for each UWBD Event type. */
+static struct uwbd_evt_type_handler uwbd_urc_evt_type_handlers[] = {
+ [UWB_RC_CET_GENERAL] = {
+ .name = "URC",
+ .uwbd_events = uwbd_urc_events,
+ .size = ARRAY_SIZE(uwbd_urc_events),
+ },
+};
+
+static const struct uwbd_event uwbd_message_handlers[] = {
+ [UWB_EVT_MSG_RESET] = {
+ .handler = uwbd_msg_handle_reset,
+ .name = "reset",
+ },
+};
+
+/*
+ * Handle an URC event passed to the UWB Daemon
+ *
+ * @evt: the event to handle
+ * @returns: 0 if the event can be kfreed, !0 on the contrary
+ * (somebody else took ownership) [coincidentally, returning
+ * a <0 errno code will free it :)].
+ *
+ * Looks up the two indirection tables (one for the type, one for the
+ * subtype) to decide which function handles it and then calls the
+ * handler.
+ *
+ * The event structure passed to the event handler has the radio
+ * controller in @evt->rc referenced. The reference will be dropped
+ * once the handler returns, so if it needs it for longer (async),
+ * it'll need to take another one.
+ */
+static
+int uwbd_event_handle_urc(struct uwb_event *evt)
+{
+ int result = -EINVAL;
+ struct uwbd_evt_type_handler *type_table;
+ uwbd_evt_handler_f handler;
+ u8 type, context;
+ u16 event;
+
+ type = evt->notif.rceb->bEventType;
+ event = le16_to_cpu(evt->notif.rceb->wEvent);
+ context = evt->notif.rceb->bEventContext;
+
+ if (type >= ARRAY_SIZE(uwbd_urc_evt_type_handlers))
+ goto out;
+ type_table = &uwbd_urc_evt_type_handlers[type];
+ if (type_table->uwbd_events == NULL)
+ goto out;
+ if (event >= type_table->size)
+ goto out;
+ handler = type_table->uwbd_events[event].handler;
+ if (handler == NULL)
+ goto out;
+
+ result = (*handler)(evt);
+out:
+ if (result < 0)
+ dev_err(&evt->rc->uwb_dev.dev,
+ "UWBD: event 0x%02x/%04x/%02x, handling failed: %d\n",
+ type, event, context, result);
+ return result;
+}
+
+static void uwbd_event_handle_message(struct uwb_event *evt)
+{
+ struct uwb_rc *rc;
+ int result;
+
+ rc = evt->rc;
+
+ if (evt->message < 0 || evt->message >= ARRAY_SIZE(uwbd_message_handlers)) {
+ dev_err(&rc->uwb_dev.dev, "UWBD: invalid message type %d\n", evt->message);
+ return;
+ }
+
+ result = uwbd_message_handlers[evt->message].handler(evt);
+ if (result < 0)
+ dev_err(&rc->uwb_dev.dev, "UWBD: '%s' message failed: %d\n",
+ uwbd_message_handlers[evt->message].name, result);
+}
+
+static void uwbd_event_handle(struct uwb_event *evt)
+{
+ struct uwb_rc *rc;
+ int should_keep;
+
+ rc = evt->rc;
+
+ if (rc->ready) {
+ switch (evt->type) {
+ case UWB_EVT_TYPE_NOTIF:
+ should_keep = uwbd_event_handle_urc(evt);
+ if (should_keep <= 0)
+ kfree(evt->notif.rceb);
+ break;
+ case UWB_EVT_TYPE_MSG:
+ uwbd_event_handle_message(evt);
+ break;
+ default:
+ dev_err(&rc->uwb_dev.dev, "UWBD: invalid event type %d\n", evt->type);
+ break;
+ }
+ }
+
+ __uwb_rc_put(rc); /* for the __uwb_rc_get() in uwb_rc_notif_cb() */
+}
+
+/**
+ * UWB Daemon
+ *
+ * Listens to all UWB notifications and takes care to track the state
+ * of the UWB neighbourhood for the kernel. When we do a run, we
+ * spinlock, move the list to a private copy and release the
+ * lock. Hold it as little as possible. Not a conflict: it is
+ * guaranteed we own the events in the private list.
+ *
+ * FIXME: should change so we don't have a 1HZ timer all the time, but
+ * only if there are devices.
+ */
+static int uwbd(void *param)
+{
+ struct uwb_rc *rc = param;
+ unsigned long flags;
+ struct uwb_event *evt;
+ int should_stop = 0;
+
+ while (1) {
+ wait_event_interruptible_timeout(
+ rc->uwbd.wq,
+ !list_empty(&rc->uwbd.event_list)
+ || (should_stop = kthread_should_stop()),
+ HZ);
+ if (should_stop)
+ break;
+
+ spin_lock_irqsave(&rc->uwbd.event_list_lock, flags);
+ if (!list_empty(&rc->uwbd.event_list)) {
+ evt = list_first_entry(&rc->uwbd.event_list, struct uwb_event, list_node);
+ list_del(&evt->list_node);
+ } else
+ evt = NULL;
+ spin_unlock_irqrestore(&rc->uwbd.event_list_lock, flags);
+
+ if (evt) {
+ uwbd_event_handle(evt);
+ kfree(evt);
+ }
+
+ uwb_beca_purge(rc); /* Purge devices that left */
+ }
+ return 0;
+}
+
+
+/** Start the UWB daemon */
+void uwbd_start(struct uwb_rc *rc)
+{
+ struct task_struct *task = kthread_run(uwbd, rc, "uwbd");
+ if (IS_ERR(task)) {
+ rc->uwbd.task = NULL;
+ printk(KERN_ERR "UWB: Cannot start management daemon; "
+ "UWB won't work\n");
+ } else {
+ rc->uwbd.task = task;
+ rc->uwbd.pid = rc->uwbd.task->pid;
+ }
+}
+
+/* Stop the UWB daemon and free any unprocessed events */
+void uwbd_stop(struct uwb_rc *rc)
+{
+ if (rc->uwbd.task)
+ kthread_stop(rc->uwbd.task);
+ uwbd_flush(rc);
+}
+
+/*
+ * Queue an event for the management daemon
+ *
+ * When some lower layer receives an event, it uses this function to
+ * push it forward to the UWB daemon.
+ *
+ * Once you pass the event, you don't own it any more, but the daemon
+ * does. It will uwb_event_free() it when done, so make sure you
+ * uwb_event_alloc()ed it or bad things will happen.
+ *
+ * If the daemon is not running, we just free the event.
+ */
+void uwbd_event_queue(struct uwb_event *evt)
+{
+ struct uwb_rc *rc = evt->rc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rc->uwbd.event_list_lock, flags);
+ if (rc->uwbd.pid != 0) {
+ list_add(&evt->list_node, &rc->uwbd.event_list);
+ wake_up_all(&rc->uwbd.wq);
+ } else {
+ __uwb_rc_put(evt->rc);
+ if (evt->type == UWB_EVT_TYPE_NOTIF)
+ kfree(evt->notif.rceb);
+ kfree(evt);
+ }
+ spin_unlock_irqrestore(&rc->uwbd.event_list_lock, flags);
+ return;
+}
+
+void uwbd_flush(struct uwb_rc *rc)
+{
+ struct uwb_event *evt, *nxt;
+
+ spin_lock_irq(&rc->uwbd.event_list_lock);
+ list_for_each_entry_safe(evt, nxt, &rc->uwbd.event_list, list_node) {
+ if (evt->rc == rc) {
+ __uwb_rc_put(rc);
+ list_del(&evt->list_node);
+ if (evt->type == UWB_EVT_TYPE_NOTIF)
+ kfree(evt->notif.rceb);
+ kfree(evt);
+ }
+ }
+ spin_unlock_irq(&rc->uwbd.event_list_lock);
+}
diff --git a/drivers/staging/uwb/whc-rc.c b/drivers/staging/uwb/whc-rc.c
new file mode 100644
index 000000000000..34020ed351ab
--- /dev/null
+++ b/drivers/staging/uwb/whc-rc.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Wireless Host Controller: Radio Control Interface (WHCI v0.95[2.3])
+ * Radio Control command/event transport to the UWB stack
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * Initialize and hook up the Radio Control interface.
+ *
+ * For each device probed, creates an 'struct whcrc' which contains
+ * just the representation of the UWB Radio Controller, and the logic
+ * for reading notifications and passing them to the UWB Core.
+ *
+ * So we initialize all of those, register the UWB Radio Controller
+ * and setup the notification/event handle to pipe the notifications
+ * to the UWB management Daemon.
+ *
+ * Once uwb_rc_add() is called, the UWB stack takes control, resets
+ * the radio and readies the device to take commands the UWB
+ * API/user-space.
+ *
+ * Note this driver is just a transport driver; the commands are
+ * formed at the UWB stack and given to this driver who will deliver
+ * them to the hw and transfer the replies/notifications back to the
+ * UWB stack through the UWB daemon (UWBD).
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include "uwb.h"
+#include "include/whci.h"
+#include "include/umc.h"
+
+#include "uwb-internal.h"
+
+/**
+ * Descriptor for an instance of the UWB Radio Control Driver that
+ * attaches to the URC interface of the WHCI PCI card.
+ *
+ * Unless there is a lock specific to the 'data members', all access
+ * is protected by uwb_rc->mutex.
+ */
+struct whcrc {
+ struct umc_dev *umc_dev;
+ struct uwb_rc *uwb_rc; /* UWB host controller */
+
+ unsigned long area;
+ void __iomem *rc_base;
+ size_t rc_len;
+ spinlock_t irq_lock;
+
+ void *evt_buf, *cmd_buf;
+ dma_addr_t evt_dma_buf, cmd_dma_buf;
+ wait_queue_head_t cmd_wq;
+ struct work_struct event_work;
+};
+
+/**
+ * Execute an UWB RC command on WHCI/RC
+ *
+ * @rc: Instance of a Radio Controller that is a whcrc
+ * @cmd: Buffer containing the RCCB and payload to execute
+ * @cmd_size: Size of the command buffer.
+ *
+ * We copy the command into whcrc->cmd_buf (as it is pretty and
+ * aligned`and physically contiguous) and then press the right keys in
+ * the controller's URCCMD register to get it to read it. We might
+ * have to wait for the cmd_sem to be open to us.
+ *
+ * NOTE: rc's mutex has to be locked
+ */
+static int whcrc_cmd(struct uwb_rc *uwb_rc,
+ const struct uwb_rccb *cmd, size_t cmd_size)
+{
+ int result = 0;
+ struct whcrc *whcrc = uwb_rc->priv;
+ struct device *dev = &whcrc->umc_dev->dev;
+ u32 urccmd;
+
+ if (cmd_size >= 4096)
+ return -EINVAL;
+
+ /*
+ * If the URC is halted, then the hardware has reset itself.
+ * Attempt to recover by restarting the device and then return
+ * an error as it's likely that the current command isn't
+ * valid for a newly started RC.
+ */
+ if (le_readl(whcrc->rc_base + URCSTS) & URCSTS_HALTED) {
+ dev_err(dev, "requesting reset of halted radio controller\n");
+ uwb_rc_reset_all(uwb_rc);
+ return -EIO;
+ }
+
+ result = wait_event_timeout(whcrc->cmd_wq,
+ !(le_readl(whcrc->rc_base + URCCMD) & URCCMD_ACTIVE), HZ/2);
+ if (result == 0) {
+ dev_err(dev, "device is not ready to execute commands\n");
+ return -ETIMEDOUT;
+ }
+
+ memmove(whcrc->cmd_buf, cmd, cmd_size);
+ le_writeq(whcrc->cmd_dma_buf, whcrc->rc_base + URCCMDADDR);
+
+ spin_lock(&whcrc->irq_lock);
+ urccmd = le_readl(whcrc->rc_base + URCCMD);
+ urccmd &= ~(URCCMD_EARV | URCCMD_SIZE_MASK);
+ le_writel(urccmd | URCCMD_ACTIVE | URCCMD_IWR | cmd_size,
+ whcrc->rc_base + URCCMD);
+ spin_unlock(&whcrc->irq_lock);
+
+ return 0;
+}
+
+static int whcrc_reset(struct uwb_rc *rc)
+{
+ struct whcrc *whcrc = rc->priv;
+
+ return umc_controller_reset(whcrc->umc_dev);
+}
+
+/**
+ * Reset event reception mechanism and tell hw we are ready to get more
+ *
+ * We have read all the events in the event buffer, so we are ready to
+ * reset it to the beginning.
+ *
+ * This is only called during initialization or after an event buffer
+ * has been retired. This means we can be sure that event processing
+ * is disabled and it's safe to update the URCEVTADDR register.
+ *
+ * There's no need to wait for the event processing to start as the
+ * URC will not clear URCCMD_ACTIVE until (internal) event buffer
+ * space is available.
+ */
+static
+void whcrc_enable_events(struct whcrc *whcrc)
+{
+ u32 urccmd;
+
+ le_writeq(whcrc->evt_dma_buf, whcrc->rc_base + URCEVTADDR);
+
+ spin_lock(&whcrc->irq_lock);
+ urccmd = le_readl(whcrc->rc_base + URCCMD) & ~URCCMD_ACTIVE;
+ le_writel(urccmd | URCCMD_EARV, whcrc->rc_base + URCCMD);
+ spin_unlock(&whcrc->irq_lock);
+}
+
+static void whcrc_event_work(struct work_struct *work)
+{
+ struct whcrc *whcrc = container_of(work, struct whcrc, event_work);
+ size_t size;
+ u64 urcevtaddr;
+
+ urcevtaddr = le_readq(whcrc->rc_base + URCEVTADDR);
+ size = urcevtaddr & URCEVTADDR_OFFSET_MASK;
+
+ uwb_rc_neh_grok(whcrc->uwb_rc, whcrc->evt_buf, size);
+ whcrc_enable_events(whcrc);
+}
+
+/**
+ * Catch interrupts?
+ *
+ * We ack inmediately (and expect the hw to do the right thing and
+ * raise another IRQ if things have changed :)
+ */
+static
+irqreturn_t whcrc_irq_cb(int irq, void *_whcrc)
+{
+ struct whcrc *whcrc = _whcrc;
+ struct device *dev = &whcrc->umc_dev->dev;
+ u32 urcsts;
+
+ urcsts = le_readl(whcrc->rc_base + URCSTS);
+ if (!(urcsts & URCSTS_INT_MASK))
+ return IRQ_NONE;
+ le_writel(urcsts & URCSTS_INT_MASK, whcrc->rc_base + URCSTS);
+
+ if (urcsts & URCSTS_HSE) {
+ dev_err(dev, "host system error -- hardware halted\n");
+ /* FIXME: do something sensible here */
+ goto out;
+ }
+ if (urcsts & URCSTS_ER)
+ schedule_work(&whcrc->event_work);
+ if (urcsts & URCSTS_RCI)
+ wake_up_all(&whcrc->cmd_wq);
+out:
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * Initialize a UMC RC interface: map regions, get (shared) IRQ
+ */
+static
+int whcrc_setup_rc_umc(struct whcrc *whcrc)
+{
+ int result = 0;
+ struct device *dev = &whcrc->umc_dev->dev;
+ struct umc_dev *umc_dev = whcrc->umc_dev;
+
+ whcrc->area = umc_dev->resource.start;
+ whcrc->rc_len = resource_size(&umc_dev->resource);
+ result = -EBUSY;
+ if (request_mem_region(whcrc->area, whcrc->rc_len, KBUILD_MODNAME) == NULL) {
+ dev_err(dev, "can't request URC region (%zu bytes @ 0x%lx): %d\n",
+ whcrc->rc_len, whcrc->area, result);
+ goto error_request_region;
+ }
+
+ whcrc->rc_base = ioremap_nocache(whcrc->area, whcrc->rc_len);
+ if (whcrc->rc_base == NULL) {
+ dev_err(dev, "can't ioremap registers (%zu bytes @ 0x%lx): %d\n",
+ whcrc->rc_len, whcrc->area, result);
+ goto error_ioremap_nocache;
+ }
+
+ result = request_irq(umc_dev->irq, whcrc_irq_cb, IRQF_SHARED,
+ KBUILD_MODNAME, whcrc);
+ if (result < 0) {
+ dev_err(dev, "can't allocate IRQ %d: %d\n",
+ umc_dev->irq, result);
+ goto error_request_irq;
+ }
+
+ result = -ENOMEM;
+ whcrc->cmd_buf = dma_alloc_coherent(&umc_dev->dev, PAGE_SIZE,
+ &whcrc->cmd_dma_buf, GFP_KERNEL);
+ if (whcrc->cmd_buf == NULL) {
+ dev_err(dev, "Can't allocate cmd transfer buffer\n");
+ goto error_cmd_buffer;
+ }
+
+ whcrc->evt_buf = dma_alloc_coherent(&umc_dev->dev, PAGE_SIZE,
+ &whcrc->evt_dma_buf, GFP_KERNEL);
+ if (whcrc->evt_buf == NULL) {
+ dev_err(dev, "Can't allocate evt transfer buffer\n");
+ goto error_evt_buffer;
+ }
+ return 0;
+
+error_evt_buffer:
+ dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->cmd_buf,
+ whcrc->cmd_dma_buf);
+error_cmd_buffer:
+ free_irq(umc_dev->irq, whcrc);
+error_request_irq:
+ iounmap(whcrc->rc_base);
+error_ioremap_nocache:
+ release_mem_region(whcrc->area, whcrc->rc_len);
+error_request_region:
+ return result;
+}
+
+
+/**
+ * Release RC's UMC resources
+ */
+static
+void whcrc_release_rc_umc(struct whcrc *whcrc)
+{
+ struct umc_dev *umc_dev = whcrc->umc_dev;
+
+ dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->evt_buf,
+ whcrc->evt_dma_buf);
+ dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->cmd_buf,
+ whcrc->cmd_dma_buf);
+ free_irq(umc_dev->irq, whcrc);
+ iounmap(whcrc->rc_base);
+ release_mem_region(whcrc->area, whcrc->rc_len);
+}
+
+
+/**
+ * whcrc_start_rc - start a WHCI radio controller
+ * @whcrc: the radio controller to start
+ *
+ * Reset the UMC device, start the radio controller, enable events and
+ * finally enable interrupts.
+ */
+static int whcrc_start_rc(struct uwb_rc *rc)
+{
+ struct whcrc *whcrc = rc->priv;
+ struct device *dev = &whcrc->umc_dev->dev;
+
+ /* Reset the thing */
+ le_writel(URCCMD_RESET, whcrc->rc_base + URCCMD);
+ if (whci_wait_for(dev, whcrc->rc_base + URCCMD, URCCMD_RESET, 0,
+ 5000, "hardware reset") < 0)
+ return -EBUSY;
+
+ /* Set the event buffer, start the controller (enable IRQs later) */
+ le_writel(0, whcrc->rc_base + URCINTR);
+ le_writel(URCCMD_RS, whcrc->rc_base + URCCMD);
+ if (whci_wait_for(dev, whcrc->rc_base + URCSTS, URCSTS_HALTED, 0,
+ 5000, "radio controller start") < 0)
+ return -ETIMEDOUT;
+ whcrc_enable_events(whcrc);
+ le_writel(URCINTR_EN_ALL, whcrc->rc_base + URCINTR);
+ return 0;
+}
+
+
+/**
+ * whcrc_stop_rc - stop a WHCI radio controller
+ * @whcrc: the radio controller to stop
+ *
+ * Disable interrupts and cancel any pending event processing work
+ * before clearing the Run/Stop bit.
+ */
+static
+void whcrc_stop_rc(struct uwb_rc *rc)
+{
+ struct whcrc *whcrc = rc->priv;
+ struct umc_dev *umc_dev = whcrc->umc_dev;
+
+ le_writel(0, whcrc->rc_base + URCINTR);
+ cancel_work_sync(&whcrc->event_work);
+
+ le_writel(0, whcrc->rc_base + URCCMD);
+ whci_wait_for(&umc_dev->dev, whcrc->rc_base + URCSTS,
+ URCSTS_HALTED, URCSTS_HALTED, 100, "radio controller stop");
+}
+
+static void whcrc_init(struct whcrc *whcrc)
+{
+ spin_lock_init(&whcrc->irq_lock);
+ init_waitqueue_head(&whcrc->cmd_wq);
+ INIT_WORK(&whcrc->event_work, whcrc_event_work);
+}
+
+/**
+ * Initialize the radio controller.
+ *
+ * NOTE: we setup whcrc->uwb_rc before calling uwb_rc_add(); in the
+ * IRQ handler we use that to determine if the hw is ready to
+ * handle events. Looks like a race condition, but it really is
+ * not.
+ */
+static
+int whcrc_probe(struct umc_dev *umc_dev)
+{
+ int result;
+ struct uwb_rc *uwb_rc;
+ struct whcrc *whcrc;
+ struct device *dev = &umc_dev->dev;
+
+ result = -ENOMEM;
+ uwb_rc = uwb_rc_alloc();
+ if (uwb_rc == NULL) {
+ dev_err(dev, "unable to allocate RC instance\n");
+ goto error_rc_alloc;
+ }
+ whcrc = kzalloc(sizeof(*whcrc), GFP_KERNEL);
+ if (whcrc == NULL) {
+ dev_err(dev, "unable to allocate WHC-RC instance\n");
+ goto error_alloc;
+ }
+ whcrc_init(whcrc);
+ whcrc->umc_dev = umc_dev;
+
+ result = whcrc_setup_rc_umc(whcrc);
+ if (result < 0) {
+ dev_err(dev, "Can't setup RC UMC interface: %d\n", result);
+ goto error_setup_rc_umc;
+ }
+ whcrc->uwb_rc = uwb_rc;
+
+ uwb_rc->owner = THIS_MODULE;
+ uwb_rc->cmd = whcrc_cmd;
+ uwb_rc->reset = whcrc_reset;
+ uwb_rc->start = whcrc_start_rc;
+ uwb_rc->stop = whcrc_stop_rc;
+
+ result = uwb_rc_add(uwb_rc, dev, whcrc);
+ if (result < 0)
+ goto error_rc_add;
+ umc_set_drvdata(umc_dev, whcrc);
+ return 0;
+
+error_rc_add:
+ whcrc_release_rc_umc(whcrc);
+error_setup_rc_umc:
+ kfree(whcrc);
+error_alloc:
+ uwb_rc_put(uwb_rc);
+error_rc_alloc:
+ return result;
+}
+
+/**
+ * Clean up the radio control resources
+ *
+ * When we up the command semaphore, everybody possibly held trying to
+ * execute a command should be granted entry and then they'll see the
+ * host is quiescing and up it (so it will chain to the next waiter).
+ * This should not happen (in any case), as we can only remove when
+ * there are no handles open...
+ */
+static void whcrc_remove(struct umc_dev *umc_dev)
+{
+ struct whcrc *whcrc = umc_get_drvdata(umc_dev);
+ struct uwb_rc *uwb_rc = whcrc->uwb_rc;
+
+ umc_set_drvdata(umc_dev, NULL);
+ uwb_rc_rm(uwb_rc);
+ whcrc_release_rc_umc(whcrc);
+ kfree(whcrc);
+ uwb_rc_put(uwb_rc);
+}
+
+static int whcrc_pre_reset(struct umc_dev *umc)
+{
+ struct whcrc *whcrc = umc_get_drvdata(umc);
+ struct uwb_rc *uwb_rc = whcrc->uwb_rc;
+
+ uwb_rc_pre_reset(uwb_rc);
+ return 0;
+}
+
+static int whcrc_post_reset(struct umc_dev *umc)
+{
+ struct whcrc *whcrc = umc_get_drvdata(umc);
+ struct uwb_rc *uwb_rc = whcrc->uwb_rc;
+
+ return uwb_rc_post_reset(uwb_rc);
+}
+
+/* PCI device ID's that we handle [so it gets loaded] */
+static struct pci_device_id __used whcrc_id_table[] = {
+ { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
+ { /* empty last entry */ }
+};
+MODULE_DEVICE_TABLE(pci, whcrc_id_table);
+
+static struct umc_driver whcrc_driver = {
+ .name = "whc-rc",
+ .cap_id = UMC_CAP_ID_WHCI_RC,
+ .probe = whcrc_probe,
+ .remove = whcrc_remove,
+ .pre_reset = whcrc_pre_reset,
+ .post_reset = whcrc_post_reset,
+};
+
+static int __init whcrc_driver_init(void)
+{
+ return umc_driver_register(&whcrc_driver);
+}
+module_init(whcrc_driver_init);
+
+static void __exit whcrc_driver_exit(void)
+{
+ umc_driver_unregister(&whcrc_driver);
+}
+module_exit(whcrc_driver_exit);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Wireless Host Controller Radio Control Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/uwb/whci.c b/drivers/staging/uwb/whci.c
new file mode 100644
index 000000000000..a8832f64d708
--- /dev/null
+++ b/drivers/staging/uwb/whci.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * WHCI UWB Multi-interface Controller enumerator.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include "include/whci.h"
+#include "include/umc.h"
+
+struct whci_card {
+ struct pci_dev *pci;
+ void __iomem *uwbbase;
+ u8 n_caps;
+ struct umc_dev *devs[0];
+};
+
+
+/* Fix faulty HW :( */
+static
+u64 whci_capdata_quirks(struct whci_card *card, u64 capdata)
+{
+ u64 capdata_orig = capdata;
+ struct pci_dev *pci_dev = card->pci;
+ if (pci_dev->vendor == PCI_VENDOR_ID_INTEL
+ && (pci_dev->device == 0x0c3b || pci_dev->device == 0004)
+ && pci_dev->class == 0x0d1010) {
+ switch (UWBCAPDATA_TO_CAP_ID(capdata)) {
+ /* WLP capability has 0x100 bytes of aperture */
+ case 0x80:
+ capdata |= 0x40 << 8; break;
+ /* WUSB capability has 0x80 bytes of aperture
+ * and ID is 1 */
+ case 0x02:
+ capdata &= ~0xffff;
+ capdata |= 0x2001;
+ break;
+ }
+ }
+ if (capdata_orig != capdata)
+ dev_warn(&pci_dev->dev,
+ "PCI v%04x d%04x c%06x#%02x: "
+ "corrected capdata from %016Lx to %016Lx\n",
+ pci_dev->vendor, pci_dev->device, pci_dev->class,
+ (unsigned)UWBCAPDATA_TO_CAP_ID(capdata),
+ (unsigned long long)capdata_orig,
+ (unsigned long long)capdata);
+ return capdata;
+}
+
+
+/**
+ * whci_wait_for - wait for a WHCI register to be set
+ *
+ * Polls (for at most @max_ms ms) until '*@reg & @mask == @result'.
+ */
+int whci_wait_for(struct device *dev, u32 __iomem *reg, u32 mask, u32 result,
+ unsigned long max_ms, const char *tag)
+{
+ unsigned t = 0;
+ u32 val;
+ for (;;) {
+ val = le_readl(reg);
+ if ((val & mask) == result)
+ break;
+ if (t >= max_ms) {
+ dev_err(dev, "%s timed out\n", tag);
+ return -ETIMEDOUT;
+ }
+ msleep(10);
+ t += 10;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(whci_wait_for);
+
+
+/*
+ * NOTE: the capinfo and capdata registers are slightly different
+ * (size and cap-id fields). So for cap #0, we need to fill
+ * in. Size comes from the size of the register block
+ * (statically calculated); cap_id comes from nowhere, we use
+ * zero, that is reserved, for the radio controller, because
+ * none was defined at the spec level.
+ */
+static int whci_add_cap(struct whci_card *card, int n)
+{
+ struct umc_dev *umc;
+ u64 capdata;
+ int bar, err;
+
+ umc = umc_device_create(&card->pci->dev, n);
+ if (umc == NULL)
+ return -ENOMEM;
+
+ capdata = le_readq(card->uwbbase + UWBCAPDATA(n));
+
+ bar = UWBCAPDATA_TO_BAR(capdata) << 1;
+
+ capdata = whci_capdata_quirks(card, capdata);
+ /* Capability 0 is the radio controller. It's size is 32
+ * bytes (WHCI0.95[2.3, T2-9]). */
+ umc->version = UWBCAPDATA_TO_VERSION(capdata);
+ umc->cap_id = n == 0 ? 0 : UWBCAPDATA_TO_CAP_ID(capdata);
+ umc->bar = bar;
+ umc->resource.start = pci_resource_start(card->pci, bar)
+ + UWBCAPDATA_TO_OFFSET(capdata);
+ umc->resource.end = umc->resource.start
+ + (n == 0 ? 0x20 : UWBCAPDATA_TO_SIZE(capdata)) - 1;
+ umc->resource.name = dev_name(&umc->dev);
+ umc->resource.flags = card->pci->resource[bar].flags;
+ umc->resource.parent = &card->pci->resource[bar];
+ umc->irq = card->pci->irq;
+
+ err = umc_device_register(umc);
+ if (err < 0)
+ goto error;
+ card->devs[n] = umc;
+ return 0;
+
+error:
+ kfree(umc);
+ return err;
+}
+
+static void whci_del_cap(struct whci_card *card, int n)
+{
+ struct umc_dev *umc = card->devs[n];
+
+ umc_device_unregister(umc);
+}
+
+static int whci_n_caps(struct pci_dev *pci)
+{
+ void __iomem *uwbbase;
+ u64 capinfo;
+
+ uwbbase = pci_iomap(pci, 0, 8);
+ if (!uwbbase)
+ return -ENOMEM;
+ capinfo = le_readq(uwbbase + UWBCAPINFO);
+ pci_iounmap(pci, uwbbase);
+
+ return UWBCAPINFO_TO_N_CAPS(capinfo);
+}
+
+static int whci_probe(struct pci_dev *pci, const struct pci_device_id *id)
+{
+ struct whci_card *card;
+ int err, n_caps, n;
+
+ err = pci_enable_device(pci);
+ if (err < 0)
+ goto error;
+ pci_enable_msi(pci);
+ pci_set_master(pci);
+ err = -ENXIO;
+ if (!pci_set_dma_mask(pci, DMA_BIT_MASK(64)))
+ pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(64));
+ else if (!pci_set_dma_mask(pci, DMA_BIT_MASK(32)))
+ pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32));
+ else
+ goto error_dma;
+
+ err = n_caps = whci_n_caps(pci);
+ if (n_caps < 0)
+ goto error_ncaps;
+
+ err = -ENOMEM;
+ card = kzalloc(sizeof(struct whci_card)
+ + sizeof(struct umc_dev *) * (n_caps + 1),
+ GFP_KERNEL);
+ if (card == NULL)
+ goto error_kzalloc;
+ card->pci = pci;
+ card->n_caps = n_caps;
+
+ err = -EBUSY;
+ if (!request_mem_region(pci_resource_start(pci, 0),
+ UWBCAPDATA_SIZE(card->n_caps),
+ "whci (capability data)"))
+ goto error_request_memregion;
+ err = -ENOMEM;
+ card->uwbbase = pci_iomap(pci, 0, UWBCAPDATA_SIZE(card->n_caps));
+ if (!card->uwbbase)
+ goto error_iomap;
+
+ /* Add each capability. */
+ for (n = 0; n <= card->n_caps; n++) {
+ err = whci_add_cap(card, n);
+ if (err < 0 && n == 0) {
+ dev_err(&pci->dev, "cannot bind UWB radio controller:"
+ " %d\n", err);
+ goto error_bind;
+ }
+ if (err < 0)
+ dev_warn(&pci->dev, "warning: cannot bind capability "
+ "#%u: %d\n", n, err);
+ }
+ pci_set_drvdata(pci, card);
+ return 0;
+
+error_bind:
+ pci_iounmap(pci, card->uwbbase);
+error_iomap:
+ release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps));
+error_request_memregion:
+ kfree(card);
+error_kzalloc:
+error_ncaps:
+error_dma:
+ pci_disable_msi(pci);
+ pci_disable_device(pci);
+error:
+ return err;
+}
+
+static void whci_remove(struct pci_dev *pci)
+{
+ struct whci_card *card = pci_get_drvdata(pci);
+ int n;
+
+ pci_set_drvdata(pci, NULL);
+ /* Unregister each capability in reverse (so the master device
+ * is unregistered last). */
+ for (n = card->n_caps; n >= 0 ; n--)
+ whci_del_cap(card, n);
+ pci_iounmap(pci, card->uwbbase);
+ release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps));
+ kfree(card);
+ pci_disable_msi(pci);
+ pci_disable_device(pci);
+}
+
+static struct pci_device_id whci_id_table[] = {
+ { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
+ { 0 },
+};
+MODULE_DEVICE_TABLE(pci, whci_id_table);
+
+
+static struct pci_driver whci_driver = {
+ .name = "whci",
+ .id_table = whci_id_table,
+ .probe = whci_probe,
+ .remove = whci_remove,
+};
+
+module_pci_driver(whci_driver);
+MODULE_DESCRIPTION("WHCI UWB Multi-interface Controller enumerator");
+MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c
index ea54cc27e645..d4d1e44b16b2 100644
--- a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c
+++ b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c
@@ -75,34 +75,27 @@ static const struct v4l2_fract
/* video formats */
static struct mmal_fmt formats[] = {
{
- .name = "4:2:0, planar, YUV",
.fourcc = V4L2_PIX_FMT_YUV420,
- .flags = 0,
.mmal = MMAL_ENCODING_I420,
.depth = 12,
.mmal_component = COMP_CAMERA,
.ybbp = 1,
.remove_padding = 1,
}, {
- .name = "4:2:2, packed, YUYV",
.fourcc = V4L2_PIX_FMT_YUYV,
- .flags = 0,
.mmal = MMAL_ENCODING_YUYV,
.depth = 16,
.mmal_component = COMP_CAMERA,
.ybbp = 2,
.remove_padding = 0,
}, {
- .name = "RGB24 (LE)",
.fourcc = V4L2_PIX_FMT_RGB24,
- .flags = 0,
.mmal = MMAL_ENCODING_RGB24,
.depth = 24,
.mmal_component = COMP_CAMERA,
.ybbp = 3,
.remove_padding = 0,
}, {
- .name = "JPEG",
.fourcc = V4L2_PIX_FMT_JPEG,
.flags = V4L2_FMT_FLAG_COMPRESSED,
.mmal = MMAL_ENCODING_JPEG,
@@ -111,7 +104,6 @@ static struct mmal_fmt formats[] = {
.ybbp = 0,
.remove_padding = 0,
}, {
- .name = "H264",
.fourcc = V4L2_PIX_FMT_H264,
.flags = V4L2_FMT_FLAG_COMPRESSED,
.mmal = MMAL_ENCODING_H264,
@@ -120,7 +112,6 @@ static struct mmal_fmt formats[] = {
.ybbp = 0,
.remove_padding = 0,
}, {
- .name = "MJPEG",
.fourcc = V4L2_PIX_FMT_MJPEG,
.flags = V4L2_FMT_FLAG_COMPRESSED,
.mmal = MMAL_ENCODING_MJPEG,
@@ -129,72 +120,56 @@ static struct mmal_fmt formats[] = {
.ybbp = 0,
.remove_padding = 0,
}, {
- .name = "4:2:2, packed, YVYU",
.fourcc = V4L2_PIX_FMT_YVYU,
- .flags = 0,
.mmal = MMAL_ENCODING_YVYU,
.depth = 16,
.mmal_component = COMP_CAMERA,
.ybbp = 2,
.remove_padding = 0,
}, {
- .name = "4:2:2, packed, VYUY",
.fourcc = V4L2_PIX_FMT_VYUY,
- .flags = 0,
.mmal = MMAL_ENCODING_VYUY,
.depth = 16,
.mmal_component = COMP_CAMERA,
.ybbp = 2,
.remove_padding = 0,
}, {
- .name = "4:2:2, packed, UYVY",
.fourcc = V4L2_PIX_FMT_UYVY,
- .flags = 0,
.mmal = MMAL_ENCODING_UYVY,
.depth = 16,
.mmal_component = COMP_CAMERA,
.ybbp = 2,
.remove_padding = 0,
}, {
- .name = "4:2:0, planar, NV12",
.fourcc = V4L2_PIX_FMT_NV12,
- .flags = 0,
.mmal = MMAL_ENCODING_NV12,
.depth = 12,
.mmal_component = COMP_CAMERA,
.ybbp = 1,
.remove_padding = 1,
}, {
- .name = "RGB24 (BE)",
.fourcc = V4L2_PIX_FMT_BGR24,
- .flags = 0,
.mmal = MMAL_ENCODING_BGR24,
.depth = 24,
.mmal_component = COMP_CAMERA,
.ybbp = 3,
.remove_padding = 0,
}, {
- .name = "4:2:0, planar, YVU",
.fourcc = V4L2_PIX_FMT_YVU420,
- .flags = 0,
.mmal = MMAL_ENCODING_YV12,
.depth = 12,
.mmal_component = COMP_CAMERA,
.ybbp = 1,
.remove_padding = 1,
}, {
- .name = "4:2:0, planar, NV21",
.fourcc = V4L2_PIX_FMT_NV21,
- .flags = 0,
.mmal = MMAL_ENCODING_NV21,
.depth = 12,
.mmal_component = COMP_CAMERA,
.ybbp = 1,
.remove_padding = 1,
}, {
- .name = "RGB32 (BE)",
.fourcc = V4L2_PIX_FMT_BGR32,
- .flags = 0,
.mmal = MMAL_ENCODING_BGRA,
.depth = 32,
.mmal_component = COMP_CAMERA,
@@ -716,9 +691,7 @@ static int vidioc_enum_fmt_vid_overlay(struct file *file, void *priv,
fmt = &formats[f->index];
- strlcpy((char *)f->description, fmt->name, sizeof(f->description));
f->pixelformat = fmt->fourcc;
- f->flags = fmt->flags;
return 0;
}
@@ -919,9 +892,7 @@ static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
fmt = &formats[f->index];
- strlcpy((char *)f->description, fmt->name, sizeof(f->description));
f->pixelformat = fmt->fourcc;
- f->flags = fmt->flags;
return 0;
}
diff --git a/drivers/staging/vc04_services/bcm2835-camera/mmal-common.h b/drivers/staging/vc04_services/bcm2835-camera/mmal-common.h
index 6f56c517d850..ff5398737b4a 100644
--- a/drivers/staging/vc04_services/bcm2835-camera/mmal-common.h
+++ b/drivers/staging/vc04_services/bcm2835-camera/mmal-common.h
@@ -26,7 +26,6 @@ struct mmal_msg_context;
/* mapping between v4l and mmal video modes */
struct mmal_fmt {
- char *name;
u32 fourcc; /* v4l2 format id */
int flags; /* v4l2 flags field */
u32 mmal;
diff --git a/drivers/staging/wusbcore/Documentation/wusb-cbaf b/drivers/staging/wusbcore/Documentation/wusb-cbaf
new file mode 100644
index 000000000000..8b3d43efce90
--- /dev/null
+++ b/drivers/staging/wusbcore/Documentation/wusb-cbaf
@@ -0,0 +1,130 @@
+#! /bin/bash
+#
+
+set -e
+
+progname=$(basename $0)
+function help
+{
+ cat <<EOF
+Usage: $progname COMMAND DEVICEs [ARGS]
+
+Command for manipulating the pairing/authentication credentials of a
+Wireless USB device that supports wired-mode Cable-Based-Association.
+
+Works in conjunction with the wusb-cba.ko driver from http://linuxuwb.org.
+
+
+DEVICE
+
+ sysfs path to the device to authenticate; for example, both this
+ guys are the same:
+
+ /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-4/1-4.4/1-4.4:1.1
+ /sys/bus/usb/drivers/wusb-cbaf/1-4.4:1.1
+
+COMMAND/ARGS are
+
+ start
+
+ Start a WUSB host controller (by setting up a CHID)
+
+ set-chid DEVICE HOST-CHID HOST-BANDGROUP HOST-NAME
+
+ Sets host information in the device; after this you can call the
+ get-cdid to see how does this device report itself to us.
+
+ get-cdid DEVICE
+
+ Get the device ID associated to the HOST-CHID we sent with
+ 'set-chid'. We might not know about it.
+
+ set-cc DEVICE
+
+ If we allow the device to connect, set a random new CDID and CK
+ (connection key). Device saves them for the next time it wants to
+ connect wireless. We save them for that next time also so we can
+ authenticate the device (when we see the CDID he uses to id
+ itself) and the CK to crypto talk to it.
+
+CHID is always 16 hex bytes in 'XX YY ZZ...' form
+BANDGROUP is almost always 0001
+
+Examples:
+
+ You can default most arguments to '' to get a sane value:
+
+ $ $progname set-chid '' '' '' "My host name"
+
+ A full sequence:
+
+ $ $progname set-chid '' '' '' "My host name"
+ $ $progname get-cdid ''
+ $ $progname set-cc ''
+
+EOF
+}
+
+
+# Defaults
+# FIXME: CHID should come from a database :), band group from the host
+host_CHID="00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"
+host_band_group="0001"
+host_name=$(hostname)
+
+devs="$(echo /sys/bus/usb/drivers/wusb-cbaf/[0-9]*)"
+hdevs="$(for h in /sys/class/uwb_rc/*/wusbhc; do readlink -f $h; done)"
+
+result=0
+case $1 in
+ start)
+ for dev in ${2:-$hdevs}
+ do
+ echo $host_CHID > $dev/wusb_chid
+ echo I: started host $(basename $dev) >&2
+ done
+ ;;
+ stop)
+ for dev in ${2:-$hdevs}
+ do
+ echo 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 > $dev/wusb_chid
+ echo I: stopped host $(basename $dev) >&2
+ done
+ ;;
+ set-chid)
+ shift
+ for dev in ${2:-$devs}; do
+ echo "${4:-$host_name}" > $dev/wusb_host_name
+ echo "${3:-$host_band_group}" > $dev/wusb_host_band_groups
+ echo ${2:-$host_CHID} > $dev/wusb_chid
+ done
+ ;;
+ get-cdid)
+ for dev in ${2:-$devs}
+ do
+ cat $dev/wusb_cdid
+ done
+ ;;
+ set-cc)
+ for dev in ${2:-$devs}; do
+ shift
+ CDID="$(head --bytes=16 /dev/urandom | od -tx1 -An)"
+ CK="$(head --bytes=16 /dev/urandom | od -tx1 -An)"
+ echo "$CDID" > $dev/wusb_cdid
+ echo "$CK" > $dev/wusb_ck
+
+ echo I: CC set >&2
+ echo "CHID: $(cat $dev/wusb_chid)"
+ echo "CDID:$CDID"
+ echo "CK: $CK"
+ done
+ ;;
+ help|h|--help|-h)
+ help
+ ;;
+ *)
+ echo "E: Unknown usage" 1>&2
+ help 1>&2
+ result=1
+esac
+exit $result
diff --git a/drivers/staging/wusbcore/Documentation/wusb-design-overview.rst b/drivers/staging/wusbcore/Documentation/wusb-design-overview.rst
new file mode 100644
index 000000000000..dc5e21609bb5
--- /dev/null
+++ b/drivers/staging/wusbcore/Documentation/wusb-design-overview.rst
@@ -0,0 +1,457 @@
+================================
+Linux UWB + Wireless USB + WiNET
+================================
+
+ Copyright (C) 2005-2006 Intel Corporation
+
+ Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License version
+ 2 as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+
+
+Please visit http://bughost.org/thewiki/Design-overview.txt-1.8 for
+updated content.
+
+ * Design-overview.txt-1.8
+
+This code implements a Ultra Wide Band stack for Linux, as well as
+drivers for the USB based UWB radio controllers defined in the
+Wireless USB 1.0 specification (including Wireless USB host controller
+and an Intel WiNET controller).
+
+.. Contents
+ 1. Introduction
+ 1. HWA: Host Wire adapters, your Wireless USB dongle
+
+ 2. DWA: Device Wired Adaptor, a Wireless USB hub for wired
+ devices
+ 3. WHCI: Wireless Host Controller Interface, the PCI WUSB host
+ adapter
+ 2. The UWB stack
+ 1. Devices and hosts: the basic structure
+
+ 2. Host Controller life cycle
+
+ 3. On the air: beacons and enumerating the radio neighborhood
+
+ 4. Device lists
+ 5. Bandwidth allocation
+
+ 3. Wireless USB Host Controller drivers
+
+ 4. Glossary
+
+
+Introduction
+============
+
+UWB is a wide-band communication protocol that is to serve also as the
+low-level protocol for others (much like TCP sits on IP). Currently
+these others are Wireless USB and TCP/IP, but seems Bluetooth and
+Firewire/1394 are coming along.
+
+UWB uses a band from roughly 3 to 10 GHz, transmitting at a max of
+~-41dB (or 0.074 uW/MHz--geography specific data is still being
+negotiated w/ regulators, so watch for changes). That band is divided in
+a bunch of ~1.5 GHz wide channels (or band groups) composed of three
+subbands/subchannels (528 MHz each). Each channel is independent of each
+other, so you could consider them different "busses". Initially this
+driver considers them all a single one.
+
+Radio time is divided in 65536 us long /superframes/, each one divided
+in 256 256us long /MASs/ (Media Allocation Slots), which are the basic
+time/media allocation units for transferring data. At the beginning of
+each superframe there is a Beacon Period (BP), where every device
+transmit its beacon on a single MAS. The length of the BP depends on how
+many devices are present and the length of their beacons.
+
+Devices have a MAC (fixed, 48 bit address) and a device (changeable, 16
+bit address) and send periodic beacons to advertise themselves and pass
+info on what they are and do. They advertise their capabilities and a
+bunch of other stuff.
+
+The different logical parts of this driver are:
+
+ *
+
+ *UWB*: the Ultra-Wide-Band stack -- manages the radio and
+ associated spectrum to allow for devices sharing it. Allows to
+ control bandwidth assignment, beaconing, scanning, etc
+
+ *
+
+ *WUSB*: the layer that sits on top of UWB to provide Wireless USB.
+ The Wireless USB spec defines means to control a UWB radio and to
+ do the actual WUSB.
+
+
+HWA: Host Wire adapters, your Wireless USB dongle
+-------------------------------------------------
+
+WUSB also defines a device called a Host Wire Adaptor (HWA), which in
+mere terms is a USB dongle that enables your PC to have UWB and Wireless
+USB. The Wireless USB Host Controller in a HWA looks to the host like a
+[Wireless] USB controller connected via USB (!)
+
+The HWA itself is broken in two or three main interfaces:
+
+ *
+
+ *RC*: Radio control -- this implements an interface to the
+ Ultra-Wide-Band radio controller. The driver for this implements a
+ USB-based UWB Radio Controller to the UWB stack.
+
+ *
+
+ *HC*: the wireless USB host controller. It looks like a USB host
+ whose root port is the radio and the WUSB devices connect to it.
+ To the system it looks like a separate USB host. The driver (will)
+ implement a USB host controller (similar to UHCI, OHCI or EHCI)
+ for which the root hub is the radio...To reiterate: it is a USB
+ controller that is connected via USB instead of PCI.
+
+ *
+
+ *WINET*: some HW provide a WiNET interface (IP over UWB). This
+ package provides a driver for it (it looks like a network
+ interface, winetX). The driver detects when there is a link up for
+ their type and kick into gear.
+
+
+DWA: Device Wired Adaptor, a Wireless USB hub for wired devices
+---------------------------------------------------------------
+
+These are the complement to HWAs. They are a USB host for connecting
+wired devices, but it is connected to your PC connected via Wireless
+USB. To the system it looks like yet another USB host. To the untrained
+eye, it looks like a hub that connects upstream wirelessly.
+
+We still offer no support for this; however, it should share a lot of
+code with the HWA-RC driver; there is a bunch of factorization work that
+has been done to support that in upcoming releases.
+
+
+WHCI: Wireless Host Controller Interface, the PCI WUSB host adapter
+-------------------------------------------------------------------
+
+This is your usual PCI device that implements WHCI. Similar in concept
+to EHCI, it allows your wireless USB devices (including DWAs) to connect
+to your host via a PCI interface. As in the case of the HWA, it has a
+Radio Control interface and the WUSB Host Controller interface per se.
+
+There is still no driver support for this, but will be in upcoming
+releases.
+
+
+The UWB stack
+=============
+
+The main mission of the UWB stack is to keep a tally of which devices
+are in radio proximity to allow drivers to connect to them. As well, it
+provides an API for controlling the local radio controllers (RCs from
+now on), such as to start/stop beaconing, scan, allocate bandwidth, etc.
+
+
+Devices and hosts: the basic structure
+--------------------------------------
+
+The main building block here is the UWB device (struct uwb_dev). For
+each device that pops up in radio presence (ie: the UWB host receives a
+beacon from it) you get a struct uwb_dev that will show up in
+/sys/bus/uwb/devices.
+
+For each RC that is detected, a new struct uwb_rc and struct uwb_dev are
+created. An entry is also created in /sys/class/uwb_rc for each RC.
+
+Each RC driver is implemented by a separate driver that plugs into the
+interface that the UWB stack provides through a struct uwb_rc_ops. The
+spec creators have been nice enough to make the message format the same
+for HWA and WHCI RCs, so the driver is really a very thin transport that
+moves the requests from the UWB API to the device [/uwb_rc_ops->cmd()/]
+and sends the replies and notifications back to the API
+[/uwb_rc_neh_grok()/]. Notifications are handled to the UWB daemon, that
+is chartered, among other things, to keep the tab of how the UWB radio
+neighborhood looks, creating and destroying devices as they show up or
+disappear.
+
+Command execution is very simple: a command block is sent and a event
+block or reply is expected back. For sending/receiving command/events, a
+handle called /neh/ (Notification/Event Handle) is opened with
+/uwb_rc_neh_open()/.
+
+The HWA-RC (USB dongle) driver (drivers/uwb/hwa-rc.c) does this job for
+the USB connected HWA. Eventually, drivers/whci-rc.c will do the same
+for the PCI connected WHCI controller.
+
+
+Host Controller life cycle
+--------------------------
+
+So let's say we connect a dongle to the system: it is detected and
+firmware uploaded if needed [for Intel's i1480
+/drivers/uwb/ptc/usb.c:ptc_usb_probe()/] and then it is reenumerated.
+Now we have a real HWA device connected and
+/drivers/uwb/hwa-rc.c:hwarc_probe()/ picks it up, that will set up the
+Wire-Adaptor environment and then suck it into the UWB stack's vision of
+the world [/drivers/uwb/lc-rc.c:uwb_rc_add()/].
+
+ *
+
+ [*] The stack should put a new RC to scan for devices
+ [/uwb_rc_scan()/] so it finds what's available around and tries to
+ connect to them, but this is policy stuff and should be driven
+ from user space. As of now, the operator is expected to do it
+ manually; see the release notes for documentation on the procedure.
+
+When a dongle is disconnected, /drivers/uwb/hwa-rc.c:hwarc_disconnect()/
+takes time of tearing everything down safely (or not...).
+
+
+On the air: beacons and enumerating the radio neighborhood
+----------------------------------------------------------
+
+So assuming we have devices and we have agreed for a channel to connect
+on (let's say 9), we put the new RC to beacon:
+
+ *
+
+ $ echo 9 0 > /sys/class/uwb_rc/uwb0/beacon
+
+Now it is visible. If there were other devices in the same radio channel
+and beacon group (that's what the zero is for), the dongle's radio
+control interface will send beacon notifications on its
+notification/event endpoint (NEEP). The beacon notifications are part of
+the event stream that is funneled into the API with
+/drivers/uwb/neh.c:uwb_rc_neh_grok()/ and delivered to the UWBD, the UWB
+daemon through a notification list.
+
+UWBD wakes up and scans the event list; finds a beacon and adds it to
+the BEACON CACHE (/uwb_beca/). If he receives a number of beacons from
+the same device, he considers it to be 'onair' and creates a new device
+[/drivers/uwb/lc-dev.c:uwbd_dev_onair()/]. Similarly, when no beacons
+are received in some time, the device is considered gone and wiped out
+[uwbd calls periodically /uwb/beacon.c:uwb_beca_purge()/ that will purge
+the beacon cache of dead devices].
+
+
+Device lists
+------------
+
+All UWB devices are kept in the list of the struct bus_type uwb_bus_type.
+
+
+Bandwidth allocation
+--------------------
+
+The UWB stack maintains a local copy of DRP availability through
+processing of incoming *DRP Availability Change* notifications. This
+local copy is currently used to present the current bandwidth
+availability to the user through the sysfs file
+/sys/class/uwb_rc/uwbx/bw_avail. In the future the bandwidth
+availability information will be used by the bandwidth reservation
+routines.
+
+The bandwidth reservation routines are in progress and are thus not
+present in the current release. When completed they will enable a user
+to initiate DRP reservation requests through interaction with sysfs. DRP
+reservation requests from remote UWB devices will also be handled. The
+bandwidth management done by the UWB stack will include callbacks to the
+higher layers will enable the higher layers to use the reservations upon
+completion. [Note: The bandwidth reservation work is in progress and
+subject to change.]
+
+
+Wireless USB Host Controller drivers
+====================================
+
+*WARNING* This section needs a lot of work!
+
+As explained above, there are three different types of HCs in the WUSB
+world: HWA-HC, DWA-HC and WHCI-HC.
+
+HWA-HC and DWA-HC share that they are Wire-Adapters (USB or WUSB
+connected controllers), and their transfer management system is almost
+identical. So is their notification delivery system.
+
+HWA-HC and WHCI-HC share that they are both WUSB host controllers, so
+they have to deal with WUSB device life cycle and maintenance, wireless
+root-hub
+
+HWA exposes a Host Controller interface (HWA-HC 0xe0/02/02). This has
+three endpoints (Notifications, Data Transfer In and Data Transfer
+Out--known as NEP, DTI and DTO in the code).
+
+We reserve UWB bandwidth for our Wireless USB Cluster, create a Cluster
+ID and tell the HC to use all that. Then we start it. This means the HC
+starts sending MMCs.
+
+ *
+
+ The MMCs are blocks of data defined somewhere in the WUSB1.0 spec
+ that define a stream in the UWB channel time allocated for sending
+ WUSB IEs (host to device commands/notifications) and Device
+ Notifications (device initiated to host). Each host defines a
+ unique Wireless USB cluster through MMCs. Devices can connect to a
+ single cluster at the time. The IEs are Information Elements, and
+ among them are the bandwidth allocations that tell each device
+ when can they transmit or receive.
+
+Now it all depends on external stimuli.
+
+New device connection
+---------------------
+
+A new device pops up, it scans the radio looking for MMCs that give out
+the existence of Wireless USB channels. Once one (or more) are found,
+selects which one to connect to. Sends a /DN_Connect/ (device
+notification connect) during the DNTS (Device Notification Time
+Slot--announced in the MMCs
+
+HC picks the /DN_Connect/ out (nep module sends to notif.c for delivery
+into /devconnect/). This process starts the authentication process for
+the device. First we allocate a /fake port/ and assign an
+unauthenticated address (128 to 255--what we really do is
+0x80 | fake_port_idx). We fiddle with the fake port status and /hub_wq/
+sees a new connection, so he moves on to enable the fake port with a reset.
+
+So now we are in the reset path -- we know we have a non-yet enumerated
+device with an unauthorized address; we ask user space to authenticate
+(FIXME: not yet done, similar to bluetooth pairing), then we do the key
+exchange (FIXME: not yet done) and issue a /set address 0/ to bring the
+device to the default state. Device is authenticated.
+
+From here, the USB stack takes control through the usb_hcd ops. hub_wq
+has seen the port status changes, as we have been toggling them. It will
+start enumerating and doing transfers through usb_hcd->urb_enqueue() to
+read descriptors and move our data.
+
+Device life cycle and keep alives
+---------------------------------
+
+Every time there is a successful transfer to/from a device, we update a
+per-device activity timestamp. If not, every now and then we check and
+if the activity timestamp gets old, we ping the device by sending it a
+Keep Alive IE; it responds with a /DN_Alive/ pong during the DNTS (this
+arrives to us as a notification through
+devconnect.c:wusb_handle_dn_alive(). If a device times out, we
+disconnect it from the system (cleaning up internal information and
+toggling the bits in the fake hub port, which kicks hub_wq into removing
+the rest of the stuff).
+
+This is done through devconnect:__wusb_check_devs(), which will scan the
+device list looking for whom needs refreshing.
+
+If the device wants to disconnect, it will either die (ugly) or send a
+/DN_Disconnect/ that will prompt a disconnection from the system.
+
+Sending and receiving data
+--------------------------
+
+Data is sent and received through /Remote Pipes/ (rpipes). An rpipe is
+/aimed/ at an endpoint in a WUSB device. This is the same for HWAs and
+DWAs.
+
+Each HC has a number of rpipes and buffers that can be assigned to them;
+when doing a data transfer (xfer), first the rpipe has to be aimed and
+prepared (buffers assigned), then we can start queueing requests for
+data in or out.
+
+Data buffers have to be segmented out before sending--so we send first a
+header (segment request) and then if there is any data, a data buffer
+immediately after to the DTI interface (yep, even the request). If our
+buffer is bigger than the max segment size, then we just do multiple
+requests.
+
+[This sucks, because doing USB scatter gatter in Linux is resource
+intensive, if any...not that the current approach is not. It just has to
+be cleaned up a lot :)].
+
+If reading, we don't send data buffers, just the segment headers saying
+we want to read segments.
+
+When the xfer is executed, we receive a notification that says data is
+ready in the DTI endpoint (handled through
+xfer.c:wa_handle_notif_xfer()). In there we read from the DTI endpoint a
+descriptor that gives us the status of the transfer, its identification
+(given when we issued it) and the segment number. If it was a data read,
+we issue another URB to read into the destination buffer the chunk of
+data coming out of the remote endpoint. Done, wait for the next guy. The
+callbacks for the URBs issued from here are the ones that will declare
+the xfer complete at some point and call its callback.
+
+Seems simple, but the implementation is not trivial.
+
+ *
+
+ *WARNING* Old!!
+
+The main xfer descriptor, wa_xfer (equivalent to a URB) contains an
+array of segments, tallys on segments and buffers and callback
+information. Buried in there is a lot of URBs for executing the segments
+and buffer transfers.
+
+For OUT xfers, there is an array of segments, one URB for each, another
+one of buffer URB. When submitting, we submit URBs for segment request
+1, buffer 1, segment 2, buffer 2...etc. Then we wait on the DTI for xfer
+result data; when all the segments are complete, we call the callback to
+finalize the transfer.
+
+For IN xfers, we only issue URBs for the segments we want to read and
+then wait for the xfer result data.
+
+URB mapping into xfers
+^^^^^^^^^^^^^^^^^^^^^^
+
+This is done by hwahc_op_urb_[en|de]queue(). In enqueue() we aim an
+rpipe to the endpoint where we have to transmit, create a transfer
+context (wa_xfer) and submit it. When the xfer is done, our callback is
+called and we assign the status bits and release the xfer resources.
+
+In dequeue() we are basically cancelling/aborting the transfer. We issue
+a xfer abort request to the HC, cancel all the URBs we had submitted
+and not yet done and when all that is done, the xfer callback will be
+called--this will call the URB callback.
+
+
+Glossary
+========
+
+*DWA* -- Device Wire Adapter
+
+USB host, wired for downstream devices, upstream connects wirelessly
+with Wireless USB.
+
+*EVENT* -- Response to a command on the NEEP
+
+*HWA* -- Host Wire Adapter / USB dongle for UWB and Wireless USB
+
+*NEH* -- Notification/Event Handle
+
+Handle/file descriptor for receiving notifications or events. The WA
+code requires you to get one of this to listen for notifications or
+events on the NEEP.
+
+*NEEP* -- Notification/Event EndPoint
+
+Stuff related to the management of the first endpoint of a HWA USB
+dongle that is used to deliver an stream of events and notifications to
+the host.
+
+*NOTIFICATION* -- Message coming in the NEEP as response to something.
+
+*RC* -- Radio Control
+
+Design-overview.txt-1.8 (last edited 2006-11-04 12:22:24 by
+InakyPerezGonzalez)
diff --git a/drivers/staging/wusbcore/Kconfig b/drivers/staging/wusbcore/Kconfig
new file mode 100644
index 000000000000..a559d023b508
--- /dev/null
+++ b/drivers/staging/wusbcore/Kconfig
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Wireless USB Core configuration
+#
+config USB_WUSB
+ tristate "Enable Wireless USB extensions"
+ depends on UWB && USB
+ select CRYPTO
+ select CRYPTO_AES
+ select CRYPTO_CCM
+ help
+ Enable the host-side support for Wireless USB.
+
+ To compile this support select Y (built in). It is safe to
+ select even if you don't have the hardware.
+
+config USB_WUSB_CBAF
+ tristate "Support WUSB Cable Based Association (CBA)"
+ depends on USB
+ help
+ Some WUSB devices support Cable Based Association. It's used to
+ enable the secure communication between the host and the
+ device.
+
+ Enable this option if your WUSB device must to be connected
+ via wired USB before establishing a wireless link.
+
+ It is safe to select even if you don't have a compatible
+ hardware.
+
+config USB_WUSB_CBAF_DEBUG
+ bool "Enable CBA debug messages"
+ depends on USB_WUSB_CBAF
+ help
+ Say Y here if you want the CBA to produce a bunch of debug messages
+ to the system log. Select this if you are having a problem with
+ CBA support and want to see more of what is going on.
+
+source "drivers/staging/wusbcore/host/Kconfig"
diff --git a/drivers/staging/wusbcore/Makefile b/drivers/staging/wusbcore/Makefile
new file mode 100644
index 000000000000..b47b874268ac
--- /dev/null
+++ b/drivers/staging/wusbcore/Makefile
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0
+ccflags-$(CONFIG_USB_WUSB_CBAF_DEBUG) := -DDEBUG
+
+obj-$(CONFIG_USB_WUSB) += wusbcore.o
+obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o
+obj-$(CONFIG_USB_WUSB_CBAF) += wusb-cbaf.o
+
+
+wusbcore-y := \
+ crypto.o \
+ devconnect.o \
+ dev-sysfs.o \
+ mmc.o \
+ pal.o \
+ rh.o \
+ reservation.o \
+ security.o \
+ wusbhc.o
+
+wusb-cbaf-y := cbaf.o
+
+wusb-wa-y := \
+ wa-hc.o \
+ wa-nep.o \
+ wa-rpipe.o \
+ wa-xfer.o
+
+obj-y += host/
diff --git a/drivers/staging/wusbcore/TODO b/drivers/staging/wusbcore/TODO
new file mode 100644
index 000000000000..abae57000534
--- /dev/null
+++ b/drivers/staging/wusbcore/TODO
@@ -0,0 +1,8 @@
+TODO: Remove in late 2019 unless there are users
+
+There seems to not be any real wireless USB devices anywhere in the wild
+anymore. It turned out to be a failed technology :(
+
+This will be removed from the tree if no one objects.
+
+Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/drivers/staging/wusbcore/cbaf.c b/drivers/staging/wusbcore/cbaf.c
new file mode 100644
index 000000000000..57062eaf7558
--- /dev/null
+++ b/drivers/staging/wusbcore/cbaf.c
@@ -0,0 +1,645 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB - Cable Based Association
+ *
+ *
+ * Copyright (C) 2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ *
+ * WUSB devices have to be paired (associated in WUSB lingo) so
+ * that they can connect to the system.
+ *
+ * One way of pairing is using CBA-Cable Based Association. First
+ * time you plug the device with a cable, association is done between
+ * host and device and subsequent times, you can connect wirelessly
+ * without having to associate again. That's the idea.
+ *
+ * This driver does nothing Earth shattering. It just provides an
+ * interface to chat with the wire-connected device so we can get a
+ * CDID (device ID) that might have been previously associated to a
+ * CHID (host ID) and to set up a new <CHID,CDID,CK> triplet
+ * (connection context), with the CK being the secret, or connection
+ * key. This is the pairing data.
+ *
+ * When a device with the CBA capability connects, the probe routine
+ * just creates a bunch of sysfs files that a user space enumeration
+ * manager uses to allow it to connect wirelessly to the system or not.
+ *
+ * The process goes like this:
+ *
+ * 1. Device plugs, cbaf is loaded, notifications happen.
+ *
+ * 2. The connection manager (CM) sees a device with CBAF capability
+ * (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE).
+ *
+ * 3. The CM writes the host name, supported band groups, and the CHID
+ * (host ID) into the wusb_host_name, wusb_host_band_groups and
+ * wusb_chid files. These get sent to the device and the CDID (if
+ * any) for this host is requested.
+ *
+ * 4. The CM can verify that the device's supported band groups
+ * (wusb_device_band_groups) are compatible with the host.
+ *
+ * 5. The CM reads the wusb_cdid file.
+ *
+ * 6. The CM looks up its database
+ *
+ * 6.1 If it has a matching CHID,CDID entry, the device has been
+ * authorized before (paired) and nothing further needs to be
+ * done.
+ *
+ * 6.2 If the CDID is zero (or the CM doesn't find a matching CDID in
+ * its database), the device is assumed to be not known. The CM
+ * may associate the host with device by: writing a randomly
+ * generated CDID to wusb_cdid and then a random CK to wusb_ck
+ * (this uploads the new CC to the device).
+ *
+ * CMD may choose to prompt the user before associating with a new
+ * device.
+ *
+ * 7. Device is unplugged.
+ *
+ * When the device tries to connect wirelessly, it will present its
+ * CDID to the WUSB host controller. The CM will query the
+ * database. If the CHID/CDID pair found, it will (with a 4-way
+ * handshake) challenge the device to demonstrate it has the CK secret
+ * key (from our database) without actually exchanging it. Once
+ * satisfied, crypto keys are derived from the CK, the device is
+ * connected and all communication is encrypted.
+ *
+ * References:
+ * [WUSB-AM] Association Models Supplement to the Certified Wireless
+ * Universal Serial Bus Specification, version 1.0.
+ */
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/usb.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include "../uwb/uwb.h"
+#include "include/wusb.h"
+#include "include/association.h"
+
+#define CBA_NAME_LEN 0x40 /* [WUSB-AM] table 4-7 */
+
+/* An instance of a Cable-Based-Association-Framework device */
+struct cbaf {
+ struct usb_device *usb_dev;
+ struct usb_interface *usb_iface;
+ void *buffer;
+ size_t buffer_size;
+
+ struct wusb_ckhdid chid;
+ char host_name[CBA_NAME_LEN];
+ u16 host_band_groups;
+
+ struct wusb_ckhdid cdid;
+ char device_name[CBA_NAME_LEN];
+ u16 device_band_groups;
+
+ struct wusb_ckhdid ck;
+};
+
+/*
+ * Verify that a CBAF USB-interface has what we need
+ *
+ * According to [WUSB-AM], CBA devices should provide at least two
+ * interfaces:
+ * - RETRIEVE_HOST_INFO
+ * - ASSOCIATE
+ *
+ * If the device doesn't provide these interfaces, we do not know how
+ * to deal with it.
+ */
+static int cbaf_check(struct cbaf *cbaf)
+{
+ int result;
+ struct device *dev = &cbaf->usb_iface->dev;
+ struct wusb_cbaf_assoc_info *assoc_info;
+ struct wusb_cbaf_assoc_request *assoc_request;
+ size_t assoc_size;
+ void *itr, *top;
+ int ar_rhi = 0, ar_assoc = 0;
+
+ result = usb_control_msg(
+ cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0),
+ CBAF_REQ_GET_ASSOCIATION_INFORMATION,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ cbaf->buffer, cbaf->buffer_size, USB_CTRL_GET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "Cannot get available association types: %d\n",
+ result);
+ return result;
+ }
+
+ assoc_info = cbaf->buffer;
+ if (result < sizeof(*assoc_info)) {
+ dev_err(dev, "Not enough data to decode association info "
+ "header (%zu vs %zu bytes required)\n",
+ (size_t)result, sizeof(*assoc_info));
+ return result;
+ }
+
+ assoc_size = le16_to_cpu(assoc_info->Length);
+ if (result < assoc_size) {
+ dev_err(dev, "Not enough data to decode association info "
+ "(%zu vs %zu bytes required)\n",
+ (size_t)assoc_size, sizeof(*assoc_info));
+ return result;
+ }
+ /*
+ * From now on, we just verify, but won't error out unless we
+ * don't find the AR_TYPE_WUSB_{RETRIEVE_HOST_INFO,ASSOCIATE}
+ * types.
+ */
+ itr = cbaf->buffer + sizeof(*assoc_info);
+ top = cbaf->buffer + assoc_size;
+ dev_dbg(dev, "Found %u association requests (%zu bytes)\n",
+ assoc_info->NumAssociationRequests, assoc_size);
+
+ while (itr < top) {
+ u16 ar_type, ar_subtype;
+ u32 ar_size;
+ const char *ar_name;
+
+ assoc_request = itr;
+
+ if (top - itr < sizeof(*assoc_request)) {
+ dev_err(dev, "Not enough data to decode association "
+ "request (%zu vs %zu bytes needed)\n",
+ top - itr, sizeof(*assoc_request));
+ break;
+ }
+
+ ar_type = le16_to_cpu(assoc_request->AssociationTypeId);
+ ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId);
+ ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize);
+ ar_name = "unknown";
+
+ switch (ar_type) {
+ case AR_TYPE_WUSB:
+ /* Verify we have what is mandated by [WUSB-AM]. */
+ switch (ar_subtype) {
+ case AR_TYPE_WUSB_RETRIEVE_HOST_INFO:
+ ar_name = "RETRIEVE_HOST_INFO";
+ ar_rhi = 1;
+ break;
+ case AR_TYPE_WUSB_ASSOCIATE:
+ /* send assoc data */
+ ar_name = "ASSOCIATE";
+ ar_assoc = 1;
+ break;
+ }
+ break;
+ }
+
+ dev_dbg(dev, "Association request #%02u: 0x%04x/%04x "
+ "(%zu bytes): %s\n",
+ assoc_request->AssociationDataIndex, ar_type,
+ ar_subtype, (size_t)ar_size, ar_name);
+
+ itr += sizeof(*assoc_request);
+ }
+
+ if (!ar_rhi) {
+ dev_err(dev, "Missing RETRIEVE_HOST_INFO association "
+ "request\n");
+ return -EINVAL;
+ }
+ if (!ar_assoc) {
+ dev_err(dev, "Missing ASSOCIATE association request\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct wusb_cbaf_host_info cbaf_host_info_defaults = {
+ .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId,
+ .AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB),
+ .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
+ .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_RETRIEVE_HOST_INFO),
+ .CHID_hdr = WUSB_AR_CHID,
+ .LangID_hdr = WUSB_AR_LangID,
+ .HostFriendlyName_hdr = WUSB_AR_HostFriendlyName,
+};
+
+/* Send WUSB host information (CHID and name) to a CBAF device */
+static int cbaf_send_host_info(struct cbaf *cbaf)
+{
+ struct wusb_cbaf_host_info *hi;
+ size_t name_len;
+ size_t hi_size;
+
+ hi = cbaf->buffer;
+ memset(hi, 0, sizeof(*hi));
+ *hi = cbaf_host_info_defaults;
+ hi->CHID = cbaf->chid;
+ hi->LangID = 0; /* FIXME: I guess... */
+ strlcpy(hi->HostFriendlyName, cbaf->host_name, CBA_NAME_LEN);
+ name_len = strlen(cbaf->host_name);
+ hi->HostFriendlyName_hdr.len = cpu_to_le16(name_len);
+ hi_size = sizeof(*hi) + name_len;
+
+ return usb_control_msg(cbaf->usb_dev,
+ usb_sndctrlpipe(cbaf->usb_dev, 0),
+ CBAF_REQ_SET_ASSOCIATION_RESPONSE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0101,
+ cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ hi, hi_size, USB_CTRL_SET_TIMEOUT);
+}
+
+/*
+ * Get device's information (CDID) associated to CHID
+ *
+ * The device will return it's information (CDID, name, bandgroups)
+ * associated to the CHID we have set before, or 0 CDID and default
+ * name and bandgroup if no CHID set or unknown.
+ */
+static int cbaf_cdid_get(struct cbaf *cbaf)
+{
+ int result;
+ struct device *dev = &cbaf->usb_iface->dev;
+ struct wusb_cbaf_device_info *di;
+ size_t needed;
+
+ di = cbaf->buffer;
+ result = usb_control_msg(
+ cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0),
+ CBAF_REQ_GET_ASSOCIATION_REQUEST,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ di, cbaf->buffer_size, USB_CTRL_GET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "Cannot request device information: %d\n",
+ result);
+ return result;
+ }
+
+ needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length);
+ if (result < needed) {
+ dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs "
+ "%zu bytes needed)\n", (size_t)result, needed);
+ return -ENOENT;
+ }
+
+ strlcpy(cbaf->device_name, di->DeviceFriendlyName, CBA_NAME_LEN);
+ cbaf->cdid = di->CDID;
+ cbaf->device_band_groups = le16_to_cpu(di->BandGroups);
+
+ return 0;
+}
+
+static ssize_t cbaf_wusb_chid_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ return sprintf(buf, "%16ph\n", cbaf->chid.data);
+}
+
+static ssize_t cbaf_wusb_chid_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ ssize_t result;
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ result = sscanf(buf,
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx",
+ &cbaf->chid.data[0] , &cbaf->chid.data[1],
+ &cbaf->chid.data[2] , &cbaf->chid.data[3],
+ &cbaf->chid.data[4] , &cbaf->chid.data[5],
+ &cbaf->chid.data[6] , &cbaf->chid.data[7],
+ &cbaf->chid.data[8] , &cbaf->chid.data[9],
+ &cbaf->chid.data[10], &cbaf->chid.data[11],
+ &cbaf->chid.data[12], &cbaf->chid.data[13],
+ &cbaf->chid.data[14], &cbaf->chid.data[15]);
+
+ if (result != 16)
+ return -EINVAL;
+
+ result = cbaf_send_host_info(cbaf);
+ if (result < 0)
+ return result;
+ result = cbaf_cdid_get(cbaf);
+ if (result < 0)
+ return result;
+ return size;
+}
+static DEVICE_ATTR(wusb_chid, 0600, cbaf_wusb_chid_show, cbaf_wusb_chid_store);
+
+static ssize_t cbaf_wusb_host_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->host_name);
+}
+
+static ssize_t cbaf_wusb_host_name_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ ssize_t result;
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ result = sscanf(buf, "%63s", cbaf->host_name);
+ if (result != 1)
+ return -EINVAL;
+
+ return size;
+}
+static DEVICE_ATTR(wusb_host_name, 0600, cbaf_wusb_host_name_show,
+ cbaf_wusb_host_name_store);
+
+static ssize_t cbaf_wusb_host_band_groups_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->host_band_groups);
+}
+
+static ssize_t cbaf_wusb_host_band_groups_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ ssize_t result;
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+ u16 band_groups = 0;
+
+ result = sscanf(buf, "%04hx", &band_groups);
+ if (result != 1)
+ return -EINVAL;
+
+ cbaf->host_band_groups = band_groups;
+
+ return size;
+}
+
+static DEVICE_ATTR(wusb_host_band_groups, 0600,
+ cbaf_wusb_host_band_groups_show,
+ cbaf_wusb_host_band_groups_store);
+
+static const struct wusb_cbaf_device_info cbaf_device_info_defaults = {
+ .Length_hdr = WUSB_AR_Length,
+ .CDID_hdr = WUSB_AR_CDID,
+ .BandGroups_hdr = WUSB_AR_BandGroups,
+ .LangID_hdr = WUSB_AR_LangID,
+ .DeviceFriendlyName_hdr = WUSB_AR_DeviceFriendlyName,
+};
+
+static ssize_t cbaf_wusb_cdid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ return sprintf(buf, "%16ph\n", cbaf->cdid.data);
+}
+
+static ssize_t cbaf_wusb_cdid_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ ssize_t result;
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+ struct wusb_ckhdid cdid;
+
+ result = sscanf(buf,
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx",
+ &cdid.data[0] , &cdid.data[1],
+ &cdid.data[2] , &cdid.data[3],
+ &cdid.data[4] , &cdid.data[5],
+ &cdid.data[6] , &cdid.data[7],
+ &cdid.data[8] , &cdid.data[9],
+ &cdid.data[10], &cdid.data[11],
+ &cdid.data[12], &cdid.data[13],
+ &cdid.data[14], &cdid.data[15]);
+ if (result != 16)
+ return -EINVAL;
+
+ cbaf->cdid = cdid;
+
+ return size;
+}
+static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_show, cbaf_wusb_cdid_store);
+
+static ssize_t cbaf_wusb_device_band_groups_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->device_band_groups);
+}
+
+static DEVICE_ATTR(wusb_device_band_groups, 0600,
+ cbaf_wusb_device_band_groups_show,
+ NULL);
+
+static ssize_t cbaf_wusb_device_name_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->device_name);
+}
+static DEVICE_ATTR(wusb_device_name, 0600, cbaf_wusb_device_name_show, NULL);
+
+static const struct wusb_cbaf_cc_data cbaf_cc_data_defaults = {
+ .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId,
+ .AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB),
+ .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
+ .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_ASSOCIATE),
+ .Length_hdr = WUSB_AR_Length,
+ .Length = cpu_to_le32(sizeof(struct wusb_cbaf_cc_data)),
+ .ConnectionContext_hdr = WUSB_AR_ConnectionContext,
+ .BandGroups_hdr = WUSB_AR_BandGroups,
+};
+
+static const struct wusb_cbaf_cc_data_fail cbaf_cc_data_fail_defaults = {
+ .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId,
+ .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId,
+ .Length_hdr = WUSB_AR_Length,
+ .AssociationStatus_hdr = WUSB_AR_AssociationStatus,
+};
+
+/*
+ * Send a new CC to the device.
+ */
+static int cbaf_cc_upload(struct cbaf *cbaf)
+{
+ int result;
+ struct device *dev = &cbaf->usb_iface->dev;
+ struct wusb_cbaf_cc_data *ccd;
+
+ ccd = cbaf->buffer;
+ *ccd = cbaf_cc_data_defaults;
+ ccd->CHID = cbaf->chid;
+ ccd->CDID = cbaf->cdid;
+ ccd->CK = cbaf->ck;
+ ccd->BandGroups = cpu_to_le16(cbaf->host_band_groups);
+
+ dev_dbg(dev, "Trying to upload CC:\n");
+ dev_dbg(dev, " CHID %16ph\n", ccd->CHID.data);
+ dev_dbg(dev, " CDID %16ph\n", ccd->CDID.data);
+ dev_dbg(dev, " Bandgroups 0x%04x\n", cbaf->host_band_groups);
+
+ result = usb_control_msg(
+ cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0),
+ CBAF_REQ_SET_ASSOCIATION_RESPONSE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ ccd, sizeof(*ccd), USB_CTRL_SET_TIMEOUT);
+
+ return result;
+}
+
+static ssize_t cbaf_wusb_ck_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ ssize_t result;
+ struct usb_interface *iface = to_usb_interface(dev);
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+
+ result = sscanf(buf,
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx",
+ &cbaf->ck.data[0] , &cbaf->ck.data[1],
+ &cbaf->ck.data[2] , &cbaf->ck.data[3],
+ &cbaf->ck.data[4] , &cbaf->ck.data[5],
+ &cbaf->ck.data[6] , &cbaf->ck.data[7],
+ &cbaf->ck.data[8] , &cbaf->ck.data[9],
+ &cbaf->ck.data[10], &cbaf->ck.data[11],
+ &cbaf->ck.data[12], &cbaf->ck.data[13],
+ &cbaf->ck.data[14], &cbaf->ck.data[15]);
+ if (result != 16)
+ return -EINVAL;
+
+ result = cbaf_cc_upload(cbaf);
+ if (result < 0)
+ return result;
+
+ return size;
+}
+static DEVICE_ATTR(wusb_ck, 0600, NULL, cbaf_wusb_ck_store);
+
+static struct attribute *cbaf_dev_attrs[] = {
+ &dev_attr_wusb_host_name.attr,
+ &dev_attr_wusb_host_band_groups.attr,
+ &dev_attr_wusb_chid.attr,
+ &dev_attr_wusb_cdid.attr,
+ &dev_attr_wusb_device_name.attr,
+ &dev_attr_wusb_device_band_groups.attr,
+ &dev_attr_wusb_ck.attr,
+ NULL,
+};
+
+static const struct attribute_group cbaf_dev_attr_group = {
+ .name = NULL, /* we want them in the same directory */
+ .attrs = cbaf_dev_attrs,
+};
+
+static int cbaf_probe(struct usb_interface *iface,
+ const struct usb_device_id *id)
+{
+ struct cbaf *cbaf;
+ struct device *dev = &iface->dev;
+ int result = -ENOMEM;
+
+ cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL);
+ if (cbaf == NULL)
+ goto error_kzalloc;
+ cbaf->buffer = kmalloc(512, GFP_KERNEL);
+ if (cbaf->buffer == NULL)
+ goto error_kmalloc_buffer;
+
+ cbaf->buffer_size = 512;
+ cbaf->usb_dev = usb_get_dev(interface_to_usbdev(iface));
+ cbaf->usb_iface = usb_get_intf(iface);
+ result = cbaf_check(cbaf);
+ if (result < 0) {
+ dev_err(dev, "This device is not WUSB-CBAF compliant and is not supported yet.\n");
+ goto error_check;
+ }
+
+ result = sysfs_create_group(&dev->kobj, &cbaf_dev_attr_group);
+ if (result < 0) {
+ dev_err(dev, "Can't register sysfs attr group: %d\n", result);
+ goto error_create_group;
+ }
+ usb_set_intfdata(iface, cbaf);
+ return 0;
+
+error_create_group:
+error_check:
+ usb_put_intf(iface);
+ usb_put_dev(cbaf->usb_dev);
+ kfree(cbaf->buffer);
+error_kmalloc_buffer:
+ kfree(cbaf);
+error_kzalloc:
+ return result;
+}
+
+static void cbaf_disconnect(struct usb_interface *iface)
+{
+ struct cbaf *cbaf = usb_get_intfdata(iface);
+ struct device *dev = &iface->dev;
+ sysfs_remove_group(&dev->kobj, &cbaf_dev_attr_group);
+ usb_set_intfdata(iface, NULL);
+ usb_put_intf(iface);
+ usb_put_dev(cbaf->usb_dev);
+ kfree(cbaf->buffer);
+ /* paranoia: clean up crypto keys */
+ kzfree(cbaf);
+}
+
+static const struct usb_device_id cbaf_id_table[] = {
+ { USB_INTERFACE_INFO(0xef, 0x03, 0x01), },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, cbaf_id_table);
+
+static struct usb_driver cbaf_driver = {
+ .name = "wusb-cbaf",
+ .id_table = cbaf_id_table,
+ .probe = cbaf_probe,
+ .disconnect = cbaf_disconnect,
+};
+
+module_usb_driver(cbaf_driver);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Wireless USB Cable Based Association");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/wusbcore/crypto.c b/drivers/staging/wusbcore/crypto.c
new file mode 100644
index 000000000000..d7d55ed19a98
--- /dev/null
+++ b/drivers/staging/wusbcore/crypto.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Ultra Wide Band
+ * AES-128 CCM Encryption
+ *
+ * Copyright (C) 2007 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * We don't do any encryption here; we use the Linux Kernel's AES-128
+ * crypto modules to construct keys and payload blocks in a way
+ * defined by WUSB1.0[6]. Check the erratas, as typos are are patched
+ * there.
+ *
+ * Thanks a zillion to John Keys for his help and clarifications over
+ * the designed-by-a-committee text.
+ *
+ * So the idea is that there is this basic Pseudo-Random-Function
+ * defined in WUSB1.0[6.5] which is the core of everything. It works
+ * by tweaking some blocks, AES crypting them and then xoring
+ * something else with them (this seems to be called CBC(AES) -- can
+ * you tell I know jack about crypto?). So we just funnel it into the
+ * Linux Crypto API.
+ *
+ * We leave a crypto test module so we can verify that vectors match,
+ * every now and then.
+ *
+ * Block size: 16 bytes -- AES seems to do things in 'block sizes'. I
+ * am learning a lot...
+ *
+ * Conveniently, some data structures that need to be
+ * funneled through AES are...16 bytes in size!
+ */
+
+#include <crypto/aes.h>
+#include <crypto/algapi.h>
+#include <crypto/hash.h>
+#include <crypto/skcipher.h>
+#include <linux/crypto.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include "../uwb/uwb.h"
+#include "include/wusb.h"
+
+static int debug_crypto_verify;
+
+module_param(debug_crypto_verify, int, 0);
+MODULE_PARM_DESC(debug_crypto_verify, "verify the key generation algorithms");
+
+static void wusb_key_dump(const void *buf, size_t len)
+{
+ print_hex_dump(KERN_ERR, " ", DUMP_PREFIX_OFFSET, 16, 1,
+ buf, len, 0);
+}
+
+/*
+ * Block of data, as understood by AES-CCM
+ *
+ * The code assumes this structure is nothing but a 16 byte array
+ * (packed in a struct to avoid common mess ups that I usually do with
+ * arrays and enforcing type checking).
+ */
+struct aes_ccm_block {
+ u8 data[16];
+} __attribute__((packed));
+
+/*
+ * Counter-mode Blocks (WUSB1.0[6.4])
+ *
+ * According to CCM (or so it seems), for the purpose of calculating
+ * the MIC, the message is broken in N counter-mode blocks, B0, B1,
+ * ... BN.
+ *
+ * B0 contains flags, the CCM nonce and l(m).
+ *
+ * B1 contains l(a), the MAC header, the encryption offset and padding.
+ *
+ * If EO is nonzero, additional blocks are built from payload bytes
+ * until EO is exhausted (FIXME: padding to 16 bytes, I guess). The
+ * padding is not xmitted.
+ */
+
+/* WUSB1.0[T6.4] */
+struct aes_ccm_b0 {
+ u8 flags; /* 0x59, per CCM spec */
+ struct aes_ccm_nonce ccm_nonce;
+ __be16 lm;
+} __attribute__((packed));
+
+/* WUSB1.0[T6.5] */
+struct aes_ccm_b1 {
+ __be16 la;
+ u8 mac_header[10];
+ __le16 eo;
+ u8 security_reserved; /* This is always zero */
+ u8 padding; /* 0 */
+} __attribute__((packed));
+
+/*
+ * Encryption Blocks (WUSB1.0[6.4.4])
+ *
+ * CCM uses Ax blocks to generate a keystream with which the MIC and
+ * the message's payload are encoded. A0 always encrypts/decrypts the
+ * MIC. Ax (x>0) are used for the successive payload blocks.
+ *
+ * The x is the counter, and is increased for each block.
+ */
+struct aes_ccm_a {
+ u8 flags; /* 0x01, per CCM spec */
+ struct aes_ccm_nonce ccm_nonce;
+ __be16 counter; /* Value of x */
+} __attribute__((packed));
+
+/* Scratch space for MAC calculations. */
+struct wusb_mac_scratch {
+ struct aes_ccm_b0 b0;
+ struct aes_ccm_b1 b1;
+ struct aes_ccm_a ax;
+};
+
+/*
+ * CC-MAC function WUSB1.0[6.5]
+ *
+ * Take a data string and produce the encrypted CBC Counter-mode MIC
+ *
+ * Note the names for most function arguments are made to (more or
+ * less) match those used in the pseudo-function definition given in
+ * WUSB1.0[6.5].
+ *
+ * @tfm_cbc: CBC(AES) blkcipher handle (initialized)
+ *
+ * @tfm_aes: AES cipher handle (initialized)
+ *
+ * @mic: buffer for placing the computed MIC (Message Integrity
+ * Code). This is exactly 8 bytes, and we expect the buffer to
+ * be at least eight bytes in length.
+ *
+ * @key: 128 bit symmetric key
+ *
+ * @n: CCM nonce
+ *
+ * @a: ASCII string, 14 bytes long (I guess zero padded if needed;
+ * we use exactly 14 bytes).
+ *
+ * @b: data stream to be processed
+ *
+ * @blen: size of b...
+ *
+ * Still not very clear how this is done, but looks like this: we
+ * create block B0 (as WUSB1.0[6.5] says), then we AES-crypt it with
+ * @key. We bytewise xor B0 with B1 (1) and AES-crypt that. Then we
+ * take the payload and divide it in blocks (16 bytes), xor them with
+ * the previous crypto result (16 bytes) and crypt it, repeat the next
+ * block with the output of the previous one, rinse wash. So we use
+ * the CBC-MAC(AES) shash, that does precisely that. The IV (Initial
+ * Vector) is 16 bytes and is set to zero, so
+ *
+ * (1) Created as 6.5 says, again, using as l(a) 'Blen + 14', and
+ * using the 14 bytes of @a to fill up
+ * b1.{mac_header,e0,security_reserved,padding}.
+ *
+ * NOTE: The definition of l(a) in WUSB1.0[6.5] vs the definition of
+ * l(m) is orthogonal, they bear no relationship, so it is not
+ * in conflict with the parameter's relation that
+ * WUSB1.0[6.4.2]) defines.
+ *
+ * NOTE: WUSB1.0[A.1]: Host Nonce is missing a nibble? (1e); fixed in
+ * first errata released on 2005/07.
+ *
+ * NOTE: we need to clean IV to zero at each invocation to make sure
+ * we start with a fresh empty Initial Vector, so that the CBC
+ * works ok.
+ *
+ * NOTE: blen is not aligned to a block size, we'll pad zeros, that's
+ * what sg[4] is for. Maybe there is a smarter way to do this.
+ */
+static int wusb_ccm_mac(struct crypto_shash *tfm_cbcmac,
+ struct wusb_mac_scratch *scratch,
+ void *mic,
+ const struct aes_ccm_nonce *n,
+ const struct aes_ccm_label *a, const void *b,
+ size_t blen)
+{
+ SHASH_DESC_ON_STACK(desc, tfm_cbcmac);
+ u8 iv[AES_BLOCK_SIZE];
+
+ /*
+ * These checks should be compile time optimized out
+ * ensure @a fills b1's mac_header and following fields
+ */
+ BUILD_BUG_ON(sizeof(*a) != sizeof(scratch->b1) - sizeof(scratch->b1.la));
+ BUILD_BUG_ON(sizeof(scratch->b0) != sizeof(struct aes_ccm_block));
+ BUILD_BUG_ON(sizeof(scratch->b1) != sizeof(struct aes_ccm_block));
+ BUILD_BUG_ON(sizeof(scratch->ax) != sizeof(struct aes_ccm_block));
+
+ /* Setup B0 */
+ scratch->b0.flags = 0x59; /* Format B0 */
+ scratch->b0.ccm_nonce = *n;
+ scratch->b0.lm = cpu_to_be16(0); /* WUSB1.0[6.5] sez l(m) is 0 */
+
+ /* Setup B1
+ *
+ * The WUSB spec is anything but clear! WUSB1.0[6.5]
+ * says that to initialize B1 from A with 'l(a) = blen +
+ * 14'--after clarification, it means to use A's contents
+ * for MAC Header, EO, sec reserved and padding.
+ */
+ scratch->b1.la = cpu_to_be16(blen + 14);
+ memcpy(&scratch->b1.mac_header, a, sizeof(*a));
+
+ desc->tfm = tfm_cbcmac;
+ crypto_shash_init(desc);
+ crypto_shash_update(desc, (u8 *)&scratch->b0, sizeof(scratch->b0) +
+ sizeof(scratch->b1));
+ crypto_shash_finup(desc, b, blen, iv);
+
+ /* Now we crypt the MIC Tag (*iv) with Ax -- values per WUSB1.0[6.5]
+ * The procedure is to AES crypt the A0 block and XOR the MIC
+ * Tag against it; we only do the first 8 bytes and place it
+ * directly in the destination buffer.
+ */
+ scratch->ax.flags = 0x01; /* as per WUSB 1.0 spec */
+ scratch->ax.ccm_nonce = *n;
+ scratch->ax.counter = 0;
+
+ /* reuse the CBC-MAC transform to perform the single block encryption */
+ crypto_shash_digest(desc, (u8 *)&scratch->ax, sizeof(scratch->ax),
+ (u8 *)&scratch->ax);
+
+ crypto_xor_cpy(mic, (u8 *)&scratch->ax, iv, 8);
+
+ return 8;
+}
+
+/*
+ * WUSB Pseudo Random Function (WUSB1.0[6.5])
+ *
+ * @b: buffer to the source data; cannot be a global or const local
+ * (will confuse the scatterlists)
+ */
+ssize_t wusb_prf(void *out, size_t out_size,
+ const u8 key[16], const struct aes_ccm_nonce *_n,
+ const struct aes_ccm_label *a,
+ const void *b, size_t blen, size_t len)
+{
+ ssize_t result, bytes = 0, bitr;
+ struct aes_ccm_nonce n = *_n;
+ struct crypto_shash *tfm_cbcmac;
+ struct wusb_mac_scratch scratch;
+ u64 sfn = 0;
+ __le64 sfn_le;
+
+ tfm_cbcmac = crypto_alloc_shash("cbcmac(aes)", 0, 0);
+ if (IS_ERR(tfm_cbcmac)) {
+ result = PTR_ERR(tfm_cbcmac);
+ printk(KERN_ERR "E: can't load CBCMAC-AES: %d\n", (int)result);
+ goto error_alloc_cbcmac;
+ }
+
+ result = crypto_shash_setkey(tfm_cbcmac, key, AES_BLOCK_SIZE);
+ if (result < 0) {
+ printk(KERN_ERR "E: can't set CBCMAC-AES key: %d\n", (int)result);
+ goto error_setkey_cbcmac;
+ }
+
+ for (bitr = 0; bitr < (len + 63) / 64; bitr++) {
+ sfn_le = cpu_to_le64(sfn++);
+ memcpy(&n.sfn, &sfn_le, sizeof(n.sfn)); /* n.sfn++... */
+ result = wusb_ccm_mac(tfm_cbcmac, &scratch, out + bytes,
+ &n, a, b, blen);
+ if (result < 0)
+ goto error_ccm_mac;
+ bytes += result;
+ }
+ result = bytes;
+
+error_ccm_mac:
+error_setkey_cbcmac:
+ crypto_free_shash(tfm_cbcmac);
+error_alloc_cbcmac:
+ return result;
+}
+
+/* WUSB1.0[A.2] test vectors */
+static const u8 stv_hsmic_key[16] = {
+ 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d,
+ 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f
+};
+
+static const struct aes_ccm_nonce stv_hsmic_n = {
+ .sfn = { 0 },
+ .tkid = { 0x76, 0x98, 0x01, },
+ .dest_addr = { .data = { 0xbe, 0x00 } },
+ .src_addr = { .data = { 0x76, 0x98 } },
+};
+
+/*
+ * Out-of-band MIC Generation verification code
+ *
+ */
+static int wusb_oob_mic_verify(void)
+{
+ int result;
+ u8 mic[8];
+ /* WUSB1.0[A.2] test vectors */
+ static const struct usb_handshake stv_hsmic_hs = {
+ .bMessageNumber = 2,
+ .bStatus = 00,
+ .tTKID = { 0x76, 0x98, 0x01 },
+ .bReserved = 00,
+ .CDID = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0x3e, 0x3f },
+ .nonce = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
+ 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f },
+ .MIC = { 0x75, 0x6a, 0x97, 0x51, 0x0c, 0x8c,
+ 0x14, 0x7b },
+ };
+ size_t hs_size;
+
+ result = wusb_oob_mic(mic, stv_hsmic_key, &stv_hsmic_n, &stv_hsmic_hs);
+ if (result < 0)
+ printk(KERN_ERR "E: WUSB OOB MIC test: failed: %d\n", result);
+ else if (memcmp(stv_hsmic_hs.MIC, mic, sizeof(mic))) {
+ printk(KERN_ERR "E: OOB MIC test: "
+ "mismatch between MIC result and WUSB1.0[A2]\n");
+ hs_size = sizeof(stv_hsmic_hs) - sizeof(stv_hsmic_hs.MIC);
+ printk(KERN_ERR "E: Handshake2 in: (%zu bytes)\n", hs_size);
+ wusb_key_dump(&stv_hsmic_hs, hs_size);
+ printk(KERN_ERR "E: CCM Nonce in: (%zu bytes)\n",
+ sizeof(stv_hsmic_n));
+ wusb_key_dump(&stv_hsmic_n, sizeof(stv_hsmic_n));
+ printk(KERN_ERR "E: MIC out:\n");
+ wusb_key_dump(mic, sizeof(mic));
+ printk(KERN_ERR "E: MIC out (from WUSB1.0[A.2]):\n");
+ wusb_key_dump(stv_hsmic_hs.MIC, sizeof(stv_hsmic_hs.MIC));
+ result = -EINVAL;
+ } else
+ result = 0;
+ return result;
+}
+
+/*
+ * Test vectors for Key derivation
+ *
+ * These come from WUSB1.0[6.5.1], the vectors in WUSB1.0[A.1]
+ * (errata corrected in 2005/07).
+ */
+static const u8 stv_key_a1[16] __attribute__ ((__aligned__(4))) = {
+ 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87,
+ 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f
+};
+
+static const struct aes_ccm_nonce stv_keydvt_n_a1 = {
+ .sfn = { 0 },
+ .tkid = { 0x76, 0x98, 0x01, },
+ .dest_addr = { .data = { 0xbe, 0x00 } },
+ .src_addr = { .data = { 0x76, 0x98 } },
+};
+
+static const struct wusb_keydvt_out stv_keydvt_out_a1 = {
+ .kck = {
+ 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d,
+ 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f
+ },
+ .ptk = {
+ 0xc8, 0x70, 0x62, 0x82, 0xb6, 0x7c, 0xe9, 0x06,
+ 0x7b, 0xc5, 0x25, 0x69, 0xf2, 0x36, 0x61, 0x2d
+ }
+};
+
+/*
+ * Performa a test to make sure we match the vectors defined in
+ * WUSB1.0[A.1](Errata2006/12)
+ */
+static int wusb_key_derive_verify(void)
+{
+ int result = 0;
+ struct wusb_keydvt_out keydvt_out;
+ /* These come from WUSB1.0[A.1] + 2006/12 errata */
+ static const struct wusb_keydvt_in stv_keydvt_in_a1 = {
+ .hnonce = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
+ },
+ .dnonce = {
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f
+ }
+ };
+
+ result = wusb_key_derive(&keydvt_out, stv_key_a1, &stv_keydvt_n_a1,
+ &stv_keydvt_in_a1);
+ if (result < 0)
+ printk(KERN_ERR "E: WUSB key derivation test: "
+ "derivation failed: %d\n", result);
+ if (memcmp(&stv_keydvt_out_a1, &keydvt_out, sizeof(keydvt_out))) {
+ printk(KERN_ERR "E: WUSB key derivation test: "
+ "mismatch between key derivation result "
+ "and WUSB1.0[A1] Errata 2006/12\n");
+ printk(KERN_ERR "E: keydvt in: key\n");
+ wusb_key_dump(stv_key_a1, sizeof(stv_key_a1));
+ printk(KERN_ERR "E: keydvt in: nonce\n");
+ wusb_key_dump(&stv_keydvt_n_a1, sizeof(stv_keydvt_n_a1));
+ printk(KERN_ERR "E: keydvt in: hnonce & dnonce\n");
+ wusb_key_dump(&stv_keydvt_in_a1, sizeof(stv_keydvt_in_a1));
+ printk(KERN_ERR "E: keydvt out: KCK\n");
+ wusb_key_dump(&keydvt_out.kck, sizeof(keydvt_out.kck));
+ printk(KERN_ERR "E: keydvt out: PTK\n");
+ wusb_key_dump(&keydvt_out.ptk, sizeof(keydvt_out.ptk));
+ result = -EINVAL;
+ } else
+ result = 0;
+ return result;
+}
+
+/*
+ * Initialize crypto system
+ *
+ * FIXME: we do nothing now, other than verifying. Later on we'll
+ * cache the encryption stuff, so that's why we have a separate init.
+ */
+int wusb_crypto_init(void)
+{
+ int result;
+
+ if (debug_crypto_verify) {
+ result = wusb_key_derive_verify();
+ if (result < 0)
+ return result;
+ return wusb_oob_mic_verify();
+ }
+ return 0;
+}
+
+void wusb_crypto_exit(void)
+{
+ /* FIXME: free cached crypto transforms */
+}
diff --git a/drivers/staging/wusbcore/dev-sysfs.c b/drivers/staging/wusbcore/dev-sysfs.c
new file mode 100644
index 000000000000..67b0a4c412b2
--- /dev/null
+++ b/drivers/staging/wusbcore/dev-sysfs.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WUSB devices
+ * sysfs bindings
+ *
+ * Copyright (C) 2007 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * Get them out of the way...
+ */
+
+#include <linux/jiffies.h>
+#include <linux/ctype.h>
+#include <linux/workqueue.h>
+#include "wusbhc.h"
+
+static ssize_t wusb_disconnect_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct usb_device *usb_dev;
+ struct wusbhc *wusbhc;
+ unsigned command;
+ u8 port_idx;
+
+ if (sscanf(buf, "%u", &command) != 1)
+ return -EINVAL;
+ if (command == 0)
+ return size;
+ usb_dev = to_usb_device(dev);
+ wusbhc = wusbhc_get_by_usb_dev(usb_dev);
+ if (wusbhc == NULL)
+ return -ENODEV;
+
+ mutex_lock(&wusbhc->mutex);
+ port_idx = wusb_port_no_to_idx(usb_dev->portnum);
+ __wusbhc_dev_disable(wusbhc, port_idx);
+ mutex_unlock(&wusbhc->mutex);
+ wusbhc_put(wusbhc);
+ return size;
+}
+static DEVICE_ATTR_WO(wusb_disconnect);
+
+static ssize_t wusb_cdid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t result;
+ struct wusb_dev *wusb_dev;
+
+ wusb_dev = wusb_dev_get_by_usb_dev(to_usb_device(dev));
+ if (wusb_dev == NULL)
+ return -ENODEV;
+ result = sprintf(buf, "%16ph\n", wusb_dev->cdid.data);
+ wusb_dev_put(wusb_dev);
+ return result;
+}
+static DEVICE_ATTR_RO(wusb_cdid);
+
+static ssize_t wusb_ck_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int result;
+ struct usb_device *usb_dev;
+ struct wusbhc *wusbhc;
+ struct wusb_ckhdid ck;
+
+ result = sscanf(buf,
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx\n",
+ &ck.data[0] , &ck.data[1],
+ &ck.data[2] , &ck.data[3],
+ &ck.data[4] , &ck.data[5],
+ &ck.data[6] , &ck.data[7],
+ &ck.data[8] , &ck.data[9],
+ &ck.data[10], &ck.data[11],
+ &ck.data[12], &ck.data[13],
+ &ck.data[14], &ck.data[15]);
+ if (result != 16)
+ return -EINVAL;
+
+ usb_dev = to_usb_device(dev);
+ wusbhc = wusbhc_get_by_usb_dev(usb_dev);
+ if (wusbhc == NULL)
+ return -ENODEV;
+ result = wusb_dev_4way_handshake(wusbhc, usb_dev->wusb_dev, &ck);
+ memzero_explicit(&ck, sizeof(ck));
+ wusbhc_put(wusbhc);
+ return result < 0 ? result : size;
+}
+static DEVICE_ATTR_WO(wusb_ck);
+
+static struct attribute *wusb_dev_attrs[] = {
+ &dev_attr_wusb_disconnect.attr,
+ &dev_attr_wusb_cdid.attr,
+ &dev_attr_wusb_ck.attr,
+ NULL,
+};
+
+static const struct attribute_group wusb_dev_attr_group = {
+ .name = NULL, /* we want them in the same directory */
+ .attrs = wusb_dev_attrs,
+};
+
+int wusb_dev_sysfs_add(struct wusbhc *wusbhc, struct usb_device *usb_dev,
+ struct wusb_dev *wusb_dev)
+{
+ int result = sysfs_create_group(&usb_dev->dev.kobj,
+ &wusb_dev_attr_group);
+ struct device *dev = &usb_dev->dev;
+ if (result < 0)
+ dev_err(dev, "Cannot register WUSB-dev attributes: %d\n",
+ result);
+ return result;
+}
+
+void wusb_dev_sysfs_rm(struct wusb_dev *wusb_dev)
+{
+ struct usb_device *usb_dev = wusb_dev->usb_dev;
+ if (usb_dev)
+ sysfs_remove_group(&usb_dev->dev.kobj, &wusb_dev_attr_group);
+}
diff --git a/drivers/staging/wusbcore/devconnect.c b/drivers/staging/wusbcore/devconnect.c
new file mode 100644
index 000000000000..1170f8baf608
--- /dev/null
+++ b/drivers/staging/wusbcore/devconnect.c
@@ -0,0 +1,1085 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8])
+ * Device Connect handling
+ *
+ * Copyright (C) 2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ * FIXME: this file needs to be broken up, it's grown too big
+ *
+ *
+ * WUSB1.0[7.1, 7.5.1, ]
+ *
+ * WUSB device connection is kind of messy. Some background:
+ *
+ * When a device wants to connect it scans the UWB radio channels
+ * looking for a WUSB Channel; a WUSB channel is defined by MMCs
+ * (Micro Managed Commands or something like that) [see
+ * Design-overview for more on this] .
+ *
+ * So, device scans the radio, finds MMCs and thus a host and checks
+ * when the next DNTS is. It sends a Device Notification Connect
+ * (DN_Connect); the host picks it up (through nep.c and notif.c, ends
+ * up in wusb_devconnect_ack(), which creates a wusb_dev structure in
+ * wusbhc->port[port_number].wusb_dev), assigns an unauth address
+ * to the device (this means from 0x80 to 0xfe) and sends, in the MMC
+ * a Connect Ack Information Element (ConnAck IE).
+ *
+ * So now the device now has a WUSB address. From now on, we use
+ * that to talk to it in the RPipes.
+ *
+ * ASSUMPTIONS:
+ *
+ * - We use the the as device address the port number where it is
+ * connected (port 0 doesn't exist). For unauth, it is 128 + that.
+ *
+ * ROADMAP:
+ *
+ * This file contains the logic for doing that--entry points:
+ *
+ * wusb_devconnect_ack() Ack a device until _acked() called.
+ * Called by notif.c:wusb_handle_dn_connect()
+ * when a DN_Connect is received.
+ *
+ * wusb_devconnect_acked() Ack done, release resources.
+ *
+ * wusb_handle_dn_alive() Called by notif.c:wusb_handle_dn()
+ * for processing a DN_Alive pong from a device.
+ *
+ * wusb_handle_dn_disconnect()Called by notif.c:wusb_handle_dn() to
+ * process a disconnect request from a
+ * device.
+ *
+ * __wusb_dev_disable() Called by rh.c:wusbhc_rh_clear_port_feat() when
+ * disabling a port.
+ *
+ * wusb_devconnect_create() Called when creating the host by
+ * lc.c:wusbhc_create().
+ *
+ * wusb_devconnect_destroy() Cleanup called removing the host. Called
+ * by lc.c:wusbhc_destroy().
+ *
+ * Each Wireless USB host maintains a list of DN_Connect requests
+ * (actually we maintain a list of pending Connect Acks, the
+ * wusbhc->ca_list).
+ *
+ * LIFE CYCLE OF port->wusb_dev
+ *
+ * Before the @wusbhc structure put()s the reference it owns for
+ * port->wusb_dev [and clean the wusb_dev pointer], it needs to
+ * lock @wusbhc->mutex.
+ */
+
+#include <linux/jiffies.h>
+#include <linux/ctype.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/export.h>
+#include "wusbhc.h"
+
+static void wusbhc_devconnect_acked_work(struct work_struct *work);
+
+static void wusb_dev_free(struct wusb_dev *wusb_dev)
+{
+ kfree(wusb_dev);
+}
+
+static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc)
+{
+ struct wusb_dev *wusb_dev;
+
+ wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL);
+ if (wusb_dev == NULL)
+ goto err;
+
+ wusb_dev->wusbhc = wusbhc;
+
+ INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work);
+
+ return wusb_dev;
+err:
+ wusb_dev_free(wusb_dev);
+ return NULL;
+}
+
+
+/*
+ * Using the Connect-Ack list, fill out the @wusbhc Connect-Ack WUSB IE
+ * properly so that it can be added to the MMC.
+ *
+ * We just get the @wusbhc->ca_list and fill out the first four ones or
+ * less (per-spec WUSB1.0[7.5, before T7-38). If the ConnectAck WUSB
+ * IE is not allocated, we alloc it.
+ *
+ * @wusbhc->mutex must be taken
+ */
+static void wusbhc_fill_cack_ie(struct wusbhc *wusbhc)
+{
+ unsigned cnt;
+ struct wusb_dev *dev_itr;
+ struct wuie_connect_ack *cack_ie;
+
+ cack_ie = &wusbhc->cack_ie;
+ cnt = 0;
+ list_for_each_entry(dev_itr, &wusbhc->cack_list, cack_node) {
+ cack_ie->blk[cnt].CDID = dev_itr->cdid;
+ cack_ie->blk[cnt].bDeviceAddress = dev_itr->addr;
+ if (++cnt >= WUIE_ELT_MAX)
+ break;
+ }
+ cack_ie->hdr.bLength = sizeof(cack_ie->hdr)
+ + cnt * sizeof(cack_ie->blk[0]);
+}
+
+/*
+ * Register a new device that wants to connect
+ *
+ * A new device wants to connect, so we add it to the Connect-Ack
+ * list. We give it an address in the unauthorized range (bit 8 set);
+ * user space will have to drive authorization further on.
+ *
+ * @dev_addr: address to use for the device (which is also the port
+ * number).
+ *
+ * @wusbhc->mutex must be taken
+ */
+static struct wusb_dev *wusbhc_cack_add(struct wusbhc *wusbhc,
+ struct wusb_dn_connect *dnc,
+ const char *pr_cdid, u8 port_idx)
+{
+ struct device *dev = wusbhc->dev;
+ struct wusb_dev *wusb_dev;
+ int new_connection = wusb_dn_connect_new_connection(dnc);
+ u8 dev_addr;
+ int result;
+
+ /* Is it registered already? */
+ list_for_each_entry(wusb_dev, &wusbhc->cack_list, cack_node)
+ if (!memcmp(&wusb_dev->cdid, &dnc->CDID,
+ sizeof(wusb_dev->cdid)))
+ return wusb_dev;
+ /* We don't have it, create an entry, register it */
+ wusb_dev = wusb_dev_alloc(wusbhc);
+ if (wusb_dev == NULL)
+ return NULL;
+ wusb_dev_init(wusb_dev);
+ wusb_dev->cdid = dnc->CDID;
+ wusb_dev->port_idx = port_idx;
+
+ /*
+ * Devices are always available within the cluster reservation
+ * and since the hardware will take the intersection of the
+ * per-device availability and the cluster reservation, the
+ * per-device availability can simply be set to always
+ * available.
+ */
+ bitmap_fill(wusb_dev->availability.bm, UWB_NUM_MAS);
+
+ /* FIXME: handle reconnects instead of assuming connects are
+ always new. */
+ if (1 && new_connection == 0)
+ new_connection = 1;
+ if (new_connection) {
+ dev_addr = (port_idx + 2) | WUSB_DEV_ADDR_UNAUTH;
+
+ dev_info(dev, "Connecting new WUSB device to address %u, "
+ "port %u\n", dev_addr, port_idx);
+
+ result = wusb_set_dev_addr(wusbhc, wusb_dev, dev_addr);
+ if (result < 0)
+ return NULL;
+ }
+ wusb_dev->entry_ts = jiffies;
+ list_add_tail(&wusb_dev->cack_node, &wusbhc->cack_list);
+ wusbhc->cack_count++;
+ wusbhc_fill_cack_ie(wusbhc);
+
+ return wusb_dev;
+}
+
+/*
+ * Remove a Connect-Ack context entry from the HCs view
+ *
+ * @wusbhc->mutex must be taken
+ */
+static void wusbhc_cack_rm(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+ list_del_init(&wusb_dev->cack_node);
+ wusbhc->cack_count--;
+ wusbhc_fill_cack_ie(wusbhc);
+}
+
+/*
+ * @wusbhc->mutex must be taken */
+static
+void wusbhc_devconnect_acked(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+ wusbhc_cack_rm(wusbhc, wusb_dev);
+ if (wusbhc->cack_count)
+ wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr);
+ else
+ wusbhc_mmcie_rm(wusbhc, &wusbhc->cack_ie.hdr);
+}
+
+static void wusbhc_devconnect_acked_work(struct work_struct *work)
+{
+ struct wusb_dev *wusb_dev = container_of(work, struct wusb_dev,
+ devconnect_acked_work);
+ struct wusbhc *wusbhc = wusb_dev->wusbhc;
+
+ mutex_lock(&wusbhc->mutex);
+ wusbhc_devconnect_acked(wusbhc, wusb_dev);
+ mutex_unlock(&wusbhc->mutex);
+
+ wusb_dev_put(wusb_dev);
+}
+
+/*
+ * Ack a device for connection
+ *
+ * FIXME: docs
+ *
+ * @pr_cdid: Printable CDID...hex Use @dnc->cdid for the real deal.
+ *
+ * So we get the connect ack IE (may have been allocated already),
+ * find an empty connect block, an empty virtual port, create an
+ * address with it (see below), make it an unauth addr [bit 7 set] and
+ * set the MMC.
+ *
+ * Addresses: because WUSB hosts have no downstream hubs, we can do a
+ * 1:1 mapping between 'port number' and device
+ * address. This simplifies many things, as during this
+ * initial connect phase the USB stack has no knowledge of
+ * the device and hasn't assigned an address yet--we know
+ * USB's choose_address() will use the same heuristics we
+ * use here, so we can assume which address will be assigned.
+ *
+ * USB stack always assigns address 1 to the root hub, so
+ * to the port number we add 2 (thus virtual port #0 is
+ * addr #2).
+ *
+ * @wusbhc shall be referenced
+ */
+static
+void wusbhc_devconnect_ack(struct wusbhc *wusbhc, struct wusb_dn_connect *dnc,
+ const char *pr_cdid)
+{
+ int result;
+ struct device *dev = wusbhc->dev;
+ struct wusb_dev *wusb_dev;
+ struct wusb_port *port;
+ unsigned idx;
+
+ mutex_lock(&wusbhc->mutex);
+
+ /* Check we are not handling it already */
+ for (idx = 0; idx < wusbhc->ports_max; idx++) {
+ port = wusb_port_by_idx(wusbhc, idx);
+ if (port->wusb_dev
+ && memcmp(&dnc->CDID, &port->wusb_dev->cdid, sizeof(dnc->CDID)) == 0)
+ goto error_unlock;
+ }
+ /* Look up those fake ports we have for a free one */
+ for (idx = 0; idx < wusbhc->ports_max; idx++) {
+ port = wusb_port_by_idx(wusbhc, idx);
+ if ((port->status & USB_PORT_STAT_POWER)
+ && !(port->status & USB_PORT_STAT_CONNECTION))
+ break;
+ }
+ if (idx >= wusbhc->ports_max) {
+ dev_err(dev, "Host controller can't connect more devices "
+ "(%u already connected); device %s rejected\n",
+ wusbhc->ports_max, pr_cdid);
+ /* NOTE: we could send a WUIE_Disconnect here, but we haven't
+ * event acked, so the device will eventually timeout the
+ * connection, right? */
+ goto error_unlock;
+ }
+
+ /* Make sure we are using no crypto on that "virtual port" */
+ wusbhc->set_ptk(wusbhc, idx, 0, NULL, 0);
+
+ /* Grab a filled in Connect-Ack context, fill out the
+ * Connect-Ack Wireless USB IE, set the MMC */
+ wusb_dev = wusbhc_cack_add(wusbhc, dnc, pr_cdid, idx);
+ if (wusb_dev == NULL)
+ goto error_unlock;
+ result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr);
+ if (result < 0)
+ goto error_unlock;
+ /* Give the device at least 2ms (WUSB1.0[7.5.1p3]), let's do
+ * three for a good measure */
+ msleep(3);
+ port->wusb_dev = wusb_dev;
+ port->status |= USB_PORT_STAT_CONNECTION;
+ port->change |= USB_PORT_STAT_C_CONNECTION;
+ /* Now the port status changed to connected; hub_wq will
+ * pick the change up and try to reset the port to bring it to
+ * the enabled state--so this process returns up to the stack
+ * and it calls back into wusbhc_rh_port_reset().
+ */
+error_unlock:
+ mutex_unlock(&wusbhc->mutex);
+ return;
+
+}
+
+/*
+ * Disconnect a Wireless USB device from its fake port
+ *
+ * Marks the port as disconnected so that hub_wq can pick up the change
+ * and drops our knowledge about the device.
+ *
+ * Assumes there is a device connected
+ *
+ * @port_index: zero based port number
+ *
+ * NOTE: @wusbhc->mutex is locked
+ *
+ * WARNING: From here it is not very safe to access anything hanging off
+ * wusb_dev
+ */
+static void __wusbhc_dev_disconnect(struct wusbhc *wusbhc,
+ struct wusb_port *port)
+{
+ struct wusb_dev *wusb_dev = port->wusb_dev;
+
+ port->status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE
+ | USB_PORT_STAT_SUSPEND | USB_PORT_STAT_RESET
+ | USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED);
+ port->change |= USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE;
+ if (wusb_dev) {
+ dev_dbg(wusbhc->dev, "disconnecting device from port %d\n", wusb_dev->port_idx);
+ if (!list_empty(&wusb_dev->cack_node))
+ list_del_init(&wusb_dev->cack_node);
+ /* For the one in cack_add() */
+ wusb_dev_put(wusb_dev);
+ }
+ port->wusb_dev = NULL;
+
+ /* After a device disconnects, change the GTK (see [WUSB]
+ * section 6.2.11.2). */
+ if (wusbhc->active)
+ wusbhc_gtk_rekey(wusbhc);
+
+ /* The Wireless USB part has forgotten about the device already; now
+ * hub_wq's timer will pick up the disconnection and remove the USB
+ * device from the system
+ */
+}
+
+/*
+ * Refresh the list of keep alives to emit in the MMC
+ *
+ * We only publish the first four devices that have a coming timeout
+ * condition. Then when we are done processing those, we go for the
+ * next ones. We ignore the ones that have timed out already (they'll
+ * be purged).
+ *
+ * This might cause the first devices to timeout the last devices in
+ * the port array...FIXME: come up with a better algorithm?
+ *
+ * Note we can't do much about MMC's ops errors; we hope next refresh
+ * will kind of handle it.
+ *
+ * NOTE: @wusbhc->mutex is locked
+ */
+static void __wusbhc_keep_alive(struct wusbhc *wusbhc)
+{
+ struct device *dev = wusbhc->dev;
+ unsigned cnt;
+ struct wusb_dev *wusb_dev;
+ struct wusb_port *wusb_port;
+ struct wuie_keep_alive *ie = &wusbhc->keep_alive_ie;
+ unsigned keep_alives, old_keep_alives;
+
+ old_keep_alives = ie->hdr.bLength - sizeof(ie->hdr);
+ keep_alives = 0;
+ for (cnt = 0;
+ keep_alives < WUIE_ELT_MAX && cnt < wusbhc->ports_max;
+ cnt++) {
+ unsigned tt = msecs_to_jiffies(wusbhc->trust_timeout);
+
+ wusb_port = wusb_port_by_idx(wusbhc, cnt);
+ wusb_dev = wusb_port->wusb_dev;
+
+ if (wusb_dev == NULL)
+ continue;
+ if (wusb_dev->usb_dev == NULL)
+ continue;
+
+ if (time_after(jiffies, wusb_dev->entry_ts + tt)) {
+ dev_err(dev, "KEEPALIVE: device %u timed out\n",
+ wusb_dev->addr);
+ __wusbhc_dev_disconnect(wusbhc, wusb_port);
+ } else if (time_after(jiffies, wusb_dev->entry_ts + tt/3)) {
+ /* Approaching timeout cut off, need to refresh */
+ ie->bDeviceAddress[keep_alives++] = wusb_dev->addr;
+ }
+ }
+ if (keep_alives & 0x1) /* pad to even number ([WUSB] section 7.5.9) */
+ ie->bDeviceAddress[keep_alives++] = 0x7f;
+ ie->hdr.bLength = sizeof(ie->hdr) +
+ keep_alives*sizeof(ie->bDeviceAddress[0]);
+ if (keep_alives > 0)
+ wusbhc_mmcie_set(wusbhc, 10, 5, &ie->hdr);
+ else if (old_keep_alives != 0)
+ wusbhc_mmcie_rm(wusbhc, &ie->hdr);
+}
+
+/*
+ * Do a run through all devices checking for timeouts
+ */
+static void wusbhc_keep_alive_run(struct work_struct *ws)
+{
+ struct delayed_work *dw = to_delayed_work(ws);
+ struct wusbhc *wusbhc = container_of(dw, struct wusbhc, keep_alive_timer);
+
+ mutex_lock(&wusbhc->mutex);
+ __wusbhc_keep_alive(wusbhc);
+ mutex_unlock(&wusbhc->mutex);
+
+ queue_delayed_work(wusbd, &wusbhc->keep_alive_timer,
+ msecs_to_jiffies(wusbhc->trust_timeout / 2));
+}
+
+/*
+ * Find the wusb_dev from its device address.
+ *
+ * The device can be found directly from the address (see
+ * wusb_cack_add() for where the device address is set to port_idx
+ * +2), except when the address is zero.
+ */
+static struct wusb_dev *wusbhc_find_dev_by_addr(struct wusbhc *wusbhc, u8 addr)
+{
+ int p;
+
+ if (addr == 0xff) /* unconnected */
+ return NULL;
+
+ if (addr > 0) {
+ int port = (addr & ~0x80) - 2;
+ if (port < 0 || port >= wusbhc->ports_max)
+ return NULL;
+ return wusb_port_by_idx(wusbhc, port)->wusb_dev;
+ }
+
+ /* Look for the device with address 0. */
+ for (p = 0; p < wusbhc->ports_max; p++) {
+ struct wusb_dev *wusb_dev = wusb_port_by_idx(wusbhc, p)->wusb_dev;
+ if (wusb_dev && wusb_dev->addr == addr)
+ return wusb_dev;
+ }
+ return NULL;
+}
+
+/*
+ * Handle a DN_Alive notification (WUSB1.0[7.6.1])
+ *
+ * This just updates the device activity timestamp and then refreshes
+ * the keep alive IE.
+ *
+ * @wusbhc shall be referenced and unlocked
+ */
+static void wusbhc_handle_dn_alive(struct wusbhc *wusbhc, u8 srcaddr)
+{
+ struct wusb_dev *wusb_dev;
+
+ mutex_lock(&wusbhc->mutex);
+ wusb_dev = wusbhc_find_dev_by_addr(wusbhc, srcaddr);
+ if (wusb_dev == NULL) {
+ dev_dbg(wusbhc->dev, "ignoring DN_Alive from unconnected device %02x\n",
+ srcaddr);
+ } else {
+ wusb_dev->entry_ts = jiffies;
+ __wusbhc_keep_alive(wusbhc);
+ }
+ mutex_unlock(&wusbhc->mutex);
+}
+
+/*
+ * Handle a DN_Connect notification (WUSB1.0[7.6.1])
+ *
+ * @wusbhc
+ * @pkt_hdr
+ * @size: Size of the buffer where the notification resides; if the
+ * notification data suggests there should be more data than
+ * available, an error will be signaled and the whole buffer
+ * consumed.
+ *
+ * @wusbhc->mutex shall be held
+ */
+static void wusbhc_handle_dn_connect(struct wusbhc *wusbhc,
+ struct wusb_dn_hdr *dn_hdr,
+ size_t size)
+{
+ struct device *dev = wusbhc->dev;
+ struct wusb_dn_connect *dnc;
+ char pr_cdid[WUSB_CKHDID_STRSIZE];
+ static const char *beacon_behaviour[] = {
+ "reserved",
+ "self-beacon",
+ "directed-beacon",
+ "no-beacon"
+ };
+
+ if (size < sizeof(*dnc)) {
+ dev_err(dev, "DN CONNECT: short notification (%zu < %zu)\n",
+ size, sizeof(*dnc));
+ return;
+ }
+
+ dnc = container_of(dn_hdr, struct wusb_dn_connect, hdr);
+ sprintf(pr_cdid, "%16ph", dnc->CDID.data);
+ dev_info(dev, "DN CONNECT: device %s @ %x (%s) wants to %s\n",
+ pr_cdid,
+ wusb_dn_connect_prev_dev_addr(dnc),
+ beacon_behaviour[wusb_dn_connect_beacon_behavior(dnc)],
+ wusb_dn_connect_new_connection(dnc) ? "connect" : "reconnect");
+ /* ACK the connect */
+ wusbhc_devconnect_ack(wusbhc, dnc, pr_cdid);
+}
+
+/*
+ * Handle a DN_Disconnect notification (WUSB1.0[7.6.1])
+ *
+ * Device is going down -- do the disconnect.
+ *
+ * @wusbhc shall be referenced and unlocked
+ */
+static void wusbhc_handle_dn_disconnect(struct wusbhc *wusbhc, u8 srcaddr)
+{
+ struct device *dev = wusbhc->dev;
+ struct wusb_dev *wusb_dev;
+
+ mutex_lock(&wusbhc->mutex);
+ wusb_dev = wusbhc_find_dev_by_addr(wusbhc, srcaddr);
+ if (wusb_dev == NULL) {
+ dev_dbg(dev, "ignoring DN DISCONNECT from unconnected device %02x\n",
+ srcaddr);
+ } else {
+ dev_info(dev, "DN DISCONNECT: device 0x%02x going down\n",
+ wusb_dev->addr);
+ __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc,
+ wusb_dev->port_idx));
+ }
+ mutex_unlock(&wusbhc->mutex);
+}
+
+/*
+ * Handle a Device Notification coming a host
+ *
+ * The Device Notification comes from a host (HWA, DWA or WHCI)
+ * wrapped in a set of headers. Somebody else has peeled off those
+ * headers for us and we just get one Device Notifications.
+ *
+ * Invalid DNs (e.g., too short) are discarded.
+ *
+ * @wusbhc shall be referenced
+ *
+ * FIXMES:
+ * - implement priorities as in WUSB1.0[Table 7-55]?
+ */
+void wusbhc_handle_dn(struct wusbhc *wusbhc, u8 srcaddr,
+ struct wusb_dn_hdr *dn_hdr, size_t size)
+{
+ struct device *dev = wusbhc->dev;
+
+ if (size < sizeof(struct wusb_dn_hdr)) {
+ dev_err(dev, "DN data shorter than DN header (%d < %d)\n",
+ (int)size, (int)sizeof(struct wusb_dn_hdr));
+ return;
+ }
+ switch (dn_hdr->bType) {
+ case WUSB_DN_CONNECT:
+ wusbhc_handle_dn_connect(wusbhc, dn_hdr, size);
+ break;
+ case WUSB_DN_ALIVE:
+ wusbhc_handle_dn_alive(wusbhc, srcaddr);
+ break;
+ case WUSB_DN_DISCONNECT:
+ wusbhc_handle_dn_disconnect(wusbhc, srcaddr);
+ break;
+ case WUSB_DN_MASAVAILCHANGED:
+ case WUSB_DN_RWAKE:
+ case WUSB_DN_SLEEP:
+ /* FIXME: handle these DNs. */
+ break;
+ case WUSB_DN_EPRDY:
+ /* The hardware handles these. */
+ break;
+ default:
+ dev_warn(dev, "unknown DN %u (%d octets) from %u\n",
+ dn_hdr->bType, (int)size, srcaddr);
+ }
+}
+EXPORT_SYMBOL_GPL(wusbhc_handle_dn);
+
+/*
+ * Disconnect a WUSB device from a the cluster
+ *
+ * @wusbhc
+ * @port Fake port where the device is (wusbhc index, not USB port number).
+ *
+ * In Wireless USB, a disconnect is basically telling the device he is
+ * being disconnected and forgetting about him.
+ *
+ * We send the device a Device Disconnect IE (WUSB1.0[7.5.11]) for 100
+ * ms and then keep going.
+ *
+ * We don't do much in case of error; we always pretend we disabled
+ * the port and disconnected the device. If physically the request
+ * didn't get there (many things can fail in the way there), the stack
+ * will reject the device's communication attempts.
+ *
+ * @wusbhc should be refcounted and locked
+ */
+void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port_idx)
+{
+ int result;
+ struct device *dev = wusbhc->dev;
+ struct wusb_dev *wusb_dev;
+ struct wuie_disconnect *ie;
+
+ wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev;
+ if (wusb_dev == NULL) {
+ /* reset no device? ignore */
+ dev_dbg(dev, "DISCONNECT: no device at port %u, ignoring\n",
+ port_idx);
+ return;
+ }
+ __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx));
+
+ ie = kzalloc(sizeof(*ie), GFP_KERNEL);
+ if (ie == NULL)
+ return;
+ ie->hdr.bLength = sizeof(*ie);
+ ie->hdr.bIEIdentifier = WUIE_ID_DEVICE_DISCONNECT;
+ ie->bDeviceAddress = wusb_dev->addr;
+ result = wusbhc_mmcie_set(wusbhc, 0, 0, &ie->hdr);
+ if (result < 0)
+ dev_err(dev, "DISCONNECT: can't set MMC: %d\n", result);
+ else {
+ /* At least 6 MMCs, assuming at least 1 MMC per zone. */
+ msleep(7*4);
+ wusbhc_mmcie_rm(wusbhc, &ie->hdr);
+ }
+ kfree(ie);
+}
+
+/*
+ * Walk over the BOS descriptor, verify and grok it
+ *
+ * @usb_dev: referenced
+ * @wusb_dev: referenced and unlocked
+ *
+ * The BOS descriptor is defined at WUSB1.0[7.4.1], and it defines a
+ * "flexible" way to wrap all kinds of descriptors inside an standard
+ * descriptor (wonder why they didn't use normal descriptors,
+ * btw). Not like they lack code.
+ *
+ * At the end we go to look for the WUSB Device Capabilities
+ * (WUSB1.0[7.4.1.1]) that is wrapped in a device capability descriptor
+ * that is part of the BOS descriptor set. That tells us what does the
+ * device support (dual role, beacon type, UWB PHY rates).
+ */
+static int wusb_dev_bos_grok(struct usb_device *usb_dev,
+ struct wusb_dev *wusb_dev,
+ struct usb_bos_descriptor *bos, size_t desc_size)
+{
+ ssize_t result;
+ struct device *dev = &usb_dev->dev;
+ void *itr, *top;
+
+ /* Walk over BOS capabilities, verify them */
+ itr = (void *)bos + sizeof(*bos);
+ top = itr + desc_size - sizeof(*bos);
+ while (itr < top) {
+ struct usb_dev_cap_header *cap_hdr = itr;
+ size_t cap_size;
+ u8 cap_type;
+ if (top - itr < sizeof(*cap_hdr)) {
+ dev_err(dev, "Device BUG? premature end of BOS header "
+ "data [offset 0x%02x]: only %zu bytes left\n",
+ (int)(itr - (void *)bos), top - itr);
+ result = -ENOSPC;
+ goto error_bad_cap;
+ }
+ cap_size = cap_hdr->bLength;
+ cap_type = cap_hdr->bDevCapabilityType;
+ if (cap_size == 0)
+ break;
+ if (cap_size > top - itr) {
+ dev_err(dev, "Device BUG? premature end of BOS data "
+ "[offset 0x%02x cap %02x %zu bytes]: "
+ "only %zu bytes left\n",
+ (int)(itr - (void *)bos),
+ cap_type, cap_size, top - itr);
+ result = -EBADF;
+ goto error_bad_cap;
+ }
+ switch (cap_type) {
+ case USB_CAP_TYPE_WIRELESS_USB:
+ if (cap_size != sizeof(*wusb_dev->wusb_cap_descr))
+ dev_err(dev, "Device BUG? WUSB Capability "
+ "descriptor is %zu bytes vs %zu "
+ "needed\n", cap_size,
+ sizeof(*wusb_dev->wusb_cap_descr));
+ else
+ wusb_dev->wusb_cap_descr = itr;
+ break;
+ default:
+ dev_err(dev, "BUG? Unknown BOS capability 0x%02x "
+ "(%zu bytes) at offset 0x%02x\n", cap_type,
+ cap_size, (int)(itr - (void *)bos));
+ }
+ itr += cap_size;
+ }
+ result = 0;
+error_bad_cap:
+ return result;
+}
+
+/*
+ * Add information from the BOS descriptors to the device
+ *
+ * @usb_dev: referenced
+ * @wusb_dev: referenced and unlocked
+ *
+ * So what we do is we alloc a space for the BOS descriptor of 64
+ * bytes; read the first four bytes which include the wTotalLength
+ * field (WUSB1.0[T7-26]) and if it fits in those 64 bytes, read the
+ * whole thing. If not we realloc to that size.
+ *
+ * Then we call the groking function, that will fill up
+ * wusb_dev->wusb_cap_descr, which is what we'll need later on.
+ */
+static int wusb_dev_bos_add(struct usb_device *usb_dev,
+ struct wusb_dev *wusb_dev)
+{
+ ssize_t result;
+ struct device *dev = &usb_dev->dev;
+ struct usb_bos_descriptor *bos;
+ size_t alloc_size = 32, desc_size = 4;
+
+ bos = kmalloc(alloc_size, GFP_KERNEL);
+ if (bos == NULL)
+ return -ENOMEM;
+ result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size);
+ if (result < 4) {
+ dev_err(dev, "Can't get BOS descriptor or too short: %zd\n",
+ result);
+ goto error_get_descriptor;
+ }
+ desc_size = le16_to_cpu(bos->wTotalLength);
+ if (desc_size >= alloc_size) {
+ kfree(bos);
+ alloc_size = desc_size;
+ bos = kmalloc(alloc_size, GFP_KERNEL);
+ if (bos == NULL)
+ return -ENOMEM;
+ }
+ result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size);
+ if (result < 0 || result != desc_size) {
+ dev_err(dev, "Can't get BOS descriptor or too short (need "
+ "%zu bytes): %zd\n", desc_size, result);
+ goto error_get_descriptor;
+ }
+ if (result < sizeof(*bos)
+ || le16_to_cpu(bos->wTotalLength) != desc_size) {
+ dev_err(dev, "Can't get BOS descriptor or too short (need "
+ "%zu bytes): %zd\n", desc_size, result);
+ goto error_get_descriptor;
+ }
+
+ result = wusb_dev_bos_grok(usb_dev, wusb_dev, bos, result);
+ if (result < 0)
+ goto error_bad_bos;
+ wusb_dev->bos = bos;
+ return 0;
+
+error_bad_bos:
+error_get_descriptor:
+ kfree(bos);
+ wusb_dev->wusb_cap_descr = NULL;
+ return result;
+}
+
+static void wusb_dev_bos_rm(struct wusb_dev *wusb_dev)
+{
+ kfree(wusb_dev->bos);
+ wusb_dev->wusb_cap_descr = NULL;
+};
+
+/*
+ * USB stack's device addition Notifier Callback
+ *
+ * Called from drivers/usb/core/hub.c when a new device is added; we
+ * use this hook to perform certain WUSB specific setup work on the
+ * new device. As well, it is the first time we can connect the
+ * wusb_dev and the usb_dev. So we note it down in wusb_dev and take a
+ * reference that we'll drop.
+ *
+ * First we need to determine if the device is a WUSB device (else we
+ * ignore it). For that we use the speed setting (USB_SPEED_WIRELESS)
+ * [FIXME: maybe we'd need something more definitive]. If so, we track
+ * it's usb_busd and from there, the WUSB HC.
+ *
+ * Because all WUSB HCs are contained in a 'struct wusbhc', voila, we
+ * get the wusbhc for the device.
+ *
+ * We have a reference on @usb_dev (as we are called at the end of its
+ * enumeration).
+ *
+ * NOTE: @usb_dev locked
+ */
+static void wusb_dev_add_ncb(struct usb_device *usb_dev)
+{
+ int result = 0;
+ struct wusb_dev *wusb_dev;
+ struct wusbhc *wusbhc;
+ struct device *dev = &usb_dev->dev;
+ u8 port_idx;
+
+ if (usb_dev->wusb == 0 || usb_dev->devnum == 1)
+ return; /* skip non wusb and wusb RHs */
+
+ usb_set_device_state(usb_dev, USB_STATE_UNAUTHENTICATED);
+
+ wusbhc = wusbhc_get_by_usb_dev(usb_dev);
+ if (wusbhc == NULL)
+ goto error_nodev;
+ mutex_lock(&wusbhc->mutex);
+ wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev);
+ port_idx = wusb_port_no_to_idx(usb_dev->portnum);
+ mutex_unlock(&wusbhc->mutex);
+ if (wusb_dev == NULL)
+ goto error_nodev;
+ wusb_dev->usb_dev = usb_get_dev(usb_dev);
+ usb_dev->wusb_dev = wusb_dev_get(wusb_dev);
+ result = wusb_dev_sec_add(wusbhc, usb_dev, wusb_dev);
+ if (result < 0) {
+ dev_err(dev, "Cannot enable security: %d\n", result);
+ goto error_sec_add;
+ }
+ /* Now query the device for it's BOS and attach it to wusb_dev */
+ result = wusb_dev_bos_add(usb_dev, wusb_dev);
+ if (result < 0) {
+ dev_err(dev, "Cannot get BOS descriptors: %d\n", result);
+ goto error_bos_add;
+ }
+ result = wusb_dev_sysfs_add(wusbhc, usb_dev, wusb_dev);
+ if (result < 0)
+ goto error_add_sysfs;
+out:
+ wusb_dev_put(wusb_dev);
+ wusbhc_put(wusbhc);
+error_nodev:
+ return;
+
+error_add_sysfs:
+ wusb_dev_bos_rm(wusb_dev);
+error_bos_add:
+ wusb_dev_sec_rm(wusb_dev);
+error_sec_add:
+ mutex_lock(&wusbhc->mutex);
+ __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx));
+ mutex_unlock(&wusbhc->mutex);
+ goto out;
+}
+
+/*
+ * Undo all the steps done at connection by the notifier callback
+ *
+ * NOTE: @usb_dev locked
+ */
+static void wusb_dev_rm_ncb(struct usb_device *usb_dev)
+{
+ struct wusb_dev *wusb_dev = usb_dev->wusb_dev;
+
+ if (usb_dev->wusb == 0 || usb_dev->devnum == 1)
+ return; /* skip non wusb and wusb RHs */
+
+ wusb_dev_sysfs_rm(wusb_dev);
+ wusb_dev_bos_rm(wusb_dev);
+ wusb_dev_sec_rm(wusb_dev);
+ wusb_dev->usb_dev = NULL;
+ usb_dev->wusb_dev = NULL;
+ wusb_dev_put(wusb_dev);
+ usb_put_dev(usb_dev);
+}
+
+/*
+ * Handle notifications from the USB stack (notifier call back)
+ *
+ * This is called when the USB stack does a
+ * usb_{bus,device}_{add,remove}() so we can do WUSB specific
+ * handling. It is called with [for the case of
+ * USB_DEVICE_{ADD,REMOVE} with the usb_dev locked.
+ */
+int wusb_usb_ncb(struct notifier_block *nb, unsigned long val,
+ void *priv)
+{
+ int result = NOTIFY_OK;
+
+ switch (val) {
+ case USB_DEVICE_ADD:
+ wusb_dev_add_ncb(priv);
+ break;
+ case USB_DEVICE_REMOVE:
+ wusb_dev_rm_ncb(priv);
+ break;
+ case USB_BUS_ADD:
+ /* ignore (for now) */
+ case USB_BUS_REMOVE:
+ break;
+ default:
+ WARN_ON(1);
+ result = NOTIFY_BAD;
+ }
+ return result;
+}
+
+/*
+ * Return a referenced wusb_dev given a @wusbhc and @usb_dev
+ */
+struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *wusbhc,
+ struct usb_device *usb_dev)
+{
+ struct wusb_dev *wusb_dev;
+ u8 port_idx;
+
+ port_idx = wusb_port_no_to_idx(usb_dev->portnum);
+ BUG_ON(port_idx > wusbhc->ports_max);
+ wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev;
+ if (wusb_dev != NULL) /* ops, device is gone */
+ wusb_dev_get(wusb_dev);
+ return wusb_dev;
+}
+EXPORT_SYMBOL_GPL(__wusb_dev_get_by_usb_dev);
+
+void wusb_dev_destroy(struct kref *_wusb_dev)
+{
+ struct wusb_dev *wusb_dev = container_of(_wusb_dev, struct wusb_dev, refcnt);
+
+ list_del_init(&wusb_dev->cack_node);
+ wusb_dev_free(wusb_dev);
+}
+EXPORT_SYMBOL_GPL(wusb_dev_destroy);
+
+/*
+ * Create all the device connect handling infrastructure
+ *
+ * This is basically the device info array, Connect Acknowledgement
+ * (cack) lists, keep-alive timers (and delayed work thread).
+ */
+int wusbhc_devconnect_create(struct wusbhc *wusbhc)
+{
+ wusbhc->keep_alive_ie.hdr.bIEIdentifier = WUIE_ID_KEEP_ALIVE;
+ wusbhc->keep_alive_ie.hdr.bLength = sizeof(wusbhc->keep_alive_ie.hdr);
+ INIT_DELAYED_WORK(&wusbhc->keep_alive_timer, wusbhc_keep_alive_run);
+
+ wusbhc->cack_ie.hdr.bIEIdentifier = WUIE_ID_CONNECTACK;
+ wusbhc->cack_ie.hdr.bLength = sizeof(wusbhc->cack_ie.hdr);
+ INIT_LIST_HEAD(&wusbhc->cack_list);
+
+ return 0;
+}
+
+/*
+ * Release all resources taken by the devconnect stuff
+ */
+void wusbhc_devconnect_destroy(struct wusbhc *wusbhc)
+{
+ /* no op */
+}
+
+/*
+ * wusbhc_devconnect_start - start accepting device connections
+ * @wusbhc: the WUSB HC
+ *
+ * Sets the Host Info IE to accept all new connections.
+ *
+ * FIXME: This also enables the keep alives but this is not necessary
+ * until there are connected and authenticated devices.
+ */
+int wusbhc_devconnect_start(struct wusbhc *wusbhc)
+{
+ struct device *dev = wusbhc->dev;
+ struct wuie_host_info *hi;
+ int result;
+
+ hi = kzalloc(sizeof(*hi), GFP_KERNEL);
+ if (hi == NULL)
+ return -ENOMEM;
+
+ hi->hdr.bLength = sizeof(*hi);
+ hi->hdr.bIEIdentifier = WUIE_ID_HOST_INFO;
+ hi->attributes = cpu_to_le16((wusbhc->rsv->stream << 3) | WUIE_HI_CAP_ALL);
+ hi->CHID = wusbhc->chid;
+ result = wusbhc_mmcie_set(wusbhc, 0, 0, &hi->hdr);
+ if (result < 0) {
+ dev_err(dev, "Cannot add Host Info MMCIE: %d\n", result);
+ goto error_mmcie_set;
+ }
+ wusbhc->wuie_host_info = hi;
+
+ queue_delayed_work(wusbd, &wusbhc->keep_alive_timer,
+ msecs_to_jiffies(wusbhc->trust_timeout / 2));
+
+ return 0;
+
+error_mmcie_set:
+ kfree(hi);
+ return result;
+}
+
+/*
+ * wusbhc_devconnect_stop - stop managing connected devices
+ * @wusbhc: the WUSB HC
+ *
+ * Disconnects any devices still connected, stops the keep alives and
+ * removes the Host Info IE.
+ */
+void wusbhc_devconnect_stop(struct wusbhc *wusbhc)
+{
+ int i;
+
+ mutex_lock(&wusbhc->mutex);
+ for (i = 0; i < wusbhc->ports_max; i++) {
+ if (wusbhc->port[i].wusb_dev)
+ __wusbhc_dev_disconnect(wusbhc, &wusbhc->port[i]);
+ }
+ mutex_unlock(&wusbhc->mutex);
+
+ cancel_delayed_work_sync(&wusbhc->keep_alive_timer);
+ wusbhc_mmcie_rm(wusbhc, &wusbhc->wuie_host_info->hdr);
+ kfree(wusbhc->wuie_host_info);
+ wusbhc->wuie_host_info = NULL;
+}
+
+/*
+ * wusb_set_dev_addr - set the WUSB device address used by the host
+ * @wusbhc: the WUSB HC the device is connect to
+ * @wusb_dev: the WUSB device
+ * @addr: new device address
+ */
+int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, u8 addr)
+{
+ int result;
+
+ wusb_dev->addr = addr;
+ result = wusbhc->dev_info_set(wusbhc, wusb_dev);
+ if (result < 0)
+ dev_err(wusbhc->dev, "device %d: failed to set device "
+ "address\n", wusb_dev->port_idx);
+ else
+ dev_info(wusbhc->dev, "device %d: %s addr %u\n",
+ wusb_dev->port_idx,
+ (addr & WUSB_DEV_ADDR_UNAUTH) ? "unauth" : "auth",
+ wusb_dev->addr);
+
+ return result;
+}
diff --git a/drivers/staging/wusbcore/host/Kconfig b/drivers/staging/wusbcore/host/Kconfig
new file mode 100644
index 000000000000..9a73f9360a08
--- /dev/null
+++ b/drivers/staging/wusbcore/host/Kconfig
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config USB_WHCI_HCD
+ tristate "Wireless USB Host Controller Interface (WHCI) driver"
+ depends on USB_PCI && USB && UWB
+ select USB_WUSB
+ select UWB_WHCI
+ help
+ A driver for PCI-based Wireless USB Host Controllers that are
+ compliant with the WHCI specification.
+
+ To compile this driver a module, choose M here: the module
+ will be called "whci-hcd".
+
+config USB_HWA_HCD
+ tristate "Host Wire Adapter (HWA) driver"
+ depends on USB && UWB
+ select USB_WUSB
+ select UWB_HWA
+ help
+ This driver enables you to connect Wireless USB devices to
+ your system using a Host Wire Adaptor USB dongle. This is an
+ UWB Radio Controller and WUSB Host Controller connected to
+ your machine via USB (specified in WUSB1.0).
+
+ To compile this driver a module, choose M here: the module
+ will be called "hwa-hc".
+
diff --git a/drivers/staging/wusbcore/host/Makefile b/drivers/staging/wusbcore/host/Makefile
new file mode 100644
index 000000000000..d65ee8a73e21
--- /dev/null
+++ b/drivers/staging/wusbcore/host/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_USB_WHCI_HCD) += whci/
+obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o
diff --git a/drivers/staging/wusbcore/host/hwa-hc.c b/drivers/staging/wusbcore/host/hwa-hc.c
new file mode 100644
index 000000000000..8d959e91fe27
--- /dev/null
+++ b/drivers/staging/wusbcore/host/hwa-hc.c
@@ -0,0 +1,875 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Host Wire Adapter:
+ * Driver glue, HWA-specific functions, bridges to WAHC and WUSBHC
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * The HWA driver is a simple layer that forwards requests to the WAHC
+ * (Wire Adater Host Controller) or WUSBHC (Wireless USB Host
+ * Controller) layers.
+ *
+ * Host Wire Adapter is the 'WUSB 1.0 standard' name for Wireless-USB
+ * Host Controller that is connected to your system via USB (a USB
+ * dongle that implements a USB host...). There is also a Device Wired
+ * Adaptor, DWA (Wireless USB hub) that uses the same mechanism for
+ * transferring data (it is after all a USB host connected via
+ * Wireless USB), we have a common layer called Wire Adapter Host
+ * Controller that does all the hard work. The WUSBHC (Wireless USB
+ * Host Controller) is the part common to WUSB Host Controllers, the
+ * HWA and the PCI-based one, that is implemented following the WHCI
+ * spec. All these layers are implemented in ../wusbcore.
+ *
+ * The main functions are hwahc_op_urb_{en,de}queue(), that pass the
+ * job of converting a URB to a Wire Adapter
+ *
+ * Entry points:
+ *
+ * hwahc_driver_*() Driver initialization, registration and
+ * teardown.
+ *
+ * hwahc_probe() New device came up, create an instance for
+ * it [from device enumeration].
+ *
+ * hwahc_disconnect() Remove device instance [from device
+ * enumeration].
+ *
+ * [__]hwahc_op_*() Host-Wire-Adaptor specific functions for
+ * starting/stopping/etc (some might be made also
+ * DWA).
+ */
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/completion.h>
+#include "../wa-hc.h"
+#include "../wusbhc.h"
+
+struct hwahc {
+ struct wusbhc wusbhc; /* has to be 1st */
+ struct wahc wa;
+};
+
+/*
+ * FIXME should be wusbhc
+ *
+ * NOTE: we need to cache the Cluster ID because later...there is no
+ * way to get it :)
+ */
+static int __hwahc_set_cluster_id(struct hwahc *hwahc, u8 cluster_id)
+{
+ int result;
+ struct wusbhc *wusbhc = &hwahc->wusbhc;
+ struct wahc *wa = &hwahc->wa;
+ struct device *dev = &wa->usb_iface->dev;
+
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_CLUSTER_ID,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ cluster_id,
+ wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (result < 0)
+ dev_err(dev, "Cannot set WUSB Cluster ID to 0x%02x: %d\n",
+ cluster_id, result);
+ else
+ wusbhc->cluster_id = cluster_id;
+ dev_info(dev, "Wireless USB Cluster ID set to 0x%02x\n", cluster_id);
+ return result;
+}
+
+static int __hwahc_op_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+
+ return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_NUM_DNTS,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ interval << 8 | slots,
+ wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+/*
+ * Reset a WUSB host controller and wait for it to complete doing it.
+ *
+ * @usb_hcd: Pointer to WUSB Host Controller instance.
+ *
+ */
+static int hwahc_op_reset(struct usb_hcd *usb_hcd)
+{
+ int result;
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct device *dev = &hwahc->wa.usb_iface->dev;
+
+ mutex_lock(&wusbhc->mutex);
+ wa_nep_disarm(&hwahc->wa);
+ result = __wa_set_feature(&hwahc->wa, WA_RESET);
+ if (result < 0) {
+ dev_err(dev, "error commanding HC to reset: %d\n", result);
+ goto error_unlock;
+ }
+ result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0);
+ if (result < 0) {
+ dev_err(dev, "error waiting for HC to reset: %d\n", result);
+ goto error_unlock;
+ }
+error_unlock:
+ mutex_unlock(&wusbhc->mutex);
+ return result;
+}
+
+/*
+ * FIXME: break this function up
+ */
+static int hwahc_op_start(struct usb_hcd *usb_hcd)
+{
+ u8 addr;
+ int result;
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ result = -ENOSPC;
+ mutex_lock(&wusbhc->mutex);
+ addr = wusb_cluster_id_get();
+ if (addr == 0)
+ goto error_cluster_id_get;
+ result = __hwahc_set_cluster_id(hwahc, addr);
+ if (result < 0)
+ goto error_set_cluster_id;
+
+ usb_hcd->uses_new_polling = 1;
+ set_bit(HCD_FLAG_POLL_RH, &usb_hcd->flags);
+ usb_hcd->state = HC_STATE_RUNNING;
+
+ /*
+ * prevent USB core from suspending the root hub since
+ * bus_suspend and bus_resume are not yet supported.
+ */
+ pm_runtime_get_noresume(&usb_hcd->self.root_hub->dev);
+
+ result = 0;
+out:
+ mutex_unlock(&wusbhc->mutex);
+ return result;
+
+error_set_cluster_id:
+ wusb_cluster_id_put(addr);
+error_cluster_id_get:
+ goto out;
+
+}
+
+/*
+ * No need to abort pipes, as when this is called, all the children
+ * has been disconnected and that has done it [through
+ * usb_disable_interface() -> usb_disable_endpoint() ->
+ * hwahc_op_ep_disable() - >rpipe_ep_disable()].
+ */
+static void hwahc_op_stop(struct usb_hcd *usb_hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+
+ mutex_lock(&wusbhc->mutex);
+ wusb_cluster_id_put(wusbhc->cluster_id);
+ mutex_unlock(&wusbhc->mutex);
+}
+
+static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+
+ /*
+ * We cannot query the HWA for the WUSB time since that requires sending
+ * a synchronous URB and this function can be called in_interrupt.
+ * Instead, query the USB frame number for our parent and use that.
+ */
+ return usb_get_current_frame_number(wa->usb_dev);
+}
+
+static int hwahc_op_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
+ gfp_t gfp)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ return wa_urb_enqueue(&hwahc->wa, urb->ep, urb, gfp);
+}
+
+static int hwahc_op_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb,
+ int status)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ return wa_urb_dequeue(&hwahc->wa, urb, status);
+}
+
+/*
+ * Release resources allocated for an endpoint
+ *
+ * If there is an associated rpipe to this endpoint, go ahead and put it.
+ */
+static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ rpipe_ep_disable(&hwahc->wa, ep);
+}
+
+static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc)
+{
+ int result;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct device *dev = &hwahc->wa.usb_iface->dev;
+
+ result = __wa_set_feature(&hwahc->wa, WA_ENABLE);
+ if (result < 0) {
+ dev_err(dev, "error commanding HC to start: %d\n", result);
+ goto error_stop;
+ }
+ result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE);
+ if (result < 0) {
+ dev_err(dev, "error waiting for HC to start: %d\n", result);
+ goto error_stop;
+ }
+ result = wa_nep_arm(&hwahc->wa, GFP_KERNEL);
+ if (result < 0) {
+ dev_err(dev, "cannot listen to notifications: %d\n", result);
+ goto error_stop;
+ }
+ /*
+ * If WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS is set,
+ * disable transfer notifications.
+ */
+ if (hwahc->wa.quirks &
+ WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS) {
+ struct usb_host_interface *cur_altsetting =
+ hwahc->wa.usb_iface->cur_altsetting;
+
+ result = usb_control_msg(hwahc->wa.usb_dev,
+ usb_sndctrlpipe(hwahc->wa.usb_dev, 0),
+ WA_REQ_ALEREON_DISABLE_XFER_NOTIFICATIONS,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_INTERFACE,
+ WA_REQ_ALEREON_FEATURE_SET,
+ cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ /*
+ * If we successfully sent the control message, start DTI here
+ * because no transfer notifications will be received which is
+ * where DTI is normally started.
+ */
+ if (result == 0)
+ result = wa_dti_start(&hwahc->wa);
+ else
+ result = 0; /* OK. Continue normally. */
+
+ if (result < 0) {
+ dev_err(dev, "cannot start DTI: %d\n", result);
+ goto error_dti_start;
+ }
+ }
+
+ return result;
+
+error_dti_start:
+ wa_nep_disarm(&hwahc->wa);
+error_stop:
+ __wa_clear_feature(&hwahc->wa, WA_ENABLE);
+ return result;
+}
+
+static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc, int delay)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ int ret;
+
+ ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_CHAN_STOP,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ delay * 1000,
+ iface_no,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (ret == 0)
+ msleep(delay);
+
+ wa_nep_disarm(&hwahc->wa);
+ __wa_stop(&hwahc->wa);
+}
+
+/*
+ * Set the UWB MAS allocation for the WUSB cluster
+ *
+ * @stream_index: stream to use (-1 for cancelling the allocation)
+ * @mas: mas bitmap to use
+ */
+static int __hwahc_op_bwa_set(struct wusbhc *wusbhc, s8 stream_index,
+ const struct uwb_mas_bm *mas)
+{
+ int result;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ struct device *dev = &wa->usb_iface->dev;
+ u8 mas_le[UWB_NUM_MAS/8];
+
+ /* Set the stream index */
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_STREAM_IDX,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ stream_index,
+ wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "Cannot set WUSB stream index: %d\n", result);
+ goto out;
+ }
+ uwb_mas_bm_copy_le(mas_le, mas);
+ /* Set the MAS allocation */
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_WUSB_MAS,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ mas_le, 32, USB_CTRL_SET_TIMEOUT);
+ if (result < 0)
+ dev_err(dev, "Cannot set WUSB MAS allocation: %d\n", result);
+out:
+ return result;
+}
+
+/*
+ * Add an IE to the host's MMC
+ *
+ * @interval: See WUSB1.0[8.5.3.1]
+ * @repeat_cnt: See WUSB1.0[8.5.3.1]
+ * @handle: See WUSB1.0[8.5.3.1]
+ * @wuie: Pointer to the header of the WUSB IE data to add.
+ * MUST BE allocated in a kmalloc buffer (no stack or
+ * vmalloc).
+ *
+ * NOTE: the format of the WUSB IEs for MMCs are different to the
+ * normal MBOA MAC IEs (IE Id + Length in MBOA MAC vs. Length +
+ * Id in WUSB IEs). Standards...you gotta love'em.
+ */
+static int __hwahc_op_mmcie_add(struct wusbhc *wusbhc, u8 interval,
+ u8 repeat_cnt, u8 handle,
+ struct wuie_hdr *wuie)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+
+ return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_ADD_MMC_IE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ interval << 8 | repeat_cnt,
+ handle << 8 | iface_no,
+ wuie, wuie->bLength, USB_CTRL_SET_TIMEOUT);
+}
+
+/*
+ * Remove an IE to the host's MMC
+ *
+ * @handle: See WUSB1.0[8.5.3.1]
+ */
+static int __hwahc_op_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_REMOVE_MMC_IE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, handle << 8 | iface_no,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+/*
+ * Update device information for a given fake port
+ *
+ * @port_idx: Fake port to which device is connected (wusbhc index, not
+ * USB port number).
+ */
+static int __hwahc_op_dev_info_set(struct wusbhc *wusbhc,
+ struct wusb_dev *wusb_dev)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ struct hwa_dev_info *dev_info;
+ int ret;
+
+ /* fill out the Device Info buffer and send it */
+ dev_info = kzalloc(sizeof(struct hwa_dev_info), GFP_KERNEL);
+ if (!dev_info)
+ return -ENOMEM;
+ uwb_mas_bm_copy_le(dev_info->bmDeviceAvailability,
+ &wusb_dev->availability);
+ dev_info->bDeviceAddress = wusb_dev->addr;
+
+ /*
+ * If the descriptors haven't been read yet, use a default PHY
+ * rate of 53.3 Mbit/s only. The correct value will be used
+ * when this will be called again as part of the
+ * authentication process (which occurs after the descriptors
+ * have been read).
+ */
+ if (wusb_dev->wusb_cap_descr)
+ dev_info->wPHYRates = wusb_dev->wusb_cap_descr->wPHYRates;
+ else
+ dev_info->wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53);
+
+ ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_DEV_INFO,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, wusb_dev->port_idx << 8 | iface_no,
+ dev_info, sizeof(struct hwa_dev_info),
+ USB_CTRL_SET_TIMEOUT);
+ kfree(dev_info);
+ return ret;
+}
+
+/*
+ * Set host's idea of which encryption (and key) method to use when
+ * talking to ad evice on a given port.
+ *
+ * If key is NULL, it means disable encryption for that "virtual port"
+ * (used when we disconnect).
+ */
+static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+ const void *key, size_t key_size,
+ u8 key_idx)
+{
+ int result = -ENOMEM;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ struct usb_key_descriptor *keyd;
+ size_t keyd_len;
+
+ keyd_len = sizeof(*keyd) + key_size;
+ keyd = kzalloc(keyd_len, GFP_KERNEL);
+ if (keyd == NULL)
+ return -ENOMEM;
+
+ keyd->bLength = keyd_len;
+ keyd->bDescriptorType = USB_DT_KEY;
+ keyd->tTKID[0] = (tkid >> 0) & 0xff;
+ keyd->tTKID[1] = (tkid >> 8) & 0xff;
+ keyd->tTKID[2] = (tkid >> 16) & 0xff;
+ memcpy(keyd->bKeyData, key, key_size);
+
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ USB_REQ_SET_DESCRIPTOR,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ USB_DT_KEY << 8 | key_idx,
+ port_idx << 8 | iface_no,
+ keyd, keyd_len, USB_CTRL_SET_TIMEOUT);
+
+ kzfree(keyd); /* clear keys etc. */
+ return result;
+}
+
+/*
+ * Set host's idea of which encryption (and key) method to use when
+ * talking to ad evice on a given port.
+ *
+ * If key is NULL, it means disable encryption for that "virtual port"
+ * (used when we disconnect).
+ */
+static int __hwahc_op_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+ const void *key, size_t key_size)
+{
+ int result = -ENOMEM;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ u8 encryption_value;
+
+ /* Tell the host which key to use to talk to the device */
+ if (key) {
+ u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_PTK,
+ WUSB_KEY_INDEX_ORIGINATOR_HOST);
+
+ result = __hwahc_dev_set_key(wusbhc, port_idx, tkid,
+ key, key_size, key_idx);
+ if (result < 0)
+ goto error_set_key;
+ encryption_value = wusbhc->ccm1_etd->bEncryptionValue;
+ } else {
+ /* FIXME: this should come from wusbhc->etd[UNSECURE].value */
+ encryption_value = 0;
+ }
+
+ /* Set the encryption type for communicating with the device */
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ USB_REQ_SET_ENCRYPTION,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ encryption_value, port_idx << 8 | iface_no,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (result < 0)
+ dev_err(wusbhc->dev, "Can't set host's WUSB encryption for "
+ "port index %u to %s (value %d): %d\n", port_idx,
+ wusb_et_name(wusbhc->ccm1_etd->bEncryptionType),
+ wusbhc->ccm1_etd->bEncryptionValue, result);
+error_set_key:
+ return result;
+}
+
+/*
+ * Set host's GTK key
+ */
+static int __hwahc_op_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+ const void *key, size_t key_size)
+{
+ u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK,
+ WUSB_KEY_INDEX_ORIGINATOR_HOST);
+
+ return __hwahc_dev_set_key(wusbhc, 0, tkid, key, key_size, key_idx);
+}
+
+/*
+ * Get the Wire Adapter class-specific descriptor
+ *
+ * NOTE: this descriptor comes with the big bundled configuration
+ * descriptor that includes the interfaces' and endpoints', so
+ * we just look for it in the cached copy kept by the USB stack.
+ *
+ * NOTE2: We convert LE fields to CPU order.
+ */
+static int wa_fill_descr(struct wahc *wa)
+{
+ int result;
+ struct device *dev = &wa->usb_iface->dev;
+ char *itr;
+ struct usb_device *usb_dev = wa->usb_dev;
+ struct usb_descriptor_header *hdr;
+ struct usb_wa_descriptor *wa_descr;
+ size_t itr_size, actconfig_idx;
+
+ actconfig_idx = (usb_dev->actconfig - usb_dev->config) /
+ sizeof(usb_dev->config[0]);
+ itr = usb_dev->rawdescriptors[actconfig_idx];
+ itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
+ while (itr_size >= sizeof(*hdr)) {
+ hdr = (struct usb_descriptor_header *) itr;
+ dev_dbg(dev, "Extra device descriptor: "
+ "type %02x/%u bytes @ %zu (%zu left)\n",
+ hdr->bDescriptorType, hdr->bLength,
+ (itr - usb_dev->rawdescriptors[actconfig_idx]),
+ itr_size);
+ if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER)
+ goto found;
+ itr += hdr->bLength;
+ itr_size -= hdr->bLength;
+ }
+ dev_err(dev, "cannot find Wire Adapter Class descriptor\n");
+ return -ENODEV;
+
+found:
+ result = -EINVAL;
+ if (hdr->bLength > itr_size) { /* is it available? */
+ dev_err(dev, "incomplete Wire Adapter Class descriptor "
+ "(%zu bytes left, %u needed)\n",
+ itr_size, hdr->bLength);
+ goto error;
+ }
+ if (hdr->bLength < sizeof(*wa->wa_descr)) {
+ dev_err(dev, "short Wire Adapter Class descriptor\n");
+ goto error;
+ }
+ wa->wa_descr = wa_descr = (struct usb_wa_descriptor *) hdr;
+ if (le16_to_cpu(wa_descr->bcdWAVersion) > 0x0100)
+ dev_warn(dev, "Wire Adapter v%d.%d newer than groked v1.0\n",
+ (le16_to_cpu(wa_descr->bcdWAVersion) & 0xff00) >> 8,
+ le16_to_cpu(wa_descr->bcdWAVersion) & 0x00ff);
+ result = 0;
+error:
+ return result;
+}
+
+static const struct hc_driver hwahc_hc_driver = {
+ .description = "hwa-hcd",
+ .product_desc = "Wireless USB HWA host controller",
+ .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd),
+ .irq = NULL, /* FIXME */
+ .flags = HCD_USB25,
+ .reset = hwahc_op_reset,
+ .start = hwahc_op_start,
+ .stop = hwahc_op_stop,
+ .get_frame_number = hwahc_op_get_frame_number,
+ .urb_enqueue = hwahc_op_urb_enqueue,
+ .urb_dequeue = hwahc_op_urb_dequeue,
+ .endpoint_disable = hwahc_op_endpoint_disable,
+
+ .hub_status_data = wusbhc_rh_status_data,
+ .hub_control = wusbhc_rh_control,
+ .start_port_reset = wusbhc_rh_start_port_reset,
+};
+
+static int hwahc_security_create(struct hwahc *hwahc)
+{
+ int result;
+ struct wusbhc *wusbhc = &hwahc->wusbhc;
+ struct usb_device *usb_dev = hwahc->wa.usb_dev;
+ struct device *dev = &usb_dev->dev;
+ struct usb_security_descriptor *secd;
+ struct usb_encryption_descriptor *etd;
+ void *itr, *top;
+ size_t itr_size, needed, bytes;
+ u8 index;
+ char buf[64];
+
+ /* Find the host's security descriptors in the config descr bundle */
+ index = (usb_dev->actconfig - usb_dev->config) /
+ sizeof(usb_dev->config[0]);
+ itr = usb_dev->rawdescriptors[index];
+ itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
+ top = itr + itr_size;
+ result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index],
+ le16_to_cpu(usb_dev->actconfig->desc.wTotalLength),
+ USB_DT_SECURITY, (void **) &secd, sizeof(*secd));
+ if (result == -1) {
+ dev_warn(dev, "BUG? WUSB host has no security descriptors\n");
+ return 0;
+ }
+ needed = sizeof(*secd);
+ if (top - (void *)secd < needed) {
+ dev_err(dev, "BUG? Not enough data to process security "
+ "descriptor header (%zu bytes left vs %zu needed)\n",
+ top - (void *) secd, needed);
+ return 0;
+ }
+ needed = le16_to_cpu(secd->wTotalLength);
+ if (top - (void *)secd < needed) {
+ dev_err(dev, "BUG? Not enough data to process security "
+ "descriptors (%zu bytes left vs %zu needed)\n",
+ top - (void *) secd, needed);
+ return 0;
+ }
+ /* Walk over the sec descriptors and store CCM1's on wusbhc */
+ itr = (void *) secd + sizeof(*secd);
+ top = (void *) secd + le16_to_cpu(secd->wTotalLength);
+ index = 0;
+ bytes = 0;
+ while (itr < top) {
+ etd = itr;
+ if (top - itr < sizeof(*etd)) {
+ dev_err(dev, "BUG: bad host security descriptor; "
+ "not enough data (%zu vs %zu left)\n",
+ top - itr, sizeof(*etd));
+ break;
+ }
+ if (etd->bLength < sizeof(*etd)) {
+ dev_err(dev, "BUG: bad host encryption descriptor; "
+ "descriptor is too short "
+ "(%zu vs %zu needed)\n",
+ (size_t)etd->bLength, sizeof(*etd));
+ break;
+ }
+ itr += etd->bLength;
+ bytes += snprintf(buf + bytes, sizeof(buf) - bytes,
+ "%s (0x%02x) ",
+ wusb_et_name(etd->bEncryptionType),
+ etd->bEncryptionValue);
+ wusbhc->ccm1_etd = etd;
+ }
+ dev_info(dev, "supported encryption types: %s\n", buf);
+ if (wusbhc->ccm1_etd == NULL) {
+ dev_err(dev, "E: host doesn't support CCM-1 crypto\n");
+ return 0;
+ }
+ /* Pretty print what we support */
+ return 0;
+}
+
+static void hwahc_security_release(struct hwahc *hwahc)
+{
+ /* nothing to do here so far... */
+}
+
+static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface,
+ kernel_ulong_t quirks)
+{
+ int result;
+ struct device *dev = &iface->dev;
+ struct wusbhc *wusbhc = &hwahc->wusbhc;
+ struct wahc *wa = &hwahc->wa;
+ struct usb_device *usb_dev = interface_to_usbdev(iface);
+
+ wa->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */
+ wa->usb_iface = usb_get_intf(iface);
+ wusbhc->dev = dev;
+ /* defer getting the uwb_rc handle until it is needed since it
+ * may not have been registered by the hwa_rc driver yet. */
+ wusbhc->uwb_rc = NULL;
+ result = wa_fill_descr(wa); /* Get the device descriptor */
+ if (result < 0)
+ goto error_fill_descriptor;
+ if (wa->wa_descr->bNumPorts > USB_MAXCHILDREN) {
+ dev_err(dev, "FIXME: USB_MAXCHILDREN too low for WUSB "
+ "adapter (%u ports)\n", wa->wa_descr->bNumPorts);
+ wusbhc->ports_max = USB_MAXCHILDREN;
+ } else {
+ wusbhc->ports_max = wa->wa_descr->bNumPorts;
+ }
+ wusbhc->mmcies_max = wa->wa_descr->bNumMMCIEs;
+ wusbhc->start = __hwahc_op_wusbhc_start;
+ wusbhc->stop = __hwahc_op_wusbhc_stop;
+ wusbhc->mmcie_add = __hwahc_op_mmcie_add;
+ wusbhc->mmcie_rm = __hwahc_op_mmcie_rm;
+ wusbhc->dev_info_set = __hwahc_op_dev_info_set;
+ wusbhc->bwa_set = __hwahc_op_bwa_set;
+ wusbhc->set_num_dnts = __hwahc_op_set_num_dnts;
+ wusbhc->set_ptk = __hwahc_op_set_ptk;
+ wusbhc->set_gtk = __hwahc_op_set_gtk;
+ result = hwahc_security_create(hwahc);
+ if (result < 0) {
+ dev_err(dev, "Can't initialize security: %d\n", result);
+ goto error_security_create;
+ }
+ wa->wusb = wusbhc; /* FIXME: ugly, need to fix */
+ result = wusbhc_create(&hwahc->wusbhc);
+ if (result < 0) {
+ dev_err(dev, "Can't create WUSB HC structures: %d\n", result);
+ goto error_wusbhc_create;
+ }
+ result = wa_create(&hwahc->wa, iface, quirks);
+ if (result < 0)
+ goto error_wa_create;
+ return 0;
+
+error_wa_create:
+ wusbhc_destroy(&hwahc->wusbhc);
+error_wusbhc_create:
+ /* WA Descr fill allocs no resources */
+error_security_create:
+error_fill_descriptor:
+ usb_put_intf(iface);
+ usb_put_dev(usb_dev);
+ return result;
+}
+
+static void hwahc_destroy(struct hwahc *hwahc)
+{
+ struct wusbhc *wusbhc = &hwahc->wusbhc;
+
+ mutex_lock(&wusbhc->mutex);
+ __wa_destroy(&hwahc->wa);
+ wusbhc_destroy(&hwahc->wusbhc);
+ hwahc_security_release(hwahc);
+ hwahc->wusbhc.dev = NULL;
+ uwb_rc_put(wusbhc->uwb_rc);
+ usb_put_intf(hwahc->wa.usb_iface);
+ usb_put_dev(hwahc->wa.usb_dev);
+ mutex_unlock(&wusbhc->mutex);
+}
+
+static void hwahc_init(struct hwahc *hwahc)
+{
+ wa_init(&hwahc->wa);
+}
+
+static int hwahc_probe(struct usb_interface *usb_iface,
+ const struct usb_device_id *id)
+{
+ int result;
+ struct usb_hcd *usb_hcd;
+ struct wusbhc *wusbhc;
+ struct hwahc *hwahc;
+ struct device *dev = &usb_iface->dev;
+
+ result = -ENOMEM;
+ usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa");
+ if (usb_hcd == NULL) {
+ dev_err(dev, "unable to allocate instance\n");
+ goto error_alloc;
+ }
+ usb_hcd->wireless = 1;
+ usb_hcd->self.sg_tablesize = ~0;
+ wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ hwahc_init(hwahc);
+ result = hwahc_create(hwahc, usb_iface, id->driver_info);
+ if (result < 0) {
+ dev_err(dev, "Cannot initialize internals: %d\n", result);
+ goto error_hwahc_create;
+ }
+ result = usb_add_hcd(usb_hcd, 0, 0);
+ if (result < 0) {
+ dev_err(dev, "Cannot add HCD: %d\n", result);
+ goto error_add_hcd;
+ }
+ device_wakeup_enable(usb_hcd->self.controller);
+ result = wusbhc_b_create(&hwahc->wusbhc);
+ if (result < 0) {
+ dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result);
+ goto error_wusbhc_b_create;
+ }
+ return 0;
+
+error_wusbhc_b_create:
+ usb_remove_hcd(usb_hcd);
+error_add_hcd:
+ hwahc_destroy(hwahc);
+error_hwahc_create:
+ usb_put_hcd(usb_hcd);
+error_alloc:
+ return result;
+}
+
+static void hwahc_disconnect(struct usb_interface *usb_iface)
+{
+ struct usb_hcd *usb_hcd;
+ struct wusbhc *wusbhc;
+ struct hwahc *hwahc;
+
+ usb_hcd = usb_get_intfdata(usb_iface);
+ wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ wusbhc_b_destroy(&hwahc->wusbhc);
+ usb_remove_hcd(usb_hcd);
+ hwahc_destroy(hwahc);
+ usb_put_hcd(usb_hcd);
+}
+
+static const struct usb_device_id hwahc_id_table[] = {
+ /* Alereon 5310 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5310, 0xe0, 0x02, 0x01),
+ .driver_info = WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC |
+ WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS },
+ /* Alereon 5611 */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5611, 0xe0, 0x02, 0x01),
+ .driver_info = WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC |
+ WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS },
+ /* FIXME: use class labels for this */
+ { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), },
+ {},
+};
+MODULE_DEVICE_TABLE(usb, hwahc_id_table);
+
+static struct usb_driver hwahc_driver = {
+ .name = "hwa-hc",
+ .probe = hwahc_probe,
+ .disconnect = hwahc_disconnect,
+ .id_table = hwahc_id_table,
+};
+
+module_usb_driver(hwahc_driver);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/wusbcore/host/whci/Makefile b/drivers/staging/wusbcore/host/whci/Makefile
new file mode 100644
index 000000000000..859d20079df6
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o
+
+whci-hcd-y := \
+ asl.o \
+ debug.o \
+ hcd.o \
+ hw.o \
+ init.o \
+ int.o \
+ pzl.o \
+ qset.o \
+ wusb.o
diff --git a/drivers/staging/wusbcore/host/whci/asl.c b/drivers/staging/wusbcore/host/whci/asl.c
new file mode 100644
index 000000000000..a2b9a50cfb80
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/asl.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) asynchronous schedule management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/gfp.h>
+#include <linux/dma-mapping.h>
+#include <linux/usb.h>
+
+#include "../../../uwb/include/umc.h"
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset,
+ struct whc_qset **next, struct whc_qset **prev)
+{
+ struct list_head *n, *p;
+
+ BUG_ON(list_empty(&whc->async_list));
+
+ n = qset->list_node.next;
+ if (n == &whc->async_list)
+ n = n->next;
+ p = qset->list_node.prev;
+ if (p == &whc->async_list)
+ p = p->prev;
+
+ *next = container_of(n, struct whc_qset, list_node);
+ *prev = container_of(p, struct whc_qset, list_node);
+
+}
+
+static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset)
+{
+ list_move(&qset->list_node, &whc->async_list);
+ qset->in_sw_list = true;
+}
+
+static void asl_qset_insert(struct whc *whc, struct whc_qset *qset)
+{
+ struct whc_qset *next, *prev;
+
+ qset_clear(whc, qset);
+
+ /* Link into ASL. */
+ qset_get_next_prev(whc, qset, &next, &prev);
+ whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma);
+ whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma);
+ qset->in_hw_list = true;
+}
+
+static void asl_qset_remove(struct whc *whc, struct whc_qset *qset)
+{
+ struct whc_qset *prev, *next;
+
+ qset_get_next_prev(whc, qset, &next, &prev);
+
+ list_move(&qset->list_node, &whc->async_removed_list);
+ qset->in_sw_list = false;
+
+ /*
+ * No more qsets in the ASL? The caller must stop the ASL as
+ * it's no longer valid.
+ */
+ if (list_empty(&whc->async_list))
+ return;
+
+ /* Remove from ASL. */
+ whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma);
+ qset->in_hw_list = false;
+}
+
+/**
+ * process_qset - process any recently inactivated or halted qTDs in a
+ * qset.
+ *
+ * After inactive qTDs are removed, new qTDs can be added if the
+ * urb queue still contains URBs.
+ *
+ * Returns any additional WUSBCMD bits for the ASL sync command (i.e.,
+ * WUSBCMD_ASYNC_QSET_RM if a halted qset was removed).
+ */
+static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
+{
+ enum whc_update update = 0;
+ uint32_t status = 0;
+
+ while (qset->ntds) {
+ struct whc_qtd *td;
+
+ td = &qset->qtd[qset->td_start];
+ status = le32_to_cpu(td->status);
+
+ /*
+ * Nothing to do with a still active qTD.
+ */
+ if (status & QTD_STS_ACTIVE)
+ break;
+
+ if (status & QTD_STS_HALTED) {
+ /* Ug, an error. */
+ process_halted_qtd(whc, qset, td);
+ /* A halted qTD always triggers an update
+ because the qset was either removed or
+ reactivated. */
+ update |= WHC_UPDATE_UPDATED;
+ goto done;
+ }
+
+ /* Mmm, a completed qTD. */
+ process_inactive_qtd(whc, qset, td);
+ }
+
+ if (!qset->remove)
+ update |= qset_add_qtds(whc, qset);
+
+done:
+ /*
+ * Remove this qset from the ASL if requested, but only if has
+ * no qTDs.
+ */
+ if (qset->remove && qset->ntds == 0) {
+ asl_qset_remove(whc, qset);
+ update |= WHC_UPDATE_REMOVED;
+ }
+ return update;
+}
+
+void asl_start(struct whc *whc)
+{
+ struct whc_qset *qset;
+
+ qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
+
+ le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR);
+
+ whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED,
+ 1000, "start ASL");
+}
+
+void asl_stop(struct whc *whc)
+{
+ whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_ASYNC_SCHED, 0,
+ 1000, "stop ASL");
+}
+
+/**
+ * asl_update - request an ASL update and wait for the hardware to be synced
+ * @whc: the WHCI HC
+ * @wusbcmd: WUSBCMD value to start the update.
+ *
+ * If the WUSB HC is inactive (i.e., the ASL is stopped) then the
+ * update must be skipped as the hardware may not respond to update
+ * requests.
+ */
+void asl_update(struct whc *whc, uint32_t wusbcmd)
+{
+ struct wusbhc *wusbhc = &whc->wusbhc;
+ long t;
+
+ mutex_lock(&wusbhc->mutex);
+ if (wusbhc->active) {
+ whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
+ t = wait_event_timeout(
+ whc->async_list_wq,
+ (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0,
+ msecs_to_jiffies(1000));
+ if (t == 0)
+ whc_hw_error(whc, "ASL update timeout");
+ }
+ mutex_unlock(&wusbhc->mutex);
+}
+
+/**
+ * scan_async_work - scan the ASL for qsets to process.
+ *
+ * Process each qset in the ASL in turn and then signal the WHC that
+ * the ASL has been updated.
+ *
+ * Then start, stop or update the asynchronous schedule as required.
+ */
+void scan_async_work(struct work_struct *work)
+{
+ struct whc *whc = container_of(work, struct whc, async_work);
+ struct whc_qset *qset, *t;
+ enum whc_update update = 0;
+
+ spin_lock_irq(&whc->lock);
+
+ /*
+ * Transerve the software list backwards so new qsets can be
+ * safely inserted into the ASL without making it non-circular.
+ */
+ list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) {
+ if (!qset->in_hw_list) {
+ asl_qset_insert(whc, qset);
+ update |= WHC_UPDATE_ADDED;
+ }
+
+ update |= process_qset(whc, qset);
+ }
+
+ spin_unlock_irq(&whc->lock);
+
+ if (update) {
+ uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB;
+ if (update & WHC_UPDATE_REMOVED)
+ wusbcmd |= WUSBCMD_ASYNC_QSET_RM;
+ asl_update(whc, wusbcmd);
+ }
+
+ /*
+ * Now that the ASL is updated, complete the removal of any
+ * removed qsets.
+ *
+ * If the qset was to be reset, do so and reinsert it into the
+ * ASL if it has pending transfers.
+ */
+ spin_lock_irq(&whc->lock);
+
+ list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) {
+ qset_remove_complete(whc, qset);
+ if (qset->reset) {
+ qset_reset(whc, qset);
+ if (!list_empty(&qset->stds)) {
+ asl_qset_insert_begin(whc, qset);
+ queue_work(whc->workqueue, &whc->async_work);
+ }
+ }
+ }
+
+ spin_unlock_irq(&whc->lock);
+}
+
+/**
+ * asl_urb_enqueue - queue an URB onto the asynchronous list (ASL).
+ * @whc: the WHCI host controller
+ * @urb: the URB to enqueue
+ * @mem_flags: flags for any memory allocations
+ *
+ * The qset for the endpoint is obtained and the urb queued on to it.
+ *
+ * Work is scheduled to update the hardware's view of the ASL.
+ */
+int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
+{
+ struct whc_qset *qset;
+ int err;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
+ if (err < 0) {
+ spin_unlock_irqrestore(&whc->lock, flags);
+ return err;
+ }
+
+ qset = get_qset(whc, urb, GFP_ATOMIC);
+ if (qset == NULL)
+ err = -ENOMEM;
+ else
+ err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
+ if (!err) {
+ if (!qset->in_sw_list && !qset->remove)
+ asl_qset_insert_begin(whc, qset);
+ } else
+ usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ if (!err)
+ queue_work(whc->workqueue, &whc->async_work);
+
+ return err;
+}
+
+/**
+ * asl_urb_dequeue - remove an URB (qset) from the async list.
+ * @whc: the WHCI host controller
+ * @urb: the URB to dequeue
+ * @status: the current status of the URB
+ *
+ * URBs that do yet have qTDs can simply be removed from the software
+ * queue, otherwise the qset must be removed from the ASL so the qTDs
+ * can be removed.
+ */
+int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
+{
+ struct whc_urb *wurb = urb->hcpriv;
+ struct whc_qset *qset = wurb->qset;
+ struct whc_std *std, *t;
+ bool has_qtd = false;
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
+ if (ret < 0)
+ goto out;
+
+ list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+ if (std->urb == urb) {
+ if (std->qtd)
+ has_qtd = true;
+ qset_free_std(whc, std);
+ } else
+ std->qtd = NULL; /* so this std is re-added when the qset is */
+ }
+
+ if (has_qtd) {
+ asl_qset_remove(whc, qset);
+ wurb->status = status;
+ wurb->is_async = true;
+ queue_work(whc->workqueue, &wurb->dequeue_work);
+ } else
+ qset_remove_urb(whc, qset, urb, status);
+out:
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ return ret;
+}
+
+/**
+ * asl_qset_delete - delete a qset from the ASL
+ */
+void asl_qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+ qset->remove = 1;
+ queue_work(whc->workqueue, &whc->async_work);
+ qset_delete(whc, qset);
+}
+
+/**
+ * asl_init - initialize the asynchronous schedule list
+ *
+ * A dummy qset with no qTDs is added to the ASL to simplify removing
+ * qsets (no need to stop the ASL when the last qset is removed).
+ */
+int asl_init(struct whc *whc)
+{
+ struct whc_qset *qset;
+
+ qset = qset_alloc(whc, GFP_KERNEL);
+ if (qset == NULL)
+ return -ENOMEM;
+
+ asl_qset_insert_begin(whc, qset);
+ asl_qset_insert(whc, qset);
+
+ return 0;
+}
+
+/**
+ * asl_clean_up - free ASL resources
+ *
+ * The ASL is stopped and empty except for the dummy qset.
+ */
+void asl_clean_up(struct whc *whc)
+{
+ struct whc_qset *qset;
+
+ if (!list_empty(&whc->async_list)) {
+ qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
+ list_del(&qset->list_node);
+ qset_free(whc, qset);
+ }
+}
diff --git a/drivers/staging/wusbcore/host/whci/debug.c b/drivers/staging/wusbcore/host/whci/debug.c
new file mode 100644
index 000000000000..443da6719147
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/debug.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) debug.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/export.h>
+
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+struct whc_dbg {
+ struct dentry *di_f;
+ struct dentry *asl_f;
+ struct dentry *pzl_f;
+};
+
+static void qset_print(struct seq_file *s, struct whc_qset *qset)
+{
+ static const char *qh_type[] = {
+ "ctrl", "isoc", "bulk", "intr", "rsvd", "rsvd", "rsvd", "lpintr", };
+ struct whc_std *std;
+ struct urb *urb = NULL;
+ int i;
+
+ seq_printf(s, "qset %08x", (u32)qset->qset_dma);
+ if (&qset->list_node == qset->whc->async_list.prev) {
+ seq_printf(s, " (dummy)\n");
+ } else {
+ seq_printf(s, " ep%d%s-%s maxpkt: %d\n",
+ qset->qh.info1 & 0x0f,
+ (qset->qh.info1 >> 4) & 0x1 ? "in" : "out",
+ qh_type[(qset->qh.info1 >> 5) & 0x7],
+ (qset->qh.info1 >> 16) & 0xffff);
+ }
+ seq_printf(s, " -> %08x\n", (u32)qset->qh.link);
+ seq_printf(s, " info: %08x %08x %08x\n",
+ qset->qh.info1, qset->qh.info2, qset->qh.info3);
+ seq_printf(s, " sts: %04x errs: %d curwin: %08x\n",
+ qset->qh.status, qset->qh.err_count, qset->qh.cur_window);
+ seq_printf(s, " TD: sts: %08x opts: %08x\n",
+ qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options);
+
+ for (i = 0; i < WHCI_QSET_TD_MAX; i++) {
+ seq_printf(s, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n",
+ i == qset->td_start ? 'S' : ' ',
+ i == qset->td_end ? 'E' : ' ',
+ i, qset->qtd[i].status, qset->qtd[i].options,
+ (u32)qset->qtd[i].page_list_ptr);
+ }
+ seq_printf(s, " ntds: %d\n", qset->ntds);
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (urb != std->urb) {
+ urb = std->urb;
+ seq_printf(s, " urb %p transferred: %d bytes\n", urb,
+ urb->actual_length);
+ }
+ if (std->qtd)
+ seq_printf(s, " sTD[%td]: %zu bytes @ %08x\n",
+ std->qtd - &qset->qtd[0],
+ std->len, std->num_pointers ?
+ (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
+ else
+ seq_printf(s, " sTD[-]: %zd bytes @ %08x\n",
+ std->len, std->num_pointers ?
+ (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
+ }
+}
+
+static int di_show(struct seq_file *s, void *p)
+{
+ struct whc *whc = s->private;
+ int d;
+
+ for (d = 0; d < whc->n_devices; d++) {
+ struct di_buf_entry *di = &whc->di_buf[d];
+
+ seq_printf(s, "DI[%d]\n", d);
+ seq_printf(s, " availability: %*pb\n",
+ UWB_NUM_MAS, (unsigned long *)di->availability_info);
+ seq_printf(s, " %c%c key idx: %d dev addr: %d\n",
+ (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ',
+ (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ',
+ (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8,
+ (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK));
+ }
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(di);
+
+static int asl_show(struct seq_file *s, void *p)
+{
+ struct whc *whc = s->private;
+ struct whc_qset *qset;
+
+ list_for_each_entry(qset, &whc->async_list, list_node) {
+ qset_print(s, qset);
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(asl);
+
+static int pzl_show(struct seq_file *s, void *p)
+{
+ struct whc *whc = s->private;
+ struct whc_qset *qset;
+ int period;
+
+ for (period = 0; period < 5; period++) {
+ seq_printf(s, "Period %d\n", period);
+ list_for_each_entry(qset, &whc->periodic_list[period], list_node) {
+ qset_print(s, qset);
+ }
+ }
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(pzl);
+
+void whc_dbg_init(struct whc *whc)
+{
+ if (whc->wusbhc.pal.debugfs_dir == NULL)
+ return;
+
+ whc->dbg = kzalloc(sizeof(struct whc_dbg), GFP_KERNEL);
+ if (whc->dbg == NULL)
+ return;
+
+ whc->dbg->di_f = debugfs_create_file("di", 0444,
+ whc->wusbhc.pal.debugfs_dir, whc,
+ &di_fops);
+ whc->dbg->asl_f = debugfs_create_file("asl", 0444,
+ whc->wusbhc.pal.debugfs_dir, whc,
+ &asl_fops);
+ whc->dbg->pzl_f = debugfs_create_file("pzl", 0444,
+ whc->wusbhc.pal.debugfs_dir, whc,
+ &pzl_fops);
+}
+
+void whc_dbg_clean_up(struct whc *whc)
+{
+ if (whc->dbg) {
+ debugfs_remove(whc->dbg->pzl_f);
+ debugfs_remove(whc->dbg->asl_f);
+ debugfs_remove(whc->dbg->di_f);
+ kfree(whc->dbg);
+ }
+}
diff --git a/drivers/staging/wusbcore/host/whci/hcd.c b/drivers/staging/wusbcore/host/whci/hcd.c
new file mode 100644
index 000000000000..bee1ff2d35be
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/hcd.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) driver.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include "../../../uwb/include/umc.h"
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+/*
+ * One time initialization.
+ *
+ * Nothing to do here.
+ */
+static int whc_reset(struct usb_hcd *usb_hcd)
+{
+ return 0;
+}
+
+/*
+ * Start the wireless host controller.
+ *
+ * Start device notification.
+ *
+ * Put hc into run state, set DNTS parameters.
+ */
+static int whc_start(struct usb_hcd *usb_hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u8 bcid;
+ int ret;
+
+ mutex_lock(&wusbhc->mutex);
+
+ le_writel(WUSBINTR_GEN_CMD_DONE
+ | WUSBINTR_HOST_ERR
+ | WUSBINTR_ASYNC_SCHED_SYNCED
+ | WUSBINTR_DNTS_INT
+ | WUSBINTR_ERR_INT
+ | WUSBINTR_INT,
+ whc->base + WUSBINTR);
+
+ /* set cluster ID */
+ bcid = wusb_cluster_id_get();
+ ret = whc_set_cluster_id(whc, bcid);
+ if (ret < 0)
+ goto out;
+ wusbhc->cluster_id = bcid;
+
+ /* start HC */
+ whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN);
+
+ usb_hcd->uses_new_polling = 1;
+ set_bit(HCD_FLAG_POLL_RH, &usb_hcd->flags);
+ usb_hcd->state = HC_STATE_RUNNING;
+
+out:
+ mutex_unlock(&wusbhc->mutex);
+ return ret;
+}
+
+
+/*
+ * Stop the wireless host controller.
+ *
+ * Stop device notification.
+ *
+ * Wait for pending transfer to stop? Put hc into stop state?
+ */
+static void whc_stop(struct usb_hcd *usb_hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ mutex_lock(&wusbhc->mutex);
+
+ /* stop HC */
+ le_writel(0, whc->base + WUSBINTR);
+ whc_write_wusbcmd(whc, WUSBCMD_RUN, 0);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_HCHALTED, WUSBSTS_HCHALTED,
+ 100, "HC to halt");
+
+ wusb_cluster_id_put(wusbhc->cluster_id);
+
+ mutex_unlock(&wusbhc->mutex);
+}
+
+static int whc_get_frame_number(struct usb_hcd *usb_hcd)
+{
+ /* Frame numbers are not applicable to WUSB. */
+ return -ENOSYS;
+}
+
+
+/*
+ * Queue an URB to the ASL or PZL
+ */
+static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ int ret;
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_INTERRUPT:
+ ret = pzl_urb_enqueue(whc, urb, mem_flags);
+ break;
+ case PIPE_ISOCHRONOUS:
+ dev_err(&whc->umc->dev, "isochronous transfers unsupported\n");
+ ret = -ENOTSUPP;
+ break;
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ default:
+ ret = asl_urb_enqueue(whc, urb, mem_flags);
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Remove a queued URB from the ASL or PZL.
+ */
+static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ int ret;
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_INTERRUPT:
+ ret = pzl_urb_dequeue(whc, urb, status);
+ break;
+ case PIPE_ISOCHRONOUS:
+ ret = -ENOTSUPP;
+ break;
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ default:
+ ret = asl_urb_dequeue(whc, urb, status);
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Wait for all URBs to the endpoint to be completed, then delete the
+ * qset.
+ */
+static void whc_endpoint_disable(struct usb_hcd *usb_hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ struct whc_qset *qset;
+
+ qset = ep->hcpriv;
+ if (qset) {
+ ep->hcpriv = NULL;
+ if (usb_endpoint_xfer_bulk(&ep->desc)
+ || usb_endpoint_xfer_control(&ep->desc))
+ asl_qset_delete(whc, qset);
+ else
+ pzl_qset_delete(whc, qset);
+ }
+}
+
+static void whc_endpoint_reset(struct usb_hcd *usb_hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ struct whc_qset *qset;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ qset = ep->hcpriv;
+ if (qset) {
+ qset->remove = 1;
+ qset->reset = 1;
+
+ if (usb_endpoint_xfer_bulk(&ep->desc)
+ || usb_endpoint_xfer_control(&ep->desc))
+ queue_work(whc->workqueue, &whc->async_work);
+ else
+ queue_work(whc->workqueue, &whc->periodic_work);
+ }
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+}
+
+
+static const struct hc_driver whc_hc_driver = {
+ .description = "whci-hcd",
+ .product_desc = "Wireless host controller",
+ .hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd),
+ .irq = whc_int_handler,
+ .flags = HCD_USB2,
+
+ .reset = whc_reset,
+ .start = whc_start,
+ .stop = whc_stop,
+ .get_frame_number = whc_get_frame_number,
+ .urb_enqueue = whc_urb_enqueue,
+ .urb_dequeue = whc_urb_dequeue,
+ .endpoint_disable = whc_endpoint_disable,
+ .endpoint_reset = whc_endpoint_reset,
+
+ .hub_status_data = wusbhc_rh_status_data,
+ .hub_control = wusbhc_rh_control,
+ .start_port_reset = wusbhc_rh_start_port_reset,
+};
+
+static int whc_probe(struct umc_dev *umc)
+{
+ int ret;
+ struct usb_hcd *usb_hcd;
+ struct wusbhc *wusbhc;
+ struct whc *whc;
+ struct device *dev = &umc->dev;
+
+ usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci");
+ if (usb_hcd == NULL) {
+ dev_err(dev, "unable to create hcd\n");
+ return -ENOMEM;
+ }
+
+ usb_hcd->wireless = 1;
+ usb_hcd->self.sg_tablesize = 2048; /* somewhat arbitrary */
+
+ wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ whc = wusbhc_to_whc(wusbhc);
+ whc->umc = umc;
+
+ ret = whc_init(whc);
+ if (ret)
+ goto error_whc_init;
+
+ wusbhc->dev = dev;
+ wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent);
+ if (!wusbhc->uwb_rc) {
+ ret = -ENODEV;
+ dev_err(dev, "cannot get radio controller\n");
+ goto error_uwb_rc;
+ }
+
+ if (whc->n_devices > USB_MAXCHILDREN) {
+ dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n",
+ whc->n_devices);
+ wusbhc->ports_max = USB_MAXCHILDREN;
+ } else
+ wusbhc->ports_max = whc->n_devices;
+ wusbhc->mmcies_max = whc->n_mmc_ies;
+ wusbhc->start = whc_wusbhc_start;
+ wusbhc->stop = whc_wusbhc_stop;
+ wusbhc->mmcie_add = whc_mmcie_add;
+ wusbhc->mmcie_rm = whc_mmcie_rm;
+ wusbhc->dev_info_set = whc_dev_info_set;
+ wusbhc->bwa_set = whc_bwa_set;
+ wusbhc->set_num_dnts = whc_set_num_dnts;
+ wusbhc->set_ptk = whc_set_ptk;
+ wusbhc->set_gtk = whc_set_gtk;
+
+ ret = wusbhc_create(wusbhc);
+ if (ret)
+ goto error_wusbhc_create;
+
+ ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED);
+ if (ret) {
+ dev_err(dev, "cannot add HCD: %d\n", ret);
+ goto error_usb_add_hcd;
+ }
+ device_wakeup_enable(usb_hcd->self.controller);
+
+ ret = wusbhc_b_create(wusbhc);
+ if (ret) {
+ dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret);
+ goto error_wusbhc_b_create;
+ }
+
+ whc_dbg_init(whc);
+
+ return 0;
+
+error_wusbhc_b_create:
+ usb_remove_hcd(usb_hcd);
+error_usb_add_hcd:
+ wusbhc_destroy(wusbhc);
+error_wusbhc_create:
+ uwb_rc_put(wusbhc->uwb_rc);
+error_uwb_rc:
+ whc_clean_up(whc);
+error_whc_init:
+ usb_put_hcd(usb_hcd);
+ return ret;
+}
+
+
+static void whc_remove(struct umc_dev *umc)
+{
+ struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev);
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ if (usb_hcd) {
+ whc_dbg_clean_up(whc);
+ wusbhc_b_destroy(wusbhc);
+ usb_remove_hcd(usb_hcd);
+ wusbhc_destroy(wusbhc);
+ uwb_rc_put(wusbhc->uwb_rc);
+ whc_clean_up(whc);
+ usb_put_hcd(usb_hcd);
+ }
+}
+
+static struct umc_driver whci_hc_driver = {
+ .name = "whci-hcd",
+ .cap_id = UMC_CAP_ID_WHCI_WUSB_HC,
+ .probe = whc_probe,
+ .remove = whc_remove,
+};
+
+static int __init whci_hc_driver_init(void)
+{
+ return umc_driver_register(&whci_hc_driver);
+}
+module_init(whci_hc_driver_init);
+
+static void __exit whci_hc_driver_exit(void)
+{
+ umc_driver_unregister(&whci_hc_driver);
+}
+module_exit(whci_hc_driver_exit);
+
+/* PCI device ID's that we handle (so it gets loaded) */
+static struct pci_device_id __used whci_hcd_id_table[] = {
+ { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
+ { /* empty last entry */ }
+};
+MODULE_DEVICE_TABLE(pci, whci_hcd_id_table);
+
+MODULE_DESCRIPTION("WHCI Wireless USB host controller driver");
+MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/wusbcore/host/whci/hw.c b/drivers/staging/wusbcore/host/whci/hw.c
new file mode 100644
index 000000000000..e4e8914abf42
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/hw.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) hardware access helpers.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+
+#include "../../../uwb/include/umc.h"
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val)
+{
+ unsigned long flags;
+ u32 cmd;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ cmd = le_readl(whc->base + WUSBCMD);
+ cmd = (cmd & ~mask) | val;
+ le_writel(cmd, whc->base + WUSBCMD);
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+}
+
+/**
+ * whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register
+ * @whc: the WHCI HC
+ * @cmd: command to start.
+ * @params: parameters for the command (the WUSBGENCMDPARAMS register value).
+ * @addr: pointer to any data for the command (may be NULL).
+ * @len: length of the data (if any).
+ */
+int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len)
+{
+ unsigned long flags;
+ dma_addr_t dma_addr;
+ int t;
+ int ret = 0;
+
+ mutex_lock(&whc->mutex);
+
+ /* Wait for previous command to complete. */
+ t = wait_event_timeout(whc->cmd_wq,
+ (le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0,
+ WHC_GENCMD_TIMEOUT_MS);
+ if (t == 0) {
+ dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n",
+ le_readl(whc->base + WUSBGENCMDSTS),
+ le_readl(whc->base + WUSBGENCMDPARAMS));
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ if (addr) {
+ memcpy(whc->gen_cmd_buf, addr, len);
+ dma_addr = whc->gen_cmd_buf_dma;
+ } else
+ dma_addr = 0;
+
+ /* Poke registers to start cmd. */
+ spin_lock_irqsave(&whc->lock, flags);
+
+ le_writel(params, whc->base + WUSBGENCMDPARAMS);
+ le_writeq(dma_addr, whc->base + WUSBGENADDR);
+
+ le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd,
+ whc->base + WUSBGENCMDSTS);
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+out:
+ mutex_unlock(&whc->mutex);
+
+ return ret;
+}
+
+/**
+ * whc_hw_error - recover from a hardware error
+ * @whc: the WHCI HC that broke.
+ * @reason: a description of the failure.
+ *
+ * Recover from broken hardware with a full reset.
+ */
+void whc_hw_error(struct whc *whc, const char *reason)
+{
+ struct wusbhc *wusbhc = &whc->wusbhc;
+
+ dev_err(&whc->umc->dev, "hardware error: %s\n", reason);
+ wusbhc_reset_all(wusbhc);
+}
diff --git a/drivers/staging/wusbcore/host/whci/init.c b/drivers/staging/wusbcore/host/whci/init.c
new file mode 100644
index 000000000000..55fd458a8f30
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/init.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) initialization.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/gfp.h>
+#include <linux/dma-mapping.h>
+
+#include "../../../uwb/include/umc.h"
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+/*
+ * Reset the host controller.
+ */
+static void whc_hw_reset(struct whc *whc)
+{
+ le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0,
+ 100, "reset");
+}
+
+static void whc_hw_init_di_buf(struct whc *whc)
+{
+ int d;
+
+ /* Disable all entries in the Device Information buffer. */
+ for (d = 0; d < whc->n_devices; d++)
+ whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE;
+
+ le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR);
+}
+
+static void whc_hw_init_dn_buf(struct whc *whc)
+{
+ /* Clear the Device Notification buffer to ensure the V (valid)
+ * bits are clear. */
+ memset(whc->dn_buf, 0, 4096);
+
+ le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR);
+}
+
+int whc_init(struct whc *whc)
+{
+ u32 whcsparams;
+ int ret, i;
+ resource_size_t start, len;
+
+ spin_lock_init(&whc->lock);
+ mutex_init(&whc->mutex);
+ init_waitqueue_head(&whc->cmd_wq);
+ init_waitqueue_head(&whc->async_list_wq);
+ init_waitqueue_head(&whc->periodic_list_wq);
+ whc->workqueue = alloc_ordered_workqueue(dev_name(&whc->umc->dev), 0);
+ if (whc->workqueue == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ INIT_WORK(&whc->dn_work, whc_dn_work);
+
+ INIT_WORK(&whc->async_work, scan_async_work);
+ INIT_LIST_HEAD(&whc->async_list);
+ INIT_LIST_HEAD(&whc->async_removed_list);
+
+ INIT_WORK(&whc->periodic_work, scan_periodic_work);
+ for (i = 0; i < 5; i++)
+ INIT_LIST_HEAD(&whc->periodic_list[i]);
+ INIT_LIST_HEAD(&whc->periodic_removed_list);
+
+ /* Map HC registers. */
+ start = whc->umc->resource.start;
+ len = whc->umc->resource.end - start + 1;
+ if (!request_mem_region(start, len, "whci-hc")) {
+ dev_err(&whc->umc->dev, "can't request HC region\n");
+ ret = -EBUSY;
+ goto error;
+ }
+ whc->base_phys = start;
+ whc->base = ioremap(start, len);
+ if (!whc->base) {
+ dev_err(&whc->umc->dev, "ioremap\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ whc_hw_reset(whc);
+
+ /* Read maximum number of devices, keys and MMC IEs. */
+ whcsparams = le_readl(whc->base + WHCSPARAMS);
+ whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams);
+ whc->n_keys = WHCSPARAMS_TO_N_KEYS(whcsparams);
+ whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams);
+
+ dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n",
+ whc->n_devices, whc->n_keys, whc->n_mmc_ies);
+
+ whc->qset_pool = dma_pool_create("qset", &whc->umc->dev,
+ sizeof(struct whc_qset), 64, 0);
+ if (whc->qset_pool == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ret = asl_init(whc);
+ if (ret < 0)
+ goto error;
+ ret = pzl_init(whc);
+ if (ret < 0)
+ goto error;
+
+ /* Allocate and initialize a buffer for generic commands, the
+ Device Information buffer, and the Device Notification
+ buffer. */
+
+ whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
+ &whc->gen_cmd_buf_dma, GFP_KERNEL);
+ if (whc->gen_cmd_buf == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ whc->dn_buf = dma_alloc_coherent(&whc->umc->dev,
+ sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
+ &whc->dn_buf_dma, GFP_KERNEL);
+ if (!whc->dn_buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ whc_hw_init_dn_buf(whc);
+
+ whc->di_buf = dma_alloc_coherent(&whc->umc->dev,
+ sizeof(struct di_buf_entry) * whc->n_devices,
+ &whc->di_buf_dma, GFP_KERNEL);
+ if (!whc->di_buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ whc_hw_init_di_buf(whc);
+
+ return 0;
+
+error:
+ whc_clean_up(whc);
+ return ret;
+}
+
+void whc_clean_up(struct whc *whc)
+{
+ resource_size_t len;
+
+ if (whc->di_buf)
+ dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices,
+ whc->di_buf, whc->di_buf_dma);
+ if (whc->dn_buf)
+ dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
+ whc->dn_buf, whc->dn_buf_dma);
+ if (whc->gen_cmd_buf)
+ dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
+ whc->gen_cmd_buf, whc->gen_cmd_buf_dma);
+
+ pzl_clean_up(whc);
+ asl_clean_up(whc);
+
+ dma_pool_destroy(whc->qset_pool);
+
+ len = resource_size(&whc->umc->resource);
+ if (whc->base)
+ iounmap(whc->base);
+ if (whc->base_phys)
+ release_mem_region(whc->base_phys, len);
+
+ if (whc->workqueue)
+ destroy_workqueue(whc->workqueue);
+}
diff --git a/drivers/staging/wusbcore/host/whci/int.c b/drivers/staging/wusbcore/host/whci/int.c
new file mode 100644
index 000000000000..bdbe35e9366f
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/int.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) interrupt handling.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+
+#include "../../../uwb/include/umc.h"
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+static void transfer_done(struct whc *whc)
+{
+ queue_work(whc->workqueue, &whc->async_work);
+ queue_work(whc->workqueue, &whc->periodic_work);
+}
+
+irqreturn_t whc_int_handler(struct usb_hcd *hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 sts;
+
+ sts = le_readl(whc->base + WUSBSTS);
+ if (!(sts & WUSBSTS_INT_MASK))
+ return IRQ_NONE;
+ le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS);
+
+ if (sts & WUSBSTS_GEN_CMD_DONE)
+ wake_up(&whc->cmd_wq);
+
+ if (sts & WUSBSTS_HOST_ERR)
+ dev_err(&whc->umc->dev, "FIXME: host system error\n");
+
+ if (sts & WUSBSTS_ASYNC_SCHED_SYNCED)
+ wake_up(&whc->async_list_wq);
+
+ if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED)
+ wake_up(&whc->periodic_list_wq);
+
+ if (sts & WUSBSTS_DNTS_INT)
+ queue_work(whc->workqueue, &whc->dn_work);
+
+ /*
+ * A transfer completed (see [WHCI] section 4.7.1.2 for when
+ * this occurs).
+ */
+ if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT))
+ transfer_done(whc);
+
+ return IRQ_HANDLED;
+}
+
+static int process_dn_buf(struct whc *whc)
+{
+ struct wusbhc *wusbhc = &whc->wusbhc;
+ struct dn_buf_entry *dn;
+ int processed = 0;
+
+ for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) {
+ if (dn->status & WHC_DN_STATUS_VALID) {
+ wusbhc_handle_dn(wusbhc, dn->src_addr,
+ (struct wusb_dn_hdr *)dn->dn_data,
+ dn->msg_size);
+ dn->status &= ~WHC_DN_STATUS_VALID;
+ processed++;
+ }
+ }
+ return processed;
+}
+
+void whc_dn_work(struct work_struct *work)
+{
+ struct whc *whc = container_of(work, struct whc, dn_work);
+ int processed;
+
+ do {
+ processed = process_dn_buf(whc);
+ } while (processed);
+}
diff --git a/drivers/staging/wusbcore/host/whci/pzl.c b/drivers/staging/wusbcore/host/whci/pzl.c
new file mode 100644
index 000000000000..6dfc075f5798
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/pzl.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) periodic schedule management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/gfp.h>
+#include <linux/dma-mapping.h>
+#include <linux/usb.h>
+
+#include "../../../uwb/include/umc.h"
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+static void update_pzl_pointers(struct whc *whc, int period, u64 addr)
+{
+ switch (period) {
+ case 0:
+ whc_qset_set_link_ptr(&whc->pz_list[0], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[2], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[4], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[6], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[8], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[10], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[12], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[14], addr);
+ break;
+ case 1:
+ whc_qset_set_link_ptr(&whc->pz_list[1], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[5], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[9], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[13], addr);
+ break;
+ case 2:
+ whc_qset_set_link_ptr(&whc->pz_list[3], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[11], addr);
+ break;
+ case 3:
+ whc_qset_set_link_ptr(&whc->pz_list[7], addr);
+ break;
+ case 4:
+ whc_qset_set_link_ptr(&whc->pz_list[15], addr);
+ break;
+ }
+}
+
+/*
+ * Return the 'period' to use for this qset. The minimum interval for
+ * the endpoint is used so whatever urbs are submitted the device is
+ * polled often enough.
+ */
+static int qset_get_period(struct whc *whc, struct whc_qset *qset)
+{
+ uint8_t bInterval = qset->ep->desc.bInterval;
+
+ if (bInterval < 6)
+ bInterval = 6;
+ if (bInterval > 10)
+ bInterval = 10;
+ return bInterval - 6;
+}
+
+static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset)
+{
+ int period;
+
+ period = qset_get_period(whc, qset);
+
+ qset_clear(whc, qset);
+ list_move(&qset->list_node, &whc->periodic_list[period]);
+ qset->in_sw_list = true;
+}
+
+static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset)
+{
+ list_move(&qset->list_node, &whc->periodic_removed_list);
+ qset->in_hw_list = false;
+ qset->in_sw_list = false;
+}
+
+/**
+ * pzl_process_qset - process any recently inactivated or halted qTDs
+ * in a qset.
+ *
+ * After inactive qTDs are removed, new qTDs can be added if the
+ * urb queue still contains URBs.
+ *
+ * Returns the schedule updates required.
+ */
+static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset)
+{
+ enum whc_update update = 0;
+ uint32_t status = 0;
+
+ while (qset->ntds) {
+ struct whc_qtd *td;
+
+ td = &qset->qtd[qset->td_start];
+ status = le32_to_cpu(td->status);
+
+ /*
+ * Nothing to do with a still active qTD.
+ */
+ if (status & QTD_STS_ACTIVE)
+ break;
+
+ if (status & QTD_STS_HALTED) {
+ /* Ug, an error. */
+ process_halted_qtd(whc, qset, td);
+ /* A halted qTD always triggers an update
+ because the qset was either removed or
+ reactivated. */
+ update |= WHC_UPDATE_UPDATED;
+ goto done;
+ }
+
+ /* Mmm, a completed qTD. */
+ process_inactive_qtd(whc, qset, td);
+ }
+
+ if (!qset->remove)
+ update |= qset_add_qtds(whc, qset);
+
+done:
+ /*
+ * If there are no qTDs in this qset, remove it from the PZL.
+ */
+ if (qset->remove && qset->ntds == 0) {
+ pzl_qset_remove(whc, qset);
+ update |= WHC_UPDATE_REMOVED;
+ }
+
+ return update;
+}
+
+/**
+ * pzl_start - start the periodic schedule
+ * @whc: the WHCI host controller
+ *
+ * The PZL must be valid (e.g., all entries in the list should have
+ * the T bit set).
+ */
+void pzl_start(struct whc *whc)
+{
+ le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
+
+ whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED,
+ 1000, "start PZL");
+}
+
+/**
+ * pzl_stop - stop the periodic schedule
+ * @whc: the WHCI host controller
+ */
+void pzl_stop(struct whc *whc)
+{
+ whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_PERIODIC_SCHED, 0,
+ 1000, "stop PZL");
+}
+
+/**
+ * pzl_update - request a PZL update and wait for the hardware to be synced
+ * @whc: the WHCI HC
+ * @wusbcmd: WUSBCMD value to start the update.
+ *
+ * If the WUSB HC is inactive (i.e., the PZL is stopped) then the
+ * update must be skipped as the hardware may not respond to update
+ * requests.
+ */
+void pzl_update(struct whc *whc, uint32_t wusbcmd)
+{
+ struct wusbhc *wusbhc = &whc->wusbhc;
+ long t;
+
+ mutex_lock(&wusbhc->mutex);
+ if (wusbhc->active) {
+ whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
+ t = wait_event_timeout(
+ whc->periodic_list_wq,
+ (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0,
+ msecs_to_jiffies(1000));
+ if (t == 0)
+ whc_hw_error(whc, "PZL update timeout");
+ }
+ mutex_unlock(&wusbhc->mutex);
+}
+
+static void update_pzl_hw_view(struct whc *whc)
+{
+ struct whc_qset *qset, *t;
+ int period;
+ u64 tmp_qh = 0;
+
+ for (period = 0; period < 5; period++) {
+ list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
+ whc_qset_set_link_ptr(&qset->qh.link, tmp_qh);
+ tmp_qh = qset->qset_dma;
+ qset->in_hw_list = true;
+ }
+ update_pzl_pointers(whc, period, tmp_qh);
+ }
+}
+
+/**
+ * scan_periodic_work - scan the PZL for qsets to process.
+ *
+ * Process each qset in the PZL in turn and then signal the WHC that
+ * the PZL has been updated.
+ *
+ * Then start, stop or update the periodic schedule as required.
+ */
+void scan_periodic_work(struct work_struct *work)
+{
+ struct whc *whc = container_of(work, struct whc, periodic_work);
+ struct whc_qset *qset, *t;
+ enum whc_update update = 0;
+ int period;
+
+ spin_lock_irq(&whc->lock);
+
+ for (period = 4; period >= 0; period--) {
+ list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
+ if (!qset->in_hw_list)
+ update |= WHC_UPDATE_ADDED;
+ update |= pzl_process_qset(whc, qset);
+ }
+ }
+
+ if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED))
+ update_pzl_hw_view(whc);
+
+ spin_unlock_irq(&whc->lock);
+
+ if (update) {
+ uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB;
+ if (update & WHC_UPDATE_REMOVED)
+ wusbcmd |= WUSBCMD_PERIODIC_QSET_RM;
+ pzl_update(whc, wusbcmd);
+ }
+
+ /*
+ * Now that the PZL is updated, complete the removal of any
+ * removed qsets.
+ *
+ * If the qset was to be reset, do so and reinsert it into the
+ * PZL if it has pending transfers.
+ */
+ spin_lock_irq(&whc->lock);
+
+ list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) {
+ qset_remove_complete(whc, qset);
+ if (qset->reset) {
+ qset_reset(whc, qset);
+ if (!list_empty(&qset->stds)) {
+ qset_insert_in_sw_list(whc, qset);
+ queue_work(whc->workqueue, &whc->periodic_work);
+ }
+ }
+ }
+
+ spin_unlock_irq(&whc->lock);
+}
+
+/**
+ * pzl_urb_enqueue - queue an URB onto the periodic list (PZL)
+ * @whc: the WHCI host controller
+ * @urb: the URB to enqueue
+ * @mem_flags: flags for any memory allocations
+ *
+ * The qset for the endpoint is obtained and the urb queued on to it.
+ *
+ * Work is scheduled to update the hardware's view of the PZL.
+ */
+int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
+{
+ struct whc_qset *qset;
+ int err;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
+ if (err < 0) {
+ spin_unlock_irqrestore(&whc->lock, flags);
+ return err;
+ }
+
+ qset = get_qset(whc, urb, GFP_ATOMIC);
+ if (qset == NULL)
+ err = -ENOMEM;
+ else
+ err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
+ if (!err) {
+ if (!qset->in_sw_list && !qset->remove)
+ qset_insert_in_sw_list(whc, qset);
+ } else
+ usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ if (!err)
+ queue_work(whc->workqueue, &whc->periodic_work);
+
+ return err;
+}
+
+/**
+ * pzl_urb_dequeue - remove an URB (qset) from the periodic list
+ * @whc: the WHCI host controller
+ * @urb: the URB to dequeue
+ * @status: the current status of the URB
+ *
+ * URBs that do yet have qTDs can simply be removed from the software
+ * queue, otherwise the qset must be removed so the qTDs can be safely
+ * removed.
+ */
+int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
+{
+ struct whc_urb *wurb = urb->hcpriv;
+ struct whc_qset *qset = wurb->qset;
+ struct whc_std *std, *t;
+ bool has_qtd = false;
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
+ if (ret < 0)
+ goto out;
+
+ list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+ if (std->urb == urb) {
+ if (std->qtd)
+ has_qtd = true;
+ qset_free_std(whc, std);
+ } else
+ std->qtd = NULL; /* so this std is re-added when the qset is */
+ }
+
+ if (has_qtd) {
+ pzl_qset_remove(whc, qset);
+ update_pzl_hw_view(whc);
+ wurb->status = status;
+ wurb->is_async = false;
+ queue_work(whc->workqueue, &wurb->dequeue_work);
+ } else
+ qset_remove_urb(whc, qset, urb, status);
+out:
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ return ret;
+}
+
+/**
+ * pzl_qset_delete - delete a qset from the PZL
+ */
+void pzl_qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+ qset->remove = 1;
+ queue_work(whc->workqueue, &whc->periodic_work);
+ qset_delete(whc, qset);
+}
+
+/**
+ * pzl_init - initialize the periodic zone list
+ * @whc: the WHCI host controller
+ */
+int pzl_init(struct whc *whc)
+{
+ int i;
+
+ whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16,
+ &whc->pz_list_dma, GFP_KERNEL);
+ if (whc->pz_list == NULL)
+ return -ENOMEM;
+
+ /* Set T bit on all elements in PZL. */
+ for (i = 0; i < 16; i++)
+ whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
+
+ le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
+
+ return 0;
+}
+
+/**
+ * pzl_clean_up - free PZL resources
+ * @whc: the WHCI host controller
+ *
+ * The PZL is stopped and empty.
+ */
+void pzl_clean_up(struct whc *whc)
+{
+ if (whc->pz_list)
+ dma_free_coherent(&whc->umc->dev, sizeof(u64) * 16, whc->pz_list,
+ whc->pz_list_dma);
+}
diff --git a/drivers/staging/wusbcore/host/whci/qset.c b/drivers/staging/wusbcore/host/whci/qset.c
new file mode 100644
index 000000000000..66459b77dc77
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/qset.c
@@ -0,0 +1,831 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) qset management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "../../../uwb/include/umc.h"
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags)
+{
+ struct whc_qset *qset;
+ dma_addr_t dma;
+
+ qset = dma_pool_zalloc(whc->qset_pool, mem_flags, &dma);
+ if (qset == NULL)
+ return NULL;
+
+ qset->qset_dma = dma;
+ qset->whc = whc;
+
+ INIT_LIST_HEAD(&qset->list_node);
+ INIT_LIST_HEAD(&qset->stds);
+
+ return qset;
+}
+
+/**
+ * qset_fill_qh - fill the static endpoint state in a qset's QHead
+ * @qset: the qset whose QH needs initializing with static endpoint
+ * state
+ * @urb: an urb for a transfer to this endpoint
+ */
+static void qset_fill_qh(struct whc *whc, struct whc_qset *qset, struct urb *urb)
+{
+ struct usb_device *usb_dev = urb->dev;
+ struct wusb_dev *wusb_dev = usb_dev->wusb_dev;
+ struct usb_wireless_ep_comp_descriptor *epcd;
+ bool is_out;
+ uint8_t phy_rate;
+
+ is_out = usb_pipeout(urb->pipe);
+
+ qset->max_packet = le16_to_cpu(urb->ep->desc.wMaxPacketSize);
+
+ epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
+ if (epcd) {
+ qset->max_seq = epcd->bMaxSequence;
+ qset->max_burst = epcd->bMaxBurst;
+ } else {
+ qset->max_seq = 2;
+ qset->max_burst = 1;
+ }
+
+ /*
+ * Initial PHY rate is 53.3 Mbit/s for control endpoints or
+ * the maximum supported by the device for other endpoints
+ * (unless limited by the user).
+ */
+ if (usb_pipecontrol(urb->pipe))
+ phy_rate = UWB_PHY_RATE_53;
+ else {
+ uint16_t phy_rates;
+
+ phy_rates = le16_to_cpu(wusb_dev->wusb_cap_descr->wPHYRates);
+ phy_rate = fls(phy_rates) - 1;
+ if (phy_rate > whc->wusbhc.phy_rate)
+ phy_rate = whc->wusbhc.phy_rate;
+ }
+
+ qset->qh.info1 = cpu_to_le32(
+ QH_INFO1_EP(usb_pipeendpoint(urb->pipe))
+ | (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN)
+ | usb_pipe_to_qh_type(urb->pipe)
+ | QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum))
+ | QH_INFO1_MAX_PKT_LEN(qset->max_packet)
+ );
+ qset->qh.info2 = cpu_to_le32(
+ QH_INFO2_BURST(qset->max_burst)
+ | QH_INFO2_DBP(0)
+ | QH_INFO2_MAX_COUNT(3)
+ | QH_INFO2_MAX_RETRY(3)
+ | QH_INFO2_MAX_SEQ(qset->max_seq - 1)
+ );
+ /* FIXME: where can we obtain these Tx parameters from? Why
+ * doesn't the chip know what Tx power to use? It knows the Rx
+ * strength and can presumably guess the Tx power required
+ * from that? */
+ qset->qh.info3 = cpu_to_le32(
+ QH_INFO3_TX_RATE(phy_rate)
+ | QH_INFO3_TX_PWR(0) /* 0 == max power */
+ );
+
+ qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
+}
+
+/**
+ * qset_clear - clear fields in a qset so it may be reinserted into a
+ * schedule.
+ *
+ * The sequence number and current window are not cleared (see
+ * qset_reset()).
+ */
+void qset_clear(struct whc *whc, struct whc_qset *qset)
+{
+ qset->td_start = qset->td_end = qset->ntds = 0;
+
+ qset->qh.link = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
+ qset->qh.status = qset->qh.status & QH_STATUS_SEQ_MASK;
+ qset->qh.err_count = 0;
+ qset->qh.scratch[0] = 0;
+ qset->qh.scratch[1] = 0;
+ qset->qh.scratch[2] = 0;
+
+ memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay));
+
+ init_completion(&qset->remove_complete);
+}
+
+/**
+ * qset_reset - reset endpoint state in a qset.
+ *
+ * Clears the sequence number and current window. This qset must not
+ * be in the ASL or PZL.
+ */
+void qset_reset(struct whc *whc, struct whc_qset *qset)
+{
+ qset->reset = 0;
+
+ qset->qh.status &= ~QH_STATUS_SEQ_MASK;
+ qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
+}
+
+/**
+ * get_qset - get the qset for an async endpoint
+ *
+ * A new qset is created if one does not already exist.
+ */
+struct whc_qset *get_qset(struct whc *whc, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct whc_qset *qset;
+
+ qset = urb->ep->hcpriv;
+ if (qset == NULL) {
+ qset = qset_alloc(whc, mem_flags);
+ if (qset == NULL)
+ return NULL;
+
+ qset->ep = urb->ep;
+ urb->ep->hcpriv = qset;
+ qset_fill_qh(whc, qset, urb);
+ }
+ return qset;
+}
+
+void qset_remove_complete(struct whc *whc, struct whc_qset *qset)
+{
+ qset->remove = 0;
+ list_del_init(&qset->list_node);
+ complete(&qset->remove_complete);
+}
+
+/**
+ * qset_add_qtds - add qTDs for an URB to a qset
+ *
+ * Returns true if the list (ASL/PZL) must be updated because (for a
+ * WHCI 0.95 controller) an activated qTD was pointed to be iCur.
+ */
+enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset)
+{
+ struct whc_std *std;
+ enum whc_update update = 0;
+
+ list_for_each_entry(std, &qset->stds, list_node) {
+ struct whc_qtd *qtd;
+ uint32_t status;
+
+ if (qset->ntds >= WHCI_QSET_TD_MAX
+ || (qset->pause_after_urb && std->urb != qset->pause_after_urb))
+ break;
+
+ if (std->qtd)
+ continue; /* already has a qTD */
+
+ qtd = std->qtd = &qset->qtd[qset->td_end];
+
+ /* Fill in setup bytes for control transfers. */
+ if (usb_pipecontrol(std->urb->pipe))
+ memcpy(qtd->setup, std->urb->setup_packet, 8);
+
+ status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len);
+
+ if (whc_std_last(std) && usb_pipeout(std->urb->pipe))
+ status |= QTD_STS_LAST_PKT;
+
+ /*
+ * For an IN transfer the iAlt field should be set so
+ * the h/w will automatically advance to the next
+ * transfer. However, if there are 8 or more TDs
+ * remaining in this transfer then iAlt cannot be set
+ * as it could point to somewhere in this transfer.
+ */
+ if (std->ntds_remaining < WHCI_QSET_TD_MAX) {
+ int ialt;
+ ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX;
+ status |= QTD_STS_IALT(ialt);
+ } else if (usb_pipein(std->urb->pipe))
+ qset->pause_after_urb = std->urb;
+
+ if (std->num_pointers)
+ qtd->options = cpu_to_le32(QTD_OPT_IOC);
+ else
+ qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL);
+ qtd->page_list_ptr = cpu_to_le64(std->dma_addr);
+
+ qtd->status = cpu_to_le32(status);
+
+ if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end)
+ update = WHC_UPDATE_UPDATED;
+
+ if (++qset->td_end >= WHCI_QSET_TD_MAX)
+ qset->td_end = 0;
+ qset->ntds++;
+ }
+
+ return update;
+}
+
+/**
+ * qset_remove_qtd - remove the first qTD from a qset.
+ *
+ * The qTD might be still active (if it's part of a IN URB that
+ * resulted in a short read) so ensure it's deactivated.
+ */
+static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
+{
+ qset->qtd[qset->td_start].status = 0;
+
+ if (++qset->td_start >= WHCI_QSET_TD_MAX)
+ qset->td_start = 0;
+ qset->ntds--;
+}
+
+static void qset_copy_bounce_to_sg(struct whc *whc, struct whc_std *std)
+{
+ struct scatterlist *sg;
+ void *bounce;
+ size_t remaining, offset;
+
+ bounce = std->bounce_buf;
+ remaining = std->len;
+
+ sg = std->bounce_sg;
+ offset = std->bounce_offset;
+
+ while (remaining) {
+ size_t len;
+
+ len = min(sg->length - offset, remaining);
+ memcpy(sg_virt(sg) + offset, bounce, len);
+
+ bounce += len;
+ remaining -= len;
+
+ offset += len;
+ if (offset >= sg->length) {
+ sg = sg_next(sg);
+ offset = 0;
+ }
+ }
+
+}
+
+/**
+ * qset_free_std - remove an sTD and free it.
+ * @whc: the WHCI host controller
+ * @std: the sTD to remove and free.
+ */
+void qset_free_std(struct whc *whc, struct whc_std *std)
+{
+ list_del(&std->list_node);
+ if (std->bounce_buf) {
+ bool is_out = usb_pipeout(std->urb->pipe);
+ dma_addr_t dma_addr;
+
+ if (std->num_pointers)
+ dma_addr = le64_to_cpu(std->pl_virt[0].buf_ptr);
+ else
+ dma_addr = std->dma_addr;
+
+ dma_unmap_single(whc->wusbhc.dev, dma_addr,
+ std->len, is_out ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ if (!is_out)
+ qset_copy_bounce_to_sg(whc, std);
+ kfree(std->bounce_buf);
+ }
+ if (std->pl_virt) {
+ if (!dma_mapping_error(whc->wusbhc.dev, std->dma_addr))
+ dma_unmap_single(whc->wusbhc.dev, std->dma_addr,
+ std->num_pointers * sizeof(struct whc_page_list_entry),
+ DMA_TO_DEVICE);
+ kfree(std->pl_virt);
+ std->pl_virt = NULL;
+ }
+ kfree(std);
+}
+
+/**
+ * qset_remove_qtds - remove an URB's qTDs (and sTDs).
+ */
+static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb)
+{
+ struct whc_std *std, *t;
+
+ list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+ if (std->urb != urb)
+ break;
+ if (std->qtd != NULL)
+ qset_remove_qtd(whc, qset);
+ qset_free_std(whc, std);
+ }
+}
+
+/**
+ * qset_free_stds - free any remaining sTDs for an URB.
+ */
+static void qset_free_stds(struct whc_qset *qset, struct urb *urb)
+{
+ struct whc_std *std, *t;
+
+ list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+ if (std->urb == urb)
+ qset_free_std(qset->whc, std);
+ }
+}
+
+static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags)
+{
+ dma_addr_t dma_addr = std->dma_addr;
+ dma_addr_t sp, ep;
+ size_t pl_len;
+ int p;
+
+ /* Short buffers don't need a page list. */
+ if (std->len <= WHCI_PAGE_SIZE) {
+ std->num_pointers = 0;
+ return 0;
+ }
+
+ sp = dma_addr & ~(WHCI_PAGE_SIZE-1);
+ ep = dma_addr + std->len;
+ std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
+
+ pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
+ std->pl_virt = kmalloc(pl_len, mem_flags);
+ if (std->pl_virt == NULL)
+ return -ENOMEM;
+ std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE);
+ if (dma_mapping_error(whc->wusbhc.dev, std->dma_addr)) {
+ kfree(std->pl_virt);
+ return -EFAULT;
+ }
+
+ for (p = 0; p < std->num_pointers; p++) {
+ std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
+ dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1);
+ }
+
+ return 0;
+}
+
+/**
+ * urb_dequeue_work - executes asl/pzl update and gives back the urb to the system.
+ */
+static void urb_dequeue_work(struct work_struct *work)
+{
+ struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work);
+ struct whc_qset *qset = wurb->qset;
+ struct whc *whc = qset->whc;
+ unsigned long flags;
+
+ if (wurb->is_async)
+ asl_update(whc, WUSBCMD_ASYNC_UPDATED
+ | WUSBCMD_ASYNC_SYNCED_DB
+ | WUSBCMD_ASYNC_QSET_RM);
+ else
+ pzl_update(whc, WUSBCMD_PERIODIC_UPDATED
+ | WUSBCMD_PERIODIC_SYNCED_DB
+ | WUSBCMD_PERIODIC_QSET_RM);
+
+ spin_lock_irqsave(&whc->lock, flags);
+ qset_remove_urb(whc, qset, wurb->urb, wurb->status);
+ spin_unlock_irqrestore(&whc->lock, flags);
+}
+
+static struct whc_std *qset_new_std(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb, gfp_t mem_flags)
+{
+ struct whc_std *std;
+
+ std = kzalloc(sizeof(struct whc_std), mem_flags);
+ if (std == NULL)
+ return NULL;
+
+ std->urb = urb;
+ std->qtd = NULL;
+
+ INIT_LIST_HEAD(&std->list_node);
+ list_add_tail(&std->list_node, &qset->stds);
+
+ return std;
+}
+
+static int qset_add_urb_sg(struct whc *whc, struct whc_qset *qset, struct urb *urb,
+ gfp_t mem_flags)
+{
+ size_t remaining;
+ struct scatterlist *sg;
+ int i;
+ int ntds = 0;
+ struct whc_std *std = NULL;
+ struct whc_page_list_entry *new_pl_virt;
+ dma_addr_t prev_end = 0;
+ size_t pl_len;
+ int p = 0;
+
+ remaining = urb->transfer_buffer_length;
+
+ for_each_sg(urb->sg, sg, urb->num_mapped_sgs, i) {
+ dma_addr_t dma_addr;
+ size_t dma_remaining;
+ dma_addr_t sp, ep;
+ int num_pointers;
+
+ if (remaining == 0) {
+ break;
+ }
+
+ dma_addr = sg_dma_address(sg);
+ dma_remaining = min_t(size_t, sg_dma_len(sg), remaining);
+
+ while (dma_remaining) {
+ size_t dma_len;
+
+ /*
+ * We can use the previous std (if it exists) provided that:
+ * - the previous one ended on a page boundary.
+ * - the current one begins on a page boundary.
+ * - the previous one isn't full.
+ *
+ * If a new std is needed but the previous one
+ * was not a whole number of packets then this
+ * sg list cannot be mapped onto multiple
+ * qTDs. Return an error and let the caller
+ * sort it out.
+ */
+ if (!std
+ || (prev_end & (WHCI_PAGE_SIZE-1))
+ || (dma_addr & (WHCI_PAGE_SIZE-1))
+ || std->len + WHCI_PAGE_SIZE > QTD_MAX_XFER_SIZE) {
+ if (std && std->len % qset->max_packet != 0)
+ return -EINVAL;
+ std = qset_new_std(whc, qset, urb, mem_flags);
+ if (std == NULL) {
+ return -ENOMEM;
+ }
+ ntds++;
+ p = 0;
+ }
+
+ dma_len = dma_remaining;
+
+ /*
+ * If the remainder of this element doesn't
+ * fit in a single qTD, limit the qTD to a
+ * whole number of packets. This allows the
+ * remainder to go into the next qTD.
+ */
+ if (std->len + dma_len > QTD_MAX_XFER_SIZE) {
+ dma_len = (QTD_MAX_XFER_SIZE / qset->max_packet)
+ * qset->max_packet - std->len;
+ }
+
+ std->len += dma_len;
+ std->ntds_remaining = -1; /* filled in later */
+
+ sp = dma_addr & ~(WHCI_PAGE_SIZE-1);
+ ep = dma_addr + dma_len;
+ num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
+ std->num_pointers += num_pointers;
+
+ pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
+
+ new_pl_virt = krealloc(std->pl_virt, pl_len, mem_flags);
+ if (new_pl_virt == NULL) {
+ kfree(std->pl_virt);
+ std->pl_virt = NULL;
+ return -ENOMEM;
+ }
+ std->pl_virt = new_pl_virt;
+
+ for (;p < std->num_pointers; p++) {
+ std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
+ dma_addr = (dma_addr + WHCI_PAGE_SIZE) & ~(WHCI_PAGE_SIZE-1);
+ }
+
+ prev_end = dma_addr = ep;
+ dma_remaining -= dma_len;
+ remaining -= dma_len;
+ }
+ }
+
+ /* Now the number of stds is know, go back and fill in
+ std->ntds_remaining. */
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (std->ntds_remaining == -1) {
+ pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
+ std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt,
+ pl_len, DMA_TO_DEVICE);
+ if (dma_mapping_error(whc->wusbhc.dev, std->dma_addr))
+ return -EFAULT;
+ std->ntds_remaining = ntds--;
+ }
+ }
+ return 0;
+}
+
+/**
+ * qset_add_urb_sg_linearize - add an urb with sg list, copying the data
+ *
+ * If the URB contains an sg list whose elements cannot be directly
+ * mapped to qTDs then the data must be transferred via bounce
+ * buffers.
+ */
+static int qset_add_urb_sg_linearize(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb, gfp_t mem_flags)
+{
+ bool is_out = usb_pipeout(urb->pipe);
+ size_t max_std_len;
+ size_t remaining;
+ int ntds = 0;
+ struct whc_std *std = NULL;
+ void *bounce = NULL;
+ struct scatterlist *sg;
+ int i;
+
+ /* limit maximum bounce buffer to 16 * 3.5 KiB ~= 28 k */
+ max_std_len = qset->max_burst * qset->max_packet;
+
+ remaining = urb->transfer_buffer_length;
+
+ for_each_sg(urb->sg, sg, urb->num_mapped_sgs, i) {
+ size_t len;
+ size_t sg_remaining;
+ void *orig;
+
+ if (remaining == 0) {
+ break;
+ }
+
+ sg_remaining = min_t(size_t, remaining, sg->length);
+ orig = sg_virt(sg);
+
+ while (sg_remaining) {
+ if (!std || std->len == max_std_len) {
+ std = qset_new_std(whc, qset, urb, mem_flags);
+ if (std == NULL)
+ return -ENOMEM;
+ std->bounce_buf = kmalloc(max_std_len, mem_flags);
+ if (std->bounce_buf == NULL)
+ return -ENOMEM;
+ std->bounce_sg = sg;
+ std->bounce_offset = orig - sg_virt(sg);
+ bounce = std->bounce_buf;
+ ntds++;
+ }
+
+ len = min(sg_remaining, max_std_len - std->len);
+
+ if (is_out)
+ memcpy(bounce, orig, len);
+
+ std->len += len;
+ std->ntds_remaining = -1; /* filled in later */
+
+ bounce += len;
+ orig += len;
+ sg_remaining -= len;
+ remaining -= len;
+ }
+ }
+
+ /*
+ * For each of the new sTDs, map the bounce buffers, create
+ * page lists (if necessary), and fill in std->ntds_remaining.
+ */
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (std->ntds_remaining != -1)
+ continue;
+
+ std->dma_addr = dma_map_single(&whc->umc->dev, std->bounce_buf, std->len,
+ is_out ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ if (dma_mapping_error(&whc->umc->dev, std->dma_addr))
+ return -EFAULT;
+
+ if (qset_fill_page_list(whc, std, mem_flags) < 0)
+ return -ENOMEM;
+
+ std->ntds_remaining = ntds--;
+ }
+
+ return 0;
+}
+
+/**
+ * qset_add_urb - add an urb to the qset's queue.
+ *
+ * The URB is chopped into sTDs, one for each qTD that will required.
+ * At least one qTD (and sTD) is required even if the transfer has no
+ * data (e.g., for some control transfers).
+ */
+int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct whc_urb *wurb;
+ int remaining = urb->transfer_buffer_length;
+ u64 transfer_dma = urb->transfer_dma;
+ int ntds_remaining;
+ int ret;
+
+ wurb = kzalloc(sizeof(struct whc_urb), mem_flags);
+ if (wurb == NULL)
+ goto err_no_mem;
+ urb->hcpriv = wurb;
+ wurb->qset = qset;
+ wurb->urb = urb;
+ INIT_WORK(&wurb->dequeue_work, urb_dequeue_work);
+
+ if (urb->num_sgs) {
+ ret = qset_add_urb_sg(whc, qset, urb, mem_flags);
+ if (ret == -EINVAL) {
+ qset_free_stds(qset, urb);
+ ret = qset_add_urb_sg_linearize(whc, qset, urb, mem_flags);
+ }
+ if (ret < 0)
+ goto err_no_mem;
+ return 0;
+ }
+
+ ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE);
+ if (ntds_remaining == 0)
+ ntds_remaining = 1;
+
+ while (ntds_remaining) {
+ struct whc_std *std;
+ size_t std_len;
+
+ std_len = remaining;
+ if (std_len > QTD_MAX_XFER_SIZE)
+ std_len = QTD_MAX_XFER_SIZE;
+
+ std = qset_new_std(whc, qset, urb, mem_flags);
+ if (std == NULL)
+ goto err_no_mem;
+
+ std->dma_addr = transfer_dma;
+ std->len = std_len;
+ std->ntds_remaining = ntds_remaining;
+
+ if (qset_fill_page_list(whc, std, mem_flags) < 0)
+ goto err_no_mem;
+
+ ntds_remaining--;
+ remaining -= std_len;
+ transfer_dma += std_len;
+ }
+
+ return 0;
+
+err_no_mem:
+ qset_free_stds(qset, urb);
+ return -ENOMEM;
+}
+
+/**
+ * qset_remove_urb - remove an URB from the urb queue.
+ *
+ * The URB is returned to the USB subsystem.
+ */
+void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb, int status)
+{
+ struct wusbhc *wusbhc = &whc->wusbhc;
+ struct whc_urb *wurb = urb->hcpriv;
+
+ usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb);
+ /* Drop the lock as urb->complete() may enqueue another urb. */
+ spin_unlock(&whc->lock);
+ wusbhc_giveback_urb(wusbhc, urb, status);
+ spin_lock(&whc->lock);
+
+ kfree(wurb);
+}
+
+/**
+ * get_urb_status_from_qtd - get the completed urb status from qTD status
+ * @urb: completed urb
+ * @status: qTD status
+ */
+static int get_urb_status_from_qtd(struct urb *urb, u32 status)
+{
+ if (status & QTD_STS_HALTED) {
+ if (status & QTD_STS_DBE)
+ return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM;
+ else if (status & QTD_STS_BABBLE)
+ return -EOVERFLOW;
+ else if (status & QTD_STS_RCE)
+ return -ETIME;
+ return -EPIPE;
+ }
+ if (usb_pipein(urb->pipe)
+ && (urb->transfer_flags & URB_SHORT_NOT_OK)
+ && urb->actual_length < urb->transfer_buffer_length)
+ return -EREMOTEIO;
+ return 0;
+}
+
+/**
+ * process_inactive_qtd - process an inactive (but not halted) qTD.
+ *
+ * Update the urb with the transfer bytes from the qTD, if the urb is
+ * completely transferred or (in the case of an IN only) the LPF is
+ * set, then the transfer is complete and the urb should be returned
+ * to the system.
+ */
+void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
+ struct whc_qtd *qtd)
+{
+ struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
+ struct urb *urb = std->urb;
+ uint32_t status;
+ bool complete;
+
+ status = le32_to_cpu(qtd->status);
+
+ urb->actual_length += std->len - QTD_STS_TO_LEN(status);
+
+ if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT))
+ complete = true;
+ else
+ complete = whc_std_last(std);
+
+ qset_remove_qtd(whc, qset);
+ qset_free_std(whc, std);
+
+ /*
+ * Transfers for this URB are complete? Then return it to the
+ * USB subsystem.
+ */
+ if (complete) {
+ qset_remove_qtds(whc, qset, urb);
+ qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status));
+
+ /*
+ * If iAlt isn't valid then the hardware didn't
+ * advance iCur. Adjust the start and end pointers to
+ * match iCur.
+ */
+ if (!(status & QTD_STS_IALT_VALID))
+ qset->td_start = qset->td_end
+ = QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status));
+ qset->pause_after_urb = NULL;
+ }
+}
+
+/**
+ * process_halted_qtd - process a qset with a halted qtd
+ *
+ * Remove all the qTDs for the failed URB and return the failed URB to
+ * the USB subsystem. Then remove all other qTDs so the qset can be
+ * removed.
+ *
+ * FIXME: this is the point where rate adaptation can be done. If a
+ * transfer failed because it exceeded the maximum number of retries
+ * then it could be reactivated with a slower rate without having to
+ * remove the qset.
+ */
+void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
+ struct whc_qtd *qtd)
+{
+ struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
+ struct urb *urb = std->urb;
+ int urb_status;
+
+ urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status));
+
+ qset_remove_qtds(whc, qset, urb);
+ qset_remove_urb(whc, qset, urb, urb_status);
+
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (qset->ntds == 0)
+ break;
+ qset_remove_qtd(whc, qset);
+ std->qtd = NULL;
+ }
+
+ qset->remove = 1;
+}
+
+void qset_free(struct whc *whc, struct whc_qset *qset)
+{
+ dma_pool_free(whc->qset_pool, qset, qset->qset_dma);
+}
+
+/**
+ * qset_delete - wait for a qset to be unused, then free it.
+ */
+void qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+ wait_for_completion(&qset->remove_complete);
+ qset_free(whc, qset);
+}
diff --git a/drivers/staging/wusbcore/host/whci/whcd.h b/drivers/staging/wusbcore/host/whci/whcd.h
new file mode 100644
index 000000000000..a442a2589e83
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/whcd.h
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) private header.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#ifndef __WHCD_H
+#define __WHCD_H
+
+#include <linux/workqueue.h>
+
+#include "../../../uwb/include/whci.h"
+#include "../../../uwb/include/umc.h"
+#include "whci-hc.h"
+
+/* Generic command timeout. */
+#define WHC_GENCMD_TIMEOUT_MS 100
+
+struct whc_dbg;
+
+struct whc {
+ struct wusbhc wusbhc;
+ struct umc_dev *umc;
+
+ resource_size_t base_phys;
+ void __iomem *base;
+ int irq;
+
+ u8 n_devices;
+ u8 n_keys;
+ u8 n_mmc_ies;
+
+ u64 *pz_list;
+ struct dn_buf_entry *dn_buf;
+ struct di_buf_entry *di_buf;
+ dma_addr_t pz_list_dma;
+ dma_addr_t dn_buf_dma;
+ dma_addr_t di_buf_dma;
+
+ spinlock_t lock;
+ struct mutex mutex;
+
+ void * gen_cmd_buf;
+ dma_addr_t gen_cmd_buf_dma;
+ wait_queue_head_t cmd_wq;
+
+ struct workqueue_struct *workqueue;
+ struct work_struct dn_work;
+
+ struct dma_pool *qset_pool;
+
+ struct list_head async_list;
+ struct list_head async_removed_list;
+ wait_queue_head_t async_list_wq;
+ struct work_struct async_work;
+
+ struct list_head periodic_list[5];
+ struct list_head periodic_removed_list;
+ wait_queue_head_t periodic_list_wq;
+ struct work_struct periodic_work;
+
+ struct whc_dbg *dbg;
+};
+
+#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc))
+
+/**
+ * struct whc_std - a software TD.
+ * @urb: the URB this sTD is for.
+ * @offset: start of the URB's data for this TD.
+ * @len: the length of data in the associated TD.
+ * @ntds_remaining: number of TDs (starting from this one) in this transfer.
+ *
+ * @bounce_buf: a bounce buffer if the std was from an urb with a sg
+ * list that could not be mapped to qTDs directly.
+ * @bounce_sg: the first scatterlist element bounce_buf is for.
+ * @bounce_offset: the offset into bounce_sg for the start of bounce_buf.
+ *
+ * Queued URBs may require more TDs than are available in a qset so we
+ * use a list of these "software TDs" (sTDs) to hold per-TD data.
+ */
+struct whc_std {
+ struct urb *urb;
+ size_t len;
+ int ntds_remaining;
+ struct whc_qtd *qtd;
+
+ struct list_head list_node;
+ int num_pointers;
+ dma_addr_t dma_addr;
+ struct whc_page_list_entry *pl_virt;
+
+ void *bounce_buf;
+ struct scatterlist *bounce_sg;
+ unsigned bounce_offset;
+};
+
+/**
+ * struct whc_urb - per URB host controller structure.
+ * @urb: the URB this struct is for.
+ * @qset: the qset associated to the URB.
+ * @dequeue_work: the work to remove the URB when dequeued.
+ * @is_async: the URB belongs to async sheduler or not.
+ * @status: the status to be returned when calling wusbhc_giveback_urb.
+ */
+struct whc_urb {
+ struct urb *urb;
+ struct whc_qset *qset;
+ struct work_struct dequeue_work;
+ bool is_async;
+ int status;
+};
+
+/**
+ * whc_std_last - is this sTD the URB's last?
+ * @std: the sTD to check.
+ */
+static inline bool whc_std_last(struct whc_std *std)
+{
+ return std->ntds_remaining <= 1;
+}
+
+enum whc_update {
+ WHC_UPDATE_ADDED = 0x01,
+ WHC_UPDATE_REMOVED = 0x02,
+ WHC_UPDATE_UPDATED = 0x04,
+};
+
+/* init.c */
+int whc_init(struct whc *whc);
+void whc_clean_up(struct whc *whc);
+
+/* hw.c */
+void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val);
+int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len);
+void whc_hw_error(struct whc *whc, const char *reason);
+
+/* wusb.c */
+int whc_wusbhc_start(struct wusbhc *wusbhc);
+void whc_wusbhc_stop(struct wusbhc *wusbhc, int delay);
+int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+ u8 handle, struct wuie_hdr *wuie);
+int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle);
+int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm);
+int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev);
+int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots);
+int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+ const void *ptk, size_t key_size);
+int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+ const void *gtk, size_t key_size);
+int whc_set_cluster_id(struct whc *whc, u8 bcid);
+
+/* int.c */
+irqreturn_t whc_int_handler(struct usb_hcd *hcd);
+void whc_dn_work(struct work_struct *work);
+
+/* asl.c */
+void asl_start(struct whc *whc);
+void asl_stop(struct whc *whc);
+int asl_init(struct whc *whc);
+void asl_clean_up(struct whc *whc);
+int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
+void asl_qset_delete(struct whc *whc, struct whc_qset *qset);
+void scan_async_work(struct work_struct *work);
+
+/* pzl.c */
+int pzl_init(struct whc *whc);
+void pzl_clean_up(struct whc *whc);
+void pzl_start(struct whc *whc);
+void pzl_stop(struct whc *whc);
+int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
+void pzl_qset_delete(struct whc *whc, struct whc_qset *qset);
+void scan_periodic_work(struct work_struct *work);
+
+/* qset.c */
+struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags);
+void qset_free(struct whc *whc, struct whc_qset *qset);
+struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+void qset_delete(struct whc *whc, struct whc_qset *qset);
+void qset_clear(struct whc *whc, struct whc_qset *qset);
+void qset_reset(struct whc *whc, struct whc_qset *qset);
+int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
+ gfp_t mem_flags);
+void qset_free_std(struct whc *whc, struct whc_std *std);
+void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb, int status);
+void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
+ struct whc_qtd *qtd);
+void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
+ struct whc_qtd *qtd);
+enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset);
+void qset_remove_complete(struct whc *whc, struct whc_qset *qset);
+void pzl_update(struct whc *whc, uint32_t wusbcmd);
+void asl_update(struct whc *whc, uint32_t wusbcmd);
+
+/* debug.c */
+void whc_dbg_init(struct whc *whc);
+void whc_dbg_clean_up(struct whc *whc);
+
+#endif /* #ifndef __WHCD_H */
diff --git a/drivers/staging/wusbcore/host/whci/whci-hc.h b/drivers/staging/wusbcore/host/whci/whci-hc.h
new file mode 100644
index 000000000000..5a86a57a80cc
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/whci-hc.h
@@ -0,0 +1,401 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) data structures.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#ifndef _WHCI_WHCI_HC_H
+#define _WHCI_WHCI_HC_H
+
+#include <linux/list.h>
+
+/**
+ * WHCI_PAGE_SIZE - page size use by WHCI
+ *
+ * WHCI assumes that host system uses pages of 4096 octets.
+ */
+#define WHCI_PAGE_SIZE 4096
+
+
+/**
+ * QTD_MAX_TXFER_SIZE - max number of bytes to transfer with a single
+ * qtd.
+ *
+ * This is 2^20 - 1.
+ */
+#define QTD_MAX_XFER_SIZE 1048575
+
+
+/**
+ * struct whc_qtd - Queue Element Transfer Descriptors (qTD)
+ *
+ * This describes the data for a bulk, control or interrupt transfer.
+ *
+ * [WHCI] section 3.2.4
+ */
+struct whc_qtd {
+ __le32 status; /*< remaining transfer len and transfer status */
+ __le32 options;
+ __le64 page_list_ptr; /*< physical pointer to data buffer page list*/
+ __u8 setup[8]; /*< setup data for control transfers */
+} __attribute__((packed));
+
+#define QTD_STS_ACTIVE (1 << 31) /* enable execution of transaction */
+#define QTD_STS_HALTED (1 << 30) /* transfer halted */
+#define QTD_STS_DBE (1 << 29) /* data buffer error */
+#define QTD_STS_BABBLE (1 << 28) /* babble detected */
+#define QTD_STS_RCE (1 << 27) /* retry count exceeded */
+#define QTD_STS_LAST_PKT (1 << 26) /* set Last Packet Flag in WUSB header */
+#define QTD_STS_INACTIVE (1 << 25) /* queue set is marked inactive */
+#define QTD_STS_IALT_VALID (1 << 23) /* iAlt field is valid */
+#define QTD_STS_IALT(i) (QTD_STS_IALT_VALID | ((i) << 20)) /* iAlt field */
+#define QTD_STS_LEN(l) ((l) << 0) /* transfer length */
+#define QTD_STS_TO_LEN(s) ((s) & 0x000fffff)
+
+#define QTD_OPT_IOC (1 << 1) /* page_list_ptr points to buffer directly */
+#define QTD_OPT_SMALL (1 << 0) /* interrupt on complete */
+
+/**
+ * struct whc_itd - Isochronous Queue Element Transfer Descriptors (iTD)
+ *
+ * This describes the data and other parameters for an isochronous
+ * transfer.
+ *
+ * [WHCI] section 3.2.5
+ */
+struct whc_itd {
+ __le16 presentation_time; /*< presentation time for OUT transfers */
+ __u8 num_segments; /*< number of data segments in segment list */
+ __u8 status; /*< command execution status */
+ __le32 options; /*< misc transfer options */
+ __le64 page_list_ptr; /*< physical pointer to data buffer page list */
+ __le64 seg_list_ptr; /*< physical pointer to segment list */
+} __attribute__((packed));
+
+#define ITD_STS_ACTIVE (1 << 7) /* enable execution of transaction */
+#define ITD_STS_DBE (1 << 5) /* data buffer error */
+#define ITD_STS_BABBLE (1 << 4) /* babble detected */
+#define ITD_STS_INACTIVE (1 << 1) /* queue set is marked inactive */
+
+#define ITD_OPT_IOC (1 << 1) /* interrupt on complete */
+#define ITD_OPT_SMALL (1 << 0) /* page_list_ptr points to buffer directly */
+
+/**
+ * Page list entry.
+ *
+ * A TD's page list must contain sufficient page list entries for the
+ * total data length in the TD.
+ *
+ * [WHCI] section 3.2.4.3
+ */
+struct whc_page_list_entry {
+ __le64 buf_ptr; /*< physical pointer to buffer */
+} __attribute__((packed));
+
+/**
+ * struct whc_seg_list_entry - Segment list entry.
+ *
+ * Describes a portion of the data buffer described in the containing
+ * qTD's page list.
+ *
+ * seg_ptr = qtd->page_list_ptr[qtd->seg_list_ptr[seg].idx].buf_ptr
+ * + qtd->seg_list_ptr[seg].offset;
+ *
+ * Segments can't cross page boundries.
+ *
+ * [WHCI] section 3.2.5.5
+ */
+struct whc_seg_list_entry {
+ __le16 len; /*< segment length */
+ __u8 idx; /*< index into page list */
+ __u8 status; /*< segment status */
+ __le16 offset; /*< 12 bit offset into page */
+} __attribute__((packed));
+
+/**
+ * struct whc_qhead - endpoint and status information for a qset.
+ *
+ * [WHCI] section 3.2.6
+ */
+struct whc_qhead {
+ __le64 link; /*< next qset in list */
+ __le32 info1;
+ __le32 info2;
+ __le32 info3;
+ __le16 status;
+ __le16 err_count; /*< transaction error count */
+ __le32 cur_window;
+ __le32 scratch[3]; /*< h/w scratch area */
+ union {
+ struct whc_qtd qtd;
+ struct whc_itd itd;
+ } overlay;
+} __attribute__((packed));
+
+#define QH_LINK_PTR_MASK (~0x03Full)
+#define QH_LINK_PTR(ptr) ((ptr) & QH_LINK_PTR_MASK)
+#define QH_LINK_IQS (1 << 4) /* isochronous queue set */
+#define QH_LINK_NTDS(n) (((n) - 1) << 1) /* number of TDs in queue set */
+#define QH_LINK_T (1 << 0) /* last queue set in periodic schedule list */
+
+#define QH_INFO1_EP(e) ((e) << 0) /* endpoint number */
+#define QH_INFO1_DIR_IN (1 << 4) /* IN transfer */
+#define QH_INFO1_DIR_OUT (0 << 4) /* OUT transfer */
+#define QH_INFO1_TR_TYPE_CTRL (0x0 << 5) /* control transfer */
+#define QH_INFO1_TR_TYPE_ISOC (0x1 << 5) /* isochronous transfer */
+#define QH_INFO1_TR_TYPE_BULK (0x2 << 5) /* bulk transfer */
+#define QH_INFO1_TR_TYPE_INT (0x3 << 5) /* interrupt */
+#define QH_INFO1_TR_TYPE_LP_INT (0x7 << 5) /* low power interrupt */
+#define QH_INFO1_DEV_INFO_IDX(i) ((i) << 8) /* index into device info buffer */
+#define QH_INFO1_SET_INACTIVE (1 << 15) /* set inactive after transfer */
+#define QH_INFO1_MAX_PKT_LEN(l) ((l) << 16) /* maximum packet length */
+
+#define QH_INFO2_BURST(b) ((b) << 0) /* maximum burst length */
+#define QH_INFO2_DBP(p) ((p) << 5) /* data burst policy (see [WUSB] table 5-7) */
+#define QH_INFO2_MAX_COUNT(c) ((c) << 8) /* max isoc/int pkts per zone */
+#define QH_INFO2_RQS (1 << 15) /* reactivate queue set */
+#define QH_INFO2_MAX_RETRY(r) ((r) << 16) /* maximum transaction retries */
+#define QH_INFO2_MAX_SEQ(s) ((s) << 20) /* maximum sequence number */
+#define QH_INFO3_MAX_DELAY(d) ((d) << 0) /* maximum stream delay in 125 us units (isoc only) */
+#define QH_INFO3_INTERVAL(i) ((i) << 16) /* segment interval in 125 us units (isoc only) */
+
+#define QH_INFO3_TX_RATE(r) ((r) << 24) /* PHY rate (see [ECMA-368] section 10.3.1.1) */
+#define QH_INFO3_TX_PWR(p) ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */
+
+#define QH_STATUS_FLOW_CTRL (1 << 15)
+#define QH_STATUS_ICUR(i) ((i) << 5)
+#define QH_STATUS_TO_ICUR(s) (((s) >> 5) & 0x7)
+#define QH_STATUS_SEQ_MASK 0x1f
+
+/**
+ * usb_pipe_to_qh_type - USB core pipe type to QH transfer type
+ *
+ * Returns the QH type field for a USB core pipe type.
+ */
+static inline unsigned usb_pipe_to_qh_type(unsigned pipe)
+{
+ static const unsigned type[] = {
+ [PIPE_ISOCHRONOUS] = QH_INFO1_TR_TYPE_ISOC,
+ [PIPE_INTERRUPT] = QH_INFO1_TR_TYPE_INT,
+ [PIPE_CONTROL] = QH_INFO1_TR_TYPE_CTRL,
+ [PIPE_BULK] = QH_INFO1_TR_TYPE_BULK,
+ };
+ return type[usb_pipetype(pipe)];
+}
+
+/**
+ * Maxiumum number of TDs in a qset.
+ */
+#define WHCI_QSET_TD_MAX 8
+
+/**
+ * struct whc_qset - WUSB data transfers to a specific endpoint
+ * @qh: the QHead of this qset
+ * @qtd: up to 8 qTDs (for qsets for control, bulk and interrupt
+ * transfers)
+ * @itd: up to 8 iTDs (for qsets for isochronous transfers)
+ * @qset_dma: DMA address for this qset
+ * @whc: WHCI HC this qset is for
+ * @ep: endpoint
+ * @stds: list of sTDs queued to this qset
+ * @ntds: number of qTDs queued (not necessarily the same as nTDs
+ * field in the QH)
+ * @td_start: index of the first qTD in the list
+ * @td_end: index of next free qTD in the list (provided
+ * ntds < WHCI_QSET_TD_MAX)
+ *
+ * Queue Sets (qsets) are added to the asynchronous schedule list
+ * (ASL) or the periodic zone list (PZL).
+ *
+ * qsets may contain up to 8 TDs (either qTDs or iTDs as appropriate).
+ * Each TD may refer to at most 1 MiB of data. If a single transfer
+ * has > 8MiB of data, TDs can be reused as they are completed since
+ * the TD list is used as a circular buffer. Similarly, several
+ * (smaller) transfers may be queued in a qset.
+ *
+ * WHCI controllers may cache portions of the qsets in the ASL and
+ * PZL, requiring the WHCD to inform the WHC that the lists have been
+ * updated (fields changed or qsets inserted or removed). For safe
+ * insertion and removal of qsets from the lists the schedule must be
+ * stopped to avoid races in updating the QH link pointers.
+ *
+ * Since the HC is free to execute qsets in any order, all transfers
+ * to an endpoint should use the same qset to ensure transfers are
+ * executed in the order they're submitted.
+ *
+ * [WHCI] section 3.2.3
+ */
+struct whc_qset {
+ struct whc_qhead qh;
+ union {
+ struct whc_qtd qtd[WHCI_QSET_TD_MAX];
+ struct whc_itd itd[WHCI_QSET_TD_MAX];
+ };
+
+ /* private data for WHCD */
+ dma_addr_t qset_dma;
+ struct whc *whc;
+ struct usb_host_endpoint *ep;
+ struct list_head stds;
+ int ntds;
+ int td_start;
+ int td_end;
+ struct list_head list_node;
+ unsigned in_sw_list:1;
+ unsigned in_hw_list:1;
+ unsigned remove:1;
+ unsigned reset:1;
+ struct urb *pause_after_urb;
+ struct completion remove_complete;
+ uint16_t max_packet;
+ uint8_t max_burst;
+ uint8_t max_seq;
+};
+
+static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target)
+{
+ if (target)
+ *ptr = (*ptr & ~(QH_LINK_PTR_MASK | QH_LINK_T)) | QH_LINK_PTR(target);
+ else
+ *ptr = QH_LINK_T;
+}
+
+/**
+ * struct di_buf_entry - Device Information (DI) buffer entry.
+ *
+ * There's one of these per connected device.
+ */
+struct di_buf_entry {
+ __le32 availability_info[8]; /*< MAS availability information, one MAS per bit */
+ __le32 addr_sec_info; /*< addressing and security info */
+ __le32 reserved[7];
+} __attribute__((packed));
+
+#define WHC_DI_SECURE (1 << 31)
+#define WHC_DI_DISABLE (1 << 30)
+#define WHC_DI_KEY_IDX(k) ((k) << 8)
+#define WHC_DI_KEY_IDX_MASK 0x0000ff00
+#define WHC_DI_DEV_ADDR(a) ((a) << 0)
+#define WHC_DI_DEV_ADDR_MASK 0x000000ff
+
+/**
+ * struct dn_buf_entry - Device Notification (DN) buffer entry.
+ *
+ * [WHCI] section 3.2.8
+ */
+struct dn_buf_entry {
+ __u8 msg_size; /*< number of octets of valid DN data */
+ __u8 reserved1;
+ __u8 src_addr; /*< source address */
+ __u8 status; /*< buffer entry status */
+ __le32 tkid; /*< TKID for source device, valid if secure bit is set */
+ __u8 dn_data[56]; /*< up to 56 octets of DN data */
+} __attribute__((packed));
+
+#define WHC_DN_STATUS_VALID (1 << 7) /* buffer entry is valid */
+#define WHC_DN_STATUS_SECURE (1 << 6) /* notification received using secure frame */
+
+#define WHC_N_DN_ENTRIES (4096 / sizeof(struct dn_buf_entry))
+
+/* The Add MMC IE WUSB Generic Command may take up to 256 bytes of
+ data. [WHCI] section 2.4.7. */
+#define WHC_GEN_CMD_DATA_LEN 256
+
+/*
+ * HC registers.
+ *
+ * [WHCI] section 2.4
+ */
+
+#define WHCIVERSION 0x00
+
+#define WHCSPARAMS 0x04
+# define WHCSPARAMS_TO_N_MMC_IES(p) (((p) >> 16) & 0xff)
+# define WHCSPARAMS_TO_N_KEYS(p) (((p) >> 8) & 0xff)
+# define WHCSPARAMS_TO_N_DEVICES(p) (((p) >> 0) & 0x7f)
+
+#define WUSBCMD 0x08
+# define WUSBCMD_BCID(b) ((b) << 16)
+# define WUSBCMD_BCID_MASK (0xff << 16)
+# define WUSBCMD_ASYNC_QSET_RM (1 << 12)
+# define WUSBCMD_PERIODIC_QSET_RM (1 << 11)
+# define WUSBCMD_WUSBSI(s) ((s) << 8)
+# define WUSBCMD_WUSBSI_MASK (0x7 << 8)
+# define WUSBCMD_ASYNC_SYNCED_DB (1 << 7)
+# define WUSBCMD_PERIODIC_SYNCED_DB (1 << 6)
+# define WUSBCMD_ASYNC_UPDATED (1 << 5)
+# define WUSBCMD_PERIODIC_UPDATED (1 << 4)
+# define WUSBCMD_ASYNC_EN (1 << 3)
+# define WUSBCMD_PERIODIC_EN (1 << 2)
+# define WUSBCMD_WHCRESET (1 << 1)
+# define WUSBCMD_RUN (1 << 0)
+
+#define WUSBSTS 0x0c
+# define WUSBSTS_ASYNC_SCHED (1 << 15)
+# define WUSBSTS_PERIODIC_SCHED (1 << 14)
+# define WUSBSTS_DNTS_SCHED (1 << 13)
+# define WUSBSTS_HCHALTED (1 << 12)
+# define WUSBSTS_GEN_CMD_DONE (1 << 9)
+# define WUSBSTS_CHAN_TIME_ROLLOVER (1 << 8)
+# define WUSBSTS_DNTS_OVERFLOW (1 << 7)
+# define WUSBSTS_BPST_ADJUSTMENT_CHANGED (1 << 6)
+# define WUSBSTS_HOST_ERR (1 << 5)
+# define WUSBSTS_ASYNC_SCHED_SYNCED (1 << 4)
+# define WUSBSTS_PERIODIC_SCHED_SYNCED (1 << 3)
+# define WUSBSTS_DNTS_INT (1 << 2)
+# define WUSBSTS_ERR_INT (1 << 1)
+# define WUSBSTS_INT (1 << 0)
+# define WUSBSTS_INT_MASK 0x3ff
+
+#define WUSBINTR 0x10
+# define WUSBINTR_GEN_CMD_DONE (1 << 9)
+# define WUSBINTR_CHAN_TIME_ROLLOVER (1 << 8)
+# define WUSBINTR_DNTS_OVERFLOW (1 << 7)
+# define WUSBINTR_BPST_ADJUSTMENT_CHANGED (1 << 6)
+# define WUSBINTR_HOST_ERR (1 << 5)
+# define WUSBINTR_ASYNC_SCHED_SYNCED (1 << 4)
+# define WUSBINTR_PERIODIC_SCHED_SYNCED (1 << 3)
+# define WUSBINTR_DNTS_INT (1 << 2)
+# define WUSBINTR_ERR_INT (1 << 1)
+# define WUSBINTR_INT (1 << 0)
+# define WUSBINTR_ALL 0x3ff
+
+#define WUSBGENCMDSTS 0x14
+# define WUSBGENCMDSTS_ACTIVE (1 << 31)
+# define WUSBGENCMDSTS_ERROR (1 << 24)
+# define WUSBGENCMDSTS_IOC (1 << 23)
+# define WUSBGENCMDSTS_MMCIE_ADD 0x01
+# define WUSBGENCMDSTS_MMCIE_RM 0x02
+# define WUSBGENCMDSTS_SET_MAS 0x03
+# define WUSBGENCMDSTS_CHAN_STOP 0x04
+# define WUSBGENCMDSTS_RWP_EN 0x05
+
+#define WUSBGENCMDPARAMS 0x18
+#define WUSBGENADDR 0x20
+#define WUSBASYNCLISTADDR 0x28
+#define WUSBDNTSBUFADDR 0x30
+#define WUSBDEVICEINFOADDR 0x38
+
+#define WUSBSETSECKEYCMD 0x40
+# define WUSBSETSECKEYCMD_SET (1 << 31)
+# define WUSBSETSECKEYCMD_ERASE (1 << 30)
+# define WUSBSETSECKEYCMD_GTK (1 << 8)
+# define WUSBSETSECKEYCMD_IDX(i) ((i) << 0)
+
+#define WUSBTKID 0x44
+#define WUSBSECKEY 0x48
+#define WUSBPERIODICLISTBASE 0x58
+#define WUSBMASINDEX 0x60
+
+#define WUSBDNTSCTRL 0x64
+# define WUSBDNTSCTRL_ACTIVE (1 << 31)
+# define WUSBDNTSCTRL_INTERVAL(i) ((i) << 8)
+# define WUSBDNTSCTRL_SLOTS(s) ((s) << 0)
+
+#define WUSBTIME 0x68
+# define WUSBTIME_CHANNEL_TIME_MASK 0x00ffffff
+
+#define WUSBBPST 0x6c
+#define WUSBDIBUPDATED 0x70
+
+#endif /* #ifndef _WHCI_WHCI_HC_H */
diff --git a/drivers/staging/wusbcore/host/whci/wusb.c b/drivers/staging/wusbcore/host/whci/wusb.c
new file mode 100644
index 000000000000..6d0068ab35e4
--- /dev/null
+++ b/drivers/staging/wusbcore/host/whci/wusb.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless Host Controller (WHC) WUSB operations.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+
+#include "../../../uwb/include/umc.h"
+#include "../../wusbhc.h"
+
+#include "whcd.h"
+
+static int whc_update_di(struct whc *whc, int idx)
+{
+ int offset = idx / 32;
+ u32 bit = 1 << (idx % 32);
+
+ le_writel(bit, whc->base + WUSBDIBUPDATED + offset);
+
+ return whci_wait_for(&whc->umc->dev,
+ whc->base + WUSBDIBUPDATED + offset, bit, 0,
+ 100, "DI update");
+}
+
+/*
+ * WHCI starts MMCs based on there being a valid GTK so these need
+ * only start/stop the asynchronous and periodic schedules and send a
+ * channel stop command.
+ */
+
+int whc_wusbhc_start(struct wusbhc *wusbhc)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ asl_start(whc);
+ pzl_start(whc);
+
+ return 0;
+}
+
+void whc_wusbhc_stop(struct wusbhc *wusbhc, int delay)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 stop_time, now_time;
+ int ret;
+
+ pzl_stop(whc);
+ asl_stop(whc);
+
+ now_time = le_readl(whc->base + WUSBTIME) & WUSBTIME_CHANNEL_TIME_MASK;
+ stop_time = (now_time + ((delay * 8) << 7)) & 0x00ffffff;
+ ret = whc_do_gencmd(whc, WUSBGENCMDSTS_CHAN_STOP, stop_time, NULL, 0);
+ if (ret == 0)
+ msleep(delay);
+}
+
+int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+ u8 handle, struct wuie_hdr *wuie)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 params;
+
+ params = (interval << 24)
+ | (repeat_cnt << 16)
+ | (wuie->bLength << 8)
+ | handle;
+
+ return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength);
+}
+
+int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 params;
+
+ params = handle;
+
+ return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0);
+}
+
+int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ if (stream_index >= 0)
+ whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index));
+
+ return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm));
+}
+
+int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ int idx = wusb_dev->port_idx;
+ struct di_buf_entry *di = &whc->di_buf[idx];
+ int ret;
+
+ mutex_lock(&whc->mutex);
+
+ uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability);
+ di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK);
+ di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr);
+
+ ret = whc_update_di(whc, idx);
+
+ mutex_unlock(&whc->mutex);
+
+ return ret;
+}
+
+/*
+ * Set the number of Device Notification Time Slots (DNTS) and enable
+ * device notifications.
+ */
+int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 dntsctrl;
+
+ dntsctrl = WUSBDNTSCTRL_ACTIVE
+ | WUSBDNTSCTRL_INTERVAL(interval)
+ | WUSBDNTSCTRL_SLOTS(slots);
+
+ le_writel(dntsctrl, whc->base + WUSBDNTSCTRL);
+
+ return 0;
+}
+
+static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid,
+ const void *key, size_t key_size, bool is_gtk)
+{
+ uint32_t setkeycmd;
+ uint32_t seckey[4];
+ int i;
+ int ret;
+
+ memcpy(seckey, key, key_size);
+ setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index);
+ if (is_gtk)
+ setkeycmd |= WUSBSETSECKEYCMD_GTK;
+
+ le_writel(tkid, whc->base + WUSBTKID);
+ for (i = 0; i < 4; i++)
+ le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i);
+ le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD);
+
+ ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD,
+ WUSBSETSECKEYCMD_SET, 0, 100, "set key");
+
+ return ret;
+}
+
+/**
+ * whc_set_ptk - set the PTK to use for a device.
+ *
+ * The index into the key table for this PTK is the same as the
+ * device's port index.
+ */
+int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+ const void *ptk, size_t key_size)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ struct di_buf_entry *di = &whc->di_buf[port_idx];
+ int ret;
+
+ mutex_lock(&whc->mutex);
+
+ if (ptk) {
+ ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false);
+ if (ret)
+ goto out;
+
+ di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK;
+ di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx);
+ } else
+ di->addr_sec_info &= ~WHC_DI_SECURE;
+
+ ret = whc_update_di(whc, port_idx);
+out:
+ mutex_unlock(&whc->mutex);
+ return ret;
+}
+
+/**
+ * whc_set_gtk - set the GTK for subsequent broadcast packets
+ *
+ * The GTK is stored in the last entry in the key table (the previous
+ * N_DEVICES entries are for the per-device PTKs).
+ */
+int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+ const void *gtk, size_t key_size)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ int ret;
+
+ mutex_lock(&whc->mutex);
+
+ ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true);
+
+ mutex_unlock(&whc->mutex);
+
+ return ret;
+}
+
+int whc_set_cluster_id(struct whc *whc, u8 bcid)
+{
+ whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid));
+ return 0;
+}
diff --git a/drivers/staging/wusbcore/include/association.h b/drivers/staging/wusbcore/include/association.h
new file mode 100644
index 000000000000..d7f3cb9b9db5
--- /dev/null
+++ b/drivers/staging/wusbcore/include/association.h
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB - Cable Based Association
+ *
+ * Copyright (C) 2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ */
+#ifndef __LINUX_USB_ASSOCIATION_H
+#define __LINUX_USB_ASSOCIATION_H
+
+
+/*
+ * Association attributes
+ *
+ * Association Models Supplement to WUSB 1.0 T[3-1]
+ *
+ * Each field in the structures has it's ID, it's length and then the
+ * value. This is the actual definition of the field's ID and its
+ * length.
+ */
+struct wusb_am_attr {
+ __u8 id;
+ __u8 len;
+};
+
+/* Different fields defined by the spec */
+#define WUSB_AR_AssociationTypeId { .id = cpu_to_le16(0x0000), .len = cpu_to_le16(2) }
+#define WUSB_AR_AssociationSubTypeId { .id = cpu_to_le16(0x0001), .len = cpu_to_le16(2) }
+#define WUSB_AR_Length { .id = cpu_to_le16(0x0002), .len = cpu_to_le16(4) }
+#define WUSB_AR_AssociationStatus { .id = cpu_to_le16(0x0004), .len = cpu_to_le16(4) }
+#define WUSB_AR_LangID { .id = cpu_to_le16(0x0008), .len = cpu_to_le16(2) }
+#define WUSB_AR_DeviceFriendlyName { .id = cpu_to_le16(0x000b), .len = cpu_to_le16(64) } /* max */
+#define WUSB_AR_HostFriendlyName { .id = cpu_to_le16(0x000c), .len = cpu_to_le16(64) } /* max */
+#define WUSB_AR_CHID { .id = cpu_to_le16(0x1000), .len = cpu_to_le16(16) }
+#define WUSB_AR_CDID { .id = cpu_to_le16(0x1001), .len = cpu_to_le16(16) }
+#define WUSB_AR_ConnectionContext { .id = cpu_to_le16(0x1002), .len = cpu_to_le16(48) }
+#define WUSB_AR_BandGroups { .id = cpu_to_le16(0x1004), .len = cpu_to_le16(2) }
+
+/* CBAF Control Requests (AMS1.0[T4-1] */
+enum {
+ CBAF_REQ_GET_ASSOCIATION_INFORMATION = 0x01,
+ CBAF_REQ_GET_ASSOCIATION_REQUEST,
+ CBAF_REQ_SET_ASSOCIATION_RESPONSE
+};
+
+/*
+ * CBAF USB-interface defitions
+ *
+ * No altsettings, one optional interrupt endpoint.
+ */
+enum {
+ CBAF_IFACECLASS = 0xef,
+ CBAF_IFACESUBCLASS = 0x03,
+ CBAF_IFACEPROTOCOL = 0x01,
+};
+
+/* Association Information (AMS1.0[T4-3]) */
+struct wusb_cbaf_assoc_info {
+ __le16 Length;
+ __u8 NumAssociationRequests;
+ __le16 Flags;
+ __u8 AssociationRequestsArray[];
+} __attribute__((packed));
+
+/* Association Request (AMS1.0[T4-4]) */
+struct wusb_cbaf_assoc_request {
+ __u8 AssociationDataIndex;
+ __u8 Reserved;
+ __le16 AssociationTypeId;
+ __le16 AssociationSubTypeId;
+ __le32 AssociationTypeInfoSize;
+} __attribute__((packed));
+
+enum {
+ AR_TYPE_WUSB = 0x0001,
+ AR_TYPE_WUSB_RETRIEVE_HOST_INFO = 0x0000,
+ AR_TYPE_WUSB_ASSOCIATE = 0x0001,
+};
+
+/* Association Attribute header (AMS1.0[3.8]) */
+struct wusb_cbaf_attr_hdr {
+ __le16 id;
+ __le16 len;
+} __attribute__((packed));
+
+/* Host Info (AMS1.0[T4-7]) (yeah, more headers and fields...) */
+struct wusb_cbaf_host_info {
+ struct wusb_cbaf_attr_hdr AssociationTypeId_hdr;
+ __le16 AssociationTypeId;
+ struct wusb_cbaf_attr_hdr AssociationSubTypeId_hdr;
+ __le16 AssociationSubTypeId;
+ struct wusb_cbaf_attr_hdr CHID_hdr;
+ struct wusb_ckhdid CHID;
+ struct wusb_cbaf_attr_hdr LangID_hdr;
+ __le16 LangID;
+ struct wusb_cbaf_attr_hdr HostFriendlyName_hdr;
+ __u8 HostFriendlyName[];
+} __attribute__((packed));
+
+/* Device Info (AMS1.0[T4-8])
+ *
+ * I still don't get this tag'n'header stuff for each goddamn
+ * field...
+ */
+struct wusb_cbaf_device_info {
+ struct wusb_cbaf_attr_hdr Length_hdr;
+ __le32 Length;
+ struct wusb_cbaf_attr_hdr CDID_hdr;
+ struct wusb_ckhdid CDID;
+ struct wusb_cbaf_attr_hdr BandGroups_hdr;
+ __le16 BandGroups;
+ struct wusb_cbaf_attr_hdr LangID_hdr;
+ __le16 LangID;
+ struct wusb_cbaf_attr_hdr DeviceFriendlyName_hdr;
+ __u8 DeviceFriendlyName[];
+} __attribute__((packed));
+
+/* Connection Context; CC_DATA - Success case (AMS1.0[T4-9]) */
+struct wusb_cbaf_cc_data {
+ struct wusb_cbaf_attr_hdr AssociationTypeId_hdr;
+ __le16 AssociationTypeId;
+ struct wusb_cbaf_attr_hdr AssociationSubTypeId_hdr;
+ __le16 AssociationSubTypeId;
+ struct wusb_cbaf_attr_hdr Length_hdr;
+ __le32 Length;
+ struct wusb_cbaf_attr_hdr ConnectionContext_hdr;
+ struct wusb_ckhdid CHID;
+ struct wusb_ckhdid CDID;
+ struct wusb_ckhdid CK;
+ struct wusb_cbaf_attr_hdr BandGroups_hdr;
+ __le16 BandGroups;
+} __attribute__((packed));
+
+/* CC_DATA - Failure case (AMS1.0[T4-10]) */
+struct wusb_cbaf_cc_data_fail {
+ struct wusb_cbaf_attr_hdr AssociationTypeId_hdr;
+ __le16 AssociationTypeId;
+ struct wusb_cbaf_attr_hdr AssociationSubTypeId_hdr;
+ __le16 AssociationSubTypeId;
+ struct wusb_cbaf_attr_hdr Length_hdr;
+ __le16 Length;
+ struct wusb_cbaf_attr_hdr AssociationStatus_hdr;
+ __u32 AssociationStatus;
+} __attribute__((packed));
+
+#endif /* __LINUX_USB_ASSOCIATION_H */
diff --git a/drivers/staging/wusbcore/include/wusb-wa.h b/drivers/staging/wusbcore/include/wusb-wa.h
new file mode 100644
index 000000000000..64a840b5106e
--- /dev/null
+++ b/drivers/staging/wusbcore/include/wusb-wa.h
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB Wire Adapter constants and structures.
+ *
+ * Copyright (C) 2005-2006 Intel Corporation.
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * FIXME: docs
+ * FIXME: organize properly, group logically
+ *
+ * All the event structures are defined in uwb/spec.h, as they are
+ * common to the WHCI and WUSB radio control interfaces.
+ *
+ * References:
+ * [WUSB] Wireless Universal Serial Bus Specification, revision 1.0, ch8
+ */
+#ifndef __LINUX_USB_WUSB_WA_H
+#define __LINUX_USB_WUSB_WA_H
+
+/**
+ * Radio Command Request for the Radio Control Interface
+ *
+ * Radio Control Interface command and event codes are the same as
+ * WHCI, and listed in include/linux/uwb.h:UWB_RC_{CMD,EVT}_*
+ */
+enum {
+ WA_EXEC_RC_CMD = 40, /* Radio Control command Request */
+};
+
+/* Wireless Adapter Requests ([WUSB] table 8-51) */
+enum {
+ WUSB_REQ_ADD_MMC_IE = 20,
+ WUSB_REQ_REMOVE_MMC_IE = 21,
+ WUSB_REQ_SET_NUM_DNTS = 22,
+ WUSB_REQ_SET_CLUSTER_ID = 23,
+ WUSB_REQ_SET_DEV_INFO = 24,
+ WUSB_REQ_GET_TIME = 25,
+ WUSB_REQ_SET_STREAM_IDX = 26,
+ WUSB_REQ_SET_WUSB_MAS = 27,
+ WUSB_REQ_CHAN_STOP = 28,
+};
+
+
+/* Wireless Adapter WUSB Channel Time types ([WUSB] table 8-52) */
+enum {
+ WUSB_TIME_ADJ = 0,
+ WUSB_TIME_BPST = 1,
+ WUSB_TIME_WUSB = 2,
+};
+
+enum {
+ WA_ENABLE = 0x01,
+ WA_RESET = 0x02,
+ RPIPE_PAUSE = 0x1,
+ RPIPE_STALL = 0x2,
+};
+
+/* Responses from Get Status request ([WUSB] section 8.3.1.6) */
+enum {
+ WA_STATUS_ENABLED = 0x01,
+ WA_STATUS_RESETTING = 0x02
+};
+
+enum rpipe_crs {
+ RPIPE_CRS_CTL = 0x01,
+ RPIPE_CRS_ISO = 0x02,
+ RPIPE_CRS_BULK = 0x04,
+ RPIPE_CRS_INTR = 0x08
+};
+
+/**
+ * RPipe descriptor ([WUSB] section 8.5.2.11)
+ *
+ * FIXME: explain rpipes
+ */
+struct usb_rpipe_descriptor {
+ u8 bLength;
+ u8 bDescriptorType;
+ __le16 wRPipeIndex;
+ __le16 wRequests;
+ __le16 wBlocks; /* rw if 0 */
+ __le16 wMaxPacketSize; /* rw */
+ union {
+ u8 dwa_bHSHubAddress; /* rw: DWA. */
+ u8 hwa_bMaxBurst; /* rw: HWA. */
+ };
+ union {
+ u8 dwa_bHSHubPort; /* rw: DWA. */
+ u8 hwa_bDeviceInfoIndex; /* rw: HWA. */
+ };
+ u8 bSpeed; /* rw: xfer rate 'enum uwb_phy_rate' */
+ union {
+ u8 dwa_bDeviceAddress; /* rw: DWA Target device address. */
+ u8 hwa_reserved; /* rw: HWA. */
+ };
+ u8 bEndpointAddress; /* rw: Target EP address */
+ u8 bDataSequence; /* ro: Current Data sequence */
+ __le32 dwCurrentWindow; /* ro */
+ u8 bMaxDataSequence; /* ro?: max supported seq */
+ u8 bInterval; /* rw: */
+ u8 bOverTheAirInterval; /* rw: */
+ u8 bmAttribute; /* ro? */
+ u8 bmCharacteristics; /* ro? enum rpipe_attr, supported xsactions */
+ u8 bmRetryOptions; /* rw? */
+ __le16 wNumTransactionErrors; /* rw */
+} __attribute__ ((packed));
+
+/**
+ * Wire Adapter Notification types ([WUSB] sections 8.4.5 & 8.5.4)
+ *
+ * These are the notifications coming on the notification endpoint of
+ * an HWA and a DWA.
+ */
+enum wa_notif_type {
+ DWA_NOTIF_RWAKE = 0x91,
+ DWA_NOTIF_PORTSTATUS = 0x92,
+ WA_NOTIF_TRANSFER = 0x93,
+ HWA_NOTIF_BPST_ADJ = 0x94,
+ HWA_NOTIF_DN = 0x95,
+};
+
+/**
+ * Wire Adapter notification header
+ *
+ * Notifications coming from a wire adapter use a common header
+ * defined in [WUSB] sections 8.4.5 & 8.5.4.
+ */
+struct wa_notif_hdr {
+ u8 bLength;
+ u8 bNotifyType; /* enum wa_notif_type */
+} __packed;
+
+/**
+ * HWA DN Received notification [(WUSB] section 8.5.4.2)
+ *
+ * The DNData is specified in WUSB1.0[7.6]. For each device
+ * notification we received, we just need to dispatch it.
+ *
+ * @dndata: this is really an array of notifications, but all start
+ * with the same header.
+ */
+struct hwa_notif_dn {
+ struct wa_notif_hdr hdr;
+ u8 bSourceDeviceAddr; /* from errata 2005/07 */
+ u8 bmAttributes;
+ struct wusb_dn_hdr dndata[];
+} __packed;
+
+/* [WUSB] section 8.3.3 */
+enum wa_xfer_type {
+ WA_XFER_TYPE_CTL = 0x80,
+ WA_XFER_TYPE_BI = 0x81, /* bulk/interrupt */
+ WA_XFER_TYPE_ISO = 0x82,
+ WA_XFER_RESULT = 0x83,
+ WA_XFER_ABORT = 0x84,
+ WA_XFER_ISO_PACKET_INFO = 0xA0,
+ WA_XFER_ISO_PACKET_STATUS = 0xA1,
+};
+
+/* [WUSB] section 8.3.3 */
+struct wa_xfer_hdr {
+ u8 bLength; /* 0x18 */
+ u8 bRequestType; /* 0x80 WA_REQUEST_TYPE_CTL */
+ __le16 wRPipe; /* RPipe index */
+ __le32 dwTransferID; /* Host-assigned ID */
+ __le32 dwTransferLength; /* Length of data to xfer */
+ u8 bTransferSegment;
+} __packed;
+
+struct wa_xfer_ctl {
+ struct wa_xfer_hdr hdr;
+ u8 bmAttribute;
+ __le16 wReserved;
+ struct usb_ctrlrequest baSetupData;
+} __packed;
+
+struct wa_xfer_bi {
+ struct wa_xfer_hdr hdr;
+ u8 bReserved;
+ __le16 wReserved;
+} __packed;
+
+/* [WUSB] section 8.5.5 */
+struct wa_xfer_hwaiso {
+ struct wa_xfer_hdr hdr;
+ u8 bReserved;
+ __le16 wPresentationTime;
+ __le32 dwNumOfPackets;
+} __packed;
+
+struct wa_xfer_packet_info_hwaiso {
+ __le16 wLength;
+ u8 bPacketType;
+ u8 bReserved;
+ __le16 PacketLength[0];
+} __packed;
+
+struct wa_xfer_packet_status_len_hwaiso {
+ __le16 PacketLength;
+ __le16 PacketStatus;
+} __packed;
+
+struct wa_xfer_packet_status_hwaiso {
+ __le16 wLength;
+ u8 bPacketType;
+ u8 bReserved;
+ struct wa_xfer_packet_status_len_hwaiso PacketStatus[0];
+} __packed;
+
+/* [WUSB] section 8.3.3.5 */
+struct wa_xfer_abort {
+ u8 bLength;
+ u8 bRequestType;
+ __le16 wRPipe; /* RPipe index */
+ __le32 dwTransferID; /* Host-assigned ID */
+} __packed;
+
+/**
+ * WA Transfer Complete notification ([WUSB] section 8.3.3.3)
+ *
+ */
+struct wa_notif_xfer {
+ struct wa_notif_hdr hdr;
+ u8 bEndpoint;
+ u8 Reserved;
+} __packed;
+
+/** Transfer result basic codes [WUSB] table 8-15 */
+enum {
+ WA_XFER_STATUS_SUCCESS,
+ WA_XFER_STATUS_HALTED,
+ WA_XFER_STATUS_DATA_BUFFER_ERROR,
+ WA_XFER_STATUS_BABBLE,
+ WA_XFER_RESERVED,
+ WA_XFER_STATUS_NOT_FOUND,
+ WA_XFER_STATUS_INSUFFICIENT_RESOURCE,
+ WA_XFER_STATUS_TRANSACTION_ERROR,
+ WA_XFER_STATUS_ABORTED,
+ WA_XFER_STATUS_RPIPE_NOT_READY,
+ WA_XFER_INVALID_FORMAT,
+ WA_XFER_UNEXPECTED_SEGMENT_NUMBER,
+ WA_XFER_STATUS_RPIPE_TYPE_MISMATCH,
+};
+
+/** [WUSB] section 8.3.3.4 */
+struct wa_xfer_result {
+ struct wa_notif_hdr hdr;
+ __le32 dwTransferID;
+ __le32 dwTransferLength;
+ u8 bTransferSegment;
+ u8 bTransferStatus;
+ __le32 dwNumOfPackets;
+} __packed;
+
+/**
+ * Wire Adapter Class Descriptor ([WUSB] section 8.5.2.7).
+ *
+ * NOTE: u16 fields are read Little Endian from the hardware.
+ *
+ * @bNumPorts is the original max number of devices that the host can
+ * connect; we might chop this so the stack can handle
+ * it. In case you need to access it, use wusbhc->ports_max
+ * if it is a Wireless USB WA.
+ */
+struct usb_wa_descriptor {
+ u8 bLength;
+ u8 bDescriptorType;
+ __le16 bcdWAVersion;
+ u8 bNumPorts; /* don't use!! */
+ u8 bmAttributes; /* Reserved == 0 */
+ __le16 wNumRPipes;
+ __le16 wRPipeMaxBlock;
+ u8 bRPipeBlockSize;
+ u8 bPwrOn2PwrGood;
+ u8 bNumMMCIEs;
+ u8 DeviceRemovable; /* FIXME: in DWA this is up to 16 bytes */
+} __packed;
+
+/**
+ * HWA Device Information Buffer (WUSB1.0[T8.54])
+ */
+struct hwa_dev_info {
+ u8 bmDeviceAvailability[32]; /* FIXME: ignored for now */
+ u8 bDeviceAddress;
+ __le16 wPHYRates;
+ u8 bmDeviceAttribute;
+} __packed;
+
+#endif /* #ifndef __LINUX_USB_WUSB_WA_H */
diff --git a/drivers/staging/wusbcore/include/wusb.h b/drivers/staging/wusbcore/include/wusb.h
new file mode 100644
index 000000000000..09771d1da7bc
--- /dev/null
+++ b/drivers/staging/wusbcore/include/wusb.h
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB Standard Definitions
+ * Event Size Tables
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * FIXME: docs
+ * FIXME: organize properly, group logically
+ *
+ * All the event structures are defined in uwb/spec.h, as they are
+ * common to the WHCI and WUSB radio control interfaces.
+ */
+
+#ifndef __WUSB_H__
+#define __WUSB_H__
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/usb/ch9.h>
+#include <linux/param.h>
+#include "../../uwb/include/spec.h"
+
+/**
+ * WUSB Information Element header
+ *
+ * I don't know why, they decided to make it different to the MBOA MAC
+ * IE Header; beats me.
+ */
+struct wuie_hdr {
+ u8 bLength;
+ u8 bIEIdentifier;
+} __attribute__((packed));
+
+enum {
+ WUIE_ID_WCTA = 0x80,
+ WUIE_ID_CONNECTACK,
+ WUIE_ID_HOST_INFO,
+ WUIE_ID_CHANGE_ANNOUNCE,
+ WUIE_ID_DEVICE_DISCONNECT,
+ WUIE_ID_HOST_DISCONNECT,
+ WUIE_ID_KEEP_ALIVE = 0x89,
+ WUIE_ID_ISOCH_DISCARD,
+ WUIE_ID_RESET_DEVICE,
+};
+
+/**
+ * Maximum number of array elements in a WUSB IE.
+ *
+ * WUSB1.0[7.5 before table 7-38] says that in WUSB IEs that
+ * are "arrays" have to limited to 4 elements. So we define it
+ * like that to ease up and submit only the neeed size.
+ */
+#define WUIE_ELT_MAX 4
+
+/**
+ * Wrapper for the data that defines a CHID, a CDID or a CK
+ *
+ * WUSB defines that CHIDs, CDIDs and CKs are a 16 byte string of
+ * data. In order to avoid confusion and enforce types, we wrap it.
+ *
+ * Make it packed, as we use it in some hw definitions.
+ */
+struct wusb_ckhdid {
+ u8 data[16];
+} __attribute__((packed));
+
+static const struct wusb_ckhdid wusb_ckhdid_zero = { .data = { 0 } };
+
+#define WUSB_CKHDID_STRSIZE (3 * sizeof(struct wusb_ckhdid) + 1)
+
+/**
+ * WUSB IE: Host Information (WUSB1.0[7.5.2])
+ *
+ * Used to provide information about the host to the Wireless USB
+ * devices in range (CHID can be used as an ASCII string).
+ */
+struct wuie_host_info {
+ struct wuie_hdr hdr;
+ __le16 attributes;
+ struct wusb_ckhdid CHID;
+} __attribute__((packed));
+
+/**
+ * WUSB IE: Connect Ack (WUSB1.0[7.5.1])
+ *
+ * Used to acknowledge device connect requests. See note for
+ * WUIE_ELT_MAX.
+ */
+struct wuie_connect_ack {
+ struct wuie_hdr hdr;
+ struct {
+ struct wusb_ckhdid CDID;
+ u8 bDeviceAddress; /* 0 means unused */
+ u8 bReserved;
+ } blk[WUIE_ELT_MAX];
+} __attribute__((packed));
+
+/**
+ * WUSB IE Host Information Element, Connect Availability
+ *
+ * WUSB1.0[7.5.2], bmAttributes description
+ */
+enum {
+ WUIE_HI_CAP_RECONNECT = 0,
+ WUIE_HI_CAP_LIMITED,
+ WUIE_HI_CAP_RESERVED,
+ WUIE_HI_CAP_ALL,
+};
+
+/**
+ * WUSB IE: Channel Stop (WUSB1.0[7.5.8])
+ *
+ * Tells devices the host is going to stop sending MMCs and will disappear.
+ */
+struct wuie_channel_stop {
+ struct wuie_hdr hdr;
+ u8 attributes;
+ u8 timestamp[3];
+} __attribute__((packed));
+
+/**
+ * WUSB IE: Keepalive (WUSB1.0[7.5.9])
+ *
+ * Ask device(s) to send keepalives.
+ */
+struct wuie_keep_alive {
+ struct wuie_hdr hdr;
+ u8 bDeviceAddress[WUIE_ELT_MAX];
+} __attribute__((packed));
+
+/**
+ * WUSB IE: Reset device (WUSB1.0[7.5.11])
+ *
+ * Tell device to reset; in all truth, we can fit 4 CDIDs, but we only
+ * use it for one at the time...
+ *
+ * In any case, this request is a wee bit silly: why don't they target
+ * by address??
+ */
+struct wuie_reset {
+ struct wuie_hdr hdr;
+ struct wusb_ckhdid CDID;
+} __attribute__((packed));
+
+/**
+ * WUSB IE: Disconnect device (WUSB1.0[7.5.11])
+ *
+ * Tell device to disconnect; we can fit 4 addresses, but we only use
+ * it for one at the time...
+ */
+struct wuie_disconnect {
+ struct wuie_hdr hdr;
+ u8 bDeviceAddress;
+ u8 padding;
+} __attribute__((packed));
+
+/**
+ * WUSB IE: Host disconnect ([WUSB] section 7.5.5)
+ *
+ * Tells all connected devices to disconnect.
+ */
+struct wuie_host_disconnect {
+ struct wuie_hdr hdr;
+} __attribute__((packed));
+
+/**
+ * WUSB Device Notification header (WUSB1.0[7.6])
+ */
+struct wusb_dn_hdr {
+ u8 bType;
+ u8 notifdata[];
+} __attribute__((packed));
+
+/** Device Notification codes (WUSB1.0[Table 7-54]) */
+enum WUSB_DN {
+ WUSB_DN_CONNECT = 0x01,
+ WUSB_DN_DISCONNECT = 0x02,
+ WUSB_DN_EPRDY = 0x03,
+ WUSB_DN_MASAVAILCHANGED = 0x04,
+ WUSB_DN_RWAKE = 0x05,
+ WUSB_DN_SLEEP = 0x06,
+ WUSB_DN_ALIVE = 0x07,
+};
+
+/** WUSB Device Notification Connect */
+struct wusb_dn_connect {
+ struct wusb_dn_hdr hdr;
+ __le16 attributes;
+ struct wusb_ckhdid CDID;
+} __attribute__((packed));
+
+static inline int wusb_dn_connect_prev_dev_addr(const struct wusb_dn_connect *dn)
+{
+ return le16_to_cpu(dn->attributes) & 0xff;
+}
+
+static inline int wusb_dn_connect_new_connection(const struct wusb_dn_connect *dn)
+{
+ return (le16_to_cpu(dn->attributes) >> 8) & 0x1;
+}
+
+static inline int wusb_dn_connect_beacon_behavior(const struct wusb_dn_connect *dn)
+{
+ return (le16_to_cpu(dn->attributes) >> 9) & 0x03;
+}
+
+/** Device is alive (aka: pong) (WUSB1.0[7.6.7]) */
+struct wusb_dn_alive {
+ struct wusb_dn_hdr hdr;
+} __attribute__((packed));
+
+/** Device is disconnecting (WUSB1.0[7.6.2]) */
+struct wusb_dn_disconnect {
+ struct wusb_dn_hdr hdr;
+} __attribute__((packed));
+
+/* General constants */
+enum {
+ WUSB_TRUST_TIMEOUT_MS = 4000, /* [WUSB] section 4.15.1 */
+};
+
+/*
+ * WUSB Crypto stuff (WUSB1.0[6])
+ */
+
+extern const char *wusb_et_name(u8);
+
+/**
+ * WUSB key index WUSB1.0[7.3.2.4], for usage when setting keys for
+ * the host or the device.
+ */
+static inline u8 wusb_key_index(int index, int type, int originator)
+{
+ return (originator << 6) | (type << 4) | index;
+}
+
+#define WUSB_KEY_INDEX_TYPE_PTK 0 /* for HWA only */
+#define WUSB_KEY_INDEX_TYPE_ASSOC 1
+#define WUSB_KEY_INDEX_TYPE_GTK 2
+#define WUSB_KEY_INDEX_ORIGINATOR_HOST 0
+#define WUSB_KEY_INDEX_ORIGINATOR_DEVICE 1
+/* bits 0-3 used for the key index. */
+#define WUSB_KEY_INDEX_MAX 15
+
+/* A CCM Nonce, defined in WUSB1.0[6.4.1] */
+struct aes_ccm_nonce {
+ u8 sfn[6]; /* Little Endian */
+ u8 tkid[3]; /* LE */
+ struct uwb_dev_addr dest_addr;
+ struct uwb_dev_addr src_addr;
+} __attribute__((packed));
+
+/* A CCM operation label, defined on WUSB1.0[6.5.x] */
+struct aes_ccm_label {
+ u8 data[14];
+} __attribute__((packed));
+
+/*
+ * Input to the key derivation sequence defined in
+ * WUSB1.0[6.5.1]. Rest of the data is in the CCM Nonce passed to the
+ * PRF function.
+ */
+struct wusb_keydvt_in {
+ u8 hnonce[16];
+ u8 dnonce[16];
+} __attribute__((packed));
+
+/*
+ * Output from the key derivation sequence defined in
+ * WUSB1.0[6.5.1].
+ */
+struct wusb_keydvt_out {
+ u8 kck[16];
+ u8 ptk[16];
+} __attribute__((packed));
+
+/* Pseudo Random Function WUSB1.0[6.5] */
+extern int wusb_crypto_init(void);
+extern void wusb_crypto_exit(void);
+extern ssize_t wusb_prf(void *out, size_t out_size,
+ const u8 key[16], const struct aes_ccm_nonce *_n,
+ const struct aes_ccm_label *a,
+ const void *b, size_t blen, size_t len);
+
+static inline int wusb_prf_64(void *out, size_t out_size, const u8 key[16],
+ const struct aes_ccm_nonce *n,
+ const struct aes_ccm_label *a,
+ const void *b, size_t blen)
+{
+ return wusb_prf(out, out_size, key, n, a, b, blen, 64);
+}
+
+static inline int wusb_prf_128(void *out, size_t out_size, const u8 key[16],
+ const struct aes_ccm_nonce *n,
+ const struct aes_ccm_label *a,
+ const void *b, size_t blen)
+{
+ return wusb_prf(out, out_size, key, n, a, b, blen, 128);
+}
+
+static inline int wusb_prf_256(void *out, size_t out_size, const u8 key[16],
+ const struct aes_ccm_nonce *n,
+ const struct aes_ccm_label *a,
+ const void *b, size_t blen)
+{
+ return wusb_prf(out, out_size, key, n, a, b, blen, 256);
+}
+
+/* Key derivation WUSB1.0[6.5.1] */
+static inline int wusb_key_derive(struct wusb_keydvt_out *keydvt_out,
+ const u8 key[16],
+ const struct aes_ccm_nonce *n,
+ const struct wusb_keydvt_in *keydvt_in)
+{
+ const struct aes_ccm_label a = { .data = "Pair-wise keys" };
+ return wusb_prf_256(keydvt_out, sizeof(*keydvt_out), key, n, &a,
+ keydvt_in, sizeof(*keydvt_in));
+}
+
+/*
+ * Out-of-band MIC Generation WUSB1.0[6.5.2]
+ *
+ * Compute the MIC over @key, @n and @hs and place it in @mic_out.
+ *
+ * @mic_out: Where to place the 8 byte MIC tag
+ * @key: KCK from the derivation process
+ * @n: CCM nonce, n->sfn == 0, TKID as established in the
+ * process.
+ * @hs: Handshake struct for phase 2 of the 4-way.
+ * hs->bStatus and hs->bReserved are zero.
+ * hs->bMessageNumber is 2 (WUSB1.0[7.3.2.5.2]
+ * hs->dest_addr is the device's USB address padded with 0
+ * hs->src_addr is the hosts's UWB device address
+ * hs->mic is ignored (as we compute that value).
+ */
+static inline int wusb_oob_mic(u8 mic_out[8], const u8 key[16],
+ const struct aes_ccm_nonce *n,
+ const struct usb_handshake *hs)
+{
+ const struct aes_ccm_label a = { .data = "out-of-bandMIC" };
+ return wusb_prf_64(mic_out, 8, key, n, &a,
+ hs, sizeof(*hs) - sizeof(hs->MIC));
+}
+
+#endif /* #ifndef __WUSB_H__ */
diff --git a/drivers/staging/wusbcore/mmc.c b/drivers/staging/wusbcore/mmc.c
new file mode 100644
index 000000000000..881e1f20d718
--- /dev/null
+++ b/drivers/staging/wusbcore/mmc.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8])
+ * MMC (Microscheduled Management Command) handling
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * WUIEs and MMC IEs...well, they are almost the same at the end. MMC
+ * IEs are Wireless USB IEs that go into the MMC period...[what is
+ * that? look in Design-overview.txt].
+ *
+ *
+ * This is a simple subsystem to keep track of which IEs are being
+ * sent by the host in the MMC period.
+ *
+ * For each WUIE we ask to send, we keep it in an array, so we can
+ * request its removal later, or replace the content. They are tracked
+ * by pointer, so be sure to use the same pointer if you want to
+ * remove it or update the contents.
+ *
+ * FIXME:
+ * - add timers that autoremove intervalled IEs?
+ */
+#include <linux/slab.h>
+#include <linux/export.h>
+#include "include/wusb.h"
+#include "wusbhc.h"
+
+/* Initialize the MMCIEs handling mechanism */
+int wusbhc_mmcie_create(struct wusbhc *wusbhc)
+{
+ u8 mmcies = wusbhc->mmcies_max;
+ wusbhc->mmcie = kcalloc(mmcies, sizeof(wusbhc->mmcie[0]), GFP_KERNEL);
+ if (wusbhc->mmcie == NULL)
+ return -ENOMEM;
+ mutex_init(&wusbhc->mmcie_mutex);
+ return 0;
+}
+
+/* Release resources used by the MMCIEs handling mechanism */
+void wusbhc_mmcie_destroy(struct wusbhc *wusbhc)
+{
+ kfree(wusbhc->mmcie);
+}
+
+/*
+ * Add or replace an MMC Wireless USB IE.
+ *
+ * @interval: See WUSB1.0[8.5.3.1]
+ * @repeat_cnt: See WUSB1.0[8.5.3.1]
+ * @handle: See WUSB1.0[8.5.3.1]
+ * @wuie: Pointer to the header of the WUSB IE data to add.
+ * MUST BE allocated in a kmalloc buffer (no stack or
+ * vmalloc).
+ * THE CALLER ALWAYS OWNS THE POINTER (we don't free it
+ * on remove, we just forget about it).
+ * @returns: 0 if ok, < 0 errno code on error.
+ *
+ * Goes over the *whole* @wusbhc->mmcie array looking for (a) the
+ * first free spot and (b) if @wuie is already in the array (aka:
+ * transmitted in the MMCs) the spot were it is.
+ *
+ * If present, we "overwrite it" (update).
+ *
+ *
+ * NOTE: Need special ordering rules -- see below WUSB1.0 Table 7-38.
+ * The host uses the handle as the 'sort' index. We
+ * allocate the last one always for the WUIE_ID_HOST_INFO, and
+ * the rest, first come first serve in inverse order.
+ *
+ * Host software must make sure that it adds the other IEs in
+ * the right order... the host hardware is responsible for
+ * placing the WCTA IEs in the right place with the other IEs
+ * set by host software.
+ *
+ * NOTE: we can access wusbhc->wa_descr without locking because it is
+ * read only.
+ */
+int wusbhc_mmcie_set(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+ struct wuie_hdr *wuie)
+{
+ int result = -ENOBUFS;
+ unsigned handle, itr;
+
+ /* Search a handle, taking into account the ordering */
+ mutex_lock(&wusbhc->mmcie_mutex);
+ switch (wuie->bIEIdentifier) {
+ case WUIE_ID_HOST_INFO:
+ /* Always last */
+ handle = wusbhc->mmcies_max - 1;
+ break;
+ case WUIE_ID_ISOCH_DISCARD:
+ dev_err(wusbhc->dev, "Special ordering case for WUIE ID 0x%x "
+ "unimplemented\n", wuie->bIEIdentifier);
+ result = -ENOSYS;
+ goto error_unlock;
+ default:
+ /* search for it or find the last empty slot */
+ handle = ~0;
+ for (itr = 0; itr < wusbhc->mmcies_max - 1; itr++) {
+ if (wusbhc->mmcie[itr] == wuie) {
+ handle = itr;
+ break;
+ }
+ if (wusbhc->mmcie[itr] == NULL)
+ handle = itr;
+ }
+ if (handle == ~0)
+ goto error_unlock;
+ }
+ result = (wusbhc->mmcie_add)(wusbhc, interval, repeat_cnt, handle,
+ wuie);
+ if (result >= 0)
+ wusbhc->mmcie[handle] = wuie;
+error_unlock:
+ mutex_unlock(&wusbhc->mmcie_mutex);
+ return result;
+}
+EXPORT_SYMBOL_GPL(wusbhc_mmcie_set);
+
+/*
+ * Remove an MMC IE previously added with wusbhc_mmcie_set()
+ *
+ * @wuie Pointer used to add the WUIE
+ */
+void wusbhc_mmcie_rm(struct wusbhc *wusbhc, struct wuie_hdr *wuie)
+{
+ int result;
+ unsigned handle, itr;
+
+ mutex_lock(&wusbhc->mmcie_mutex);
+ for (itr = 0; itr < wusbhc->mmcies_max; itr++) {
+ if (wusbhc->mmcie[itr] == wuie) {
+ handle = itr;
+ goto found;
+ }
+ }
+ mutex_unlock(&wusbhc->mmcie_mutex);
+ return;
+
+found:
+ result = (wusbhc->mmcie_rm)(wusbhc, handle);
+ if (result == 0)
+ wusbhc->mmcie[itr] = NULL;
+ mutex_unlock(&wusbhc->mmcie_mutex);
+}
+EXPORT_SYMBOL_GPL(wusbhc_mmcie_rm);
+
+static int wusbhc_mmc_start(struct wusbhc *wusbhc)
+{
+ int ret;
+
+ mutex_lock(&wusbhc->mutex);
+ ret = wusbhc->start(wusbhc);
+ if (ret >= 0)
+ wusbhc->active = 1;
+ mutex_unlock(&wusbhc->mutex);
+
+ return ret;
+}
+
+static void wusbhc_mmc_stop(struct wusbhc *wusbhc)
+{
+ mutex_lock(&wusbhc->mutex);
+ wusbhc->active = 0;
+ wusbhc->stop(wusbhc, WUSB_CHANNEL_STOP_DELAY_MS);
+ mutex_unlock(&wusbhc->mutex);
+}
+
+/*
+ * wusbhc_start - start transmitting MMCs and accepting connections
+ * @wusbhc: the HC to start
+ *
+ * Establishes a cluster reservation, enables device connections, and
+ * starts MMCs with appropriate DNTS parameters.
+ */
+int wusbhc_start(struct wusbhc *wusbhc)
+{
+ int result;
+ struct device *dev = wusbhc->dev;
+
+ WARN_ON(wusbhc->wuie_host_info != NULL);
+ BUG_ON(wusbhc->uwb_rc == NULL);
+
+ result = wusbhc_rsv_establish(wusbhc);
+ if (result < 0) {
+ dev_err(dev, "cannot establish cluster reservation: %d\n",
+ result);
+ goto error_rsv_establish;
+ }
+
+ result = wusbhc_devconnect_start(wusbhc);
+ if (result < 0) {
+ dev_err(dev, "error enabling device connections: %d\n",
+ result);
+ goto error_devconnect_start;
+ }
+
+ result = wusbhc_sec_start(wusbhc);
+ if (result < 0) {
+ dev_err(dev, "error starting security in the HC: %d\n",
+ result);
+ goto error_sec_start;
+ }
+
+ result = wusbhc->set_num_dnts(wusbhc, wusbhc->dnts_interval,
+ wusbhc->dnts_num_slots);
+ if (result < 0) {
+ dev_err(dev, "Cannot set DNTS parameters: %d\n", result);
+ goto error_set_num_dnts;
+ }
+ result = wusbhc_mmc_start(wusbhc);
+ if (result < 0) {
+ dev_err(dev, "error starting wusbch: %d\n", result);
+ goto error_wusbhc_start;
+ }
+
+ return 0;
+
+error_wusbhc_start:
+ wusbhc_sec_stop(wusbhc);
+error_set_num_dnts:
+error_sec_start:
+ wusbhc_devconnect_stop(wusbhc);
+error_devconnect_start:
+ wusbhc_rsv_terminate(wusbhc);
+error_rsv_establish:
+ return result;
+}
+
+/*
+ * wusbhc_stop - stop transmitting MMCs
+ * @wusbhc: the HC to stop
+ *
+ * Stops the WUSB channel and removes the cluster reservation.
+ */
+void wusbhc_stop(struct wusbhc *wusbhc)
+{
+ wusbhc_mmc_stop(wusbhc);
+ wusbhc_sec_stop(wusbhc);
+ wusbhc_devconnect_stop(wusbhc);
+ wusbhc_rsv_terminate(wusbhc);
+}
+
+/*
+ * Set/reset/update a new CHID
+ *
+ * Depending on the previous state of the MMCs, start, stop or change
+ * the sent MMC. This effectively switches the host controller on and
+ * off (radio wise).
+ */
+int wusbhc_chid_set(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid)
+{
+ int result = 0;
+
+ if (memcmp(chid, &wusb_ckhdid_zero, sizeof(*chid)) == 0)
+ chid = NULL;
+
+ mutex_lock(&wusbhc->mutex);
+ if (chid) {
+ if (wusbhc->active) {
+ mutex_unlock(&wusbhc->mutex);
+ return -EBUSY;
+ }
+ wusbhc->chid = *chid;
+ }
+
+ /* register with UWB if we haven't already since we are about to start
+ the radio. */
+ if ((chid) && (wusbhc->uwb_rc == NULL)) {
+ wusbhc->uwb_rc = uwb_rc_get_by_grandpa(wusbhc->dev->parent);
+ if (wusbhc->uwb_rc == NULL) {
+ result = -ENODEV;
+ dev_err(wusbhc->dev,
+ "Cannot get associated UWB Host Controller\n");
+ goto error_rc_get;
+ }
+
+ result = wusbhc_pal_register(wusbhc);
+ if (result < 0) {
+ dev_err(wusbhc->dev, "Cannot register as a UWB PAL\n");
+ goto error_pal_register;
+ }
+ }
+ mutex_unlock(&wusbhc->mutex);
+
+ if (chid)
+ result = uwb_radio_start(&wusbhc->pal);
+ else if (wusbhc->uwb_rc)
+ uwb_radio_stop(&wusbhc->pal);
+
+ return result;
+
+error_pal_register:
+ uwb_rc_put(wusbhc->uwb_rc);
+ wusbhc->uwb_rc = NULL;
+error_rc_get:
+ mutex_unlock(&wusbhc->mutex);
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(wusbhc_chid_set);
diff --git a/drivers/staging/wusbcore/pal.c b/drivers/staging/wusbcore/pal.c
new file mode 100644
index 000000000000..30f569131471
--- /dev/null
+++ b/drivers/staging/wusbcore/pal.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB Host Controller
+ * UWB Protocol Adaptation Layer (PAL) glue.
+ *
+ * Copyright (C) 2008 Cambridge Silicon Radio Ltd.
+ */
+#include "wusbhc.h"
+
+static void wusbhc_channel_changed(struct uwb_pal *pal, int channel)
+{
+ struct wusbhc *wusbhc = container_of(pal, struct wusbhc, pal);
+
+ dev_dbg(wusbhc->dev, "%s: channel = %d\n", __func__, channel);
+ if (channel < 0)
+ wusbhc_stop(wusbhc);
+ else
+ wusbhc_start(wusbhc);
+}
+
+/**
+ * wusbhc_pal_register - register the WUSB HC as a UWB PAL
+ * @wusbhc: the WUSB HC
+ */
+int wusbhc_pal_register(struct wusbhc *wusbhc)
+{
+ uwb_pal_init(&wusbhc->pal);
+
+ wusbhc->pal.name = "wusbhc";
+ wusbhc->pal.device = wusbhc->usb_hcd.self.controller;
+ wusbhc->pal.rc = wusbhc->uwb_rc;
+ wusbhc->pal.channel_changed = wusbhc_channel_changed;
+
+ return uwb_pal_register(&wusbhc->pal);
+}
+
+/**
+ * wusbhc_pal_unregister - unregister the WUSB HC as a UWB PAL
+ * @wusbhc: the WUSB HC
+ */
+void wusbhc_pal_unregister(struct wusbhc *wusbhc)
+{
+ if (wusbhc->uwb_rc)
+ uwb_pal_unregister(&wusbhc->pal);
+}
diff --git a/drivers/staging/wusbcore/reservation.c b/drivers/staging/wusbcore/reservation.c
new file mode 100644
index 000000000000..b921faac698b
--- /dev/null
+++ b/drivers/staging/wusbcore/reservation.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WUSB cluster reservation management
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ */
+#include <linux/kernel.h>
+
+#include "../uwb/uwb.h"
+#include "wusbhc.h"
+
+/*
+ * WUSB cluster reservations are multicast reservations with the
+ * broadcast cluster ID (BCID) as the target DevAddr.
+ *
+ * FIXME: consider adjusting the reservation depending on what devices
+ * are attached.
+ */
+
+static int wusbhc_bwa_set(struct wusbhc *wusbhc, u8 stream,
+ const struct uwb_mas_bm *mas)
+{
+ if (mas == NULL)
+ mas = &uwb_mas_bm_zero;
+ return wusbhc->bwa_set(wusbhc, stream, mas);
+}
+
+/**
+ * wusbhc_rsv_complete_cb - WUSB HC reservation complete callback
+ * @rsv: the reservation
+ *
+ * Either set or clear the HC's view of the reservation.
+ *
+ * FIXME: when a reservation is denied the HC should be stopped.
+ */
+static void wusbhc_rsv_complete_cb(struct uwb_rsv *rsv)
+{
+ struct wusbhc *wusbhc = rsv->pal_priv;
+ struct device *dev = wusbhc->dev;
+ struct uwb_mas_bm mas;
+
+ dev_dbg(dev, "%s: state = %d\n", __func__, rsv->state);
+ switch (rsv->state) {
+ case UWB_RSV_STATE_O_ESTABLISHED:
+ uwb_rsv_get_usable_mas(rsv, &mas);
+ dev_dbg(dev, "established reservation: %*pb\n",
+ UWB_NUM_MAS, mas.bm);
+ wusbhc_bwa_set(wusbhc, rsv->stream, &mas);
+ break;
+ case UWB_RSV_STATE_NONE:
+ dev_dbg(dev, "removed reservation\n");
+ wusbhc_bwa_set(wusbhc, 0, NULL);
+ break;
+ default:
+ dev_dbg(dev, "unexpected reservation state: %d\n", rsv->state);
+ break;
+ }
+}
+
+
+/**
+ * wusbhc_rsv_establish - establish a reservation for the cluster
+ * @wusbhc: the WUSB HC requesting a bandwidth reservation
+ */
+int wusbhc_rsv_establish(struct wusbhc *wusbhc)
+{
+ struct uwb_rc *rc = wusbhc->uwb_rc;
+ struct uwb_rsv *rsv;
+ struct uwb_dev_addr bcid;
+ int ret;
+
+ if (rc == NULL)
+ return -ENODEV;
+
+ rsv = uwb_rsv_create(rc, wusbhc_rsv_complete_cb, wusbhc);
+ if (rsv == NULL)
+ return -ENOMEM;
+
+ bcid.data[0] = wusbhc->cluster_id;
+ bcid.data[1] = 0;
+
+ rsv->target.type = UWB_RSV_TARGET_DEVADDR;
+ rsv->target.devaddr = bcid;
+ rsv->type = UWB_DRP_TYPE_PRIVATE;
+ rsv->max_mas = 256; /* try to get as much as possible */
+ rsv->min_mas = 15; /* one MAS per zone */
+ rsv->max_interval = 1; /* max latency is one zone */
+ rsv->is_multicast = true;
+
+ ret = uwb_rsv_establish(rsv);
+ if (ret == 0)
+ wusbhc->rsv = rsv;
+ else
+ uwb_rsv_destroy(rsv);
+ return ret;
+}
+
+
+/**
+ * wusbhc_rsv_terminate - terminate the cluster reservation
+ * @wusbhc: the WUSB host whose reservation is to be terminated
+ */
+void wusbhc_rsv_terminate(struct wusbhc *wusbhc)
+{
+ if (wusbhc->rsv) {
+ uwb_rsv_terminate(wusbhc->rsv);
+ uwb_rsv_destroy(wusbhc->rsv);
+ wusbhc->rsv = NULL;
+ }
+}
diff --git a/drivers/staging/wusbcore/rh.c b/drivers/staging/wusbcore/rh.c
new file mode 100644
index 000000000000..20c08cd9dcbf
--- /dev/null
+++ b/drivers/staging/wusbcore/rh.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB Host Controller
+ * Root Hub operations
+ *
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * We fake a root hub that has fake ports (as many as simultaneous
+ * devices the Wireless USB Host Controller can deal with). For each
+ * port we keep an state in @wusbhc->port[index] identical to the one
+ * specified in the USB2.0[ch11] spec and some extra device
+ * information that complements the one in 'struct usb_device' (as
+ * this lacs a hcpriv pointer).
+ *
+ * Note this is common to WHCI and HWA host controllers.
+ *
+ * Through here we enable most of the state changes that the USB stack
+ * will use to connect or disconnect devices. We need to do some
+ * forced adaptation of Wireless USB device states vs. wired:
+ *
+ * USB: WUSB:
+ *
+ * Port Powered-off port slot n/a
+ * Powered-on port slot available
+ * Disconnected port slot available
+ * Connected port slot assigned device
+ * device sent DN_Connect
+ * device was authenticated
+ * Enabled device is authenticated, transitioned
+ * from unauth -> auth -> default address
+ * -> enabled
+ * Reset disconnect
+ * Disable disconnect
+ *
+ * This maps the standard USB port states with the WUSB device states
+ * so we can fake ports without having to modify the USB stack.
+ *
+ * FIXME: this process will change in the future
+ *
+ *
+ * ENTRY POINTS
+ *
+ * Our entry points into here are, as in hcd.c, the USB stack root hub
+ * ops defined in the usb_hcd struct:
+ *
+ * wusbhc_rh_status_data() Provide hub and port status data bitmap
+ *
+ * wusbhc_rh_control() Execution of all the major requests
+ * you can do to a hub (Set|Clear
+ * features, get descriptors, status, etc).
+ *
+ * wusbhc_rh_[suspend|resume]() That
+ *
+ * wusbhc_rh_start_port_reset() ??? unimplemented
+ */
+#include <linux/slab.h>
+#include <linux/export.h>
+#include "wusbhc.h"
+
+/*
+ * Reset a fake port
+ *
+ * Using a Reset Device IE is too heavyweight as it causes the device
+ * to enter the UnConnected state and leave the cluster, this can mean
+ * that when the device reconnects it is connected to a different fake
+ * port.
+ *
+ * Instead, reset authenticated devices with a SetAddress(0), followed
+ * by a SetAddresss(AuthAddr).
+ *
+ * For unauthenticated devices just pretend to reset but do nothing.
+ * If the device initialization continues to fail it will eventually
+ * time out after TrustTimeout and enter the UnConnected state.
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ *
+ * Supposedly we are the only thread accesing @wusbhc->port; in any
+ * case, maybe we should move the mutex locking from
+ * wusbhc_devconnect_auth() to here.
+ *
+ * @port_idx refers to the wusbhc's port index, not the USB port number
+ */
+static int wusbhc_rh_port_reset(struct wusbhc *wusbhc, u8 port_idx)
+{
+ int result = 0;
+ struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx);
+ struct wusb_dev *wusb_dev = port->wusb_dev;
+
+ if (wusb_dev == NULL)
+ return -ENOTCONN;
+
+ port->status |= USB_PORT_STAT_RESET;
+ port->change |= USB_PORT_STAT_C_RESET;
+
+ if (wusb_dev->addr & WUSB_DEV_ADDR_UNAUTH)
+ result = 0;
+ else
+ result = wusb_dev_update_address(wusbhc, wusb_dev);
+
+ port->status &= ~USB_PORT_STAT_RESET;
+ port->status |= USB_PORT_STAT_ENABLE;
+ port->change |= USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_ENABLE;
+
+ return result;
+}
+
+/*
+ * Return the hub change status bitmap
+ *
+ * The bits in the change status bitmap are cleared when a
+ * ClearPortFeature request is issued (USB2.0[11.12.3,11.12.4].
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ *
+ * WARNING!! This gets called from atomic context; we cannot get the
+ * mutex--the only race condition we can find is some bit
+ * changing just after we copy it, which shouldn't be too
+ * big of a problem [and we can't make it an spinlock
+ * because other parts need to take it and sleep] .
+ *
+ * @usb_hcd is refcounted, so it won't disappear under us
+ * and before killing a host, the polling of the root hub
+ * would be stopped anyway.
+ */
+int wusbhc_rh_status_data(struct usb_hcd *usb_hcd, char *_buf)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ size_t cnt, size, bits_set = 0;
+
+ /* WE DON'T LOCK, see comment */
+ /* round up to bytes. Hub bit is bit 0 so add 1. */
+ size = DIV_ROUND_UP(wusbhc->ports_max + 1, 8);
+
+ /* clear the output buffer. */
+ memset(_buf, 0, size);
+ /* set the bit for each changed port. */
+ for (cnt = 0; cnt < wusbhc->ports_max; cnt++) {
+
+ if (wusb_port_by_idx(wusbhc, cnt)->change) {
+ const int bitpos = cnt+1;
+
+ _buf[bitpos/8] |= (1 << (bitpos % 8));
+ bits_set++;
+ }
+ }
+
+ return bits_set ? size : 0;
+}
+EXPORT_SYMBOL_GPL(wusbhc_rh_status_data);
+
+/*
+ * Return the hub's descriptor
+ *
+ * NOTE: almost cut and paste from ehci-hub.c
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked
+ */
+static int wusbhc_rh_get_hub_descr(struct wusbhc *wusbhc, u16 wValue,
+ u16 wIndex,
+ struct usb_hub_descriptor *descr,
+ u16 wLength)
+{
+ u16 temp = 1 + (wusbhc->ports_max / 8);
+ u8 length = 7 + 2 * temp;
+
+ if (wLength < length)
+ return -ENOSPC;
+ descr->bDescLength = 7 + 2 * temp;
+ descr->bDescriptorType = USB_DT_HUB; /* HUB type */
+ descr->bNbrPorts = wusbhc->ports_max;
+ descr->wHubCharacteristics = cpu_to_le16(
+ HUB_CHAR_COMMON_LPSM /* All ports power at once */
+ | 0x00 /* not part of compound device */
+ | HUB_CHAR_NO_OCPM /* No overcurrent protection */
+ | 0x00 /* 8 FS think time FIXME ?? */
+ | 0x00); /* No port indicators */
+ descr->bPwrOn2PwrGood = 0;
+ descr->bHubContrCurrent = 0;
+ /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
+ memset(&descr->u.hs.DeviceRemovable[0], 0, temp);
+ memset(&descr->u.hs.DeviceRemovable[temp], 0xff, temp);
+ return 0;
+}
+
+/*
+ * Clear a hub feature
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ *
+ * Nothing to do, so no locking needed ;)
+ */
+static int wusbhc_rh_clear_hub_feat(struct wusbhc *wusbhc, u16 feature)
+{
+ int result;
+
+ switch (feature) {
+ case C_HUB_LOCAL_POWER:
+ /* FIXME: maybe plug bit 0 to the power input status,
+ * if any?
+ * see wusbhc_rh_get_hub_status() */
+ case C_HUB_OVER_CURRENT:
+ result = 0;
+ break;
+ default:
+ result = -EPIPE;
+ }
+ return result;
+}
+
+/*
+ * Return hub status (it is always zero...)
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ *
+ * Nothing to do, so no locking needed ;)
+ */
+static int wusbhc_rh_get_hub_status(struct wusbhc *wusbhc, u32 *buf,
+ u16 wLength)
+{
+ /* FIXME: maybe plug bit 0 to the power input status (if any)? */
+ *buf = 0;
+ return 0;
+}
+
+/*
+ * Set a port feature
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ */
+static int wusbhc_rh_set_port_feat(struct wusbhc *wusbhc, u16 feature,
+ u8 selector, u8 port_idx)
+{
+ struct device *dev = wusbhc->dev;
+
+ if (port_idx > wusbhc->ports_max)
+ return -EINVAL;
+
+ switch (feature) {
+ /* According to USB2.0[11.24.2.13]p2, these features
+ * are not required to be implemented. */
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ case USB_PORT_FEAT_C_ENABLE:
+ case USB_PORT_FEAT_C_SUSPEND:
+ case USB_PORT_FEAT_C_CONNECTION:
+ case USB_PORT_FEAT_C_RESET:
+ return 0;
+ case USB_PORT_FEAT_POWER:
+ /* No such thing, but we fake it works */
+ mutex_lock(&wusbhc->mutex);
+ wusb_port_by_idx(wusbhc, port_idx)->status |= USB_PORT_STAT_POWER;
+ mutex_unlock(&wusbhc->mutex);
+ return 0;
+ case USB_PORT_FEAT_RESET:
+ return wusbhc_rh_port_reset(wusbhc, port_idx);
+ case USB_PORT_FEAT_ENABLE:
+ case USB_PORT_FEAT_SUSPEND:
+ dev_err(dev, "(port_idx %d) set feat %d/%d UNIMPLEMENTED\n",
+ port_idx, feature, selector);
+ return -ENOSYS;
+ default:
+ dev_err(dev, "(port_idx %d) set feat %d/%d UNKNOWN\n",
+ port_idx, feature, selector);
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+/*
+ * Clear a port feature...
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ */
+static int wusbhc_rh_clear_port_feat(struct wusbhc *wusbhc, u16 feature,
+ u8 selector, u8 port_idx)
+{
+ int result = 0;
+ struct device *dev = wusbhc->dev;
+
+ if (port_idx > wusbhc->ports_max)
+ return -EINVAL;
+
+ mutex_lock(&wusbhc->mutex);
+ switch (feature) {
+ case USB_PORT_FEAT_POWER: /* fake port always on */
+ /* According to USB2.0[11.24.2.7.1.4], no need to implement? */
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_RESET;
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_CONNECTION;
+ break;
+ case USB_PORT_FEAT_ENABLE:
+ __wusbhc_dev_disable(wusbhc, port_idx);
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_ENABLE;
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ case USB_PORT_FEAT_C_SUSPEND:
+ dev_err(dev, "(port_idx %d) Clear feat %d/%d UNIMPLEMENTED\n",
+ port_idx, feature, selector);
+ result = -ENOSYS;
+ break;
+ default:
+ dev_err(dev, "(port_idx %d) Clear feat %d/%d UNKNOWN\n",
+ port_idx, feature, selector);
+ result = -EPIPE;
+ break;
+ }
+ mutex_unlock(&wusbhc->mutex);
+
+ return result;
+}
+
+/*
+ * Return the port's status
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ */
+static int wusbhc_rh_get_port_status(struct wusbhc *wusbhc, u16 port_idx,
+ u32 *_buf, u16 wLength)
+{
+ __le16 *buf = (__le16 *)_buf;
+
+ if (port_idx > wusbhc->ports_max)
+ return -EINVAL;
+
+ mutex_lock(&wusbhc->mutex);
+ buf[0] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->status);
+ buf[1] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->change);
+ mutex_unlock(&wusbhc->mutex);
+
+ return 0;
+}
+
+/*
+ * Entry point for Root Hub operations
+ *
+ * @wusbhc is assumed referenced and @wusbhc->mutex unlocked.
+ */
+int wusbhc_rh_control(struct usb_hcd *usb_hcd, u16 reqntype, u16 wValue,
+ u16 wIndex, char *buf, u16 wLength)
+{
+ int result = -ENOSYS;
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+
+ switch (reqntype) {
+ case GetHubDescriptor:
+ result = wusbhc_rh_get_hub_descr(
+ wusbhc, wValue, wIndex,
+ (struct usb_hub_descriptor *) buf, wLength);
+ break;
+ case ClearHubFeature:
+ result = wusbhc_rh_clear_hub_feat(wusbhc, wValue);
+ break;
+ case GetHubStatus:
+ result = wusbhc_rh_get_hub_status(wusbhc, (u32 *)buf, wLength);
+ break;
+
+ case SetPortFeature:
+ result = wusbhc_rh_set_port_feat(wusbhc, wValue, wIndex >> 8,
+ (wIndex & 0xff) - 1);
+ break;
+ case ClearPortFeature:
+ result = wusbhc_rh_clear_port_feat(wusbhc, wValue, wIndex >> 8,
+ (wIndex & 0xff) - 1);
+ break;
+ case GetPortStatus:
+ result = wusbhc_rh_get_port_status(wusbhc, wIndex - 1,
+ (u32 *)buf, wLength);
+ break;
+
+ case SetHubFeature:
+ default:
+ dev_err(wusbhc->dev, "%s (%p [%p], %x, %x, %x, %p, %x) "
+ "UNIMPLEMENTED\n", __func__, usb_hcd, wusbhc, reqntype,
+ wValue, wIndex, buf, wLength);
+ /* dump_stack(); */
+ result = -ENOSYS;
+ }
+ return result;
+}
+EXPORT_SYMBOL_GPL(wusbhc_rh_control);
+
+int wusbhc_rh_start_port_reset(struct usb_hcd *usb_hcd, unsigned port_idx)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ dev_err(wusbhc->dev, "%s (%p [%p], port_idx %u) UNIMPLEMENTED\n",
+ __func__, usb_hcd, wusbhc, port_idx);
+ WARN_ON(1);
+ return -ENOSYS;
+}
+EXPORT_SYMBOL_GPL(wusbhc_rh_start_port_reset);
+
+static void wusb_port_init(struct wusb_port *port)
+{
+ port->status |= USB_PORT_STAT_HIGH_SPEED;
+}
+
+/*
+ * Alloc fake port specific fields and status.
+ */
+int wusbhc_rh_create(struct wusbhc *wusbhc)
+{
+ int result = -ENOMEM;
+ size_t port_size, itr;
+ port_size = wusbhc->ports_max * sizeof(wusbhc->port[0]);
+ wusbhc->port = kzalloc(port_size, GFP_KERNEL);
+ if (wusbhc->port == NULL)
+ goto error_port_alloc;
+ for (itr = 0; itr < wusbhc->ports_max; itr++)
+ wusb_port_init(&wusbhc->port[itr]);
+ result = 0;
+error_port_alloc:
+ return result;
+}
+
+void wusbhc_rh_destroy(struct wusbhc *wusbhc)
+{
+ kfree(wusbhc->port);
+}
diff --git a/drivers/staging/wusbcore/security.c b/drivers/staging/wusbcore/security.c
new file mode 100644
index 000000000000..14ac8c98ac9e
--- /dev/null
+++ b/drivers/staging/wusbcore/security.c
@@ -0,0 +1,599 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB Host Controller
+ * Security support: encryption enablement, etc
+ *
+ * Copyright (C) 2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ */
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/usb/ch9.h>
+#include <linux/random.h>
+#include <linux/export.h>
+#include "wusbhc.h"
+#include <asm/unaligned.h>
+
+static void wusbhc_gtk_rekey_work(struct work_struct *work);
+
+int wusbhc_sec_create(struct wusbhc *wusbhc)
+{
+ /*
+ * WQ is singlethread because we need to serialize rekey operations.
+ * Use a separate workqueue for security operations instead of the
+ * wusbd workqueue because security operations may need to communicate
+ * directly with downstream wireless devices using synchronous URBs.
+ * If a device is not responding, this could block other host
+ * controller operations.
+ */
+ wusbhc->wq_security = create_singlethread_workqueue("wusbd_security");
+ if (wusbhc->wq_security == NULL) {
+ pr_err("WUSB-core: Cannot create wusbd_security workqueue\n");
+ return -ENOMEM;
+ }
+
+ wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) +
+ sizeof(wusbhc->gtk.data);
+ wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY;
+ wusbhc->gtk.descr.bReserved = 0;
+ wusbhc->gtk_index = 0;
+
+ INIT_WORK(&wusbhc->gtk_rekey_work, wusbhc_gtk_rekey_work);
+
+ return 0;
+}
+
+
+/* Called when the HC is destroyed */
+void wusbhc_sec_destroy(struct wusbhc *wusbhc)
+{
+ destroy_workqueue(wusbhc->wq_security);
+}
+
+
+/**
+ * wusbhc_next_tkid - generate a new, currently unused, TKID
+ * @wusbhc: the WUSB host controller
+ * @wusb_dev: the device whose PTK the TKID is for
+ * (or NULL for a TKID for a GTK)
+ *
+ * The generated TKID consists of two parts: the device's authenticated
+ * address (or 0 or a GTK); and an incrementing number. This ensures
+ * that TKIDs cannot be shared between devices and by the time the
+ * incrementing number wraps around the older TKIDs will no longer be
+ * in use (a maximum of two keys may be active at any one time).
+ */
+static u32 wusbhc_next_tkid(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+ u32 *tkid;
+ u32 addr;
+
+ if (wusb_dev == NULL) {
+ tkid = &wusbhc->gtk_tkid;
+ addr = 0;
+ } else {
+ tkid = &wusb_port_by_idx(wusbhc, wusb_dev->port_idx)->ptk_tkid;
+ addr = wusb_dev->addr & 0x7f;
+ }
+
+ *tkid = (addr << 8) | ((*tkid + 1) & 0xff);
+
+ return *tkid;
+}
+
+static void wusbhc_generate_gtk(struct wusbhc *wusbhc)
+{
+ const size_t key_size = sizeof(wusbhc->gtk.data);
+ u32 tkid;
+
+ tkid = wusbhc_next_tkid(wusbhc, NULL);
+
+ wusbhc->gtk.descr.tTKID[0] = (tkid >> 0) & 0xff;
+ wusbhc->gtk.descr.tTKID[1] = (tkid >> 8) & 0xff;
+ wusbhc->gtk.descr.tTKID[2] = (tkid >> 16) & 0xff;
+
+ get_random_bytes(wusbhc->gtk.descr.bKeyData, key_size);
+}
+
+/**
+ * wusbhc_sec_start - start the security management process
+ * @wusbhc: the WUSB host controller
+ *
+ * Generate and set an initial GTK on the host controller.
+ *
+ * Called when the HC is started.
+ */
+int wusbhc_sec_start(struct wusbhc *wusbhc)
+{
+ const size_t key_size = sizeof(wusbhc->gtk.data);
+ int result;
+
+ wusbhc_generate_gtk(wusbhc);
+
+ result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid,
+ &wusbhc->gtk.descr.bKeyData, key_size);
+ if (result < 0)
+ dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n",
+ result);
+
+ return result;
+}
+
+/**
+ * wusbhc_sec_stop - stop the security management process
+ * @wusbhc: the WUSB host controller
+ *
+ * Wait for any pending GTK rekeys to stop.
+ */
+void wusbhc_sec_stop(struct wusbhc *wusbhc)
+{
+ cancel_work_sync(&wusbhc->gtk_rekey_work);
+}
+
+
+/** @returns encryption type name */
+const char *wusb_et_name(u8 x)
+{
+ switch (x) {
+ case USB_ENC_TYPE_UNSECURE: return "unsecure";
+ case USB_ENC_TYPE_WIRED: return "wired";
+ case USB_ENC_TYPE_CCM_1: return "CCM-1";
+ case USB_ENC_TYPE_RSA_1: return "RSA-1";
+ default: return "unknown";
+ }
+}
+EXPORT_SYMBOL_GPL(wusb_et_name);
+
+/*
+ * Set the device encryption method
+ *
+ * We tell the device which encryption method to use; we do this when
+ * setting up the device's security.
+ */
+static int wusb_dev_set_encryption(struct usb_device *usb_dev, int value)
+{
+ int result;
+ struct device *dev = &usb_dev->dev;
+ struct wusb_dev *wusb_dev = usb_dev->wusb_dev;
+
+ if (value) {
+ value = wusb_dev->ccm1_etd.bEncryptionValue;
+ } else {
+ /* FIXME: should be wusb_dev->etd[UNSECURE].bEncryptionValue */
+ value = 0;
+ }
+ /* Set device's */
+ result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+ USB_REQ_SET_ENCRYPTION,
+ USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+ value, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (result < 0)
+ dev_err(dev, "Can't set device's WUSB encryption to "
+ "%s (value %d): %d\n",
+ wusb_et_name(wusb_dev->ccm1_etd.bEncryptionType),
+ wusb_dev->ccm1_etd.bEncryptionValue, result);
+ return result;
+}
+
+/*
+ * Set the GTK to be used by a device.
+ *
+ * The device must be authenticated.
+ */
+static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+ struct usb_device *usb_dev = wusb_dev->usb_dev;
+ u8 key_index = wusb_key_index(wusbhc->gtk_index,
+ WUSB_KEY_INDEX_TYPE_GTK, WUSB_KEY_INDEX_ORIGINATOR_HOST);
+
+ return usb_control_msg(
+ usb_dev, usb_sndctrlpipe(usb_dev, 0),
+ USB_REQ_SET_DESCRIPTOR,
+ USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+ USB_DT_KEY << 8 | key_index, 0,
+ &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength,
+ USB_CTRL_SET_TIMEOUT);
+}
+
+
+/* FIXME: prototype for adding security */
+int wusb_dev_sec_add(struct wusbhc *wusbhc,
+ struct usb_device *usb_dev, struct wusb_dev *wusb_dev)
+{
+ int result, bytes, secd_size;
+ struct device *dev = &usb_dev->dev;
+ struct usb_security_descriptor *secd, *new_secd;
+ const struct usb_encryption_descriptor *etd, *ccm1_etd = NULL;
+ const void *itr, *top;
+ char buf[64];
+
+ secd = kmalloc(sizeof(*secd), GFP_KERNEL);
+ if (secd == NULL) {
+ result = -ENOMEM;
+ goto out;
+ }
+
+ result = usb_get_descriptor(usb_dev, USB_DT_SECURITY,
+ 0, secd, sizeof(*secd));
+ if (result < (int)sizeof(*secd)) {
+ dev_err(dev, "Can't read security descriptor or "
+ "not enough data: %d\n", result);
+ goto out;
+ }
+ secd_size = le16_to_cpu(secd->wTotalLength);
+ new_secd = krealloc(secd, secd_size, GFP_KERNEL);
+ if (new_secd == NULL) {
+ dev_err(dev,
+ "Can't allocate space for security descriptors\n");
+ result = -ENOMEM;
+ goto out;
+ }
+ secd = new_secd;
+ result = usb_get_descriptor(usb_dev, USB_DT_SECURITY,
+ 0, secd, secd_size);
+ if (result < secd_size) {
+ dev_err(dev, "Can't read security descriptor or "
+ "not enough data: %d\n", result);
+ goto out;
+ }
+ bytes = 0;
+ itr = &secd[1];
+ top = (void *)secd + result;
+ while (itr < top) {
+ etd = itr;
+ if (top - itr < sizeof(*etd)) {
+ dev_err(dev, "BUG: bad device security descriptor; "
+ "not enough data (%zu vs %zu bytes left)\n",
+ top - itr, sizeof(*etd));
+ break;
+ }
+ if (etd->bLength < sizeof(*etd)) {
+ dev_err(dev, "BUG: bad device encryption descriptor; "
+ "descriptor is too short "
+ "(%u vs %zu needed)\n",
+ etd->bLength, sizeof(*etd));
+ break;
+ }
+ itr += etd->bLength;
+ bytes += snprintf(buf + bytes, sizeof(buf) - bytes,
+ "%s (0x%02x/%02x) ",
+ wusb_et_name(etd->bEncryptionType),
+ etd->bEncryptionValue, etd->bAuthKeyIndex);
+ if (etd->bEncryptionType == USB_ENC_TYPE_CCM_1)
+ ccm1_etd = etd;
+ }
+ /* This code only supports CCM1 as of now. */
+ /* FIXME: user has to choose which sec mode to use?
+ * In theory we want CCM */
+ if (ccm1_etd == NULL) {
+ dev_err(dev, "WUSB device doesn't support CCM1 encryption, "
+ "can't use!\n");
+ result = -EINVAL;
+ goto out;
+ }
+ wusb_dev->ccm1_etd = *ccm1_etd;
+ dev_dbg(dev, "supported encryption: %s; using %s (0x%02x/%02x)\n",
+ buf, wusb_et_name(ccm1_etd->bEncryptionType),
+ ccm1_etd->bEncryptionValue, ccm1_etd->bAuthKeyIndex);
+ result = 0;
+out:
+ kfree(secd);
+ return result;
+}
+
+void wusb_dev_sec_rm(struct wusb_dev *wusb_dev)
+{
+ /* Nothing so far */
+}
+
+/**
+ * Update the address of an unauthenticated WUSB device
+ *
+ * Once we have successfully authenticated, we take it to addr0 state
+ * and then to a normal address.
+ *
+ * Before the device's address (as known by it) was usb_dev->devnum |
+ * 0x80 (unauthenticated address). With this we update it to usb_dev->devnum.
+ */
+int wusb_dev_update_address(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+ int result = -ENOMEM;
+ struct usb_device *usb_dev = wusb_dev->usb_dev;
+ struct device *dev = &usb_dev->dev;
+ u8 new_address = wusb_dev->addr & 0x7F;
+
+ /* Set address 0 */
+ result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+ USB_REQ_SET_ADDRESS,
+ USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+ 0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "auth failed: can't set address 0: %d\n",
+ result);
+ goto error_addr0;
+ }
+ result = wusb_set_dev_addr(wusbhc, wusb_dev, 0);
+ if (result < 0)
+ goto error_addr0;
+ usb_set_device_state(usb_dev, USB_STATE_DEFAULT);
+ usb_ep0_reinit(usb_dev);
+
+ /* Set new (authenticated) address. */
+ result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+ USB_REQ_SET_ADDRESS,
+ USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+ new_address, 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "auth failed: can't set address %u: %d\n",
+ new_address, result);
+ goto error_addr;
+ }
+ result = wusb_set_dev_addr(wusbhc, wusb_dev, new_address);
+ if (result < 0)
+ goto error_addr;
+ usb_set_device_state(usb_dev, USB_STATE_ADDRESS);
+ usb_ep0_reinit(usb_dev);
+ usb_dev->authenticated = 1;
+error_addr:
+error_addr0:
+ return result;
+}
+
+/*
+ *
+ *
+ */
+/* FIXME: split and cleanup */
+int wusb_dev_4way_handshake(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev,
+ struct wusb_ckhdid *ck)
+{
+ int result = -ENOMEM;
+ struct usb_device *usb_dev = wusb_dev->usb_dev;
+ struct device *dev = &usb_dev->dev;
+ u32 tkid;
+ struct usb_handshake *hs;
+ struct aes_ccm_nonce ccm_n;
+ u8 mic[8];
+ struct wusb_keydvt_in keydvt_in;
+ struct wusb_keydvt_out keydvt_out;
+
+ hs = kcalloc(3, sizeof(hs[0]), GFP_KERNEL);
+ if (!hs)
+ goto error_kzalloc;
+
+ /* We need to turn encryption before beginning the 4way
+ * hshake (WUSB1.0[.3.2.2]) */
+ result = wusb_dev_set_encryption(usb_dev, 1);
+ if (result < 0)
+ goto error_dev_set_encryption;
+
+ tkid = wusbhc_next_tkid(wusbhc, wusb_dev);
+
+ hs[0].bMessageNumber = 1;
+ hs[0].bStatus = 0;
+ put_unaligned_le32(tkid, hs[0].tTKID);
+ hs[0].bReserved = 0;
+ memcpy(hs[0].CDID, &wusb_dev->cdid, sizeof(hs[0].CDID));
+ get_random_bytes(&hs[0].nonce, sizeof(hs[0].nonce));
+ memset(hs[0].MIC, 0, sizeof(hs[0].MIC)); /* Per WUSB1.0[T7-22] */
+
+ result = usb_control_msg(
+ usb_dev, usb_sndctrlpipe(usb_dev, 0),
+ USB_REQ_SET_HANDSHAKE,
+ USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+ 1, 0, &hs[0], sizeof(hs[0]), USB_CTRL_SET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "Handshake1: request failed: %d\n", result);
+ goto error_hs1;
+ }
+
+ /* Handshake 2, from the device -- need to verify fields */
+ result = usb_control_msg(
+ usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+ USB_REQ_GET_HANDSHAKE,
+ USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+ 2, 0, &hs[1], sizeof(hs[1]), USB_CTRL_GET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "Handshake2: request failed: %d\n", result);
+ goto error_hs2;
+ }
+
+ result = -EINVAL;
+ if (hs[1].bMessageNumber != 2) {
+ dev_err(dev, "Handshake2 failed: bad message number %u\n",
+ hs[1].bMessageNumber);
+ goto error_hs2;
+ }
+ if (hs[1].bStatus != 0) {
+ dev_err(dev, "Handshake2 failed: bad status %u\n",
+ hs[1].bStatus);
+ goto error_hs2;
+ }
+ if (memcmp(hs[0].tTKID, hs[1].tTKID, sizeof(hs[0].tTKID))) {
+ dev_err(dev, "Handshake2 failed: TKID mismatch "
+ "(#1 0x%02x%02x%02x vs #2 0x%02x%02x%02x)\n",
+ hs[0].tTKID[0], hs[0].tTKID[1], hs[0].tTKID[2],
+ hs[1].tTKID[0], hs[1].tTKID[1], hs[1].tTKID[2]);
+ goto error_hs2;
+ }
+ if (memcmp(hs[0].CDID, hs[1].CDID, sizeof(hs[0].CDID))) {
+ dev_err(dev, "Handshake2 failed: CDID mismatch\n");
+ goto error_hs2;
+ }
+
+ /* Setup the CCM nonce */
+ memset(&ccm_n.sfn, 0, sizeof(ccm_n.sfn)); /* Per WUSB1.0[6.5.2] */
+ put_unaligned_le32(tkid, ccm_n.tkid);
+ ccm_n.src_addr = wusbhc->uwb_rc->uwb_dev.dev_addr;
+ ccm_n.dest_addr.data[0] = wusb_dev->addr;
+ ccm_n.dest_addr.data[1] = 0;
+
+ /* Derive the KCK and PTK from CK, the CCM, H and D nonces */
+ memcpy(keydvt_in.hnonce, hs[0].nonce, sizeof(keydvt_in.hnonce));
+ memcpy(keydvt_in.dnonce, hs[1].nonce, sizeof(keydvt_in.dnonce));
+ result = wusb_key_derive(&keydvt_out, ck->data, &ccm_n, &keydvt_in);
+ if (result < 0) {
+ dev_err(dev, "Handshake2 failed: cannot derive keys: %d\n",
+ result);
+ goto error_hs2;
+ }
+
+ /* Compute MIC and verify it */
+ result = wusb_oob_mic(mic, keydvt_out.kck, &ccm_n, &hs[1]);
+ if (result < 0) {
+ dev_err(dev, "Handshake2 failed: cannot compute MIC: %d\n",
+ result);
+ goto error_hs2;
+ }
+
+ if (memcmp(hs[1].MIC, mic, sizeof(hs[1].MIC))) {
+ dev_err(dev, "Handshake2 failed: MIC mismatch\n");
+ goto error_hs2;
+ }
+
+ /* Send Handshake3 */
+ hs[2].bMessageNumber = 3;
+ hs[2].bStatus = 0;
+ put_unaligned_le32(tkid, hs[2].tTKID);
+ hs[2].bReserved = 0;
+ memcpy(hs[2].CDID, &wusb_dev->cdid, sizeof(hs[2].CDID));
+ memcpy(hs[2].nonce, hs[0].nonce, sizeof(hs[2].nonce));
+ result = wusb_oob_mic(hs[2].MIC, keydvt_out.kck, &ccm_n, &hs[2]);
+ if (result < 0) {
+ dev_err(dev, "Handshake3 failed: cannot compute MIC: %d\n",
+ result);
+ goto error_hs2;
+ }
+
+ result = usb_control_msg(
+ usb_dev, usb_sndctrlpipe(usb_dev, 0),
+ USB_REQ_SET_HANDSHAKE,
+ USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+ 3, 0, &hs[2], sizeof(hs[2]), USB_CTRL_SET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "Handshake3: request failed: %d\n", result);
+ goto error_hs3;
+ }
+
+ result = wusbhc->set_ptk(wusbhc, wusb_dev->port_idx, tkid,
+ keydvt_out.ptk, sizeof(keydvt_out.ptk));
+ if (result < 0)
+ goto error_wusbhc_set_ptk;
+
+ result = wusb_dev_set_gtk(wusbhc, wusb_dev);
+ if (result < 0) {
+ dev_err(dev, "Set GTK for device: request failed: %d\n",
+ result);
+ goto error_wusbhc_set_gtk;
+ }
+
+ /* Update the device's address from unauth to auth */
+ if (usb_dev->authenticated == 0) {
+ result = wusb_dev_update_address(wusbhc, wusb_dev);
+ if (result < 0)
+ goto error_dev_update_address;
+ }
+ result = 0;
+ dev_info(dev, "device authenticated\n");
+
+error_dev_update_address:
+error_wusbhc_set_gtk:
+error_wusbhc_set_ptk:
+error_hs3:
+error_hs2:
+error_hs1:
+ memset(hs, 0, 3*sizeof(hs[0]));
+ memzero_explicit(&keydvt_out, sizeof(keydvt_out));
+ memzero_explicit(&keydvt_in, sizeof(keydvt_in));
+ memzero_explicit(&ccm_n, sizeof(ccm_n));
+ memzero_explicit(mic, sizeof(mic));
+ if (result < 0)
+ wusb_dev_set_encryption(usb_dev, 0);
+error_dev_set_encryption:
+ kfree(hs);
+error_kzalloc:
+ return result;
+}
+
+/*
+ * Once all connected and authenticated devices have received the new
+ * GTK, switch the host to using it.
+ */
+static void wusbhc_gtk_rekey_work(struct work_struct *work)
+{
+ struct wusbhc *wusbhc = container_of(work,
+ struct wusbhc, gtk_rekey_work);
+ size_t key_size = sizeof(wusbhc->gtk.data);
+ int port_idx;
+ struct wusb_dev *wusb_dev, *wusb_dev_next;
+ LIST_HEAD(rekey_list);
+
+ mutex_lock(&wusbhc->mutex);
+ /* generate the new key */
+ wusbhc_generate_gtk(wusbhc);
+ /* roll the gtk index. */
+ wusbhc->gtk_index = (wusbhc->gtk_index + 1) % (WUSB_KEY_INDEX_MAX + 1);
+ /*
+ * Save all connected devices on a list while holding wusbhc->mutex and
+ * take a reference to each one. Then submit the set key request to
+ * them after releasing the lock in order to avoid a deadlock.
+ */
+ for (port_idx = 0; port_idx < wusbhc->ports_max; port_idx++) {
+ wusb_dev = wusbhc->port[port_idx].wusb_dev;
+ if (!wusb_dev || !wusb_dev->usb_dev
+ || !wusb_dev->usb_dev->authenticated)
+ continue;
+
+ wusb_dev_get(wusb_dev);
+ list_add_tail(&wusb_dev->rekey_node, &rekey_list);
+ }
+ mutex_unlock(&wusbhc->mutex);
+
+ /* Submit the rekey requests without holding wusbhc->mutex. */
+ list_for_each_entry_safe(wusb_dev, wusb_dev_next, &rekey_list,
+ rekey_node) {
+ list_del_init(&wusb_dev->rekey_node);
+ dev_dbg(&wusb_dev->usb_dev->dev,
+ "%s: rekey device at port %d\n",
+ __func__, wusb_dev->port_idx);
+
+ if (wusb_dev_set_gtk(wusbhc, wusb_dev) < 0) {
+ dev_err(&wusb_dev->usb_dev->dev,
+ "%s: rekey device at port %d failed\n",
+ __func__, wusb_dev->port_idx);
+ }
+ wusb_dev_put(wusb_dev);
+ }
+
+ /* Switch the host controller to use the new GTK. */
+ mutex_lock(&wusbhc->mutex);
+ wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid,
+ &wusbhc->gtk.descr.bKeyData, key_size);
+ mutex_unlock(&wusbhc->mutex);
+}
+
+/**
+ * wusbhc_gtk_rekey - generate and distribute a new GTK
+ * @wusbhc: the WUSB host controller
+ *
+ * Generate a new GTK and distribute it to all connected and
+ * authenticated devices. When all devices have the new GTK, the host
+ * starts using it.
+ *
+ * This must be called after every device disconnect (see [WUSB]
+ * section 6.2.11.2).
+ */
+void wusbhc_gtk_rekey(struct wusbhc *wusbhc)
+{
+ /*
+ * We need to submit a URB to the downstream WUSB devices in order to
+ * change the group key. This can't be done while holding the
+ * wusbhc->mutex since that is also taken in the urb_enqueue routine
+ * and will cause a deadlock. Instead, queue a work item to do
+ * it when the lock is not held
+ */
+ queue_work(wusbhc->wq_security, &wusbhc->gtk_rekey_work);
+}
diff --git a/drivers/staging/wusbcore/wa-hc.c b/drivers/staging/wusbcore/wa-hc.c
new file mode 100644
index 000000000000..6827075fb8a1
--- /dev/null
+++ b/drivers/staging/wusbcore/wa-hc.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wire Adapter Host Controller Driver
+ * Common items to HWA and DWA based HCDs
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ */
+#include <linux/slab.h>
+#include <linux/module.h>
+#include "wusbhc.h"
+#include "wa-hc.h"
+
+/**
+ * Assumes
+ *
+ * wa->usb_dev and wa->usb_iface initialized and refcounted,
+ * wa->wa_descr initialized.
+ */
+int wa_create(struct wahc *wa, struct usb_interface *iface,
+ kernel_ulong_t quirks)
+{
+ int result;
+ struct device *dev = &iface->dev;
+
+ if (iface->cur_altsetting->desc.bNumEndpoints < 3)
+ return -ENODEV;
+
+ result = wa_rpipes_create(wa);
+ if (result < 0)
+ goto error_rpipes_create;
+ wa->quirks = quirks;
+ /* Fill up Data Transfer EP pointers */
+ wa->dti_epd = &iface->cur_altsetting->endpoint[1].desc;
+ wa->dto_epd = &iface->cur_altsetting->endpoint[2].desc;
+ wa->dti_buf_size = usb_endpoint_maxp(wa->dti_epd);
+ wa->dti_buf = kmalloc(wa->dti_buf_size, GFP_KERNEL);
+ if (wa->dti_buf == NULL) {
+ result = -ENOMEM;
+ goto error_dti_buf_alloc;
+ }
+ result = wa_nep_create(wa, iface);
+ if (result < 0) {
+ dev_err(dev, "WA-CDS: can't initialize notif endpoint: %d\n",
+ result);
+ goto error_nep_create;
+ }
+ return 0;
+
+error_nep_create:
+ kfree(wa->dti_buf);
+error_dti_buf_alloc:
+ wa_rpipes_destroy(wa);
+error_rpipes_create:
+ return result;
+}
+EXPORT_SYMBOL_GPL(wa_create);
+
+
+void __wa_destroy(struct wahc *wa)
+{
+ if (wa->dti_urb) {
+ usb_kill_urb(wa->dti_urb);
+ usb_put_urb(wa->dti_urb);
+ }
+ kfree(wa->dti_buf);
+ wa_nep_destroy(wa);
+ wa_rpipes_destroy(wa);
+}
+EXPORT_SYMBOL_GPL(__wa_destroy);
+
+/**
+ * wa_reset_all - reset the WA device
+ * @wa: the WA to be reset
+ *
+ * For HWAs the radio controller and all other PALs are also reset.
+ */
+void wa_reset_all(struct wahc *wa)
+{
+ /* FIXME: assuming HWA. */
+ wusbhc_reset_all(wa->wusb);
+}
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Wireless USB Wire Adapter core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/wusbcore/wa-hc.h b/drivers/staging/wusbcore/wa-hc.h
new file mode 100644
index 000000000000..5a38465724c2
--- /dev/null
+++ b/drivers/staging/wusbcore/wa-hc.h
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HWA Host Controller Driver
+ * Wire Adapter Control/Data Streaming Iface (WUSB1.0[8])
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This driver implements a USB Host Controller (struct usb_hcd) for a
+ * Wireless USB Host Controller based on the Wireless USB 1.0
+ * Host-Wire-Adapter specification (in layman terms, a USB-dongle that
+ * implements a Wireless USB host).
+ *
+ * Check out the Design-overview.txt file in the source documentation
+ * for other details on the implementation.
+ *
+ * Main blocks:
+ *
+ * driver glue with the driver API, workqueue daemon
+ *
+ * lc RC instance life cycle management (create, destroy...)
+ *
+ * hcd glue with the USB API Host Controller Interface API.
+ *
+ * nep Notification EndPoint management: collect notifications
+ * and queue them with the workqueue daemon.
+ *
+ * Handle notifications as coming from the NEP. Sends them
+ * off others to their respective modules (eg: connect,
+ * disconnect and reset go to devconnect).
+ *
+ * rpipe Remote Pipe management; rpipe is what we use to write
+ * to an endpoint on a WUSB device that is connected to a
+ * HWA RC.
+ *
+ * xfer Transfer management -- this is all the code that gets a
+ * buffer and pushes it to a device (or viceversa). *
+ *
+ * Some day a lot of this code will be shared between this driver and
+ * the drivers for DWA (xfer, rpipe).
+ *
+ * All starts at driver.c:hwahc_probe(), when one of this guys is
+ * connected. hwahc_disconnect() stops it.
+ *
+ * During operation, the main driver is devices connecting or
+ * disconnecting. They cause the HWA RC to send notifications into
+ * nep.c:hwahc_nep_cb() that will dispatch them to
+ * notif.c:wa_notif_dispatch(). From there they will fan to cause
+ * device connects, disconnects, etc.
+ *
+ * Note much of the activity is difficult to follow. For example a
+ * device connect goes to devconnect, which will cause the "fake" root
+ * hub port to show a connect and stop there. Then hub_wq will notice
+ * and call into the rh.c:hwahc_rc_port_reset() code to authenticate
+ * the device (and this might require user intervention) and enable
+ * the port.
+ *
+ * We also have a timer workqueue going from devconnect.c that
+ * schedules in hwahc_devconnect_create().
+ *
+ * The rest of the traffic is in the usual entry points of a USB HCD,
+ * which are hooked up in driver.c:hwahc_rc_driver, and defined in
+ * hcd.c.
+ */
+
+#ifndef __HWAHC_INTERNAL_H__
+#define __HWAHC_INTERNAL_H__
+
+#include <linux/completion.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include "../uwb/uwb.h"
+#include "include/wusb.h"
+#include "include/wusb-wa.h"
+
+struct wusbhc;
+struct wahc;
+extern void wa_urb_enqueue_run(struct work_struct *ws);
+extern void wa_process_errored_transfers_run(struct work_struct *ws);
+
+/**
+ * RPipe instance
+ *
+ * @descr's fields are kept in LE, as we need to send it back and
+ * forth.
+ *
+ * @wa is referenced when set
+ *
+ * @segs_available is the number of requests segments that still can
+ * be submitted to the controller without overloading
+ * it. It is initialized to descr->wRequests when
+ * aiming.
+ *
+ * A rpipe supports a max of descr->wRequests at the same time; before
+ * submitting seg_lock has to be taken. If segs_avail > 0, then we can
+ * submit; if not, we have to queue them.
+ */
+struct wa_rpipe {
+ struct kref refcnt;
+ struct usb_rpipe_descriptor descr;
+ struct usb_host_endpoint *ep;
+ struct wahc *wa;
+ spinlock_t seg_lock;
+ struct list_head seg_list;
+ struct list_head list_node;
+ atomic_t segs_available;
+ u8 buffer[1]; /* For reads/writes on USB */
+};
+
+
+enum wa_dti_state {
+ WA_DTI_TRANSFER_RESULT_PENDING,
+ WA_DTI_ISOC_PACKET_STATUS_PENDING,
+ WA_DTI_BUF_IN_DATA_PENDING
+};
+
+enum wa_quirks {
+ /*
+ * The Alereon HWA expects the data frames in isochronous transfer
+ * requests to be concatenated and not sent as separate packets.
+ */
+ WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC = 0x01,
+ /*
+ * The Alereon HWA can be instructed to not send transfer notifications
+ * as an optimization.
+ */
+ WUSB_QUIRK_ALEREON_HWA_DISABLE_XFER_NOTIFICATIONS = 0x02,
+};
+
+enum wa_vendor_specific_requests {
+ WA_REQ_ALEREON_DISABLE_XFER_NOTIFICATIONS = 0x4C,
+ WA_REQ_ALEREON_FEATURE_SET = 0x01,
+ WA_REQ_ALEREON_FEATURE_CLEAR = 0x00,
+};
+
+#define WA_MAX_BUF_IN_URBS 4
+/**
+ * Instance of a HWA Host Controller
+ *
+ * Except where a more specific lock/mutex applies or atomic, all
+ * fields protected by @mutex.
+ *
+ * @wa_descr Can be accessed without locking because it is in
+ * the same area where the device descriptors were
+ * read, so it is guaranteed to exist unmodified while
+ * the device exists.
+ *
+ * Endianess has been converted to CPU's.
+ *
+ * @nep_* can be accessed without locking as its processing is
+ * serialized; we submit a NEP URB and it comes to
+ * hwahc_nep_cb(), which won't issue another URB until it is
+ * done processing it.
+ *
+ * @xfer_list:
+ *
+ * List of active transfers to verify existence from a xfer id
+ * gotten from the xfer result message. Can't use urb->list because
+ * it goes by endpoint, and we don't know the endpoint at the time
+ * when we get the xfer result message. We can't really rely on the
+ * pointer (will have to change for 64 bits) as the xfer id is 32 bits.
+ *
+ * @xfer_delayed_list: List of transfers that need to be started
+ * (with a workqueue, because they were
+ * submitted from an atomic context).
+ *
+ * FIXME: this needs to be layered up: a wusbhc layer (for sharing
+ * commonalities with WHCI), a wa layer (for sharing
+ * commonalities with DWA-RC).
+ */
+struct wahc {
+ struct usb_device *usb_dev;
+ struct usb_interface *usb_iface;
+
+ /* HC to deliver notifications */
+ union {
+ struct wusbhc *wusb;
+ struct dwahc *dwa;
+ };
+
+ const struct usb_endpoint_descriptor *dto_epd, *dti_epd;
+ const struct usb_wa_descriptor *wa_descr;
+
+ struct urb *nep_urb; /* Notification EndPoint [lockless] */
+ struct edc nep_edc;
+ void *nep_buffer;
+ size_t nep_buffer_size;
+
+ atomic_t notifs_queued;
+
+ u16 rpipes;
+ unsigned long *rpipe_bm; /* rpipe usage bitmap */
+ struct list_head rpipe_delayed_list; /* delayed RPIPES. */
+ spinlock_t rpipe_lock; /* protect rpipe_bm and delayed list */
+ struct mutex rpipe_mutex; /* assigning resources to endpoints */
+
+ /*
+ * dti_state is used to track the state of the dti_urb. When dti_state
+ * is WA_DTI_ISOC_PACKET_STATUS_PENDING, dti_isoc_xfer_in_progress and
+ * dti_isoc_xfer_seg identify which xfer the incoming isoc packet
+ * status refers to.
+ */
+ enum wa_dti_state dti_state;
+ u32 dti_isoc_xfer_in_progress;
+ u8 dti_isoc_xfer_seg;
+ struct urb *dti_urb; /* URB for reading xfer results */
+ /* URBs for reading data in */
+ struct urb buf_in_urbs[WA_MAX_BUF_IN_URBS];
+ int active_buf_in_urbs; /* number of buf_in_urbs active. */
+ struct edc dti_edc; /* DTI error density counter */
+ void *dti_buf;
+ size_t dti_buf_size;
+
+ unsigned long dto_in_use; /* protect dto endoint serialization */
+
+ s32 status; /* For reading status */
+
+ struct list_head xfer_list;
+ struct list_head xfer_delayed_list;
+ struct list_head xfer_errored_list;
+ /*
+ * lock for the above xfer lists. Can be taken while a xfer->lock is
+ * held but not in the reverse order.
+ */
+ spinlock_t xfer_list_lock;
+ struct work_struct xfer_enqueue_work;
+ struct work_struct xfer_error_work;
+ atomic_t xfer_id_count;
+
+ kernel_ulong_t quirks;
+};
+
+
+extern int wa_create(struct wahc *wa, struct usb_interface *iface,
+ kernel_ulong_t);
+extern void __wa_destroy(struct wahc *wa);
+extern int wa_dti_start(struct wahc *wa);
+void wa_reset_all(struct wahc *wa);
+
+
+/* Miscellaneous constants */
+enum {
+ /** Max number of EPROTO errors we tolerate on the NEP in a
+ * period of time */
+ HWAHC_EPROTO_MAX = 16,
+ /** Period of time for EPROTO errors (in jiffies) */
+ HWAHC_EPROTO_PERIOD = 4 * HZ,
+};
+
+
+/* Notification endpoint handling */
+extern int wa_nep_create(struct wahc *, struct usb_interface *);
+extern void wa_nep_destroy(struct wahc *);
+
+static inline int wa_nep_arm(struct wahc *wa, gfp_t gfp_mask)
+{
+ struct urb *urb = wa->nep_urb;
+ urb->transfer_buffer = wa->nep_buffer;
+ urb->transfer_buffer_length = wa->nep_buffer_size;
+ return usb_submit_urb(urb, gfp_mask);
+}
+
+static inline void wa_nep_disarm(struct wahc *wa)
+{
+ usb_kill_urb(wa->nep_urb);
+}
+
+
+/* RPipes */
+static inline void wa_rpipe_init(struct wahc *wa)
+{
+ INIT_LIST_HEAD(&wa->rpipe_delayed_list);
+ spin_lock_init(&wa->rpipe_lock);
+ mutex_init(&wa->rpipe_mutex);
+}
+
+static inline void wa_init(struct wahc *wa)
+{
+ int index;
+
+ edc_init(&wa->nep_edc);
+ atomic_set(&wa->notifs_queued, 0);
+ wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING;
+ wa_rpipe_init(wa);
+ edc_init(&wa->dti_edc);
+ INIT_LIST_HEAD(&wa->xfer_list);
+ INIT_LIST_HEAD(&wa->xfer_delayed_list);
+ INIT_LIST_HEAD(&wa->xfer_errored_list);
+ spin_lock_init(&wa->xfer_list_lock);
+ INIT_WORK(&wa->xfer_enqueue_work, wa_urb_enqueue_run);
+ INIT_WORK(&wa->xfer_error_work, wa_process_errored_transfers_run);
+ wa->dto_in_use = 0;
+ atomic_set(&wa->xfer_id_count, 1);
+ /* init the buf in URBs */
+ for (index = 0; index < WA_MAX_BUF_IN_URBS; ++index)
+ usb_init_urb(&(wa->buf_in_urbs[index]));
+ wa->active_buf_in_urbs = 0;
+}
+
+/**
+ * Destroy a pipe (when refcount drops to zero)
+ *
+ * Assumes it has been moved to the "QUIESCING" state.
+ */
+struct wa_xfer;
+extern void rpipe_destroy(struct kref *_rpipe);
+static inline
+void __rpipe_get(struct wa_rpipe *rpipe)
+{
+ kref_get(&rpipe->refcnt);
+}
+extern int rpipe_get_by_ep(struct wahc *, struct usb_host_endpoint *,
+ struct urb *, gfp_t);
+static inline void rpipe_put(struct wa_rpipe *rpipe)
+{
+ kref_put(&rpipe->refcnt, rpipe_destroy);
+
+}
+extern void rpipe_ep_disable(struct wahc *, struct usb_host_endpoint *);
+extern void rpipe_clear_feature_stalled(struct wahc *,
+ struct usb_host_endpoint *);
+extern int wa_rpipes_create(struct wahc *);
+extern void wa_rpipes_destroy(struct wahc *);
+static inline void rpipe_avail_dec(struct wa_rpipe *rpipe)
+{
+ atomic_dec(&rpipe->segs_available);
+}
+
+/**
+ * Returns true if the rpipe is ready to submit more segments.
+ */
+static inline int rpipe_avail_inc(struct wa_rpipe *rpipe)
+{
+ return atomic_inc_return(&rpipe->segs_available) > 0
+ && !list_empty(&rpipe->seg_list);
+}
+
+
+/* Transferring data */
+extern int wa_urb_enqueue(struct wahc *, struct usb_host_endpoint *,
+ struct urb *, gfp_t);
+extern int wa_urb_dequeue(struct wahc *, struct urb *, int);
+extern void wa_handle_notif_xfer(struct wahc *, struct wa_notif_hdr *);
+
+
+/* Misc
+ *
+ * FIXME: Refcounting for the actual @hwahc object is not correct; I
+ * mean, this should be refcounting on the HCD underneath, but
+ * it is not. In any case, the semantics for HCD refcounting
+ * are *weird*...on refcount reaching zero it just frees
+ * it...no RC specific function is called...unless I miss
+ * something.
+ *
+ * FIXME: has to go away in favour of a 'struct' hcd based solution
+ */
+static inline struct wahc *wa_get(struct wahc *wa)
+{
+ usb_get_intf(wa->usb_iface);
+ return wa;
+}
+
+static inline void wa_put(struct wahc *wa)
+{
+ usb_put_intf(wa->usb_iface);
+}
+
+
+static inline int __wa_feature(struct wahc *wa, unsigned op, u16 feature)
+{
+ return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ op ? USB_REQ_SET_FEATURE : USB_REQ_CLEAR_FEATURE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ feature,
+ wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+
+static inline int __wa_set_feature(struct wahc *wa, u16 feature)
+{
+ return __wa_feature(wa, 1, feature);
+}
+
+
+static inline int __wa_clear_feature(struct wahc *wa, u16 feature)
+{
+ return __wa_feature(wa, 0, feature);
+}
+
+
+/**
+ * Return the status of a Wire Adapter
+ *
+ * @wa: Wire Adapter instance
+ * @returns < 0 errno code on error, or status bitmap as described
+ * in WUSB1.0[8.3.1.6].
+ *
+ * NOTE: need malloc, some arches don't take USB from the stack
+ */
+static inline
+s32 __wa_get_status(struct wahc *wa)
+{
+ s32 result;
+ result = usb_control_msg(
+ wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0),
+ USB_REQ_GET_STATUS,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ &wa->status, sizeof(wa->status), USB_CTRL_GET_TIMEOUT);
+ if (result >= 0)
+ result = wa->status;
+ return result;
+}
+
+
+/**
+ * Waits until the Wire Adapter's status matches @mask/@value
+ *
+ * @wa: Wire Adapter instance.
+ * @returns < 0 errno code on error, otherwise status.
+ *
+ * Loop until the WAs status matches the mask and value (status & mask
+ * == value). Timeout if it doesn't happen.
+ *
+ * FIXME: is there an official specification on how long status
+ * changes can take?
+ */
+static inline s32 __wa_wait_status(struct wahc *wa, u32 mask, u32 value)
+{
+ s32 result;
+ unsigned loops = 10;
+ do {
+ msleep(50);
+ result = __wa_get_status(wa);
+ if ((result & mask) == value)
+ break;
+ if (loops-- == 0) {
+ result = -ETIMEDOUT;
+ break;
+ }
+ } while (result >= 0);
+ return result;
+}
+
+
+/** Command @hwahc to stop, @returns 0 if ok, < 0 errno code on error */
+static inline int __wa_stop(struct wahc *wa)
+{
+ int result;
+ struct device *dev = &wa->usb_iface->dev;
+
+ result = __wa_clear_feature(wa, WA_ENABLE);
+ if (result < 0 && result != -ENODEV) {
+ dev_err(dev, "error commanding HC to stop: %d\n", result);
+ goto out;
+ }
+ result = __wa_wait_status(wa, WA_ENABLE, 0);
+ if (result < 0 && result != -ENODEV)
+ dev_err(dev, "error waiting for HC to stop: %d\n", result);
+out:
+ return 0;
+}
+
+
+#endif /* #ifndef __HWAHC_INTERNAL_H__ */
diff --git a/drivers/staging/wusbcore/wa-nep.c b/drivers/staging/wusbcore/wa-nep.c
new file mode 100644
index 000000000000..5f0656db5482
--- /dev/null
+++ b/drivers/staging/wusbcore/wa-nep.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8])
+ * Notification EndPoint support
+ *
+ * Copyright (C) 2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This part takes care of getting the notification from the hw
+ * only and dispatching through wusbwad into
+ * wa_notif_dispatch. Handling is done there.
+ *
+ * WA notifications are limited in size; most of them are three or
+ * four bytes long, and the longest is the HWA Device Notification,
+ * which would not exceed 38 bytes (DNs are limited in payload to 32
+ * bytes plus 3 bytes header (WUSB1.0[7.6p2]), plus 3 bytes HWA
+ * header (WUSB1.0[8.5.4.2]).
+ *
+ * It is not clear if more than one Device Notification can be packed
+ * in a HWA Notification, I assume no because of the wording in
+ * WUSB1.0[8.5.4.2]. In any case, the bigger any notification could
+ * get is 256 bytes (as the bLength field is a byte).
+ *
+ * So what we do is we have this buffer and read into it; when a
+ * notification arrives we schedule work to a specific, single thread
+ * workqueue (so notifications are serialized) and copy the
+ * notification data. After scheduling the work, we rearm the read from
+ * the notification endpoint.
+ *
+ * Entry points here are:
+ *
+ * wa_nep_[create|destroy]() To initialize/release this subsystem
+ *
+ * wa_nep_cb() Callback for the notification
+ * endpoint; when data is ready, this
+ * does the dispatching.
+ */
+#include <linux/workqueue.h>
+#include <linux/ctype.h>
+#include <linux/slab.h>
+
+#include "wa-hc.h"
+#include "wusbhc.h"
+
+/* Structure for queueing notifications to the workqueue */
+struct wa_notif_work {
+ struct work_struct work;
+ struct wahc *wa;
+ size_t size;
+ u8 data[];
+};
+
+/*
+ * Process incoming notifications from the WA's Notification EndPoint
+ * [the wuswad daemon, basically]
+ *
+ * @_nw: Pointer to a descriptor which has the pointer to the
+ * @wa, the size of the buffer and the work queue
+ * structure (so we can free all when done).
+ * @returns 0 if ok, < 0 errno code on error.
+ *
+ * All notifications follow the same format; they need to start with a
+ * 'struct wa_notif_hdr' header, so it is easy to parse through
+ * them. We just break the buffer in individual notifications (the
+ * standard doesn't say if it can be done or is forbidden, so we are
+ * cautious) and dispatch each.
+ *
+ * So the handling layers are is:
+ *
+ * WA specific notification (from NEP)
+ * Device Notification Received -> wa_handle_notif_dn()
+ * WUSB Device notification generic handling
+ * BPST Adjustment -> wa_handle_notif_bpst_adj()
+ * ... -> ...
+ *
+ * @wa has to be referenced
+ */
+static void wa_notif_dispatch(struct work_struct *ws)
+{
+ void *itr;
+ u8 missing = 0;
+ struct wa_notif_work *nw = container_of(ws, struct wa_notif_work,
+ work);
+ struct wahc *wa = nw->wa;
+ struct wa_notif_hdr *notif_hdr;
+ size_t size;
+
+ struct device *dev = &wa->usb_iface->dev;
+
+#if 0
+ /* FIXME: need to check for this??? */
+ if (usb_hcd->state == HC_STATE_QUIESCING) /* Going down? */
+ goto out; /* screw it */
+#endif
+ atomic_dec(&wa->notifs_queued); /* Throttling ctl */
+ size = nw->size;
+ itr = nw->data;
+
+ while (size) {
+ if (size < sizeof(*notif_hdr)) {
+ missing = sizeof(*notif_hdr) - size;
+ goto exhausted_buffer;
+ }
+ notif_hdr = itr;
+ if (size < notif_hdr->bLength)
+ goto exhausted_buffer;
+ itr += notif_hdr->bLength;
+ size -= notif_hdr->bLength;
+ /* Dispatch the notification [don't use itr or size!] */
+ switch (notif_hdr->bNotifyType) {
+ case HWA_NOTIF_DN: {
+ struct hwa_notif_dn *hwa_dn;
+ hwa_dn = container_of(notif_hdr, struct hwa_notif_dn,
+ hdr);
+ wusbhc_handle_dn(wa->wusb, hwa_dn->bSourceDeviceAddr,
+ hwa_dn->dndata,
+ notif_hdr->bLength - sizeof(*hwa_dn));
+ break;
+ }
+ case WA_NOTIF_TRANSFER:
+ wa_handle_notif_xfer(wa, notif_hdr);
+ break;
+ case HWA_NOTIF_BPST_ADJ:
+ break; /* no action needed for BPST ADJ. */
+ case DWA_NOTIF_RWAKE:
+ case DWA_NOTIF_PORTSTATUS:
+ /* FIXME: unimplemented WA NOTIFs */
+ /* fallthru */
+ default:
+ dev_err(dev, "HWA: unknown notification 0x%x, "
+ "%zu bytes; discarding\n",
+ notif_hdr->bNotifyType,
+ (size_t)notif_hdr->bLength);
+ break;
+ }
+ }
+out:
+ wa_put(wa);
+ kfree(nw);
+ return;
+
+ /* THIS SHOULD NOT HAPPEN
+ *
+ * Buffer exahusted with partial data remaining; just warn and
+ * discard the data, as this should not happen.
+ */
+exhausted_buffer:
+ dev_warn(dev, "HWA: device sent short notification, "
+ "%d bytes missing; discarding %d bytes.\n",
+ missing, (int)size);
+ goto out;
+}
+
+/*
+ * Deliver incoming WA notifications to the wusbwa workqueue
+ *
+ * @wa: Pointer the Wire Adapter Controller Data Streaming
+ * instance (part of an 'struct usb_hcd').
+ * @size: Size of the received buffer
+ * @returns 0 if ok, < 0 errno code on error.
+ *
+ * The input buffer is @wa->nep_buffer, with @size bytes
+ * (guaranteed to fit in the allocated space,
+ * @wa->nep_buffer_size).
+ */
+static int wa_nep_queue(struct wahc *wa, size_t size)
+{
+ int result = 0;
+ struct device *dev = &wa->usb_iface->dev;
+ struct wa_notif_work *nw;
+
+ /* dev_fnstart(dev, "(wa %p, size %zu)\n", wa, size); */
+ BUG_ON(size > wa->nep_buffer_size);
+ if (size == 0)
+ goto out;
+ if (atomic_read(&wa->notifs_queued) > 200) {
+ if (printk_ratelimit())
+ dev_err(dev, "Too many notifications queued, "
+ "throttling back\n");
+ goto out;
+ }
+ nw = kzalloc(sizeof(*nw) + size, GFP_ATOMIC);
+ if (nw == NULL) {
+ if (printk_ratelimit())
+ dev_err(dev, "No memory to queue notification\n");
+ result = -ENOMEM;
+ goto out;
+ }
+ INIT_WORK(&nw->work, wa_notif_dispatch);
+ nw->wa = wa_get(wa);
+ nw->size = size;
+ memcpy(nw->data, wa->nep_buffer, size);
+ atomic_inc(&wa->notifs_queued); /* Throttling ctl */
+ queue_work(wusbd, &nw->work);
+out:
+ /* dev_fnend(dev, "(wa %p, size %zu) = result\n", wa, size, result); */
+ return result;
+}
+
+/*
+ * Callback for the notification event endpoint
+ *
+ * Check's that everything is fine and then passes the data to be
+ * queued to the workqueue.
+ */
+static void wa_nep_cb(struct urb *urb)
+{
+ int result;
+ struct wahc *wa = urb->context;
+ struct device *dev = &wa->usb_iface->dev;
+
+ switch (result = urb->status) {
+ case 0:
+ result = wa_nep_queue(wa, urb->actual_length);
+ if (result < 0)
+ dev_err(dev, "NEP: unable to process notification(s): "
+ "%d\n", result);
+ break;
+ case -ECONNRESET: /* Not an error, but a controlled situation; */
+ case -ENOENT: /* (we killed the URB)...so, no broadcast */
+ case -ESHUTDOWN:
+ dev_dbg(dev, "NEP: going down %d\n", urb->status);
+ goto out;
+ default: /* On general errors, we retry unless it gets ugly */
+ if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)) {
+ dev_err(dev, "NEP: URB max acceptable errors "
+ "exceeded, resetting device\n");
+ wa_reset_all(wa);
+ goto out;
+ }
+ dev_err(dev, "NEP: URB error %d\n", urb->status);
+ }
+ result = wa_nep_arm(wa, GFP_ATOMIC);
+ if (result < 0) {
+ dev_err(dev, "NEP: cannot submit URB: %d\n", result);
+ wa_reset_all(wa);
+ }
+out:
+ return;
+}
+
+/*
+ * Initialize @wa's notification and event's endpoint stuff
+ *
+ * This includes the allocating the read buffer, the context ID
+ * allocation bitmap, the URB and submitting the URB.
+ */
+int wa_nep_create(struct wahc *wa, struct usb_interface *iface)
+{
+ int result;
+ struct usb_endpoint_descriptor *epd;
+ struct usb_device *usb_dev = interface_to_usbdev(iface);
+ struct device *dev = &iface->dev;
+
+ edc_init(&wa->nep_edc);
+ epd = &iface->cur_altsetting->endpoint[0].desc;
+ wa->nep_buffer_size = 1024;
+ wa->nep_buffer = kmalloc(wa->nep_buffer_size, GFP_KERNEL);
+ if (!wa->nep_buffer)
+ goto error_nep_buffer;
+ wa->nep_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (wa->nep_urb == NULL)
+ goto error_urb_alloc;
+ usb_fill_int_urb(wa->nep_urb, usb_dev,
+ usb_rcvintpipe(usb_dev, epd->bEndpointAddress),
+ wa->nep_buffer, wa->nep_buffer_size,
+ wa_nep_cb, wa, epd->bInterval);
+ result = wa_nep_arm(wa, GFP_KERNEL);
+ if (result < 0) {
+ dev_err(dev, "Cannot submit notification URB: %d\n", result);
+ goto error_nep_arm;
+ }
+ return 0;
+
+error_nep_arm:
+ usb_free_urb(wa->nep_urb);
+error_urb_alloc:
+ kfree(wa->nep_buffer);
+error_nep_buffer:
+ return -ENOMEM;
+}
+
+void wa_nep_destroy(struct wahc *wa)
+{
+ wa_nep_disarm(wa);
+ usb_free_urb(wa->nep_urb);
+ kfree(wa->nep_buffer);
+}
diff --git a/drivers/staging/wusbcore/wa-rpipe.c b/drivers/staging/wusbcore/wa-rpipe.c
new file mode 100644
index 000000000000..a5734cbcd5ad
--- /dev/null
+++ b/drivers/staging/wusbcore/wa-rpipe.c
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WUSB Wire Adapter
+ * rpipe management
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * FIXME: docs
+ *
+ * RPIPE
+ *
+ * Targeted at different downstream endpoints
+ *
+ * Descriptor: use to config the remote pipe.
+ *
+ * The number of blocks could be dynamic (wBlocks in descriptor is
+ * 0)--need to schedule them then.
+ *
+ * Each bit in wa->rpipe_bm represents if an rpipe is being used or
+ * not. Rpipes are represented with a 'struct wa_rpipe' that is
+ * attached to the hcpriv member of a 'struct usb_host_endpoint'.
+ *
+ * When you need to xfer data to an endpoint, you get an rpipe for it
+ * with wa_ep_rpipe_get(), which gives you a reference to the rpipe
+ * and keeps a single one (the first one) with the endpoint. When you
+ * are done transferring, you drop that reference. At the end the
+ * rpipe is always allocated and bound to the endpoint. There it might
+ * be recycled when not used.
+ *
+ * Addresses:
+ *
+ * We use a 1:1 mapping mechanism between port address (0 based
+ * index, actually) and the address. The USB stack knows about this.
+ *
+ * USB Stack port number 4 (1 based)
+ * WUSB code port index 3 (0 based)
+ * USB Address 5 (2 based -- 0 is for default, 1 for root hub)
+ *
+ * Now, because we don't use the concept as default address exactly
+ * like the (wired) USB code does, we need to kind of skip it. So we
+ * never take addresses from the urb->pipe, but from the
+ * urb->dev->devnum, to make sure that we always have the right
+ * destination address.
+ */
+#include <linux/atomic.h>
+#include <linux/bitmap.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+
+#include "wusbhc.h"
+#include "wa-hc.h"
+
+static int __rpipe_get_descr(struct wahc *wa,
+ struct usb_rpipe_descriptor *descr, u16 index)
+{
+ ssize_t result;
+ struct device *dev = &wa->usb_iface->dev;
+
+ /* Get the RPIPE descriptor -- we cannot use the usb_get_descriptor()
+ * function because the arguments are different.
+ */
+ result = usb_control_msg(
+ wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0),
+ USB_REQ_GET_DESCRIPTOR,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE,
+ USB_DT_RPIPE<<8, index, descr, sizeof(*descr),
+ USB_CTRL_GET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "rpipe %u: get descriptor failed: %d\n",
+ index, (int)result);
+ goto error;
+ }
+ if (result < sizeof(*descr)) {
+ dev_err(dev, "rpipe %u: got short descriptor "
+ "(%zd vs %zd bytes needed)\n",
+ index, result, sizeof(*descr));
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ return result;
+}
+
+/*
+ *
+ * The descriptor is assumed to be properly initialized (ie: you got
+ * it through __rpipe_get_descr()).
+ */
+static int __rpipe_set_descr(struct wahc *wa,
+ struct usb_rpipe_descriptor *descr, u16 index)
+{
+ ssize_t result;
+ struct device *dev = &wa->usb_iface->dev;
+
+ /* we cannot use the usb_get_descriptor() function because the
+ * arguments are different.
+ */
+ result = usb_control_msg(
+ wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ USB_REQ_SET_DESCRIPTOR,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE,
+ USB_DT_RPIPE<<8, index, descr, sizeof(*descr),
+ USB_CTRL_SET_TIMEOUT);
+ if (result < 0) {
+ dev_err(dev, "rpipe %u: set descriptor failed: %d\n",
+ index, (int)result);
+ goto error;
+ }
+ if (result < sizeof(*descr)) {
+ dev_err(dev, "rpipe %u: sent short descriptor "
+ "(%zd vs %zd bytes required)\n",
+ index, result, sizeof(*descr));
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ return result;
+
+}
+
+static void rpipe_init(struct wa_rpipe *rpipe)
+{
+ kref_init(&rpipe->refcnt);
+ spin_lock_init(&rpipe->seg_lock);
+ INIT_LIST_HEAD(&rpipe->seg_list);
+ INIT_LIST_HEAD(&rpipe->list_node);
+}
+
+static unsigned rpipe_get_idx(struct wahc *wa, unsigned rpipe_idx)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&wa->rpipe_lock, flags);
+ rpipe_idx = find_next_zero_bit(wa->rpipe_bm, wa->rpipes, rpipe_idx);
+ if (rpipe_idx < wa->rpipes)
+ set_bit(rpipe_idx, wa->rpipe_bm);
+ spin_unlock_irqrestore(&wa->rpipe_lock, flags);
+
+ return rpipe_idx;
+}
+
+static void rpipe_put_idx(struct wahc *wa, unsigned rpipe_idx)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&wa->rpipe_lock, flags);
+ clear_bit(rpipe_idx, wa->rpipe_bm);
+ spin_unlock_irqrestore(&wa->rpipe_lock, flags);
+}
+
+void rpipe_destroy(struct kref *_rpipe)
+{
+ struct wa_rpipe *rpipe = container_of(_rpipe, struct wa_rpipe, refcnt);
+ u8 index = le16_to_cpu(rpipe->descr.wRPipeIndex);
+
+ if (rpipe->ep)
+ rpipe->ep->hcpriv = NULL;
+ rpipe_put_idx(rpipe->wa, index);
+ wa_put(rpipe->wa);
+ kfree(rpipe);
+}
+EXPORT_SYMBOL_GPL(rpipe_destroy);
+
+/*
+ * Locate an idle rpipe, create an structure for it and return it
+ *
+ * @wa is referenced and unlocked
+ * @crs enum rpipe_attr, required endpoint characteristics
+ *
+ * The rpipe can be used only sequentially (not in parallel).
+ *
+ * The rpipe is moved into the "ready" state.
+ */
+static int rpipe_get_idle(struct wa_rpipe **prpipe, struct wahc *wa, u8 crs,
+ gfp_t gfp)
+{
+ int result;
+ unsigned rpipe_idx;
+ struct wa_rpipe *rpipe;
+ struct device *dev = &wa->usb_iface->dev;
+
+ rpipe = kzalloc(sizeof(*rpipe), gfp);
+ if (rpipe == NULL)
+ return -ENOMEM;
+ rpipe_init(rpipe);
+
+ /* Look for an idle pipe */
+ for (rpipe_idx = 0; rpipe_idx < wa->rpipes; rpipe_idx++) {
+ rpipe_idx = rpipe_get_idx(wa, rpipe_idx);
+ if (rpipe_idx >= wa->rpipes) /* no more pipes :( */
+ break;
+ result = __rpipe_get_descr(wa, &rpipe->descr, rpipe_idx);
+ if (result < 0)
+ dev_err(dev, "Can't get descriptor for rpipe %u: %d\n",
+ rpipe_idx, result);
+ else if ((rpipe->descr.bmCharacteristics & crs) != 0)
+ goto found;
+ rpipe_put_idx(wa, rpipe_idx);
+ }
+ *prpipe = NULL;
+ kfree(rpipe);
+ return -ENXIO;
+
+found:
+ set_bit(rpipe_idx, wa->rpipe_bm);
+ rpipe->wa = wa_get(wa);
+ *prpipe = rpipe;
+ return 0;
+}
+
+static int __rpipe_reset(struct wahc *wa, unsigned index)
+{
+ int result;
+ struct device *dev = &wa->usb_iface->dev;
+
+ result = usb_control_msg(
+ wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ USB_REQ_RPIPE_RESET,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE,
+ 0, index, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (result < 0)
+ dev_err(dev, "rpipe %u: reset failed: %d\n",
+ index, result);
+ return result;
+}
+
+/*
+ * Fake companion descriptor for ep0
+ *
+ * See WUSB1.0[7.4.4], most of this is zero for bulk/int/ctl
+ */
+static struct usb_wireless_ep_comp_descriptor epc0 = {
+ .bLength = sizeof(epc0),
+ .bDescriptorType = USB_DT_WIRELESS_ENDPOINT_COMP,
+ .bMaxBurst = 1,
+ .bMaxSequence = 2,
+};
+
+/*
+ * Look for EP companion descriptor
+ *
+ * Get there, look for Inara in the endpoint's extra descriptors
+ */
+static struct usb_wireless_ep_comp_descriptor *rpipe_epc_find(
+ struct device *dev, struct usb_host_endpoint *ep)
+{
+ void *itr;
+ size_t itr_size;
+ struct usb_descriptor_header *hdr;
+ struct usb_wireless_ep_comp_descriptor *epcd;
+
+ if (ep->desc.bEndpointAddress == 0) {
+ epcd = &epc0;
+ goto out;
+ }
+ itr = ep->extra;
+ itr_size = ep->extralen;
+ epcd = NULL;
+ while (itr_size > 0) {
+ if (itr_size < sizeof(*hdr)) {
+ dev_err(dev, "HW Bug? ep 0x%02x: extra descriptors "
+ "at offset %zu: only %zu bytes left\n",
+ ep->desc.bEndpointAddress,
+ itr - (void *) ep->extra, itr_size);
+ break;
+ }
+ hdr = itr;
+ if (hdr->bDescriptorType == USB_DT_WIRELESS_ENDPOINT_COMP) {
+ epcd = itr;
+ break;
+ }
+ if (hdr->bLength > itr_size) {
+ dev_err(dev, "HW Bug? ep 0x%02x: extra descriptor "
+ "at offset %zu (type 0x%02x) "
+ "length %d but only %zu bytes left\n",
+ ep->desc.bEndpointAddress,
+ itr - (void *) ep->extra, hdr->bDescriptorType,
+ hdr->bLength, itr_size);
+ break;
+ }
+ itr += hdr->bLength;
+ itr_size -= hdr->bLength;
+ }
+out:
+ return epcd;
+}
+
+/*
+ * Aim an rpipe to its device & endpoint destination
+ *
+ * Make sure we change the address to unauthenticated if the device
+ * is WUSB and it is not authenticated.
+ */
+static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa,
+ struct usb_host_endpoint *ep, struct urb *urb, gfp_t gfp)
+{
+ int result = -ENOMSG; /* better code for lack of companion? */
+ struct device *dev = &wa->usb_iface->dev;
+ struct usb_device *usb_dev = urb->dev;
+ struct usb_wireless_ep_comp_descriptor *epcd;
+ u32 ack_window, epcd_max_sequence;
+ u8 unauth;
+
+ epcd = rpipe_epc_find(dev, ep);
+ if (epcd == NULL) {
+ dev_err(dev, "ep 0x%02x: can't find companion descriptor\n",
+ ep->desc.bEndpointAddress);
+ goto error;
+ }
+ unauth = usb_dev->wusb && !usb_dev->authenticated ? 0x80 : 0;
+ __rpipe_reset(wa, le16_to_cpu(rpipe->descr.wRPipeIndex));
+ atomic_set(&rpipe->segs_available,
+ le16_to_cpu(rpipe->descr.wRequests));
+ /* FIXME: block allocation system; request with queuing and timeout */
+ /* FIXME: compute so seg_size > ep->maxpktsize */
+ rpipe->descr.wBlocks = cpu_to_le16(16); /* given */
+ /* ep0 maxpktsize is 0x200 (WUSB1.0[4.8.1]) */
+ if (usb_endpoint_xfer_isoc(&ep->desc))
+ rpipe->descr.wMaxPacketSize = epcd->wOverTheAirPacketSize;
+ else
+ rpipe->descr.wMaxPacketSize = ep->desc.wMaxPacketSize;
+
+ rpipe->descr.hwa_bMaxBurst = max(min_t(unsigned int,
+ epcd->bMaxBurst, 16U), 1U);
+ rpipe->descr.hwa_bDeviceInfoIndex =
+ wusb_port_no_to_idx(urb->dev->portnum);
+ /* FIXME: use maximum speed as supported or recommended by device */
+ rpipe->descr.bSpeed = usb_pipeendpoint(urb->pipe) == 0 ?
+ UWB_PHY_RATE_53 : UWB_PHY_RATE_200;
+
+ dev_dbg(dev, "addr %u (0x%02x) rpipe #%u ep# %u speed %d\n",
+ urb->dev->devnum, urb->dev->devnum | unauth,
+ le16_to_cpu(rpipe->descr.wRPipeIndex),
+ usb_pipeendpoint(urb->pipe), rpipe->descr.bSpeed);
+
+ rpipe->descr.hwa_reserved = 0;
+
+ rpipe->descr.bEndpointAddress = ep->desc.bEndpointAddress;
+ /* FIXME: bDataSequence */
+ rpipe->descr.bDataSequence = 0;
+
+ /* start with base window of hwa_bMaxBurst bits starting at 0. */
+ ack_window = 0xFFFFFFFF >> (32 - rpipe->descr.hwa_bMaxBurst);
+ rpipe->descr.dwCurrentWindow = cpu_to_le32(ack_window);
+ epcd_max_sequence = max(min_t(unsigned int,
+ epcd->bMaxSequence, 32U), 2U);
+ rpipe->descr.bMaxDataSequence = epcd_max_sequence - 1;
+ rpipe->descr.bInterval = ep->desc.bInterval;
+ if (usb_endpoint_xfer_isoc(&ep->desc))
+ rpipe->descr.bOverTheAirInterval = epcd->bOverTheAirInterval;
+ else
+ rpipe->descr.bOverTheAirInterval = 0; /* 0 if not isoc */
+ /* FIXME: xmit power & preamble blah blah */
+ rpipe->descr.bmAttribute = (ep->desc.bmAttributes &
+ USB_ENDPOINT_XFERTYPE_MASK);
+ /* rpipe->descr.bmCharacteristics RO */
+ rpipe->descr.bmRetryOptions = (wa->wusb->retry_count & 0xF);
+ /* FIXME: use for assessing link quality? */
+ rpipe->descr.wNumTransactionErrors = 0;
+ result = __rpipe_set_descr(wa, &rpipe->descr,
+ le16_to_cpu(rpipe->descr.wRPipeIndex));
+ if (result < 0) {
+ dev_err(dev, "Cannot aim rpipe: %d\n", result);
+ goto error;
+ }
+ result = 0;
+error:
+ return result;
+}
+
+/*
+ * Check an aimed rpipe to make sure it points to where we want
+ *
+ * We use bit 19 of the Linux USB pipe bitmap for unauth vs auth
+ * space; when it is like that, we or 0x80 to make an unauth address.
+ */
+static int rpipe_check_aim(const struct wa_rpipe *rpipe, const struct wahc *wa,
+ const struct usb_host_endpoint *ep,
+ const struct urb *urb, gfp_t gfp)
+{
+ int result = 0;
+ struct device *dev = &wa->usb_iface->dev;
+ u8 portnum = wusb_port_no_to_idx(urb->dev->portnum);
+
+#define AIM_CHECK(rdf, val, text) \
+ do { \
+ if (rpipe->descr.rdf != (val)) { \
+ dev_err(dev, \
+ "rpipe aim discrepancy: " #rdf " " text "\n", \
+ rpipe->descr.rdf, (val)); \
+ result = -EINVAL; \
+ WARN_ON(1); \
+ } \
+ } while (0)
+ AIM_CHECK(hwa_bDeviceInfoIndex, portnum, "(%u vs %u)");
+ AIM_CHECK(bSpeed, usb_pipeendpoint(urb->pipe) == 0 ?
+ UWB_PHY_RATE_53 : UWB_PHY_RATE_200,
+ "(%u vs %u)");
+ AIM_CHECK(bEndpointAddress, ep->desc.bEndpointAddress, "(%u vs %u)");
+ AIM_CHECK(bInterval, ep->desc.bInterval, "(%u vs %u)");
+ AIM_CHECK(bmAttribute, ep->desc.bmAttributes & 0x03, "(%u vs %u)");
+#undef AIM_CHECK
+ return result;
+}
+
+#ifndef CONFIG_BUG
+#define CONFIG_BUG 0
+#endif
+
+/*
+ * Make sure there is an rpipe allocated for an endpoint
+ *
+ * If already allocated, we just refcount it; if not, we get an
+ * idle one, aim it to the right location and take it.
+ *
+ * Attaches to ep->hcpriv and rpipe->ep to ep.
+ */
+int rpipe_get_by_ep(struct wahc *wa, struct usb_host_endpoint *ep,
+ struct urb *urb, gfp_t gfp)
+{
+ int result = 0;
+ struct device *dev = &wa->usb_iface->dev;
+ struct wa_rpipe *rpipe;
+ u8 eptype;
+
+ mutex_lock(&wa->rpipe_mutex);
+ rpipe = ep->hcpriv;
+ if (rpipe != NULL) {
+ if (CONFIG_BUG == 1) {
+ result = rpipe_check_aim(rpipe, wa, ep, urb, gfp);
+ if (result < 0)
+ goto error;
+ }
+ __rpipe_get(rpipe);
+ dev_dbg(dev, "ep 0x%02x: reusing rpipe %u\n",
+ ep->desc.bEndpointAddress,
+ le16_to_cpu(rpipe->descr.wRPipeIndex));
+ } else {
+ /* hmm, assign idle rpipe, aim it */
+ result = -ENOBUFS;
+ eptype = ep->desc.bmAttributes & 0x03;
+ result = rpipe_get_idle(&rpipe, wa, 1 << eptype, gfp);
+ if (result < 0)
+ goto error;
+ result = rpipe_aim(rpipe, wa, ep, urb, gfp);
+ if (result < 0) {
+ rpipe_put(rpipe);
+ goto error;
+ }
+ ep->hcpriv = rpipe;
+ rpipe->ep = ep;
+ __rpipe_get(rpipe); /* for caching into ep->hcpriv */
+ dev_dbg(dev, "ep 0x%02x: using rpipe %u\n",
+ ep->desc.bEndpointAddress,
+ le16_to_cpu(rpipe->descr.wRPipeIndex));
+ }
+error:
+ mutex_unlock(&wa->rpipe_mutex);
+ return result;
+}
+
+/*
+ * Allocate the bitmap for each rpipe.
+ */
+int wa_rpipes_create(struct wahc *wa)
+{
+ wa->rpipes = le16_to_cpu(wa->wa_descr->wNumRPipes);
+ wa->rpipe_bm = bitmap_zalloc(wa->rpipes, GFP_KERNEL);
+ if (wa->rpipe_bm == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+void wa_rpipes_destroy(struct wahc *wa)
+{
+ struct device *dev = &wa->usb_iface->dev;
+
+ if (!bitmap_empty(wa->rpipe_bm, wa->rpipes)) {
+ WARN_ON(1);
+ dev_err(dev, "BUG: pipes not released on exit: %*pb\n",
+ wa->rpipes, wa->rpipe_bm);
+ }
+ bitmap_free(wa->rpipe_bm);
+}
+
+/*
+ * Release resources allocated for an endpoint
+ *
+ * If there is an associated rpipe to this endpoint, Abort any pending
+ * transfers and put it. If the rpipe ends up being destroyed,
+ * __rpipe_destroy() will cleanup ep->hcpriv.
+ *
+ * This is called before calling hcd->stop(), so you don't need to do
+ * anything else in there.
+ */
+void rpipe_ep_disable(struct wahc *wa, struct usb_host_endpoint *ep)
+{
+ struct wa_rpipe *rpipe;
+
+ mutex_lock(&wa->rpipe_mutex);
+ rpipe = ep->hcpriv;
+ if (rpipe != NULL) {
+ u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex);
+
+ usb_control_msg(
+ wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ USB_REQ_RPIPE_ABORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE,
+ 0, index, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ rpipe_put(rpipe);
+ }
+ mutex_unlock(&wa->rpipe_mutex);
+}
+EXPORT_SYMBOL_GPL(rpipe_ep_disable);
+
+/* Clear the stalled status of an RPIPE. */
+void rpipe_clear_feature_stalled(struct wahc *wa, struct usb_host_endpoint *ep)
+{
+ struct wa_rpipe *rpipe;
+
+ mutex_lock(&wa->rpipe_mutex);
+ rpipe = ep->hcpriv;
+ if (rpipe != NULL) {
+ u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex);
+
+ usb_control_msg(
+ wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ USB_REQ_CLEAR_FEATURE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE,
+ RPIPE_STALL, index, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ }
+ mutex_unlock(&wa->rpipe_mutex);
+}
+EXPORT_SYMBOL_GPL(rpipe_clear_feature_stalled);
diff --git a/drivers/staging/wusbcore/wa-xfer.c b/drivers/staging/wusbcore/wa-xfer.c
new file mode 100644
index 000000000000..abf88cea37bb
--- /dev/null
+++ b/drivers/staging/wusbcore/wa-xfer.c
@@ -0,0 +1,2927 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WUSB Wire Adapter
+ * Data transfer and URB enqueing
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * How transfers work: get a buffer, break it up in segments (segment
+ * size is a multiple of the maxpacket size). For each segment issue a
+ * segment request (struct wa_xfer_*), then send the data buffer if
+ * out or nothing if in (all over the DTO endpoint).
+ *
+ * For each submitted segment request, a notification will come over
+ * the NEP endpoint and a transfer result (struct xfer_result) will
+ * arrive in the DTI URB. Read it, get the xfer ID, see if there is
+ * data coming (inbound transfer), schedule a read and handle it.
+ *
+ * Sounds simple, it is a pain to implement.
+ *
+ *
+ * ENTRY POINTS
+ *
+ * FIXME
+ *
+ * LIFE CYCLE / STATE DIAGRAM
+ *
+ * FIXME
+ *
+ * THIS CODE IS DISGUSTING
+ *
+ * Warned you are; it's my second try and still not happy with it.
+ *
+ * NOTES:
+ *
+ * - No iso
+ *
+ * - Supports DMA xfers, control, bulk and maybe interrupt
+ *
+ * - Does not recycle unused rpipes
+ *
+ * An rpipe is assigned to an endpoint the first time it is used,
+ * and then it's there, assigned, until the endpoint is disabled
+ * (destroyed [{h,d}wahc_op_ep_disable()]. The assignment of the
+ * rpipe to the endpoint is done under the wa->rpipe_sem semaphore
+ * (should be a mutex).
+ *
+ * Two methods it could be done:
+ *
+ * (a) set up a timer every time an rpipe's use count drops to 1
+ * (which means unused) or when a transfer ends. Reset the
+ * timer when a xfer is queued. If the timer expires, release
+ * the rpipe [see rpipe_ep_disable()].
+ *
+ * (b) when looking for free rpipes to attach [rpipe_get_by_ep()],
+ * when none are found go over the list, check their endpoint
+ * and their activity record (if no last-xfer-done-ts in the
+ * last x seconds) take it
+ *
+ * However, due to the fact that we have a set of limited
+ * resources (max-segments-at-the-same-time per xfer,
+ * xfers-per-ripe, blocks-per-rpipe, rpipes-per-host), at the end
+ * we are going to have to rebuild all this based on an scheduler,
+ * to where we have a list of transactions to do and based on the
+ * availability of the different required components (blocks,
+ * rpipes, segment slots, etc), we go scheduling them. Painful.
+ */
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/hash.h>
+#include <linux/ratelimit.h>
+#include <linux/export.h>
+#include <linux/scatterlist.h>
+
+#include "wa-hc.h"
+#include "wusbhc.h"
+
+enum {
+ /* [WUSB] section 8.3.3 allocates 7 bits for the segment index. */
+ WA_SEGS_MAX = 128,
+};
+
+enum wa_seg_status {
+ WA_SEG_NOTREADY,
+ WA_SEG_READY,
+ WA_SEG_DELAYED,
+ WA_SEG_SUBMITTED,
+ WA_SEG_PENDING,
+ WA_SEG_DTI_PENDING,
+ WA_SEG_DONE,
+ WA_SEG_ERROR,
+ WA_SEG_ABORTED,
+};
+
+static void wa_xfer_delayed_run(struct wa_rpipe *);
+static int __wa_xfer_delayed_run(struct wa_rpipe *rpipe, int *dto_waiting);
+
+/*
+ * Life cycle governed by 'struct urb' (the refcount of the struct is
+ * that of the 'struct urb' and usb_free_urb() would free the whole
+ * struct).
+ */
+struct wa_seg {
+ struct urb tr_urb; /* transfer request urb. */
+ struct urb *isoc_pack_desc_urb; /* for isoc packet descriptor. */
+ struct urb *dto_urb; /* for data output. */
+ struct list_head list_node; /* for rpipe->req_list */
+ struct wa_xfer *xfer; /* out xfer */
+ u8 index; /* which segment we are */
+ int isoc_frame_count; /* number of isoc frames in this segment. */
+ int isoc_frame_offset; /* starting frame offset in the xfer URB. */
+ /* Isoc frame that the current transfer buffer corresponds to. */
+ int isoc_frame_index;
+ int isoc_size; /* size of all isoc frames sent by this seg. */
+ enum wa_seg_status status;
+ ssize_t result; /* bytes xfered or error */
+ struct wa_xfer_hdr xfer_hdr;
+};
+
+static inline void wa_seg_init(struct wa_seg *seg)
+{
+ usb_init_urb(&seg->tr_urb);
+
+ /* set the remaining memory to 0. */
+ memset(((void *)seg) + sizeof(seg->tr_urb), 0,
+ sizeof(*seg) - sizeof(seg->tr_urb));
+}
+
+/*
+ * Protected by xfer->lock
+ *
+ */
+struct wa_xfer {
+ struct kref refcnt;
+ struct list_head list_node;
+ spinlock_t lock;
+ u32 id;
+
+ struct wahc *wa; /* Wire adapter we are plugged to */
+ struct usb_host_endpoint *ep;
+ struct urb *urb; /* URB we are transferring for */
+ struct wa_seg **seg; /* transfer segments */
+ u8 segs, segs_submitted, segs_done;
+ unsigned is_inbound:1;
+ unsigned is_dma:1;
+ size_t seg_size;
+ int result;
+
+ gfp_t gfp; /* allocation mask */
+
+ struct wusb_dev *wusb_dev; /* for activity timestamps */
+};
+
+static void __wa_populate_dto_urb_isoc(struct wa_xfer *xfer,
+ struct wa_seg *seg, int curr_iso_frame);
+static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer,
+ int starting_index, enum wa_seg_status status);
+
+static inline void wa_xfer_init(struct wa_xfer *xfer)
+{
+ kref_init(&xfer->refcnt);
+ INIT_LIST_HEAD(&xfer->list_node);
+ spin_lock_init(&xfer->lock);
+}
+
+/*
+ * Destroy a transfer structure
+ *
+ * Note that freeing xfer->seg[cnt]->tr_urb will free the containing
+ * xfer->seg[cnt] memory that was allocated by __wa_xfer_setup_segs.
+ */
+static void wa_xfer_destroy(struct kref *_xfer)
+{
+ struct wa_xfer *xfer = container_of(_xfer, struct wa_xfer, refcnt);
+ if (xfer->seg) {
+ unsigned cnt;
+ for (cnt = 0; cnt < xfer->segs; cnt++) {
+ struct wa_seg *seg = xfer->seg[cnt];
+ if (seg) {
+ usb_free_urb(seg->isoc_pack_desc_urb);
+ if (seg->dto_urb) {
+ kfree(seg->dto_urb->sg);
+ usb_free_urb(seg->dto_urb);
+ }
+ usb_free_urb(&seg->tr_urb);
+ }
+ }
+ kfree(xfer->seg);
+ }
+ kfree(xfer);
+}
+
+static void wa_xfer_get(struct wa_xfer *xfer)
+{
+ kref_get(&xfer->refcnt);
+}
+
+static void wa_xfer_put(struct wa_xfer *xfer)
+{
+ kref_put(&xfer->refcnt, wa_xfer_destroy);
+}
+
+/*
+ * Try to get exclusive access to the DTO endpoint resource. Return true
+ * if successful.
+ */
+static inline int __wa_dto_try_get(struct wahc *wa)
+{
+ return (test_and_set_bit(0, &wa->dto_in_use) == 0);
+}
+
+/* Release the DTO endpoint resource. */
+static inline void __wa_dto_put(struct wahc *wa)
+{
+ clear_bit_unlock(0, &wa->dto_in_use);
+}
+
+/* Service RPIPEs that are waiting on the DTO resource. */
+static void wa_check_for_delayed_rpipes(struct wahc *wa)
+{
+ unsigned long flags;
+ int dto_waiting = 0;
+ struct wa_rpipe *rpipe;
+
+ spin_lock_irqsave(&wa->rpipe_lock, flags);
+ while (!list_empty(&wa->rpipe_delayed_list) && !dto_waiting) {
+ rpipe = list_first_entry(&wa->rpipe_delayed_list,
+ struct wa_rpipe, list_node);
+ __wa_xfer_delayed_run(rpipe, &dto_waiting);
+ /* remove this RPIPE from the list if it is not waiting. */
+ if (!dto_waiting) {
+ pr_debug("%s: RPIPE %d serviced and removed from delayed list.\n",
+ __func__,
+ le16_to_cpu(rpipe->descr.wRPipeIndex));
+ list_del_init(&rpipe->list_node);
+ }
+ }
+ spin_unlock_irqrestore(&wa->rpipe_lock, flags);
+}
+
+/* add this RPIPE to the end of the delayed RPIPE list. */
+static void wa_add_delayed_rpipe(struct wahc *wa, struct wa_rpipe *rpipe)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&wa->rpipe_lock, flags);
+ /* add rpipe to the list if it is not already on it. */
+ if (list_empty(&rpipe->list_node)) {
+ pr_debug("%s: adding RPIPE %d to the delayed list.\n",
+ __func__, le16_to_cpu(rpipe->descr.wRPipeIndex));
+ list_add_tail(&rpipe->list_node, &wa->rpipe_delayed_list);
+ }
+ spin_unlock_irqrestore(&wa->rpipe_lock, flags);
+}
+
+/*
+ * xfer is referenced
+ *
+ * xfer->lock has to be unlocked
+ *
+ * We take xfer->lock for setting the result; this is a barrier
+ * against drivers/usb/core/hcd.c:unlink1() being called after we call
+ * usb_hcd_giveback_urb() and wa_urb_dequeue() trying to get a
+ * reference to the transfer.
+ */
+static void wa_xfer_giveback(struct wa_xfer *xfer)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&xfer->wa->xfer_list_lock, flags);
+ list_del_init(&xfer->list_node);
+ usb_hcd_unlink_urb_from_ep(&(xfer->wa->wusb->usb_hcd), xfer->urb);
+ spin_unlock_irqrestore(&xfer->wa->xfer_list_lock, flags);
+ /* FIXME: segmentation broken -- kills DWA */
+ wusbhc_giveback_urb(xfer->wa->wusb, xfer->urb, xfer->result);
+ wa_put(xfer->wa);
+ wa_xfer_put(xfer);
+}
+
+/*
+ * xfer is referenced
+ *
+ * xfer->lock has to be unlocked
+ */
+static void wa_xfer_completion(struct wa_xfer *xfer)
+{
+ if (xfer->wusb_dev)
+ wusb_dev_put(xfer->wusb_dev);
+ rpipe_put(xfer->ep->hcpriv);
+ wa_xfer_giveback(xfer);
+}
+
+/*
+ * Initialize a transfer's ID
+ *
+ * We need to use a sequential number; if we use the pointer or the
+ * hash of the pointer, it can repeat over sequential transfers and
+ * then it will confuse the HWA....wonder why in hell they put a 32
+ * bit handle in there then.
+ */
+static void wa_xfer_id_init(struct wa_xfer *xfer)
+{
+ xfer->id = atomic_add_return(1, &xfer->wa->xfer_id_count);
+}
+
+/* Return the xfer's ID. */
+static inline u32 wa_xfer_id(struct wa_xfer *xfer)
+{
+ return xfer->id;
+}
+
+/* Return the xfer's ID in transport format (little endian). */
+static inline __le32 wa_xfer_id_le32(struct wa_xfer *xfer)
+{
+ return cpu_to_le32(xfer->id);
+}
+
+/*
+ * If transfer is done, wrap it up and return true
+ *
+ * xfer->lock has to be locked
+ */
+static unsigned __wa_xfer_is_done(struct wa_xfer *xfer)
+{
+ struct device *dev = &xfer->wa->usb_iface->dev;
+ unsigned result, cnt;
+ struct wa_seg *seg;
+ struct urb *urb = xfer->urb;
+ unsigned found_short = 0;
+
+ result = xfer->segs_done == xfer->segs_submitted;
+ if (result == 0)
+ goto out;
+ urb->actual_length = 0;
+ for (cnt = 0; cnt < xfer->segs; cnt++) {
+ seg = xfer->seg[cnt];
+ switch (seg->status) {
+ case WA_SEG_DONE:
+ if (found_short && seg->result > 0) {
+ dev_dbg(dev, "xfer %p ID %08X#%u: bad short segments (%zu)\n",
+ xfer, wa_xfer_id(xfer), cnt,
+ seg->result);
+ urb->status = -EINVAL;
+ goto out;
+ }
+ urb->actual_length += seg->result;
+ if (!(usb_pipeisoc(xfer->urb->pipe))
+ && seg->result < xfer->seg_size
+ && cnt != xfer->segs-1)
+ found_short = 1;
+ dev_dbg(dev, "xfer %p ID %08X#%u: DONE short %d "
+ "result %zu urb->actual_length %d\n",
+ xfer, wa_xfer_id(xfer), seg->index, found_short,
+ seg->result, urb->actual_length);
+ break;
+ case WA_SEG_ERROR:
+ xfer->result = seg->result;
+ dev_dbg(dev, "xfer %p ID %08X#%u: ERROR result %zi(0x%08zX)\n",
+ xfer, wa_xfer_id(xfer), seg->index, seg->result,
+ seg->result);
+ goto out;
+ case WA_SEG_ABORTED:
+ xfer->result = seg->result;
+ dev_dbg(dev, "xfer %p ID %08X#%u: ABORTED result %zi(0x%08zX)\n",
+ xfer, wa_xfer_id(xfer), seg->index, seg->result,
+ seg->result);
+ goto out;
+ default:
+ dev_warn(dev, "xfer %p ID %08X#%u: is_done bad state %d\n",
+ xfer, wa_xfer_id(xfer), cnt, seg->status);
+ xfer->result = -EINVAL;
+ goto out;
+ }
+ }
+ xfer->result = 0;
+out:
+ return result;
+}
+
+/*
+ * Mark the given segment as done. Return true if this completes the xfer.
+ * This should only be called for segs that have been submitted to an RPIPE.
+ * Delayed segs are not marked as submitted so they do not need to be marked
+ * as done when cleaning up.
+ *
+ * xfer->lock has to be locked
+ */
+static unsigned __wa_xfer_mark_seg_as_done(struct wa_xfer *xfer,
+ struct wa_seg *seg, enum wa_seg_status status)
+{
+ seg->status = status;
+ xfer->segs_done++;
+
+ /* check for done. */
+ return __wa_xfer_is_done(xfer);
+}
+
+/*
+ * Search for a transfer list ID on the HCD's URB list
+ *
+ * For 32 bit architectures, we use the pointer itself; for 64 bits, a
+ * 32-bit hash of the pointer.
+ *
+ * @returns NULL if not found.
+ */
+static struct wa_xfer *wa_xfer_get_by_id(struct wahc *wa, u32 id)
+{
+ unsigned long flags;
+ struct wa_xfer *xfer_itr;
+ spin_lock_irqsave(&wa->xfer_list_lock, flags);
+ list_for_each_entry(xfer_itr, &wa->xfer_list, list_node) {
+ if (id == xfer_itr->id) {
+ wa_xfer_get(xfer_itr);
+ goto out;
+ }
+ }
+ xfer_itr = NULL;
+out:
+ spin_unlock_irqrestore(&wa->xfer_list_lock, flags);
+ return xfer_itr;
+}
+
+struct wa_xfer_abort_buffer {
+ struct urb urb;
+ struct wahc *wa;
+ struct wa_xfer_abort cmd;
+};
+
+static void __wa_xfer_abort_cb(struct urb *urb)
+{
+ struct wa_xfer_abort_buffer *b = urb->context;
+ struct wahc *wa = b->wa;
+
+ /*
+ * If the abort request URB failed, then the HWA did not get the abort
+ * command. Forcibly clean up the xfer without waiting for a Transfer
+ * Result from the HWA.
+ */
+ if (urb->status < 0) {
+ struct wa_xfer *xfer;
+ struct device *dev = &wa->usb_iface->dev;
+
+ xfer = wa_xfer_get_by_id(wa, le32_to_cpu(b->cmd.dwTransferID));
+ dev_err(dev, "%s: Transfer Abort request failed. result: %d\n",
+ __func__, urb->status);
+ if (xfer) {
+ unsigned long flags;
+ int done, seg_index = 0;
+ struct wa_rpipe *rpipe = xfer->ep->hcpriv;
+
+ dev_err(dev, "%s: cleaning up xfer %p ID 0x%08X.\n",
+ __func__, xfer, wa_xfer_id(xfer));
+ spin_lock_irqsave(&xfer->lock, flags);
+ /* skip done segs. */
+ while (seg_index < xfer->segs) {
+ struct wa_seg *seg = xfer->seg[seg_index];
+
+ if ((seg->status == WA_SEG_DONE) ||
+ (seg->status == WA_SEG_ERROR)) {
+ ++seg_index;
+ } else {
+ break;
+ }
+ }
+ /* mark remaining segs as aborted. */
+ wa_complete_remaining_xfer_segs(xfer, seg_index,
+ WA_SEG_ABORTED);
+ done = __wa_xfer_is_done(xfer);
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ wa_xfer_delayed_run(rpipe);
+ wa_xfer_put(xfer);
+ } else {
+ dev_err(dev, "%s: xfer ID 0x%08X already gone.\n",
+ __func__, le32_to_cpu(b->cmd.dwTransferID));
+ }
+ }
+
+ wa_put(wa); /* taken in __wa_xfer_abort */
+ usb_put_urb(&b->urb);
+}
+
+/*
+ * Aborts an ongoing transaction
+ *
+ * Assumes the transfer is referenced and locked and in a submitted
+ * state (mainly that there is an endpoint/rpipe assigned).
+ *
+ * The callback (see above) does nothing but freeing up the data by
+ * putting the URB. Because the URB is allocated at the head of the
+ * struct, the whole space we allocated is kfreed. *
+ */
+static int __wa_xfer_abort(struct wa_xfer *xfer)
+{
+ int result = -ENOMEM;
+ struct device *dev = &xfer->wa->usb_iface->dev;
+ struct wa_xfer_abort_buffer *b;
+ struct wa_rpipe *rpipe = xfer->ep->hcpriv;
+
+ b = kmalloc(sizeof(*b), GFP_ATOMIC);
+ if (b == NULL)
+ goto error_kmalloc;
+ b->cmd.bLength = sizeof(b->cmd);
+ b->cmd.bRequestType = WA_XFER_ABORT;
+ b->cmd.wRPipe = rpipe->descr.wRPipeIndex;
+ b->cmd.dwTransferID = wa_xfer_id_le32(xfer);
+ b->wa = wa_get(xfer->wa);
+
+ usb_init_urb(&b->urb);
+ usb_fill_bulk_urb(&b->urb, xfer->wa->usb_dev,
+ usb_sndbulkpipe(xfer->wa->usb_dev,
+ xfer->wa->dto_epd->bEndpointAddress),
+ &b->cmd, sizeof(b->cmd), __wa_xfer_abort_cb, b);
+ result = usb_submit_urb(&b->urb, GFP_ATOMIC);
+ if (result < 0)
+ goto error_submit;
+ return result; /* callback frees! */
+
+
+error_submit:
+ wa_put(xfer->wa);
+ if (printk_ratelimit())
+ dev_err(dev, "xfer %p: Can't submit abort request: %d\n",
+ xfer, result);
+ kfree(b);
+error_kmalloc:
+ return result;
+
+}
+
+/*
+ * Calculate the number of isoc frames starting from isoc_frame_offset
+ * that will fit a in transfer segment.
+ */
+static int __wa_seg_calculate_isoc_frame_count(struct wa_xfer *xfer,
+ int isoc_frame_offset, int *total_size)
+{
+ int segment_size = 0, frame_count = 0;
+ int index = isoc_frame_offset;
+ struct usb_iso_packet_descriptor *iso_frame_desc =
+ xfer->urb->iso_frame_desc;
+
+ while ((index < xfer->urb->number_of_packets)
+ && ((segment_size + iso_frame_desc[index].length)
+ <= xfer->seg_size)) {
+ /*
+ * For Alereon HWA devices, only include an isoc frame in an
+ * out segment if it is physically contiguous with the previous
+ * frame. This is required because those devices expect
+ * the isoc frames to be sent as a single USB transaction as
+ * opposed to one transaction per frame with standard HWA.
+ */
+ if ((xfer->wa->quirks & WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC)
+ && (xfer->is_inbound == 0)
+ && (index > isoc_frame_offset)
+ && ((iso_frame_desc[index - 1].offset +
+ iso_frame_desc[index - 1].length) !=
+ iso_frame_desc[index].offset))
+ break;
+
+ /* this frame fits. count it. */
+ ++frame_count;
+ segment_size += iso_frame_desc[index].length;
+
+ /* move to the next isoc frame. */
+ ++index;
+ }
+
+ *total_size = segment_size;
+ return frame_count;
+}
+
+/*
+ *
+ * @returns < 0 on error, transfer segment request size if ok
+ */
+static ssize_t __wa_xfer_setup_sizes(struct wa_xfer *xfer,
+ enum wa_xfer_type *pxfer_type)
+{
+ ssize_t result;
+ struct device *dev = &xfer->wa->usb_iface->dev;
+ size_t maxpktsize;
+ struct urb *urb = xfer->urb;
+ struct wa_rpipe *rpipe = xfer->ep->hcpriv;
+
+ switch (rpipe->descr.bmAttribute & 0x3) {
+ case USB_ENDPOINT_XFER_CONTROL:
+ *pxfer_type = WA_XFER_TYPE_CTL;
+ result = sizeof(struct wa_xfer_ctl);
+ break;
+ case USB_ENDPOINT_XFER_INT:
+ case USB_ENDPOINT_XFER_BULK:
+ *pxfer_type = WA_XFER_TYPE_BI;
+ result = sizeof(struct wa_xfer_bi);
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ *pxfer_type = WA_XFER_TYPE_ISO;
+ result = sizeof(struct wa_xfer_hwaiso);
+ break;
+ default:
+ /* never happens */
+ BUG();
+ result = -EINVAL; /* shut gcc up */
+ }
+ xfer->is_inbound = urb->pipe & USB_DIR_IN ? 1 : 0;
+ xfer->is_dma = urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP ? 1 : 0;
+
+ maxpktsize = le16_to_cpu(rpipe->descr.wMaxPacketSize);
+ xfer->seg_size = le16_to_cpu(rpipe->descr.wBlocks)
+ * 1 << (xfer->wa->wa_descr->bRPipeBlockSize - 1);
+ /* Compute the segment size and make sure it is a multiple of
+ * the maxpktsize (WUSB1.0[8.3.3.1])...not really too much of
+ * a check (FIXME) */
+ if (xfer->seg_size < maxpktsize) {
+ dev_err(dev,
+ "HW BUG? seg_size %zu smaller than maxpktsize %zu\n",
+ xfer->seg_size, maxpktsize);
+ result = -EINVAL;
+ goto error;
+ }
+ xfer->seg_size = (xfer->seg_size / maxpktsize) * maxpktsize;
+ if ((rpipe->descr.bmAttribute & 0x3) == USB_ENDPOINT_XFER_ISOC) {
+ int index = 0;
+
+ xfer->segs = 0;
+ /*
+ * loop over urb->number_of_packets to determine how many
+ * xfer segments will be needed to send the isoc frames.
+ */
+ while (index < urb->number_of_packets) {
+ int seg_size; /* don't care. */
+ index += __wa_seg_calculate_isoc_frame_count(xfer,
+ index, &seg_size);
+ ++xfer->segs;
+ }
+ } else {
+ xfer->segs = DIV_ROUND_UP(urb->transfer_buffer_length,
+ xfer->seg_size);
+ if (xfer->segs == 0 && *pxfer_type == WA_XFER_TYPE_CTL)
+ xfer->segs = 1;
+ }
+
+ if (xfer->segs > WA_SEGS_MAX) {
+ dev_err(dev, "BUG? oops, number of segments %zu bigger than %d\n",
+ (urb->transfer_buffer_length/xfer->seg_size),
+ WA_SEGS_MAX);
+ result = -EINVAL;
+ goto error;
+ }
+error:
+ return result;
+}
+
+static void __wa_setup_isoc_packet_descr(
+ struct wa_xfer_packet_info_hwaiso *packet_desc,
+ struct wa_xfer *xfer,
+ struct wa_seg *seg) {
+ struct usb_iso_packet_descriptor *iso_frame_desc =
+ xfer->urb->iso_frame_desc;
+ int frame_index;
+
+ /* populate isoc packet descriptor. */
+ packet_desc->bPacketType = WA_XFER_ISO_PACKET_INFO;
+ packet_desc->wLength = cpu_to_le16(struct_size(packet_desc,
+ PacketLength,
+ seg->isoc_frame_count));
+ for (frame_index = 0; frame_index < seg->isoc_frame_count;
+ ++frame_index) {
+ int offset_index = frame_index + seg->isoc_frame_offset;
+ packet_desc->PacketLength[frame_index] =
+ cpu_to_le16(iso_frame_desc[offset_index].length);
+ }
+}
+
+
+/* Fill in the common request header and xfer-type specific data. */
+static void __wa_xfer_setup_hdr0(struct wa_xfer *xfer,
+ struct wa_xfer_hdr *xfer_hdr0,
+ enum wa_xfer_type xfer_type,
+ size_t xfer_hdr_size)
+{
+ struct wa_rpipe *rpipe = xfer->ep->hcpriv;
+ struct wa_seg *seg = xfer->seg[0];
+
+ xfer_hdr0 = &seg->xfer_hdr;
+ xfer_hdr0->bLength = xfer_hdr_size;
+ xfer_hdr0->bRequestType = xfer_type;
+ xfer_hdr0->wRPipe = rpipe->descr.wRPipeIndex;
+ xfer_hdr0->dwTransferID = wa_xfer_id_le32(xfer);
+ xfer_hdr0->bTransferSegment = 0;
+ switch (xfer_type) {
+ case WA_XFER_TYPE_CTL: {
+ struct wa_xfer_ctl *xfer_ctl =
+ container_of(xfer_hdr0, struct wa_xfer_ctl, hdr);
+ xfer_ctl->bmAttribute = xfer->is_inbound ? 1 : 0;
+ memcpy(&xfer_ctl->baSetupData, xfer->urb->setup_packet,
+ sizeof(xfer_ctl->baSetupData));
+ break;
+ }
+ case WA_XFER_TYPE_BI:
+ break;
+ case WA_XFER_TYPE_ISO: {
+ struct wa_xfer_hwaiso *xfer_iso =
+ container_of(xfer_hdr0, struct wa_xfer_hwaiso, hdr);
+ struct wa_xfer_packet_info_hwaiso *packet_desc =
+ ((void *)xfer_iso) + xfer_hdr_size;
+
+ /* populate the isoc section of the transfer request. */
+ xfer_iso->dwNumOfPackets = cpu_to_le32(seg->isoc_frame_count);
+ /* populate isoc packet descriptor. */
+ __wa_setup_isoc_packet_descr(packet_desc, xfer, seg);
+ break;
+ }
+ default:
+ BUG();
+ };
+}
+
+/*
+ * Callback for the OUT data phase of the segment request
+ *
+ * Check wa_seg_tr_cb(); most comments also apply here because this
+ * function does almost the same thing and they work closely
+ * together.
+ *
+ * If the seg request has failed but this DTO phase has succeeded,
+ * wa_seg_tr_cb() has already failed the segment and moved the
+ * status to WA_SEG_ERROR, so this will go through 'case 0' and
+ * effectively do nothing.
+ */
+static void wa_seg_dto_cb(struct urb *urb)
+{
+ struct wa_seg *seg = urb->context;
+ struct wa_xfer *xfer = seg->xfer;
+ struct wahc *wa;
+ struct device *dev;
+ struct wa_rpipe *rpipe;
+ unsigned long flags;
+ unsigned rpipe_ready = 0;
+ int data_send_done = 1, release_dto = 0, holding_dto = 0;
+ u8 done = 0;
+ int result;
+
+ /* free the sg if it was used. */
+ kfree(urb->sg);
+ urb->sg = NULL;
+
+ spin_lock_irqsave(&xfer->lock, flags);
+ wa = xfer->wa;
+ dev = &wa->usb_iface->dev;
+ if (usb_pipeisoc(xfer->urb->pipe)) {
+ /* Alereon HWA sends all isoc frames in a single transfer. */
+ if (wa->quirks & WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC)
+ seg->isoc_frame_index += seg->isoc_frame_count;
+ else
+ seg->isoc_frame_index += 1;
+ if (seg->isoc_frame_index < seg->isoc_frame_count) {
+ data_send_done = 0;
+ holding_dto = 1; /* checked in error cases. */
+ /*
+ * if this is the last isoc frame of the segment, we
+ * can release DTO after sending this frame.
+ */
+ if ((seg->isoc_frame_index + 1) >=
+ seg->isoc_frame_count)
+ release_dto = 1;
+ }
+ dev_dbg(dev, "xfer 0x%08X#%u: isoc frame = %d, holding_dto = %d, release_dto = %d.\n",
+ wa_xfer_id(xfer), seg->index, seg->isoc_frame_index,
+ holding_dto, release_dto);
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+
+ switch (urb->status) {
+ case 0:
+ spin_lock_irqsave(&xfer->lock, flags);
+ seg->result += urb->actual_length;
+ if (data_send_done) {
+ dev_dbg(dev, "xfer 0x%08X#%u: data out done (%zu bytes)\n",
+ wa_xfer_id(xfer), seg->index, seg->result);
+ if (seg->status < WA_SEG_PENDING)
+ seg->status = WA_SEG_PENDING;
+ } else {
+ /* should only hit this for isoc xfers. */
+ /*
+ * Populate the dto URB with the next isoc frame buffer,
+ * send the URB and release DTO if we no longer need it.
+ */
+ __wa_populate_dto_urb_isoc(xfer, seg,
+ seg->isoc_frame_offset + seg->isoc_frame_index);
+
+ /* resubmit the URB with the next isoc frame. */
+ /* take a ref on resubmit. */
+ wa_xfer_get(xfer);
+ result = usb_submit_urb(seg->dto_urb, GFP_ATOMIC);
+ if (result < 0) {
+ dev_err(dev, "xfer 0x%08X#%u: DTO submit failed: %d\n",
+ wa_xfer_id(xfer), seg->index, result);
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ goto error_dto_submit;
+ }
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (release_dto) {
+ __wa_dto_put(wa);
+ wa_check_for_delayed_rpipes(wa);
+ }
+ break;
+ case -ECONNRESET: /* URB unlinked; no need to do anything */
+ case -ENOENT: /* as it was done by the who unlinked us */
+ if (holding_dto) {
+ __wa_dto_put(wa);
+ wa_check_for_delayed_rpipes(wa);
+ }
+ break;
+ default: /* Other errors ... */
+ dev_err(dev, "xfer 0x%08X#%u: data out error %d\n",
+ wa_xfer_id(xfer), seg->index, urb->status);
+ goto error_default;
+ }
+
+ /* taken when this URB was submitted. */
+ wa_xfer_put(xfer);
+ return;
+
+error_dto_submit:
+ /* taken on resubmit attempt. */
+ wa_xfer_put(xfer);
+error_default:
+ spin_lock_irqsave(&xfer->lock, flags);
+ rpipe = xfer->ep->hcpriv;
+ if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)){
+ dev_err(dev, "DTO: URB max acceptable errors exceeded, resetting device\n");
+ wa_reset_all(wa);
+ }
+ if (seg->status != WA_SEG_ERROR) {
+ seg->result = urb->status;
+ __wa_xfer_abort(xfer);
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_ERROR);
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (holding_dto) {
+ __wa_dto_put(wa);
+ wa_check_for_delayed_rpipes(wa);
+ }
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ /* taken when this URB was submitted. */
+ wa_xfer_put(xfer);
+}
+
+/*
+ * Callback for the isoc packet descriptor phase of the segment request
+ *
+ * Check wa_seg_tr_cb(); most comments also apply here because this
+ * function does almost the same thing and they work closely
+ * together.
+ *
+ * If the seg request has failed but this phase has succeeded,
+ * wa_seg_tr_cb() has already failed the segment and moved the
+ * status to WA_SEG_ERROR, so this will go through 'case 0' and
+ * effectively do nothing.
+ */
+static void wa_seg_iso_pack_desc_cb(struct urb *urb)
+{
+ struct wa_seg *seg = urb->context;
+ struct wa_xfer *xfer = seg->xfer;
+ struct wahc *wa;
+ struct device *dev;
+ struct wa_rpipe *rpipe;
+ unsigned long flags;
+ unsigned rpipe_ready = 0;
+ u8 done = 0;
+
+ switch (urb->status) {
+ case 0:
+ spin_lock_irqsave(&xfer->lock, flags);
+ wa = xfer->wa;
+ dev = &wa->usb_iface->dev;
+ dev_dbg(dev, "iso xfer %08X#%u: packet descriptor done\n",
+ wa_xfer_id(xfer), seg->index);
+ if (xfer->is_inbound && seg->status < WA_SEG_PENDING)
+ seg->status = WA_SEG_PENDING;
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ break;
+ case -ECONNRESET: /* URB unlinked; no need to do anything */
+ case -ENOENT: /* as it was done by the who unlinked us */
+ break;
+ default: /* Other errors ... */
+ spin_lock_irqsave(&xfer->lock, flags);
+ wa = xfer->wa;
+ dev = &wa->usb_iface->dev;
+ rpipe = xfer->ep->hcpriv;
+ pr_err_ratelimited("iso xfer %08X#%u: packet descriptor error %d\n",
+ wa_xfer_id(xfer), seg->index, urb->status);
+ if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)){
+ dev_err(dev, "iso xfer: URB max acceptable errors exceeded, resetting device\n");
+ wa_reset_all(wa);
+ }
+ if (seg->status != WA_SEG_ERROR) {
+ usb_unlink_urb(seg->dto_urb);
+ seg->result = urb->status;
+ __wa_xfer_abort(xfer);
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ done = __wa_xfer_mark_seg_as_done(xfer, seg,
+ WA_SEG_ERROR);
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ }
+ /* taken when this URB was submitted. */
+ wa_xfer_put(xfer);
+}
+
+/*
+ * Callback for the segment request
+ *
+ * If successful transition state (unless already transitioned or
+ * outbound transfer); otherwise, take a note of the error, mark this
+ * segment done and try completion.
+ *
+ * Note we don't access until we are sure that the transfer hasn't
+ * been cancelled (ECONNRESET, ENOENT), which could mean that
+ * seg->xfer could be already gone.
+ *
+ * We have to check before setting the status to WA_SEG_PENDING
+ * because sometimes the xfer result callback arrives before this
+ * callback (geeeeeeze), so it might happen that we are already in
+ * another state. As well, we don't set it if the transfer is not inbound,
+ * as in that case, wa_seg_dto_cb will do it when the OUT data phase
+ * finishes.
+ */
+static void wa_seg_tr_cb(struct urb *urb)
+{
+ struct wa_seg *seg = urb->context;
+ struct wa_xfer *xfer = seg->xfer;
+ struct wahc *wa;
+ struct device *dev;
+ struct wa_rpipe *rpipe;
+ unsigned long flags;
+ unsigned rpipe_ready;
+ u8 done = 0;
+
+ switch (urb->status) {
+ case 0:
+ spin_lock_irqsave(&xfer->lock, flags);
+ wa = xfer->wa;
+ dev = &wa->usb_iface->dev;
+ dev_dbg(dev, "xfer %p ID 0x%08X#%u: request done\n",
+ xfer, wa_xfer_id(xfer), seg->index);
+ if (xfer->is_inbound &&
+ seg->status < WA_SEG_PENDING &&
+ !(usb_pipeisoc(xfer->urb->pipe)))
+ seg->status = WA_SEG_PENDING;
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ break;
+ case -ECONNRESET: /* URB unlinked; no need to do anything */
+ case -ENOENT: /* as it was done by the who unlinked us */
+ break;
+ default: /* Other errors ... */
+ spin_lock_irqsave(&xfer->lock, flags);
+ wa = xfer->wa;
+ dev = &wa->usb_iface->dev;
+ rpipe = xfer->ep->hcpriv;
+ if (printk_ratelimit())
+ dev_err(dev, "xfer %p ID 0x%08X#%u: request error %d\n",
+ xfer, wa_xfer_id(xfer), seg->index,
+ urb->status);
+ if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)){
+ dev_err(dev, "DTO: URB max acceptable errors "
+ "exceeded, resetting device\n");
+ wa_reset_all(wa);
+ }
+ usb_unlink_urb(seg->isoc_pack_desc_urb);
+ usb_unlink_urb(seg->dto_urb);
+ seg->result = urb->status;
+ __wa_xfer_abort(xfer);
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_ERROR);
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ }
+ /* taken when this URB was submitted. */
+ wa_xfer_put(xfer);
+}
+
+/*
+ * Allocate an SG list to store bytes_to_transfer bytes and copy the
+ * subset of the in_sg that matches the buffer subset
+ * we are about to transfer.
+ */
+static struct scatterlist *wa_xfer_create_subset_sg(struct scatterlist *in_sg,
+ const unsigned int bytes_transferred,
+ const unsigned int bytes_to_transfer, int *out_num_sgs)
+{
+ struct scatterlist *out_sg;
+ unsigned int bytes_processed = 0, offset_into_current_page_data = 0,
+ nents;
+ struct scatterlist *current_xfer_sg = in_sg;
+ struct scatterlist *current_seg_sg, *last_seg_sg;
+
+ /* skip previously transferred pages. */
+ while ((current_xfer_sg) &&
+ (bytes_processed < bytes_transferred)) {
+ bytes_processed += current_xfer_sg->length;
+
+ /* advance the sg if current segment starts on or past the
+ next page. */
+ if (bytes_processed <= bytes_transferred)
+ current_xfer_sg = sg_next(current_xfer_sg);
+ }
+
+ /* the data for the current segment starts in current_xfer_sg.
+ calculate the offset. */
+ if (bytes_processed > bytes_transferred) {
+ offset_into_current_page_data = current_xfer_sg->length -
+ (bytes_processed - bytes_transferred);
+ }
+
+ /* calculate the number of pages needed by this segment. */
+ nents = DIV_ROUND_UP((bytes_to_transfer +
+ offset_into_current_page_data +
+ current_xfer_sg->offset),
+ PAGE_SIZE);
+
+ out_sg = kmalloc((sizeof(struct scatterlist) * nents), GFP_ATOMIC);
+ if (out_sg) {
+ sg_init_table(out_sg, nents);
+
+ /* copy the portion of the incoming SG that correlates to the
+ * data to be transferred by this segment to the segment SG. */
+ last_seg_sg = current_seg_sg = out_sg;
+ bytes_processed = 0;
+
+ /* reset nents and calculate the actual number of sg entries
+ needed. */
+ nents = 0;
+ while ((bytes_processed < bytes_to_transfer) &&
+ current_seg_sg && current_xfer_sg) {
+ unsigned int page_len = min((current_xfer_sg->length -
+ offset_into_current_page_data),
+ (bytes_to_transfer - bytes_processed));
+
+ sg_set_page(current_seg_sg, sg_page(current_xfer_sg),
+ page_len,
+ current_xfer_sg->offset +
+ offset_into_current_page_data);
+
+ bytes_processed += page_len;
+
+ last_seg_sg = current_seg_sg;
+ current_seg_sg = sg_next(current_seg_sg);
+ current_xfer_sg = sg_next(current_xfer_sg);
+
+ /* only the first page may require additional offset. */
+ offset_into_current_page_data = 0;
+ nents++;
+ }
+
+ /* update num_sgs and terminate the list since we may have
+ * concatenated pages. */
+ sg_mark_end(last_seg_sg);
+ *out_num_sgs = nents;
+ }
+
+ return out_sg;
+}
+
+/*
+ * Populate DMA buffer info for the isoc dto urb.
+ */
+static void __wa_populate_dto_urb_isoc(struct wa_xfer *xfer,
+ struct wa_seg *seg, int curr_iso_frame)
+{
+ seg->dto_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ seg->dto_urb->sg = NULL;
+ seg->dto_urb->num_sgs = 0;
+ /* dto urb buffer address pulled from iso_frame_desc. */
+ seg->dto_urb->transfer_dma = xfer->urb->transfer_dma +
+ xfer->urb->iso_frame_desc[curr_iso_frame].offset;
+ /* The Alereon HWA sends a single URB with all isoc segs. */
+ if (xfer->wa->quirks & WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC)
+ seg->dto_urb->transfer_buffer_length = seg->isoc_size;
+ else
+ seg->dto_urb->transfer_buffer_length =
+ xfer->urb->iso_frame_desc[curr_iso_frame].length;
+}
+
+/*
+ * Populate buffer ptr and size, DMA buffer or SG list for the dto urb.
+ */
+static int __wa_populate_dto_urb(struct wa_xfer *xfer,
+ struct wa_seg *seg, size_t buf_itr_offset, size_t buf_itr_size)
+{
+ int result = 0;
+
+ if (xfer->is_dma) {
+ seg->dto_urb->transfer_dma =
+ xfer->urb->transfer_dma + buf_itr_offset;
+ seg->dto_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ seg->dto_urb->sg = NULL;
+ seg->dto_urb->num_sgs = 0;
+ } else {
+ /* do buffer or SG processing. */
+ seg->dto_urb->transfer_flags &=
+ ~URB_NO_TRANSFER_DMA_MAP;
+ /* this should always be 0 before a resubmit. */
+ seg->dto_urb->num_mapped_sgs = 0;
+
+ if (xfer->urb->transfer_buffer) {
+ seg->dto_urb->transfer_buffer =
+ xfer->urb->transfer_buffer +
+ buf_itr_offset;
+ seg->dto_urb->sg = NULL;
+ seg->dto_urb->num_sgs = 0;
+ } else {
+ seg->dto_urb->transfer_buffer = NULL;
+
+ /*
+ * allocate an SG list to store seg_size bytes
+ * and copy the subset of the xfer->urb->sg that
+ * matches the buffer subset we are about to
+ * read.
+ */
+ seg->dto_urb->sg = wa_xfer_create_subset_sg(
+ xfer->urb->sg,
+ buf_itr_offset, buf_itr_size,
+ &(seg->dto_urb->num_sgs));
+ if (!(seg->dto_urb->sg))
+ result = -ENOMEM;
+ }
+ }
+ seg->dto_urb->transfer_buffer_length = buf_itr_size;
+
+ return result;
+}
+
+/*
+ * Allocate the segs array and initialize each of them
+ *
+ * The segments are freed by wa_xfer_destroy() when the xfer use count
+ * drops to zero; however, because each segment is given the same life
+ * cycle as the USB URB it contains, it is actually freed by
+ * usb_put_urb() on the contained USB URB (twisted, eh?).
+ */
+static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size)
+{
+ int result, cnt, isoc_frame_offset = 0;
+ size_t alloc_size = sizeof(*xfer->seg[0])
+ - sizeof(xfer->seg[0]->xfer_hdr) + xfer_hdr_size;
+ struct usb_device *usb_dev = xfer->wa->usb_dev;
+ const struct usb_endpoint_descriptor *dto_epd = xfer->wa->dto_epd;
+ struct wa_seg *seg;
+ size_t buf_itr, buf_size, buf_itr_size;
+
+ result = -ENOMEM;
+ xfer->seg = kcalloc(xfer->segs, sizeof(xfer->seg[0]), GFP_ATOMIC);
+ if (xfer->seg == NULL)
+ goto error_segs_kzalloc;
+ buf_itr = 0;
+ buf_size = xfer->urb->transfer_buffer_length;
+ for (cnt = 0; cnt < xfer->segs; cnt++) {
+ size_t iso_pkt_descr_size = 0;
+ int seg_isoc_frame_count = 0, seg_isoc_size = 0;
+
+ /*
+ * Adjust the size of the segment object to contain space for
+ * the isoc packet descriptor buffer.
+ */
+ if (usb_pipeisoc(xfer->urb->pipe)) {
+ seg_isoc_frame_count =
+ __wa_seg_calculate_isoc_frame_count(xfer,
+ isoc_frame_offset, &seg_isoc_size);
+
+ iso_pkt_descr_size =
+ sizeof(struct wa_xfer_packet_info_hwaiso) +
+ (seg_isoc_frame_count * sizeof(__le16));
+ }
+ result = -ENOMEM;
+ seg = xfer->seg[cnt] = kmalloc(alloc_size + iso_pkt_descr_size,
+ GFP_ATOMIC);
+ if (seg == NULL)
+ goto error_seg_kmalloc;
+ wa_seg_init(seg);
+ seg->xfer = xfer;
+ seg->index = cnt;
+ usb_fill_bulk_urb(&seg->tr_urb, usb_dev,
+ usb_sndbulkpipe(usb_dev,
+ dto_epd->bEndpointAddress),
+ &seg->xfer_hdr, xfer_hdr_size,
+ wa_seg_tr_cb, seg);
+ buf_itr_size = min(buf_size, xfer->seg_size);
+
+ if (usb_pipeisoc(xfer->urb->pipe)) {
+ seg->isoc_frame_count = seg_isoc_frame_count;
+ seg->isoc_frame_offset = isoc_frame_offset;
+ seg->isoc_size = seg_isoc_size;
+ /* iso packet descriptor. */
+ seg->isoc_pack_desc_urb =
+ usb_alloc_urb(0, GFP_ATOMIC);
+ if (seg->isoc_pack_desc_urb == NULL)
+ goto error_iso_pack_desc_alloc;
+ /*
+ * The buffer for the isoc packet descriptor starts
+ * after the transfer request header in the
+ * segment object memory buffer.
+ */
+ usb_fill_bulk_urb(
+ seg->isoc_pack_desc_urb, usb_dev,
+ usb_sndbulkpipe(usb_dev,
+ dto_epd->bEndpointAddress),
+ (void *)(&seg->xfer_hdr) +
+ xfer_hdr_size,
+ iso_pkt_descr_size,
+ wa_seg_iso_pack_desc_cb, seg);
+
+ /* adjust starting frame offset for next seg. */
+ isoc_frame_offset += seg_isoc_frame_count;
+ }
+
+ if (xfer->is_inbound == 0 && buf_size > 0) {
+ /* outbound data. */
+ seg->dto_urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (seg->dto_urb == NULL)
+ goto error_dto_alloc;
+ usb_fill_bulk_urb(
+ seg->dto_urb, usb_dev,
+ usb_sndbulkpipe(usb_dev,
+ dto_epd->bEndpointAddress),
+ NULL, 0, wa_seg_dto_cb, seg);
+
+ if (usb_pipeisoc(xfer->urb->pipe)) {
+ /*
+ * Fill in the xfer buffer information for the
+ * first isoc frame. Subsequent frames in this
+ * segment will be filled in and sent from the
+ * DTO completion routine, if needed.
+ */
+ __wa_populate_dto_urb_isoc(xfer, seg,
+ seg->isoc_frame_offset);
+ } else {
+ /* fill in the xfer buffer information. */
+ result = __wa_populate_dto_urb(xfer, seg,
+ buf_itr, buf_itr_size);
+ if (result < 0)
+ goto error_seg_outbound_populate;
+
+ buf_itr += buf_itr_size;
+ buf_size -= buf_itr_size;
+ }
+ }
+ seg->status = WA_SEG_READY;
+ }
+ return 0;
+
+ /*
+ * Free the memory for the current segment which failed to init.
+ * Use the fact that cnt is left at were it failed. The remaining
+ * segments will be cleaned up by wa_xfer_destroy.
+ */
+error_seg_outbound_populate:
+ usb_free_urb(xfer->seg[cnt]->dto_urb);
+error_dto_alloc:
+ usb_free_urb(xfer->seg[cnt]->isoc_pack_desc_urb);
+error_iso_pack_desc_alloc:
+ kfree(xfer->seg[cnt]);
+ xfer->seg[cnt] = NULL;
+error_seg_kmalloc:
+error_segs_kzalloc:
+ return result;
+}
+
+/*
+ * Allocates all the stuff needed to submit a transfer
+ *
+ * Breaks the whole data buffer in a list of segments, each one has a
+ * structure allocated to it and linked in xfer->seg[index]
+ *
+ * FIXME: merge setup_segs() and the last part of this function, no
+ * need to do two for loops when we could run everything in a
+ * single one
+ */
+static int __wa_xfer_setup(struct wa_xfer *xfer, struct urb *urb)
+{
+ int result;
+ struct device *dev = &xfer->wa->usb_iface->dev;
+ enum wa_xfer_type xfer_type = 0; /* shut up GCC */
+ size_t xfer_hdr_size, cnt, transfer_size;
+ struct wa_xfer_hdr *xfer_hdr0, *xfer_hdr;
+
+ result = __wa_xfer_setup_sizes(xfer, &xfer_type);
+ if (result < 0)
+ goto error_setup_sizes;
+ xfer_hdr_size = result;
+ result = __wa_xfer_setup_segs(xfer, xfer_hdr_size);
+ if (result < 0) {
+ dev_err(dev, "xfer %p: Failed to allocate %d segments: %d\n",
+ xfer, xfer->segs, result);
+ goto error_setup_segs;
+ }
+ /* Fill the first header */
+ xfer_hdr0 = &xfer->seg[0]->xfer_hdr;
+ wa_xfer_id_init(xfer);
+ __wa_xfer_setup_hdr0(xfer, xfer_hdr0, xfer_type, xfer_hdr_size);
+
+ /* Fill remaining headers */
+ xfer_hdr = xfer_hdr0;
+ if (xfer_type == WA_XFER_TYPE_ISO) {
+ xfer_hdr0->dwTransferLength =
+ cpu_to_le32(xfer->seg[0]->isoc_size);
+ for (cnt = 1; cnt < xfer->segs; cnt++) {
+ struct wa_xfer_packet_info_hwaiso *packet_desc;
+ struct wa_seg *seg = xfer->seg[cnt];
+ struct wa_xfer_hwaiso *xfer_iso;
+
+ xfer_hdr = &seg->xfer_hdr;
+ xfer_iso = container_of(xfer_hdr,
+ struct wa_xfer_hwaiso, hdr);
+ packet_desc = ((void *)xfer_hdr) + xfer_hdr_size;
+ /*
+ * Copy values from the 0th header. Segment specific
+ * values are set below.
+ */
+ memcpy(xfer_hdr, xfer_hdr0, xfer_hdr_size);
+ xfer_hdr->bTransferSegment = cnt;
+ xfer_hdr->dwTransferLength =
+ cpu_to_le32(seg->isoc_size);
+ xfer_iso->dwNumOfPackets =
+ cpu_to_le32(seg->isoc_frame_count);
+ __wa_setup_isoc_packet_descr(packet_desc, xfer, seg);
+ seg->status = WA_SEG_READY;
+ }
+ } else {
+ transfer_size = urb->transfer_buffer_length;
+ xfer_hdr0->dwTransferLength = transfer_size > xfer->seg_size ?
+ cpu_to_le32(xfer->seg_size) :
+ cpu_to_le32(transfer_size);
+ transfer_size -= xfer->seg_size;
+ for (cnt = 1; cnt < xfer->segs; cnt++) {
+ xfer_hdr = &xfer->seg[cnt]->xfer_hdr;
+ memcpy(xfer_hdr, xfer_hdr0, xfer_hdr_size);
+ xfer_hdr->bTransferSegment = cnt;
+ xfer_hdr->dwTransferLength =
+ transfer_size > xfer->seg_size ?
+ cpu_to_le32(xfer->seg_size)
+ : cpu_to_le32(transfer_size);
+ xfer->seg[cnt]->status = WA_SEG_READY;
+ transfer_size -= xfer->seg_size;
+ }
+ }
+ xfer_hdr->bTransferSegment |= 0x80; /* this is the last segment */
+ result = 0;
+error_setup_segs:
+error_setup_sizes:
+ return result;
+}
+
+/*
+ *
+ *
+ * rpipe->seg_lock is held!
+ */
+static int __wa_seg_submit(struct wa_rpipe *rpipe, struct wa_xfer *xfer,
+ struct wa_seg *seg, int *dto_done)
+{
+ int result;
+
+ /* default to done unless we encounter a multi-frame isoc segment. */
+ *dto_done = 1;
+
+ /*
+ * Take a ref for each segment urb so the xfer cannot disappear until
+ * all of the callbacks run.
+ */
+ wa_xfer_get(xfer);
+ /* submit the transfer request. */
+ seg->status = WA_SEG_SUBMITTED;
+ result = usb_submit_urb(&seg->tr_urb, GFP_ATOMIC);
+ if (result < 0) {
+ pr_err("%s: xfer %p#%u: REQ submit failed: %d\n",
+ __func__, xfer, seg->index, result);
+ wa_xfer_put(xfer);
+ goto error_tr_submit;
+ }
+ /* submit the isoc packet descriptor if present. */
+ if (seg->isoc_pack_desc_urb) {
+ wa_xfer_get(xfer);
+ result = usb_submit_urb(seg->isoc_pack_desc_urb, GFP_ATOMIC);
+ seg->isoc_frame_index = 0;
+ if (result < 0) {
+ pr_err("%s: xfer %p#%u: ISO packet descriptor submit failed: %d\n",
+ __func__, xfer, seg->index, result);
+ wa_xfer_put(xfer);
+ goto error_iso_pack_desc_submit;
+ }
+ }
+ /* submit the out data if this is an out request. */
+ if (seg->dto_urb) {
+ struct wahc *wa = xfer->wa;
+ wa_xfer_get(xfer);
+ result = usb_submit_urb(seg->dto_urb, GFP_ATOMIC);
+ if (result < 0) {
+ pr_err("%s: xfer %p#%u: DTO submit failed: %d\n",
+ __func__, xfer, seg->index, result);
+ wa_xfer_put(xfer);
+ goto error_dto_submit;
+ }
+ /*
+ * If this segment contains more than one isoc frame, hold
+ * onto the dto resource until we send all frames.
+ * Only applies to non-Alereon devices.
+ */
+ if (((wa->quirks & WUSB_QUIRK_ALEREON_HWA_CONCAT_ISOC) == 0)
+ && (seg->isoc_frame_count > 1))
+ *dto_done = 0;
+ }
+ rpipe_avail_dec(rpipe);
+ return 0;
+
+error_dto_submit:
+ usb_unlink_urb(seg->isoc_pack_desc_urb);
+error_iso_pack_desc_submit:
+ usb_unlink_urb(&seg->tr_urb);
+error_tr_submit:
+ seg->status = WA_SEG_ERROR;
+ seg->result = result;
+ *dto_done = 1;
+ return result;
+}
+
+/*
+ * Execute more queued request segments until the maximum concurrent allowed.
+ * Return true if the DTO resource was acquired and released.
+ *
+ * The ugly unlock/lock sequence on the error path is needed as the
+ * xfer->lock normally nests the seg_lock and not viceversa.
+ */
+static int __wa_xfer_delayed_run(struct wa_rpipe *rpipe, int *dto_waiting)
+{
+ int result, dto_acquired = 0, dto_done = 0;
+ struct device *dev = &rpipe->wa->usb_iface->dev;
+ struct wa_seg *seg;
+ struct wa_xfer *xfer;
+ unsigned long flags;
+
+ *dto_waiting = 0;
+
+ spin_lock_irqsave(&rpipe->seg_lock, flags);
+ while (atomic_read(&rpipe->segs_available) > 0
+ && !list_empty(&rpipe->seg_list)
+ && (dto_acquired = __wa_dto_try_get(rpipe->wa))) {
+ seg = list_first_entry(&(rpipe->seg_list), struct wa_seg,
+ list_node);
+ list_del(&seg->list_node);
+ xfer = seg->xfer;
+ /*
+ * Get a reference to the xfer in case the callbacks for the
+ * URBs submitted by __wa_seg_submit attempt to complete
+ * the xfer before this function completes.
+ */
+ wa_xfer_get(xfer);
+ result = __wa_seg_submit(rpipe, xfer, seg, &dto_done);
+ /* release the dto resource if this RPIPE is done with it. */
+ if (dto_done)
+ __wa_dto_put(rpipe->wa);
+ dev_dbg(dev, "xfer %p ID %08X#%u submitted from delayed [%d segments available] %d\n",
+ xfer, wa_xfer_id(xfer), seg->index,
+ atomic_read(&rpipe->segs_available), result);
+ if (unlikely(result < 0)) {
+ int done;
+
+ spin_unlock_irqrestore(&rpipe->seg_lock, flags);
+ spin_lock_irqsave(&xfer->lock, flags);
+ __wa_xfer_abort(xfer);
+ /*
+ * This seg was marked as submitted when it was put on
+ * the RPIPE seg_list. Mark it done.
+ */
+ xfer->segs_done++;
+ done = __wa_xfer_is_done(xfer);
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ spin_lock_irqsave(&rpipe->seg_lock, flags);
+ }
+ wa_xfer_put(xfer);
+ }
+ /*
+ * Mark this RPIPE as waiting if dto was not acquired, there are
+ * delayed segs and no active transfers to wake us up later.
+ */
+ if (!dto_acquired && !list_empty(&rpipe->seg_list)
+ && (atomic_read(&rpipe->segs_available) ==
+ le16_to_cpu(rpipe->descr.wRequests)))
+ *dto_waiting = 1;
+
+ spin_unlock_irqrestore(&rpipe->seg_lock, flags);
+
+ return dto_done;
+}
+
+static void wa_xfer_delayed_run(struct wa_rpipe *rpipe)
+{
+ int dto_waiting;
+ int dto_done = __wa_xfer_delayed_run(rpipe, &dto_waiting);
+
+ /*
+ * If this RPIPE is waiting on the DTO resource, add it to the tail of
+ * the waiting list.
+ * Otherwise, if the WA DTO resource was acquired and released by
+ * __wa_xfer_delayed_run, another RPIPE may have attempted to acquire
+ * DTO and failed during that time. Check the delayed list and process
+ * any waiters. Start searching from the next RPIPE index.
+ */
+ if (dto_waiting)
+ wa_add_delayed_rpipe(rpipe->wa, rpipe);
+ else if (dto_done)
+ wa_check_for_delayed_rpipes(rpipe->wa);
+}
+
+/*
+ *
+ * xfer->lock is taken
+ *
+ * On failure submitting we just stop submitting and return error;
+ * wa_urb_enqueue_b() will execute the completion path
+ */
+static int __wa_xfer_submit(struct wa_xfer *xfer)
+{
+ int result, dto_acquired = 0, dto_done = 0, dto_waiting = 0;
+ struct wahc *wa = xfer->wa;
+ struct device *dev = &wa->usb_iface->dev;
+ unsigned cnt;
+ struct wa_seg *seg;
+ unsigned long flags;
+ struct wa_rpipe *rpipe = xfer->ep->hcpriv;
+ size_t maxrequests = le16_to_cpu(rpipe->descr.wRequests);
+ u8 available;
+ u8 empty;
+
+ spin_lock_irqsave(&wa->xfer_list_lock, flags);
+ list_add_tail(&xfer->list_node, &wa->xfer_list);
+ spin_unlock_irqrestore(&wa->xfer_list_lock, flags);
+
+ BUG_ON(atomic_read(&rpipe->segs_available) > maxrequests);
+ result = 0;
+ spin_lock_irqsave(&rpipe->seg_lock, flags);
+ for (cnt = 0; cnt < xfer->segs; cnt++) {
+ int delay_seg = 1;
+
+ available = atomic_read(&rpipe->segs_available);
+ empty = list_empty(&rpipe->seg_list);
+ seg = xfer->seg[cnt];
+ if (available && empty) {
+ /*
+ * Only attempt to acquire DTO if we have a segment
+ * to send.
+ */
+ dto_acquired = __wa_dto_try_get(rpipe->wa);
+ if (dto_acquired) {
+ delay_seg = 0;
+ result = __wa_seg_submit(rpipe, xfer, seg,
+ &dto_done);
+ dev_dbg(dev, "xfer %p ID 0x%08X#%u: available %u empty %u submitted\n",
+ xfer, wa_xfer_id(xfer), cnt, available,
+ empty);
+ if (dto_done)
+ __wa_dto_put(rpipe->wa);
+
+ if (result < 0) {
+ __wa_xfer_abort(xfer);
+ goto error_seg_submit;
+ }
+ }
+ }
+
+ if (delay_seg) {
+ dev_dbg(dev, "xfer %p ID 0x%08X#%u: available %u empty %u delayed\n",
+ xfer, wa_xfer_id(xfer), cnt, available, empty);
+ seg->status = WA_SEG_DELAYED;
+ list_add_tail(&seg->list_node, &rpipe->seg_list);
+ }
+ xfer->segs_submitted++;
+ }
+error_seg_submit:
+ /*
+ * Mark this RPIPE as waiting if dto was not acquired, there are
+ * delayed segs and no active transfers to wake us up later.
+ */
+ if (!dto_acquired && !list_empty(&rpipe->seg_list)
+ && (atomic_read(&rpipe->segs_available) ==
+ le16_to_cpu(rpipe->descr.wRequests)))
+ dto_waiting = 1;
+ spin_unlock_irqrestore(&rpipe->seg_lock, flags);
+
+ if (dto_waiting)
+ wa_add_delayed_rpipe(rpipe->wa, rpipe);
+ else if (dto_done)
+ wa_check_for_delayed_rpipes(rpipe->wa);
+
+ return result;
+}
+
+/*
+ * Second part of a URB/transfer enqueuement
+ *
+ * Assumes this comes from wa_urb_enqueue() [maybe through
+ * wa_urb_enqueue_run()]. At this point:
+ *
+ * xfer->wa filled and refcounted
+ * xfer->ep filled with rpipe refcounted if
+ * delayed == 0
+ * xfer->urb filled and refcounted (this is the case when called
+ * from wa_urb_enqueue() as we come from usb_submit_urb()
+ * and when called by wa_urb_enqueue_run(), as we took an
+ * extra ref dropped by _run() after we return).
+ * xfer->gfp filled
+ *
+ * If we fail at __wa_xfer_submit(), then we just check if we are done
+ * and if so, we run the completion procedure. However, if we are not
+ * yet done, we do nothing and wait for the completion handlers from
+ * the submitted URBs or from the xfer-result path to kick in. If xfer
+ * result never kicks in, the xfer will timeout from the USB code and
+ * dequeue() will be called.
+ */
+static int wa_urb_enqueue_b(struct wa_xfer *xfer)
+{
+ int result;
+ unsigned long flags;
+ struct urb *urb = xfer->urb;
+ struct wahc *wa = xfer->wa;
+ struct wusbhc *wusbhc = wa->wusb;
+ struct wusb_dev *wusb_dev;
+ unsigned done;
+
+ result = rpipe_get_by_ep(wa, xfer->ep, urb, xfer->gfp);
+ if (result < 0) {
+ pr_err("%s: error_rpipe_get\n", __func__);
+ goto error_rpipe_get;
+ }
+ result = -ENODEV;
+ /* FIXME: segmentation broken -- kills DWA */
+ mutex_lock(&wusbhc->mutex); /* get a WUSB dev */
+ if (urb->dev == NULL) {
+ mutex_unlock(&wusbhc->mutex);
+ pr_err("%s: error usb dev gone\n", __func__);
+ goto error_dev_gone;
+ }
+ wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev);
+ if (wusb_dev == NULL) {
+ mutex_unlock(&wusbhc->mutex);
+ dev_err(&(urb->dev->dev), "%s: error wusb dev gone\n",
+ __func__);
+ goto error_dev_gone;
+ }
+ mutex_unlock(&wusbhc->mutex);
+
+ spin_lock_irqsave(&xfer->lock, flags);
+ xfer->wusb_dev = wusb_dev;
+ result = urb->status;
+ if (urb->status != -EINPROGRESS) {
+ dev_err(&(urb->dev->dev), "%s: error_dequeued\n", __func__);
+ goto error_dequeued;
+ }
+
+ result = __wa_xfer_setup(xfer, urb);
+ if (result < 0) {
+ dev_err(&(urb->dev->dev), "%s: error_xfer_setup\n", __func__);
+ goto error_xfer_setup;
+ }
+ /*
+ * Get a xfer reference since __wa_xfer_submit starts asynchronous
+ * operations that may try to complete the xfer before this function
+ * exits.
+ */
+ wa_xfer_get(xfer);
+ result = __wa_xfer_submit(xfer);
+ if (result < 0) {
+ dev_err(&(urb->dev->dev), "%s: error_xfer_submit\n", __func__);
+ goto error_xfer_submit;
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ wa_xfer_put(xfer);
+ return 0;
+
+ /*
+ * this is basically wa_xfer_completion() broken up wa_xfer_giveback()
+ * does a wa_xfer_put() that will call wa_xfer_destroy() and undo
+ * setup().
+ */
+error_xfer_setup:
+error_dequeued:
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ /* FIXME: segmentation broken, kills DWA */
+ if (wusb_dev)
+ wusb_dev_put(wusb_dev);
+error_dev_gone:
+ rpipe_put(xfer->ep->hcpriv);
+error_rpipe_get:
+ xfer->result = result;
+ return result;
+
+error_xfer_submit:
+ done = __wa_xfer_is_done(xfer);
+ xfer->result = result;
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ wa_xfer_put(xfer);
+ /* return success since the completion routine will run. */
+ return 0;
+}
+
+/*
+ * Execute the delayed transfers in the Wire Adapter @wa
+ *
+ * We need to be careful here, as dequeue() could be called in the
+ * middle. That's why we do the whole thing under the
+ * wa->xfer_list_lock. If dequeue() jumps in, it first locks xfer->lock
+ * and then checks the list -- so as we would be acquiring in inverse
+ * order, we move the delayed list to a separate list while locked and then
+ * submit them without the list lock held.
+ */
+void wa_urb_enqueue_run(struct work_struct *ws)
+{
+ struct wahc *wa = container_of(ws, struct wahc, xfer_enqueue_work);
+ struct wa_xfer *xfer, *next;
+ struct urb *urb;
+ LIST_HEAD(tmp_list);
+
+ /* Create a copy of the wa->xfer_delayed_list while holding the lock */
+ spin_lock_irq(&wa->xfer_list_lock);
+ list_cut_position(&tmp_list, &wa->xfer_delayed_list,
+ wa->xfer_delayed_list.prev);
+ spin_unlock_irq(&wa->xfer_list_lock);
+
+ /*
+ * enqueue from temp list without list lock held since wa_urb_enqueue_b
+ * can take xfer->lock as well as lock mutexes.
+ */
+ list_for_each_entry_safe(xfer, next, &tmp_list, list_node) {
+ list_del_init(&xfer->list_node);
+
+ urb = xfer->urb;
+ if (wa_urb_enqueue_b(xfer) < 0)
+ wa_xfer_giveback(xfer);
+ usb_put_urb(urb); /* taken when queuing */
+ }
+}
+EXPORT_SYMBOL_GPL(wa_urb_enqueue_run);
+
+/*
+ * Process the errored transfers on the Wire Adapter outside of interrupt.
+ */
+void wa_process_errored_transfers_run(struct work_struct *ws)
+{
+ struct wahc *wa = container_of(ws, struct wahc, xfer_error_work);
+ struct wa_xfer *xfer, *next;
+ LIST_HEAD(tmp_list);
+
+ pr_info("%s: Run delayed STALL processing.\n", __func__);
+
+ /* Create a copy of the wa->xfer_errored_list while holding the lock */
+ spin_lock_irq(&wa->xfer_list_lock);
+ list_cut_position(&tmp_list, &wa->xfer_errored_list,
+ wa->xfer_errored_list.prev);
+ spin_unlock_irq(&wa->xfer_list_lock);
+
+ /*
+ * run rpipe_clear_feature_stalled from temp list without list lock
+ * held.
+ */
+ list_for_each_entry_safe(xfer, next, &tmp_list, list_node) {
+ struct usb_host_endpoint *ep;
+ unsigned long flags;
+ struct wa_rpipe *rpipe;
+
+ spin_lock_irqsave(&xfer->lock, flags);
+ ep = xfer->ep;
+ rpipe = ep->hcpriv;
+ spin_unlock_irqrestore(&xfer->lock, flags);
+
+ /* clear RPIPE feature stalled without holding a lock. */
+ rpipe_clear_feature_stalled(wa, ep);
+
+ /* complete the xfer. This removes it from the tmp list. */
+ wa_xfer_completion(xfer);
+
+ /* check for work. */
+ wa_xfer_delayed_run(rpipe);
+ }
+}
+EXPORT_SYMBOL_GPL(wa_process_errored_transfers_run);
+
+/*
+ * Submit a transfer to the Wire Adapter in a delayed way
+ *
+ * The process of enqueuing involves possible sleeps() [see
+ * enqueue_b(), for the rpipe_get() and the mutex_lock()]. If we are
+ * in an atomic section, we defer the enqueue_b() call--else we call direct.
+ *
+ * @urb: We own a reference to it done by the HCI Linux USB stack that
+ * will be given up by calling usb_hcd_giveback_urb() or by
+ * returning error from this function -> ergo we don't have to
+ * refcount it.
+ */
+int wa_urb_enqueue(struct wahc *wa, struct usb_host_endpoint *ep,
+ struct urb *urb, gfp_t gfp)
+{
+ int result;
+ struct device *dev = &wa->usb_iface->dev;
+ struct wa_xfer *xfer;
+ unsigned long my_flags;
+ unsigned cant_sleep = irqs_disabled() | in_atomic();
+
+ if ((urb->transfer_buffer == NULL)
+ && (urb->sg == NULL)
+ && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
+ && urb->transfer_buffer_length != 0) {
+ dev_err(dev, "BUG? urb %p: NULL xfer buffer & NODMA\n", urb);
+ dump_stack();
+ }
+
+ spin_lock_irqsave(&wa->xfer_list_lock, my_flags);
+ result = usb_hcd_link_urb_to_ep(&(wa->wusb->usb_hcd), urb);
+ spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags);
+ if (result < 0)
+ goto error_link_urb;
+
+ result = -ENOMEM;
+ xfer = kzalloc(sizeof(*xfer), gfp);
+ if (xfer == NULL)
+ goto error_kmalloc;
+
+ result = -ENOENT;
+ if (urb->status != -EINPROGRESS) /* cancelled */
+ goto error_dequeued; /* before starting? */
+ wa_xfer_init(xfer);
+ xfer->wa = wa_get(wa);
+ xfer->urb = urb;
+ xfer->gfp = gfp;
+ xfer->ep = ep;
+ urb->hcpriv = xfer;
+
+ dev_dbg(dev, "xfer %p urb %p pipe 0x%02x [%d bytes] %s %s %s\n",
+ xfer, urb, urb->pipe, urb->transfer_buffer_length,
+ urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP ? "dma" : "nodma",
+ urb->pipe & USB_DIR_IN ? "inbound" : "outbound",
+ cant_sleep ? "deferred" : "inline");
+
+ if (cant_sleep) {
+ usb_get_urb(urb);
+ spin_lock_irqsave(&wa->xfer_list_lock, my_flags);
+ list_add_tail(&xfer->list_node, &wa->xfer_delayed_list);
+ spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags);
+ queue_work(wusbd, &wa->xfer_enqueue_work);
+ } else {
+ result = wa_urb_enqueue_b(xfer);
+ if (result < 0) {
+ /*
+ * URB submit/enqueue failed. Clean up, return an
+ * error and do not run the callback. This avoids
+ * an infinite submit/complete loop.
+ */
+ dev_err(dev, "%s: URB enqueue failed: %d\n",
+ __func__, result);
+ wa_put(xfer->wa);
+ wa_xfer_put(xfer);
+ spin_lock_irqsave(&wa->xfer_list_lock, my_flags);
+ usb_hcd_unlink_urb_from_ep(&(wa->wusb->usb_hcd), urb);
+ spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags);
+ return result;
+ }
+ }
+ return 0;
+
+error_dequeued:
+ kfree(xfer);
+error_kmalloc:
+ spin_lock_irqsave(&wa->xfer_list_lock, my_flags);
+ usb_hcd_unlink_urb_from_ep(&(wa->wusb->usb_hcd), urb);
+ spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags);
+error_link_urb:
+ return result;
+}
+EXPORT_SYMBOL_GPL(wa_urb_enqueue);
+
+/*
+ * Dequeue a URB and make sure uwb_hcd_giveback_urb() [completion
+ * handler] is called.
+ *
+ * Until a transfer goes successfully through wa_urb_enqueue() it
+ * needs to be dequeued with completion calling; when stuck in delayed
+ * or before wa_xfer_setup() is called, we need to do completion.
+ *
+ * not setup If there is no hcpriv yet, that means that that enqueue
+ * still had no time to set the xfer up. Because
+ * urb->status should be other than -EINPROGRESS,
+ * enqueue() will catch that and bail out.
+ *
+ * If the transfer has gone through setup, we just need to clean it
+ * up. If it has gone through submit(), we have to abort it [with an
+ * asynch request] and then make sure we cancel each segment.
+ *
+ */
+int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status)
+{
+ unsigned long flags;
+ struct wa_xfer *xfer;
+ struct wa_seg *seg;
+ struct wa_rpipe *rpipe;
+ unsigned cnt, done = 0, xfer_abort_pending;
+ unsigned rpipe_ready = 0;
+ int result;
+
+ /* check if it is safe to unlink. */
+ spin_lock_irqsave(&wa->xfer_list_lock, flags);
+ result = usb_hcd_check_unlink_urb(&(wa->wusb->usb_hcd), urb, status);
+ if ((result == 0) && urb->hcpriv) {
+ /*
+ * Get a xfer ref to prevent a race with wa_xfer_giveback
+ * cleaning up the xfer while we are working with it.
+ */
+ wa_xfer_get(urb->hcpriv);
+ }
+ spin_unlock_irqrestore(&wa->xfer_list_lock, flags);
+ if (result)
+ return result;
+
+ xfer = urb->hcpriv;
+ if (xfer == NULL)
+ return -ENOENT;
+ spin_lock_irqsave(&xfer->lock, flags);
+ pr_debug("%s: DEQUEUE xfer id 0x%08X\n", __func__, wa_xfer_id(xfer));
+ rpipe = xfer->ep->hcpriv;
+ if (rpipe == NULL) {
+ pr_debug("%s: xfer %p id 0x%08X has no RPIPE. %s",
+ __func__, xfer, wa_xfer_id(xfer),
+ "Probably already aborted.\n" );
+ result = -ENOENT;
+ goto out_unlock;
+ }
+ /*
+ * Check for done to avoid racing with wa_xfer_giveback and completing
+ * twice.
+ */
+ if (__wa_xfer_is_done(xfer)) {
+ pr_debug("%s: xfer %p id 0x%08X already done.\n", __func__,
+ xfer, wa_xfer_id(xfer));
+ result = -ENOENT;
+ goto out_unlock;
+ }
+ /* Check the delayed list -> if there, release and complete */
+ spin_lock(&wa->xfer_list_lock);
+ if (!list_empty(&xfer->list_node) && xfer->seg == NULL)
+ goto dequeue_delayed;
+ spin_unlock(&wa->xfer_list_lock);
+ if (xfer->seg == NULL) /* still hasn't reached */
+ goto out_unlock; /* setup(), enqueue_b() completes */
+ /* Ok, the xfer is in flight already, it's been setup and submitted.*/
+ xfer_abort_pending = __wa_xfer_abort(xfer) >= 0;
+ /*
+ * grab the rpipe->seg_lock here to prevent racing with
+ * __wa_xfer_delayed_run.
+ */
+ spin_lock(&rpipe->seg_lock);
+ for (cnt = 0; cnt < xfer->segs; cnt++) {
+ seg = xfer->seg[cnt];
+ pr_debug("%s: xfer id 0x%08X#%d status = %d\n",
+ __func__, wa_xfer_id(xfer), cnt, seg->status);
+ switch (seg->status) {
+ case WA_SEG_NOTREADY:
+ case WA_SEG_READY:
+ printk(KERN_ERR "xfer %p#%u: dequeue bad state %u\n",
+ xfer, cnt, seg->status);
+ WARN_ON(1);
+ break;
+ case WA_SEG_DELAYED:
+ /*
+ * delete from rpipe delayed list. If no segments on
+ * this xfer have been submitted, __wa_xfer_is_done will
+ * trigger a giveback below. Otherwise, the submitted
+ * segments will be completed in the DTI interrupt.
+ */
+ seg->status = WA_SEG_ABORTED;
+ seg->result = -ENOENT;
+ list_del(&seg->list_node);
+ xfer->segs_done++;
+ break;
+ case WA_SEG_DONE:
+ case WA_SEG_ERROR:
+ case WA_SEG_ABORTED:
+ break;
+ /*
+ * The buf_in data for a segment in the
+ * WA_SEG_DTI_PENDING state is actively being read.
+ * Let wa_buf_in_cb handle it since it will be called
+ * and will increment xfer->segs_done. Cleaning up
+ * here could cause wa_buf_in_cb to access the xfer
+ * after it has been completed/freed.
+ */
+ case WA_SEG_DTI_PENDING:
+ break;
+ /*
+ * In the states below, the HWA device already knows
+ * about the transfer. If an abort request was sent,
+ * allow the HWA to process it and wait for the
+ * results. Otherwise, the DTI state and seg completed
+ * counts can get out of sync.
+ */
+ case WA_SEG_SUBMITTED:
+ case WA_SEG_PENDING:
+ /*
+ * Check if the abort was successfully sent. This could
+ * be false if the HWA has been removed but we haven't
+ * gotten the disconnect notification yet.
+ */
+ if (!xfer_abort_pending) {
+ seg->status = WA_SEG_ABORTED;
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ xfer->segs_done++;
+ }
+ break;
+ }
+ }
+ spin_unlock(&rpipe->seg_lock);
+ xfer->result = urb->status; /* -ENOENT or -ECONNRESET */
+ done = __wa_xfer_is_done(xfer);
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ wa_xfer_put(xfer);
+ return result;
+
+out_unlock:
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ wa_xfer_put(xfer);
+ return result;
+
+dequeue_delayed:
+ list_del_init(&xfer->list_node);
+ spin_unlock(&wa->xfer_list_lock);
+ xfer->result = urb->status;
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ wa_xfer_giveback(xfer);
+ wa_xfer_put(xfer);
+ usb_put_urb(urb); /* we got a ref in enqueue() */
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wa_urb_dequeue);
+
+/*
+ * Translation from WA status codes (WUSB1.0 Table 8.15) to errno
+ * codes
+ *
+ * Positive errno values are internal inconsistencies and should be
+ * flagged louder. Negative are to be passed up to the user in the
+ * normal way.
+ *
+ * @status: USB WA status code -- high two bits are stripped.
+ */
+static int wa_xfer_status_to_errno(u8 status)
+{
+ int errno;
+ u8 real_status = status;
+ static int xlat[] = {
+ [WA_XFER_STATUS_SUCCESS] = 0,
+ [WA_XFER_STATUS_HALTED] = -EPIPE,
+ [WA_XFER_STATUS_DATA_BUFFER_ERROR] = -ENOBUFS,
+ [WA_XFER_STATUS_BABBLE] = -EOVERFLOW,
+ [WA_XFER_RESERVED] = EINVAL,
+ [WA_XFER_STATUS_NOT_FOUND] = 0,
+ [WA_XFER_STATUS_INSUFFICIENT_RESOURCE] = -ENOMEM,
+ [WA_XFER_STATUS_TRANSACTION_ERROR] = -EILSEQ,
+ [WA_XFER_STATUS_ABORTED] = -ENOENT,
+ [WA_XFER_STATUS_RPIPE_NOT_READY] = EINVAL,
+ [WA_XFER_INVALID_FORMAT] = EINVAL,
+ [WA_XFER_UNEXPECTED_SEGMENT_NUMBER] = EINVAL,
+ [WA_XFER_STATUS_RPIPE_TYPE_MISMATCH] = EINVAL,
+ };
+ status &= 0x3f;
+
+ if (status == 0)
+ return 0;
+ if (status >= ARRAY_SIZE(xlat)) {
+ printk_ratelimited(KERN_ERR "%s(): BUG? "
+ "Unknown WA transfer status 0x%02x\n",
+ __func__, real_status);
+ return -EINVAL;
+ }
+ errno = xlat[status];
+ if (unlikely(errno > 0)) {
+ printk_ratelimited(KERN_ERR "%s(): BUG? "
+ "Inconsistent WA status: 0x%02x\n",
+ __func__, real_status);
+ errno = -errno;
+ }
+ return errno;
+}
+
+/*
+ * If a last segment flag and/or a transfer result error is encountered,
+ * no other segment transfer results will be returned from the device.
+ * Mark the remaining submitted or pending xfers as completed so that
+ * the xfer will complete cleanly.
+ *
+ * xfer->lock must be held
+ *
+ */
+static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer,
+ int starting_index, enum wa_seg_status status)
+{
+ int index;
+ struct wa_rpipe *rpipe = xfer->ep->hcpriv;
+
+ for (index = starting_index; index < xfer->segs_submitted; index++) {
+ struct wa_seg *current_seg = xfer->seg[index];
+
+ BUG_ON(current_seg == NULL);
+
+ switch (current_seg->status) {
+ case WA_SEG_SUBMITTED:
+ case WA_SEG_PENDING:
+ case WA_SEG_DTI_PENDING:
+ rpipe_avail_inc(rpipe);
+ /*
+ * do not increment RPIPE avail for the WA_SEG_DELAYED case
+ * since it has not been submitted to the RPIPE.
+ */
+ /* fall through */
+ case WA_SEG_DELAYED:
+ xfer->segs_done++;
+ current_seg->status = status;
+ break;
+ case WA_SEG_ABORTED:
+ break;
+ default:
+ WARN(1, "%s: xfer 0x%08X#%d. bad seg status = %d\n",
+ __func__, wa_xfer_id(xfer), index,
+ current_seg->status);
+ break;
+ }
+ }
+}
+
+/* Populate the given urb based on the current isoc transfer state. */
+static int __wa_populate_buf_in_urb_isoc(struct wahc *wa,
+ struct urb *buf_in_urb, struct wa_xfer *xfer, struct wa_seg *seg)
+{
+ int urb_start_frame = seg->isoc_frame_index + seg->isoc_frame_offset;
+ int seg_index, total_len = 0, urb_frame_index = urb_start_frame;
+ struct usb_iso_packet_descriptor *iso_frame_desc =
+ xfer->urb->iso_frame_desc;
+ const int dti_packet_size = usb_endpoint_maxp(wa->dti_epd);
+ int next_frame_contiguous;
+ struct usb_iso_packet_descriptor *iso_frame;
+
+ BUG_ON(buf_in_urb->status == -EINPROGRESS);
+
+ /*
+ * If the current frame actual_length is contiguous with the next frame
+ * and actual_length is a multiple of the DTI endpoint max packet size,
+ * combine the current frame with the next frame in a single URB. This
+ * reduces the number of URBs that must be submitted in that case.
+ */
+ seg_index = seg->isoc_frame_index;
+ do {
+ next_frame_contiguous = 0;
+
+ iso_frame = &iso_frame_desc[urb_frame_index];
+ total_len += iso_frame->actual_length;
+ ++urb_frame_index;
+ ++seg_index;
+
+ if (seg_index < seg->isoc_frame_count) {
+ struct usb_iso_packet_descriptor *next_iso_frame;
+
+ next_iso_frame = &iso_frame_desc[urb_frame_index];
+
+ if ((iso_frame->offset + iso_frame->actual_length) ==
+ next_iso_frame->offset)
+ next_frame_contiguous = 1;
+ }
+ } while (next_frame_contiguous
+ && ((iso_frame->actual_length % dti_packet_size) == 0));
+
+ /* this should always be 0 before a resubmit. */
+ buf_in_urb->num_mapped_sgs = 0;
+ buf_in_urb->transfer_dma = xfer->urb->transfer_dma +
+ iso_frame_desc[urb_start_frame].offset;
+ buf_in_urb->transfer_buffer_length = total_len;
+ buf_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ buf_in_urb->transfer_buffer = NULL;
+ buf_in_urb->sg = NULL;
+ buf_in_urb->num_sgs = 0;
+ buf_in_urb->context = seg;
+
+ /* return the number of frames included in this URB. */
+ return seg_index - seg->isoc_frame_index;
+}
+
+/* Populate the given urb based on the current transfer state. */
+static int wa_populate_buf_in_urb(struct urb *buf_in_urb, struct wa_xfer *xfer,
+ unsigned int seg_idx, unsigned int bytes_transferred)
+{
+ int result = 0;
+ struct wa_seg *seg = xfer->seg[seg_idx];
+
+ BUG_ON(buf_in_urb->status == -EINPROGRESS);
+ /* this should always be 0 before a resubmit. */
+ buf_in_urb->num_mapped_sgs = 0;
+
+ if (xfer->is_dma) {
+ buf_in_urb->transfer_dma = xfer->urb->transfer_dma
+ + (seg_idx * xfer->seg_size);
+ buf_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ buf_in_urb->transfer_buffer = NULL;
+ buf_in_urb->sg = NULL;
+ buf_in_urb->num_sgs = 0;
+ } else {
+ /* do buffer or SG processing. */
+ buf_in_urb->transfer_flags &= ~URB_NO_TRANSFER_DMA_MAP;
+
+ if (xfer->urb->transfer_buffer) {
+ buf_in_urb->transfer_buffer =
+ xfer->urb->transfer_buffer
+ + (seg_idx * xfer->seg_size);
+ buf_in_urb->sg = NULL;
+ buf_in_urb->num_sgs = 0;
+ } else {
+ /* allocate an SG list to store seg_size bytes
+ and copy the subset of the xfer->urb->sg
+ that matches the buffer subset we are
+ about to read. */
+ buf_in_urb->sg = wa_xfer_create_subset_sg(
+ xfer->urb->sg,
+ seg_idx * xfer->seg_size,
+ bytes_transferred,
+ &(buf_in_urb->num_sgs));
+
+ if (!(buf_in_urb->sg)) {
+ buf_in_urb->num_sgs = 0;
+ result = -ENOMEM;
+ }
+ buf_in_urb->transfer_buffer = NULL;
+ }
+ }
+ buf_in_urb->transfer_buffer_length = bytes_transferred;
+ buf_in_urb->context = seg;
+
+ return result;
+}
+
+/*
+ * Process a xfer result completion message
+ *
+ * inbound transfers: need to schedule a buf_in_urb read
+ *
+ * FIXME: this function needs to be broken up in parts
+ */
+static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer,
+ struct wa_xfer_result *xfer_result)
+{
+ int result;
+ struct device *dev = &wa->usb_iface->dev;
+ unsigned long flags;
+ unsigned int seg_idx;
+ struct wa_seg *seg;
+ struct wa_rpipe *rpipe;
+ unsigned done = 0;
+ u8 usb_status;
+ unsigned rpipe_ready = 0;
+ unsigned bytes_transferred = le32_to_cpu(xfer_result->dwTransferLength);
+ struct urb *buf_in_urb = &(wa->buf_in_urbs[0]);
+
+ spin_lock_irqsave(&xfer->lock, flags);
+ seg_idx = xfer_result->bTransferSegment & 0x7f;
+ if (unlikely(seg_idx >= xfer->segs))
+ goto error_bad_seg;
+ seg = xfer->seg[seg_idx];
+ rpipe = xfer->ep->hcpriv;
+ usb_status = xfer_result->bTransferStatus;
+ dev_dbg(dev, "xfer %p ID 0x%08X#%u: bTransferStatus 0x%02x (seg status %u)\n",
+ xfer, wa_xfer_id(xfer), seg_idx, usb_status, seg->status);
+ if (seg->status == WA_SEG_ABORTED
+ || seg->status == WA_SEG_ERROR) /* already handled */
+ goto segment_aborted;
+ if (seg->status == WA_SEG_SUBMITTED) /* ops, got here */
+ seg->status = WA_SEG_PENDING; /* before wa_seg{_dto}_cb() */
+ if (seg->status != WA_SEG_PENDING) {
+ if (printk_ratelimit())
+ dev_err(dev, "xfer %p#%u: Bad segment state %u\n",
+ xfer, seg_idx, seg->status);
+ seg->status = WA_SEG_PENDING; /* workaround/"fix" it */
+ }
+ if (usb_status & 0x80) {
+ seg->result = wa_xfer_status_to_errno(usb_status);
+ dev_err(dev, "DTI: xfer %p 0x%08X:#%u failed (0x%02x)\n",
+ xfer, xfer->id, seg->index, usb_status);
+ seg->status = ((usb_status & 0x7F) == WA_XFER_STATUS_ABORTED) ?
+ WA_SEG_ABORTED : WA_SEG_ERROR;
+ goto error_complete;
+ }
+ /* FIXME: we ignore warnings, tally them for stats */
+ if (usb_status & 0x40) /* Warning?... */
+ usb_status = 0; /* ... pass */
+ /*
+ * If the last segment bit is set, complete the remaining segments.
+ * When the current segment is completed, either in wa_buf_in_cb for
+ * transfers with data or below for no data, the xfer will complete.
+ */
+ if (xfer_result->bTransferSegment & 0x80)
+ wa_complete_remaining_xfer_segs(xfer, seg->index + 1,
+ WA_SEG_DONE);
+ if (usb_pipeisoc(xfer->urb->pipe)
+ && (le32_to_cpu(xfer_result->dwNumOfPackets) > 0)) {
+ /* set up WA state to read the isoc packet status next. */
+ wa->dti_isoc_xfer_in_progress = wa_xfer_id(xfer);
+ wa->dti_isoc_xfer_seg = seg_idx;
+ wa->dti_state = WA_DTI_ISOC_PACKET_STATUS_PENDING;
+ } else if (xfer->is_inbound && !usb_pipeisoc(xfer->urb->pipe)
+ && (bytes_transferred > 0)) {
+ /* IN data phase: read to buffer */
+ seg->status = WA_SEG_DTI_PENDING;
+ result = wa_populate_buf_in_urb(buf_in_urb, xfer, seg_idx,
+ bytes_transferred);
+ if (result < 0)
+ goto error_buf_in_populate;
+ ++(wa->active_buf_in_urbs);
+ result = usb_submit_urb(buf_in_urb, GFP_ATOMIC);
+ if (result < 0) {
+ --(wa->active_buf_in_urbs);
+ goto error_submit_buf_in;
+ }
+ } else {
+ /* OUT data phase or no data, complete it -- */
+ seg->result = bytes_transferred;
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_DONE);
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ return;
+
+error_submit_buf_in:
+ if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
+ dev_err(dev, "DTI: URB max acceptable errors "
+ "exceeded, resetting device\n");
+ wa_reset_all(wa);
+ }
+ if (printk_ratelimit())
+ dev_err(dev, "xfer %p#%u: can't submit DTI data phase: %d\n",
+ xfer, seg_idx, result);
+ seg->result = result;
+ kfree(buf_in_urb->sg);
+ buf_in_urb->sg = NULL;
+error_buf_in_populate:
+ __wa_xfer_abort(xfer);
+ seg->status = WA_SEG_ERROR;
+error_complete:
+ xfer->segs_done++;
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ wa_complete_remaining_xfer_segs(xfer, seg->index + 1, seg->status);
+ done = __wa_xfer_is_done(xfer);
+ /*
+ * queue work item to clear STALL for control endpoints.
+ * Otherwise, let endpoint_reset take care of it.
+ */
+ if (((usb_status & 0x3f) == WA_XFER_STATUS_HALTED) &&
+ usb_endpoint_xfer_control(&xfer->ep->desc) &&
+ done) {
+
+ dev_info(dev, "Control EP stall. Queue delayed work.\n");
+ spin_lock(&wa->xfer_list_lock);
+ /* move xfer from xfer_list to xfer_errored_list. */
+ list_move_tail(&xfer->list_node, &wa->xfer_errored_list);
+ spin_unlock(&wa->xfer_list_lock);
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ queue_work(wusbd, &wa->xfer_error_work);
+ } else {
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ }
+
+ return;
+
+error_bad_seg:
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ wa_urb_dequeue(wa, xfer->urb, -ENOENT);
+ if (printk_ratelimit())
+ dev_err(dev, "xfer %p#%u: bad segment\n", xfer, seg_idx);
+ if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
+ dev_err(dev, "DTI: URB max acceptable errors "
+ "exceeded, resetting device\n");
+ wa_reset_all(wa);
+ }
+ return;
+
+segment_aborted:
+ /* nothing to do, as the aborter did the completion */
+ spin_unlock_irqrestore(&xfer->lock, flags);
+}
+
+/*
+ * Process a isochronous packet status message
+ *
+ * inbound transfers: need to schedule a buf_in_urb read
+ */
+static int wa_process_iso_packet_status(struct wahc *wa, struct urb *urb)
+{
+ struct device *dev = &wa->usb_iface->dev;
+ struct wa_xfer_packet_status_hwaiso *packet_status;
+ struct wa_xfer_packet_status_len_hwaiso *status_array;
+ struct wa_xfer *xfer;
+ unsigned long flags;
+ struct wa_seg *seg;
+ struct wa_rpipe *rpipe;
+ unsigned done = 0, dti_busy = 0, data_frame_count = 0, seg_index;
+ unsigned first_frame_index = 0, rpipe_ready = 0;
+ size_t expected_size;
+
+ /* We have a xfer result buffer; check it */
+ dev_dbg(dev, "DTI: isoc packet status %d bytes at %p\n",
+ urb->actual_length, urb->transfer_buffer);
+ packet_status = (struct wa_xfer_packet_status_hwaiso *)(wa->dti_buf);
+ if (packet_status->bPacketType != WA_XFER_ISO_PACKET_STATUS) {
+ dev_err(dev, "DTI Error: isoc packet status--bad type 0x%02x\n",
+ packet_status->bPacketType);
+ goto error_parse_buffer;
+ }
+ xfer = wa_xfer_get_by_id(wa, wa->dti_isoc_xfer_in_progress);
+ if (xfer == NULL) {
+ dev_err(dev, "DTI Error: isoc packet status--unknown xfer 0x%08x\n",
+ wa->dti_isoc_xfer_in_progress);
+ goto error_parse_buffer;
+ }
+ spin_lock_irqsave(&xfer->lock, flags);
+ if (unlikely(wa->dti_isoc_xfer_seg >= xfer->segs))
+ goto error_bad_seg;
+ seg = xfer->seg[wa->dti_isoc_xfer_seg];
+ rpipe = xfer->ep->hcpriv;
+ expected_size = struct_size(packet_status, PacketStatus,
+ seg->isoc_frame_count);
+ if (urb->actual_length != expected_size) {
+ dev_err(dev, "DTI Error: isoc packet status--bad urb length (%d bytes vs %zu needed)\n",
+ urb->actual_length, expected_size);
+ goto error_bad_seg;
+ }
+ if (le16_to_cpu(packet_status->wLength) != expected_size) {
+ dev_err(dev, "DTI Error: isoc packet status--bad length %u\n",
+ le16_to_cpu(packet_status->wLength));
+ goto error_bad_seg;
+ }
+ /* write isoc packet status and lengths back to the xfer urb. */
+ status_array = packet_status->PacketStatus;
+ xfer->urb->start_frame =
+ wa->wusb->usb_hcd.driver->get_frame_number(&wa->wusb->usb_hcd);
+ for (seg_index = 0; seg_index < seg->isoc_frame_count; ++seg_index) {
+ struct usb_iso_packet_descriptor *iso_frame_desc =
+ xfer->urb->iso_frame_desc;
+ const int xfer_frame_index =
+ seg->isoc_frame_offset + seg_index;
+
+ iso_frame_desc[xfer_frame_index].status =
+ wa_xfer_status_to_errno(
+ le16_to_cpu(status_array[seg_index].PacketStatus));
+ iso_frame_desc[xfer_frame_index].actual_length =
+ le16_to_cpu(status_array[seg_index].PacketLength);
+ /* track the number of frames successfully transferred. */
+ if (iso_frame_desc[xfer_frame_index].actual_length > 0) {
+ /* save the starting frame index for buf_in_urb. */
+ if (!data_frame_count)
+ first_frame_index = seg_index;
+ ++data_frame_count;
+ }
+ }
+
+ if (xfer->is_inbound && data_frame_count) {
+ int result, total_frames_read = 0, urb_index = 0;
+ struct urb *buf_in_urb;
+
+ /* IN data phase: read to buffer */
+ seg->status = WA_SEG_DTI_PENDING;
+
+ /* start with the first frame with data. */
+ seg->isoc_frame_index = first_frame_index;
+ /* submit up to WA_MAX_BUF_IN_URBS read URBs. */
+ do {
+ int urb_frame_index, urb_frame_count;
+ struct usb_iso_packet_descriptor *iso_frame_desc;
+
+ buf_in_urb = &(wa->buf_in_urbs[urb_index]);
+ urb_frame_count = __wa_populate_buf_in_urb_isoc(wa,
+ buf_in_urb, xfer, seg);
+ /* advance frame index to start of next read URB. */
+ seg->isoc_frame_index += urb_frame_count;
+ total_frames_read += urb_frame_count;
+
+ ++(wa->active_buf_in_urbs);
+ result = usb_submit_urb(buf_in_urb, GFP_ATOMIC);
+
+ /* skip 0-byte frames. */
+ urb_frame_index =
+ seg->isoc_frame_offset + seg->isoc_frame_index;
+ iso_frame_desc =
+ &(xfer->urb->iso_frame_desc[urb_frame_index]);
+ while ((seg->isoc_frame_index <
+ seg->isoc_frame_count) &&
+ (iso_frame_desc->actual_length == 0)) {
+ ++(seg->isoc_frame_index);
+ ++iso_frame_desc;
+ }
+ ++urb_index;
+
+ } while ((result == 0) && (urb_index < WA_MAX_BUF_IN_URBS)
+ && (seg->isoc_frame_index <
+ seg->isoc_frame_count));
+
+ if (result < 0) {
+ --(wa->active_buf_in_urbs);
+ dev_err(dev, "DTI Error: Could not submit buf in URB (%d)",
+ result);
+ wa_reset_all(wa);
+ } else if (data_frame_count > total_frames_read)
+ /* If we need to read more frames, set DTI busy. */
+ dti_busy = 1;
+ } else {
+ /* OUT transfer or no more IN data, complete it -- */
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ done = __wa_xfer_mark_seg_as_done(xfer, seg, WA_SEG_DONE);
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (dti_busy)
+ wa->dti_state = WA_DTI_BUF_IN_DATA_PENDING;
+ else
+ wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING;
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ wa_xfer_put(xfer);
+ return dti_busy;
+
+error_bad_seg:
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ wa_xfer_put(xfer);
+error_parse_buffer:
+ return dti_busy;
+}
+
+/*
+ * Callback for the IN data phase
+ *
+ * If successful transition state; otherwise, take a note of the
+ * error, mark this segment done and try completion.
+ *
+ * Note we don't access until we are sure that the transfer hasn't
+ * been cancelled (ECONNRESET, ENOENT), which could mean that
+ * seg->xfer could be already gone.
+ */
+static void wa_buf_in_cb(struct urb *urb)
+{
+ struct wa_seg *seg = urb->context;
+ struct wa_xfer *xfer = seg->xfer;
+ struct wahc *wa;
+ struct device *dev;
+ struct wa_rpipe *rpipe;
+ unsigned rpipe_ready = 0, isoc_data_frame_count = 0;
+ unsigned long flags;
+ int resubmit_dti = 0, active_buf_in_urbs;
+ u8 done = 0;
+
+ /* free the sg if it was used. */
+ kfree(urb->sg);
+ urb->sg = NULL;
+
+ spin_lock_irqsave(&xfer->lock, flags);
+ wa = xfer->wa;
+ dev = &wa->usb_iface->dev;
+ --(wa->active_buf_in_urbs);
+ active_buf_in_urbs = wa->active_buf_in_urbs;
+ rpipe = xfer->ep->hcpriv;
+
+ if (usb_pipeisoc(xfer->urb->pipe)) {
+ struct usb_iso_packet_descriptor *iso_frame_desc =
+ xfer->urb->iso_frame_desc;
+ int seg_index;
+
+ /*
+ * Find the next isoc frame with data and count how many
+ * frames with data remain.
+ */
+ seg_index = seg->isoc_frame_index;
+ while (seg_index < seg->isoc_frame_count) {
+ const int urb_frame_index =
+ seg->isoc_frame_offset + seg_index;
+
+ if (iso_frame_desc[urb_frame_index].actual_length > 0) {
+ /* save the index of the next frame with data */
+ if (!isoc_data_frame_count)
+ seg->isoc_frame_index = seg_index;
+ ++isoc_data_frame_count;
+ }
+ ++seg_index;
+ }
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+
+ switch (urb->status) {
+ case 0:
+ spin_lock_irqsave(&xfer->lock, flags);
+
+ seg->result += urb->actual_length;
+ if (isoc_data_frame_count > 0) {
+ int result, urb_frame_count;
+
+ /* submit a read URB for the next frame with data. */
+ urb_frame_count = __wa_populate_buf_in_urb_isoc(wa, urb,
+ xfer, seg);
+ /* advance index to start of next read URB. */
+ seg->isoc_frame_index += urb_frame_count;
+ ++(wa->active_buf_in_urbs);
+ result = usb_submit_urb(urb, GFP_ATOMIC);
+ if (result < 0) {
+ --(wa->active_buf_in_urbs);
+ dev_err(dev, "DTI Error: Could not submit buf in URB (%d)",
+ result);
+ wa_reset_all(wa);
+ }
+ /*
+ * If we are in this callback and
+ * isoc_data_frame_count > 0, it means that the dti_urb
+ * submission was delayed in wa_dti_cb. Once
+ * we submit the last buf_in_urb, we can submit the
+ * delayed dti_urb.
+ */
+ resubmit_dti = (isoc_data_frame_count ==
+ urb_frame_count);
+ } else if (active_buf_in_urbs == 0) {
+ dev_dbg(dev,
+ "xfer %p 0x%08X#%u: data in done (%zu bytes)\n",
+ xfer, wa_xfer_id(xfer), seg->index,
+ seg->result);
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ done = __wa_xfer_mark_seg_as_done(xfer, seg,
+ WA_SEG_DONE);
+ }
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ break;
+ case -ECONNRESET: /* URB unlinked; no need to do anything */
+ case -ENOENT: /* as it was done by the who unlinked us */
+ break;
+ default: /* Other errors ... */
+ /*
+ * Error on data buf read. Only resubmit DTI if it hasn't
+ * already been done by previously hitting this error or by a
+ * successful completion of the previous buf_in_urb.
+ */
+ resubmit_dti = wa->dti_state != WA_DTI_TRANSFER_RESULT_PENDING;
+ spin_lock_irqsave(&xfer->lock, flags);
+ if (printk_ratelimit())
+ dev_err(dev, "xfer %p 0x%08X#%u: data in error %d\n",
+ xfer, wa_xfer_id(xfer), seg->index,
+ urb->status);
+ if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)){
+ dev_err(dev, "DTO: URB max acceptable errors "
+ "exceeded, resetting device\n");
+ wa_reset_all(wa);
+ }
+ seg->result = urb->status;
+ rpipe_ready = rpipe_avail_inc(rpipe);
+ if (active_buf_in_urbs == 0)
+ done = __wa_xfer_mark_seg_as_done(xfer, seg,
+ WA_SEG_ERROR);
+ else
+ __wa_xfer_abort(xfer);
+ spin_unlock_irqrestore(&xfer->lock, flags);
+ if (done)
+ wa_xfer_completion(xfer);
+ if (rpipe_ready)
+ wa_xfer_delayed_run(rpipe);
+ }
+
+ if (resubmit_dti) {
+ int result;
+
+ wa->dti_state = WA_DTI_TRANSFER_RESULT_PENDING;
+
+ result = usb_submit_urb(wa->dti_urb, GFP_ATOMIC);
+ if (result < 0) {
+ dev_err(dev, "DTI Error: Could not submit DTI URB (%d)\n",
+ result);
+ wa_reset_all(wa);
+ }
+ }
+}
+
+/*
+ * Handle an incoming transfer result buffer
+ *
+ * Given a transfer result buffer, it completes the transfer (possibly
+ * scheduling and buffer in read) and then resubmits the DTI URB for a
+ * new transfer result read.
+ *
+ *
+ * The xfer_result DTI URB state machine
+ *
+ * States: OFF | RXR (Read-Xfer-Result) | RBI (Read-Buffer-In)
+ *
+ * We start in OFF mode, the first xfer_result notification [through
+ * wa_handle_notif_xfer()] moves us to RXR by posting the DTI-URB to
+ * read.
+ *
+ * We receive a buffer -- if it is not a xfer_result, we complain and
+ * repost the DTI-URB. If it is a xfer_result then do the xfer seg
+ * request accounting. If it is an IN segment, we move to RBI and post
+ * a BUF-IN-URB to the right buffer. The BUF-IN-URB callback will
+ * repost the DTI-URB and move to RXR state. if there was no IN
+ * segment, it will repost the DTI-URB.
+ *
+ * We go back to OFF when we detect a ENOENT or ESHUTDOWN (or too many
+ * errors) in the URBs.
+ */
+static void wa_dti_cb(struct urb *urb)
+{
+ int result, dti_busy = 0;
+ struct wahc *wa = urb->context;
+ struct device *dev = &wa->usb_iface->dev;
+ u32 xfer_id;
+ u8 usb_status;
+
+ BUG_ON(wa->dti_urb != urb);
+ switch (wa->dti_urb->status) {
+ case 0:
+ if (wa->dti_state == WA_DTI_TRANSFER_RESULT_PENDING) {
+ struct wa_xfer_result *xfer_result;
+ struct wa_xfer *xfer;
+
+ /* We have a xfer result buffer; check it */
+ dev_dbg(dev, "DTI: xfer result %d bytes at %p\n",
+ urb->actual_length, urb->transfer_buffer);
+ if (urb->actual_length != sizeof(*xfer_result)) {
+ dev_err(dev, "DTI Error: xfer result--bad size xfer result (%d bytes vs %zu needed)\n",
+ urb->actual_length,
+ sizeof(*xfer_result));
+ break;
+ }
+ xfer_result = (struct wa_xfer_result *)(wa->dti_buf);
+ if (xfer_result->hdr.bLength != sizeof(*xfer_result)) {
+ dev_err(dev, "DTI Error: xfer result--bad header length %u\n",
+ xfer_result->hdr.bLength);
+ break;
+ }
+ if (xfer_result->hdr.bNotifyType != WA_XFER_RESULT) {
+ dev_err(dev, "DTI Error: xfer result--bad header type 0x%02x\n",
+ xfer_result->hdr.bNotifyType);
+ break;
+ }
+ xfer_id = le32_to_cpu(xfer_result->dwTransferID);
+ usb_status = xfer_result->bTransferStatus & 0x3f;
+ if (usb_status == WA_XFER_STATUS_NOT_FOUND) {
+ /* taken care of already */
+ dev_dbg(dev, "%s: xfer 0x%08X#%u not found.\n",
+ __func__, xfer_id,
+ xfer_result->bTransferSegment & 0x7f);
+ break;
+ }
+ xfer = wa_xfer_get_by_id(wa, xfer_id);
+ if (xfer == NULL) {
+ /* FIXME: transaction not found. */
+ dev_err(dev, "DTI Error: xfer result--unknown xfer 0x%08x (status 0x%02x)\n",
+ xfer_id, usb_status);
+ break;
+ }
+ wa_xfer_result_chew(wa, xfer, xfer_result);
+ wa_xfer_put(xfer);
+ } else if (wa->dti_state == WA_DTI_ISOC_PACKET_STATUS_PENDING) {
+ dti_busy = wa_process_iso_packet_status(wa, urb);
+ } else {
+ dev_err(dev, "DTI Error: unexpected EP state = %d\n",
+ wa->dti_state);
+ }
+ break;
+ case -ENOENT: /* (we killed the URB)...so, no broadcast */
+ case -ESHUTDOWN: /* going away! */
+ dev_dbg(dev, "DTI: going down! %d\n", urb->status);
+ goto out;
+ default:
+ /* Unknown error */
+ if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS,
+ EDC_ERROR_TIMEFRAME)) {
+ dev_err(dev, "DTI: URB max acceptable errors "
+ "exceeded, resetting device\n");
+ wa_reset_all(wa);
+ goto out;
+ }
+ if (printk_ratelimit())
+ dev_err(dev, "DTI: URB error %d\n", urb->status);
+ break;
+ }
+
+ /* Resubmit the DTI URB if we are not busy processing isoc in frames. */
+ if (!dti_busy) {
+ result = usb_submit_urb(wa->dti_urb, GFP_ATOMIC);
+ if (result < 0) {
+ dev_err(dev, "DTI Error: Could not submit DTI URB (%d)\n",
+ result);
+ wa_reset_all(wa);
+ }
+ }
+out:
+ return;
+}
+
+/*
+ * Initialize the DTI URB for reading transfer result notifications and also
+ * the buffer-in URB, for reading buffers. Then we just submit the DTI URB.
+ */
+int wa_dti_start(struct wahc *wa)
+{
+ const struct usb_endpoint_descriptor *dti_epd = wa->dti_epd;
+ struct device *dev = &wa->usb_iface->dev;
+ int result = -ENOMEM, index;
+
+ if (wa->dti_urb != NULL) /* DTI URB already started */
+ goto out;
+
+ wa->dti_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (wa->dti_urb == NULL)
+ goto error_dti_urb_alloc;
+ usb_fill_bulk_urb(
+ wa->dti_urb, wa->usb_dev,
+ usb_rcvbulkpipe(wa->usb_dev, 0x80 | dti_epd->bEndpointAddress),
+ wa->dti_buf, wa->dti_buf_size,
+ wa_dti_cb, wa);
+
+ /* init the buf in URBs */
+ for (index = 0; index < WA_MAX_BUF_IN_URBS; ++index) {
+ usb_fill_bulk_urb(
+ &(wa->buf_in_urbs[index]), wa->usb_dev,
+ usb_rcvbulkpipe(wa->usb_dev,
+ 0x80 | dti_epd->bEndpointAddress),
+ NULL, 0, wa_buf_in_cb, wa);
+ }
+ result = usb_submit_urb(wa->dti_urb, GFP_KERNEL);
+ if (result < 0) {
+ dev_err(dev, "DTI Error: Could not submit DTI URB (%d) resetting\n",
+ result);
+ goto error_dti_urb_submit;
+ }
+out:
+ return 0;
+
+error_dti_urb_submit:
+ usb_put_urb(wa->dti_urb);
+ wa->dti_urb = NULL;
+error_dti_urb_alloc:
+ return result;
+}
+EXPORT_SYMBOL_GPL(wa_dti_start);
+/*
+ * Transfer complete notification
+ *
+ * Called from the notif.c code. We get a notification on EP2 saying
+ * that some endpoint has some transfer result data available. We are
+ * about to read it.
+ *
+ * To speed up things, we always have a URB reading the DTI URB; we
+ * don't really set it up and start it until the first xfer complete
+ * notification arrives, which is what we do here.
+ *
+ * Follow up in wa_dti_cb(), as that's where the whole state
+ * machine starts.
+ *
+ * @wa shall be referenced
+ */
+void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr)
+{
+ struct device *dev = &wa->usb_iface->dev;
+ struct wa_notif_xfer *notif_xfer;
+ const struct usb_endpoint_descriptor *dti_epd = wa->dti_epd;
+
+ notif_xfer = container_of(notif_hdr, struct wa_notif_xfer, hdr);
+ BUG_ON(notif_hdr->bNotifyType != WA_NOTIF_TRANSFER);
+
+ if ((0x80 | notif_xfer->bEndpoint) != dti_epd->bEndpointAddress) {
+ /* FIXME: hardcoded limitation, adapt */
+ dev_err(dev, "BUG: DTI ep is %u, not %u (hack me)\n",
+ notif_xfer->bEndpoint, dti_epd->bEndpointAddress);
+ goto error;
+ }
+
+ /* attempt to start the DTI ep processing. */
+ if (wa_dti_start(wa) < 0)
+ goto error;
+
+ return;
+
+error:
+ wa_reset_all(wa);
+}
diff --git a/drivers/staging/wusbcore/wusbhc.c b/drivers/staging/wusbcore/wusbhc.c
new file mode 100644
index 000000000000..d0b404d258e8
--- /dev/null
+++ b/drivers/staging/wusbcore/wusbhc.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB Host Controller
+ * sysfs glue, wusbcore module support and life cycle management
+ *
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * Creation/destruction of wusbhc is split in two parts; that that
+ * doesn't require the HCD to be added (wusbhc_{create,destroy}) and
+ * the one that requires (phase B, wusbhc_b_{create,destroy}).
+ *
+ * This is so because usb_add_hcd() will start the HC, and thus, all
+ * the HC specific stuff has to be already initialized (like sysfs
+ * thingies).
+ */
+#include <linux/device.h>
+#include <linux/module.h>
+#include "wusbhc.h"
+
+/**
+ * Extract the wusbhc that corresponds to a USB Host Controller class device
+ *
+ * WARNING! Apply only if @dev is that of a
+ * wusbhc.usb_hcd.self->class_dev; otherwise, you loose.
+ */
+static struct wusbhc *usbhc_dev_to_wusbhc(struct device *dev)
+{
+ struct usb_bus *usb_bus = dev_get_drvdata(dev);
+ struct usb_hcd *usb_hcd = bus_to_hcd(usb_bus);
+ return usb_hcd_to_wusbhc(usb_hcd);
+}
+
+/*
+ * Show & store the current WUSB trust timeout
+ *
+ * We don't do locking--it is an 'atomic' value.
+ *
+ * The units that we store/show are always MILLISECONDS. However, the
+ * value of trust_timeout is jiffies.
+ */
+static ssize_t wusb_trust_timeout_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", wusbhc->trust_timeout);
+}
+
+static ssize_t wusb_trust_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+ ssize_t result = -ENOSYS;
+ unsigned trust_timeout;
+
+ result = sscanf(buf, "%u", &trust_timeout);
+ if (result != 1) {
+ result = -EINVAL;
+ goto out;
+ }
+ wusbhc->trust_timeout = min_t(unsigned, trust_timeout, 500);
+ cancel_delayed_work(&wusbhc->keep_alive_timer);
+ flush_workqueue(wusbd);
+ queue_delayed_work(wusbd, &wusbhc->keep_alive_timer,
+ msecs_to_jiffies(wusbhc->trust_timeout / 2));
+out:
+ return result < 0 ? result : size;
+}
+static DEVICE_ATTR_RW(wusb_trust_timeout);
+
+/*
+ * Show the current WUSB CHID.
+ */
+static ssize_t wusb_chid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+ const struct wusb_ckhdid *chid;
+
+ if (wusbhc->wuie_host_info != NULL)
+ chid = &wusbhc->wuie_host_info->CHID;
+ else
+ chid = &wusb_ckhdid_zero;
+
+ return sprintf(buf, "%16ph\n", chid->data);
+}
+
+/*
+ * Store a new CHID.
+ *
+ * - Write an all zeros CHID and it will stop the controller
+ * - Write a non-zero CHID and it will start it.
+ *
+ * See wusbhc_chid_set() for more info.
+ */
+static ssize_t wusb_chid_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+ struct wusb_ckhdid chid;
+ ssize_t result;
+
+ result = sscanf(buf,
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx "
+ "%02hhx %02hhx %02hhx %02hhx\n",
+ &chid.data[0] , &chid.data[1] ,
+ &chid.data[2] , &chid.data[3] ,
+ &chid.data[4] , &chid.data[5] ,
+ &chid.data[6] , &chid.data[7] ,
+ &chid.data[8] , &chid.data[9] ,
+ &chid.data[10], &chid.data[11],
+ &chid.data[12], &chid.data[13],
+ &chid.data[14], &chid.data[15]);
+ if (result != 16) {
+ dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits): "
+ "%d\n", (int)result);
+ return -EINVAL;
+ }
+ result = wusbhc_chid_set(wusbhc, &chid);
+ return result < 0 ? result : size;
+}
+static DEVICE_ATTR_RW(wusb_chid);
+
+
+static ssize_t wusb_phy_rate_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+
+ return sprintf(buf, "%d\n", wusbhc->phy_rate);
+}
+
+static ssize_t wusb_phy_rate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+ uint8_t phy_rate;
+ ssize_t result;
+
+ result = sscanf(buf, "%hhu", &phy_rate);
+ if (result != 1)
+ return -EINVAL;
+ if (phy_rate >= UWB_PHY_RATE_INVALID)
+ return -EINVAL;
+
+ wusbhc->phy_rate = phy_rate;
+ return size;
+}
+static DEVICE_ATTR_RW(wusb_phy_rate);
+
+static ssize_t wusb_dnts_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+
+ return sprintf(buf, "num slots: %d\ninterval: %dms\n",
+ wusbhc->dnts_num_slots, wusbhc->dnts_interval);
+}
+
+static ssize_t wusb_dnts_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+ uint8_t num_slots, interval;
+ ssize_t result;
+
+ result = sscanf(buf, "%hhu %hhu", &num_slots, &interval);
+
+ if (result != 2)
+ return -EINVAL;
+
+ wusbhc->dnts_num_slots = num_slots;
+ wusbhc->dnts_interval = interval;
+
+ return size;
+}
+static DEVICE_ATTR_RW(wusb_dnts);
+
+static ssize_t wusb_retry_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+
+ return sprintf(buf, "%d\n", wusbhc->retry_count);
+}
+
+static ssize_t wusb_retry_count_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev);
+ uint8_t retry_count;
+ ssize_t result;
+
+ result = sscanf(buf, "%hhu", &retry_count);
+
+ if (result != 1)
+ return -EINVAL;
+
+ wusbhc->retry_count = max_t(uint8_t, retry_count,
+ WUSB_RETRY_COUNT_MAX);
+
+ return size;
+}
+static DEVICE_ATTR_RW(wusb_retry_count);
+
+/* Group all the WUSBHC attributes */
+static struct attribute *wusbhc_attrs[] = {
+ &dev_attr_wusb_trust_timeout.attr,
+ &dev_attr_wusb_chid.attr,
+ &dev_attr_wusb_phy_rate.attr,
+ &dev_attr_wusb_dnts.attr,
+ &dev_attr_wusb_retry_count.attr,
+ NULL,
+};
+
+static const struct attribute_group wusbhc_attr_group = {
+ .name = NULL, /* we want them in the same directory */
+ .attrs = wusbhc_attrs,
+};
+
+/*
+ * Create a wusbhc instance
+ *
+ * NOTEs:
+ *
+ * - assumes *wusbhc has been zeroed and wusbhc->usb_hcd has been
+ * initialized but not added.
+ *
+ * - fill out ports_max, mmcies_max and mmcie_{add,rm} before calling.
+ *
+ * - fill out wusbhc->uwb_rc and refcount it before calling
+ * - fill out the wusbhc->sec_modes array
+ */
+int wusbhc_create(struct wusbhc *wusbhc)
+{
+ int result = 0;
+
+ /* set defaults. These can be overwritten using sysfs attributes. */
+ wusbhc->trust_timeout = WUSB_TRUST_TIMEOUT_MS;
+ wusbhc->phy_rate = UWB_PHY_RATE_INVALID - 1;
+ wusbhc->dnts_num_slots = 4;
+ wusbhc->dnts_interval = 2;
+ wusbhc->retry_count = WUSB_RETRY_COUNT_INFINITE;
+
+ mutex_init(&wusbhc->mutex);
+ result = wusbhc_mmcie_create(wusbhc);
+ if (result < 0)
+ goto error_mmcie_create;
+ result = wusbhc_devconnect_create(wusbhc);
+ if (result < 0)
+ goto error_devconnect_create;
+ result = wusbhc_rh_create(wusbhc);
+ if (result < 0)
+ goto error_rh_create;
+ result = wusbhc_sec_create(wusbhc);
+ if (result < 0)
+ goto error_sec_create;
+ return 0;
+
+error_sec_create:
+ wusbhc_rh_destroy(wusbhc);
+error_rh_create:
+ wusbhc_devconnect_destroy(wusbhc);
+error_devconnect_create:
+ wusbhc_mmcie_destroy(wusbhc);
+error_mmcie_create:
+ return result;
+}
+EXPORT_SYMBOL_GPL(wusbhc_create);
+
+static inline struct kobject *wusbhc_kobj(struct wusbhc *wusbhc)
+{
+ return &wusbhc->usb_hcd.self.controller->kobj;
+}
+
+/*
+ * Phase B of a wusbhc instance creation
+ *
+ * Creates fields that depend on wusbhc->usb_hcd having been
+ * added. This is where we create the sysfs files in
+ * /sys/class/usb_host/usb_hostX/.
+ *
+ * NOTE: Assumes wusbhc->usb_hcd has been already added by the upper
+ * layer (hwahc or whci)
+ */
+int wusbhc_b_create(struct wusbhc *wusbhc)
+{
+ int result = 0;
+ struct device *dev = wusbhc->usb_hcd.self.controller;
+
+ result = sysfs_create_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group);
+ if (result < 0) {
+ dev_err(dev, "Cannot register WUSBHC attributes: %d\n",
+ result);
+ goto error_create_attr_group;
+ }
+
+ return 0;
+error_create_attr_group:
+ return result;
+}
+EXPORT_SYMBOL_GPL(wusbhc_b_create);
+
+void wusbhc_b_destroy(struct wusbhc *wusbhc)
+{
+ wusbhc_pal_unregister(wusbhc);
+ sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group);
+}
+EXPORT_SYMBOL_GPL(wusbhc_b_destroy);
+
+void wusbhc_destroy(struct wusbhc *wusbhc)
+{
+ wusbhc_sec_destroy(wusbhc);
+ wusbhc_rh_destroy(wusbhc);
+ wusbhc_devconnect_destroy(wusbhc);
+ wusbhc_mmcie_destroy(wusbhc);
+}
+EXPORT_SYMBOL_GPL(wusbhc_destroy);
+
+struct workqueue_struct *wusbd;
+EXPORT_SYMBOL_GPL(wusbd);
+
+/*
+ * WUSB Cluster ID allocation map
+ *
+ * Each WUSB bus in a channel is identified with a Cluster Id in the
+ * unauth address pace (WUSB1.0[4.3]). We take the range 0xe0 to 0xff
+ * (that's space for 31 WUSB controllers, as 0xff can't be taken). We
+ * start taking from 0xff, 0xfe, 0xfd... (hence the += or -= 0xff).
+ *
+ * For each one we taken, we pin it in the bitap
+ */
+#define CLUSTER_IDS 32
+static DECLARE_BITMAP(wusb_cluster_id_table, CLUSTER_IDS);
+static DEFINE_SPINLOCK(wusb_cluster_ids_lock);
+
+/*
+ * Get a WUSB Cluster ID
+ *
+ * Need to release with wusb_cluster_id_put() when done w/ it.
+ */
+/* FIXME: coordinate with the choose_addres() from the USB stack */
+/* we want to leave the top of the 128 range for cluster addresses and
+ * the bottom for device addresses (as we map them one on one with
+ * ports). */
+u8 wusb_cluster_id_get(void)
+{
+ u8 id;
+ spin_lock(&wusb_cluster_ids_lock);
+ id = find_first_zero_bit(wusb_cluster_id_table, CLUSTER_IDS);
+ if (id >= CLUSTER_IDS) {
+ id = 0;
+ goto out;
+ }
+ set_bit(id, wusb_cluster_id_table);
+ id = (u8) 0xff - id;
+out:
+ spin_unlock(&wusb_cluster_ids_lock);
+ return id;
+
+}
+EXPORT_SYMBOL_GPL(wusb_cluster_id_get);
+
+/*
+ * Release a WUSB Cluster ID
+ *
+ * Obtained it with wusb_cluster_id_get()
+ */
+void wusb_cluster_id_put(u8 id)
+{
+ id = 0xff - id;
+ BUG_ON(id >= CLUSTER_IDS);
+ spin_lock(&wusb_cluster_ids_lock);
+ WARN_ON(!test_bit(id, wusb_cluster_id_table));
+ clear_bit(id, wusb_cluster_id_table);
+ spin_unlock(&wusb_cluster_ids_lock);
+}
+EXPORT_SYMBOL_GPL(wusb_cluster_id_put);
+
+/**
+ * wusbhc_giveback_urb - return an URB to the USB core
+ * @wusbhc: the host controller the URB is from.
+ * @urb: the URB.
+ * @status: the URB's status.
+ *
+ * Return an URB to the USB core doing some additional WUSB specific
+ * processing.
+ *
+ * - After a successful transfer, update the trust timeout timestamp
+ * for the WUSB device.
+ *
+ * - [WUSB] sections 4.13 and 7.5.1 specify the stop retransmission
+ * condition for the WCONNECTACK_IE is that the host has observed
+ * the associated device responding to a control transfer.
+ */
+void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, int status)
+{
+ struct wusb_dev *wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc,
+ urb->dev);
+
+ if (status == 0 && wusb_dev) {
+ wusb_dev->entry_ts = jiffies;
+
+ /* wusbhc_devconnect_acked() can't be called from
+ atomic context so defer it to a work queue. */
+ if (!list_empty(&wusb_dev->cack_node))
+ queue_work(wusbd, &wusb_dev->devconnect_acked_work);
+ else
+ wusb_dev_put(wusb_dev);
+ }
+
+ usb_hcd_giveback_urb(&wusbhc->usb_hcd, urb, status);
+}
+EXPORT_SYMBOL_GPL(wusbhc_giveback_urb);
+
+/**
+ * wusbhc_reset_all - reset the HC hardware
+ * @wusbhc: the host controller to reset.
+ *
+ * Request a full hardware reset of the chip. This will also reset
+ * the radio controller and any other PALs.
+ */
+void wusbhc_reset_all(struct wusbhc *wusbhc)
+{
+ if (wusbhc->uwb_rc)
+ uwb_rc_reset_all(wusbhc->uwb_rc);
+}
+EXPORT_SYMBOL_GPL(wusbhc_reset_all);
+
+static struct notifier_block wusb_usb_notifier = {
+ .notifier_call = wusb_usb_ncb,
+ .priority = INT_MAX /* Need to be called first of all */
+};
+
+static int __init wusbcore_init(void)
+{
+ int result;
+ result = wusb_crypto_init();
+ if (result < 0)
+ goto error_crypto_init;
+ /* WQ is singlethread because we need to serialize notifications */
+ wusbd = create_singlethread_workqueue("wusbd");
+ if (wusbd == NULL) {
+ result = -ENOMEM;
+ printk(KERN_ERR "WUSB-core: Cannot create wusbd workqueue\n");
+ goto error_wusbd_create;
+ }
+ usb_register_notify(&wusb_usb_notifier);
+ bitmap_zero(wusb_cluster_id_table, CLUSTER_IDS);
+ set_bit(0, wusb_cluster_id_table); /* reserve Cluster ID 0xff */
+ return 0;
+
+error_wusbd_create:
+ wusb_crypto_exit();
+error_crypto_init:
+ return result;
+
+}
+module_init(wusbcore_init);
+
+static void __exit wusbcore_exit(void)
+{
+ clear_bit(0, wusb_cluster_id_table);
+ if (!bitmap_empty(wusb_cluster_id_table, CLUSTER_IDS)) {
+ printk(KERN_ERR "BUG: WUSB Cluster IDs not released on exit: %*pb\n",
+ CLUSTER_IDS, wusb_cluster_id_table);
+ WARN_ON(1);
+ }
+ usb_unregister_notify(&wusb_usb_notifier);
+ destroy_workqueue(wusbd);
+ wusb_crypto_exit();
+}
+module_exit(wusbcore_exit);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Wireless USB core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/wusbcore/wusbhc.h b/drivers/staging/wusbcore/wusbhc.h
new file mode 100644
index 000000000000..716244a2ec44
--- /dev/null
+++ b/drivers/staging/wusbcore/wusbhc.h
@@ -0,0 +1,487 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Wireless USB Host Controller
+ * Common infrastructure for WHCI and HWA WUSB-HC drivers
+ *
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This driver implements parts common to all Wireless USB Host
+ * Controllers (struct wusbhc, embedding a struct usb_hcd) and is used
+ * by:
+ *
+ * - hwahc: HWA, USB-dongle that implements a Wireless USB host
+ * controller, (Wireless USB 1.0 Host-Wire-Adapter specification).
+ *
+ * - whci: WHCI, a PCI card with a wireless host controller
+ * (Wireless Host Controller Interface 1.0 specification).
+ *
+ * Check out the Design-overview.txt file in the source documentation
+ * for other details on the implementation.
+ *
+ * Main blocks:
+ *
+ * rh Root Hub emulation (part of the HCD glue)
+ *
+ * devconnect Handle all the issues related to device connection,
+ * authentication, disconnection, timeout, reseting,
+ * keepalives, etc.
+ *
+ * mmc MMC IE broadcasting handling
+ *
+ * A host controller driver just initializes its stuff and as part of
+ * that, creates a 'struct wusbhc' instance that handles all the
+ * common WUSB mechanisms. Links in the function ops that are specific
+ * to it and then registers the host controller. Ready to run.
+ */
+
+#ifndef __WUSBHC_H__
+#define __WUSBHC_H__
+
+#include <linux/usb.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/kref.h>
+#include <linux/workqueue.h>
+#include <linux/usb/hcd.h>
+#include "../uwb/uwb.h"
+#include "include/wusb.h"
+
+/*
+ * Time from a WUSB channel stop request to the last transmitted MMC.
+ *
+ * This needs to be > 4.096 ms in case no MMCs can be transmitted in
+ * zone 0.
+ */
+#define WUSB_CHANNEL_STOP_DELAY_MS 8
+#define WUSB_RETRY_COUNT_MAX 15
+#define WUSB_RETRY_COUNT_INFINITE 0
+
+/**
+ * Wireless USB device
+ *
+ * Describe a WUSB device connected to the cluster. This struct
+ * belongs to the 'struct wusb_port' it is attached to and it is
+ * responsible for putting and clearing the pointer to it.
+ *
+ * Note this "complements" the 'struct usb_device' that the usb_hcd
+ * keeps for each connected USB device. However, it extends some
+ * information that is not available (there is no hcpriv ptr in it!)
+ * *and* most importantly, it's life cycle is different. It is created
+ * as soon as we get a DN_Connect (connect request notification) from
+ * the device through the WUSB host controller; the USB stack doesn't
+ * create the device until we authenticate it. FIXME: this will
+ * change.
+ *
+ * @bos: This is allocated when the BOS descriptors are read from
+ * the device and freed upon the wusb_dev struct dying.
+ * @wusb_cap_descr: points into @bos, and has been verified to be size
+ * safe.
+ */
+struct wusb_dev {
+ struct kref refcnt;
+ struct wusbhc *wusbhc;
+ struct list_head cack_node; /* Connect-Ack list */
+ struct list_head rekey_node; /* GTK rekey list */
+ u8 port_idx;
+ u8 addr;
+ u8 beacon_type:4;
+ struct usb_encryption_descriptor ccm1_etd;
+ struct wusb_ckhdid cdid;
+ unsigned long entry_ts;
+ struct usb_bos_descriptor *bos;
+ struct usb_wireless_cap_descriptor *wusb_cap_descr;
+ struct uwb_mas_bm availability;
+ struct work_struct devconnect_acked_work;
+ struct usb_device *usb_dev;
+};
+
+#define WUSB_DEV_ADDR_UNAUTH 0x80
+
+static inline void wusb_dev_init(struct wusb_dev *wusb_dev)
+{
+ kref_init(&wusb_dev->refcnt);
+ /* no need to init the cack_node */
+}
+
+extern void wusb_dev_destroy(struct kref *_wusb_dev);
+
+static inline struct wusb_dev *wusb_dev_get(struct wusb_dev *wusb_dev)
+{
+ kref_get(&wusb_dev->refcnt);
+ return wusb_dev;
+}
+
+static inline void wusb_dev_put(struct wusb_dev *wusb_dev)
+{
+ kref_put(&wusb_dev->refcnt, wusb_dev_destroy);
+}
+
+/**
+ * Wireless USB Host Controller root hub "fake" ports
+ * (state and device information)
+ *
+ * Wireless USB is wireless, so there are no ports; but we
+ * fake'em. Each RC can connect a max of devices at the same time
+ * (given in the Wireless Adapter descriptor, bNumPorts or WHCI's
+ * caps), referred to in wusbhc->ports_max.
+ *
+ * See rh.c for more information.
+ *
+ * The @status and @change use the same bits as in USB2.0[11.24.2.7],
+ * so we don't have to do much when getting the port's status.
+ *
+ * WUSB1.0[7.1], USB2.0[11.24.2.7.1,fig 11-10],
+ * include/linux/usb_ch9.h (#define USB_PORT_STAT_*)
+ */
+struct wusb_port {
+ u16 status;
+ u16 change;
+ struct wusb_dev *wusb_dev; /* connected device's info */
+ u32 ptk_tkid;
+};
+
+/**
+ * WUSB Host Controller specifics
+ *
+ * All fields that are common to all Wireless USB controller types
+ * (HWA and WHCI) are grouped here. Host Controller
+ * functions/operations that only deal with general Wireless USB HC
+ * issues use this data type to refer to the host.
+ *
+ * @usb_hcd Instantiation of a USB host controller
+ * (initialized by upper layer [HWA=HC or WHCI].
+ *
+ * @dev Device that implements this; initialized by the
+ * upper layer (HWA-HC, WHCI...); this device should
+ * have a refcount.
+ *
+ * @trust_timeout After this time without hearing for device
+ * activity, we consider the device gone and we have to
+ * re-authenticate.
+ *
+ * Can be accessed w/o locking--however, read to a
+ * local variable then use.
+ *
+ * @chid WUSB Cluster Host ID: this is supposed to be a
+ * unique value that doesn't change across reboots (so
+ * that your devices do not require re-association).
+ *
+ * Read/Write protected by @mutex
+ *
+ * @dev_info This array has ports_max elements. It is used to
+ * give the HC information about the WUSB devices (see
+ * 'struct wusb_dev_info').
+ *
+ * For HWA we need to allocate it in heap; for WHCI it
+ * needs to be permanently mapped, so we keep it for
+ * both and make it easy. Call wusbhc->dev_info_set()
+ * to update an entry.
+ *
+ * @ports_max Number of simultaneous device connections (fake
+ * ports) this HC will take. Read-only.
+ *
+ * @port Array of port status for each fake root port. Guaranteed to
+ * always be the same length during device existence
+ * [this allows for some unlocked but referenced reading].
+ *
+ * @mmcies_max Max number of Information Elements this HC can send
+ * in its MMC. Read-only.
+ *
+ * @start Start the WUSB channel.
+ *
+ * @stop Stop the WUSB channel after the specified number of
+ * milliseconds. Channel Stop IEs should be transmitted
+ * as required by [WUSB] 4.16.2.1.
+ *
+ * @mmcie_add HC specific operation (WHCI or HWA) for adding an
+ * MMCIE.
+ *
+ * @mmcie_rm HC specific operation (WHCI or HWA) for removing an
+ * MMCIE.
+ *
+ * @set_ptk: Set the PTK and enable encryption for a device. Or, if
+ * the supplied key is NULL, disable encryption for that
+ * device.
+ *
+ * @set_gtk: Set the GTK to be used for all future broadcast packets
+ * (i.e., MMCs). With some hardware, setting the GTK may start
+ * MMC transmission.
+ *
+ * NOTE:
+ *
+ * - If wusb_dev->usb_dev is not NULL, then usb_dev is valid
+ * (wusb_dev has a refcount on it). Likewise, if usb_dev->wusb_dev
+ * is not NULL, usb_dev->wusb_dev is valid (usb_dev keeps a
+ * refcount on it).
+ *
+ * Most of the times when you need to use it, it will be non-NULL,
+ * so there is no real need to check for it (wusb_dev will
+ * disappear before usb_dev).
+ *
+ * - The following fields need to be filled out before calling
+ * wusbhc_create(): ports_max, mmcies_max, mmcie_{add,rm}.
+ *
+ * - there is no wusbhc_init() method, we do everything in
+ * wusbhc_create().
+ *
+ * - Creation is done in two phases, wusbhc_create() and
+ * wusbhc_create_b(); b are the parts that need to be called after
+ * calling usb_hcd_add(&wusbhc->usb_hcd).
+ */
+struct wusbhc {
+ struct usb_hcd usb_hcd; /* HAS TO BE 1st */
+ struct device *dev;
+ struct uwb_rc *uwb_rc;
+ struct uwb_pal pal;
+
+ unsigned trust_timeout; /* in jiffies */
+ struct wusb_ckhdid chid;
+ uint8_t phy_rate;
+ uint8_t dnts_num_slots;
+ uint8_t dnts_interval;
+ uint8_t retry_count;
+ struct wuie_host_info *wuie_host_info;
+
+ struct mutex mutex; /* locks everything else */
+ u16 cluster_id; /* Wireless USB Cluster ID */
+ struct wusb_port *port; /* Fake port status handling */
+ struct wusb_dev_info *dev_info; /* for Set Device Info mgmt */
+ u8 ports_max;
+ unsigned active:1; /* currently xmit'ing MMCs */
+ struct wuie_keep_alive keep_alive_ie; /* protected by mutex */
+ struct delayed_work keep_alive_timer;
+ struct list_head cack_list; /* Connect acknowledging */
+ size_t cack_count; /* protected by 'mutex' */
+ struct wuie_connect_ack cack_ie;
+ struct uwb_rsv *rsv; /* cluster bandwidth reservation */
+
+ struct mutex mmcie_mutex; /* MMC WUIE handling */
+ struct wuie_hdr **mmcie; /* WUIE array */
+ u8 mmcies_max;
+ /* FIXME: make wusbhc_ops? */
+ int (*start)(struct wusbhc *wusbhc);
+ void (*stop)(struct wusbhc *wusbhc, int delay);
+ int (*mmcie_add)(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+ u8 handle, struct wuie_hdr *wuie);
+ int (*mmcie_rm)(struct wusbhc *wusbhc, u8 handle);
+ int (*dev_info_set)(struct wusbhc *, struct wusb_dev *wusb_dev);
+ int (*bwa_set)(struct wusbhc *wusbhc, s8 stream_index,
+ const struct uwb_mas_bm *);
+ int (*set_ptk)(struct wusbhc *wusbhc, u8 port_idx,
+ u32 tkid, const void *key, size_t key_size);
+ int (*set_gtk)(struct wusbhc *wusbhc,
+ u32 tkid, const void *key, size_t key_size);
+ int (*set_num_dnts)(struct wusbhc *wusbhc, u8 interval, u8 slots);
+
+ struct {
+ struct usb_key_descriptor descr;
+ u8 data[16]; /* GTK key data */
+ } __attribute__((packed)) gtk;
+ u8 gtk_index;
+ u32 gtk_tkid;
+
+ /* workqueue for WUSB security related tasks. */
+ struct workqueue_struct *wq_security;
+ struct work_struct gtk_rekey_work;
+
+ struct usb_encryption_descriptor *ccm1_etd;
+};
+
+#define usb_hcd_to_wusbhc(u) container_of((u), struct wusbhc, usb_hcd)
+
+
+extern int wusbhc_create(struct wusbhc *);
+extern int wusbhc_b_create(struct wusbhc *);
+extern void wusbhc_b_destroy(struct wusbhc *);
+extern void wusbhc_destroy(struct wusbhc *);
+extern int wusb_dev_sysfs_add(struct wusbhc *, struct usb_device *,
+ struct wusb_dev *);
+extern void wusb_dev_sysfs_rm(struct wusb_dev *);
+extern int wusbhc_sec_create(struct wusbhc *);
+extern int wusbhc_sec_start(struct wusbhc *);
+extern void wusbhc_sec_stop(struct wusbhc *);
+extern void wusbhc_sec_destroy(struct wusbhc *);
+extern void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb,
+ int status);
+void wusbhc_reset_all(struct wusbhc *wusbhc);
+
+int wusbhc_pal_register(struct wusbhc *wusbhc);
+void wusbhc_pal_unregister(struct wusbhc *wusbhc);
+
+/*
+ * Return @usb_dev's @usb_hcd (properly referenced) or NULL if gone
+ *
+ * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr)
+ *
+ * This is a safe assumption as @usb_dev->bus is referenced all the
+ * time during the @usb_dev life cycle.
+ */
+static inline
+struct usb_hcd *usb_hcd_get_by_usb_dev(struct usb_device *usb_dev)
+{
+ struct usb_hcd *usb_hcd;
+ usb_hcd = bus_to_hcd(usb_dev->bus);
+ return usb_get_hcd(usb_hcd);
+}
+
+/*
+ * Increment the reference count on a wusbhc.
+ *
+ * @wusbhc's life cycle is identical to that of the underlying usb_hcd.
+ */
+static inline struct wusbhc *wusbhc_get(struct wusbhc *wusbhc)
+{
+ return usb_get_hcd(&wusbhc->usb_hcd) ? wusbhc : NULL;
+}
+
+/*
+ * Return the wusbhc associated to a @usb_dev
+ *
+ * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr)
+ *
+ * @returns: wusbhc for @usb_dev; NULL if the @usb_dev is being torn down.
+ * WARNING: referenced at the usb_hcd level, unlocked
+ *
+ * FIXME: move offline
+ */
+static inline struct wusbhc *wusbhc_get_by_usb_dev(struct usb_device *usb_dev)
+{
+ struct wusbhc *wusbhc = NULL;
+ struct usb_hcd *usb_hcd;
+ if (usb_dev->devnum > 1 && !usb_dev->wusb) {
+ /* but root hubs */
+ dev_err(&usb_dev->dev, "devnum %d wusb %d\n", usb_dev->devnum,
+ usb_dev->wusb);
+ BUG_ON(usb_dev->devnum > 1 && !usb_dev->wusb);
+ }
+ usb_hcd = usb_hcd_get_by_usb_dev(usb_dev);
+ if (usb_hcd == NULL)
+ return NULL;
+ BUG_ON(usb_hcd->wireless == 0);
+ return wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+}
+
+
+static inline void wusbhc_put(struct wusbhc *wusbhc)
+{
+ usb_put_hcd(&wusbhc->usb_hcd);
+}
+
+int wusbhc_start(struct wusbhc *wusbhc);
+void wusbhc_stop(struct wusbhc *wusbhc);
+extern int wusbhc_chid_set(struct wusbhc *, const struct wusb_ckhdid *);
+
+/* Device connect handling */
+extern int wusbhc_devconnect_create(struct wusbhc *);
+extern void wusbhc_devconnect_destroy(struct wusbhc *);
+extern int wusbhc_devconnect_start(struct wusbhc *wusbhc);
+extern void wusbhc_devconnect_stop(struct wusbhc *wusbhc);
+extern void wusbhc_handle_dn(struct wusbhc *, u8 srcaddr,
+ struct wusb_dn_hdr *dn_hdr, size_t size);
+extern void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port);
+extern int wusb_usb_ncb(struct notifier_block *nb, unsigned long val,
+ void *priv);
+extern int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev,
+ u8 addr);
+
+/* Wireless USB fake Root Hub methods */
+extern int wusbhc_rh_create(struct wusbhc *);
+extern void wusbhc_rh_destroy(struct wusbhc *);
+
+extern int wusbhc_rh_status_data(struct usb_hcd *, char *);
+extern int wusbhc_rh_control(struct usb_hcd *, u16, u16, u16, char *, u16);
+extern int wusbhc_rh_start_port_reset(struct usb_hcd *, unsigned);
+
+/* MMC handling */
+extern int wusbhc_mmcie_create(struct wusbhc *);
+extern void wusbhc_mmcie_destroy(struct wusbhc *);
+extern int wusbhc_mmcie_set(struct wusbhc *, u8 interval, u8 repeat_cnt,
+ struct wuie_hdr *);
+extern void wusbhc_mmcie_rm(struct wusbhc *, struct wuie_hdr *);
+
+/* Bandwidth reservation */
+int wusbhc_rsv_establish(struct wusbhc *wusbhc);
+void wusbhc_rsv_terminate(struct wusbhc *wusbhc);
+
+/*
+ * I've always said
+ * I wanted a wedding in a church...
+ *
+ * but lately I've been thinking about
+ * the Botanical Gardens.
+ *
+ * We could do it by the tulips.
+ * It'll be beautiful
+ *
+ * --Security!
+ */
+extern int wusb_dev_sec_add(struct wusbhc *, struct usb_device *,
+ struct wusb_dev *);
+extern void wusb_dev_sec_rm(struct wusb_dev *) ;
+extern int wusb_dev_4way_handshake(struct wusbhc *, struct wusb_dev *,
+ struct wusb_ckhdid *ck);
+void wusbhc_gtk_rekey(struct wusbhc *wusbhc);
+int wusb_dev_update_address(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev);
+
+
+/* WUSB Cluster ID handling */
+extern u8 wusb_cluster_id_get(void);
+extern void wusb_cluster_id_put(u8);
+
+/*
+ * wusb_port_by_idx - return the port associated to a zero-based port index
+ *
+ * NOTE: valid without locking as long as wusbhc is referenced (as the
+ * number of ports doesn't change). The data pointed to has to
+ * be verified though :)
+ */
+static inline struct wusb_port *wusb_port_by_idx(struct wusbhc *wusbhc,
+ u8 port_idx)
+{
+ return &wusbhc->port[port_idx];
+}
+
+/*
+ * wusb_port_no_to_idx - Convert port number (per usb_dev->portnum) to
+ * a port_idx.
+ *
+ * USB stack USB ports are 1 based!!
+ *
+ * NOTE: only valid for WUSB devices!!!
+ */
+static inline u8 wusb_port_no_to_idx(u8 port_no)
+{
+ return port_no - 1;
+}
+
+extern struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *,
+ struct usb_device *);
+
+/*
+ * Return a referenced wusb_dev given a @usb_dev
+ *
+ * Returns NULL if the usb_dev is being torn down.
+ *
+ * FIXME: move offline
+ */
+static inline
+struct wusb_dev *wusb_dev_get_by_usb_dev(struct usb_device *usb_dev)
+{
+ struct wusbhc *wusbhc;
+ struct wusb_dev *wusb_dev;
+ wusbhc = wusbhc_get_by_usb_dev(usb_dev);
+ if (wusbhc == NULL)
+ return NULL;
+ mutex_lock(&wusbhc->mutex);
+ wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev);
+ mutex_unlock(&wusbhc->mutex);
+ wusbhc_put(wusbhc);
+ return wusb_dev;
+}
+
+/* Misc */
+
+extern struct workqueue_struct *wusbd;
+#endif /* #ifndef __WUSBHC_H__ */