View Issue Details

IDProjectCategoryView StatusLast Update
0000596channel: elrepo/el7--elrepo--request-for-enhancement--public2016-04-12 10:58
Reporterbenkonrath Assigned Topperry  
PrioritynormalSeverityfeatureReproducibilityN/A
Status closedResolutionnot fixable 
Summary0000596: Request: touchpad and fn key support for MacBookPro 12,1 (early 2015) in EL7
DescriptionSupport for the touchpad and fn key support for MacBookPro 12,1 (early 2015) landed in kernel version 4.2. It would be really nice to get updated modules for EL7 so that this notebook can be easily setup with EL7 based distros.

This kernel.org bug report has the 3 final patches that when into the 4.2 kernel.

https://bugzilla.kernel.org/show_bug.cgi?id=96771

Both the bcm5974 and the hid-apple drivers will need to patched. I browsed through the kernel source and it looks like there are a couple of other patches you might want to put into the updated modules.

https://github.com/torvalds/linux/commits/ae982073095a44f004d7ffb9f271077abef9dbcf/drivers/hid/hid-apple.c
https://github.com/torvalds/linux/commits/dbe08116b87cdc2217f11a78b5b70e29068b7efd/drivers/input/mouse/bcm5974.c

I'm definitely available for testing any modules you create. Thanks for all your hard work on this project.
TagsNo tags attached.
Attached Files
lsmod-macbookpro-121-kernel-4.2.1-ml.txt (5,002 bytes)   
Module                  Size  Used by
bnep                   20480  2 
fuse                   98304  3 
ip6t_rpfilter          16384  1 
ip6t_REJECT            16384  2 
nf_reject_ipv6         16384  1 ip6t_REJECT
ipt_REJECT             16384  2 
nf_reject_ipv4         16384  1 ipt_REJECT
xt_conntrack           16384  9 
ebtable_nat            16384  0 
ebtable_broute         16384  0 
ebtable_filter         16384  0 
ebtables               36864  3 ebtable_broute,ebtable_nat,ebtable_filter
ip6table_nat           16384  1 
nf_conntrack_ipv6      20480  6 
nf_defrag_ipv6         36864  1 nf_conntrack_ipv6
nf_nat_ipv6            16384  1 ip6table_nat
ip6table_mangle        16384  1 
ip6table_security      16384  1 
ip6table_raw           16384  1 
ip6table_filter        16384  1 
ip6_tables             28672  5 ip6table_filter,ip6table_mangle,ip6table_security,ip6table_nat,ip6table_raw
iptable_nat            16384  1 
nf_conntrack_ipv4      16384  5 
nf_defrag_ipv4         16384  1 nf_conntrack_ipv4
nf_nat_ipv4            16384  1 iptable_nat
nf_nat                 24576  2 nf_nat_ipv4,nf_nat_ipv6
nf_conntrack          106496  6 nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_conntrack_ipv4,nf_conntrack_ipv6
iptable_mangle         16384  1 
iptable_security       16384  1 
iptable_raw            16384  1 
iptable_filter         16384  1 
ip_tables              28672  5 iptable_security,iptable_filter,iptable_mangle,iptable_nat,iptable_raw
vfat                   20480  1 
fat                    69632  1 vfat
iTCO_wdt               16384  0 
iTCO_vendor_support    16384  1 iTCO_wdt
applesmc               20480  0 
input_polldev          16384  1 applesmc
x86_pkg_temp_thermal    16384  0 
intel_powerclamp       16384  0 
coretemp               16384  0 
kvm_intel             172032  0 
kvm                   516096  1 kvm_intel
snd_hda_codec_cirrus    20480  1 
snd_hda_codec_hdmi     49152  1 
snd_hda_codec_generic    73728  1 snd_hda_codec_cirrus
pcspkr                 16384  0 
i2c_i801               24576  0 
snd_hda_intel          36864  7 
joydev                 20480  0 
snd_hda_codec         135168  4 snd_hda_codec_hdmi,snd_hda_codec_generic,snd_hda_intel,snd_hda_codec_cirrus
snd_hda_core           65536  4 snd_hda_codec_hdmi,snd_hda_codec_generic,snd_hda_codec,snd_hda_intel
snd_hwdep              16384  1 snd_hda_codec
brcmfmac              233472  0 
brcmutil               16384  1 brcmfmac
btusb                  45056  0 
snd_seq                69632  0 
btrtl                  16384  1 btusb
btbcm                  16384  1 btusb
btintel                16384  1 btusb
snd_seq_device         16384  1 snd_seq
snd_pcm                98304  4 snd_hda_codec_hdmi,snd_hda_codec,snd_hda_intel,snd_hda_core
bluetooth             491520  11 bnep,btbcm,btrtl,btusb,btintel
cfg80211              548864  1 brcmfmac
mmc_core              131072  1 brcmfmac
rfkill                 24576  4 cfg80211,bluetooth
input_leds             16384  0 
bcm5974                16384  0 
lpc_ich                24576  0 
mfd_core               16384  1 lpc_ich
mei_me                 24576  0 
shpchp                 36864  0 
mei                    90112  1 mei_me
snd_timer              32768  2 snd_pcm,snd_seq
snd                    81920  24 snd_hwdep,snd_timer,snd_hda_codec_hdmi,snd_pcm,snd_seq,snd_hda_codec_generic,snd_hda_codec,snd_hda_intel,snd_seq_device,snd_hda_codec_cirrus
soundcore              16384  1 snd
sbs                    16384  0 
sbshc                  16384  1 sbs
acpi_als               16384  0 
kfifo_buf              16384  1 acpi_als
industrialio           57344  2 acpi_als,kfifo_buf
spi_pxa2xx_platform    24576  0 
apple_bl               16384  0 
binfmt_misc            20480  1 
uinput                 20480  0 
xfs                   925696  2 
libcrc32c              16384  1 xfs
dm_crypt               28672  1 
sd_mod                 40960  4 
uas                    24576  0 
usb_storage            69632  1 uas
crct10dif_pclmul       16384  0 
crc32_pclmul           16384  0 
crc32c_intel           24576  1 
ghash_clmulni_intel    16384  0 
aesni_intel           167936  2 
glue_helper            16384  1 aesni_intel
lrw                    16384  1 aesni_intel
gf128mul               16384  1 lrw
ablk_helper            16384  1 aesni_intel
cryptd                 20480  4 ghash_clmulni_intel,aesni_intel,ablk_helper
ahci                   36864  3 
libahci                32768  1 ahci
libata                237568  2 ahci,libahci
i915                 1118208  4 
i2c_algo_bit           16384  1 i915
drm_kms_helper        126976  1 i915
drm                   356352  5 i915,drm_kms_helper
video                  36864  1 i915
asix                   40960  0 
usbnet                 40960  1 asix
mii                    16384  2 asix,usbnet
dm_mirror              24576  0 
dm_region_hash         20480  1 dm_mirror
dm_log                 20480  2 dm_region_hash,dm_mirror
dm_mod                110592  11 dm_log,dm_mirror,dm_crypt
modinfo-bcm5974-macbookpro-121-kernel-4.2.1-ml.txt (2,657 bytes)   
filename:       /lib/modules/4.2.1-1.el7.elrepo.x86_64/kernel/drivers/input/mouse/bcm5974.ko
license:        GPL
description:    Apple USB BCM5974 multitouch driver
author:         Henrik Rydberg
srcversion:     9918D260A34AD1472D292E2
alias:          usb:v05ACp0274d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0273d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0272d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0292d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0291d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0290d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp025Bd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp025Ad*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0259d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0264d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0263d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0262d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0254d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0253d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0252d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Ed*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Dd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Cd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Bd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Ad*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0249d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0247d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0246d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0245d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0244d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0243d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0242d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0241d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0240d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp023Fd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0238d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0237d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0236d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0232d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0231d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0230d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0225d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0224d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0223d*dc*dsc*dp*ic03isc*ip02in*
depends:        
intree:         Y
vermagic:       4.2.1-1.el7.elrepo.x86_64 SMP mod_unload modversions 
parm:           debug:Activate debugging output (int)
bcm5974.c (32,173 bytes)   
/*
 * Apple USB BCM5974 (Macbook Air and Penryn Macbook Pro) multitouch driver
 *
 * Copyright (C) 2008	   Henrik Rydberg (rydberg@euromail.se)
 * Copyright (C) 2015      John Horan (knasher@gmail.com)
 *
 * The USB initialization and package decoding was made by
 * Scott Shawcroft as part of the touchd user-space driver project:
 * Copyright (C) 2008	   Scott Shawcroft (scott.shawcroft@gmail.com)
 *
 * The BCM5974 driver is based on the appletouch driver:
 * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
 * Copyright (C) 2005      Johannes Berg (johannes@sipsolutions.net)
 * Copyright (C) 2005	   Stelian Pop (stelian@popies.net)
 * Copyright (C) 2005	   Frank Arnold (frank@scirocco-5v-turbo.de)
 * Copyright (C) 2005	   Peter Osterlund (petero2@telia.com)
 * Copyright (C) 2005	   Michael Hanselmann (linux-kernel@hansmi.ch)
 * Copyright (C) 2006	   Nicolas Boichat (nicolas@boichat.ch)
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
#include <linux/mutex.h>
#include <linux/input/mt.h>

#define USB_VENDOR_ID_APPLE		0x05ac

/* MacbookAir, aka wellspring */
#define USB_DEVICE_ID_APPLE_WELLSPRING_ANSI	0x0223
#define USB_DEVICE_ID_APPLE_WELLSPRING_ISO	0x0224
#define USB_DEVICE_ID_APPLE_WELLSPRING_JIS	0x0225
/* MacbookProPenryn, aka wellspring2 */
#define USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI	0x0230
#define USB_DEVICE_ID_APPLE_WELLSPRING2_ISO	0x0231
#define USB_DEVICE_ID_APPLE_WELLSPRING2_JIS	0x0232
/* Macbook5,1 (unibody), aka wellspring3 */
#define USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI	0x0236
#define USB_DEVICE_ID_APPLE_WELLSPRING3_ISO	0x0237
#define USB_DEVICE_ID_APPLE_WELLSPRING3_JIS	0x0238
/* MacbookAir3,2 (unibody), aka wellspring5 */
#define USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI	0x023f
#define USB_DEVICE_ID_APPLE_WELLSPRING4_ISO	0x0240
#define USB_DEVICE_ID_APPLE_WELLSPRING4_JIS	0x0241
/* MacbookAir3,1 (unibody), aka wellspring4 */
#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI	0x0242
#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO	0x0243
#define USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS	0x0244
/* Macbook8 (unibody, March 2011) */
#define USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI	0x0245
#define USB_DEVICE_ID_APPLE_WELLSPRING5_ISO	0x0246
#define USB_DEVICE_ID_APPLE_WELLSPRING5_JIS	0x0247
/* MacbookAir4,1 (unibody, July 2011) */
#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI	0x0249
#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO	0x024a
#define USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS	0x024b
/* MacbookAir4,2 (unibody, July 2011) */
#define USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI	0x024c
#define USB_DEVICE_ID_APPLE_WELLSPRING6_ISO	0x024d
#define USB_DEVICE_ID_APPLE_WELLSPRING6_JIS	0x024e
/* Macbook8,2 (unibody) */
#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI	0x0252
#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO	0x0253
#define USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS	0x0254
/* MacbookPro10,1 (unibody, June 2012) */
#define USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI	0x0262
#define USB_DEVICE_ID_APPLE_WELLSPRING7_ISO	0x0263
#define USB_DEVICE_ID_APPLE_WELLSPRING7_JIS	0x0264
/* MacbookPro10,2 (unibody, October 2012) */
#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI	0x0259
#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO	0x025a
#define USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS	0x025b
/* MacbookAir6,2 (unibody, June 2013) */
#define USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI	0x0290
#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO	0x0291
#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS	0x0292
/* MacbookPro12,1 (2015) */
#define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI	0x0272
#define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO	0x0273
#define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS	0x0274

#define BCM5974_DEVICE(prod) {					\
	.match_flags = (USB_DEVICE_ID_MATCH_DEVICE |		\
			USB_DEVICE_ID_MATCH_INT_CLASS |		\
			USB_DEVICE_ID_MATCH_INT_PROTOCOL),	\
	.idVendor = USB_VENDOR_ID_APPLE,			\
	.idProduct = (prod),					\
	.bInterfaceClass = USB_INTERFACE_CLASS_HID,		\
	.bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE	\
}

/* table of devices that work with this driver */
static const struct usb_device_id bcm5974_table[] = {
	/* MacbookAir1.1 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING_JIS),
	/* MacbookProPenryn */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING2_JIS),
	/* Macbook5,1 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING3_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING3_JIS),
	/* MacbookAir3,2 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4_JIS),
	/* MacbookAir3,1 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS),
	/* MacbookPro8 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5_JIS),
	/* MacbookAir4,1 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS),
	/* MacbookAir4,2 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING6_JIS),
	/* MacbookPro8,2 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS),
	/* MacbookPro10,1 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7_JIS),
	/* MacbookPro10,2 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS),
	/* MacbookAir6,2 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING8_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING8_JIS),
	/* MacbookPro12,1 */
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_ISO),
	BCM5974_DEVICE(USB_DEVICE_ID_APPLE_WELLSPRING9_JIS),
	/* Terminating entry */
	{}
};
MODULE_DEVICE_TABLE(usb, bcm5974_table);

MODULE_AUTHOR("Henrik Rydberg");
MODULE_DESCRIPTION("Apple USB BCM5974 multitouch driver");
MODULE_LICENSE("GPL");

#define dprintk(level, format, a...)\
	{ if (debug >= level) printk(KERN_DEBUG format, ##a); }

static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "Activate debugging output");

/* button data structure */
struct bt_data {
	u8 unknown1;		/* constant */
	u8 button;		/* left button */
	u8 rel_x;		/* relative x coordinate */
	u8 rel_y;		/* relative y coordinate */
};

/* trackpad header types */
enum tp_type {
	TYPE1,			/* plain trackpad */
	TYPE2,			/* button integrated in trackpad */
	TYPE3,			/* additional header fields since June 2013 */
	TYPE4			/* additional header field for pressure data */
};

/* trackpad finger data offsets, le16-aligned */
#define HEADER_TYPE1		(13 * sizeof(__le16))
#define HEADER_TYPE2		(15 * sizeof(__le16))
#define HEADER_TYPE3		(19 * sizeof(__le16))
#define HEADER_TYPE4		(23 * sizeof(__le16))

/* trackpad button data offsets */
#define BUTTON_TYPE1		0
#define BUTTON_TYPE2		15
#define BUTTON_TYPE3		23
#define BUTTON_TYPE4		31

/* list of device capability bits */
#define HAS_INTEGRATED_BUTTON	1

/* trackpad finger data block size */
#define FSIZE_TYPE1		(14 * sizeof(__le16))
#define FSIZE_TYPE2		(14 * sizeof(__le16))
#define FSIZE_TYPE3		(14 * sizeof(__le16))
#define FSIZE_TYPE4		(15 * sizeof(__le16))

/* offset from header to finger struct */
#define DELTA_TYPE1		(0 * sizeof(__le16))
#define DELTA_TYPE2		(0 * sizeof(__le16))
#define DELTA_TYPE3		(0 * sizeof(__le16))
#define DELTA_TYPE4		(1 * sizeof(__le16))

/* usb control message mode switch data */
#define USBMSG_TYPE1		8, 0x300, 0, 0, 0x1, 0x8
#define USBMSG_TYPE2		8, 0x300, 0, 0, 0x1, 0x8
#define USBMSG_TYPE3		8, 0x300, 0, 0, 0x1, 0x8
#define USBMSG_TYPE4		2, 0x302, 2, 1, 0x1, 0x0

/* Wellspring initialization constants */
#define BCM5974_WELLSPRING_MODE_READ_REQUEST_ID		1
#define BCM5974_WELLSPRING_MODE_WRITE_REQUEST_ID	9

/* trackpad finger structure, le16-aligned */
struct tp_finger {
	__le16 origin;		/* zero when switching track finger */
	__le16 abs_x;		/* absolute x coodinate */
	__le16 abs_y;		/* absolute y coodinate */
	__le16 rel_x;		/* relative x coodinate */
	__le16 rel_y;		/* relative y coodinate */
	__le16 tool_major;	/* tool area, major axis */
	__le16 tool_minor;	/* tool area, minor axis */
	__le16 orientation;	/* 16384 when point, else 15 bit angle */
	__le16 touch_major;	/* touch area, major axis */
	__le16 touch_minor;	/* touch area, minor axis */
	__le16 unused[2];	/* zeros */
	__le16 pressure;	/* pressure on forcetouch touchpad */
	__le16 multi;		/* one finger: varies, more fingers: constant */
} __attribute__((packed,aligned(2)));

/* trackpad finger data size, empirically at least ten fingers */
#define MAX_FINGERS		16
#define MAX_FINGER_ORIENTATION	16384

/* device-specific parameters */
struct bcm5974_param {
	int snratio;		/* signal-to-noise ratio */
	int min;		/* device minimum reading */
	int max;		/* device maximum reading */
};

/* device-specific configuration */
struct bcm5974_config {
	int ansi, iso, jis;	/* the product id of this device */
	int caps;		/* device capability bitmask */
	int bt_ep;		/* the endpoint of the button interface */
	int bt_datalen;		/* data length of the button interface */
	int tp_ep;		/* the endpoint of the trackpad interface */
	enum tp_type tp_type;	/* type of trackpad interface */
	int tp_header;		/* bytes in header block */
	int tp_datalen;		/* data length of the trackpad interface */
	int tp_button;		/* offset to button data */
	int tp_fsize;		/* bytes in single finger block */
	int tp_delta;		/* offset from header to finger struct */
	int um_size;		/* usb control message length */
	int um_req_val;		/* usb control message value */
	int um_req_idx;		/* usb control message index */
	int um_switch_idx;	/* usb control message mode switch index */
	int um_switch_on;	/* usb control message mode switch on */
	int um_switch_off;	/* usb control message mode switch off */
	struct bcm5974_param p;	/* finger pressure limits */
	struct bcm5974_param w;	/* finger width limits */
	struct bcm5974_param x;	/* horizontal limits */
	struct bcm5974_param y;	/* vertical limits */
	struct bcm5974_param o;	/* orientation limits */
};

/* logical device structure */
struct bcm5974 {
	char phys[64];
	struct usb_device *udev;	/* usb device */
	struct usb_interface *intf;	/* our interface */
	struct input_dev *input;	/* input dev */
	struct bcm5974_config cfg;	/* device configuration */
	struct mutex pm_mutex;		/* serialize access to open/suspend */
	int opened;			/* 1: opened, 0: closed */
	struct urb *bt_urb;		/* button usb request block */
	struct bt_data *bt_data;	/* button transferred data */
	struct urb *tp_urb;		/* trackpad usb request block */
	u8 *tp_data;			/* trackpad transferred data */
	const struct tp_finger *index[MAX_FINGERS];	/* finger index data */
	struct input_mt_pos pos[MAX_FINGERS];		/* position array */
	int slots[MAX_FINGERS];				/* slot assignments */
};

/* trackpad finger block data, le16-aligned */
static const struct tp_finger *get_tp_finger(const struct bcm5974 *dev, int i)
{
	const struct bcm5974_config *c = &dev->cfg;
	u8 *f_base = dev->tp_data + c->tp_header + c->tp_delta;

	return (const struct tp_finger *)(f_base + i * c->tp_fsize);
}

#define DATAFORMAT(type)				\
	type,						\
	HEADER_##type,					\
	HEADER_##type + (MAX_FINGERS) * (FSIZE_##type),	\
	BUTTON_##type,					\
	FSIZE_##type,					\
	DELTA_##type,					\
	USBMSG_##type

/* logical signal quality */
#define SN_PRESSURE	45		/* pressure signal-to-noise ratio */
#define SN_WIDTH	25		/* width signal-to-noise ratio */
#define SN_COORD	250		/* coordinate signal-to-noise ratio */
#define SN_ORIENT	10		/* orientation signal-to-noise ratio */

/* device constants */
static const struct bcm5974_config bcm5974_config_table[] = {
	{
		USB_DEVICE_ID_APPLE_WELLSPRING_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING_JIS,
		0,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE1),
		{ SN_PRESSURE, 0, 256 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4824, 5342 },
		{ SN_COORD, -172, 5820 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING2_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING2_JIS,
		0,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE1),
		{ SN_PRESSURE, 0, 256 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4824, 4824 },
		{ SN_COORD, -172, 4290 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING3_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING3_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4460, 5166 },
		{ SN_COORD, -75, 6700 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING4_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING4_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4620, 5140 },
		{ SN_COORD, -150, 6600 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4616, 5112 },
		{ SN_COORD, -142, 5234 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING5_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING5_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4415, 5050 },
		{ SN_COORD, -55, 6680 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING6_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING6_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4620, 5140 },
		{ SN_COORD, -150, 6600 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4750, 5280 },
		{ SN_COORD, -150, 6730 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4620, 5140 },
		{ SN_COORD, -150, 6600 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING7_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING7_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4750, 5280 },
		{ SN_COORD, -150, 6730 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS,
		HAS_INTEGRATED_BUTTON,
		0x84, sizeof(struct bt_data),
		0x81, DATAFORMAT(TYPE2),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4750, 5280 },
		{ SN_COORD, -150, 6730 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING8_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING8_JIS,
		HAS_INTEGRATED_BUTTON,
		0, sizeof(struct bt_data),
		0x83, DATAFORMAT(TYPE3),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4620, 5140 },
		{ SN_COORD, -150, 6600 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{
		USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI,
		USB_DEVICE_ID_APPLE_WELLSPRING9_ISO,
		USB_DEVICE_ID_APPLE_WELLSPRING9_JIS,
		HAS_INTEGRATED_BUTTON,
		0, sizeof(struct bt_data),
		0x83, DATAFORMAT(TYPE4),
		{ SN_PRESSURE, 0, 300 },
		{ SN_WIDTH, 0, 2048 },
		{ SN_COORD, -4828, 5345 },
		{ SN_COORD, -203, 6803 },
		{ SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION }
	},
	{}
};

/* return the device-specific configuration by device */
static const struct bcm5974_config *bcm5974_get_config(struct usb_device *udev)
{
	u16 id = le16_to_cpu(udev->descriptor.idProduct);
	const struct bcm5974_config *cfg;

	for (cfg = bcm5974_config_table; cfg->ansi; ++cfg)
		if (cfg->ansi == id || cfg->iso == id || cfg->jis == id)
			return cfg;

	return bcm5974_config_table;
}

/* convert 16-bit little endian to signed integer */
static inline int raw2int(__le16 x)
{
	return (signed short)le16_to_cpu(x);
}

static void set_abs(struct input_dev *input, unsigned int code,
		    const struct bcm5974_param *p)
{
	int fuzz = p->snratio ? (p->max - p->min) / p->snratio : 0;
	input_set_abs_params(input, code, p->min, p->max, fuzz, 0);
}

/* setup which logical events to report */
static void setup_events_to_report(struct input_dev *input_dev,
				   const struct bcm5974_config *cfg)
{
	__set_bit(EV_ABS, input_dev->evbit);

	/* for synaptics only */
	input_set_abs_params(input_dev, ABS_PRESSURE, 0, 256, 5, 0);
	input_set_abs_params(input_dev, ABS_TOOL_WIDTH, 0, 16, 0, 0);

	/* finger touch area */
	set_abs(input_dev, ABS_MT_TOUCH_MAJOR, &cfg->w);
	set_abs(input_dev, ABS_MT_TOUCH_MINOR, &cfg->w);
	/* finger approach area */
	set_abs(input_dev, ABS_MT_WIDTH_MAJOR, &cfg->w);
	set_abs(input_dev, ABS_MT_WIDTH_MINOR, &cfg->w);
	/* finger orientation */
	set_abs(input_dev, ABS_MT_ORIENTATION, &cfg->o);
	/* finger position */
	set_abs(input_dev, ABS_MT_POSITION_X, &cfg->x);
	set_abs(input_dev, ABS_MT_POSITION_Y, &cfg->y);

	__set_bit(EV_KEY, input_dev->evbit);
	__set_bit(BTN_LEFT, input_dev->keybit);

	if (cfg->caps & HAS_INTEGRATED_BUTTON)
		__set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit);

	input_mt_init_slots(input_dev, MAX_FINGERS,
		INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK);
}

/* report button data as logical button state */
static int report_bt_state(struct bcm5974 *dev, int size)
{
	if (size != sizeof(struct bt_data))
		return -EIO;

	dprintk(7,
		"bcm5974: button data: %x %x %x %x\n",
		dev->bt_data->unknown1, dev->bt_data->button,
		dev->bt_data->rel_x, dev->bt_data->rel_y);

	input_report_key(dev->input, BTN_LEFT, dev->bt_data->button);
	input_sync(dev->input);

	return 0;
}

static void report_finger_data(struct input_dev *input, int slot,
			       const struct input_mt_pos *pos,
			       const struct tp_finger *f)
{
	input_mt_slot(input, slot);
	input_mt_report_slot_state(input, MT_TOOL_FINGER, true);

	input_report_abs(input, ABS_MT_TOUCH_MAJOR,
			 raw2int(f->touch_major) << 1);
	input_report_abs(input, ABS_MT_TOUCH_MINOR,
			 raw2int(f->touch_minor) << 1);
	input_report_abs(input, ABS_MT_WIDTH_MAJOR,
			 raw2int(f->tool_major) << 1);
	input_report_abs(input, ABS_MT_WIDTH_MINOR,
			 raw2int(f->tool_minor) << 1);
	input_report_abs(input, ABS_MT_ORIENTATION,
			 MAX_FINGER_ORIENTATION - raw2int(f->orientation));
	input_report_abs(input, ABS_MT_POSITION_X, pos->x);
	input_report_abs(input, ABS_MT_POSITION_Y, pos->y);
}

static void report_synaptics_data(struct input_dev *input,
				  const struct bcm5974_config *cfg,
				  const struct tp_finger *f, int raw_n)
{
	int abs_p = 0, abs_w = 0;

	if (raw_n) {
		int p = raw2int(f->touch_major);
		int w = raw2int(f->tool_major);
		if (p > 0 && raw2int(f->origin)) {
			abs_p = clamp_val(256 * p / cfg->p.max, 0, 255);
			abs_w = clamp_val(16 * w / cfg->w.max, 0, 15);
		}
	}

	input_report_abs(input, ABS_PRESSURE, abs_p);
	input_report_abs(input, ABS_TOOL_WIDTH, abs_w);
}

/* report trackpad data as logical trackpad state */
static int report_tp_state(struct bcm5974 *dev, int size)
{
	const struct bcm5974_config *c = &dev->cfg;
	const struct tp_finger *f;
	struct input_dev *input = dev->input;
	int raw_n, i, n = 0;

	if (size < c->tp_header || (size - c->tp_header) % c->tp_fsize != 0)
		return -EIO;

	raw_n = (size - c->tp_header) / c->tp_fsize;

	for (i = 0; i < raw_n; i++) {
		f = get_tp_finger(dev, i);
		if (raw2int(f->touch_major) == 0)
			continue;
		dev->pos[n].x = raw2int(f->abs_x);
		dev->pos[n].y = c->y.min + c->y.max - raw2int(f->abs_y);
		dev->index[n++] = f;
	}

	input_mt_assign_slots(input, dev->slots, dev->pos, n);

	for (i = 0; i < n; i++)
		report_finger_data(input, dev->slots[i],
				   &dev->pos[i], dev->index[i]);

	input_mt_sync_frame(input);

	report_synaptics_data(input, c, get_tp_finger(dev, 0), raw_n);

	/* later types report button events via integrated button only */
	if (c->caps & HAS_INTEGRATED_BUTTON) {
		int ibt = raw2int(dev->tp_data[c->tp_button]);
		input_report_key(input, BTN_LEFT, ibt);
	}

	input_sync(input);

	return 0;
}

static int bcm5974_wellspring_mode(struct bcm5974 *dev, bool on)
{
	const struct bcm5974_config *c = &dev->cfg;
	int retval = 0, size;
	char *data;

	/* Type 3 does not require a mode switch */
	if (dev->cfg.tp_type == TYPE3)
		return 0;

	data = kmalloc(c->um_size, GFP_KERNEL);
	if (!data) {
		dev_err(&dev->intf->dev, "out of memory\n");
		retval = -ENOMEM;
		goto out;
	}

	/* read configuration */
	size = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
			BCM5974_WELLSPRING_MODE_READ_REQUEST_ID,
			USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
			c->um_req_val, c->um_req_idx, data, c->um_size, 5000);

	if (size != c->um_size) {
		dev_err(&dev->intf->dev, "could not read from device\n");
		retval = -EIO;
		goto out;
	}

	/* apply the mode switch */
	data[c->um_switch_idx] = on ? c->um_switch_on : c->um_switch_off;

	/* write configuration */
	size = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
			BCM5974_WELLSPRING_MODE_WRITE_REQUEST_ID,
			USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
			c->um_req_val, c->um_req_idx, data, c->um_size, 5000);

	if (size != c->um_size) {
		dev_err(&dev->intf->dev, "could not write to device\n");
		retval = -EIO;
		goto out;
	}

	dprintk(2, "bcm5974: switched to %s mode.\n",
		on ? "wellspring" : "normal");

 out:
	kfree(data);
	return retval;
}

static void bcm5974_irq_button(struct urb *urb)
{
	struct bcm5974 *dev = urb->context;
	struct usb_interface *intf = dev->intf;
	int error;

	switch (urb->status) {
	case 0:
		break;
	case -EOVERFLOW:
	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
		dev_dbg(&intf->dev, "button urb shutting down: %d\n",
			urb->status);
		return;
	default:
		dev_dbg(&intf->dev, "button urb status: %d\n", urb->status);
		goto exit;
	}

	if (report_bt_state(dev, dev->bt_urb->actual_length))
		dprintk(1, "bcm5974: bad button package, length: %d\n",
			dev->bt_urb->actual_length);

exit:
	error = usb_submit_urb(dev->bt_urb, GFP_ATOMIC);
	if (error)
		dev_err(&intf->dev, "button urb failed: %d\n", error);
}

static void bcm5974_irq_trackpad(struct urb *urb)
{
	struct bcm5974 *dev = urb->context;
	struct usb_interface *intf = dev->intf;
	int error;

	switch (urb->status) {
	case 0:
		break;
	case -EOVERFLOW:
	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
		dev_dbg(&intf->dev, "trackpad urb shutting down: %d\n",
			urb->status);
		return;
	default:
		dev_dbg(&intf->dev, "trackpad urb status: %d\n", urb->status);
		goto exit;
	}

	/* control response ignored */
	if (dev->tp_urb->actual_length == 2)
		goto exit;

	if (report_tp_state(dev, dev->tp_urb->actual_length))
		dprintk(1, "bcm5974: bad trackpad package, length: %d\n",
			dev->tp_urb->actual_length);

exit:
	error = usb_submit_urb(dev->tp_urb, GFP_ATOMIC);
	if (error)
		dev_err(&intf->dev, "trackpad urb failed: %d\n", error);
}

/*
 * The Wellspring trackpad, like many recent Apple trackpads, share
 * the usb device with the keyboard. Since keyboards are usually
 * handled by the HID system, the device ends up being handled by two
 * modules. Setting up the device therefore becomes slightly
 * complicated. To enable multitouch features, a mode switch is
 * required, which is usually applied via the control interface of the
 * device.  It can be argued where this switch should take place. In
 * some drivers, like appletouch, the switch is made during
 * probe. However, the hid module may also alter the state of the
 * device, resulting in trackpad malfunction under certain
 * circumstances. To get around this problem, there is at least one
 * example that utilizes the USB_QUIRK_RESET_RESUME quirk in order to
 * receive a reset_resume request rather than the normal resume.
 * Since the implementation of reset_resume is equal to mode switch
 * plus start_traffic, it seems easier to always do the switch when
 * starting traffic on the device.
 */
static int bcm5974_start_traffic(struct bcm5974 *dev)
{
	int error;

	error = bcm5974_wellspring_mode(dev, true);
	if (error) {
		dprintk(1, "bcm5974: mode switch failed\n");
		goto err_out;
	}

	if (dev->bt_urb) {
		error = usb_submit_urb(dev->bt_urb, GFP_KERNEL);
		if (error)
			goto err_reset_mode;
	}

	error = usb_submit_urb(dev->tp_urb, GFP_KERNEL);
	if (error)
		goto err_kill_bt;

	return 0;

err_kill_bt:
	usb_kill_urb(dev->bt_urb);
err_reset_mode:
	bcm5974_wellspring_mode(dev, false);
err_out:
	return error;
}

static void bcm5974_pause_traffic(struct bcm5974 *dev)
{
	usb_kill_urb(dev->tp_urb);
	usb_kill_urb(dev->bt_urb);
	bcm5974_wellspring_mode(dev, false);
}

/*
 * The code below implements open/close and manual suspend/resume.
 * All functions may be called in random order.
 *
 * Opening a suspended device fails with EACCES - permission denied.
 *
 * Failing a resume leaves the device resumed but closed.
 */
static int bcm5974_open(struct input_dev *input)
{
	struct bcm5974 *dev = input_get_drvdata(input);
	int error;

	error = usb_autopm_get_interface(dev->intf);
	if (error)
		return error;

	mutex_lock(&dev->pm_mutex);

	error = bcm5974_start_traffic(dev);
	if (!error)
		dev->opened = 1;

	mutex_unlock(&dev->pm_mutex);

	if (error)
		usb_autopm_put_interface(dev->intf);

	return error;
}

static void bcm5974_close(struct input_dev *input)
{
	struct bcm5974 *dev = input_get_drvdata(input);

	mutex_lock(&dev->pm_mutex);

	bcm5974_pause_traffic(dev);
	dev->opened = 0;

	mutex_unlock(&dev->pm_mutex);

	usb_autopm_put_interface(dev->intf);
}

static int bcm5974_suspend(struct usb_interface *iface, pm_message_t message)
{
	struct bcm5974 *dev = usb_get_intfdata(iface);

	mutex_lock(&dev->pm_mutex);

	if (dev->opened)
		bcm5974_pause_traffic(dev);

	mutex_unlock(&dev->pm_mutex);

	return 0;
}

static int bcm5974_resume(struct usb_interface *iface)
{
	struct bcm5974 *dev = usb_get_intfdata(iface);
	int error = 0;

	mutex_lock(&dev->pm_mutex);

	if (dev->opened)
		error = bcm5974_start_traffic(dev);

	mutex_unlock(&dev->pm_mutex);

	return error;
}

static int bcm5974_probe(struct usb_interface *iface,
			 const struct usb_device_id *id)
{
	struct usb_device *udev = interface_to_usbdev(iface);
	const struct bcm5974_config *cfg;
	struct bcm5974 *dev;
	struct input_dev *input_dev;
	int error = -ENOMEM;

	/* find the product index */
	cfg = bcm5974_get_config(udev);

	/* allocate memory for our device state and initialize it */
	dev = kzalloc(sizeof(struct bcm5974), GFP_KERNEL);
	input_dev = input_allocate_device();
	if (!dev || !input_dev) {
		dev_err(&iface->dev, "out of memory\n");
		goto err_free_devs;
	}

	dev->udev = udev;
	dev->intf = iface;
	dev->input = input_dev;
	dev->cfg = *cfg;
	mutex_init(&dev->pm_mutex);

	/* setup urbs */
	if (cfg->tp_type == TYPE1) {
		dev->bt_urb = usb_alloc_urb(0, GFP_KERNEL);
		if (!dev->bt_urb)
			goto err_free_devs;
	}

	dev->tp_urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!dev->tp_urb)
		goto err_free_bt_urb;

	if (dev->bt_urb) {
		dev->bt_data = usb_alloc_coherent(dev->udev,
					  dev->cfg.bt_datalen, GFP_KERNEL,
					  &dev->bt_urb->transfer_dma);
		if (!dev->bt_data)
			goto err_free_urb;
	}

	dev->tp_data = usb_alloc_coherent(dev->udev,
					  dev->cfg.tp_datalen, GFP_KERNEL,
					  &dev->tp_urb->transfer_dma);
	if (!dev->tp_data)
		goto err_free_bt_buffer;

	if (dev->bt_urb)
		usb_fill_int_urb(dev->bt_urb, udev,
				 usb_rcvintpipe(udev, cfg->bt_ep),
				 dev->bt_data, dev->cfg.bt_datalen,
				 bcm5974_irq_button, dev, 1);

	usb_fill_int_urb(dev->tp_urb, udev,
			 usb_rcvintpipe(udev, cfg->tp_ep),
			 dev->tp_data, dev->cfg.tp_datalen,
			 bcm5974_irq_trackpad, dev, 1);

	/* create bcm5974 device */
	usb_make_path(udev, dev->phys, sizeof(dev->phys));
	strlcat(dev->phys, "/input0", sizeof(dev->phys));

	input_dev->name = "bcm5974";
	input_dev->phys = dev->phys;
	usb_to_input_id(dev->udev, &input_dev->id);
	/* report driver capabilities via the version field */
	input_dev->id.version = cfg->caps;
	input_dev->dev.parent = &iface->dev;

	input_set_drvdata(input_dev, dev);

	input_dev->open = bcm5974_open;
	input_dev->close = bcm5974_close;

	setup_events_to_report(input_dev, cfg);

	error = input_register_device(dev->input);
	if (error)
		goto err_free_buffer;

	/* save our data pointer in this interface device */
	usb_set_intfdata(iface, dev);

	return 0;

err_free_buffer:
	usb_free_coherent(dev->udev, dev->cfg.tp_datalen,
		dev->tp_data, dev->tp_urb->transfer_dma);
err_free_bt_buffer:
	if (dev->bt_urb)
		usb_free_coherent(dev->udev, dev->cfg.bt_datalen,
				  dev->bt_data, dev->bt_urb->transfer_dma);
err_free_urb:
	usb_free_urb(dev->tp_urb);
err_free_bt_urb:
	usb_free_urb(dev->bt_urb);
err_free_devs:
	usb_set_intfdata(iface, NULL);
	input_free_device(input_dev);
	kfree(dev);
	return error;
}

static void bcm5974_disconnect(struct usb_interface *iface)
{
	struct bcm5974 *dev = usb_get_intfdata(iface);

	usb_set_intfdata(iface, NULL);

	input_unregister_device(dev->input);
	usb_free_coherent(dev->udev, dev->cfg.tp_datalen,
			  dev->tp_data, dev->tp_urb->transfer_dma);
	if (dev->bt_urb)
		usb_free_coherent(dev->udev, dev->cfg.bt_datalen,
				  dev->bt_data, dev->bt_urb->transfer_dma);
	usb_free_urb(dev->tp_urb);
	usb_free_urb(dev->bt_urb);
	kfree(dev);
}

static struct usb_driver bcm5974_driver = {
	.name			= "bcm5974",
	.probe			= bcm5974_probe,
	.disconnect		= bcm5974_disconnect,
	.suspend		= bcm5974_suspend,
	.resume			= bcm5974_resume,
	.id_table		= bcm5974_table,
	.supports_autosuspend	= 1,
};

module_usb_driver(bcm5974_driver);
bcm5974.c (32,173 bytes)   
modinfo-bcm5974-3.10.0-327.13.1.txt (2,832 bytes)   
filename:       /lib/modules/3.10.0-327.13.1.el7.x86_64/weak-updates/bcm5974/bcm5974.ko
license:        GPL
description:    Apple USB BCM5974 multitouch driver
author:         Henrik Rydberg
rhelversion:    7.2
srcversion:     1E77EB0C1C3EDE1C288F335
alias:          usb:v05ACp0274d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0273d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0272d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0292d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0291d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0290d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp025Bd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp025Ad*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0259d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0264d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0263d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0262d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0254d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0253d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0252d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Ed*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Dd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Cd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Bd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Ad*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0249d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0247d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0246d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0245d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0244d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0243d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0242d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0241d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0240d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp023Fd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0238d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0237d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0236d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0232d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0231d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0230d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0225d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0224d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0223d*dc*dsc*dp*ic03isc*ip02in*
depends:        
vermagic:       3.10.0-327.el7.x86_64 SMP mod_unload modversions 
signer:         The ELRepo Project (http://elrepo.org): ELRepo.org Secure Boot Key
sig_key:        F3:65:AD:34:81:A7:B2:0E:34:27:B6:1B:2A:26:63:5B:83:FE:42:7B
sig_hashalgo:   sha256
parm:           debug:Activate debugging output (int)
modinfo-bcm5974-4.4.6.txt (2,657 bytes)   
filename:       /lib/modules/4.4.6-1.el7.elrepo.x86_64/kernel/drivers/input/mouse/bcm5974.ko
license:        GPL
description:    Apple USB BCM5974 multitouch driver
author:         Henrik Rydberg
srcversion:     9918D260A34AD1472D292E2
alias:          usb:v05ACp0274d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0273d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0272d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0292d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0291d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0290d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp025Bd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp025Ad*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0259d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0264d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0263d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0262d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0254d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0253d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0252d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Ed*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Dd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Cd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Bd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp024Ad*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0249d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0247d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0246d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0245d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0244d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0243d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0242d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0241d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0240d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp023Fd*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0238d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0237d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0236d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0232d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0231d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0230d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0225d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0224d*dc*dsc*dp*ic03isc*ip02in*
alias:          usb:v05ACp0223d*dc*dsc*dp*ic03isc*ip02in*
depends:        
intree:         Y
vermagic:       4.4.6-1.el7.elrepo.x86_64 SMP mod_unload modversions 
parm:           debug:Activate debugging output (int)
modinfo-bcm5974-4.4.6.txt (2,657 bytes)   
Reported upstream

Activities

pperry

2015-09-23 15:15

administrator   ~0004490

Acknowledged, and assigned to me as the person most likely to be able to look at it. However, I'm very over-committed at the moment so no promises as to when that might be.

But to get us started...

Can I suggest you test our latest kernel-ml package here (currently kernel-ml-4.2.1-1.el7.elrepo.x86_64.rpm):

http://elrepo.org/linux/kernel/el7/x86_64/RPMS/

Being a 4.2 kernel, hopefully it has the necessary drivers enabled and working? If not, perhaps the modules need enabling in the kernel configuration file.

Then, once working, I'd need to know exactly which modules are loaded and any modules they depend upon. 'lsmod' will show you which modules are loaded, and modinfo will show you the inter-module dependencies for the modules of interest. This info gives me a starting point for backporting a driver for the RHEL (distro) kernel.

pperry

2015-09-23 15:33

administrator   ~0004491

I've just checked the config of our kernel-ml-4.2.1 package and it looks like the necessary bcm5974 and HID modules are all enabled:

[phil@Build64R7 config-4.2]$ grep -Eir 'bcm5974|apple|multitouch' *
CONFIG_MOUSE_APPLETOUCH=m
CONFIG_MOUSE_BCM5974=m
CONFIG_SENSORS_APPLESMC=m
CONFIG_BACKLIGHT_APPLE=m
CONFIG_HID_APPLE=y
CONFIG_HID_APPLEIR=m
CONFIG_HID_MULTITOUCH=m
CONFIG_USB_APPLEDISPLAY=m
CONFIG_APPLE_GMUX=m

benkonrath

2015-09-24 00:44

reporter   ~0004492

Hi, Thanks for the quick reply. I definitely understand being over-committed with work so please feel free to schedule any work on this whenever you have time. The kernel-ml-4.2.1 is working for me and while I prefer to run the official kernels (for the security updates), at least I have a solution for now.

In the meantime, I've uploaded the output from 'lsmod' and 'modinfo bcm5974' while running kernel-ml-4.2.1. hid-apple seems to be built into the kernel for both upstream kernels and kernel-ml-4.2.1. Will it be possible to create an updated hid-apple kernel module that overrides the compiled in driver?

pperry

2015-09-26 07:50

administrator   ~0004496

As kernel-ml is simply a packaged version of the latest upstream mainline kernel, it should have all the latest security updates, but I understand the desire to run the distro kernel.

It might be possible to backport the bcm5974 mouse driver, but unfortunately I don't believe it is possible to override the built in hid-apple keyboard driver.

As you have established the drivers in kernel-4.2.1 work for you, I would file a bug ASAP against RHEL7 requesting the drivers be updated to support your hardware. RHEL7.2 beta is already released, and I don't see anything in the changelog to suggest you will be lucky there, but an RFE filed now would have a good chance for 7.3.

So my recommendation would be to run kernel-ml for now and file an RFE with Red Hat requesting support be added (backported) to the distro kernel ASAP.

benkonrath

2015-10-03 02:37

reporter   ~0004506

Ok, thanks. I've filed a request on the Red Hat bugzilla.

https://bugzilla.redhat.com/show_bug.cgi?id=1267894

I don't know if I got the component correct and it looks like it's not a public report. I'm not sure if that's the standard for RHEL kernel bug reports.

After running with 4.2.1 for a while, I've been getting regular hard locks. It happens when I'm running on battery and using wifi so there might be a bug there. I realize this is an upstream kernel bug; I'm just mentioning it in case somebody else is in the same situation. I'll try with 4.2.2 to see how it goes.

pperry

2015-10-03 02:46

administrator   ~0004507

Thanks for reporting back.

Yes, it's marked private. Please could you add me to the bug in bugzilla so I can follow it, my email address is: ned@unixmail.co.uk

Thanks

benkonrath

2016-02-21 12:18

reporter   ~0004688

I'm still having some problems with the ml kernels so I thought I'd see how far I could get with getting the bcm5974 compiled on the EL7 kernel (3.10.0-327.10.1.el7.x86_64).

I managed to successfully compile drivers/input/mouse/bcm5974.c from kernel 4.4.2. I just had to make one change; I removed the last argument on line 640 to match the EL7 signature of the 'input_mt_assign_slots()' function:

input_mt_assign_slots(input, dev->slots, dev->pos, n, 0); ->
input_mt_assign_slots(input, dev->slots, dev->pos, n);

I'll attach the source file that I used.

Now that I have the driver compiled, I'm not sure how to proceed. I tried doing 'insmod bcm5974.ko' but the multitouch features didn't work and I didn't see anything in dmesg about the finding the hardware. Do you have any tips on
what I can try next? Could it be possible that the driver needs to be loaded before other input drivers?

You can see that the 2 patches for this hardware (from 28 June 2015) only changed bcm5974.c so I'm not sure what other modules would need be loaded or backported.

https://github.com/torvalds/linux/commits/master/drivers/input/mouse/bcm5974.c

Any suggestions are appreciated. Thanks.

pperry

2016-03-28 08:16

administrator   ~0004712

Hi,

Firstly, my apologies for the delay.

I've built you a kmod package of the bcm5974 driver using your fix to input_mt_assign_slots(). I actually used the latest mainline code from kernel-4.5, but it's not changed since July 2015.

I've released the packages into the el7 testing repository and they are currently syncing to the mirrors so should show up shortly:

kmod-bcm5974-0.0-1.el7.elrepo.x86_64.rpm
bcm5974-kmod-0.0-1.el7.elrepo.src.rpm

To install and test, please do:

yum --enablerepo=elrepo-testing install kmod-bcm5974

and then reboot your system.

'modinfo bcm5974' should then show the new driver loaded.

I'd appreciate any feedback once you've had a chance to test.

Thanks

benkonrath

2016-04-12 09:38

reporter   ~0004726

Thanks for making the module. I just tested it out and it doesn't seem to be working. My guess is that the changes to the apple hid module are also required.

The kernel-lt package (4.4.6) is working well for me these days - much better than the hard freezes I was getting with the 4.2 kernel-ml. I don't think it's going to be possible to get the apple input hardware working with the default RHEL kernel so I'll just continue to use the kernel-lt version.

Thanks again for all your help on this. Feel free to close this issue.

pperry

2016-04-12 10:56

administrator   ~0004727

Thanks for the feedback. I've removed that package from the repo.

I agree that one of our kernels appears to be the best solution for you at this point. Sorry we weren't able to be of more help on this occasion. Please feel free to reopen or file another bug/issue if anything changes.

Issue History

Date Modified Username Field Change
2015-09-23 07:15 benkonrath New Issue
2015-09-23 07:15 benkonrath Status new => assigned
2015-09-23 07:15 benkonrath Assigned To => toracat
2015-09-23 15:06 pperry Assigned To toracat => pperry
2015-09-23 15:15 pperry Note Added: 0004490
2015-09-23 15:33 pperry Note Added: 0004491
2015-09-24 00:36 benkonrath File Added: lsmod-macbookpro-121-kernel-4.2.1-ml.txt
2015-09-24 00:36 benkonrath File Added: modinfo-bcm5974-macbookpro-121-kernel-4.2.1-ml.txt
2015-09-24 00:44 benkonrath Note Added: 0004492
2015-09-26 07:50 pperry Note Added: 0004496
2015-10-03 02:37 benkonrath Note Added: 0004506
2015-10-03 02:46 pperry Note Added: 0004507
2016-02-21 12:18 benkonrath Note Added: 0004688
2016-02-21 12:20 benkonrath File Added: bcm5974.c
2016-03-28 08:16 pperry Note Added: 0004712
2016-04-12 09:33 benkonrath File Added: modinfo-bcm5974-3.10.0-327.13.1.txt
2016-04-12 09:33 benkonrath File Added: modinfo-bcm5974-4.4.6.txt
2016-04-12 09:38 benkonrath Note Added: 0004726
2016-04-12 10:56 pperry Note Added: 0004727
2016-04-12 10:58 pperry Status assigned => closed
2016-04-12 10:58 pperry Resolution open => not fixable