Skip to contentSkip to conversation

Greybus driver subsystem for 4.9-rc1

GIT PULL In review Greg KH 10 participants 67 messages Sep 14, 2016 – Oct 7, 2016 2 messages not in the archive Page 1 of 2

Subsystems (2)
lore
Share & tools
lore

Greg KH

author maintainer
#0 lore raw

"'tis but a scratch" - The Black Knight

Hi all,

As many of you know, I, and lots of other developers, have been working on a new application level protocol for the Unipro hardware transport called "Greybus". It has shown up in numerous demos from Google for the Ara Phone platform, and an older version of the codebase is currently shipping in a phone from Motorola any-day-now. There is a semi-up-to-date version of the protocol specification available at:
https://github.com/projectara/greybus-spec
A newer version should be available soon, matching the code implementation here, as soon as I work through a few final details with some lawyers on the wording of the license.

Many other software packages to help out with greybus devices are on that github project page (manfest generator, emulator, firmware examples, etc.), if people are interested.

Given that it's never a good idea to keep subsystems out of the mainline kernel, I've put together this pull request that adds the greybus driver layer to drivers/greybus/. Because this was 2 1/2 years of work, with many many developers contributing, I didn't want to flatten all of their effort into a few small patches, as that wouldn't be very fair. So I've built a git tree with all of the changes going back to the first commit, and merged it into the kernel tree, just like btrfs was merged into the kernel.

The protocol design, and implementation, was a group effort, and it would not have turned out well at all without the contributions of all of these talented developers.

I'll send out a follow-up set of "simple" patches that just add the files to the kernel tree, to give people an idea of the code involved. Overall, it's a tiny stand-alone driver subsystem, only 37k lines, that implements a protocol which allows for "generic" cameras, audio devices, and other class type devices, as well as a bridged "physical" layer protocol to talk to serial, spi, uart, pwm, gpio, i2c, and even USB host controllers. Included here is a USB bridge host controller driver that interacts with a USB device that converts USB data to Unipro data, allowing any system to talk to a Unipro platform (no special SoC interface required.)

Unless people point out some major problems with this, I'd like to get it merged into 4.9-rc1.

The shortlog and diffstat can be seen below.

If anyone has any questions about this, please let us know.

thanks,

greg k-h

---------------

The following changes since commit c6935931c1894ff857616ff8549b61236a19148f:

Linux 4.8-rc5 (2016-09-04 14:31:46 -0700)

are available in the git repository at:

git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git/ greybus

for you to fetch changes up to f86bfc90a401681838866b5f6826d86cc4c7010c:

greybus: remove CONFIG_PM_RUNTIME from kernel_ver.h (2016-09-09 13:27:19 +0200)

----------------------------------------------------------------
Akash Choudhari (1):
greybus: Revert "Added a sysfs entry to power down the SVC"

Alex Elder (345):
greybus: uart-gb: improve minor device number error checking
greybus: uart-gb: a few minor bug fixes
greybus: switch to the term "manifest"
greybus: define struct greybus_manifest
greybus: call put_device() on error
greybus: interpret descriptor type properly
greybus: validate descriptor sizes
greybus: fix manifest parsing size bug
greybus: simple fixes
greybus: rename struct greybus_device
greybus: quick fix for sysfs serial number
greybus: get rid of a weird idiom
greybus: embed workqueue structure in struct gbuf
greybus: encapsulate URB status checking
greybus: some more renames
greybus: update a few header file comments
greybus: fix document version number
greybus: fix the guard in "greybus_manifest.h"
greybus: descriptor type is 1 byte
greybus: fix version check
greybus: reorder greybus_svc_in() arguments
greybus: kill struct gmod_cport
greybus: clarify device vs module
greybus: descriptor type updates
greybus: isolate greybus module code
greybus: define greybus interface abstraction
greybus: define greybus function abstraction
greybus: introduce a connection abstraction
greybus: introduce an operation abstraction
greybus: start improving manifest parsing
greybus: manifest interface descriptor parsing
greybus: manifest cport descriptor parsing
greybus: kill off old manifest code
greybus: fix connection header declarations
greybus: October 1 updates
greybus: create host device cport id map
greybus: allocate connection host cport id
greybus: get rid of functions now...
greybus: record connection protocol
greybus: fix some hasty bugs
greybus: fix greybus_class_type symbol names
greybus: use ida for cport id allocation
greybus: fix module setup
greybus: gbuf: have caller set actual_length
greybus: specify type when creating an operation
greybus: add a connection->private field
greybus: define gb_connection_err()
greybus: fix two misnamed functions
greybus: bury some dead code
greybus: add bg_hd_connection_find()
greybus: look up connection for recevied messages
greybus: allocate gbufs using the connection
greybus: use alloc_gbuf_data() for both directions
greybus: kill __alloc_gbuf()
greybus: get rid of gbuf->hdpriv
greybus: don't assume subdevs are valid
greybus: unlock gbuf mutex on free
greybus: unlock hd mutex on free
greybus: don't restrict input buffer size
greybus: zero all data buffers
greybus: drop hd arg from submit_gbuf method
greybus: stop init_subdevs stuff
greybus: add response buffer to an operation
greybus: add gb_operation_find()
greybus: let operation layer examine incoming data
greybus: move receive handling to operation layer
greybus: add device initialization
greybus: kill off gbuf work queue
greybus: kill old cport handlers
greybus: add i2c driver
greybus: initial operations-based GPIO driver
greybus: add write retry support for i2c
greybus: update gbuf status for completion handlers
greybus: report gbuf errors
greybus: properly drop device reference
greybus: create a slab cache for operations
greybus: drop gb_* device fields from gb_module
greybus: remove cports and strings from gb_module
greybus: move ap_disconnect()
greybus: leverage ap_disconnect() in ap_probe()
greybus: fix op_cycle logic
greybus: stash power supply pointer in connection
greybus: improve module cleanup code
greybus: time out operation requests
greybus: define operation_cancel()
greybus: define connection state
greybus: make svc_set_route_send() public
greybus: only initialize interfaces when up
greybus: set route before sending packets
greybus: update AP id service message
greybus: fix repeated input errors
greybus: gpio: kill gpio_controller->gpio
greybus: embed the i2c adapter struct
greybus: begin abstracting connection operations
greybus: drop the cport id on error
greybus: use protocol_id for numeric values
greybus: introduce protocol abstraction
greybus: fix a bug in gb_operation_gbuf_complete()
greybus: drop a redundant assignment
greybus: make _gb_protocol_find() static
greybus: control printing message
greybus: improve some error messages
greybus: identify protocol by id *and* version
greybus: count rather than list protocol users
greybus: order the protocols list
greybus: register preallocated protocols
greybus: move methods into protocol
greybus: add an incoming request receive method
greybus: kill test_sink.c
greybus: kill old cport handler code
greybus: get rid of gbuf->actual_length
greybus: record gbuf->operation
greybus: remove gbuf->context
greybus: get rid of greybus_gbuf_finished()
greybus: move gb_operation_gbuf_complete()
greybus: kill gbuf->complete
greybus: don't assume PAGE_SIZE for URB size
greybus: fix request timeout bug
greybus: move timeout out of gb_operation_insert()
greybus: simplify pending operations tracking
greybus: op_cycle doesn't need to be atomic
greybus: pass operation type on request receive
greybus: move operation timeout teardown
greybus: delete some lines in "greybus.h"
greybus: kill gb_operation_gbuf_complete()
greybus: clean up gb_connection_operation_recv()
greybus: reference count operations
greybus: record a gbuf's destination CPort id
greybus: use gbuf's destination cport id
greybus: record the host device in a gbuf
greybus: use a simple list of hd connections
greybus: get rid of cport_id_map_lock
greybus: kill greybus_{get,put}_gbuf()
greybus: kill gbuf->kref
greybus: move the definition of struct gbuf
greybus: define struct gb_message
greybus: start using struct gb_message
greybus: move gbuf initialization to caller
greybus: use null gbuf->transfer_buffer
greybus: embed gbufs into operation message structure
greybus: kill the gbuf slab cache
greybus: kill off the last of gbuf.c
greybus: fix an allocation flag bug
greybus: prepend cport byte for all gbufs
greybus: improve data buffer alignment
greybus: fill in destination data at send time
greybus: allocate space without gbufs
greybus: free space without gbufs
greybus: cancel buffers via magic cookie
greybus: stash hd as context for all URBs
greybus: send buffers without gbufs
greybus: rework message initialization
greybus: send messages, not gbufs
greybus: cancel messages, not gbufs
greybus: rework receve handling
greybus: kill the last gbuf remnants
greybus: stop storing hd in message
greybus: stop storing dest_cport_id in message
greybus: pass gfp_flags for message allocation
greybus: explicitly mark cookies as opaque
greybus: tidy up svc_in_callback() and cport_in_callback()
greybus: get rid of message status
greybus: use "operation_id" for certain values
greybus: refactor gb_connection_recv()
greybus: fix battery_operation()
greybus: fix uart request_operation()
greybus: fix vibrator request_operation()
greybus: get rid of uart request_operation()
greybus: distinguish incoming from outgoing requests
greybus: send operation result in response message header
greybus: define gb_operation_status_map()
greybus: remove status from all responses
greybus: drop a now-empty structure
greybus: fix a timeout race
greybus: complete overflow responses
greybus: have greybus allocate its own buffers
greybus: rename message buffer fields
greybus: dynamically allocate requests and responses
greybus: embed message buffer into message structure
greybus: define greybus_data_sent()
greybus: rename greybus_cport_in()
greybus: use errno for operation result
greybus: abandon incoming requests for now
greybus: handle data send errors in workqueue
greybus: add a reference to pending operations
greybus: minor tweak in gb_connection_recv_response()
greybus: cancel operation on timeout
greybus: cancel whole operation on interrupt
greybus: kill gb_operation_wait()
greybus: rework synchronous operation completion
greybus: encapsulate operation result access
greybus: first operation error prevails
greybus: use special operation result valus
greybus: fix some error codes
greybus: enforce receive buffer size
greybus: update operation result atomically
greybus: ignore a null cookie when canceling buffer
greybus: protect cookie with a mutex
greybus: use outgoing flag when creating operation
greybus: enforce max representable message size
greybus: define -EILSEQ to mean implementation error
greybus: renumber operation result values
greybus: drop gfp_mask from gb_message_send()
greybus: always drop reference in gb_operation_work()
greybus: move copy of incoming request data
greybus: short message is OK for errors
greybus: pass result in gb_connection_recv_response()
greybus: enforce non-zero operation type requirement
greybus: use operation type 0 to signal incoming data
greybus: introduce gb_operation_message_init()
greybus: enforce a buffer headroom maximum size
greybus: create a slab cache for simple messages
greybus: set result in gb_operation_response_send()
greybus: activate incoming request handling
greybus: introduce gb_operation_errno_map()
greybus: send operation response messages
greybus: use little-endian in PWM requests
greybus: fix a bug in gb_operation_sync()
greybus: define the invalid operation type symbolically
greybus: don't use 0 as an operation id
greybus: get rid of pending operations list
greybus: make op_cycle atomic (again)
greybus: introduce gb_operation_request_send_sync()
greybus: fix an error message
greybus: set up connection->private properly
greybus: don't let i2c code assume non-null payload pointer
greybus: only record message payload size
greybus: use null pointer for empty payload
greybus: record type in operation structure
greybus: define GB_OP_NONEXISTENT
greybus: ENODEV can be an expected error too
greybus: switch cport id used for sends
greybus: add Linaro copyrights
greybus: clean up some small messes
greybus: get rid of {conceal,reveal}_urb()
greybus: manifest: use size_t for a size variable
greybus: es1: test apb1_log_task safely
greybus: es2: test apb1_log_task safely
greybus: reduce the ranting
greybus: bundle: use kstrdup() for state file
greybus: battery: free struct on error in caller
greybus: battery: use feature tag rather than kernel version
greybus: eliminate extra response flag definitions
greybus: loopback: fix the type attribute check
greybus: loopback: return the right error value
greybus: loopback: fix an incorrect comment
greybus: loopback: symbolically define max wait time
greybus: loopback: define loopback functions symbolically
greybus: loopback: support module-initiated requests
greybus: endo: rename gb_svc
greybus: endo: pass endo_id to gb_endo_create()
greybus: core: return error code when creating endo
greybus: core: return error code when creating host device
greybus: include "gpbridge.h" from "greybus.h"
greybus: rename "gpbridge.h"
greybus: endo: encapsulate computing the max interface id
greybus: endo: rework some attributes
greybus: endo: record AP interface id
greybus: core: don't set up endo until host device is initialized
greybus: add copyright statements
greybus: update copyrights
greybus: introduce SVC protocol
greybus: greybus_protocols: fix guard tag
greybus: add documentation for Endo sysfs files
greybus: update sysfs documentation files
greybus: endo: drop redundant prefixes from sysfs basenames
greybus: endo: delete "0xXXXX" portion in sysfs "endo" directory
greybus: svc: driver is basic to Greybus (not GP Bridge)
greybus: connection: make gb_connection_hd_find() private
greybus: uart: properly interpret receive data size
greybus: core: rename greybus_deregister()
greybus: manifest: clean up a few pr_err() calls
greybus: drop some unnecessary headers
greybus: tag core init and exit functions
greybus: endo: define endo_init() and endo_exit()
greybus: endo: clean up id assignment code
greybus: manifest: really minor cleanups
greybus: manifest: use bundle's embedded interface pointer
greybus: manifest: rework cport parsing
greybus: manifest: rework bundle parsing
greybus: bundle: check for duplicate bundle ids
greybus: connection: check for duplicate cport ids
greybus: connection: remove extra kfree() call
greybus: connection: drop unneeded gb_protocol_put() calls
greybus: connection: un-abstract host cport id allocation
greybus: manifest: clean up properly when parsing cports
greybus: bundle: refactor gb_bundle_find()
greybus: bundle: fix gb_bundle_destroy()
greybus: manifest: clean up properly when parsing bundles
greybus: introduce cport_id_valid()
greybus: esX: encapsulate packing cport id into header
greybus: rename HOST_DEV_CPORT_ID_MAX
greybus: reserve host cport id 0
greybus: esX: use one byte to encode cport ids in header
greybus: interface: declare gb_interface_destroy()
greybus: looback: fix two typos
greybus: loopback: drop unneeded casts for void pointers
greybus: loopback: use U64_MAX for initialization
greybus: loopback: use u32 for stats update
greybus: loopback: record 32-bit min and max
greybus: loopback: use a 32-bit count
greybus: loopback: error is an unsigned attribute
greybus: loopback: all read-only attributes are unsigned
greybus: loopback: use separate attribute macro for average
greybus: loopback: compute average stats on demand only
greybus: loopback: fix connection cleanup paths
greybus: kernel_ver.h: define U32_MAX and U64_MAX
greybus: db3-platform: fix code that fetches reset GPIO
greybus: db3-platform: fix some typos
greybus: db3-platform: get rid of redundant gpio tests
greybus: es2: rename es2 data structures
greybus: es2: move APB log task into the es2_ap_dev struct
greybus: es2: move logging fifo into es2 struct
greybus: es2: move logging dentries into es2 struct
greybus: es2: don't assume just one AP bridge
greybus: connection: verify disabled when destroyed
greybus: add operation traces
greybus: tracing: define events using macros
greybus: tracing: eliminate "location" comments
greybus: tracing: refine comments
greybus: tracing: fix a bad tracepoint
greybus: tracing: fix hd traces
greybus: tracing: add module traces
greybus: tracing: define interface traces
greybus: report right error value
greybus: fix unbalanced mutex
greybus: eliminate unneeded null check
greybus: fix pointless null check
greybus: use PTR_ERR_OR_ZERO()
greybus: drop a bogus semicolon
greybus: tracing: fix "make check" warnings
greybus: define BUNDLE_ID_NONE
greybus: tracing: fix host device num_cports
greybus: tracing: reorder trace definitions
greybus: tracing: fix module num_interfaces
greybus: tracing: assign "parent" id first
greybus: tracing: add interface mode_switch
greybus: tracing: define bundle traces
greybus: tracing: define connection traces
greybus: tracing: add timing traces
greybus: tracing: fix message traces
greybus: don't key on an enumerated constant
greybus: tracing: drop "greybus" prefix
greybus: get rid of a compile warning
greybus: use memdup_user()

Alexandre Bailon (29):
greybus: i2c-gb: fix bad message size in gb_i2c
greybus: protocol.c: fix a kernel panic caused by __gb_protocol_register
greybus: gpio.c: fix a bad irq number
greybus: Export greybus debugfs folder
greybus: Dump log from APB1
greybus: Add loopback protocol
greybus: es1.c: Don't use magic value for USB control request
greybus: es2.c: Don't use magic value for USB control request
greybus: es2.c: create dedicated struct for cport_in and cport_out
greybus: es2.c: Increase the number of bulk endpoints
greybus: es2.c: add a control request for endpoints mapping
greybus: es1/es2: set transfer flag to send a zero-length packet
greybus: Greybus driver: add a new callbacks to driver
greybus: es2: update the bulk_ep_set value accepted by map_to_cpor_ep()
greybus: es2: rename misnamed variables and methods
greybus: es2: change (un)map methods to static
greybus: es2: add some documentation about endpoints mapping
greybus: loopback: fix invalid response size
greybus: gb_loopback: Fix throughput calculations
greybus: loopback: Fix calculations error for ping transfers
greybus: loopback: Fix incoherency in calculations in the case of error
greybus: loopback: Fix throughput calculations
greybus: loopback: Fix averaging
greybus: loopback: Fix warning on 32-bit build
greybus: loopback: round closest the sixth decimal
greybus: loopback: Fix broken synchonous test
greybus: loopback: Fix broken loopback min values
greybus: es2: Add a new bulk in endpoint for APBridgeA RPC
greybus: es2: Implement APBridgeA RPC (ARPC)

Ann Chen (2):
greybus: vibrator: integrate runtime pm
greybus: Add workqueue to handle vibrator timeout

Axel Haslam (24):
greybus: greybus/loopback: remove mask attribute
greybus: greybus/loopback: register a struct device.
greybus: loopback_test: make output to csv file a parameter option
greybus: loopback_test: Decrease the max number of devices
greybus: greybus/loopback: Add reserved fields to transfer request
greybus: Notify user space only when the test finished.
greybus: Loopback_test: use poll instead of inotify
greybus: loopback_test: Use timeout argument
greybus: loopback_test: handle SIGINT signal
greybus: uart: Update line coding settings only when needed
greybus: uart fix missing negation on DTR setting
greybus: Fix loopback app after rename to gpphy
greybus: uart: Handle CRTSCTS flag in termios
greybus: uart: Implement dtr_rts callback.
greybus: uart: Use a fifo to send data to the modules
greybus: uart: Add credits based tracking for transmit path
greybus: uart: Implement flush_buffer
greybus: uart: wait for credits on shutdown
greybus: uart: Add runtime pm support
greybus: gpio: Add runtime_pm suppourt
greybus: pwm: Add runtime_pm support
greybus: spi: Add runtime_pm support
greybus: loopback: add runtime pm support
greybus: gbphy: fix compile error with CONFIG_PM_RUNTIME disabled

Bartosz Golaszewski (4):
greybus: loopback: allocate a response even for a 0-byte request
greybus: legacy: fix a null pointer dereference
greybus: audio_manager: add missing header
greybus: pm: add error handling to bundle activation

Bill Pemberton (3):
greybus: gb-vibrator: remove useless if in timeout_store()
greybus: Add FIXME warnings for possible NULL dereferences
greybus: gpio-gb: remove checks for negative offset variable

Bryan O'Donoghue (132):
greybus: Support building greybus on host PC
greybus: uart: Move UART protocol structs/defines to greybus_protocols.h
greybus: uart: Tidy naming convention to more closely match spec
greybus: uart: Reduce UART count from 255 to 16
greybus: uart: Update uart.c to register tty ports
greybus: uart: send_data should return size or error
greybus: uart: kmalloc for send_data once only
greybus: uart: Add gb_uart_request_recv for receiving async UART data
greybus: uart: Remove magic numbers make struct gb_tty variable names consistent
greybus: uart: Latch modem control signals for tciomget
greybus: greybus/uart: Fix sparse warning
greybus: greybus/uart: Relocate UART parity/overrun/framing/break signals
greybus: greybus/uart: Add support for UART error signals
greybus: greybus/uart: Update UART to reflect field size changes
greybus: greybus/loopback: Move loopback operation definitions
greybus: greybus/loopback: remove magic number in state-machine
greybus: greybus/loopback: remove spurious pr_err in sysfs store
greybus: greybus/loopback: make loopback type input equivalent to protocol type
greybus: greybus/loopback: add sink to loopback protocol
greybus: greybus/loopback: truncate maximum loop data to link size
greybus: greybus/loopback: update throughput metrics to improve granularity
greybus: greybus/loopback: fix broken tabs in greybus_protocols.h
greybus: greybus/loopback: fix 64bit printf format error
greybus: greybus/uart: fix typo in defintion
greybus: greybus/manifest: reserve control connection cport/bundle ids
greybus: greybus/manifest: convert pr_err to dev_err
greybus: greybus/loopback: timestamp seeding should not drop metrics
greybus: greybus/loopback: update loopback operation description comment
greybus: greybus/loopback: run operations a set number of times
greybus: greybus/loopback: add commentary to sysfs variables
greybus: greybus/loopback: rename frequency to requests
greybus: greybus/loopback: convert loopback wake/sleep to a waitqueue
greybus: greybus/loopback: remove redundant timestamping
greybus: greybus/loopback: make loopback code thread safe
greybus: greybus/loopback: provide interface to read all latency data-points
greybus: greybus/loopback: warn user if kfifo cannot log all data
greybus: greybus/connection: fix jump label on device_add failure
greybus: greybus/connection: add a timestamp kfifo to track connection handoff
greybus: greybus/es-drivers: add outbound timestamp to connection
greybus: greybus/loopback: functionally decompose calculation of turn-around times
greybus: greybus/loopback: handle timestamp roll-over
greybus: greybus/interface: change typo replicable => replaceable
greybus: greybus/loopback: add ability to graph greybus latency
greybus: greybus/loopback: convert sample report interface to debugfs
greybus: greybus/loopback: support synchronized tests over multiple cports
greybus: greybus/loopback: add gb_loopback_nsec_to_usec_latency
greybus: greybus/loopback: functionally decompose gb_loopback_calc_latency
greybus: greybus/loopback: graph round-trip time for all threads
greybus: greybus/loopback: add bitmask of connections to include in test
greybus: greybus/loopback: initialized ms_wait negate warning
greybus: greybus/loopback: remove checkpatch error causing macro
greybus: greybus/loopback: add module level sys/debug fs data points
greybus: greybus/loopback: fix typo in comment
greybus: greybus/loopback: make sure to list_del on connection_exit
greybus: greybus/loopback: ensure count decrement happens before sysfs_remove_groups
greybus: greybus/loopback: hold a coarse lock while init/exit run
greybus: greybus/loopback: exit kfree after mutex release
greybus: greybus/loopback: ensure debugfs entires are cleaned up on exit
greybus: greybus/loopback: ensure sysfs entries are cleaned up on exit
greybus: greybus/loopback: convert pr_info to dev_err
greybus: greybus/loopback: add response len to loopback protocol
greybus: greybus/loopback: sort list of connections for masking purposes
greybus: greybus/loopback: add gb_loopback_operation_sync
greybus: greybus/loopback: use dev_name to populate sysfsname
greybus: greybus/tracepoints: add tracepoint definitions
greybus: greybus/operation, core: hook tracepoints into message opertions
greybus: greybus/loopback: drop dependency on internal timestamps
greybus: greybus/tracepoints: add tracepoints for host_device tx/rx
greybus: greybus/es1, es2: hook tracepoints to hardware send/recv operations
greybus: greybus/loopback: move sysfs entries to /sys/bus/greybus/devices/endo0
greybus: greybus/loopback: masked out threads should sleep
greybus: greybus/loopback: drop redundant endo0 string from debugfs entry name
greybus: greybus/loopback: remove module specific identifier from debugfs name
greybus: greybus/loopback: add reserved fields to track firmware latencies
greybus: greybus/es1,es2: add USB vendor command to timestamp
greybus: greybus/connection: add latency tag enable/disable callbacks
greybus: greybus/loopback: send command to APBridge to tag throughput
greybus: greybus/loopback: add tracker variables to hold firmware timestamps
greybus: greybus/loopback: capture and present firmware supplied latencies
greybus: greybus/manifest: fix the placement of arguments to pr_err
greybus: greybus/audio-pcm: fix use of variable unitialized
greybus: greybus/loopback: Relax locking during loopback operations
greybus: greybus/loopback: Move latency_ts initialization to transfer routine
greybus: greybus/loopback: Drop unused timeval variables
greybus: greybus/loopback: drop bus aggregate calculation
greybus: greybus/loopback: Convert cross-thread mutex to spinlock while relaxing connect locks
greybus: greybus/loopback: Add asynchronous bi-directional support
greybus: greybus/loopback: Convert thread delay to microseconds
greybus: greybus/loopback: Ensure we reset stats once and once only
greybus: greybus/loopback: Drop NULL check on container_of pointer
greybus: greybus/loopback: Retrun -ENOMEM if operation allocation fails
greybus: greybus/loopback: Wait for all async operations to complete on exit
greybus: greybus/loopback: Add asynchronous backoff
greybus: svc: Change GB_SVC_TYPE_LINK_CONFIG to 0x10
greybus: greybus/timesync: Add Control and SVC TimeSync command/response data definitions
greybus: greybus/timesync: Add Control and SVC protocol TimeSync operation definitions
greybus: greybus/control: Add TimeSync control commands
greybus: Ensure gb->mutex is held when adding timer
greybus: Fixup __u64, __u32 to __le64, __le32 in timesync declarations
greybus: greybus_trace.h: Fix dodgy indentation
greybus: greybus_protocols.h Add SVC_TIMESYNC_WAKE_PINS_ACQUIRE/RELEASE
greybus: greybus_protocols.h Add SVC_TIMESYNC_PING
greybus: arche-platform: Rework platform/wd-line state transition logic
greybus: svc: Add TimeSync SVC commands
greybus: control: Add TimeSync get-last-event logic
greybus: control: Drop unused parameter from timesync_authoritative
greybus: hd: Add TimeSync APBridge commands
greybus: interface: Extract and store Interface feature byte
greybus: es2.c: Declare local __le64 not u64
greybus: Fix unbalanced irq_enable() backtrace
greybus: timesync: Add timesync core driver
greybus: timesync: Bind TimeSync into Greybus
greybus: tracepoints: Add standard Linux tracepoint for TimeSync event
greybus: timesync: Add gb_timesync_frame_time_to_timespec()
greybus: timesync: Add debugfs entry to display frame-ping in ktime
greybus: uart: Fix minor number leak
greybus: timesync: Do 64 bit divisions in a 32 friendly way
greybus: timesync: Do not hold mutex on cancel_delayed_work_sync
greybus: timesync: Fix transitions to the INACTIVE state
greybus: timesync: Rework timesync removal serialization logic
greybus: timesync: Enforce TimeSync locks as subordinate to Interface locks
greybus: timesync: Initialize the timesync ping fields to zero
greybus: timesync: Make printout consitent with other greybus messages
greybus: timesync/pm: Make synchronous call to restore FrameTime
greybus: greybus_protocols.h/es2: Ensure __le32 is used not u32
greybus: greybus_protocols.h: convert __u32 to __le32
greybus: timesync: Bugfix ping should not result in -EAGAIN
greybus: timesync: reduce initial startup time
greybus: timesync: probe shouldn't complete until FrameTime sync does
greybus: timesync: Implement a retry mechanism
greybus: timesync: Ensure parallel synchronous calls succeed
greybus: timesync: Printout strobe count on sync failure

David Lin (40):
greybus: arche-platform: fix incorrect gpio variable type
greybus: greybus_trace: Fix broken greybus ftrace
greybus: svc: add Interface power measurements support
greybus: arche-platform: Power-off unipro subsystem upon suspend
greybus: svc watchdog: Disable watchdog upon entering suspend
greybus: es2: Fix apb_log null pointer exception
greybus: svc: add AP power measurements debugfs support
greybus: svc: clean up gb_svc struct for pwrmon
greybus: svc: free pwrmon_rails memory upon exit
greybus: operation: fix an inconsistent indent
greybus: legacy: remove legacy driver support
greybus: legacy: remove protocol.o from the makefile
greybus: svc: pwrmon: validate svc protocol op status when getting rail names
greybus: audio: remove the unnecessary return statement
greybus: control: add bundle suspend and resume preparations
greybus: control: add bundle deactivate and activate operation
greybus: control: add interface suspend prepare operation
greybus: control: add interface deactivate prepare operation
greybus: control: add interface hibernate abort operation
greybus: svc: add interface resume operation
greybus: svc: add power mode call for link hibernation
greybus: interface: implement unipro link hibernate call
greybus: control: add connection suspend and resume calls
greybus: interface: send deactivate prepare when interface is disabled
greybus: bundle: add activate and deactivate
greybus: interface: add runtime pm support
greybus: bundle: add runtime pm support
greybus: audio: add runtime pm support
greybus: camera: add runtime pm support
greybus: gbphy: add gbphy runtime pm support
greybus: i2c: add runtime pm support
greybus: hd: arche-platform: implement greybus shutdown
greybus: timesync: do not print frametime by default
greybus: operation: print id when synchronous operation timeout
greybus: svc_watchdog: Add sysfs file to change the behavior of bite
greybus: audio: add runtime pm to enumerated control and DAPM widget
greybus: interface: delete control device upon enable failure
greybus: svc_watchdog: use schedule_delayed_work helper
greybus: interface: fix timesync registration sequencing
greybus: light: fix incorrect led attribute files allocation

Dinko Mironov (1):
greybus: audio: Fix incorrect counting of 'ida'

Eli Sennesh (5):
greybus: firmware and svc: detect the difference between ES2 and ES3 chips
greybus: firmware/bootrom: debug output from bootrom protocol
greybus: operation: rate-limit dev_err printing on the receive path
greybus: update UniPro Set Interface Power Mode operation to match spec
greybus: update UniPro Set Interface Power Mode operation to match spec

Evgeniy Borisov (6):
greybus: camera-gb: Extend the configure streams interface
greybus: camera-gb: Remove hardcode for CSI TX number of lanes
greybus: camera-gb: Add description of interface header
greybus: camera-gb: Extend gb camera module structure
greybus: camera-gb: Implement camera module reference counting as subject.
greybus: camera: Add RAW data format

Fabien Parent (15):
greybus: es2: rename misnamed CPORT_MAX into CPORT_COUNT
greybus: add num_cports field to greybus hd
greybus: es2: dynamically allocate array for cport <-> ep mapping
greybus: es2: get cport count from apb1 usb device
greybus: es{1,2}: remove control endpoint field
greybus: connection: fail to bind if connection init fails
greybus: es{1,2}: remove obselete define
greybus: es2: implement cport reset control request
greybus: connection: destroy connection on failing to bind it
greybus: connection: add api to {en,dis}able unipro fct flow
greybus: apba: add fct flow usb control requests
greybus: es2: implement the fct flow control requests
greybus: connection: {en,dis}able fct flow in connection management
greybus: camera: disable E2EFC on CSI connection
greybus: connection: remove CDSI1 hack

Georgi Dobrev (2):
greybus: Added a sysfs entry to power down the SVC
greybus: greybus-driver: Add intf_oops operation

Gjorgji Rosikopulos (11):
greybus: camera: HACK: Export GB camera interface
greybus: camera: HACK: Register gb camera to the HOST camera
greybus: camera: Fix backword compatibility in configure streams
greybus: camera: Add support for configure streams flag in gb interface
greybus: camera: Update configure stream based on new interface
greybus: camera: add semiplanar and planar formats
greybus: camera: Improve module registration mechanism
greybus: camera: Use pointer for gb camera module ops
greybus: camera: Add metadata format
greybus: camera: Add debug data format
greybus: camera: Rename debug and metadata mbus formats

Greg KH (8):
greybus: battery: update for 4.1 power supply api changes
greybus: gb-audio: fix build warning
greybus: gb-audio: fix build breakage on 4.1-rc1
greybus: es2: comment out unused functions
greybus: audio_codec: Don't be tricky with the driver model
greybus: add bundle class to the bundle uevent
greybus: svc: double the ping delay timeout
greybus: camera: only build module against msm kernel

Greg Kroah-Hartman (376):
greybus: Initial commit
greybus: Import most recent greybus code to new repo.
greybus: README and .gitignore updates
greybus: update README with info on how to build and contact me.
greybus: i2c-gb: actually add the i2c adapter properly...
greybus: greybus.h: tiny coding style cleanups
greybus: Greybus SD/MMC host driver
greybus: add framework for 'struct gbuf'
greybus: gpio driver
greybus: gpio-gb.c: it now builds properly
greybus: uart framework added, doesn't build
greybus: uart-gb: more work on tty functions
greybus: more uart work
greybus: uart-gb: now builds, more framework added
greybus: start moving the function types into the greybus core
greybus: static module_init/exit functions
greybus: hook up sdio, gpio, and tty into the greybus core.
greybus: i2c: tie to the proper place on the greybus_device
greybus: i2c: use same naming convention everywhere
greybus: gpio: tie into gb core properly
greybus: sdio: tie into gb core properly
greybus: uart: tie into gb core properly
greybus: initial framework for ES1 usb AP driver
greybus: can't use devm anymore, we aren't tieing into the driver model lifecycle :(
greybus: actually get the devm() change to build...
greybus: first framework for the es1 ap controller
greybus: es1-ap-usb: more init framework added.
greybus: register the bus with the driver core and add framework for debugfs files.
greybus: ap message loop added.
greybus: structures added
greybus: more structure definitions added
greybus: split svc msg out into separate header file
greybus: greybus_desc.h created
greybus: header file s/u8/__u8/g
greybus: greybus.h: tiny movement around
greybus: more changes due to name changes in the greybus document
greybus: s/greybus_device_id/greybus_module_id/g
greybus: get field names right for descriptors
greybus: turn off warnings for es1-ap-usb.c to make it easier to build for now...
greybus: export gb_new_ap_msg so that the es1 module can use it
greybus: start parsing descriptor structures
greybus: start parsing descriptor fields
greybus: sysfs attributes for functions and more driver core integration.
greybus: serial number attribute added
greybus: module id attributes
greybus: greybus_string()
greybus: split sysfs functions out to separate file.
greybus: minor checkpatch cleanups
greybus: add proper packing to all greybus message types
greybus: uart-gb: remove unneeded THIS_MODULE setting
greybus: add es1_ap_desc.c to describe the ES1 USB device descriptors
greybus: host controller additions
greybus: more hd work
greybus: first cut at parsing svc messages sent to the AP
greybus: add battery module
greybus: battery FIXME added
greybus: minor whitespace cleanups to make checkpatch.pl happy
greybus: es1_ap_desc.c: updated ES1 USB device descriptor
greybus: es1: finialized USB device structure
greybus: es1: forgot to free our urb on disconnect
greybus: pass appropriate type to create function
greybus: fix endian issue in sysfs.c
greybus: Fix build errors on older kernels.
greybus: Merge branch 'master' of github.com:gregkh/greybus
greybus: AP: move a bunch of svc message handling logic into ap.c
greybus: es1: add the start of cport urb handling.
greybus: es1: allocate cport out urbs properly
greybus: es1: handle cport data in and out
greybus: ap: convert to workqueue from thread
greybus: ap: cleanup of process ap message loop
greybus: Makefile: add 'check' option to run sparse with endian checks enabled
greybus: es1 endpoint descriptor: minor fixes to get the config right
greybus: tty driver fixes to get init working properly
greybus: uart-gb: let the core dynamically allocate the major number
greybus: core: verify major/minor number of greybus protocol
greybus: es1: functionally complete
greybus: devices: endpoint description of device
greybus: gbuf recieve path work, not done, dinner time...
greybus: gbuf: cport in buffer stream logic
greybus: add test_sink driver
greybus: fix hd init sequence of setting up parent and driver pointers properly
greybus: es1: set buffer sizes for messages based on email discussions
greybus: gbuf: implement submission logic
greybus: gbuf: clean up logic of who owns what "part" of the gbuf
greybus: svc_msg.h: add data for hotplug message
greybus: greybus.h: add function prototype for add/remove a module
greybus: core.c: create empty functions to keep linking working for hotplug/remove
greybus: ap: start validating the message better
greybus: ap: validate the rest of the svc message buffer sizes
greybus: core: hook up the hotplug message
greybus: fix up coding style issue I caused with the last patch...
greybus: es1: no BUG_ON() code, report an error and recover.
greybus: operation: fix endian issue in the operation message header size field.
greybus: manifest: some minor sparse warning fixups.
greybus: manifest.c: minor sparse cleanup
greybus: connection: properly lock idr
greybus: greybus.h: remove transfer_flags
greybus: greybus_id.h: checkpatch cleanup
greybus: svc_msg.h: add bsd license to file so that firmware can use it.
greybus: greybus_manifest.h: add BSD license so that firmware can share it.
greybus: greybus_manifest.h: fix up class protocol numbers to match the spec.
greybus: gpio-gb: allow it to build properly for all current kernel versions.
greybus: battery-gb: provide accessors for a few more functions
greybus: battery: some hooking up to the greybus core
greybus: add LED protocol numbers
greybus: battery-gb: Add battery communication with the module
greybus: Merge branch 'gregkh_work' into master
greybus: battery-gb.c: fix memory leak found by Viresh
greybus: battery-gb: Allow kernel values to get out of sync with greybus spec
greybus: module: fix double free of module
greybus: hook up greybus to the driver model
greybus: module: enable all sysfs attributes
greybus: i2c: point to the proper parent device
greybus: module: don't create duplicate module ids
greybus: operation: fix some sparse warnings
greybus: gpio-gb: fix some endian sparse warnings that were real.
greybus: gbuf: implement gbuf_kill_gbuf()
greybus: core: check for valid hcd callbacks
greybus: core: make greybus_kill_gbuf not return a value
greybus: operation: make the timeout a per-operation thing, not per-connection
greybus: uart-gb: convert over to the connection interface
greybus: uart-gb: remove global init functions
greybus: FIXME/XXX removals: We have proper reference counting now
greybus: module: remove unneeded XXX comment about module id values
greybus: kernel_ver.h: add ATTRIBUTE_GROUPS() macro for older kernels
greybus: uart-gb: mark some functions static
greybus: battery-gb: remove some #if 0 code
greybus: sdio-gb: convert to the connection interface.
greybus: Merge branch 'master' into driver_model_rework
greybus: #if 0 cleanups
greybus: Remove id_table usages
greybus: es1-ap-usb: document the lack of callback for the outgoing bulk urbs
greybus: connection: fix up error patch logic in gb_connection_create()
greybus: Revert "manifest: improve print message"
greybus: Revert "manifest: remove extra loop for finding module descriptor"
greybus: skeleton for future uevents.
greybus: Greybus UART connection driver
greybus: uart: handle NULL size requests in request_operation()
greybus: greybus_manifest.h: update with full list of protocols
greybus: vibrator-gb: add vibrator driver
greybus: vibrator-gb: fixes based on Marti's review comments.
greybus: vibrator-gb: proper allocate minor numbers
greybus: Merge branch 'master' into vibrator-gb
greybus: vibrator-gb: fix up api changes
greybus: Merge branch 'master' into vibrator-gb
greybus: vibrator-gb.c: fix up incorrect comment
greybus: uart-gb: handle throttle/unthrottle properly
greybus: uart-gb: fix up typo from previous patch moving the status variable around.
greybus: pwm-gb.c: fix up missing { } for else
greybus: add -Wall to the build flags
greybus: operation: create gb_operation_sync for sending "simple" messages
greybus: battery-gb: convert to use gb_operation_sync
greybus: vibrator-gb: convert to use gb_operation_sync
greybus: uart-gb: convert to use gb_operation_sync
greybus: gpio-gb: convert to use gb_operation_sync
greybus: i2c-gb: convert to use gb_operation_sync
greybus: pwm-gb: convert to use gb_operation_sync
greybus: uart-gb: clean up send_line_coding
greybus: usb-gb: import a "buildable" version of the usb-gb.c driver
greybus: operation: fix up sparse warning
greybus: battery-gb.c: add new functions from Greybus spec document.
greybus: greybus_manifest.h: add FIXME for version
greybus: uart-gb.c: don't include module.h
greybus: module versions: remove them
greybus: module: remove obsolete gb_tty pointer
greybus: interface_block: rename module.[c|h] to interface_block.[c|h]
greybus: interface_block: s/gb_module/gb_interface_block/
greybus: interface_block: rename the sysfs files to not have 'module' in them
greybus: sysfs documentation: Document the greybus interface block sysfs files.
greybus: interface_block: move sysfs files into the interface_block.c file
greybus: connection: document the sysfs files
greybus: interface: document sysfs files
greybus: driver matching: Greybus drivers bind to interface blocks, not modules
greybus: bundle: rename interface.[c|h] to bundle.[c|h]
greybus: es1-ap-usb: don't protest when getting -EPROTO USB errors
greybus: bundle: s/gb_interface/gb_bundle/g
greybus: interface_block.c: rename the "interfaces" list "bundles"
greybus: rename interface_block.[c|h] -> interface.[c|h]
greybus: interface: s/gb_interface_block/gb_interface/g
greybus: interface: rename greybus_interface_block_id to greybus_interface_id
greybus: interface.h: remove unused #defines
greybus: greybus_id.h: minor typo fixes
greybus: interface: we really are creating/destroying interfaces not modules.
greybus: greybus_host_device: rename modules -> interfaces
greybus: interface: rename gb_modules_lock -> gb_interfaces_lock
greybus: add module support
greybus: interface: remove global manifest_descs list
greybus: protocol: switch gb_protocol_register() to return an int
greybus: export needed symbols for protocols
greybus: protocol: split binding of prototcols to connections out of init
greybus: i2c-gb: split out into a stand-alone kernel module.
greybus: bundle: create GB_DEVICE_ID_BAD
greybus: protocol: add a module owner to a protocol
greybus: vibrator-gb: move vibrator protocol to a stand-alone module.
greybus: battery-gb: move the battery protocol out to a stand-alone module
greybus: gpb: Create a "GP Bridge" kernel module
greybus: i2c-gb: move i2c protocol into the gpbridge driver
greybus: module: get rid of global list of modules
greybus: protocol: name protocols.
greybus: protocol: fix oops when no protocol is assigned
greybus: sysfs: put a \n at the end of all sysfs files
greybus: Makefile: provide install target
greybus: rename gpbridge.ko to gb-phy.ko
greybus: rename vibrator-gb.ko to gb-vibrator.ko
greybus: rename battery-gb.c to gb-battery.c
greybus: rename es1-ap-usb.ko to gb-es1.ko
greybus: Move the es1_ap_desc.c file to Documentation directory
greybus: es1: no need to assign a variable we return directly
greybus: es2: add ES2 Greybus Host driver
greybus: Revert "protocol.c: fix a kernel panic caused by __gb_protocol_register"
greybus: Revert "protocol: dedup protocol find code"
greybus: ap.c: our workqueue should be ordered
greybus: es1.c: wait until the last possible minute to start the svc messages
greybus: es2: fix USB id to not be the same as ES1
greybus: es1: fix build warning for apb1_log_enable_write
greybus: es1: fix tiny whitespace issues
greybus: es1: use <linux/uaccess.h> and not <asm/uaccess.h>
greybus: es1: no need to check for NULL on debugfs_remove()
greybus: es1: struct file_operations needs to be const
greybus: es1: decimal modes are not what are wanted for debugfs
greybus: es1: move debugfs function to use kstrotoint_from_user()
greybus: es1: fix checkpatch warning about blank lines needed
greybus: es1: separate usb_log enable/disable logic into different functions
greybus: es1: allow the debug log to be stopped
greybus: Merge branch 'es1-fixes' into master
greybus: debugfs: we shouldn't care if debugfs is working or not
greybus: es2: sync up with recent es1.c changes
greybus: loopback: fix build breakage about SZ_4K
greybus: loopback: use the attribute groups, not group
greybus: kernel_ver.h: add sysfs_create_groups() and sysfs_remove_groups()
greybus: Documentation/sysfs: add a proposed sysfs tree for greybus
greybus: module.c: add attributes
greybus: endo: add endo structures and logic
greybus: endo: hook up endos into the device tree
greybus: Documentation/sysfs-bus-greybus: update kernel version and date
greybus: Documentation/sysfs-bus-greybus: document the endo and SVC
greybus: Documentation/sysfs-bus-greybus: document module sysfs files
greybus: gpbridge.h: whitespace cleanups
greybus: gpbridge.h: remove BIT() define
greybus: bundle: add state sysfs file
greybus: MODULE_LICENSE cleanup
greybus: kernel_ver.h: fix leading space coding style issues
greybus: kernel_ver.h: add a ' ' character to KERNEL_VERSION()
greybus: kernel_ver.h: add a blank line after a variable definition
greybus: protocol.h: macros should not have a trailing ';'
greybus: protocol.h: fix up long lines in define_get_version()
greybus: audio: fix some sparse static warnings
greybus: audio: fix a bunch of endian issues
greybus: vibrator: convert idr to be an ida
greybus: connection: remove lock around ida_simple_* functions
greybus: raw: add raw greybus kernel driver
greybus: Merge branch 'master' of github.com:gregkh/greybus
greybus: Merge branch 'master' of github.com:gregkh/greybus
greybus: es?: decrease buffer size to 2k
greybus: remove __init from .h files
greybus: Merge branch alex into Alexandre
greybus: Revert "greybus: reserve host cport id 0"
greybus: Revert "examples: add manifest file for sdio"
greybus: properly cleanup ida and idr structures when shutting down
greybus: connection: prevent oops for protocol_id sysfs file
greybus: loopback: add poll support to the iteration_count sysfs file
greybus: es1: remove svc endpoint message handling
greybus: es2: remove svc endpoint message handling
greybus: greybus.h: remove greybus_svc_in()
greybus: remove submit_svc from the host driver
greybus: interface: make gb_interface_destroy() static
greybus: core: clean up ida memory for host controller
greybus: svc: revert svc changes to keep things working for a while.
greybus: es1: remove svc endpoint message handling
greybus: es2: remove svc endpoint message handling
greybus: greybus.h: remove greybus_svc_in()
greybus: remove submit_svc from the host driver
greybus: interface: make gb_interface_destroy() static
greybus: Merge branch 'master' into svc to keep it up to date.
greybus: Revert "connection: remove special check for svc cport id"
greybus: Merge branch 'master' into svc
greybus: Merge branch 'master' into svc
greybus: Merge branch 'master' into svc
greybus: Merge branch 'master' into branch 'svc'.
greybus: Merge branch 'master' into branch 'svc'.
greybus: gpio: handle api change in generic_handle_irq_desc()
greybus: vibrator: use the bundle struct device instead of the connector
greybus: uart: use the bundle struct device instead of the connector
greybus: spi: use the bundle struct device instead of the connector
greybus: hid: use the bundle struct device instead of the connector
greybus: audio: use the bundle struct device instead of the connector
greybus: firmware: use the bundle struct device instead of the connector
greybus: gpio: use the bundle struct device instead of the connector
greybus: i2c: use the bundle struct device instead of the connector
greybus: pwm: use the bundle struct device instead of the connector
greybus: light : use the bundle struct device instead of the connector
greybus: sdio: use the bundle struct device instead of the connector
greybus: usb: use the bundle struct device instead of the connector
greybus: operation: use the bundle struct device instead of the connector
greybus: control: use the bundle struct device instead of the connector
greybus: raw: use the bundle struct device instead of the connector
greybus: connection: remove 'struct device' from 'struct gb_connection'
greybus: connection: use the bundle struct device instead of the connector
greybus: loopback: use the bundle struct device instead of the connector
greybus: greybus_trace: use the bundle struct device instead of the connector
greybus: protocol: use the bundle struct device instead of the connector
greybus: bundle.h: add a private field to struct gb_bundle
greybus: core: remove uevent handling for gb_connection
greybus: svc: don't trust any struct devices
greybus: es2: use official USB vendor/device id
greybus: hid: hid should not be part of the bridged-phy driver.
greybus: Camera: remove f_dentry usage
greybus: tools: Add tools directory to greybus repo and add loopback
greybus: loopback_test: fix warning about signed/unsigned comparison
greybus: loopback_test: null terminate the dict structure
greybus: tools: don't always build them.
greybus: arche-platform: merge arche-apb-ctrl and arche-platform
greybus: host: provide "generic" apbridge output calls
greybus: APBridge: move APBridge request protocol to a common .h file
greybus: uevent: add GREYBUS_ID to uevent
greybus: Makefile: fix up build test for gb-audio-codec.ko
greybus: Revert "camera: Fix backword compatibility in configure streams"
greybus: firmware: log the name of the firmware being requested
greybus: svc: add ping command
greybus: lsgb: add lsgb shell script
greybus: lsgb: Minor tweaks
greybus: svc: add a "watchdog" to check the network health
greybus: svc: return proper error code on watchdog create error
greybus: svc watchdog: allow it to be enabled/disabled from userspace
greybus: audio_manager: use an 'ida' for the module id
greybus: lsgb: remove it, it's in the gb-utils repo
greybus: fix sparse warning in manifest.c
greybus: only build arche platform driver if usb3613 is enabled
greybus: Revert "gb_loopback: Fix throughput calculations"
greybus: uart: properly calculate max buffer size
greybus: gpio: use bundle device for error messages
greybus: gpio: handle api changes for 4.5 kernel release
greybus: expose full 32 bits of vid/pid to userspace
greybus: gpbridge.h: move protocol init/exit prototypes
greybus: manifest: reserve the Bridged PHY class
greybus: properly annotate struct gb_control_timesync_enable_request
greybus: connection: add functions to get/set private data
greybus: convert drivers to use connection->private set/get
greybus: trivial: add checkpatch.pl to the tree
greybus: scripts: create subdir for checkpatch and spelling.txt
greybus: gpbridge: implement gpbridge "bus" logic
greybus: UART: convert to a gpbridge driver
greybus: PWM: convert to a gpbridge driver
greybus: I2C: convert to a gpbridge driver
greybus: GPIO: convert to a gpbridge driver
greybus: SDIO: convert to a gpbridge driver
greybus: SPI: convert to a gpbridge driver
greybus: USB: convert to a gpbridge driver
greybus: make greybus_gpbdev_type static
greybus: kernel_ver.h: provide pwm_is_enabled()
greybus: Revert "greybus: don't use spin_lock_irq()"
greybus: move all firmware prefix strings to one place
greybus: fix up copyright dates on arpc.h
greybus: Revert "svc: drop all symbol exports"
greybus: es2: remove #if 0 code
greybus: es2: remove struct cport_to_ep
greybus: es2: remove bulk_out array
greybus: es2: remove bulk_in array
greybus: es2: remove unneeded BULK_* #defines
greybus: es2: No need to check before freeing an urb
greybus: es2: fix up usb probe error messages
Merge greybus driver tree into 4.8-rc5
greybus: Add drivers/greybus to the build
greybus: remove local checkpatch.pl copy
greybus: kernel_ver.h: remove lots of stuff
greybus: Documentation: remove sysfs tree
greybus: remove README and LICENSE
greybus: remove .gitignore file
greybus: move the sysfs documentation to Documentation/ABI/testing
greybus: remove old es1 endpoint description
greybus: hid: remove KERNEL_VERSION checks
greybus: vibrator: remove KERNEL_VERSION checks
greybus: gpio: remove KERNEL_VERSION checks
greybus: spi: remove KERNEL_VERSION checks
greybus: sdio: fix min() type check
greybus: remove CONFIG_PM_RUNTIME from kernel_ver.h

Jackson Chang (1):
greybus: SDIO: Add runtime pm support

Jacopo Mondi (17):
greybus: camera: Add support for flags to stream_configure
greybus: camera: Stream config change unipro speed
greybus: camera: Reset link speed on failed config
greybus: camera: Add missing return value
greybus: camera: Add CSI configuration parameters
greybus: camera: Rename clock_mode to flags
greybus: camera: Register capabilities operation
greybus: camera: Early update num_streams and flags
greybus: camera: Fix number of configured streams
greybus: camera: Fix size of configure_streams(0)
greybus: camera: Add bpp to Greybus Format description
greybus: camera: Add wrapper for sync operation with flags
greybus: camera: Update Configure Streams Response
greybus: camera: Configure APB-A with new params
greybus: camera: Update CSI config parameters
greybus: camera: Remove reference to ara subdevice
greybus: camera: Remove support for legacy modules

Jeffrey Carlyle (8):
greybus: Revert "bootrom: Implement timeouts to detect Module failures"
greybus: svc: implement svc_intf_{vsys,refclk,unipro}_{enable,disable} operations
greybus: svc: implement svc_intf_activate
greybus: svc: support status in svc_intf_activate response
greybus: interface: retry enumeration of UniPro-only modules
greybus: Revert "update UniPro Set Interface Power Mode operation to match spec"
greybus: svc: implement connection_quiescing call
greybus: connection: switch to using spin_lock_irqsave/spin_lock_irqrestore excluisvely

Joel Porquet (2):
greybus: add support for the log protocol
greybus: firmware.h: rename TFTF firmware filenames

Johan Hovold (547):
greybus: connection: fix non-atomic allocations under spin lock
greybus: bundle: fix sleep-while-atomic in gb_bundle_destroy
greybus: gpio: fix memory leaks at init and exit
greybus: operation: fix locking issues
greybus: connection: fix locking in gb_hd_connection_find
greybus: pwm: fix memory leak in error path
greybus: vibrator: fix memory leak in error path
greybus: connection: fix oops after failed init
greybus: gpio: fix set-debounce request alignment
greybus: pwm: fix config-request alignment
greybus: uart: remove packed-attribute from line-coding struct
greybus: usb: silence compiler warning
greybus: ap: fix svc handshake protocol check
greybus: ap: fix typo in comment
greybus: ap: clean up svc link management error path
greybus: connection: replace custom error function with dev_err
greybus: operation: use dev_err in gb_operation_sync
greybus: operation: remove unnecessary cast
greybus: gpio: fix truncated debounce times
greybus: gpio: remove incorrect todo comments
greybus: gpio: remove redundant argument verification
greybus: gpio: remove overly defensive argument verification
greybus: gpio: remove unnecessary explicit cast
greybus: gpio: make gb_gpio_controller pointer naming consistent
greybus: gpio: fix error handling
greybus: gpio: use dev_err and dev_warn
greybus: gpio: clean up line-state allocation
greybus: gpio: refuse to set value of input pins
greybus: gpio: add error messages to callbacks not propagating errors
greybus: operation: fix missing symbol exports
greybus: operation: fix typo in comment
greybus: operation: fix callback handling and documentation
greybus: operation: fix use-after-free when sending responses
greybus: operation: fix memory leak in request_send error path
greybus: operation: fix use-after-free and infinite loop on unhandled requests
greybus: operation: fix null-deref on operation cancel
greybus: operation: fix null-deref on operation destroy
greybus: operation: fix incoming request payload size
greybus: operation: replace pr_err with dev_err
greybus: operation: make incomplete-message errors more informative
greybus: gpio: fix null-deref on short irq requests
greybus: gpio: fix null-deref on unexpected irq requests
greybus: gpio: fix missing response on request errors
greybus: hid: replace pr_err with dev_err
greybus: hid: fix success response being sent on errors
greybus: hid: fix null-deref on short report requests
greybus: hid: fix missing input verification of report events
greybus: hid: fix missing response on request errors
greybus: operation: refactor response handling
greybus: es1: drop unnecessary casts
greybus: es1: fix DMA-buffer on stack
greybus: es1: fix buffer-size limit
greybus: es2: sync up with recent es1 changes
greybus: operation: fix unaligned memory accesses in receive path
greybus: remove obsolete buffer-alignment requirement
greybus: operation: move message-header definition to header file
greybus: pass messages to host drivers
greybus: operation: fix potential message corruption
greybus: operation: add explicit padding to message header
greybus: es1: fix transfer-buffer alignment
greybus: es2: sync up with recent es1 changes
greybus: drop host-driver buffer headroom
greybus: loopback: fix 64-bit divisions
greybus: remove unnecessary greybus.h includes
greybus: remove unused prototypes from header
greybus: fix host-device buffer constraints
greybus: operation: allow maximum-sized messages
greybus: core: add lower-limit for host-device buffers
greybus: operation: add helper to retrieve max payload size
greybus: gpbridge: fix section mismatches
greybus: operation: add support for incoming unidirectional operations
greybus: gpio: fix debugfs output
greybus: gpio: remove unused irq-ack operation
greybus: gpio: don't call irq-flow handler directly
greybus: gpio: use irq-domain lookups
greybus: gpio: rename irq mask and unmask callbacks
greybus: gpio: fix atomic sleep when using interrupts
greybus: gpio: fix interrupt protocol
greybus: gpio: update operation types
greybus: gpio: remove unused debounce define
greybus: gpio: add irq-type defines
greybus: esx: fix null-deref on hotplug events
greybus: operation/esx: fix message-cancellation lifetime bugs
greybus: operation: allow atomic operation allocations
greybus: operation: allow atomic request submissions
greybus: operation: make response helper static
greybus: operation: fix cancellation use-after-free
greybus: operation: add incoming-operation flag
greybus: operation: add unidirectional-operation flag
greybus: operation: remove obsolete operation-field comments
greybus: operation: drop redundant oom message
greybus: operation: allocate response before setting result
greybus: operation: fix cancellation of responses
greybus: operation: fix operation-destroy race
greybus: operation: fix use-after-free in response receive path
greybus: operation: fix outgoing-response corruption
greybus: operation: fix incoming-response corruption
greybus: operation: fix another cancellation use-after-free
greybus: operation: clean up create-incoming error path
greybus: operation: add active counter
greybus: operation: make cancellation synchronous
greybus: operation: complete operations on cancellation
greybus: operation: clean up operation work function
greybus: operation: suppress response submission on connection tear down
greybus: connection: add connection-state locking
greybus: operation: fix connection tear down
greybus: connection: fix protocol tear-down race
greybus: operation: fix operation look-up race
greybus: operation: fix response-cancellation race
greybus: operation: split incoming and outgoing cancellation
greybus: operation: allow drivers to define custom timeouts
greybus: operation: add gb_operation_sync_timeout helper
greybus: kernel_ver: add list_last_entry for old kernels
greybus: operation: fix atomic response allocation
greybus: operation: fix atomic message submission
greybus: operation: fix operation ordering
greybus: operation: clean up greybus_message_sent
greybus: connection: fix connection initialisation
greybus: connection: clean up connection creation
greybus: connection: clean up creation error paths
greybus: operation: use per-connection work queues
greybus: operation: add completion work queue
greybus: usb: clean up driver
greybus: usb: renumber operation types
greybus: usb: fix hcd allocation, deregistration and deallocation
greybus: usb: fix hc_driver fields
greybus: usb: implement hub_control callback
greybus: usb: disable protocol driver
greybus: greybus_protocols: fix typo in comment
greybus: svc: fix message packing
greybus: uart: fix message packing
greybus: sdio: fix message packing
greybus: greybus_protocols: use only type attributes for message packing
greybus: firmware: fix potential stack corruption
greybus: endo: fix endo-id allocation flag
greybus: svc: fix device-id allocation flag
greybus: svc: fix hot-plug-state allocation flag
greybus: interface: fix potential attribute-buffer overflow
greybus: pwm: replace pr_err with dev_err
greybus: core: fix hd-creation error path
greybus: es1: fix build-time cport constraint
greybus: fix cport-id defines
greybus: vibrator: fix tear-down race
greybus: sdio: fix tear-down use-after-free
greybus: sdio: fix work-queue leak and use-after-free
greybus: svc: fix ida memory leak
greybus: svc: fix version response
greybus: greybus_protocols: add missing version-request definition
greybus: connection: clean up svc-connection creation
greybus: connection: clean up init error paths
greybus: connection: clean up control-disconnected helper
greybus: connection: add control-connected helper
greybus: connection: add protocol-version helper
greybus: hd: add optional cport enable and disable callbacks
greybus: hd: remove connection-create/destroy callbacks
greybus: es1/2: fix use-after-free in completion callback
greybus: operation: remove gb_operation_destroy
greybus: es1/es2: clean up error messages
greybus: connection: replace pr_err with dev_err
greybus: operation: drop OOM-response FIXME
greybus: operation: clean up error messages
greybus: protocol: warn on bad deregistration
greybus: protocol: warn on protocol put errors
greybus: protocol: make version debug message more informative
greybus: vibrator: add missing protocol-register error handling
greybus: protocol: make protocol-lookup error message more informative
greybus: connection: remove duplicate protocol lookup error message
greybus: build: add pwm to config options to enable
greybus: gpio: handle set_irq_flags api change
greybus: endo: fix use-after-free in error path
greybus: endo: fix device-id allocation
greybus: connection: remove unused time-stamp defines
greybus: control: remove unused request handler
greybus: es2: update obsolete bridge-device comments
greybus: remove unused host-device id
greybus: remove unused connection device type
greybus: connection: use bundle device for bundle error messages
greybus: connection: fix potential null-deref in hd_cport_enable
greybus: connection: fix potential null-deref in connection create
greybus: connection: fix potential null-deref when binding protocol
greybus: connection: kill gb_hd_connections_exit
greybus: endo: fix ida memory leak
greybus: remove obsolete comment
greybus: create host-device compilation unit
greybus: hd: rename host-device structure
greybus: hd: rename host-driver structure
greybus: hd: use common prefix for exported functions
greybus: es2: fix use-after-free at disconnect
greybus: es2: fix endpoint requirement check
greybus: es2: separate urb allocation and submission
greybus: es1: separate urb allocation and submission
greybus: es2: separate stopping and deallocating urbs
greybus: es1: separate stopping and deallocating urbs
greybus: es2: fix debugfs dentry leak
greybus: es1: fix debugfs dentry leak
greybus: es2: add more structure to probe error handling
greybus: es1: add more structure to probe error handling
greybus: hd: fix host-device life time issues
greybus: es2: clean up es2 destructor
greybus: loopback: fix layer violation
greybus: battery: fix class-device parent
greybus: light: fix class-device parent
greybus: interface: fix potential use-after-free at remove
greybus: interface: fix obsolete comment
greybus: use decimal notation for interface and cport ids
greybus: svc: use bit-macro for cport flags
greybus: firmware: fix information leak
greybus: firmware: break long lines
greybus: svc: fix missing version-request sanity checks
greybus: fix bundle-id match macros
greybus: remove unique-id matching
greybus: interface: remove unique id
greybus: fix vendor and product matching
greybus: move id-matching back to core
greybus: add class matching macro
greybus: connection: fix cport-id range
greybus: hd: fix cport-count check
greybus: Documentation/sysfs: update the example sysfs tree
greybus: Documentation/sysfs-bus-greybus: update the bus ABI documentation
greybus: interface: rename vendor and product attributes
greybus: interface: remove device_id attribute
greybus: interface: add interface_id attribute
greybus: bundle: rename class attribute
greybus: bundle: use hexadecimal notation for class attribute
greybus: bundle: add bundle_id attribute
greybus: hd: make host device a device
greybus: connection: drop parent parameter from connection create
greybus: interface: make interfaces children of host devices
greybus: bundle: rename bundle devices
greybus: svc: make svc a device
greybus: svc: clean up svc initialisation
greybus: svc: register svc device at hello
greybus: svc: move endo id and ap interface id to svc
greybus: interface: kill gb_create_bundle_connection
greybus: connection: clean up connection-creation interface
greybus: connection: fix potential null-deref on create
greybus: connection: kill GB_PROTOCOL_SKIP_SVC_CONNECTION
greybus: connection: add name field
greybus: connection: handle static connections
greybus: operation: handle static connections
greybus: protocol: handle static connections
greybus: hd: fix svc-connection handling
greybus: svc: clean up log messages
greybus: loopback: remove endo reference
greybus: kill the endo
greybus: connection: handle bundle-less connections in svc helpers
greybus: control: do not assume a control bundle
greybus: connection: unconditionally enable connections
greybus: manifest: refactor cport-descriptor release
greybus: interface: drop the control bundle
greybus: Documentation/sysfs: rename interface and bundle attributes
greybus: svc: clean up hotplug/unplug request handlers
greybus: svc: clean up deferred hotplug handler
greybus: svc: clean up interface-remove helper
greybus: svc: generalise deferred request handling
greybus: svc: defer processing of hot-unplug requests
greybus: svc: fix racy hotplug handling
greybus: svc: flush workqueue at connection exit
greybus: core: add bus id to uevents
greybus: core: add interface id to interface and bundle uevents
greybus: core: add bundle id to bundle uevents
greybus: svc: add missing boot-status error message
greybus: bundle: remove obsolete function prototype
greybus: bundle: kill gb_bundle_bind_protocols
greybus: connection: fail on missing protocol
greybus: connection: bind protocol at init
greybus: connection: unbind protocol at exit
greybus: connection: separate connection creation and enabling
greybus: hd: initialise device last
greybus: hd: make svc-connection life time coincide with host-device
greybus: svc: create svc along with host device
greybus: use decimal notation for interfaces, bundles and cports
greybus: interface: remove defensive WARN_ON
greybus: bundle: kill the bundle lock
greybus: bundle: use dev_err for bundle-creation errors
greybus: bundle: separate connection disabling and destruction
greybus: bundle: separate bundle creation and registration
greybus: interface: separate manifest parsing from bundle registration
greybus: interface: fix interface-registration race
greybus: interface: clean up error messages
greybus: es2: demote dynamic-urb error message
greybus: interface: clean up control-connection handling
greybus: interface: fix hot-unplug control-connection timeouts
greybus: svc: drop copy-pasted function header
greybus: es2: clean up csi-config request
greybus: camera: fix memory leak in configure-streams error path
greybus: camera: fix memory leak in capture-request handler
greybus: camera: destroy data connection on link-config errors
greybus: core: add drvdata accessors
greybus: core: fix greybus driver registration
greybus: core: fix greybus device matching
greybus: connection: fix version-request error handling
greybus: power_supply: fix lock imbalance in init error path
greybus: connection: fix connection-state handling
greybus: connection: refactor connection enable and disable
greybus: legacy: add legacy-protocol bundle driver
greybus: connection: rename legacy init and exit functions
greybus: connection: remove disable from destructor
greybus: connection: add per-connection request handlers
greybus: connection: always cancel operations on connection disable
greybus: connection: clean up operation cancellation on disable
greybus: connection: disable operations on enable errors
greybus: connection: make connection enable/disable thread safe
greybus: connection: add unidirectional enabled state
greybus: connection: add helper to disable incoming operations
greybus: core: disable incoming operations pre disconnect
greybus: core: add defensive connection disable post disconnect
greybus: firmware: remove skip-disconnected flag
greybus: connection: remove skip-connected legacy protocol flags
greybus: greybus_protocols: remove control-protocol version
greybus: control: drop legacy-protocol dependency
greybus: svc: drop legacy-protocol dependency
greybus: connection: move legacy-protocol handling to legacy driver
greybus: legacy: use protocol pointer to determine state
greybus: core: disable bundle connections on hot-unplug
greybus: svc: store protocol version
greybus: connection: remove broken protocol-version handling
greybus: control: add bundle-version operation
greybus: connection: remove WARN_ON from destroy
greybus: connection: destroy workqueue at unregister
greybus: connection: drop the connection_mutex
greybus: connection: fix lookup race
greybus: connection: serialise connection creation
greybus: connection: make cport lookup thread-safe
greybus: legacy: add private driver data
greybus: core: defer connection creation to driver probe
greybus: manifest: check for duplicate CPort descriptors when parsing
greybus: connection: drop protocol parameter from static interface
greybus: connection: add helper to create control connections
greybus: connection: drop the legacy protocol-id parameter
greybus: connection: simplify connection-creation interface
greybus: connection: rename connection-create interface
greybus: connection: return error-valued pointer on creation errors
greybus: legacy: refactor legacy-connection handling
greybus: legacy: add initialized flag
greybus: connection: remove legacy protocol id from core
greybus: legacy: look up protocol at connection creation
greybus: connection: set request handlers at creation
greybus: hid: clean up init error paths
greybus: hid: convert to bundle driver
greybus: vibrator: convert to bundle driver
greybus: firmware: abort if AP_READY fails
greybus: hd: fix host-device-removal race
greybus: legacy: remove unimplemented classes from id table
greybus: firmware: convert to bundle driver
greybus: manifest: add interface-device prefix to messages
greybus: core: add device prefix to error messages
greybus: i2c: add bundle-device prefix to error messages
greybus: loopback: add missing pr_fmt
greybus: spi: add bundle-device prefix to error messages
greybus: bundle: remove private data field
greybus: raw: fix memory leak on disconnect
greybus: es2: fix cport-count error handling
greybus: Documentation: remove svc unique_id attribute
greybus: uart: add max-payload sanity check
greybus: uart: add missing serial-state sanity check
greybus: uart: fix incomplete receive-data sanity checks
greybus: Documentation/sysfs: add module devices
greybus: Documentation/sysfs: move module 1-4 to position 5
greybus: Documentation/sysfs: make 1-5 a 2x2 module
greybus: operation: add temporary pointer to response handler
greybus: operation: add support for short responses
greybus: loopback: fix double error count
greybus: raw: use hexadecimal notation for request types
greybus: connection: add connection-flag interface
greybus: svc: clean up CPortFlags handling
greybus: connection: generalise CPortFlags handling
greybus: connection: add CSD connection flag
greybus: hd: rename CPort-features callbacks
greybus: greybus_manifest: remove unused AP class and protocol
greybus: greybus_manifest: remove unused SVC class
greybus: interface: remove unused function prototype
greybus: interface: remove unused drvdata helpers
greybus: interface: add comment about early control-connection disable
greybus: svc: refactor interface-route creation
greybus: svc: refactor interface-route destruction
greybus: interface: separate initialisation and registration
greybus: interface: free bundles on initialisation errors
greybus: svc: always register interfaces at hotplug
greybus: interface: disable control connection on initialisation errors
greybus: interface: remove useless spinlock
greybus: interface: move interface-removal helper
greybus: interface: rename initialisation function
greybus: interface: separate disabling from removal
greybus: svc: unexport eject helper
greybus: svc: print an error message on failed eject attempts
greybus: greybus_protocols: remove svc-eject timeout define
greybus: core: fix two container-of macros
greybus: greybus_protocols: align DME-attribute values
greybus: svc: remove bogus interface-reset helper
greybus: greybus_protocols: remove bogus comment about svc bundle
greybus: svc: indent CPort flag defines properly
greybus: interface: move boot-status clearing to interface enable
greybus: interface: fix es2 boot-status mask
greybus: interface: clean up and rename init-status helper
greybus: interface: clean up ES3-bootroom-quirk handling
greybus: interface: move route creation to interface activation
greybus: interface: deactivate interface on enumeration failure
greybus: interface: clean up device-id handling
greybus: greybus_protocols: rename NULL DME selector index
greybus: interface: read DME attributes at activation
greybus: interface: add ES2 init-status quirk flag
greybus: interface: clean up ES2 VID/PID hack
greybus: interface: clean up DME attribute handling
greybus: interface: add Ara serial-number support
greybus: manifest: fix illegal free in error path
greybus: Documentation: move the interface power attributes
greybus: Documentation/sysfs: remove interface unique_id attribute
greybus: Documentation/sysfs: rename the svc eject attribute
greybus: Documentation/sysfs: add example control devices
greybus: interface: disable interface on registration failures
greybus: control: move timesync-operation functions
greybus: core: make the control object be a device
greybus: interface: register control device at hotplug
greybus: interface: move vendor and product strings to control device
greybus: control: return error pointer when failing to create control device
greybus: interface: reduce control-device lifetime
greybus: svc: make sure to deactivate all interfaces on disconnect
greybus: svc: keep interfaces registered during mode switch
greybus: svc: remove interface-remove helper
greybus: svc: keep error messages uniform
greybus: svc: fix pwrmon return value
greybus: svc: fix function-parameter indentation
greybus: svc: use a common prefix for debugfs functions
greybus: Documentation/sysfs: add entry for control device
greybus: Documentation/sysfs: sort entries alphabetically
greybus: svc: refactor interface re-enable
greybus: core: add module abstraction
greybus: module: implement controlled module removal
greybus: interface: add active state flag
greybus: svc: add stub functions for v_sys, refclk and unipro
greybus: svc: add stub interface-activate function
greybus: interface: implement interface activation and power down
greybus: svc: implement module inserted and removed operations
greybus: svc: implement interface mailbox event
greybus: interface: fix broken UniPro flow control
greybus: interface: remove unused prototype
greybus: fix outdated kernel-doc comment
greybus: operation: update gb_operation_request_send() documentation
greybus: operation: add support for initiating unidirectional operations
greybus: operation: add helper functions for unidirectional operations
greybus: control: implement mode-switch operation
greybus: loopback: remove unsupported version request
greybus: svc: define the version request
greybus: operation: fix definition of the invalid type
greybus: interface: fix type in enable error message
greybus: interface: remove unused interface-version quirk
greybus: connection: add no-flow-control connection flag
greybus: connection: add offloaded connection flag
greybus: connection: add helper for creating offloaded connection
greybus: audio: mark the data connection as offloaded
greybus: hd: move CPort allocation to host-device code
greybus: es2: fix probe error handling
greybus: es2: clean up CDSI CPort reservation
greybus: hd: generalise cport allocation
greybus: es2: clean up cport-reset handling
greybus: es2: add support for CDSI1 allocation
greybus: connection: add camera-data connected workaround
greybus: camera: fix data-connection handling
greybus: operation: fix broken activation logic
greybus: svc: fix another pwrmon return value
greybus: audio_apbridgea: fix two return values
greybus: svc: use EREMOTEIO for remote errors
greybus: interface: fix svc-resource error messages
greybus: remove redundant latency-tag sanity checks
greybus: core: avoid I/O to disconnected interfaces
greybus: interface: clean up bootrom quirk handling
greybus: interface: avoid I/O to bootrom during removal
greybus: connection: add control connection flag
greybus: control: implement disconnecting operation
greybus: svc: add stub connection-quiescing operation
greybus: connection: log cport-enable error messages
greybus: es2: reset cports at disable
greybus: connection: rename an error label
greybus: connection: move CPort Buffer configuration out of svc helpers
greybus: connection: remove unused invalid state
greybus: operation: clean up early connection-state check
greybus: operation: restructure activation state handling
greybus: operation: add helper for creating core operations
greybus: connection: implement proper connection closure
greybus: connection: add flush host-device callback
greybus: kernel_ver: backport reinit_completion to pre-3.13
greybus: control: add error message to mode-switch helper
greybus: interface: implement generic mode-switch functionality
greybus: svc: remove deprecated hotplug operations
greybus: fix tracepoint-related whitespace issues
greybus: hd: fix gb_hd_release tracepoint
greybus: fix forced disable of offloaded connections
greybus: hd: add flag argument to cport_enable callback
greybus: connection: prevent drivers from specifying core flags
greybus: connection: add support for high-priority connections
greybus: es2: add support for greybus cport flags
greybus: Revert "connection: switch to using spin_lock_irqsave/spin_lock_irqrestore excluisvely"
greybus: Revert "greybus: gb_connections_lock: don't use spin_lock_irq()"
greybus: Revert "greybus: es2.c: don't use spin_lock_irq()"
greybus: Revert "greybus: uart: don't use spin_lock_irq()"
greybus: es2: fix arpc return value
greybus: es2: fix arpc response lookups
greybus: connection: drop the svc quiescing operation
greybus: interface: add interface-type attribute
greybus: interface: partition attribute group
greybus: interface: make attributes type dependent
greybus: interface: amend interface registration message
greybus: module: suppress activation error message for dummy interfaces
greybus: interface: use an enum for interface type
greybus: interface: do not read DME during reactivation
greybus: interface: clean up ES3 activation-retry hack
greybus: interface: make sure type is invariant during reactivation
greybus: interface: prevent reactivation during removal
greybus: interface: fix power_state documentation
greybus: interface: hibernate UniPro link in activation error path
greybus: interface: fix interface_id attribute group
greybus: control: suppress bundle_activate() for bootrom
greybus: operation: fix broken response error messages
greybus: operation: fix broken response tracepoint
greybus: operation: clean up request handler
greybus: Documentation/sysfs: replace Ara references
greybus: replace Ara references
greybus: es2: fix arpc response-allocation error handling
greybus: es2: fix arpc error message
greybus: es2: fix arpc active-list corruption
greybus: remove unused protocol-version messages
greybus: control: remove some braces
greybus: connection: fix offloaded-connection ping cport id
greybus: es2: clean up ARPC symbol names
greybus: es2: always set result value
greybus: es2: allow ARPC result to be ignored
greybus: es2: add define for ARPC CPort requests
greybus: es2: rename USB vendor-request timeout define
greybus: greybus_protocols: fix apba vendor-request comment
greybus: es2: fix USB vendor-request prefixes
greybus: arpc: move arpc definitions to their own header
greybus: hd/es2: add cport_connected callback and ARPC
greybus: hd/es2: add cport_quiesce callback and ARPC
greybus: hd/es2: add cport_clear callback and ARPC
greybus: hd: add cport_shutdown callback
greybus: control: make disconnecting a core operation
greybus: es2: fix memory leak in probe error path
greybus: svc: drop all symbol exports
greybus: svc: drop unnecessary symbol exports
greybus: es2: implement flush callback
greybus: es2: implement shutdown callback
greybus: connection: implement new connection handling
greybus: hd/es2: remove obsolete callbacks
greybus: es2: remove obsolete cport-reset ARPC
greybus: svc: fix timeout indentation
greybus: greybus_protocols: remove svc key-event type

John Stultz (4):
greybus: Changes required for integrating into the Android build
greybus: build: Add -fno-pic for 32bit arm as well
greybus: Move briged phy structure definitions into gpbridge.h
greybus: gb-audio: Add integrated greybus audio driver

Kris Huang (3):
greybus: lights: Add runtime pm support
greybus: lights: enable multi color LED support
greybus: lights: Control runtime pm suspend/resume on AP side

Laurent Pinchart (25):
greybus: es2: Reserve CPorts 16 and 17
greybus: Add camera protocol definition
greybus: Add driver for the camera class protocol
greybus: camera: Fix endian conversion issues
greybus: svc: Add support for the link config operation
greybus: camera: Configure link speed for the data connection
greybus: es2: Add support for CSI transmitter configuration
greybus: camera: Configure the bridge CSI transmitter
greybus: camera: Set link power mode to HS-G2 with 2 lanes
greybus: camera: Fix remaining endian conversion issues
greybus: es2: Fix endian conversion issues
greybus: es2: Don't use stack memory as USB request data
greybus: camera: Raise the CSI-2 bandwidth
greybus: svc: Replace link config hack with standard operation
greybus: camera: Return the result flags from the configure_streams response
greybus: camera: Factorize link power mode configuration code into a function
greybus: camera: Clean up when AP link power mode configuration failed
greybus: camera: Set power mode after configuring streams
greybus: camera: Don't configure CSI TX in test only mode
greybus: camera: Implement the capabilities operation
greybus: camera: Convert to bundle driver
greybus: camera: Clean up on stream configuration failure
greybus: camera: Fix data connection setup
greybus: camera: Create and destroy data connection on demand
greybus: camera: Hardcode the APB-A CSI-2 TX parameters

Mark A. Greer (11):
greybus: gb-audio: Set clock edges to match rt5647 codec requirements
greybus: gb-audio: Allocate space for 20 configurations
greybus: gb-audio: Clean up codec name generation
greybus: gb-audio: Fix DAI formats and master/slave settings
greybus: gb-audio: cpu_dai.fmt does not exist in v4.1
greybus: gb-audio: Add I2C device for rt5647 codec
greybus: gb-audio: Remove useless comment
greybus: gb-audio: Activate TX CPort in PCM workqueue
greybus: gb-audio: Activate TX connection Bundle CPort ID
greybus: gb-audio: Set I2S Configuration according to ASOC requests
greybus: gb-audio: Set samples per message during init

Mark Greer (16):
greybus: Initial I2S definitions
greybus: gb-audio: Reset sample count when CPort deactivated
greybus: gb-audio: Ensure i2c adapter struct exists before using
greybus: audio: Remove I2S Bridged-PHY Protcol based audio driver
greybus: i2s: Remove deprecated I2S Bridged-PHY Protocol
greybus: audio: Allocate protocol and class values
greybus: audio: Use underscore in file name
greybus: audio: Add Audio Device Class Protocol definitions
greybus: audio: Add Greybus Audio Device Class Protocol helper routines
greybus: audio: Add direct audio streaming control for APBridgeA
greybus: audio: Register CPorts for specific directions
greybus: audio: Split start and stop APBridgeA requests
greybus: audio: Use CSD instead of E2EFC for audio data connections
greybus: audio: apbridgea: Remove GET_TX/RX_DELAY message types
greybus: audio: Remove GET_TX/RX_DELAY message types
greybus: audio: Fix incorrect direction value when enabling RX

Marti Bolivar (4):
greybus: uart-gb.c: replace alloc_tty_driver with tty_alloc_driver
greybus: uart-gb.c: dynamically allocate device numbers
greybus: gb_hd_connection_find(): fix "not found" case
greybus: remove obsolete SVC result codes

Matt Porter (33):
greybus: update svc_msg_header fields and users to match spec
greybus: move versioning from svc message header to handshake function
greybus: update GREYBUS_VERSION_[MAJOR|MINOR] to match spec
greybus: es1-ap-usb: fix svc control pipe flags
greybus: initialize all fields in an SVC handshake message
greybus: update descriptor module_id->module to match spec
greybus: remove serial number descriptor to match spec
greybus: remove unused function descriptor fields and change class->function_type
greybus: update string descriptor length field to __u8 type to match spec
greybus: update descriptor type enums to match renumbering in spec
greybus: es1-ap-usb: adjust SVC buffer size to handle worst case
greybus: remove additional unused fields from the cport descriptor
greybus: fix error message on parse of cport descriptor size
greybus: fix gb_add_module() by enabling the device_add()
greybus: implement core module removal path
greybus: fix manifest parsing problem with descriptor payload
greybus: fix gb_manifest_parse() successful return path
greybus: es1-ap-usb: handle -EPROTO in check_urb_status()
greybus: gpio-gb: remove unused status field from struct gb_gpio_get_value_request
greybus: gpio-gb: fix offset error checking and usage
greybus: ap: add support for the AP Device ID unipro management function message
greybus: module: add gb_module_find()
greybus: interface: add gb_interface_find()
greybus: ap: process the UniPro link up message
greybus: svc: remove unneeded fields from the unipro set route message payload
greybus: ap: add svc_set_route_send() command and use it on a link up event
greybus: svc: remove the DDB function message support
greybus: module: set device_id when initializing an interface
greybus: connection: call connection_init hook after setting the handler
greybus: add pwm protocol driver
greybus: remove unused gb_connection_handler externs
greybus: operation: add missing gb_operation_response_send() export
greybus: gpio: add interrupt handling support

Michael Mogenson (2):
greybus: remove gb_i2c_timeout_operation
greybus: remove gb_i2c_retries_operation

Michael Scott (13):
greybus: build: android: replace hard-coded build destination with variable
greybus: build: android: add all greybus modules to the build
greybus: build: android: Fix script which locates .ko files and moves them into ramdisk
greybus: build: android: replace Linaro build specific logic with AOSP equivalents
greybus: build: android: fix strip module build step
greybus: build: remove Android makefile
greybus: firmware: replace colons with underscore in firmware file request
greybus: arche-platform: ensure wake-detect pin is deasserted
greybus: arche-platform: bring SVC out of reset later in probe
greybus: arche-platform: assert wake-detect to complete WAKE_OUT event
greybus: arche-platform: reduce wait between WAKE_OUT checks
greybus: interface: clear upper 16-bits of version_id and product_id
greybus: adjust kernel version check for led_sysfs_is_disabled

Mitchell Tasman (1):
greybus: svc: reconfig APBridgeA-Switch link to handle required load

Perry Hung (13):
greybus: gb_operation: replace timeout workqueue
greybus: gb_operation: drop operation refcount on cancel
greybus: makefile: fix unused cflags
greybus: battery: fix panic on operation error
greybus: connection: silence warning on unbound protocols
greybus: svc: add route create operation
greybus: svc: create bidirectional routes on hotplug request
greybus: svc: connection: ask SVC to create connections
greybus: svc: add flags and traffic class parameter to connection create op
greybus: svc: add route create operation
greybus: svc: create bidirectional routes on hotplug request
greybus: svc: connection: ask SVC to create connections
greybus: svc: add flags and traffic class parameter to connection create op

Philip Yang (2):
greybus: HID: Add runtime pm support
greybus: power_supply: Add runtime pm support

Phong Tran (12):
greybus: loopback: add more clean up when init connection fails
greybus: svc: Fix endian of hotplug request data
greybus: svc: use macro for init and exit protocol
greybus: audio: Fix typo for macro I2S data version minor
greybus: audio: Remove the MODULE_LICENSE()
greybus: uart: Fix the memory leak in connection init
greybus: endo: move endo id map declaration to endo.h
greybus: uart: remove the redundant unregister chrdev
greybus: uart: fix the clean up while uart initiates connection unsucessfully
greybus: sdio: remove the redefine of sdio major and minor
greybus: sdio: change the order of remove and free mmc host
greybus: sdio: correct the usage of mmc request in work queues handler

Rob Herring (1):
greybus: es2: fix driver name collision with es1

Rui Miguel Silva (90):
greybus: es1: remove useless statement
greybus: es1: release urb on error path
greybus: protocol: dedup protocol find code
greybus: manifest: be coherent with protocol name
greybus: make: check kernel configuration options
greybus: makefile: add MMC to the required option list
greybus: sdio: extend sdio implementation
greybus: sdio: fix defines for older kernels
greybus: kernel_ver: add sg copy operations for kernel < 3.11
greybus: examples: add manifest file for sdio
greybus: sdio: split cmd_flags to there meaning
greybus: sdio: rework of event handler
greybus: sdio: fix return of get_cd and get_ro
greybus: sdio: fix call to stop command if no data exist
greybus: sdio: pass only data pointer to tranfer funtion
greybus: sdio: check number of blocks in transfer
greybus: sdio: add need poll to host caps
greybus: sdio: fix transfer buffer handling and blocks counting
greybus: makefile: add needed config options for lights
greybus: lights: add lights implementation
greybus: makefile: use POSIX functions for kernel cmp
greybus: greybus_protocol: remove unused lights macros
greybus: sdio: fix command type defines
greybus: svc: fix endianness for svc message
greybus: gpio: fix generic_handle_irq_desc api change
greybus: sdio: add field to get_caps response
greybus: sdio: fix card removable detection
greybus: sdio: convert greybus ocr values to mmc ones
greybus: sdio: clarify operator precedence
greybus: sdio: send data block details at command request
greybus: sdio: some cleanups in command function
greybus: battery: move battery file to power_supply
greybus: battery: move implementation to power_supply
greybus: makefile: add power_supply config check
greybus: power_supply: rework and operation changes
greybus: manifest: fix bundle descriptor parse
greybus: spi: add rdwr field to transfer descriptor
greybus: spi: add master and device config operations
greybus: spi: fix transfers bigger than greybus payload
greybus: lights: add v4l2 flash operations
greybus: lights: avoid channel torch double free
greybus: lights: default value for v4l2 flash controls
greybus: spi: move chipselect to one byte size
greybus: spi: rename label to undo operation
greybus: spi: validate spi master register
greybus: spi: unregister master on device add fail
greybus: spi: fix and cleanup spi devices handling
greybus: camera: fix data types of operations
greybus: power_supply: do not release failed supply alloc
greybus: power_supply: fix unregister on error path
greybus: power_supply: free supplies at release
greybus: power_supply: fix error path at supplies setup
greybus: power_supply: fix leak getting string properties
greybus: power_supply: fix use after free of power supply
greybus: power_supply: fix check for configured supply
greybus: svc: add interface eject operation
greybus: svc: add intf_eject attribute
greybus: makefile: add requirement for CONFIG_INPUT
greybus: kernel_ver: add handle for lights in 4.5
greybus: lights: remove sync operation and work queue
greybus: svc: add key event handling
greybus: power_supply: rework get descriptors
greybus: uart: fix double free of tty port
greybus: spi: add device_type field to device config
greybus: lights: remove unnecessary checks
greybus: lights: remove has_flash on failure
greybus: lights: fix check for configured lights
greybus: lights: remove unnecessary check
greybus: spi: fix message transfer over greybus
greybus: spi: use timeout request send operation
greybus: spi: rename rdwr field to xfer_flags
greybus: spi: add inprogress bit to xfer_flags
greybus: sdio: avoid extra memory operation at data transfer
greybus: power_supply: change property values to integer
greybus: power_supply: convert greybus properties to power supply properties
greybus: power_supply: remove references to kernel header
greybus: power_supply: add missing defines present in the specification
greybus: sdio: convert vdd kernel values to greybus
greybus: sdio: add switch voltage operation
greybus: greybus_protocol: fix order of sdio get caps response
greybus: sdio: increase maximum segment size
greybus: power_supply: add callback to handle power supply changes
greybus: power_supply: fix update interval check at request handler
greybus: power_supply: invalidate cache at update request
greybus: power_supply: fix name setting location
greybus: light: remove KERNEL_VERSION checks
greybus: sdio: remove KERNEL_VERSION checks
greybus: power_supply: remove KERNEL_VERSION checks
greybus: makefile: fix dependency of spi to spilib
greybus: Kconfig: enable possibility to select light driver

Ryan Lim (3):
greybus: loopback_test: Cancel only the tests running on selected devices
greybus: loopback_test: Fix -z argument bug
greybus: loopback_test: Add -f argument to cancel all tests

Sachin Pandhare (1):
greybus: manifest: simplify descriptor address calculation

Sandeep Patil (12):
greybus: power_supply: reverse version check for new psy API
greybus: hd: add bus_id attribute
greybus: Documentation/sysfs: add entry for host device bus_id
greybus: Documentation/sysfs: fix sysfs paths
greybus: greybus/core: add MODULE uevent var for all control devices
greybus: gpbridge: add gpbridge device type
greybus: gpbridge: make gpbridge device ids start with 1
greybus: gpbridge: add uevent vars for gpbridge devices
greybus: arche-platform: Fix the MODULE_LICENSE to match with header
greybus: gpbridge: rename 'gpbridge' to 'gbphy' everywhere
greybus: svc: remove input device handling in SVC protocol
greybus: es2: use a single bulk ep pair for all greybus data

Svetlin Ankov (1):
greybus: audio: Add Audio Manager

Vaibhav Agarwal (72):
greybus: Audio: Add skeleton code for GB virtual codec driver
greybus: audio: Build audio module conditionally
greybus: audio: Use greybus connection device for codec registration
greybus: audio: Add topology parser for GB codec
greybus: audio: Add GB Audio class protocol functionality in GB codec DAI ops
greybus: audio: Report uevent on GB codec module insertion/removal
greybus: audio: cleanup unnecessary dev_err messages
greybus: audio: Cleanup GB protocol connections in case of abrupt codec removal
greybus: audio: Enable codec module detection on different slots
greybus: audio_codec: update codec_name as per driver->name
greybus: audio: codec driver cleanup
greybus: audio: use variable 'is_connected' to maintain module state
greybus: audio: Enable support for multiple codec modules
greybus: audio: schedule workqueue to perform codec cleanup on module removal
greybus: audio: return success for stop trigger if device removed
greybus: audio: Use single codec driver registration
greybus: audio: Add module specific driver
greybus: audio: fix to resolve multiple audio module playback issue
greybus: audio: Fix widget pointer update during control parsing
greybus: audio: Added jack support to audio module
greybus: audio: Add I2S_RX path related settings
greybus: audio_manager: Split device type into i/p & o/p devices
greybus: audio: Update device type based on widget types
greybus: audio: acquire wakelock during active playback
greybus: audio: acquire wakelock during active playback
greybus: audio: Changes in response to ASoC cleanup
greybus: audio: Reorder gb_deactivate sequence to avoid protocol error
greybus: audio: Rename widget_type to jack_attrubute
greybus: audio: Identify jack type based on attributes reported
greybus: audio:gb_manager: Use proper locking around kobject_xxx
greybus: audio: Cleanup GB Audio bundle driver.
greybus: audio: Remove redundant lock protection & is_connected field
greybus: audio: Reorganize sequence in GB audio bundle & codec driver
greybus: audio: Reduce codec->lock granularity
greybus: audio: Use standard API to set connections' private data
greybus: audio: gb_manager: Use valid argument while removing ida allocated id.
greybus: audio: Fix memory leak
greybus: hd: Add API to release reserved CPorts
greybus: es2: Release reserved cports CDSI0 and CDSI1
greybus: audio: Report warning in case module is already removed
greybus: audio: Report DISCONNECT event after resource cleanup.
greybus: audio: Ratelimit err messages
greybus: Use mute_stream callback to initiate GB data xfer
greybus: Remove apbridgea_shutdown_xx sequence if already done
greybus: audio: Ratelimit err messages in bundle, topology driver
greybus: audio: topology: Add helper API to map controlid with widget name
greybus: added warning message in case of missing widget
greybus: audio: topology: Use csize while computing next_ptr in parser
greybus: audio: topology: Enable enumerated control support
greybus: audio: Fix incorrect codec state modification
greybus: audio: Maintain proper codec state during shutdown sequence.
greybus: audio: Avoid using ARA keyword
greybus: audio: Remove un-necessary goto statement
greybus: audio: Enable audio path based on control switch state only
greybus: audio: Split helper APIs based on stream direction
greybus: audio: Update dai_driver table with appropriate fields
greybus: audio: Update parameters for gbaudio_module_update API
greybus: audio: Use AIF widget to enable path between module & APB
greybus: audio: Add id to identify data connection
greybus: audio: Find data connection based on id
greybus: Remove unused field from data_connection
greybus: audio: Update pm runtime support in dai_ops callback
greybus: audio: Maintain runtime stream params for each DAI
greybus: audio: Maintain module stream state for each data connection
greybus: audio: Report jack removal along with module removal
greybus: audio: Add check for invalid index while mapping control
greybus: Use valid control pointer while freeing memory
greybus: audio: Remove unnecessary num_jack field from module_info
greybus: audio: Added jack_type support in topology struct
greybus: audio: Fetch jack_mask, button_mask from module's topology data
greybus: audio: Report jack events conditionally
greybus: audio: Avoid reporting spurious button events

Vaibhav Hiremath (67):
greybus: platform: Add platform driver for DB3 AP bridge
greybus: db3(arche)-platform: Remove all APB control code
greybus: arche-platform: Rename db3-platform to arche-platform
greybus: arche-apb-ctrl: Add APB control driver
greybus: arche-apb-ctrl: Assert reset to APB at the end of probe
greybus: arche_platform: Remove child's platform device as part of _remove() fn
greybus: arche-platform: Export gpio (reset & sys_boot) to user
greybus: arche-apb-ctrl: Set wake_detect gpio to low initially
greybus: arche-apb-ctrl: Do not bring APB out of reset in probe
greybus: arche-apb-ctrl: Remove wake_detect gpio and related code
greybus: arche-apb-ctrl: deassert reset at the end of probe
greybus: arche-platform: Add wake/detect support along with handshaking with AP
greybus: arche-apb-ctrl: Enable the clocks after regulator enable
greybus: arche-platform: Disable clock as part of driver remove
greybus: arche-apb-ctrl: Remove extra delay in APB reset
greybus: Revert "arche-apb-ctrl: Remove extra delay in APB reset"
greybus: arche-platform: make apb_state common to both platform drivers
greybus: arche-platform: Introduce coldboot_seq fn
greybus: arche-platform: Cancel delayed_work in driver remove fn
greybus: arche-platform: Rename cleanup fn to poweroff_seq
greybus: arche-platform: Add state variable to driver data
greybus: arche-platform: Pull wake/detect low in poweroff
greybus: arche_platform: Add sysfs to allow user to change state
greybus: arche-platform: Introduce FW_FLASHING state
greybus: arche-apb-ctrl: Re-arrange init sequence
greybus: arche-apb-ctrl: Rename init_seq and cleanup fn
greybus: arche-apb-ctrl: Set default state value to OFF
greybus: arche-apb-ctrl: Pass only platform_device to _seq fns
greybus: arche-apb-ctrl: Add sysfs to allow user to change state
greybus: arche-apb-ctrl: Introduce ara,init-disable property for APB
greybus: arche-apb-ctrl: Rename internal operational state fns
greybus: arche-apb-ctrl: Export operational fns from driver
greybus: arche-platform: Fix boot, poweroff and fw_flashing seq with APBs
greybus: arche-platform: Do not export any gpios
greybus: arche-apb-ctrl: Do not export any gpios
greybus: arche-platform: Control usb3613 from arche-platform
greybus: arche-apb-ctrl: Do not coldboot APBs in probe
greybus: arche-platform: Return immediately if in same state from state change fns
greybus: arche-platform: Avoid doing same thing again in poweroff fn
greybus: arche-apb-ctrl: Return immediately if in same state from state change fns
greybus: arche-platform: Fix exit path in probe fn
greybus: arche-apb-ctrl: Remove extra delay in APB reset
greybus: arche-platform: Convert delayed work to do only hub3613 configuration
greybus: arche-platform: Add wake detect state based on functionality
greybus: arche-platform: Enable interrupt support on wake/detect line
greybus: arche-platform: Assert wake/detect after SVC reset without delay
greybus: arche-platform: Set direction of wake/detect gpio in poweroff fn
greybus: arche-platform: Disable wake/detect interrupt in poweroff
greybus: arche-platform: Make sure APB power cycles on coldboot
greybus: arche-platform: Disable HUB3613 only in APB poweroff fn
greybus: arche-platfrom: Get rid of 2sec delay in USB3613 configuration
greybus: gpbridge: Add gpbridge driver init/exit helper macros
greybus: connection: export gb_connection_disable_rx() fn
greybus: Documentation: Add sysfs information about bridged-phy device
greybus: arche-platform: Rename spinlock variable lock => wake_detect_lock
greybus: arche-platform: Export fn to allow timesync driver to change the state
greybus: arche-platform: Do not assert wake/detect signal to SVC
greybus: arche-platform: Rename assert_wakedetect=>arche_platform_wd_irq_en
greybus: arche-platform: Make fw_flashing_seq() return error
greybus: arche-platform: Enter ACTIVE state only from OFF state
greybus: arche-platform: Enable SVC clock during FW_FLASHING state
greybus: svc: Pass the correct pointer to input_free_device()
greybus: ratelimit errors usually seen on unipro_reset
greybus: arche-platform: Add support for init-off feature
greybus: arche-apb-ctrl: Rename ara,init-disable => arche,init-disable
greybus: arche-platform: Reset SVC & APB only if turned off by SUSPEND_PREPARE
greybus: arche-platform: Add support for SPI bus sharing for Mihi

Viresh Kumar (410):
greybus: Fix parameters of core_param()
greybus: .gitignore: minor updates
greybus: gitignore: few more additions
greybus: manifest: no need to initialize 'result'
greybus: manifest: initialize variable during definition
greybus: manifest: don't free unallocated resources
greybus: Fix missing gb_protocol_exit() on gb_exit()
greybus: don't set ->dev.driver to NULL when it is already NULL
greybus: debug: mark debug messages with pr_debug() instead of printk
greybus: sysfs: generalize gb_module_attr() to capture more cases
greybus: module: free resources properly on failures
greybus: interface: free resources properly on failures
greybus: connection: free resources properly on failures
greybus: manifest: remove extra loop for finding module descriptor
greybus: manifest: improve print message
greybus: core: remove unnecessary braces
greybus: core: use 'drv' instead of dev->driver
greybus: operation: free resources in the reverse order of allocation
greybus: nullify dangling pointers
greybus: connection: try cancelling operations only if list isn't empty
greybus: manifest: make sure manifest_descs list is empty before parsing manifest
greybus: connection: fix duplicating naming in _gb_hd_connection_insert()
greybus: core: re-use gb_module_find() in gb_remove_module()
greybus: module: move gb_module_find() to a more logical location
greybus: module: move module specific code to module.c
greybus: interface: move gb_module_interface_init() to interface.c
greybus: ap: remove extra parameter to convert_ap_message()
greybus: ap: keep all svc function routines together
greybus: Pass '*ptr' to sizeof() wherever possible
greybus: ap: function_id is already set by svc_msg_alloc()
greybus: operation: don't complete operation twice
greybus: Random spell fixes
greybus: es1-ap-usb: don't use 'es1' after it is freed
greybus: Remove "-gb" suffix from .c files
greybus: Remove "gb-" prefix from .c files
greybus: uart: s/REQ/TYPE
greybus: create get_version() routines with the help of a macro
greybus: remove unused version-response structs
greybus: i2c: fix name conflict between function and struct: gb_i2c_transfer_response
greybus: i2c: fix name conflict between function and struct: gb_i2c_transfer_request
greybus: spi: add bridged-PHY spi protocol driver
greybus: spi:fix sparse warnings
greybus: gpb: Fix print mistakes
greybus: hid: add HID class driver
greybus: operation: s/status/result to match field name
greybus: operation: Fix comment mistake
greybus: interface: remove double underscore from fn name
greybus: core: place module_{init|exit}() right below the routines
greybus: hid: don't support OUTPUT report over interrupt channel
greybus: core: Don't initialize greybus if it is disabled
greybus: manifest: descriptor size should be >= header size
greybus: manifest: don't need to check for desc_size == 0 anymore
greybus: Greybus: Place module_init/exit() right after respective routines
greybus: interface: put module->dev on failures
greybus: kernel_ver.h: include <linux/kernel.h> to fix warning
greybus: Unregister devices to get them freed
greybus: interface: Fetch interface id instead of module id during setup
greybus: Add bundle descriptor type
greybus: hid: Use payload-size to get report size
greybus: bundle: Create bundles using bundle descriptors
greybus: bundle: Initialize all bundles on link-up
greybus: manifest: Use interface descriptor instead of module descriptor to get information
greybus: drop module descriptors
greybus: driver corresponds to a bundle, not interface
greybus: bundle: s/class_type/class
greybus: endo: Use a real endo id
greybus: endo: Rename endo's 'type' as 'id'
greybus: Explicitly mark structures as packed
greybus: manifest: Account for padding in string descriptor
greybus: manifest: Warn if descriptor size > expected size
greybus: manifest: Remove vendor, product and unique-id from interface descriptor
greybus: manifest: drop interface-version field
greybus: Remove class descriptor
greybus: Explicitly add pad-bytes to manifest descriptors
greybus: Move id-field before bundle-field in CPort Descriptor.
greybus: endo: Add code to validate Endo ID
greybus: endo: Create modules after validating Endo ID
greybus: raw: include uaccess.h to fix warning
greybus: gpio: fix tab/space mistake
greybus: spi: Move structure definitions into gpbridge.h
greybus: protocol: remove leading underscore from _gb_protocol_find()
greybus: connection: name routines consistently
greybus: endo: name routines consistently
greybus: interface: name routines consistently
greybus: module: name routines consistently
greybus: raw: move module_{init|exit} to the end of functions
greybus: gpbridge: rename gpb.c to gpbridge.c
greybus: protocol: Add gb_gpbridge_protocol_driver()
greybus: Use gb_gpbridge_protocol_init()
greybus: endo: Add missing '\n' sprintf() for sysfs files
greybus: operation: Remove unused variable 'buffer'
greybus: s/bundle_cport_id/intf_cport_id
greybus: Generate greybus wide unique ids for endo devices
greybus: Prefix module-id with endo id
greybus: Tear down devices in the reverse order
greybus: endo: Fix compilation warning
greybus: manifest: Allow bundles/connections list to be non-empty at manifest parsing
greybus: Add control protocol support
greybus: interface: Get manifest using Control protocol
greybus: hotplug function doesn't need to contain manifest now
greybus: connection: send [dis]connected events over control CPort
greybus: control: Warn if non-control cport/bundles have control protocol/classes
greybus: define more greybus classes
greybus: svc: Fix doc-style comment
greybus: svc: Use macro's for major/minor numbers
greybus: Rename gb_gpbridge_protocol_driver() as gb_builtin_protocol_driver()
greybus: control: Use gb_builtin_protocol_driver()
greybus: connection: bind protocol after the connection is operational
greybus: connection: intf_cport_id is local to an interface
greybus: svc: Drop svc_set_route_send()
greybus: interface: device_id belongs to the interface
greybus: bundles: remove gb_bundle(s)_init()
greybus: connection: Exit connection before destroying it
greybus: interface: Fix comment mistake (s/add/init)
greybus: connection: Use connection->dev for printing messages
greybus: svc: SVC is part of greybus core
greybus: svc: save pointer to struct gb_svc in struct gb_interface
greybus: Cleanup connection leftovers
greybus: connections: comment style fix
greybus: interface: drop stale comment
greybus: sync protocol and class definitions
greybus: protocol: move version_response structure to greybus_protocols.h
greybus: Define device ids for AP/SVC/modules
greybus: Define cport/bundle for SVC protocol
greybus: connection: don't send connected/disconnected events for SVC connection
greybus: connection: Create gb_connection_create_range() to specify hd-cport-id range
greybus: connection: Allow a bundle-less connection
greybus: interface: Update gb_create_control_connection() to support SVC protocol
greybus: svc: Add helpers to create AP<->SVC connection
greybus: svc: Implement SVC requests
greybus: svc: No need of a separate version request
greybus: svc: Fix build warning on 64bit systems
greybus: svc: fully initialize the partially initialized connection
greybus: svc: connection is created only once now
greybus: svc: remove interface->svc pointer
greybus: interface: localize gb_interface_destroy()
greybus: connection: compare hd_cport_id for matching SVC protocol
greybus: es1/2: Lets start using svc protocol
greybus: Switch to Cport 0 for svc and control protocol
greybus: get rid of old svc-protocol
greybus: es1: create svc connection early enough
greybus: remove svc_msg.h
greybus: initialize svc connection while creating hd
greybus: es1/2: Lets start using svc protocol
greybus: Switch to Cport 0 for svc and control protocol
greybus: get rid of old svc-protocol
greybus: es1: create svc connection early enough
greybus: remove svc_msg.h
greybus: initialize svc connection while creating hd
greybus: connection: remove special check for svc cport id
greybus: operation: Drop alignment attribute from operation message header
greybus: operation: Move operation header to greybus_protocols.h
greybus: svc: Move '{' to a new line in function definitions
greybus: raw: Print expected/actual payload size on mismatch
greybus: gpio: Print expected/actual payload size on mismatch
greybus: svc: Print expected/actual payload size on mismatch
greybus: svc: Handle hotplug request from a workqueue
greybus: sdio: error out only for smaller payloads received
greybus: hid: spell fix (s/infomation/information)
greybus: battery: Use (already defined) major/minor macros
greybus: gpio: Use (already defined) major/minor macros
greybus: hid: Use (already defined) major/minor macros
greybus: i2c: Use (already defined) major/minor macros
greybus: pwm: Use (already defined) major/minor macros
greybus: spi: Use (already defined) major/minor macros
greybus: uart: Use (already defined) major/minor macros
greybus: usb: Use (already defined) major/minor macros
greybus: vibrator: Use (already defined) major/minor macros
greybus: svc: Remove FIXME for firmware download
greybus: audio: Add '<' to the print message for short messages
greybus: svc: error out only for smaller payloads received
greybus: connection: disconnect connection on failures during initialization
greybus: define greybus wide protocol request numbers
greybus: connection: Save major/minor supported by module
greybus: connection: request protocol version before initializing connection
greybus: audio: Drop get_version support
greybus: battery: Drop get_version support
greybus: control: Drop get_version support
greybus: gpio: Drop get_version support
greybus: hid: Drop get_version support
greybus: i2c: Drop get_version support
greybus: loopback: Drop get_version support
greybus: pwm: Drop get_version support
greybus: raw: Drop get_version support
greybus: sdio: Drop get_version support
greybus: spi: Drop get_version support
greybus: uart: Drop get_version support
greybus: usb: Drop get_version support
greybus: vibrator: Drop get_version support
greybus: svc: preserve major/minor of protocol supported by SVC
greybus: protocol: Drop define_get_version support
greybus: protocol: Remove unnecessary params of gb_protocol_get_version()
greybus: interface: Preserve data received during hotplug event
greybus: protocol: Create request structure from within gb_protocol_get_version()
greybus: Add firmware protocol driver
greybus: connection: Send protocol version for firmware protocol
greybus: protocol: send own protocol version while requesting it
greybus: battery: Move request/response structure/definitions to greybus_protocols.h
greybus: hid: Move request/response structure/definitions to greybus_protocols.h
greybus: raw: Move request/response structure/definitions to greybus_protocols.h
greybus: connection: Destroy wq on failure
greybus: connection: Don't free resources freed by gb_connection_release()
greybus: Drop protocol specific _PROTOCOL_VERSION and _INVALID macros
greybus: operation: print message type on errors
greybus: loopback: Send some sensible data
greybus: loopback: Print error on data mismatch
greybus: operation: Fix wrong order of arguments
greybus: svc: get hd directly from connection
greybus: svc: Include system headers at the top
greybus: svc: reject invalid state requests
greybus: greybus_protocols: svc: Add missing comment
greybus: greybus_protocols: Pack structure as required by the module firmware
greybus: greybus_protocols: Pack all request/response structure
greybus: svc: No need to return errors from [gb_]svc_connection_destroy()
greybus: svc: Kill unnecessary wrapper functions
greybus: svc: unexport few internal functions
greybus: bundle: kill unnecessary forward declaration of routine
greybus: connection: staticize gb_connection_init()
greybus: connection: convert connected dev_warn() to dev_err()
greybus: connection: no need to verify connection->protocol
greybus: connection: Propagate error properly
greybus: connection: call gb_connection_exit() from gb_connection_destroy()
greybus: connection: protocol can be NULL in gb_connection_exit()
greybus: spi: Allow spi-core to allocate bus numbers dynamically
greybus: manifest: don't reject the interface on failing to initialize a cport
greybus: manifest: release cport descriptors to avoid 'excess descriptors' warning
greybus: connection: call gb_svc_connection_create() from gb_connection_init()
greybus: connection: destroy svc connection on connection exit or errors
greybus: connection: Call connection_destroy() from gb_connection_svc_connection_destroy()
greybus: Add flags to struct gb_protocol
greybus: svc: No need of per-direction route-create requests
greybus: svc: destroy the route on module hot-unplug
greybus: connection: Add sysfs 'ap_cport_id' file for connections
greybus: svc: Implement DME peer get/set attributes helpers
greybus: firmware: firmware's file name is 32 characters long
greybus: firmware: Fix incorrect firmware file's name
greybus: firmware: firmware image name has .tftf instead of .fw
greybus: firmware: Send AP-ready operation
greybus: Documentation: Update sysfs-bus-greybus
greybus: svc: Use 'dev' instead '&op->connection->dev'
greybus: svc: Allow consecutive hotplug events for the same module
greybus: svc: Read and clear module's boot status
greybus: interface: Pass interface pointer to gb_interface_remove()
greybus: interface: gb_interface_create() isn't called for existing interfaces
greybus: firmware: drop 'stage' from ready-to-boot request
greybus: firmware: Don't send control-disconnected event for firmware protocol
greybus: svc: skip setting flags for boot over unipro
greybus: bundle: fix double freeing of bundle structure
greybus: interface: fix double freeing of interface structure
greybus: module: fix double freeing of module structure
greybus: endo: move greybus_endo_setup() to endo.c
greybus: db3-platform: Use dev_info for success messages
greybus: es2: Send cport-id in wValue field to usb_control_msg()
greybus: es2: remove fake vendor/product ids
greybus: es1: Remove the (now) unused es1 driver
greybus: firmware: Fetch es2 VID/PID to distinguish module vendors
greybus: Documentation: Fix N-svc directory name
greybus: Documentation: Arrange entries in alphabetical order
greybus: svc: Use -EIO instead of -EINVAL for unipro errors
greybus: don't use %h and %hh for printing short and char variables
greybus: Prefix hexadecimal values with 0x while printing them
greybus: interface: Prefix hexadecimal values with '0x'
greybus: interface: Prefix DDBL1 attributes with ddbl1_ instead of unipro_
greybus: interface: Expose DDBL1 manufacturing and production id in sysfs
greybus: control: Use Macro's instead of direct values for major/minor
greybus: svc: Set interface's hotplug attributes before using them
greybus: interface: Receive serial-number on hotplug event
greybus: interface: Fetch and expose version of interface's firmware
greybus: arche-platform: arche_pdata is guaranteed to be valid
greybus: arche-platform: Export GPIOs after populating APBs
greybus: arche-platform: Put APB in reset if of_platform_populate() fails
greybus: arche-platform: svc_reset_gpio can't be invalid in arche_platform_cleanup()
greybus: arche-platform: propagate errors returned by gpiolib
greybus: arche-apb: Spelling and whitespace fixes
greybus: arche-apb: platform data 'apb' is guaranteed to be valid
greybus: arche-apb: Replace gpio_is_valid() with gpio < 0 checks
greybus: arche-apb: devm_regulator_get() doesn't return NULL
greybus: arche-apb: NULL is a valid regulator
greybus: arche-apb: Properly use dev_err/info/warn
greybus: arche-apb: Don't use gpio after failing to request it
greybus: arche-apb: Do cleanup within apb_ctrl_init_seq() for error cases
greybus: arche-apb: Drop unnecessary checks
greybus: arche: Remove unwanted headers and rearrange others
greybus: svc: Expose and retain VID/PID received from bootrom for ES2
greybus: audio_codec: Free gccodec on codec probe failure
greybus: audio_codec: Free gccodec on dia probe failure
greybus: audio_codec: convert to bundle driver
greybus: audio: Rename Audio class and remove the unused one
greybus: audio: Fix sparse warnings
greybus: control: Fix sparse warnings
greybus: connection: Fix sparse warnings around locking
greybus: raw: Use consistent label names in connection_init()
greybus: raw: Don't use (possibly) uninitialized raw->device in gb_raw_receive()
greybus: raw: convert to bundle driver
greybus: loopback: convert to bundle driver
greybus: lights: Break light setup into two parts
greybus: lights: convert to bundle driver
greybus: power_supply: Break supply setup into two parts
greybus: power_supply: convert to bundle driver
greybus: manifest: Parse cports (within a bundle) in the order from manifest blob
greybus: interface: Add print messages on interface initialization/removal
greybus: hid: Don't disable connection-tx before destroying hid-device
greybus: hid: Replace WARN_ON() with dev_err()
greybus: checkpatch.pl: Mark --no-tree as default option
greybus: firmware: Rename to bootrom protocol
greybus: bootrom: fix typo
greybus: audio-codec: Staticize few routines to fix build warnings
greybus: es2: Allow proper release of greybus host device
greybus: es2: disable cport-in after remove greybus host device
greybus: es2: don't protest when getting -ENOENT/ESHUTDOWN USB errors
greybus: es2: move ap_disconnect() below ap_probe()
greybus: arche-ctrl: Don't expose driver internals to arche-platform driver
greybus: Bundle: Initialize dma_mask for bundle device
greybus: firmware: Add firmware management bundle driver
greybus: firmware: Add firmware-download protocol driver
greybus: fw-download: Create a macro for length of firmware file's name
greybus: bootrom: Implement timeouts to detect Module failures
greybus: Revert "interface: Fetch and expose version of interface's firmware"
greybus: uart: Kill reference_count hack
greybus: fw-download: Manage firmware requests with kref
greybus: fw-download: Introduce timeouts for firmware downloads
greybus: bootrom: Implement timeouts to detect Module failures
greybus: fw-download: Replace timer with delayed-work
greybus: gpbridge: Expose protocol_id in sysfs
greybus: core: Rename greybus_module_match()
greybus: Fix probing of gpbridge devices
greybus: gpbridge: Export few routines
greybus: gpbridge: Create module_gpbridge_driver()
greybus: gpio: Create separate module
greybus: i2c : Create separate module
greybus: pwm: Create separate module
greybus: sdio: Create separate module
greybus: spi: Create separate module
greybus: uart: Create separate module
greybus: usb: Create separate module
greybus: gpbridge: Rename gb-phy.ko module
greybus: gpbridge: Remove (now) unused macro gb_gpbridge_builtin_driver()
greybus: Remove bridge PHY protocol specific classes
greybus: spi: Restructure spi.c to share it with other bundle drivers
greybus: spi: Separate out spilib from spi bridged PHY bundle driver
greybus: fw-management: Add firmware-management protocol driver
greybus: fw-management: Add ioctl to initiate mode-switch
greybus: fw-management: Free fw-mgmt only after all users are gone
greybus: firmware: Add SPI protocol support
greybus: Documentation: Document firmware-management interfaces
greybus: fw-management: Replace double-tabs with space for structures
greybus: fw-management: Fix 'make check' warnings
greybus: fw-management: Update header's license
greybus: fw-core: destroy connections on error
greybus: fw-management: Fix error message's text
greybus: Documentation/sysfs: Rename gpbridge to gbphy
greybus: Documentation/sysfs: gbphy0 is an invalid name
greybus: Remove unused VERSION specific macros
greybus: gbphy: Remove protocol specific version handling
greybus: fw-mgmt: Add hooks to do mode-switch
greybus: control: Mode-switch is a 'core' operation
greybus: interface: Mode switch takes over a second
greybus: Add comment about the mode_switch_started flag
greybus: pwm: Fix compilation with v4.3 kernel
greybus: bootrom: Compile as a separate module
greybus: core: Make greybus_match_one_id() return bool
greybus: Use BIT(2) for GREYBUS_ID_MATCH_CLASS
greybus: Remove legacy suspend/resume callbacks
greybus: hd: Export host device tracepoint from hd.c
greybus: connection: Avoid unnecessary line breaks
greybus: firmware: Fix spelling mistake
greybus: Remove extra blank lines
greybus: connection: Return bool from gb_connection_intf_find()
greybus: camera: Initialize mutex before using it
greybus: svc: disable connection after all its users are gone
greybus: manifest: Disallow reuse of control cport
greybus: firmware: Add license and copyright header to application
greybus: firmware: prefix char-device with 'gb-'
greybus: connection: add trace events before disabling connection
greybus: Revert "greybus: ratelimit errors usually seen on unipro_reset"
greybus: don't use spin_lock_irq()
greybus: uart: don't use spin_lock_irq()
greybus: es2.c: don't use spin_lock_irq()
greybus: gb_connections_lock: don't use spin_lock_irq()
greybus: bootrom: Enhance timeout error message
greybus: bootrom: send timeout in milliseconds to gb_bootrom_set_timeout()
greybus: bootrom: Wait for 10 seconds for mode-switch
greybus: kernel_ver: Add kstrtobool()
greybus: light: Initialize mutex before using it
greybus: Add Component Authentication Protocol support
greybus: Documentation: Document Authentication interfaces
greybus: es2: Fix 'make check' warnings with arpc
greybus: audio_topology: Fix compile warning
greybus: bootrom: Use "s2l" instead of boot stage in package name
greybus: interface: add power_state attribute
greybus: firmware: Improve test application
greybus: spilib: make spilib independent of gbphy
greybus: control: Print bundle-id in print messages
greybus: bootrom: Skip setting timeout in failure path of size request
greybus: bootrom: Don't print duplicate error messages
greybus: bootrom: Set timeout before sending AP_READY
greybus: bootrom: Create gb_bootrom_cancel_timeout()
greybus: bootrom: Rename download_firmware() as find_firmware()
greybus: firmware: Add runtime PM support
greybus: interface: Rename *_NO_PM as *_NO_BUNDLE_ACTIVATE
greybus: interface: Add quirk for no PM for S2 Loader
greybus: firmware: Add new status types for backend updated request
greybus: firmware: Add 'status' byte to backend fw version response
greybus: firmware: Update Documentation and sample application
greybus: firmware: use 'tag' instead of 'firmware_tag'
greybus: firmware: Fix typo in documentation
greybus: firmware: Remove extra parenthesis
greybus: firmware: s/_LEN/_SIZE
greybus: firmware: s/should/must
greybus: firmware: add comment to show size calculations

Vishal Bhoj (1):
greybus: build: android: avoid building for targets that don't have kernel binary

 Documentation/ABI/testing/sysfs-bus-greybus        |  275 +++
 drivers/Kconfig                                    |    2 +
 drivers/Makefile                                   |    1 +
 .../greybus/Documentation/firmware/authenticate.c  |  139 ++
 .../Documentation/firmware/firmware-management     |  333 +++
 drivers/greybus/Documentation/firmware/firmware.c  |  262 +++
 drivers/greybus/Kconfig                            |  219 ++
 drivers/greybus/Makefile                           |   96 +
 drivers/greybus/arche-apb-ctrl.c                   |  522 +++++
 drivers/greybus/arche-platform.c                   |  828 +++++++
 drivers/greybus/arche_platform.h                   |   39 +
 drivers/greybus/arpc.h                             |  109 +
 drivers/greybus/audio_apbridgea.c                  |  207 ++
 drivers/greybus/audio_apbridgea.h                  |  156 ++
 drivers/greybus/audio_codec.c                      | 1132 ++++++++++
 drivers/greybus/audio_codec.h                      |  283 +++
 drivers/greybus/audio_gb.c                         |  228 ++
 drivers/greybus/audio_manager.c                    |  184 ++
 drivers/greybus/audio_manager.h                    |   83 +
 drivers/greybus/audio_manager_module.c             |  258 +++
 drivers/greybus/audio_manager_private.h            |   28 +
 drivers/greybus/audio_manager_sysfs.c              |  102 +
 drivers/greybus/audio_module.c                     |  482 +++++
 drivers/greybus/audio_topology.c                   | 1442 +++++++++++++
 drivers/greybus/authentication.c                   |  429 ++++
 drivers/greybus/bootrom.c                          |  524 +++++
 drivers/greybus/bundle.c                           |  253 +++
 drivers/greybus/bundle.h                           |   90 +
 drivers/greybus/camera.c                           | 1400 ++++++++++++
 drivers/greybus/connection.c                       |  938 ++++++++
 drivers/greybus/connection.h                       |  129 ++
 drivers/greybus/control.c                          |  635 ++++++
 drivers/greybus/control.h                          |   65 +
 drivers/greybus/core.c                             |  361 ++++
 drivers/greybus/debugfs.c                          |   31 +
 drivers/greybus/devices                            |   11 +
 drivers/greybus/es2.c                              | 1597 ++++++++++++++
 drivers/greybus/firmware.h                         |   42 +
 drivers/greybus/fw-core.c                          |  312 +++
 drivers/greybus/fw-download.c                      |  465 ++++
 drivers/greybus/fw-management.c                    |  721 +++++++
 drivers/greybus/gb-camera.h                        |  127 ++
 drivers/greybus/gbphy.c                            |  360 ++++
 drivers/greybus/gbphy.h                            |  110 +
 drivers/greybus/gpio.c                             |  767 +++++++
 drivers/greybus/greybus.h                          |  154 ++
 drivers/greybus/greybus_authentication.h           |  120 ++
 drivers/greybus/greybus_firmware.h                 |  120 ++
 drivers/greybus/greybus_id.h                       |   26 +
 drivers/greybus/greybus_manifest.h                 |  177 ++
 drivers/greybus/greybus_protocols.h                | 2268 ++++++++++++++++++++
 drivers/greybus/greybus_trace.h                    |  531 +++++
 drivers/greybus/hd.c                               |  257 +++
 drivers/greybus/hd.h                               |   90 +
 drivers/greybus/hid.c                              |  536 +++++
 drivers/greybus/i2c.c                              |  343 +++
 drivers/greybus/interface.c                        | 1316 ++++++++++++
 drivers/greybus/interface.h                        |   88 +
 drivers/greybus/light.c                            | 1359 ++++++++++++
 drivers/greybus/log.c                              |  132 ++
 drivers/greybus/loopback.c                         | 1365 ++++++++++++
 drivers/greybus/manifest.c                         |  535 +++++
 drivers/greybus/manifest.h                         |   16 +
 drivers/greybus/module.c                           |  238 ++
 drivers/greybus/module.h                           |   34 +
 drivers/greybus/operation.c                        | 1239 +++++++++++
 drivers/greybus/operation.h                        |  210 ++
 drivers/greybus/power_supply.c                     | 1141 ++++++++++
 drivers/greybus/pwm.c                              |  338 +++
 drivers/greybus/raw.c                              |  381 ++++
 drivers/greybus/sdio.c                             |  884 ++++++++
 drivers/greybus/spi.c                              |   79 +
 drivers/greybus/spilib.c                           |  565 +++++
 drivers/greybus/spilib.h                           |   24 +
 drivers/greybus/svc.c                              | 1486 +++++++++++++
 drivers/greybus/svc.h                              |  109 +
 drivers/greybus/svc_watchdog.c                     |  198 ++
 drivers/greybus/timesync.c                         | 1357 ++++++++++++
 drivers/greybus/timesync.h                         |   45 +
 drivers/greybus/timesync_platform.c                |   77 +
 drivers/greybus/tools/.gitignore                   |    1 +
 drivers/greybus/tools/Android.mk                   |   10 +
 drivers/greybus/tools/Makefile                     |   31 +
 drivers/greybus/tools/README.loopback              |  198 ++
 drivers/greybus/tools/lbtest                       |  168 ++
 drivers/greybus/tools/loopback_test.c              | 1000 +++++++++
 drivers/greybus/uart.c                             | 1075 ++++++++++
 drivers/greybus/usb.c                              |  247 +++
 drivers/greybus/vibrator.c                         |  249 +++
 89 files changed, 37594 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-greybus
 create mode 100644 drivers/greybus/Documentation/firmware/authenticate.c
 create mode 100644 drivers/greybus/Documentation/firmware/firmware-management
 create mode 100644 drivers/greybus/Documentation/firmware/firmware.c
 create mode 100644 drivers/greybus/Kconfig
 create mode 100644 drivers/greybus/Makefile
 create mode 100644 drivers/greybus/arche-apb-ctrl.c
 create mode 100644 drivers/greybus/arche-platform.c
 create mode 100644 drivers/greybus/arche_platform.h
 create mode 100644 drivers/greybus/arpc.h
 create mode 100644 drivers/greybus/audio_apbridgea.c
 create mode 100644 drivers/greybus/audio_apbridgea.h
 create mode 100644 drivers/greybus/audio_codec.c
 create mode 100644 drivers/greybus/audio_codec.h
 create mode 100644 drivers/greybus/audio_gb.c
 create mode 100644 drivers/greybus/audio_manager.c
 create mode 100644 drivers/greybus/audio_manager.h
 create mode 100644 drivers/greybus/audio_manager_module.c
 create mode 100644 drivers/greybus/audio_manager_private.h
 create mode 100644 drivers/greybus/audio_manager_sysfs.c
 create mode 100644 drivers/greybus/audio_module.c
 create mode 100644 drivers/greybus/audio_topology.c
 create mode 100644 drivers/greybus/authentication.c
 create mode 100644 drivers/greybus/bootrom.c
 create mode 100644 drivers/greybus/bundle.c
 create mode 100644 drivers/greybus/bundle.h
 create mode 100644 drivers/greybus/camera.c
 create mode 100644 drivers/greybus/connection.c
 create mode 100644 drivers/greybus/connection.h
 create mode 100644 drivers/greybus/control.c
 create mode 100644 drivers/greybus/control.h
 create mode 100644 drivers/greybus/core.c
 create mode 100644 drivers/greybus/debugfs.c
 create mode 100644 drivers/greybus/devices
 create mode 100644 drivers/greybus/es2.c
 create mode 100644 drivers/greybus/firmware.h
 create mode 100644 drivers/greybus/fw-core.c
 create mode 100644 drivers/greybus/fw-download.c
 create mode 100644 drivers/greybus/fw-management.c
 create mode 100644 drivers/greybus/gb-camera.h
 create mode 100644 drivers/greybus/gbphy.c
 create mode 100644 drivers/greybus/gbphy.h
 create mode 100644 drivers/greybus/gpio.c
 create mode 100644 drivers/greybus/greybus.h
 create mode 100644 drivers/greybus/greybus_authentication.h
 create mode 100644 drivers/greybus/greybus_firmware.h
 create mode 100644 drivers/greybus/greybus_id.h
 create mode 100644 drivers/greybus/greybus_manifest.h
 create mode 100644 drivers/greybus/greybus_protocols.h
 create mode 100644 drivers/greybus/greybus_trace.h
 create mode 100644 drivers/greybus/hd.c
 create mode 100644 drivers/greybus/hd.h
 create mode 100644 drivers/greybus/hid.c
 create mode 100644 drivers/greybus/i2c.c
 create mode 100644 drivers/greybus/interface.c
 create mode 100644 drivers/greybus/interface.h
 create mode 100644 drivers/greybus/light.c
 create mode 100644 drivers/greybus/log.c
 create mode 100644 drivers/greybus/loopback.c
 create mode 100644 drivers/greybus/manifest.c
 create mode 100644 drivers/greybus/manifest.h
 create mode 100644 drivers/greybus/module.c
 create mode 100644 drivers/greybus/module.h
 create mode 100644 drivers/greybus/operation.c
 create mode 100644 drivers/greybus/operation.h
 create mode 100644 drivers/greybus/power_supply.c
 create mode 100644 drivers/greybus/pwm.c
 create mode 100644 drivers/greybus/raw.c
 create mode 100644 drivers/greybus/sdio.c
 create mode 100644 drivers/greybus/spi.c
 create mode 100644 drivers/greybus/spilib.c
 create mode 100644 drivers/greybus/spilib.h
 create mode 100644 drivers/greybus/svc.c
 create mode 100644 drivers/greybus/svc.h
 create mode 100644 drivers/greybus/svc_watchdog.c
 create mode 100644 drivers/greybus/timesync.c
 create mode 100644 drivers/greybus/timesync.h
 create mode 100644 drivers/greybus/timesync_platform.c
 create mode 100644 drivers/greybus/tools/.gitignore
 create mode 100644 drivers/greybus/tools/Android.mk
 create mode 100644 drivers/greybus/tools/Makefile
 create mode 100644 drivers/greybus/tools/README.loopback
 create mode 100755 drivers/greybus/tools/lbtest
 create mode 100644 drivers/greybus/tools/loopback_test.c
 create mode 100644 drivers/greybus/uart.c
 create mode 100644 drivers/greybus/usb.c
 create mode 100644 drivers/greybus/vibrator.c
#1 lore raw

Hi Greg,

Quoted 11 lines · Greg KH

Given that it's never a good idea to keep subsystems out of the mainline kernel, I've put together this pull request that adds the greybus driver layer to drivers/greybus/. Because this was 2 1/2 years of work, with many many developers contributing, I didn't want to flatten all of their effort into a few small patches, as that wouldn't be very fair. So I've built a git tree with all of the changes going back to the first commit, and merged it into the kernel tree, just like btrfs was merged into the kernel.

Unless people point out some major problems with this, I'd like to get it merged into 4.9-rc1.

I'm extremely concerned that these patches have *never* seen upstream review, and this pull request gives no real opportunity for people to make a judgement regarding the code, as many relevant parties have not been Cc'd.

Quoted 1 line

From a quick scan of the git tree, I can see code (that isn't even

placed under staging/) for which I have fundamental objections to as a maintainer, and has not been Cc'd to a relevant list.

For example, I see commit 5a450477311fbfe2 ("greybus: timesync: Add timesync core driver"). This states that it directly accesses the ARMv7 architected timer, though it's unclear as to precisely what it's doing since it introduces an (undocumented) compatible string, and what should be an unnecessary devicetree property.

That's never gone to the linux-arm-kernel mainline list, myself or Marc (as maintainers of the arch timer driver), nor has the binding seen any review on the devicetree mailing list.

Given that, for at least that patch, NAK.

Thanks,
Mark.

author maintainer
#2 lore raw
Quoted 12 lines · Mark Rutland

Hi Greg,

Quoted 8 lines · Greg KH

Given that it's never a good idea to keep subsystems out of the mainline kernel, I've put together this pull request that adds the greybus driver layer to drivers/greybus/. Because this was 2 1/2 years of work, with many many developers contributing, I didn't want to flatten all of their effort into a few small patches, as that wouldn't be very fair. So I've built a git tree with all of the changes going back to the first commit, and merged it into the kernel tree, just like btrfs was merged into the kernel.

Quoted 7 lines
Quoted 2 lines

Unless people point out some major problems with this, I'd like to get it merged into 4.9-rc1.

I'm extremely concerned that these patches have *never* seen upstream review, and this pull request gives no real opportunity for people to make a judgement regarding the code, as many relevant parties have not been Cc'd.

As I said, I will send a set of simple patches, I wanted to get this out as soon as possible and other things came up today. Will do it in the morning, sorry.

Quoted 10 lines

From a quick scan of the git tree, I can see code (that isn't even placed under staging/) for which I have fundamental objections to as a maintainer, and has not been Cc'd to a relevant list.

For example, I see commit 5a450477311fbfe2 ("greybus: timesync: Add timesync core driver"). This states that it directly accesses the ARMv7 architected timer, though it's unclear as to precisely what it's doing since it introduces an (undocumented) compatible string, and what should be an unnecessary devicetree property.

Quoted 3 lines

That's never gone to the linux-arm-kernel mainline list, myself or Marc (as maintainers of the arch timer driver), nor has the binding seen any review on the devicetree mailing list.

Hm, odd, I thought we had Rob review all of the device tree bindings, but maybe the timesync stuff missed him. And timesync is "odd" to say the least, wait until you see the firmware side of it :)

Let me post the patches tomorrow and then we can review them.

thanks,

greg k-h

author maintainer
#3 lore raw
Quoted 16 lines · Greg KH
Quoted 15 lines · Mark Rutland

Hi Greg,

Quoted 11 lines · Greg KH

Given that it's never a good idea to keep subsystems out of the mainline kernel, I've put together this pull request that adds the greybus driver layer to drivers/greybus/. Because this was 2 1/2 years of work, with many many developers contributing, I didn't want to flatten all of their effort into a few small patches, as that wouldn't be very fair. So I've built a git tree with all of the changes going back to the first commit, and merged it into the kernel tree, just like btrfs was merged into the kernel.

Unless people point out some major problems with this, I'd like to get it merged into 4.9-rc1.

Quoted 8 lines
Quoted 4 lines

I'm extremely concerned that these patches have *never* seen upstream review, and this pull request gives no real opportunity for people to make a judgement regarding the code, as many relevant parties have not been Cc'd.

As I said, I will send a set of simple patches, I wanted to get this out as soon as possible and other things came up today. Will do it in the morning, sorry.

Here's the timesync code pulled out into a simple patch if you want to see it.

Bryan, any explanations you want to provide that would help in clarifying Mark's issues?

thanks,

greg k-h

---
 drivers/greybus/timesync.c          | 1357 ++++++++++++++++++++++++++++++++++++
 drivers/greybus/timesync.h          |   45 +
 drivers/greybus/timesync_platform.c |   77 ++
 3 files changed, 1479 insertions(+)
drivers/greybus/timesync.c new file +1357 -0
@@ -0,0 +1,1357 @@
+/*
+ * TimeSync API driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+#include <linux/debugfs.h>
+#include <linux/hrtimer.h>
+#include "greybus.h"
+#include "timesync.h"
+#include "greybus_trace.h"
+
+/*
+ * Minimum inter-strobe value of one millisecond is chosen because it
+ * just-about fits the common definition of a jiffy.
+ *
+ * Maximum value OTOH is constrained by the number of bits the SVC can fit
+ * into a 16 bit up-counter. The SVC configures the timer in microseconds
+ * so the maximum allowable value is 65535 microseconds. We clip that value
+ * to 10000 microseconds for the sake of using nice round base 10 numbers
+ * and since right-now there's no imaginable use-case requiring anything
+ * other than a one millisecond inter-strobe time, let alone something
+ * higher than ten milliseconds.
+ */
+#define GB_TIMESYNC_STROBE_DELAY_US		1000
+#define GB_TIMESYNC_DEFAULT_OFFSET_US		1000
+
+/* Work queue timers long, short and SVC strobe timeout */
+#define GB_TIMESYNC_DELAYED_WORK_LONG		msecs_to_jiffies(10)
+#define GB_TIMESYNC_DELAYED_WORK_SHORT		msecs_to_jiffies(1)
+#define GB_TIMESYNC_MAX_WAIT_SVC		msecs_to_jiffies(5000)
+#define GB_TIMESYNC_KTIME_UPDATE		msecs_to_jiffies(1000)
+#define GB_TIMESYNC_MAX_KTIME_CONVERSION	15
+
+/* Maximum number of times we'll retry a failed synchronous sync */
+#define GB_TIMESYNC_MAX_RETRIES			5
+
+/* Reported nanoseconds/femtoseconds per clock */
+static u64 gb_timesync_ns_per_clock;
+static u64 gb_timesync_fs_per_clock;
+
+/* Maximum difference we will accept converting FrameTime to ktime */
+static u32 gb_timesync_max_ktime_diff;
+
+/* Reported clock rate */
+static unsigned long gb_timesync_clock_rate;
+
+/* Workqueue */
+static void gb_timesync_worker(struct work_struct *work);
+
+/* List of SVCs with one FrameTime per SVC */
+static LIST_HEAD(gb_timesync_svc_list);
+
+/* Synchronize parallel contexts accessing a valid timesync_svc pointer */
+static DEFINE_MUTEX(gb_timesync_svc_list_mutex);
+
+/* Structure to convert from FrameTime to timespec/ktime */
+struct gb_timesync_frame_time_data {
+	u64 frame_time;
+	struct timespec ts;
+};
+
+struct gb_timesync_svc {
+	struct list_head list;
+	struct list_head interface_list;
+	struct gb_svc *svc;
+	struct gb_timesync_host_device *timesync_hd;
+
+	spinlock_t spinlock;	/* Per SVC spinlock to sync with ISR */
+	struct mutex mutex;	/* Per SVC mutex for regular synchronization */
+
+	struct dentry *frame_time_dentry;
+	struct dentry *frame_ktime_dentry;
+	struct workqueue_struct *work_queue;
+	wait_queue_head_t wait_queue;
+	struct delayed_work delayed_work;
+	struct timer_list ktime_timer;
+
+	/* The current local FrameTime */
+	u64 frame_time_offset;
+	struct gb_timesync_frame_time_data strobe_data[GB_TIMESYNC_MAX_STROBES];
+	struct gb_timesync_frame_time_data ktime_data;
+
+	/* The SVC FrameTime and relative AP FrameTime @ last TIMESYNC_PING */
+	u64 svc_ping_frame_time;
+	u64 ap_ping_frame_time;
+
+	/* Transitory settings */
+	u32 strobe_mask;
+	bool offset_down;
+	bool print_ping;
+	bool capture_ping;
+	int strobe;
+
+	/* Current state */
+	int state;
+};
+
+struct gb_timesync_host_device {
+	struct list_head list;
+	struct gb_host_device *hd;
+	u64 ping_frame_time;
+};
+
+struct gb_timesync_interface {
+	struct list_head list;
+	struct gb_interface *interface;
+	u64 ping_frame_time;
+};
+
+enum gb_timesync_state {
+	GB_TIMESYNC_STATE_INVALID		= 0,
+	GB_TIMESYNC_STATE_INACTIVE		= 1,
+	GB_TIMESYNC_STATE_INIT			= 2,
+	GB_TIMESYNC_STATE_WAIT_SVC		= 3,
+	GB_TIMESYNC_STATE_AUTHORITATIVE		= 4,
+	GB_TIMESYNC_STATE_PING			= 5,
+	GB_TIMESYNC_STATE_ACTIVE		= 6,
+};
+
+static void gb_timesync_ktime_timer_fn(unsigned long data);
+
+static u64 gb_timesync_adjust_count(struct gb_timesync_svc *timesync_svc,
+				    u64 counts)
+{
+	if (timesync_svc->offset_down)
+		return counts - timesync_svc->frame_time_offset;
+	else
+		return counts + timesync_svc->frame_time_offset;
+}
+
+/*
+ * This function provides the authoritative FrameTime to a calling function. It
+ * is designed to be lockless and should remain that way the caller is assumed
+ * to be state-aware.
+ */
+static u64 __gb_timesync_get_frame_time(struct gb_timesync_svc *timesync_svc)
+{
+	u64 clocks = gb_timesync_platform_get_counter();
+
+	return gb_timesync_adjust_count(timesync_svc, clocks);
+}
+
+static void gb_timesync_schedule_svc_timeout(struct gb_timesync_svc
+					     *timesync_svc)
+{
+	queue_delayed_work(timesync_svc->work_queue,
+			   &timesync_svc->delayed_work,
+			   GB_TIMESYNC_MAX_WAIT_SVC);
+}
+
+static void gb_timesync_set_state(struct gb_timesync_svc *timesync_svc,
+				  int state)
+{
+	switch (state) {
+	case GB_TIMESYNC_STATE_INVALID:
+		timesync_svc->state = state;
+		wake_up(&timesync_svc->wait_queue);
+		break;
+	case GB_TIMESYNC_STATE_INACTIVE:
+		timesync_svc->state = state;
+		wake_up(&timesync_svc->wait_queue);
+		break;
+	case GB_TIMESYNC_STATE_INIT:
+		if (timesync_svc->state != GB_TIMESYNC_STATE_INVALID) {
+			timesync_svc->strobe = 0;
+			timesync_svc->frame_time_offset = 0;
+			timesync_svc->state = state;
+			cancel_delayed_work(&timesync_svc->delayed_work);
+			queue_delayed_work(timesync_svc->work_queue,
+					   &timesync_svc->delayed_work,
+					   GB_TIMESYNC_DELAYED_WORK_LONG);
+		}
+		break;
+	case GB_TIMESYNC_STATE_WAIT_SVC:
+		if (timesync_svc->state == GB_TIMESYNC_STATE_INIT)
+			timesync_svc->state = state;
+		break;
+	case GB_TIMESYNC_STATE_AUTHORITATIVE:
+		if (timesync_svc->state == GB_TIMESYNC_STATE_WAIT_SVC) {
+			timesync_svc->state = state;
+			cancel_delayed_work(&timesync_svc->delayed_work);
+			queue_delayed_work(timesync_svc->work_queue,
+					   &timesync_svc->delayed_work, 0);
+		}
+		break;
+	case GB_TIMESYNC_STATE_PING:
+		if (timesync_svc->state == GB_TIMESYNC_STATE_ACTIVE) {
+			timesync_svc->state = state;
+			queue_delayed_work(timesync_svc->work_queue,
+					   &timesync_svc->delayed_work,
+					   GB_TIMESYNC_DELAYED_WORK_SHORT);
+		}
+		break;
+	case GB_TIMESYNC_STATE_ACTIVE:
+		if (timesync_svc->state == GB_TIMESYNC_STATE_AUTHORITATIVE ||
+		    timesync_svc->state == GB_TIMESYNC_STATE_PING) {
+			timesync_svc->state = state;
+			wake_up(&timesync_svc->wait_queue);
+		}
+		break;
+	}
+
+	if (WARN_ON(timesync_svc->state != state)) {
+		pr_err("Invalid state transition %d=>%d\n",
+		       timesync_svc->state, state);
+	}
+}
+
+static void gb_timesync_set_state_atomic(struct gb_timesync_svc *timesync_svc,
+					 int state)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&timesync_svc->spinlock, flags);
+	gb_timesync_set_state(timesync_svc, state);
+	spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
+}
+
+static u64 gb_timesync_diff(u64 x, u64 y)
+{
+	if (x > y)
+		return x - y;
+	else
+		return y - x;
+}
+
+static void gb_timesync_adjust_to_svc(struct gb_timesync_svc *svc,
+				      u64 svc_frame_time, u64 ap_frame_time)
+{
+	if (svc_frame_time > ap_frame_time) {
+		svc->frame_time_offset = svc_frame_time - ap_frame_time;
+		svc->offset_down = false;
+	} else {
+		svc->frame_time_offset = ap_frame_time - svc_frame_time;
+		svc->offset_down = true;
+	}
+}
+
+/*
+ * Associate a FrameTime with a ktime timestamp represented as struct timespec
+ * Requires the calling context to hold timesync_svc->mutex
+ */
+static void gb_timesync_store_ktime(struct gb_timesync_svc *timesync_svc,
+				    struct timespec ts, u64 frame_time)
+{
+	timesync_svc->ktime_data.ts = ts;
+	timesync_svc->ktime_data.frame_time = frame_time;
+}
+
+/*
+ * Find the two pulses that best-match our expected inter-strobe gap and
+ * then calculate the difference between the SVC time at the second pulse
+ * to the local time at the second pulse.
+ */
+static void gb_timesync_collate_frame_time(struct gb_timesync_svc *timesync_svc,
+					   u64 *frame_time)
+{
+	int i = 0;
+	u64 delta, ap_frame_time;
+	u64 strobe_delay_ns = GB_TIMESYNC_STROBE_DELAY_US * NSEC_PER_USEC;
+	u64 least = 0;
+
+	for (i = 1; i < GB_TIMESYNC_MAX_STROBES; i++) {
+		delta = timesync_svc->strobe_data[i].frame_time -
+			timesync_svc->strobe_data[i - 1].frame_time;
+		delta *= gb_timesync_ns_per_clock;
+		delta = gb_timesync_diff(delta, strobe_delay_ns);
+
+		if (!least || delta < least) {
+			least = delta;
+			gb_timesync_adjust_to_svc(timesync_svc, frame_time[i],
+						  timesync_svc->strobe_data[i].frame_time);
+
+			ap_frame_time = timesync_svc->strobe_data[i].frame_time;
+			ap_frame_time = gb_timesync_adjust_count(timesync_svc,
+								 ap_frame_time);
+			gb_timesync_store_ktime(timesync_svc,
+						timesync_svc->strobe_data[i].ts,
+						ap_frame_time);
+
+			pr_debug("adjust %s local %llu svc %llu delta %llu\n",
+				 timesync_svc->offset_down ? "down" : "up",
+				 timesync_svc->strobe_data[i].frame_time,
+				 frame_time[i], delta);
+		}
+	}
+}
+
+static void gb_timesync_teardown(struct gb_timesync_svc *timesync_svc)
+{
+	struct gb_timesync_interface *timesync_interface;
+	struct gb_svc *svc = timesync_svc->svc;
+	struct gb_interface *interface;
+	struct gb_host_device *hd;
+	int ret;
+
+	list_for_each_entry(timesync_interface,
+			    &timesync_svc->interface_list, list) {
+		interface = timesync_interface->interface;
+		ret = gb_interface_timesync_disable(interface);
+		if (ret) {
+			dev_err(&interface->dev,
+				"interface timesync_disable %d\n", ret);
+		}
+	}
+
+	hd = timesync_svc->timesync_hd->hd;
+	ret = hd->driver->timesync_disable(hd);
+	if (ret < 0) {
+		dev_err(&hd->dev, "host timesync_disable %d\n",
+			ret);
+	}
+
+	gb_svc_timesync_wake_pins_release(svc);
+	gb_svc_timesync_disable(svc);
+	gb_timesync_platform_unlock_bus();
+
+	gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_INACTIVE);
+}
+
+static void gb_timesync_platform_lock_bus_fail(struct gb_timesync_svc
+						*timesync_svc, int ret)
+{
+	if (ret == -EAGAIN) {
+		gb_timesync_set_state(timesync_svc, timesync_svc->state);
+	} else {
+		pr_err("Failed to lock timesync bus %d\n", ret);
+		gb_timesync_set_state(timesync_svc, GB_TIMESYNC_STATE_INACTIVE);
+	}
+}
+
+static void gb_timesync_enable(struct gb_timesync_svc *timesync_svc)
+{
+	struct gb_svc *svc = timesync_svc->svc;
+	struct gb_host_device *hd;
+	struct gb_timesync_interface *timesync_interface;
+	struct gb_interface *interface;
+	u64 init_frame_time;
+	unsigned long clock_rate = gb_timesync_clock_rate;
+	int ret;
+
+	/*
+	 * Get access to the wake pins in the AP and SVC
+	 * Release these pins either in gb_timesync_teardown() or in
+	 * gb_timesync_authoritative()
+	 */
+	ret = gb_timesync_platform_lock_bus(timesync_svc);
+	if (ret < 0) {
+		gb_timesync_platform_lock_bus_fail(timesync_svc, ret);
+		return;
+	}
+	ret = gb_svc_timesync_wake_pins_acquire(svc, timesync_svc->strobe_mask);
+	if (ret) {
+		dev_err(&svc->dev,
+			"gb_svc_timesync_wake_pins_acquire %d\n", ret);
+		gb_timesync_teardown(timesync_svc);
+		return;
+	}
+
+	/* Choose an initial time in the future */
+	init_frame_time = __gb_timesync_get_frame_time(timesync_svc) + 100000UL;
+
+	/* Send enable command to all relevant participants */
+	list_for_each_entry(timesync_interface, &timesync_svc->interface_list,
+			    list) {
+		interface = timesync_interface->interface;
+		ret = gb_interface_timesync_enable(interface,
+						   GB_TIMESYNC_MAX_STROBES,
+						   init_frame_time,
+						   GB_TIMESYNC_STROBE_DELAY_US,
+						   clock_rate);
+		if (ret) {
+			dev_err(&interface->dev,
+				"interface timesync_enable %d\n", ret);
+		}
+	}
+
+	hd = timesync_svc->timesync_hd->hd;
+	ret = hd->driver->timesync_enable(hd, GB_TIMESYNC_MAX_STROBES,
+					  init_frame_time,
+					  GB_TIMESYNC_STROBE_DELAY_US,
+					  clock_rate);
+	if (ret < 0) {
+		dev_err(&hd->dev, "host timesync_enable %d\n",
+			ret);
+	}
+
+	gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_WAIT_SVC);
+	ret = gb_svc_timesync_enable(svc, GB_TIMESYNC_MAX_STROBES,
+				     init_frame_time,
+				     GB_TIMESYNC_STROBE_DELAY_US,
+				     clock_rate);
+	if (ret) {
+		dev_err(&svc->dev,
+			"gb_svc_timesync_enable %d\n", ret);
+		gb_timesync_teardown(timesync_svc);
+		return;
+	}
+
+	/* Schedule a timeout waiting for SVC to complete strobing */
+	gb_timesync_schedule_svc_timeout(timesync_svc);
+}
+
+static void gb_timesync_authoritative(struct gb_timesync_svc *timesync_svc)
+{
+	struct gb_svc *svc = timesync_svc->svc;
+	struct gb_host_device *hd;
+	struct gb_timesync_interface *timesync_interface;
+	struct gb_interface *interface;
+	u64 svc_frame_time[GB_TIMESYNC_MAX_STROBES];
+	int ret;
+
+	/* Get authoritative time from SVC and adjust local clock */
+	ret = gb_svc_timesync_authoritative(svc, svc_frame_time);
+	if (ret) {
+		dev_err(&svc->dev,
+			"gb_svc_timesync_authoritative %d\n", ret);
+		gb_timesync_teardown(timesync_svc);
+		return;
+	}
+	gb_timesync_collate_frame_time(timesync_svc, svc_frame_time);
+
+	/* Transmit authoritative time to downstream slaves */
+	hd = timesync_svc->timesync_hd->hd;
+	ret = hd->driver->timesync_authoritative(hd, svc_frame_time);
+	if (ret < 0)
+		dev_err(&hd->dev, "host timesync_authoritative %d\n", ret);
+
+	list_for_each_entry(timesync_interface,
+			    &timesync_svc->interface_list, list) {
+		interface = timesync_interface->interface;
+		ret = gb_interface_timesync_authoritative(
+						interface,
+						svc_frame_time);
+		if (ret) {
+			dev_err(&interface->dev,
+				"interface timesync_authoritative %d\n", ret);
+		}
+	}
+
+	/* Release wake pins */
+	gb_svc_timesync_wake_pins_release(svc);
+	gb_timesync_platform_unlock_bus();
+
+	/* Transition to state ACTIVE */
+	gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_ACTIVE);
+
+	/* Schedule a ping to verify the synchronized system time */
+	timesync_svc->print_ping = true;
+	gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_PING);
+}
+
+static int __gb_timesync_get_status(struct gb_timesync_svc *timesync_svc)
+{
+	int ret = -EINVAL;
+
+	switch (timesync_svc->state) {
+	case GB_TIMESYNC_STATE_INVALID:
+	case GB_TIMESYNC_STATE_INACTIVE:
+		ret = -ENODEV;
+		break;
+	case GB_TIMESYNC_STATE_INIT:
+	case GB_TIMESYNC_STATE_WAIT_SVC:
+	case GB_TIMESYNC_STATE_AUTHORITATIVE:
+		ret = -EAGAIN;
+		break;
+	case GB_TIMESYNC_STATE_PING:
+	case GB_TIMESYNC_STATE_ACTIVE:
+		ret = 0;
+		break;
+	}
+	return ret;
+}
+
+/*
+ * This routine takes a FrameTime and derives the difference with-respect
+ * to a reference FrameTime/ktime pair. It then returns the calculated
+ * ktime based on the difference between the supplied FrameTime and
+ * the reference FrameTime.
+ *
+ * The time difference is calculated to six decimal places. Taking 19.2MHz
+ * as an example this means we have 52.083333~ nanoseconds per clock or
+ * 52083333~ femtoseconds per clock.
+ *
+ * Naively taking the count difference and converting to
+ * seconds/nanoseconds would quickly see the 0.0833 component produce
+ * noticeable errors. For example a time difference of one second would
+ * loose 19200000 * 0.08333x nanoseconds or 1.59 seconds.
+ *
+ * In contrast calculating in femtoseconds the same example of 19200000 *
+ * 0.000000083333x nanoseconds per count of error is just 1.59 nanoseconds!
+ *
+ * Continuing the example of 19.2 MHz we cap the maximum error difference
+ * at a worst-case 0.3 microseconds over a potential calculation window of
+ * abount 15 seconds, meaning you can convert a FrameTime that is <= 15
+ * seconds older/younger than the reference time with a maximum error of
+ * 0.2385 useconds. Note 19.2MHz is an example frequency not a requirement.
+ */
+static int gb_timesync_to_timespec(struct gb_timesync_svc *timesync_svc,
+				   u64 frame_time, struct timespec *ts)
+{
+	unsigned long flags;
+	u64 delta_fs, counts, sec, nsec;
+	bool add;
+	int ret = 0;
+
+	memset(ts, 0x00, sizeof(*ts));
+	mutex_lock(&timesync_svc->mutex);
+	spin_lock_irqsave(&timesync_svc->spinlock, flags);
+
+	ret = __gb_timesync_get_status(timesync_svc);
+	if (ret)
+		goto done;
+
+	/* Support calculating ktime upwards or downwards from the reference */
+	if (frame_time < timesync_svc->ktime_data.frame_time) {
+		add = false;
+		counts = timesync_svc->ktime_data.frame_time - frame_time;
+	} else {
+		add = true;
+		counts = frame_time - timesync_svc->ktime_data.frame_time;
+	}
+
+	/* Enforce the .23 of a usecond boundary @ 19.2MHz */
+	if (counts > gb_timesync_max_ktime_diff) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	/* Determine the time difference in femtoseconds */
+	delta_fs = counts * gb_timesync_fs_per_clock;
+
+	/* Convert to seconds */
+	sec = delta_fs;
+	do_div(sec, NSEC_PER_SEC);
+	do_div(sec, 1000000UL);
+
+	/* Get the nanosecond remainder */
+	nsec = do_div(delta_fs, sec);
+	do_div(nsec, 1000000UL);
+
+	if (add) {
+		/* Add the calculated offset - overflow nanoseconds upwards */
+		ts->tv_sec = timesync_svc->ktime_data.ts.tv_sec + sec;
+		ts->tv_nsec = timesync_svc->ktime_data.ts.tv_nsec + nsec;
+		if (ts->tv_nsec >= NSEC_PER_SEC) {
+			ts->tv_sec++;
+			ts->tv_nsec -= NSEC_PER_SEC;
+		}
+	} else {
+		/* Subtract the difference over/underflow as necessary */
+		if (nsec > timesync_svc->ktime_data.ts.tv_nsec) {
+			sec++;
+			nsec = nsec + timesync_svc->ktime_data.ts.tv_nsec;
+			nsec = do_div(nsec, NSEC_PER_SEC);
+		} else {
+			nsec = timesync_svc->ktime_data.ts.tv_nsec - nsec;
+		}
+		/* Cannot return a negative second value */
+		if (sec > timesync_svc->ktime_data.ts.tv_sec) {
+			ret = -EINVAL;
+			goto done;
+		}
+		ts->tv_sec = timesync_svc->ktime_data.ts.tv_sec - sec;
+		ts->tv_nsec = nsec;
+	}
+done:
+	spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
+	mutex_unlock(&timesync_svc->mutex);
+	return ret;
+}
+
+static size_t gb_timesync_log_frame_time(struct gb_timesync_svc *timesync_svc,
+					 char *buf, size_t buflen)
+{
+	struct gb_svc *svc = timesync_svc->svc;
+	struct gb_host_device *hd;
+	struct gb_timesync_interface *timesync_interface;
+	struct gb_interface *interface;
+	unsigned int len;
+	size_t off;
+
+	/* AP/SVC */
+	off = snprintf(buf, buflen, "%s frametime: ap=%llu %s=%llu ",
+		       greybus_bus_type.name,
+		       timesync_svc->ap_ping_frame_time, dev_name(&svc->dev),
+		       timesync_svc->svc_ping_frame_time);
+	len = buflen - off;
+
+	/* APB/GPB */
+	if (len < buflen) {
+		hd = timesync_svc->timesync_hd->hd;
+		off += snprintf(&buf[off], len, "%s=%llu ", dev_name(&hd->dev),
+				timesync_svc->timesync_hd->ping_frame_time);
+		len = buflen - off;
+	}
+
+	list_for_each_entry(timesync_interface,
+			    &timesync_svc->interface_list, list) {
+		if (len < buflen) {
+			interface = timesync_interface->interface;
+			off += snprintf(&buf[off], len, "%s=%llu ",
+					dev_name(&interface->dev),
+					timesync_interface->ping_frame_time);
+			len = buflen - off;
+		}
+	}
+	if (len < buflen)
+		off += snprintf(&buf[off], len, "\n");
+	return off;
+}
+
+static size_t gb_timesync_log_frame_ktime(struct gb_timesync_svc *timesync_svc,
+					  char *buf, size_t buflen)
+{
+	struct gb_svc *svc = timesync_svc->svc;
+	struct gb_host_device *hd;
+	struct gb_timesync_interface *timesync_interface;
+	struct gb_interface *interface;
+	struct timespec ts;
+	unsigned int len;
+	size_t off;
+
+	/* AP */
+	gb_timesync_to_timespec(timesync_svc, timesync_svc->ap_ping_frame_time,
+				&ts);
+	off = snprintf(buf, buflen, "%s frametime: ap=%lu.%lu ",
+		       greybus_bus_type.name, ts.tv_sec, ts.tv_nsec);
+	len = buflen - off;
+	if (len >= buflen)
+		goto done;
+
+	/* SVC */
+	gb_timesync_to_timespec(timesync_svc, timesync_svc->svc_ping_frame_time,
+				&ts);
+	off += snprintf(&buf[off], len, "%s=%lu.%lu ", dev_name(&svc->dev),
+			ts.tv_sec, ts.tv_nsec);
+	len = buflen - off;
+	if (len >= buflen)
+		goto done;
+
+	/* APB/GPB */
+	hd = timesync_svc->timesync_hd->hd;
+	gb_timesync_to_timespec(timesync_svc,
+				timesync_svc->timesync_hd->ping_frame_time,
+				&ts);
+	off += snprintf(&buf[off], len, "%s=%lu.%lu ",
+			dev_name(&hd->dev),
+			ts.tv_sec, ts.tv_nsec);
+	len = buflen - off;
+	if (len >= buflen)
+		goto done;
+
+	list_for_each_entry(timesync_interface,
+			    &timesync_svc->interface_list, list) {
+		interface = timesync_interface->interface;
+		gb_timesync_to_timespec(timesync_svc,
+					timesync_interface->ping_frame_time,
+					&ts);
+		off += snprintf(&buf[off], len, "%s=%lu.%lu ",
+				dev_name(&interface->dev),
+				ts.tv_sec, ts.tv_nsec);
+		len = buflen - off;
+		if (len >= buflen)
+			goto done;
+	}
+	off += snprintf(&buf[off], len, "\n");
+done:
+	return off;
+}
+
+/*
+ * Send an SVC initiated wake 'ping' to each TimeSync participant.
+ * Get the FrameTime from each participant associated with the wake
+ * ping.
+ */
+static void gb_timesync_ping(struct gb_timesync_svc *timesync_svc)
+{
+	struct gb_svc *svc = timesync_svc->svc;
+	struct gb_host_device *hd;
+	struct gb_timesync_interface *timesync_interface;
+	struct gb_control *control;
+	u64 *ping_frame_time;
+	int ret;
+
+	/* Get access to the wake pins in the AP and SVC */
+	ret = gb_timesync_platform_lock_bus(timesync_svc);
+	if (ret < 0) {
+		gb_timesync_platform_lock_bus_fail(timesync_svc, ret);
+		return;
+	}
+	ret = gb_svc_timesync_wake_pins_acquire(svc, timesync_svc->strobe_mask);
+	if (ret) {
+		dev_err(&svc->dev,
+			"gb_svc_timesync_wake_pins_acquire %d\n", ret);
+		gb_timesync_teardown(timesync_svc);
+		return;
+	}
+
+	/* Have SVC generate a timesync ping */
+	timesync_svc->capture_ping = true;
+	timesync_svc->svc_ping_frame_time = 0;
+	ret = gb_svc_timesync_ping(svc, &timesync_svc->svc_ping_frame_time);
+	timesync_svc->capture_ping = false;
+	if (ret) {
+		dev_err(&svc->dev,
+			"gb_svc_timesync_ping %d\n", ret);
+		gb_timesync_teardown(timesync_svc);
+		return;
+	}
+
+	/* Get the ping FrameTime from each APB/GPB */
+	hd = timesync_svc->timesync_hd->hd;
+	timesync_svc->timesync_hd->ping_frame_time = 0;
+	ret = hd->driver->timesync_get_last_event(hd,
+		&timesync_svc->timesync_hd->ping_frame_time);
+	if (ret)
+		dev_err(&hd->dev, "host timesync_get_last_event %d\n", ret);
+
+	list_for_each_entry(timesync_interface,
+			    &timesync_svc->interface_list, list) {
+		control = timesync_interface->interface->control;
+		timesync_interface->ping_frame_time = 0;
+		ping_frame_time = &timesync_interface->ping_frame_time;
+		ret = gb_control_timesync_get_last_event(control,
+							 ping_frame_time);
+		if (ret) {
+			dev_err(&timesync_interface->interface->dev,
+				"gb_control_timesync_get_last_event %d\n", ret);
+		}
+	}
+
+	/* Ping success - move to timesync active */
+	gb_svc_timesync_wake_pins_release(svc);
+	gb_timesync_platform_unlock_bus();
+	gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_ACTIVE);
+}
+
+static void gb_timesync_log_ping_time(struct gb_timesync_svc *timesync_svc)
+{
+	char *buf;
+
+	if (!timesync_svc->print_ping)
+		return;
+
+	buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (buf) {
+		gb_timesync_log_frame_time(timesync_svc, buf, PAGE_SIZE);
+		dev_dbg(&timesync_svc->svc->dev, "%s", buf);
+		kfree(buf);
+	}
+}
+
+/*
+ * Perform the actual work of scheduled TimeSync logic.
+ */
+static void gb_timesync_worker(struct work_struct *work)
+{
+	struct delayed_work *delayed_work = to_delayed_work(work);
+	struct gb_timesync_svc *timesync_svc =
+		container_of(delayed_work, struct gb_timesync_svc, delayed_work);
+
+	mutex_lock(&timesync_svc->mutex);
+
+	switch (timesync_svc->state) {
+	case GB_TIMESYNC_STATE_INIT:
+		gb_timesync_enable(timesync_svc);
+		break;
+
+	case GB_TIMESYNC_STATE_WAIT_SVC:
+		dev_err(&timesync_svc->svc->dev,
+			"timeout SVC strobe completion %d/%d\n",
+			timesync_svc->strobe, GB_TIMESYNC_MAX_STROBES);
+		gb_timesync_teardown(timesync_svc);
+		break;
+
+	case GB_TIMESYNC_STATE_AUTHORITATIVE:
+		gb_timesync_authoritative(timesync_svc);
+		break;
+
+	case GB_TIMESYNC_STATE_PING:
+		gb_timesync_ping(timesync_svc);
+		gb_timesync_log_ping_time(timesync_svc);
+		break;
+
+	default:
+		pr_err("Invalid state %d for delayed work\n",
+		       timesync_svc->state);
+		break;
+	}
+
+	mutex_unlock(&timesync_svc->mutex);
+}
+
+/*
+ * Schedule a new TimeSync INIT or PING operation serialized w/r to
+ * gb_timesync_worker().
+ */
+static int gb_timesync_schedule(struct gb_timesync_svc *timesync_svc, int state)
+{
+	int ret = 0;
+
+	if (state != GB_TIMESYNC_STATE_INIT && state != GB_TIMESYNC_STATE_PING)
+		return -EINVAL;
+
+	mutex_lock(&timesync_svc->mutex);
+	if (timesync_svc->state !=  GB_TIMESYNC_STATE_INVALID) {
+		gb_timesync_set_state_atomic(timesync_svc, state);
+	} else {
+		ret = -ENODEV;
+	}
+	mutex_unlock(&timesync_svc->mutex);
+	return ret;
+}
+
+static int __gb_timesync_schedule_synchronous(
+	struct gb_timesync_svc *timesync_svc, int state)
+{
+	unsigned long flags;
+	int ret;
+
+	ret = gb_timesync_schedule(timesync_svc, state);
+	if (ret)
+		return ret;
+
+	ret = wait_event_interruptible(timesync_svc->wait_queue,
+			(timesync_svc->state == GB_TIMESYNC_STATE_ACTIVE ||
+			 timesync_svc->state == GB_TIMESYNC_STATE_INACTIVE ||
+			 timesync_svc->state == GB_TIMESYNC_STATE_INVALID));
+	if (ret)
+		return ret;
+
+	mutex_lock(&timesync_svc->mutex);
+	spin_lock_irqsave(&timesync_svc->spinlock, flags);
+
+	ret = __gb_timesync_get_status(timesync_svc);
+
+	spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
+	mutex_unlock(&timesync_svc->mutex);
+
+	return ret;
+}
+
+static struct gb_timesync_svc *gb_timesync_find_timesync_svc(
+	struct gb_host_device *hd)
+{
+	struct gb_timesync_svc *timesync_svc;
+
+	list_for_each_entry(timesync_svc, &gb_timesync_svc_list, list) {
+		if (timesync_svc->svc == hd->svc)
+			return timesync_svc;
+	}
+	return NULL;
+}
+
+static struct gb_timesync_interface *gb_timesync_find_timesync_interface(
+	struct gb_timesync_svc *timesync_svc,
+	struct gb_interface *interface)
+{
+	struct gb_timesync_interface *timesync_interface;
+
+	list_for_each_entry(timesync_interface, &timesync_svc->interface_list, list) {
+		if (timesync_interface->interface == interface)
+			return timesync_interface;
+	}
+	return NULL;
+}
+
+int gb_timesync_schedule_synchronous(struct gb_interface *interface)
+{
+	int ret;
+	struct gb_timesync_svc *timesync_svc;
+	int retries;
+
+	if (!(interface->features & GREYBUS_INTERFACE_FEATURE_TIMESYNC))
+		return 0;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	for (retries = 0; retries < GB_TIMESYNC_MAX_RETRIES; retries++) {
+		timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
+		if (!timesync_svc) {
+			ret = -ENODEV;
+			goto done;
+		}
+
+		ret = __gb_timesync_schedule_synchronous(timesync_svc,
+						 GB_TIMESYNC_STATE_INIT);
+		if (!ret)
+			break;
+	}
+	if (ret && retries == GB_TIMESYNC_MAX_RETRIES)
+		ret = -ETIMEDOUT;
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_timesync_schedule_synchronous);
+
+void gb_timesync_schedule_asynchronous(struct gb_interface *interface)
+{
+	struct gb_timesync_svc *timesync_svc;
+
+	if (!(interface->features & GREYBUS_INTERFACE_FEATURE_TIMESYNC))
+		return;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
+	if (!timesync_svc)
+		goto done;
+
+	gb_timesync_schedule(timesync_svc, GB_TIMESYNC_STATE_INIT);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return;
+}
+EXPORT_SYMBOL_GPL(gb_timesync_schedule_asynchronous);
+
+static ssize_t gb_timesync_ping_read(struct file *file, char __user *ubuf,
+				     size_t len, loff_t *offset, bool ktime)
+{
+	struct gb_timesync_svc *timesync_svc = file->f_inode->i_private;
+	char *buf;
+	ssize_t ret = 0;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	mutex_lock(&timesync_svc->mutex);
+	if (list_empty(&timesync_svc->interface_list))
+		ret = -ENODEV;
+	timesync_svc->print_ping = false;
+	mutex_unlock(&timesync_svc->mutex);
+	if (ret)
+		goto done;
+
+	ret = __gb_timesync_schedule_synchronous(timesync_svc,
+						 GB_TIMESYNC_STATE_PING);
+	if (ret)
+		goto done;
+
+	buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	if (ktime)
+		ret = gb_timesync_log_frame_ktime(timesync_svc, buf, PAGE_SIZE);
+	else
+		ret = gb_timesync_log_frame_time(timesync_svc, buf, PAGE_SIZE);
+	if (ret > 0)
+		ret = simple_read_from_buffer(ubuf, len, offset, buf, ret);
+	kfree(buf);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return ret;
+}
+
+static ssize_t gb_timesync_ping_read_frame_time(struct file *file,
+						char __user *buf,
+						size_t len, loff_t *offset)
+{
+	return gb_timesync_ping_read(file, buf, len, offset, false);
+}
+
+static ssize_t gb_timesync_ping_read_frame_ktime(struct file *file,
+						 char __user *buf,
+						 size_t len, loff_t *offset)
+{
+	return gb_timesync_ping_read(file, buf, len, offset, true);
+}
+
+static const struct file_operations gb_timesync_debugfs_frame_time_ops = {
+	.read		= gb_timesync_ping_read_frame_time,
+};
+
+static const struct file_operations gb_timesync_debugfs_frame_ktime_ops = {
+	.read		= gb_timesync_ping_read_frame_ktime,
+};
+
+static int gb_timesync_hd_add(struct gb_timesync_svc *timesync_svc,
+			      struct gb_host_device *hd)
+{
+	struct gb_timesync_host_device *timesync_hd;
+
+	timesync_hd = kzalloc(sizeof(*timesync_hd), GFP_KERNEL);
+	if (!timesync_hd)
+		return -ENOMEM;
+
+	WARN_ON(timesync_svc->timesync_hd);
+	timesync_hd->hd = hd;
+	timesync_svc->timesync_hd = timesync_hd;
+
+	return 0;
+}
+
+static void gb_timesync_hd_remove(struct gb_timesync_svc *timesync_svc,
+				  struct gb_host_device *hd)
+{
+	if (timesync_svc->timesync_hd->hd == hd) {
+		kfree(timesync_svc->timesync_hd);
+		timesync_svc->timesync_hd = NULL;
+		return;
+	}
+	WARN_ON(1);
+}
+
+int gb_timesync_svc_add(struct gb_svc *svc)
+{
+	struct gb_timesync_svc *timesync_svc;
+	int ret;
+
+	timesync_svc = kzalloc(sizeof(*timesync_svc), GFP_KERNEL);
+	if (!timesync_svc)
+		return -ENOMEM;
+
+	timesync_svc->work_queue =
+		create_singlethread_workqueue("gb-timesync-work_queue");
+
+	if (!timesync_svc->work_queue) {
+		kfree(timesync_svc);
+		return -ENOMEM;
+	}
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	INIT_LIST_HEAD(&timesync_svc->interface_list);
+	INIT_DELAYED_WORK(&timesync_svc->delayed_work, gb_timesync_worker);
+	mutex_init(&timesync_svc->mutex);
+	spin_lock_init(&timesync_svc->spinlock);
+	init_waitqueue_head(&timesync_svc->wait_queue);
+
+	timesync_svc->svc = svc;
+	timesync_svc->frame_time_offset = 0;
+	timesync_svc->capture_ping = false;
+	gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_INACTIVE);
+
+	timesync_svc->frame_time_dentry =
+		debugfs_create_file("frame-time", S_IRUGO, svc->debugfs_dentry,
+				    timesync_svc,
+				    &gb_timesync_debugfs_frame_time_ops);
+	timesync_svc->frame_ktime_dentry =
+		debugfs_create_file("frame-ktime", S_IRUGO, svc->debugfs_dentry,
+				    timesync_svc,
+				    &gb_timesync_debugfs_frame_ktime_ops);
+
+	list_add(&timesync_svc->list, &gb_timesync_svc_list);
+	ret = gb_timesync_hd_add(timesync_svc, svc->hd);
+	if (ret) {
+		list_del(&timesync_svc->list);
+		debugfs_remove(timesync_svc->frame_ktime_dentry);
+		debugfs_remove(timesync_svc->frame_time_dentry);
+		destroy_workqueue(timesync_svc->work_queue);
+		kfree(timesync_svc);
+		goto done;
+	}
+
+	init_timer(&timesync_svc->ktime_timer);
+	timesync_svc->ktime_timer.function = gb_timesync_ktime_timer_fn;
+	timesync_svc->ktime_timer.expires = jiffies + GB_TIMESYNC_KTIME_UPDATE;
+	timesync_svc->ktime_timer.data = (unsigned long)timesync_svc;
+	add_timer(&timesync_svc->ktime_timer);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_timesync_svc_add);
+
+void gb_timesync_svc_remove(struct gb_svc *svc)
+{
+	struct gb_timesync_svc *timesync_svc;
+	struct gb_timesync_interface *timesync_interface;
+	struct gb_timesync_interface *next;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	timesync_svc = gb_timesync_find_timesync_svc(svc->hd);
+	if (!timesync_svc)
+		goto done;
+
+	cancel_delayed_work_sync(&timesync_svc->delayed_work);
+
+	mutex_lock(&timesync_svc->mutex);
+
+	gb_timesync_set_state_atomic(timesync_svc, GB_TIMESYNC_STATE_INVALID);
+	del_timer_sync(&timesync_svc->ktime_timer);
+	gb_timesync_teardown(timesync_svc);
+
+	gb_timesync_hd_remove(timesync_svc, svc->hd);
+	list_for_each_entry_safe(timesync_interface, next,
+				 &timesync_svc->interface_list, list) {
+		list_del(&timesync_interface->list);
+		kfree(timesync_interface);
+	}
+	debugfs_remove(timesync_svc->frame_ktime_dentry);
+	debugfs_remove(timesync_svc->frame_time_dentry);
+	destroy_workqueue(timesync_svc->work_queue);
+	list_del(&timesync_svc->list);
+
+	mutex_unlock(&timesync_svc->mutex);
+
+	kfree(timesync_svc);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+}
+EXPORT_SYMBOL_GPL(gb_timesync_svc_remove);
+
+/*
+ * Add a Greybus Interface to the set of TimeSync Interfaces.
+ */
+int gb_timesync_interface_add(struct gb_interface *interface)
+{
+	struct gb_timesync_svc *timesync_svc;
+	struct gb_timesync_interface *timesync_interface;
+	int ret = 0;
+
+	if (!(interface->features & GREYBUS_INTERFACE_FEATURE_TIMESYNC))
+		return 0;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
+	if (!timesync_svc) {
+		ret = -ENODEV;
+		goto done;
+	}
+
+	timesync_interface = kzalloc(sizeof(*timesync_interface), GFP_KERNEL);
+	if (!timesync_interface) {
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	mutex_lock(&timesync_svc->mutex);
+	timesync_interface->interface = interface;
+	list_add(&timesync_interface->list, &timesync_svc->interface_list);
+	timesync_svc->strobe_mask |= 1 << interface->interface_id;
+	mutex_unlock(&timesync_svc->mutex);
+
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_timesync_interface_add);
+
+/*
+ * Remove a Greybus Interface from the set of TimeSync Interfaces.
+ */
+void gb_timesync_interface_remove(struct gb_interface *interface)
+{
+	struct gb_timesync_svc *timesync_svc;
+	struct gb_timesync_interface *timesync_interface;
+
+	if (!(interface->features & GREYBUS_INTERFACE_FEATURE_TIMESYNC))
+		return;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
+	if (!timesync_svc)
+		goto done;
+
+	timesync_interface = gb_timesync_find_timesync_interface(timesync_svc,
+								 interface);
+	if (!timesync_interface)
+		goto done;
+
+	mutex_lock(&timesync_svc->mutex);
+	timesync_svc->strobe_mask &= ~(1 << interface->interface_id);
+	list_del(&timesync_interface->list);
+	kfree(timesync_interface);
+	mutex_unlock(&timesync_svc->mutex);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+}
+EXPORT_SYMBOL_GPL(gb_timesync_interface_remove);
+
+/*
+ * Give the authoritative FrameTime to the calling function. Returns zero if we
+ * are not in GB_TIMESYNC_STATE_ACTIVE.
+ */
+static u64 gb_timesync_get_frame_time(struct gb_timesync_svc *timesync_svc)
+{
+	unsigned long flags;
+	u64 ret;
+
+	spin_lock_irqsave(&timesync_svc->spinlock, flags);
+	if (timesync_svc->state == GB_TIMESYNC_STATE_ACTIVE)
+		ret = __gb_timesync_get_frame_time(timesync_svc);
+	else
+		ret = 0;
+	spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
+	return ret;
+}
+
+u64 gb_timesync_get_frame_time_by_interface(struct gb_interface *interface)
+{
+	struct gb_timesync_svc *timesync_svc;
+	u64 ret = 0;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
+	if (!timesync_svc)
+		goto done;
+
+	ret = gb_timesync_get_frame_time(timesync_svc);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_timesync_get_frame_time_by_interface);
+
+u64 gb_timesync_get_frame_time_by_svc(struct gb_svc *svc)
+{
+	struct gb_timesync_svc *timesync_svc;
+	u64 ret = 0;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	timesync_svc = gb_timesync_find_timesync_svc(svc->hd);
+	if (!timesync_svc)
+		goto done;
+
+	ret = gb_timesync_get_frame_time(timesync_svc);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_timesync_get_frame_time_by_svc);
+
+/* Incrementally updates the conversion base from FrameTime to ktime */
+static void gb_timesync_ktime_timer_fn(unsigned long data)
+{
+	struct gb_timesync_svc *timesync_svc =
+		(struct gb_timesync_svc *)data;
+	unsigned long flags;
+	u64 frame_time;
+	struct timespec ts;
+
+	spin_lock_irqsave(&timesync_svc->spinlock, flags);
+
+	if (timesync_svc->state != GB_TIMESYNC_STATE_ACTIVE)
+		goto done;
+
+	ktime_get_ts(&ts);
+	frame_time = __gb_timesync_get_frame_time(timesync_svc);
+	gb_timesync_store_ktime(timesync_svc, ts, frame_time);
+
+done:
+	spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
+	mod_timer(&timesync_svc->ktime_timer,
+		  jiffies + GB_TIMESYNC_KTIME_UPDATE);
+}
+
+int gb_timesync_to_timespec_by_svc(struct gb_svc *svc, u64 frame_time,
+				   struct timespec *ts)
+{
+	struct gb_timesync_svc *timesync_svc;
+	int ret = 0;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	timesync_svc = gb_timesync_find_timesync_svc(svc->hd);
+	if (!timesync_svc) {
+		ret = -ENODEV;
+		goto done;
+	}
+	ret = gb_timesync_to_timespec(timesync_svc, frame_time, ts);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_timesync_to_timespec_by_svc);
+
+int gb_timesync_to_timespec_by_interface(struct gb_interface *interface,
+					 u64 frame_time, struct timespec *ts)
+{
+	struct gb_timesync_svc *timesync_svc;
+	int ret = 0;
+
+	mutex_lock(&gb_timesync_svc_list_mutex);
+	timesync_svc = gb_timesync_find_timesync_svc(interface->hd);
+	if (!timesync_svc) {
+		ret = -ENODEV;
+		goto done;
+	}
+
+	ret = gb_timesync_to_timespec(timesync_svc, frame_time, ts);
+done:
+	mutex_unlock(&gb_timesync_svc_list_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_timesync_to_timespec_by_interface);
+
+void gb_timesync_irq(struct gb_timesync_svc *timesync_svc)
+{
+	unsigned long flags;
+	u64 strobe_time;
+	bool strobe_is_ping = true;
+	struct timespec ts;
+
+	ktime_get_ts(&ts);
+	strobe_time = __gb_timesync_get_frame_time(timesync_svc);
+
+	spin_lock_irqsave(&timesync_svc->spinlock, flags);
+
+	if (timesync_svc->state == GB_TIMESYNC_STATE_PING) {
+		if (!timesync_svc->capture_ping)
+			goto done_nolog;
+		timesync_svc->ap_ping_frame_time = strobe_time;
+		goto done_log;
+	} else if (timesync_svc->state != GB_TIMESYNC_STATE_WAIT_SVC) {
+		goto done_nolog;
+	}
+
+	timesync_svc->strobe_data[timesync_svc->strobe].frame_time = strobe_time;
+	timesync_svc->strobe_data[timesync_svc->strobe].ts = ts;
+
+	if (++timesync_svc->strobe == GB_TIMESYNC_MAX_STROBES) {
+		gb_timesync_set_state(timesync_svc,
+				      GB_TIMESYNC_STATE_AUTHORITATIVE);
+	}
+	strobe_is_ping = false;
+done_log:
+	trace_gb_timesync_irq(strobe_is_ping, timesync_svc->strobe,
+			      GB_TIMESYNC_MAX_STROBES, strobe_time);
+done_nolog:
+	spin_unlock_irqrestore(&timesync_svc->spinlock, flags);
+}
+EXPORT_SYMBOL(gb_timesync_irq);
+
+int __init gb_timesync_init(void)
+{
+	int ret = 0;
+
+	ret = gb_timesync_platform_init();
+	if (ret) {
+		pr_err("timesync platform init fail!\n");
+		return ret;
+	}
+
+	gb_timesync_clock_rate = gb_timesync_platform_get_clock_rate();
+
+	/* Calculate nanoseconds and femtoseconds per clock */
+	gb_timesync_fs_per_clock = FSEC_PER_SEC;
+	do_div(gb_timesync_fs_per_clock, gb_timesync_clock_rate);
+	gb_timesync_ns_per_clock = NSEC_PER_SEC;
+	do_div(gb_timesync_ns_per_clock, gb_timesync_clock_rate);
+
+	/* Calculate the maximum number of clocks we will convert to ktime */
+	gb_timesync_max_ktime_diff =
+		GB_TIMESYNC_MAX_KTIME_CONVERSION * gb_timesync_clock_rate;
+
+	pr_info("Time-Sync @ %lu Hz max ktime conversion +/- %d seconds\n",
+		gb_timesync_clock_rate, GB_TIMESYNC_MAX_KTIME_CONVERSION);
+	return 0;
+}
+
+void gb_timesync_exit(void)
+{
+	gb_timesync_platform_exit();
+}
drivers/greybus/timesync.h new file +45 -0

45 lines not on this page · raw message

drivers/greybus/timesync_platform.c new file +77 -0

77 lines not on this page · raw message

#4 lore raw

On Wed, 2016-09-14 at 20:29 +0200, Greg KH wrote:

trivial note:

Quoted 29 lines
drivers/greybus/timesync.c new file +29 -0
+static size_t gb_timesync_log_frame_time(struct gb_timesync_svc *timesync_svc,
+					 char *buf, size_t buflen)
+{
+	struct gb_svc *svc = timesync_svc->svc;
+	struct gb_host_device *hd;
+	struct gb_timesync_interface *timesync_interface;
+	struct gb_interface *interface;
+	unsigned int len;
+	size_t off;
+
+	/* AP/SVC */
+	off = snprintf(buf, buflen, "%s frametime: ap=%llu %s=%llu ",
+		       greybus_bus_type.name,
+		       timesync_svc->ap_ping_frame_time, dev_name(&svc->dev),
+		       timesync_svc->svc_ping_frame_time);
+	len = buflen - off;
+
+	/* APB/GPB */
+	if (len < buflen) {
+		hd = timesync_svc->timesync_hd->hd;
+		off += snprintf(&buf[off], len, "%s=%llu ", dev_name(&hd->dev),
+				timesync_svc->timesync_hd->ping_frame_time);
+		len = buflen - off;
+	}
+
+	list_for_each_entry(timesync_interface,
+			    &timesync_svc->interface_list, list) {
+		if (len < buflen) {
+			interface = timesync_interface->interface;

drivers/greybus/timesync.c line 614

Quoted 10 lines
drivers/greybus/timesync.c new file +10 -0
+			off += snprintf(&buf[off], len, "%s=%llu ",
+					dev_name(&interface->dev),
+					timesync_interface->ping_frame_time);
+			len = buflen - off;
+		}
+	}
+	if (len < buflen)
+		off += snprintf(&buf[off], len, "\n");
+	return off;
+}

The unnecessary trailing blank can be avoided by converting

	snprintf(, "format ', ...);
	for (...)
		snprintf(, "format ", ...);
	snprintf(, "\n");

to

	snprintf(, "format', ...);
	for (...)
		snprintf(, " format", ...);
	snprintf(, "\n");
Quoted 1 line
#5 lore raw
In reply to Greg KH
Quoted 39 lines · Greg KH
Quoted 33 lines · Greg KH
Quoted 24 lines · Mark Rutland

Hi Greg,

Quoted 17 lines · Greg KH

Given that it's never a good idea to keep subsystems out of the mainline
kernel, I've put together this pull request that adds the greybus driver
layer to drivers/greybus/.  Because this was 2 1/2 years of work, with
many many developers contributing, I didn't want to flatten all of their
effort into a few small patches, as that wouldn't be very fair.  So I've
built a git tree with all of the changes going back to the first commit,
and merged it into the kernel tree, just like btrfs was merged into the
kernel.

Quoted 3 lines

Unless people point out some major problems with this, I'd like to get
it merged into 4.9-rc1.

Quoted 7 lines

I'm extremely concerned that these patches have *never* seen upstream
review, and this pull request gives no real opportunity for people to
make a judgement regarding the code, as many relevant parties have not
been Cc'd.

Quoted 5 lines

As I said, I will send a set of simple patches, I wanted to get this out
as soon as possible and other things came up today.  Will do it in the
morning, sorry.

Quoted 6 lines

Here's the timesync code pulled out into a simple patch if you want to
see it.

Bryan, any explanations you want to provide that would help in clarifying Mark's issues?

As Douglas Adams would say - "don't panic".

If you look at the final state the code ends up in - we're doing get_cycles(); as opposed to reading an architectural timer directly.

u64 gb_timesync_platform_get_counter(void)
{
        return (u64)get_cycles();
}

You have the entire git history - from the early days where we were reading one of the unused ARMv8 timers the MSM8994 has to the later days where we just do get_cycles()...

At the time when we first started writing the code it wasn't 100% clear if get_cycles() would do, so it was safer to allocate an unused architectural timer and read it directly. Later on and with some experimentation it was possible to switch to get_cycles().

---
bod

#6 lore raw
Quoted 6 lines · Bryan O'Donoghue
Quoted 2 lines · Greg KH

Bryan, any explanations you want to provide that would help in clarifying Mark's issues?

As Douglas Adams would say - "don't panic".

Quoted 7 lines

If you look at the final state the code ends up in - we're doing get_cycles(); as opposed to reading an architectural timer directly.

u64 gb_timesync_platform_get_counter(void)
{
????????return (u64)get_cycles();
}

I did in fact notice this, though my wording was somewhat unclear on that part.

Quoted 8 lines

You have the entire git history - from the early days where we were reading one of the unused ARMv8 timers the MSM8994 has to the later days where we just do get_cycles()...

At the time when we first started writing the code it wasn't 100% clear if get_cycles() would do, so it was safer to allocate an unused architectural timer and read it directly. Later on and with some experimentation it was possible to switch to get_cycles().

I don't think the history matters, and I don't think that one can rely on get_cycles() in this manner outside of arch code. Looking at the state of the tree [1] as of the final commit [2] in the greybus branch, my points still stand:

* The "google,greybus-frame-time-counter" node is superfluous. It does not describe a particular device, and duplicates information we have elsewhere. It does not explicitly define the relationship with the underlying clocksource.

* The clock-frequency property isn't necessary. The architected timer drivers know the frequency of the architected timers (MMIO or sysreg), and they should be queried as to the frequency.

* In general, get_cycles() isn't guaranteed to be any clocksource or cycle counter in particular, so even if the clock-frequency property matches the architected timer, that doesn't help.

Beyond that, the fallback code using cpufreq and presumably an actual cycle counter will be broken in a number of cases -- cycle counts will not match across CPUs, and on CPUs whic gate clocks in WFI/WFE, they'll drift randomly.

Per the comment at the top of the file, it looks like you want a system-wide stable clocksource. If you want that, you need to use a generic API that allows drivers and arch code to actually provide that, rather than building one yourself that doesn't work.

If you're trying to synchronise with other agents in the system that are reading from the MMIO arch timers, then that relationship should be described explicitly in the DT.

Thanks,
Mark.

[1] https://git.kernel.org/cgit/linux/kernel/git/gregkh/char-misc.git/tree/drivers/greybus/timesync_platform.c?h=greybus&id=f86bfc90a401681838866b5f6826d86cc4c7010c
[2] https://git.kernel.org/cgit/linux/kernel/git/gregkh/char-misc.git/commit/?h=greybus&id=f86bfc90a401681838866b5f6826d86cc4c7010c

#7 lore raw
Quoted 3 lines · Mark Rutland
Quoted 1 line · Bryan O'Donoghue

I don't think the history matters,

Your comment seemed to indicate you thought we were reading a architectural timer directly - which we aren't.

Quoted 2 lines

and I don't think that one can rely
on get_cycles() in this manner outside of arch code.

I don't follow your meaning. What's wrong with get_cycles() ? You've already said you don't think reading an architectural timer directly is correct.

The objective is to read one of the free-running counters in MSM8994, clocked by the PMIC. The refclk provided by PMIC is distributed to each processor in the system.

Quoted 8 lines

Looking at the
state of the tree [1] as of the final commit [2] in the greybus branch,
my points still stand:

* The "google,greybus-frame-time-counter" node is superfluous. It does
  not describe a particular device,

It describes a timer running @ 19.2MHz, clocked by PMIC refclk.

Quoted 2 lines

and duplicates information we have
  elsewhere.

Can you give an example ?

Quoted 8 lines

It does not explicitly define the relationship with the
  underlying clocksource.

* The clock-frequency property isn't necessary. The architected timer drivers know the frequency of the architected timers (MMIO or sysreg),
  and they should be queried as to the frequency.

OK so if I'm understanding you. You think get_cycles() is fine but that instead of encoding a "greybus-frame-time-counter" the platform code should interrogate the frequency provided - is that correct ?

Quoted 2 lines

Beyond that, the fallback code using cpufreq and presumably an actual cycle counter will be broken in a number of cases

Of course the fallback will be broken... it's not supposed to work if you don't have a timer that can be used - just compile, run and print a complaint - i.e., this won't really do FrameTime on an x86... then again since so much of the underlying greybus/unipro hardware - requires a 19.2MHz refclk - if you were to try to do greybus on x86 you'd need to solve that problem.

Quoted 6 lines

Per the comment at the top of the file, it looks like you want a system-wide stable clocksource. If you want that, you need to use a generic API that allows drivers and arch code to actually provide that,
rather than building one yourself that doesn't work.

Hmm. The objective is to read one of the timers clocked by the PMIC refclk input. refclk is provided to each processor in the system - and on MSM8994 clocks the MMIO timers. It's used to drive the PLL on the other processors - which in turn drive the timers that the Modules use to read their own local counters. We want to read that counter on MSM directly - get_cycles() has worked nicely so far.

Quoted 4 lines

If you're trying to synchronise with other agents in the system that are
reading from the MMIO arch timers,

No. The MMIO timers are useful only to the MSM. We don't have any type of parallel (or serial) bus that can access that on-chip resource.

MSM8994 -- > USB
             APBridge (timer) -> UniPro bus
                                            -> Module with a UART -> Module with a GPIO -> Module with an etc, etc -> SPI bus
                                            -> SVC
                                               Owns FrameTime

So the SVC owns FrameTime and diseminates that to other entities in the system by way of a GPIO and greybus. It's up to the MSM8994 to select a timer that works for it - the other processors in the system are responsible for their own timers.

---
bod

#8 lore raw
Quoted 1 line · Bryan O'Donoghue

Here's a slightly better diagram.

PMIC -> refclk provided to each (timer) element below.

MSM8994(timer) -- > USB
WD8a
               APBridgeA (timer) -> UniPro bus
               WD8a
                                            -> Module(timer) with UART WD1
                                            -> Module(timer) with GPIO WD2
                                            -> Module(timer) with blah WD3
                              -> SPI bus
                                            -> SVC(timer) Owns FrameTime GPIO {WD0...WDn}

So yes, each processor has it's own timer. We aren't trying to read the MSM's FrameTime.

---
bod

#9 lore raw
In reply to Bryan O'Donoghue

Hi,

More questions below. Perhaps some of these will be implicitly answered when the linearised patches appear, and I'm happy to wait until then to continue the discussion, as I suspect otherwise we're all likely to end up exasperated.

Please do Cc me on those.

Regardless, until those appear and a reasonable time has been given for replies, my comments regarding the lack of review stand, as does my NAK for the series.

Quoted 7 lines · Bryan O'Donoghue
Quoted 3 lines · Mark Rutland
Quoted 1 line · Bryan O'Donoghue

?

I don't think the history matters,

Your comment seemed to indicate you thought we were reading a architectural timer directly - which we aren't.

Sure, and as I pointed out, the comment in the HEAD commit still claims it does, even if the code doesn't. This is at best, confusing, and the history of how it came to be there doesn't really matter...

Quoted 6 lines
Quoted 2 lines

and I don't think that one can rely
on get_cycles() in this manner outside of arch code.

I don't follow your meaning. What's wrong with get_cycles() ? You've already said you don't think reading an architectural timer directly is correct.

I pointed out a number of issues in my previous reply.

For example, you have absolutely no guarantee as to what backs get_cycles(). Despite this, the code assumes that get_cycles() is backed by something running at the frequency described in a "google,greybus-frame-time-counter" node.

Even if this *happens* to match what some piece of arch code provides today on some platform, it is in no way *guaranteed*.

Quoted 9 lines

The objective is to read one of the free-running counters in MSM8994, clocked by the PMIC. The refclk provided by PMIC is distributed to each processor in the system.

Quoted 5 lines

Looking at the
state of the tree [1] as of the final commit [2] in the greybus branch,
my points still stand:

Quoted 5 lines
Quoted 3 lines

* The "google,greybus-frame-time-counter" node is superfluous. It does
? not describe a particular device,

It describes a timer running @ 19.2MHz, clocked by PMIC refclk.

... which you assume is whatever backs get_cycles(), which you in practice assume is the architected timer. For which we *already* have a binding and driver.

Given that, as far as I can tell, "google,greybus-frame-time-counter" describes a software construct that uses this, not an actual piece of hardware.

Quoted 3 lines
Quoted 1 line

and duplicates information we have ? elsewhere.

Can you give an example ?

Trivially, the CNTFRQ register in the architected timer (which is common across MMIO/sysreg), which you can query with arch_timer_get_rate().

Note that isn't guaranteed to match get_cycles() either. You need a better API to call.

Quoted 8 lines
Quoted 4 lines

* The clock-frequency property isn't necessary. The architected timer ? drivers know the frequency of the architected timers (MMIO or sysreg),
? and they should be queried as to the frequency.

OK so if I'm understanding you. You think get_cycles() is fine but that instead of encoding a "greybus-frame-time-counter" the platform code should interrogate the frequency provided - is that correct ?

You should definitely interrogate the relevant driver, somehow.

Without a higher-level view of what you're trying to achieve, it's not clear to me whether get_cycles() is the right interface.

Quoted 9 lines
Quoted 2 lines

Beyond that, the fallback code using cpufreq and presumably an actual cycle counter will be broken in a number of cases

Of course the fallback will be broken... it's not supposed to work if you don't have a timer that can be used - just compile, run and print a complaint - i.e., this won't really do FrameTime on an x86... then again since so much of the underlying greybus/unipro hardware - requires a 19.2MHz refclk - if you were to try to do greybus on x86 you'd need to solve that problem.

If it's never going to work, why give the illusion that it might?

If you don't have the necessary prerequisites, fail to probe entirely. Prevent drivers that depend on the non-existent functionality from probing, or have them avoid the facility which is not available.

Don't provide them with something that can appear to work for a while, yet will fall over in a breeze.

Quoted 11 lines
Quoted 4 lines

Per the comment at the top of the file, it looks like you want a system-wide stable clocksource. If you want that, you need to use a generic API that allows drivers and arch code to actually provide that, rather than building one yourself that doesn't work.

Hmm. The objective is to read one of the timers clocked by the PMIC refclk input. refclk is provided to each processor in the system - and on MSM8994 clocks the MMIO timers. It's used to drive the PLL on the other processors - which in turn drive the timers that the Modules use to read their own local counters. We want to read that counter on MSM directly - get_cycles() has worked nicely so far.

This is too low-level for me to see what you're actually trying to achieve. The fact that you want to read a specific timer is an implementation detail.

Why can't you use any other timer? Correctness? Performance? (Why) is the fact that the PLL drives module timers important?

Quoted 5 lines
Quoted 2 lines

If you're trying to synchronise with other agents in the system that are reading from the MMIO arch timers,

No. The MMIO timers are useful only to the MSM. We don't have any type of parallel (or serial) bus that can access that on-chip resource.

To be clear, by "other agents in the system", I'm also asking about other devices within the SoC (e.g. anything other than the CPUs running this instance of Linux).

Quoted 9 lines

MSM8994 -- > USB
? ? ? ? ? ? ?APBridge (timer) -> UniPro bus
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -> Module with a UART ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -> Module with a GPIO ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -> Module with an etc, etc ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -> SPI bus
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -> SVC
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Owns FrameTime

Quoted 4 lines

So the SVC owns FrameTime and diseminates that to other entities in the system by way of a GPIO and greybus. It's up to the MSM8994 to select a timer that works for it - the other processors in the system are responsible for their own timers.

Sorry, but this doesn't clarify much from my PoV.

* What are the requirements for that timer?

* Is there_any_ implicit relationship with the module timers derived from the fact they share a parent PLL? Is that a requirement somehow?

Thanks,
Mark.

#10 lore raw
Quoted 25 lines · Mark Rutland

Hi,

More questions below. Perhaps some of these will be implicitly answered
when the linearised patches appear, and I'm happy to wait until then to
continue the discussion, as I suspect otherwise we're all likely to end
up exasperated.

Please do Cc me on those.

Regardless, until those appear and a reasonable time has been given for
replies, my comments regarding the lack of review stand, as does my NAK
for the series.

Quoted 6 lines · Bryan O'Donoghue
Quoted 4 lines · Mark Rutland
Quoted 2 lines · Bryan O'Donoghue

Quoted 8 lines
Quoted 3 lines
Quoted 1 line

I don't think the history matters,

Your comment seemed to indicate you thought we were reading a architectural timer directly - which we aren't.

Sure, and as I pointed out, the comment in the HEAD commit still claims
it does, even if the code doesn't. This is at best, confusing, and the
history of how it came to be there doesn't really matter...

TBH a whole git history will invariably contain things developers did, thought better of and then backed out as is the case here.

Quoted 11 lines
Quoted 10 lines
Quoted 5 lines

and I don't think that one can rely
on get_cycles() in this manner outside of arch code.

I don't follow your meaning. What's wrong with get_cycles() ? You've
already said you don't think reading an architectural timer directly is
correct.

I pointed out a number of issues in my previous reply.

On MSM8994 the timer backing get_cycles() is one of the MMIO architectural timers (which is why I switched over in the end). There's not much else that can be done bar custom silicon - this particular timer is as good as it gets, more of a "how do we synchronise time with the hardware we have" than a "lets design in a feature to synchronise time" - which was something we were focusing in on for later silicon...

Quoted 9 lines

For example, you have absolutely no guarantee as to what backs get_cycles(). Despite this, the code assumes that get_cycles() is backed
by something running at the frequency described in a "google,greybus-frame-time-counter" node.

Even if this *happens* to match what some piece of arch code provides today on some platform, it is in no way *guaranteed*.

That's the point though, if you declare "google,greybus-frame-time- counter" in your platform code - then you can use 'get_cycles()' in this manner - if not - then you need to take steps in your own new platform to provide that same level of functionality. You could switch to an MSM8996 or an MSM8998 declare this node and bob's your uncle.

OTOH declaring this node on x86 would be a bit pointless. You'd be better off providing a timer on a PCI bar, and binding that into greybus with some x86/x86-platform code...

Quoted 14 lines
Quoted 14 lines

The objective is to read one of the free-running counters in MSM8994,
clocked by the PMIC. The refclk provided by PMIC is distributed to each
processor in the system.

Quoted 6 lines

 Looking at the
state of the tree [1] as of the final commit [2] in the greybus branch,
my points still stand:

Quoted 8 lines
Quoted 4 lines
Quoted 3 lines

* The "google,greybus-frame-time-counter" node is superfluous. It does
  not describe a particular device,

It describes a timer running @ 19.2MHz, clocked by PMIC refclk.

... which you assume is whatever backs get_cycles(), which you in practice assume is the architected timer. For which we *already* have a
binding and driver.

It's a requirement rather than assumption. If you declare that node, it's assumed the timer driving get_cycles() does what it says on the greybus-frame-time-counter tin.

Quoted 8 lines
Quoted 2 lines
Quoted 1 line

 and duplicates information we have   elsewhere.

Can you give an example ?

Trivially, the CNTFRQ register in the architected timer (which is common
across MMIO/sysreg), which you can query with arch_timer_get_rate().

Note that isn't guaranteed to match get_cycles() either. You need a better API to call.

In that case a DT entry makes sense I'd say.

Quoted 1 line

You should definitely interrogate the relevant driver, somehow.

Hrmm. TBH if we are ruling out arch_timer_get_rate() then I think a DT entry (which BTW is greybus specific) is the more intelligent way forward. The greybus platform implementer needs to understand the dependencies and take action to meet those dependencies should he or she wish to support this feature.

I'm not opposed necessarily to calling arch_timer_get_rate() instead of a DT binding, assuming it works, and drawing a line under it for MSM8994. As I've said it's up to a system architect for other platforms to go and do the necessary design to support this feature and this will almost certainly require new platform code both here and in other places anyway.

Quoted 4 lines

Without a higher-level view of what you're trying to achieve, it's not
clear to me whether get_cycles() is the right interface.

I appreciate that.

---
bod

#11 lore raw
Quoted 6 lines · Bryan O'Donoghue
Quoted 5 lines · Mark Rutland

For example, you have absolutely no guarantee as to what backs get_cycles(). Despite this, the code assumes that get_cycles() is backed by something running at the frequency described in a "google,greybus-frame-time-counter" node.

Quoted 6 lines
Quoted 2 lines

Even if this *happens* to match what some piece of arch code provides today on some platform, it is in no way *guaranteed*.

That's the point though, if you declare "google,greybus-frame-time- counter" in your platform code - then you can use 'get_cycles()' in this manner

To be clear, *some* properties (and perhaps additional nodes) may need to be in the DT, in order to capture the hardware property or relationship that you are reliant upon.

However, the DT cannot possibly know anything about get_cycles(), as get_cycles() is a kernel implementation details that's subject to arbitrary change at any point in time, independent of the DT.

The "google,greybus-frame-time-counter" node, as it stands, does not capture any relevant hardware detail, and does not belong in the DT.

Quoted 4 lines
Quoted 2 lines

Without a higher-level view of what you're trying to achieve, it's not clear to me whether get_cycles() is the right interface.

I appreciate that.

Until that's clarified, we won't make any progress here.

Thanks,
Mark.

#12 lore raw
Quoted 12 lines · Mark Rutland
Quoted 11 lines · Bryan O'Donoghue
Quoted 9 lines · Mark Rutland

For example, you have absolutely no guarantee as to what backs get_cycles(). Despite this, the code assumes that get_cycles() is backed by something running at the frequency described in a "google,greybus-frame-time-counter" node.

Even if this *happens* to match what some piece of arch code provides
today on some platform, it is in no way *guaranteed*.

Quoted 7 lines
Quoted 3 lines

That's the point though, if you declare "google,greybus-frame-time- counter" in your platform code - then you can use 'get_cycles()' in this manner

To be clear, *some* properties (and perhaps additional nodes) may need
to be in the DT, in order to capture the hardware property or relationship that you are reliant upon.

Sure but on the relevant platform we know

1. get_cycles() is derived from an architectured MMIO timer
2. What is clocking that timer

So any platform that declares that property must be aware of what its doing.

For clarity this is the alternative to reading another register directly bypassing get_cycles().

Quoted 7 lines
Quoted 6 lines
Quoted 5 lines

Without a higher-level view of what you're trying to achieve, it's
not clear to me whether get_cycles() is the right interface.

I appreciate that.

Until that's clarified, we won't make any progress here.

Let's see. We're synchronizing a set of distributed timers via a GPIO pulse. Each processor on the system has at least one timer block directly driven by the [email protected] or a PLL driven by [email protected]

FrameTime is a 64 bit free-running counter running @19.2MHz that is used as a common source of reference for events. On the MSM side this implies reading one of the timers driven by that refclk directly.

The choices on MSM8994 to access one of those timer blocks are

1. Read the register directly as is done in earlier patches or
2. Read get_cycles() as is done in later patches.

It's no more complex than that.

---
bod

#13 lore raw
Quoted 19 lines · Bryan O'Donoghue
Quoted 10 lines · Mark Rutland
Quoted 9 lines · Bryan O'Donoghue
Quoted 8 lines · Mark Rutland

For example, you have absolutely no guarantee as to what backs get_cycles(). Despite this, the code assumes that get_cycles() is backed by something running at the frequency described in a "google,greybus-frame-time-counter" node.

Even if this *happens* to match what some piece of arch code provides
today on some platform, it is in no way *guaranteed*.

Quoted 7 lines
Quoted 3 lines

That's the point though, if you declare "google,greybus-frame-time- counter" in your platform code - then you can use 'get_cycles()' in this manner

To be clear, *some* properties (and perhaps additional nodes) may need
to be in the DT, in order to capture the hardware property or relationship that you are reliant upon.

Quoted 7 lines

Sure but on the relevant platform we know?

1. get_cycles() is derived from an architectured MMIO timer
2. What is clocking that timer

So any platform that declares that property must be aware of what its doing.

I can't say this any more explicitly:

****************************************************
* The DT *cannot* know anything about get_cycles() *
****************************************************

It's no more complex than that.

Thanks,
Mark.

#14 lore raw
Quoted 8 lines · Mark Rutland

On
I can't say this any more explicitly:

****************************************************
* The DT *cannot* know anything about get_cycles() *
****************************************************

It's no more complex than that.

Sure think Mark I understand your point.

I think to satisfy your objection the most sensible change to make is using arch_timer_get_rate() compared to a desired rate of 19200000.

---
bod

#15 lore raw
In reply to Greg KH
Quoted 35 lines · Greg KH
Quoted 12 lines · Mark Rutland

Hi Greg,

Quoted 8 lines · Greg KH

Given that it's never a good idea to keep subsystems out of the mainline kernel, I've put together this pull request that adds the greybus driver layer to drivers/greybus/. Because this was 2 1/2 years of work, with many many developers contributing, I didn't want to flatten all of their effort into a few small patches, as that wouldn't be very fair. So I've built a git tree with all of the changes going back to the first commit, and merged it into the kernel tree, just like btrfs was merged into the kernel.

Quoted 7 lines
Quoted 2 lines

Unless people point out some major problems with this, I'd like to get it merged into 4.9-rc1.

I'm extremely concerned that these patches have *never* seen upstream review, and this pull request gives no real opportunity for people to make a judgement regarding the code, as many relevant parties have not been Cc'd.

As I said, I will send a set of simple patches, I wanted to get this out as soon as possible and other things came up today. Will do it in the morning, sorry.

Quoted 10 lines

From a quick scan of the git tree, I can see code (that isn't even placed under staging/) for which I have fundamental objections to as a maintainer, and has not been Cc'd to a relevant list.

For example, I see commit 5a450477311fbfe2 ("greybus: timesync: Add timesync core driver"). This states that it directly accesses the ARMv7 architected timer, though it's unclear as to precisely what it's doing since it introduces an (undocumented) compatible string, and what should be an unnecessary devicetree property.

Quoted 7 lines
Quoted 3 lines

That's never gone to the linux-arm-kernel mainline list, myself or Marc (as maintainers of the arch timer driver), nor has the binding seen any review on the devicetree mailing list.

Hm, odd, I thought we had Rob review all of the device tree bindings, but maybe the timesync stuff missed him. And timesync is "odd" to say the least, wait until you see the firmware side of it :)

I have not. There weren't any when I was involved (other than SOC related bindings). Some like USB devices were discussed at least, but at the time there was no common definition of how to deal with soldered, on-board USB devices. And that was also what's good enough to move forward with development, not ready for mainline. There's a binding now for USB, but what's there for arche predates it IIRC. This is the first I've heard of timesync having a binding. I can't imagine why it needs one.

I was going to stay out of this, but now that I'm here...

I'm not all that worried about the quality of the code, but do question whether this really makes sense to merge at this time. While I worked on Ara and would like to see all this code be used, I'm pretty doubtful it will be. Yes, Motorola is using a version of it, but they forked it some time back and changed who knows what. So what is upstream won't likely even work with any publicly available device. And how long that Motorola phone lasts is unknown. Sure it is self contained, but maintenance to keep it in tree is not 0.

There's also things that never got solved. Like how do you describe devices on I2C, SPI, UART, etc. behind a greybus device? The plan was to use DT overlays, but that was never solved and brings a whole set of problems to solve upstream.

Rob

author maintainer
#16 lore raw
Quoted 44 lines · Rob Herring
Quoted 35 lines · Greg KH
Quoted 12 lines · Mark Rutland

Hi Greg,

Quoted 8 lines · Greg KH

Given that it's never a good idea to keep subsystems out of the mainline kernel, I've put together this pull request that adds the greybus driver layer to drivers/greybus/. Because this was 2 1/2 years of work, with many many developers contributing, I didn't want to flatten all of their effort into a few small patches, as that wouldn't be very fair. So I've built a git tree with all of the changes going back to the first commit, and merged it into the kernel tree, just like btrfs was merged into the kernel.

Quoted 7 lines
Quoted 2 lines

Unless people point out some major problems with this, I'd like to get it merged into 4.9-rc1.

I'm extremely concerned that these patches have *never* seen upstream review, and this pull request gives no real opportunity for people to make a judgement regarding the code, as many relevant parties have not been Cc'd.

As I said, I will send a set of simple patches, I wanted to get this out as soon as possible and other things came up today. Will do it in the morning, sorry.

Quoted 10 lines

From a quick scan of the git tree, I can see code (that isn't even placed under staging/) for which I have fundamental objections to as a maintainer, and has not been Cc'd to a relevant list.

For example, I see commit 5a450477311fbfe2 ("greybus: timesync: Add timesync core driver"). This states that it directly accesses the ARMv7 architected timer, though it's unclear as to precisely what it's doing since it introduces an (undocumented) compatible string, and what should be an unnecessary devicetree property.

Quoted 7 lines
Quoted 3 lines

That's never gone to the linux-arm-kernel mainline list, myself or Marc (as maintainers of the arch timer driver), nor has the binding seen any review on the devicetree mailing list.

Hm, odd, I thought we had Rob review all of the device tree bindings, but maybe the timesync stuff missed him. And timesync is "odd" to say the least, wait until you see the firmware side of it :)

Quoted 8 lines

I have not. There weren't any when I was involved (other than SOC related bindings). Some like USB devices were discussed at least, but at the time there was no common definition of how to deal with soldered, on-board USB devices. And that was also what's good enough to move forward with development, not ready for mainline. There's a binding now for USB, but what's there for arche predates it IIRC. This is the first I've heard of timesync having a binding. I can't imagine why it needs one.

Ah, I'll let Bryan answer that one :)

Quoted 10 lines

I was going to stay out of this, but now that I'm here...

I'm not all that worried about the quality of the code, but do question whether this really makes sense to merge at this time. While I worked on Ara and would like to see all this code be used, I'm pretty doubtful it will be. Yes, Motorola is using a version of it, but they forked it some time back and changed who knows what. So what is upstream won't likely even work with any publicly available device. And how long that Motorola phone lasts is unknown. Sure it is self contained, but maintenance to keep it in tree is not 0.

We have people who are willing to maintain it (me and Johan) and others on the cc: have expressed interest in doing things with it (Rui has great ideas about extending gbsim in ways to make it easier to test and use).

And there is never a restriction on accepting kernel code for "publicly availble devices only", remember, we have ripped CPU code out of the kernel for chips that never shipped. It just has to not affect others in ways that would affect them.

I merge new driver subsystems to the kernel every release that are much larger than this, and have almost no users out there, so this shouldn't be a surprise to anyone (who here has a most-bus device? A unisys visorbus device? MCB device? I can go on :)

And with gbsim, you can run this code today on your laptop, or directly on a target system like a beagle bone black or minnowboard. There's a talk about how to use greybus over IP at ELC in a few weeks, and someone is working on porting the firmware side to an arduino to make it simper to control the devices on such a target from a host computer using the greybus protocol.

And getting Motorola to merge back in with this upstream code is a good goal, having it upstream is a neutral place where everyone can work together on it. Keeping it in a random github project makes forks almost inevitable, as we all know.

Quoted 4 lines

There's also things that never got solved. Like how do you describe devices on I2C, SPI, UART, etc. behind a greybus device? The plan was to use DT overlays, but that was never solved and brings a whole set of problems to solve upstream.

That is only an issue if you want to bind a kernel driver for an existing i2c/spi chip to an i2c/spi greybus device. With the code we have today, we do it for a specific SPI chip (for firmware download), but rely on everything to be userspace-only accesses to make it simpler at this point in time.

When DT overlays get more settled down, yes, I want to revisit this idea of how to do it for greybus devices, but that's a long-term goal and is not required at all right now to have a working system and devices.

thanks,

greg k-h

#17 lore raw
Quoted 6 lines · Greg KH

This

Quoted 1 line

is the first I've heard of timesync having a binding. I can't

imagine

Quoted 1 line

why it needs one.

Ah, I'll let Bryan answer that one :)

It's possible we could drop the binding. It was needed to describe the register location of the MMIO architectural register on MSM8994. I have this binding ATM the describe fact that get_cycles() on MSM8994 returns a free-running counter, clocked by refclk and that refclk is provided to each processor that want to do FrameTime - i.e. the clock driving get_cycles() comes from PMIC and drives the relevant PLLs on the downstream processors clocking their respective TMR blocks.

static const struct of_device_id arch_timer_of_match[] = { { .compatible   = "google,greybus-frame-time-counter", }, {},
};

I'm not aware of a corresponding kernel API that describes the frequency get_cycles() operates at but if there is one then there's no need for this binding.

A message that is not in the archive (sent off-list or not yet received): [email protected]

A message that is not in the archive (sent off-list or not yet received): [email protected]

#20 lore raw

Re: [patch 11/32] greybus: camera driver

Hi Greg,

Thank you for the patch.

Quoted 11 lines · On Thursday 15 Sep 2016 14:23:51 Greg Kroah-Hartman wrote:

This driver implements the Greybus camera protocol.

---
 drivers/greybus/camera.c    | 1400 ++++++++++++++++++++++++++++++++++++++++
 drivers/greybus/gb-camera.h |  127 +++
 2 files changed, 1527 insertions(+)
drivers/greybus/camera.c new file +0 -0
@@ -0,0 +1,1400 @@

[snip]

Quoted 32 lines
drivers/greybus/camera.c new file +32 -0
+/* GB format to media code map */
+static const struct gb_camera_fmt_info gb_fmt_info[] = {
+	{
+		.mbus_code = V4L2_MBUS_FMT_UYVY8_1X16,
+		.gb_format = 0x01,
+		.bpp	   = 16,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_NV12_1x8,
+		.gb_format = 0x12,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_NV21_1x8,
+		.gb_format = 0x13,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_YU12_1x8,
+		.gb_format = 0x16,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_YV12_1x8,
+		.gb_format = 0x17,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_JPEG_1X8,
+		.gb_format = 0x40,
+		.bpp	   = 0,
+	},

drivers/greybus/camera.c

Quoted 10 lines
drivers/greybus/camera.c new file +10 -0
+	{
+		.mbus_code = V4L2_MBUS_FMT_GB_CAM_METADATA_1X8,
+		.gb_format = 0x41,
+		.bpp	   = 0,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_GB_CAM_DEBUG_DATA_1X8,
+		.gb_format = 0x42,
+		.bpp	   = 0,
+	},

These two formats will likely not be accepted in their current state in the media subsystem, I would propose just dropping them.

Quoted 41 lines
drivers/greybus/camera.c new file +41 -0
+	{
+		.mbus_code = V4L2_MBUS_FMT_SBGGR10_1X10,
+		.gb_format = 0x80,
+		.bpp	   = 10,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SGBRG10_1X10,
+		.gb_format = 0x81,
+		.bpp	   = 10,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SGRBG10_1X10,
+		.gb_format = 0x82,
+		.bpp	   = 10,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SRGGB10_1X10,
+		.gb_format = 0x83,
+		.bpp	   = 10,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SBGGR12_1X12,
+		.gb_format = 0x84,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SGBRG12_1X12,
+		.gb_format = 0x85,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SGRBG12_1X12,
+		.gb_format = 0x86,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SRGGB12_1X12,
+		.gb_format = 0x87,
+		.bpp	   = 12,
+	},
+};

[snip]

Signature · 3 lines
Regards,

Laurent Pinchart
#21 lore raw
In reply to Greg KH
Quoted 6 lines · Greg KH

I'll send out a follow-up set of "simple" patches that just add the files to the kernel tree, to give people an idea of the code involved. Overall, it's a tiny stand-alone driver subsystem, only 37k lines, that implements a protocol which allows for "generic" cameras, audio devices, and other class type devices, as well as a bridged "physical" layer protocol to talk to serial, spi, uart, pwm, gpio, i2c, and even USB host

Just emphasizing what Mark Rutland said complete NACK, in particular the drivers for functions have not been posted upstream at all (and it's concerning that they're all being added under drivers/greybus rather than within the relevant subsystem like we do normally). I've not looked at the code as it has not been submitted but given that and that the reason I found this pull request was an ASoC contributor who had seen it and looked at the code going "oh dear, greybus..." on IRC I'm very concerned.

Sending a pull request for code that's never been seen upstream seems completely premature.

author maintainer
#22 lore raw
Quoted 18 lines · Mark Brown
Quoted 6 lines · Greg KH

I'll send out a follow-up set of "simple" patches that just add the files to the kernel tree, to give people an idea of the code involved. Overall, it's a tiny stand-alone driver subsystem, only 37k lines, that implements a protocol which allows for "generic" cameras, audio devices, and other class type devices, as well as a bridged "physical" layer protocol to talk to serial, spi, uart, pwm, gpio, i2c, and even USB host

Just emphasizing what Mark Rutland said complete NACK, in particular the drivers for functions have not been posted upstream at all (and it's concerning that they're all being added under drivers/greybus rather than within the relevant subsystem like we do normally). I've not looked at the code as it has not been submitted but given that and that the reason I found this pull request was an ASoC contributor who had seen it and looked at the code going "oh dear, greybus..." on IRC I'm very concerned.

Quoted 2 lines

Sending a pull request for code that's never been seen upstream seems completely premature.

Hey, how does code get upstream then? :)

Anyway, as vger seems to be marking all of the greybus patches I send out as "spam" according to a filter, it's making it a bit hard to send patches out, but I'll try it again "by hand" later today...

As for the drivers all living under drivers/greybus/ I understand, but we need the greybus core present first before we can get the drivers in. How about we do what happened with IIO, we take the greybus core code in drivers/greybus/ and put the drivers in staging, and then move them out of staging into the "real" portion of the kernel as they get reviewed and accepted?

Any objections to that workflow?

thanks,

greg k-h

#23 lore raw
Quoted 6 lines · Greg KH
Quoted 2 lines · Mark Brown

Sending a pull request for code that's never been seen upstream seems completely premature.

Hey, how does code get upstream then? :)

By having the actual code to the mailing list.

Quoted 6 lines

As for the drivers all living under drivers/greybus/ I understand, but we need the greybus core present first before we can get the drivers in. How about we do what happened with IIO, we take the greybus core code in drivers/greybus/ and put the drivers in staging, and then move them out of staging into the "real" portion of the kernel as they get reviewed and accepted?

Yes, staging would be appropriate for unreviewed code.

author maintainer
#24 lore raw
Quoted 9 lines · Mark Brown
Quoted 6 lines · Greg KH
Quoted 2 lines · Mark Brown

Sending a pull request for code that's never been seen upstream seems completely premature.

Hey, how does code get upstream then? :)

By having the actual code to the mailing list.

Ugh, vger keeps blocking the patches, I'm going to try for a third time now...

Quoted 8 lines
Quoted 6 lines

As for the drivers all living under drivers/greybus/ I understand, but we need the greybus core present first before we can get the drivers in. How about we do what happened with IIO, we take the greybus core code in drivers/greybus/ and put the drivers in staging, and then move them out of staging into the "real" portion of the kernel as they get reviewed and accepted?

Yes, staging would be appropriate for unreviewed code.

Sounds good.

thanks,

greg k-h

author maintainer
#25 lore raw
Quoted 7 lines · Greg KH
Quoted 6 lines · Mark Brown
Quoted 4 lines · Greg KH
Quoted 2 lines · Mark Brown

Sending a pull request for code that's never been seen upstream seems completely premature.

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

Hey, how does code get upstream then? :)

By having the actual code to the mailing list.

Ugh, vger keeps blocking the patches, I'm going to try for a third time now...

third time seems to have worked, except for patch 01/32, which was only a single .h file. That file can be seen here if people are curious:
https://git.kernel.org/cgit/linux/kernel/git/gregkh/char-misc.git/tree/drivers/greybus/greybus_protocols.h?h=greybus

I'll now respin the series, moving everything into drivers/staging/greybus/ and leave it there for 4.9. Then we can work on moving the core out, and then the drivers to the needed locations.

thanks,

greg k-h

author maintainer
#26 lore raw
Quoted 15 lines · Greg KH
Quoted 7 lines · Greg KH
Quoted 6 lines · Mark Brown
Quoted 4 lines · Greg KH
Quoted 2 lines · Mark Brown

Sending a pull request for code that's never been seen upstream seems completely premature.

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

Hey, how does code get upstream then? :)

By having the actual code to the mailing list.

Ugh, vger keeps blocking the patches, I'm going to try for a third time now...

Quoted 7 lines

third time seems to have worked, except for patch 01/32, which was only a single .h file. That file can be seen here if people are curious:
https://git.kernel.org/cgit/linux/kernel/git/gregkh/char-misc.git/tree/drivers/greybus/greybus_protocols.h?h=greybus

I'll now respin the series, moving everything into drivers/staging/greybus/ and leave it there for 4.9. Then we can work on moving the core out, and then the drivers to the needed locations.

This is now all in drivers/staging/greybus/ and will show up in the next linux-next.

And because of this, we already have a checkpatch.pl cleanup patch sent to us for the code for things we missed :)

thanks,

greg k-h

#27 lore raw

+new email ids for myself & Vaibhav H

--
thanks,
./va

Quoted 33 lines · Greg KH
Quoted 15 lines · Greg KH
Quoted 7 lines · Greg KH
Quoted 6 lines · Mark Brown
Quoted 4 lines · Greg KH
Quoted 2 lines · Mark Brown

Sending a pull request for code that's never been seen upstream seems completely premature.

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

Hey, how does code get upstream then? :)

By having the actual code to the mailing list.

Ugh, vger keeps blocking the patches, I'm going to try for a third time now...

Quoted 7 lines

third time seems to have worked, except for patch 01/32, which was only a single .h file. That file can be seen here if people are curious:
https://git.kernel.org/cgit/linux/kernel/git/gregkh/char-misc.git/tree/drivers/greybus/greybus_protocols.h?h=greybus

I'll now respin the series, moving everything into drivers/staging/greybus/ and leave it there for 4.9. Then we can work on moving the core out, and then the drivers to the needed locations.

This is now all in drivers/staging/greybus/ and will show up in the next linux-next.

And because of this, we already have a checkpatch.pl cleanup patch sent to us for the code for things we missed :)

thanks,

greg k-h

#28 lore raw
In reply to Greg KH
Quoted 28 lines · CEST Greg KH
Quoted 18 lines · Mark Brown
Quoted 6 lines · Greg KH

I'll send out a follow-up set of "simple" patches that just add the files to the kernel tree, to give people an idea of the code involved. Overall, it's a tiny stand-alone driver subsystem, only 37k lines, that implements a protocol which allows for "generic" cameras, audio devices, and other class type devices, as well as a bridged "physical" layer protocol to talk to serial, spi, uart, pwm, gpio, i2c, and even USB host

Just emphasizing what Mark Rutland said complete NACK, in particular the drivers for functions have not been posted upstream at all (and it's concerning that they're all being added under drivers/greybus rather than within the relevant subsystem like we do normally). I've not looked at the code as it has not been submitted but given that and that the reason I found this pull request was an ASoC contributor who had seen it and looked at the code going "oh dear, greybus..." on IRC I'm very concerned.

Quoted 2 lines

Sending a pull request for code that's never been seen upstream seems completely premature.

Hey, how does code get upstream then? :)

Anyway, as vger seems to be marking all of the greybus patches I send out as "spam" according to a filter, it's making it a bit hard to send patches out, but I'll try it again "by hand" later today...

Quoted 8 lines

As for the drivers all living under drivers/greybus/ I understand, but we need the greybus core present first before we can get the drivers in. How about we do what happened with IIO, we take the greybus core code in drivers/greybus/ and put the drivers in staging, and then move them out of staging into the "real" portion of the kernel as they get reviewed and accepted?

Any objections to that workflow?

I think that will work fine. If we can review just the core code, the patch series probably gets small enough that more people are inclined to take a closer look. At least speaking for me personally, reviewing 32 nontrivial patches at once is unlikely to happen.

Quoted 1 line

From a brief look, I found that at least the smaller device drivers

should be uncontroversial and won't have a problem getting merged into the subsystem directories once they have be reviewed by the respective maintainers.

Some drivers are larger (audio sticks out by quite a bit) and are more likely to be controversial. Some other drivers (e.g. vibrator or bootrom) don't have an obvious subsystem they belong into, which can also cause problems, but having them in staging in the meantime seems fine.

Arnd

#29 lore raw
In reply to Greg KH

Hi,

Apologies for the late reply to this; I don't subscribe to LKML with my work address and didn't spot this sub-thread until now.

Quoted 19 lines · Greg KH
Quoted 18 lines · Mark Brown
Quoted 6 lines · Greg KH

I'll send out a follow-up set of "simple" patches that just add the files to the kernel tree, to give people an idea of the code involved. Overall, it's a tiny stand-alone driver subsystem, only 37k lines, that implements a protocol which allows for "generic" cameras, audio devices, and other class type devices, as well as a bridged "physical" layer protocol to talk to serial, spi, uart, pwm, gpio, i2c, and even USB host

Just emphasizing what Mark Rutland said complete NACK, in particular the drivers for functions have not been posted upstream at all (and it's concerning that they're all being added under drivers/greybus rather than within the relevant subsystem like we do normally). I've not looked at the code as it has not been submitted but given that and that the reason I found this pull request was an ASoC contributor who had seen it and looked at the code going "oh dear, greybus..." on IRC I'm very concerned.

Quoted 4 lines
Quoted 2 lines

Sending a pull request for code that's never been seen upstream seems completely premature.

Hey, how does code get upstream then? :)

As Mark Brown said, after review.

Quoted 4 lines

Anyway, as vger seems to be marking all of the greybus patches I send out as "spam" according to a filter, it's making it a bit hard to send patches out, but I'll try it again "by hand" later today...

Quoted 8 lines

As for the drivers all living under drivers/greybus/ I understand, but we need the greybus core present first before we can get the drivers in. How about we do what happened with IIO, we take the greybus core code in drivers/greybus/ and put the drivers in staging, and then move them out of staging into the "real" portion of the kernel as they get reviewed and accepted?

Any objections to that workflow?

Personally, yes.

I would rather see the few core patches go through some level of review first. It's vastly easier to handle that than to reverse-engineer an understanding of the code when "move XXX out of staging/" patches appear.

I'm more than happy to review a reasonably-size, linearised series for that core code.

My concerns previously mentioned still apply to the patches queued in linux-next, regardless of whether these patches are under staging. Especially given the ABI implications of the devicetree bindings.

Thanks,
Mark.

author maintainer
#30 lore raw
Quoted 16 lines · Mark Rutland
Quoted 8 lines

As for the drivers all living under drivers/greybus/ I understand, but we need the greybus core present first before we can get the drivers in. How about we do what happened with IIO, we take the greybus core code in drivers/greybus/ and put the drivers in staging, and then move them out of staging into the "real" portion of the kernel as they get reviewed and accepted?

Any objections to that workflow?

Personally, yes.

I would rather see the few core patches go through some level of review first. It's vastly easier to handle that than to reverse-engineer an understanding of the code when "move XXX out of staging/" patches appear.

Quoted 6 lines

I'm more than happy to review a reasonably-size, linearised series for that core code.

My concerns previously mentioned still apply to the patches queued in linux-next, regardless of whether these patches are under staging. Especially given the ABI implications of the devicetree bindings.

There are no ABI implications just yet, we are free to change anything we want. It's all in drivers/staging/ now, where we will clean it up some more over the next few weeks and then post reviewable sets of patches to move portions out of staging (greybus core first, then some class drivers, then the subsystem-specific things). That will give you your set of clean patches to review, and with the first rounds, no device tree bindings at all :)

thanks,

greg k-h

author maintainer
#31 lore raw

cover Greybus driver subsystem

In reply to Greg KH

This is a simple set of patches, adding the greybus core and drivers to the kernel tree.

It breaks the larger git tree submission up into individual files to make it easier for reviewing.

Any questions or comments, please let us know.

thanks,

greg k-h

author maintainer
#32 lore raw

PATCH 2/32 greybus: interface control logic

This is the Greybus interface control code. It handles the interactions between the Greybus core and the Greybus interface devices in the system.

---
 drivers/greybus/interface.c | 1316 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/greybus/interface.h |   88 ++
 2 files changed, 1404 insertions(+)
drivers/greybus/interface.c new file +1316 -0
@@ -0,0 +1,1316 @@
+/*
+ * Greybus interface code
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/delay.h>
+
+#include "greybus.h"
+#include "greybus_trace.h"
+
+#define GB_INTERFACE_MODE_SWITCH_TIMEOUT	2000
+
+#define GB_INTERFACE_DEVICE_ID_BAD	0xff
+
+#define GB_INTERFACE_AUTOSUSPEND_MS			3000
+
+/* Time required for interface to enter standby before disabling REFCLK */
+#define GB_INTERFACE_SUSPEND_HIBERNATE_DELAY_MS			20
+
+/* Don't-care selector index */
+#define DME_SELECTOR_INDEX_NULL		0
+
+/* DME attributes */
+/* FIXME: remove ES2 support and DME_T_TST_SRC_INCREMENT */
+#define DME_T_TST_SRC_INCREMENT		0x4083
+
+#define DME_DDBL1_MANUFACTURERID	0x5003
+#define DME_DDBL1_PRODUCTID		0x5004
+
+#define DME_TOSHIBA_GMP_VID		0x6000
+#define DME_TOSHIBA_GMP_PID		0x6001
+#define DME_TOSHIBA_GMP_SN0		0x6002
+#define DME_TOSHIBA_GMP_SN1		0x6003
+#define DME_TOSHIBA_GMP_INIT_STATUS	0x6101
+
+/* DDBL1 Manufacturer and Product ids */
+#define TOSHIBA_DMID			0x0126
+#define TOSHIBA_ES2_BRIDGE_DPID		0x1000
+#define TOSHIBA_ES3_APBRIDGE_DPID	0x1001
+#define TOSHIBA_ES3_GBPHY_DPID	0x1002
+
+static int gb_interface_hibernate_link(struct gb_interface *intf);
+static int gb_interface_refclk_set(struct gb_interface *intf, bool enable);
+
+static int gb_interface_dme_attr_get(struct gb_interface *intf,
+							u16 attr, u32 *val)
+{
+	return gb_svc_dme_peer_get(intf->hd->svc, intf->interface_id,
+					attr, DME_SELECTOR_INDEX_NULL, val);
+}
+
+static int gb_interface_read_ara_dme(struct gb_interface *intf)
+{
+	u32 sn0, sn1;
+	int ret;
+
+	/*
+	 * Unless this is a Toshiba bridge, bail out until we have defined
+	 * standard GMP attributes.
+	 */
+	if (intf->ddbl1_manufacturer_id != TOSHIBA_DMID) {
+		dev_err(&intf->dev, "unknown manufacturer %08x\n",
+				intf->ddbl1_manufacturer_id);
+		return -ENODEV;
+	}
+
+	ret = gb_interface_dme_attr_get(intf, DME_TOSHIBA_GMP_VID,
+					&intf->vendor_id);
+	if (ret)
+		return ret;
+
+	ret = gb_interface_dme_attr_get(intf, DME_TOSHIBA_GMP_PID,
+					&intf->product_id);
+	if (ret)
+		return ret;
+
+	ret = gb_interface_dme_attr_get(intf, DME_TOSHIBA_GMP_SN0, &sn0);
+	if (ret)
+		return ret;
+
+	ret = gb_interface_dme_attr_get(intf, DME_TOSHIBA_GMP_SN1, &sn1);
+	if (ret)
+		return ret;
+
+	intf->serial_number = (u64)sn1 << 32 | sn0;
+
+	return 0;
+}
+
+static int gb_interface_read_dme(struct gb_interface *intf)
+{
+	int ret;
+
+	/* DME attributes have already been read */
+	if (intf->dme_read)
+		return 0;
+
+	ret = gb_interface_dme_attr_get(intf, DME_DDBL1_MANUFACTURERID,
+					&intf->ddbl1_manufacturer_id);
+	if (ret)
+		return ret;
+
+	ret = gb_interface_dme_attr_get(intf, DME_DDBL1_PRODUCTID,
+					&intf->ddbl1_product_id);
+	if (ret)
+		return ret;
+
+	if (intf->ddbl1_manufacturer_id == TOSHIBA_DMID &&
+			intf->ddbl1_product_id == TOSHIBA_ES2_BRIDGE_DPID) {
+		intf->quirks |= GB_INTERFACE_QUIRK_NO_GMP_IDS;
+		intf->quirks |= GB_INTERFACE_QUIRK_NO_INIT_STATUS;
+	}
+
+	ret = gb_interface_read_ara_dme(intf);
+	if (ret)
+		return ret;
+
+	intf->dme_read = true;
+
+	return 0;
+}
+
+static int gb_interface_route_create(struct gb_interface *intf)
+{
+	struct gb_svc *svc = intf->hd->svc;
+	u8 intf_id = intf->interface_id;
+	u8 device_id;
+	int ret;
+
+	/* Allocate an interface device id. */
+	ret = ida_simple_get(&svc->device_id_map,
+			     GB_SVC_DEVICE_ID_MIN, GB_SVC_DEVICE_ID_MAX + 1,
+			     GFP_KERNEL);
+	if (ret < 0) {
+		dev_err(&intf->dev, "failed to allocate device id: %d\n", ret);
+		return ret;
+	}
+	device_id = ret;
+
+	ret = gb_svc_intf_device_id(svc, intf_id, device_id);
+	if (ret) {
+		dev_err(&intf->dev, "failed to set device id %u: %d\n",
+				device_id, ret);
+		goto err_ida_remove;
+	}
+
+	/* FIXME: Hard-coded AP device id. */
+	ret = gb_svc_route_create(svc, svc->ap_intf_id, GB_SVC_DEVICE_ID_AP,
+				  intf_id, device_id);
+	if (ret) {
+		dev_err(&intf->dev, "failed to create route: %d\n", ret);
+		goto err_svc_id_free;
+	}
+
+	intf->device_id = device_id;
+
+	return 0;
+
+err_svc_id_free:
+	/*
+	 * XXX Should we tell SVC that this id doesn't belong to interface
+	 * XXX anymore.
+	 */
+err_ida_remove:
+	ida_simple_remove(&svc->device_id_map, device_id);
+
+	return ret;
+}
+
+static void gb_interface_route_destroy(struct gb_interface *intf)
+{
+	struct gb_svc *svc = intf->hd->svc;
+
+	if (intf->device_id == GB_INTERFACE_DEVICE_ID_BAD)
+		return;
+
+	gb_svc_route_destroy(svc, svc->ap_intf_id, intf->interface_id);
+	ida_simple_remove(&svc->device_id_map, intf->device_id);
+	intf->device_id = GB_INTERFACE_DEVICE_ID_BAD;
+}
+
+/* Locking: Caller holds the interface mutex. */
+static int gb_interface_legacy_mode_switch(struct gb_interface *intf)
+{
+	int ret;
+
+	dev_info(&intf->dev, "legacy mode switch detected\n");
+
+	/* Mark as disconnected to prevent I/O during disable. */
+	intf->disconnected = true;
+	gb_interface_disable(intf);
+	intf->disconnected = false;
+
+	ret = gb_interface_enable(intf);
+	if (ret) {
+		dev_err(&intf->dev, "failed to re-enable interface: %d\n", ret);
+		gb_interface_deactivate(intf);
+	}
+
+	return ret;
+}
+
+void gb_interface_mailbox_event(struct gb_interface *intf, u16 result,
+								u32 mailbox)
+{
+	mutex_lock(&intf->mutex);
+
+	if (result) {
+		dev_warn(&intf->dev,
+				"mailbox event with UniPro error: 0x%04x\n",
+				result);
+		goto err_disable;
+	}
+
+	if (mailbox != GB_SVC_INTF_MAILBOX_GREYBUS) {
+		dev_warn(&intf->dev,
+				"mailbox event with unexpected value: 0x%08x\n",
+				mailbox);
+		goto err_disable;
+	}
+
+	if (intf->quirks & GB_INTERFACE_QUIRK_LEGACY_MODE_SWITCH) {
+		gb_interface_legacy_mode_switch(intf);
+		goto out_unlock;
+	}
+
+	if (!intf->mode_switch) {
+		dev_warn(&intf->dev, "unexpected mailbox event: 0x%08x\n",
+				mailbox);
+		goto err_disable;
+	}
+
+	dev_info(&intf->dev, "mode switch detected\n");
+
+	complete(&intf->mode_switch_completion);
+
+out_unlock:
+	mutex_unlock(&intf->mutex);
+
+	return;
+
+err_disable:
+	gb_interface_disable(intf);
+	gb_interface_deactivate(intf);
+	mutex_unlock(&intf->mutex);
+}
+
+static void gb_interface_mode_switch_work(struct work_struct *work)
+{
+	struct gb_interface *intf;
+	struct gb_control *control;
+	unsigned long timeout;
+	int ret;
+
+	intf = container_of(work, struct gb_interface, mode_switch_work);
+
+	mutex_lock(&intf->mutex);
+	/* Make sure interface is still enabled. */
+	if (!intf->enabled) {
+		dev_dbg(&intf->dev, "mode switch aborted\n");
+		intf->mode_switch = false;
+		mutex_unlock(&intf->mutex);
+		goto out_interface_put;
+	}
+
+	/*
+	 * Prepare the control device for mode switch and make sure to get an
+	 * extra reference before it goes away during interface disable.
+	 */
+	control = gb_control_get(intf->control);
+	gb_control_mode_switch_prepare(control);
+	gb_interface_disable(intf);
+	mutex_unlock(&intf->mutex);
+
+	timeout = msecs_to_jiffies(GB_INTERFACE_MODE_SWITCH_TIMEOUT);
+	ret = wait_for_completion_interruptible_timeout(
+			&intf->mode_switch_completion, timeout);
+
+	/* Finalise control-connection mode switch. */
+	gb_control_mode_switch_complete(control);
+	gb_control_put(control);
+
+	if (ret < 0) {
+		dev_err(&intf->dev, "mode switch interrupted\n");
+		goto err_deactivate;
+	} else if (ret == 0) {
+		dev_err(&intf->dev, "mode switch timed out\n");
+		goto err_deactivate;
+	}
+
+	/* Re-enable (re-enumerate) interface if still active. */
+	mutex_lock(&intf->mutex);
+	intf->mode_switch = false;
+	if (intf->active) {
+		ret = gb_interface_enable(intf);
+		if (ret) {
+			dev_err(&intf->dev, "failed to re-enable interface: %d\n",
+					ret);
+			gb_interface_deactivate(intf);
+		}
+	}
+	mutex_unlock(&intf->mutex);
+
+out_interface_put:
+	gb_interface_put(intf);
+
+	return;
+
+err_deactivate:
+	mutex_lock(&intf->mutex);
+	intf->mode_switch = false;
+	gb_interface_deactivate(intf);
+	mutex_unlock(&intf->mutex);
+
+	gb_interface_put(intf);
+}
+
+int gb_interface_request_mode_switch(struct gb_interface *intf)
+{
+	int ret = 0;
+
+	mutex_lock(&intf->mutex);
+	if (intf->mode_switch) {
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+	intf->mode_switch = true;
+	reinit_completion(&intf->mode_switch_completion);
+
+	/*
+	 * Get a reference to the interface device, which will be put once the
+	 * mode switch is complete.
+	 */
+	get_device(&intf->dev);
+
+	if (!queue_work(system_long_wq, &intf->mode_switch_work)) {
+		put_device(&intf->dev);
+		ret = -EBUSY;
+		goto out_unlock;
+	}
+
+out_unlock:
+	mutex_unlock(&intf->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_interface_request_mode_switch);
+
+/*
+ * T_TstSrcIncrement is written by the module on ES2 as a stand-in for the
+ * init-status attribute DME_TOSHIBA_INIT_STATUS. The AP needs to read and
+ * clear it after reading a non-zero value from it.
+ *
+ * FIXME: This is module-hardware dependent and needs to be extended for every
+ * type of module we want to support.
+ */
+static int gb_interface_read_and_clear_init_status(struct gb_interface *intf)
+{
+	struct gb_host_device *hd = intf->hd;
+	unsigned long bootrom_quirks;
+	unsigned long s2l_quirks;
+	int ret;
+	u32 value;
+	u16 attr;
+	u8 init_status;
+
+	/*
+	 * ES2 bridges use T_TstSrcIncrement for the init status.
+	 *
+	 * FIXME: Remove ES2 support
+	 */
+	if (intf->quirks & GB_INTERFACE_QUIRK_NO_INIT_STATUS)
+		attr = DME_T_TST_SRC_INCREMENT;
+	else
+		attr = DME_TOSHIBA_GMP_INIT_STATUS;
+
+	ret = gb_svc_dme_peer_get(hd->svc, intf->interface_id, attr,
+				  DME_SELECTOR_INDEX_NULL, &value);
+	if (ret)
+		return ret;
+
+	/*
+	 * A nonzero init status indicates the module has finished
+	 * initializing.
+	 */
+	if (!value) {
+		dev_err(&intf->dev, "invalid init status\n");
+		return -ENODEV;
+	}
+
+	/*
+	 * Extract the init status.
+	 *
+	 * For ES2: We need to check lowest 8 bits of 'value'.
+	 * For ES3: We need to check highest 8 bits out of 32 of 'value'.
+	 *
+	 * FIXME: Remove ES2 support
+	 */
+	if (intf->quirks & GB_INTERFACE_QUIRK_NO_INIT_STATUS)
+		init_status = value & 0xff;
+	else
+		init_status = value >> 24;
+
+	/*
+	 * Check if the interface is executing the quirky ES3 bootrom that,
+	 * for example, requires E2EFC, CSD and CSV to be disabled.
+	 */
+	bootrom_quirks = GB_INTERFACE_QUIRK_NO_CPORT_FEATURES |
+				GB_INTERFACE_QUIRK_FORCED_DISABLE |
+				GB_INTERFACE_QUIRK_LEGACY_MODE_SWITCH |
+				GB_INTERFACE_QUIRK_NO_BUNDLE_ACTIVATE;
+
+	s2l_quirks = GB_INTERFACE_QUIRK_NO_PM;
+
+	switch (init_status) {
+	case GB_INIT_BOOTROM_UNIPRO_BOOT_STARTED:
+	case GB_INIT_BOOTROM_FALLBACK_UNIPRO_BOOT_STARTED:
+		intf->quirks |= bootrom_quirks;
+		break;
+	case GB_INIT_S2_LOADER_BOOT_STARTED:
+		/* S2 Loader doesn't support runtime PM */
+		intf->quirks &= ~bootrom_quirks;
+		intf->quirks |= s2l_quirks;
+		break;
+	default:
+		intf->quirks &= ~bootrom_quirks;
+		intf->quirks &= ~s2l_quirks;
+	}
+
+	/* Clear the init status. */
+	return gb_svc_dme_peer_set(hd->svc, intf->interface_id, attr,
+				   DME_SELECTOR_INDEX_NULL, 0);
+}
+
+/* interface sysfs attributes */
+#define gb_interface_attr(field, type)					\
+static ssize_t field##_show(struct device *dev,				\
+			    struct device_attribute *attr,		\
+			    char *buf)					\
+{									\
+	struct gb_interface *intf = to_gb_interface(dev);		\
+	return scnprintf(buf, PAGE_SIZE, type"\n", intf->field);	\
+}									\
+static DEVICE_ATTR_RO(field)
+
+gb_interface_attr(ddbl1_manufacturer_id, "0x%08x");
+gb_interface_attr(ddbl1_product_id, "0x%08x");
+gb_interface_attr(interface_id, "%u");
+gb_interface_attr(vendor_id, "0x%08x");
+gb_interface_attr(product_id, "0x%08x");
+gb_interface_attr(serial_number, "0x%016llx");
+
+static ssize_t voltage_now_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+	int ret;
+	u32 measurement;
+
+	ret = gb_svc_pwrmon_intf_sample_get(intf->hd->svc, intf->interface_id,
+					    GB_SVC_PWRMON_TYPE_VOL,
+					    &measurement);
+	if (ret) {
+		dev_err(&intf->dev, "failed to get voltage sample (%d)\n", ret);
+		return ret;
+	}
+
+	return sprintf(buf, "%u\n", measurement);
+}
+static DEVICE_ATTR_RO(voltage_now);
+
+static ssize_t current_now_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+	int ret;
+	u32 measurement;
+
+	ret = gb_svc_pwrmon_intf_sample_get(intf->hd->svc, intf->interface_id,
+					    GB_SVC_PWRMON_TYPE_CURR,
+					    &measurement);
+	if (ret) {
+		dev_err(&intf->dev, "failed to get current sample (%d)\n", ret);
+		return ret;
+	}
+
+	return sprintf(buf, "%u\n", measurement);
+}
+static DEVICE_ATTR_RO(current_now);
+
+static ssize_t power_now_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+	int ret;
+	u32 measurement;
+
+	ret = gb_svc_pwrmon_intf_sample_get(intf->hd->svc, intf->interface_id,
+					    GB_SVC_PWRMON_TYPE_PWR,
+					    &measurement);
+	if (ret) {
+		dev_err(&intf->dev, "failed to get power sample (%d)\n", ret);
+		return ret;
+	}
+
+	return sprintf(buf, "%u\n", measurement);
+}
+static DEVICE_ATTR_RO(power_now);
+
+static ssize_t power_state_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+
+	if (intf->active)
+		return scnprintf(buf, PAGE_SIZE, "on\n");
+	else
+		return scnprintf(buf, PAGE_SIZE, "off\n");
+}
+
+static ssize_t power_state_store(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t len)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+	bool activate;
+	int ret = 0;
+
+	if (kstrtobool(buf, &activate))
+		return -EINVAL;
+
+	mutex_lock(&intf->mutex);
+
+	if (activate == intf->active)
+		goto unlock;
+
+	if (activate) {
+		ret = gb_interface_activate(intf);
+		if (ret) {
+			dev_err(&intf->dev,
+				"failed to activate interface: %d\n", ret);
+			goto unlock;
+		}
+
+		ret = gb_interface_enable(intf);
+		if (ret) {
+			dev_err(&intf->dev,
+				"failed to enable interface: %d\n", ret);
+			gb_interface_deactivate(intf);
+			goto unlock;
+		}
+	} else {
+		gb_interface_disable(intf);
+		gb_interface_deactivate(intf);
+	}
+
+unlock:
+	mutex_unlock(&intf->mutex);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+static DEVICE_ATTR_RW(power_state);
+
+static const char *gb_interface_type_string(struct gb_interface *intf)
+{
+	static const char * const types[] = {
+		[GB_INTERFACE_TYPE_INVALID] = "invalid",
+		[GB_INTERFACE_TYPE_UNKNOWN] = "unknown",
+		[GB_INTERFACE_TYPE_DUMMY] = "dummy",
+		[GB_INTERFACE_TYPE_UNIPRO] = "unipro",
+		[GB_INTERFACE_TYPE_GREYBUS] = "greybus",
+	};
+
+	return types[intf->type];
+}
+
+static ssize_t interface_type_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+
+	return sprintf(buf, "%s\n", gb_interface_type_string(intf));
+}
+static DEVICE_ATTR_RO(interface_type);
+
+static struct attribute *interface_unipro_attrs[] = {
+	&dev_attr_ddbl1_manufacturer_id.attr,
+	&dev_attr_ddbl1_product_id.attr,
+	NULL
+};
+
+static struct attribute *interface_greybus_attrs[] = {
+	&dev_attr_vendor_id.attr,
+	&dev_attr_product_id.attr,
+	&dev_attr_serial_number.attr,
+	NULL
+};
+
+static struct attribute *interface_power_attrs[] = {
+	&dev_attr_voltage_now.attr,
+	&dev_attr_current_now.attr,
+	&dev_attr_power_now.attr,
+	&dev_attr_power_state.attr,
+	NULL
+};
+
+static struct attribute *interface_common_attrs[] = {
+	&dev_attr_interface_id.attr,
+	&dev_attr_interface_type.attr,
+	NULL
+};
+
+static umode_t interface_unipro_is_visible(struct kobject *kobj,
+						struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct gb_interface *intf = to_gb_interface(dev);
+
+	switch (intf->type) {
+	case GB_INTERFACE_TYPE_UNIPRO:
+	case GB_INTERFACE_TYPE_GREYBUS:
+		return attr->mode;
+	default:
+		return 0;
+	}
+}
+
+static umode_t interface_greybus_is_visible(struct kobject *kobj,
+						struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct gb_interface *intf = to_gb_interface(dev);
+
+	switch (intf->type) {
+	case GB_INTERFACE_TYPE_GREYBUS:
+		return attr->mode;
+	default:
+		return 0;
+	}
+}
+
+static umode_t interface_power_is_visible(struct kobject *kobj,
+						struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct gb_interface *intf = to_gb_interface(dev);
+
+	switch (intf->type) {
+	case GB_INTERFACE_TYPE_UNIPRO:
+	case GB_INTERFACE_TYPE_GREYBUS:
+		return attr->mode;
+	default:
+		return 0;
+	}
+}
+
+static const struct attribute_group interface_unipro_group = {
+	.is_visible	= interface_unipro_is_visible,
+	.attrs		= interface_unipro_attrs,
+};
+
+static const struct attribute_group interface_greybus_group = {
+	.is_visible	= interface_greybus_is_visible,
+	.attrs		= interface_greybus_attrs,
+};
+
+static const struct attribute_group interface_power_group = {
+	.is_visible	= interface_power_is_visible,
+	.attrs		= interface_power_attrs,
+};
+
+static const struct attribute_group interface_common_group = {
+	.attrs		= interface_common_attrs,
+};
+
+static const struct attribute_group *interface_groups[] = {
+	&interface_unipro_group,
+	&interface_greybus_group,
+	&interface_power_group,
+	&interface_common_group,
+	NULL
+};
+
+static void gb_interface_release(struct device *dev)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+
+	trace_gb_interface_release(intf);
+
+	kfree(intf);
+}
+
+#ifdef CONFIG_PM
+static int gb_interface_suspend(struct device *dev)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+	int ret, timesync_ret;
+
+	ret = gb_control_interface_suspend_prepare(intf->control);
+	if (ret)
+		return ret;
+
+	gb_timesync_interface_remove(intf);
+
+	ret = gb_control_suspend(intf->control);
+	if (ret)
+		goto err_hibernate_abort;
+
+	ret = gb_interface_hibernate_link(intf);
+	if (ret)
+		return ret;
+
+	/* Delay to allow interface to enter standby before disabling refclk */
+	msleep(GB_INTERFACE_SUSPEND_HIBERNATE_DELAY_MS);
+
+	ret = gb_interface_refclk_set(intf, false);
+	if (ret)
+		return ret;
+
+	return 0;
+
+err_hibernate_abort:
+	gb_control_interface_hibernate_abort(intf->control);
+
+	timesync_ret = gb_timesync_interface_add(intf);
+	if (timesync_ret) {
+		dev_err(dev, "failed to add to timesync: %d\n", timesync_ret);
+		return timesync_ret;
+	}
+
+	return ret;
+}
+
+static int gb_interface_resume(struct device *dev)
+{
+	struct gb_interface *intf = to_gb_interface(dev);
+	struct gb_svc *svc = intf->hd->svc;
+	int ret;
+
+	ret = gb_interface_refclk_set(intf, true);
+	if (ret)
+		return ret;
+
+	ret = gb_svc_intf_resume(svc, intf->interface_id);
+	if (ret)
+		return ret;
+
+	ret = gb_control_resume(intf->control);
+	if (ret)
+		return ret;
+
+	ret = gb_timesync_interface_add(intf);
+	if (ret) {
+		dev_err(dev, "failed to add to timesync: %d\n", ret);
+		return ret;
+	}
+
+	ret = gb_timesync_schedule_synchronous(intf);
+	if (ret) {
+		dev_err(dev, "failed to synchronize FrameTime: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int gb_interface_runtime_idle(struct device *dev)
+{
+	pm_runtime_mark_last_busy(dev);
+	pm_request_autosuspend(dev);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_interface_pm_ops = {
+	SET_RUNTIME_PM_OPS(gb_interface_suspend, gb_interface_resume,
+			   gb_interface_runtime_idle)
+};
+
+struct device_type greybus_interface_type = {
+	.name =		"greybus_interface",
+	.release =	gb_interface_release,
+	.pm =		&gb_interface_pm_ops,
+};
+
+/*
+ * A Greybus module represents a user-replaceable component on a GMP
+ * phone.  An interface is the physical connection on that module.  A
+ * module may have more than one interface.
+ *
+ * Create a gb_interface structure to represent a discovered interface.
+ * The position of interface within the Endo is encoded in "interface_id"
+ * argument.
+ *
+ * Returns a pointer to the new interfce or a null pointer if a
+ * failure occurs due to memory exhaustion.
+ */
+struct gb_interface *gb_interface_create(struct gb_module *module,
+					 u8 interface_id)
+{
+	struct gb_host_device *hd = module->hd;
+	struct gb_interface *intf;
+
+	intf = kzalloc(sizeof(*intf), GFP_KERNEL);
+	if (!intf)
+		return NULL;
+
+	intf->hd = hd;		/* XXX refcount? */
+	intf->module = module;
+	intf->interface_id = interface_id;
+	INIT_LIST_HEAD(&intf->bundles);
+	INIT_LIST_HEAD(&intf->manifest_descs);
+	mutex_init(&intf->mutex);
+	INIT_WORK(&intf->mode_switch_work, gb_interface_mode_switch_work);
+	init_completion(&intf->mode_switch_completion);
+
+	/* Invalid device id to start with */
+	intf->device_id = GB_INTERFACE_DEVICE_ID_BAD;
+
+	intf->dev.parent = &module->dev;
+	intf->dev.bus = &greybus_bus_type;
+	intf->dev.type = &greybus_interface_type;
+	intf->dev.groups = interface_groups;
+	intf->dev.dma_mask = module->dev.dma_mask;
+	device_initialize(&intf->dev);
+	dev_set_name(&intf->dev, "%s.%u", dev_name(&module->dev),
+			interface_id);
+
+	pm_runtime_set_autosuspend_delay(&intf->dev,
+					 GB_INTERFACE_AUTOSUSPEND_MS);
+
+	trace_gb_interface_create(intf);
+
+	return intf;
+}
+
+static int gb_interface_vsys_set(struct gb_interface *intf, bool enable)
+{
+	struct gb_svc *svc = intf->hd->svc;
+	int ret;
+
+	dev_dbg(&intf->dev, "%s - %d\n", __func__, enable);
+
+	ret = gb_svc_intf_vsys_set(svc, intf->interface_id, enable);
+	if (ret) {
+		dev_err(&intf->dev, "failed to set v_sys: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int gb_interface_refclk_set(struct gb_interface *intf, bool enable)
+{
+	struct gb_svc *svc = intf->hd->svc;
+	int ret;
+
+	dev_dbg(&intf->dev, "%s - %d\n", __func__, enable);
+
+	ret = gb_svc_intf_refclk_set(svc, intf->interface_id, enable);
+	if (ret) {
+		dev_err(&intf->dev, "failed to set refclk: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int gb_interface_unipro_set(struct gb_interface *intf, bool enable)
+{
+	struct gb_svc *svc = intf->hd->svc;
+	int ret;
+
+	dev_dbg(&intf->dev, "%s - %d\n", __func__, enable);
+
+	ret = gb_svc_intf_unipro_set(svc, intf->interface_id, enable);
+	if (ret) {
+		dev_err(&intf->dev, "failed to set UniPro: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int gb_interface_activate_operation(struct gb_interface *intf,
+					   enum gb_interface_type *intf_type)
+{
+	struct gb_svc *svc = intf->hd->svc;
+	u8 type;
+	int ret;
+
+	dev_dbg(&intf->dev, "%s\n", __func__);
+
+	ret = gb_svc_intf_activate(svc, intf->interface_id, &type);
+	if (ret) {
+		dev_err(&intf->dev, "failed to activate: %d\n", ret);
+		return ret;
+	}
+
+	switch (type) {
+	case GB_SVC_INTF_TYPE_DUMMY:
+		*intf_type = GB_INTERFACE_TYPE_DUMMY;
+		/* FIXME: handle as an error for now */
+		return -ENODEV;
+	case GB_SVC_INTF_TYPE_UNIPRO:
+		*intf_type = GB_INTERFACE_TYPE_UNIPRO;
+		dev_err(&intf->dev, "interface type UniPro not supported\n");
+		/* FIXME: handle as an error for now */
+		return -ENODEV;
+	case GB_SVC_INTF_TYPE_GREYBUS:
+		*intf_type = GB_INTERFACE_TYPE_GREYBUS;
+		break;
+	default:
+		dev_err(&intf->dev, "unknown interface type: %u\n", type);
+		*intf_type = GB_INTERFACE_TYPE_UNKNOWN;
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int gb_interface_hibernate_link(struct gb_interface *intf)
+{
+	struct gb_svc *svc = intf->hd->svc;
+
+	return gb_svc_intf_set_power_mode_hibernate(svc, intf->interface_id);
+}
+
+static int _gb_interface_activate(struct gb_interface *intf,
+				  enum gb_interface_type *type)
+{
+	int ret;
+
+	*type = GB_INTERFACE_TYPE_UNKNOWN;
+
+	if (intf->ejected || intf->removed)
+		return -ENODEV;
+
+	ret = gb_interface_vsys_set(intf, true);
+	if (ret)
+		return ret;
+
+	ret = gb_interface_refclk_set(intf, true);
+	if (ret)
+		goto err_vsys_disable;
+
+	ret = gb_interface_unipro_set(intf, true);
+	if (ret)
+		goto err_refclk_disable;
+
+	ret = gb_interface_activate_operation(intf, type);
+	if (ret) {
+		switch (*type) {
+		case GB_INTERFACE_TYPE_UNIPRO:
+		case GB_INTERFACE_TYPE_GREYBUS:
+			goto err_hibernate_link;
+		default:
+			goto err_unipro_disable;
+		}
+	}
+
+	ret = gb_interface_read_dme(intf);
+	if (ret)
+		goto err_hibernate_link;
+
+	ret = gb_interface_route_create(intf);
+	if (ret)
+		goto err_hibernate_link;
+
+	intf->active = true;
+
+	trace_gb_interface_activate(intf);
+
+	return 0;
+
+err_hibernate_link:
+	gb_interface_hibernate_link(intf);
+err_unipro_disable:
+	gb_interface_unipro_set(intf, false);
+err_refclk_disable:
+	gb_interface_refclk_set(intf, false);
+err_vsys_disable:
+	gb_interface_vsys_set(intf, false);
+
+	return ret;
+}
+
+/*
+ * At present, we assume a UniPro-only module to be a Greybus module that
+ * failed to send its mailbox poke. There is some reason to believe that this
+ * is because of a bug in the ES3 bootrom.
+ *
+ * FIXME: Check if this is a Toshiba bridge before retrying?
+ */
+static int _gb_interface_activate_es3_hack(struct gb_interface *intf,
+					   enum gb_interface_type *type)
+{
+	int retries = 3;
+	int ret;
+
+	while (retries--) {
+		ret = _gb_interface_activate(intf, type);
+		if (ret == -ENODEV && *type == GB_INTERFACE_TYPE_UNIPRO)
+			continue;
+
+		break;
+	}
+
+	return ret;
+}
+
+/*
+ * Activate an interface.
+ *
+ * Locking: Caller holds the interface mutex.
+ */
+int gb_interface_activate(struct gb_interface *intf)
+{
+	enum gb_interface_type type;
+	int ret;
+
+	switch (intf->type) {
+	case GB_INTERFACE_TYPE_INVALID:
+	case GB_INTERFACE_TYPE_GREYBUS:
+		ret = _gb_interface_activate_es3_hack(intf, &type);
+		break;
+	default:
+		ret = _gb_interface_activate(intf, &type);
+	}
+
+	/* Make sure type is detected correctly during reactivation. */
+	if (intf->type != GB_INTERFACE_TYPE_INVALID) {
+		if (type != intf->type) {
+			dev_err(&intf->dev, "failed to detect interface type\n");
+
+			if (!ret)
+				gb_interface_deactivate(intf);
+
+			return -EIO;
+		}
+	} else {
+		intf->type = type;
+	}
+
+	return ret;
+}
+
+/*
+ * Deactivate an interface.
+ *
+ * Locking: Caller holds the interface mutex.
+ */
+void gb_interface_deactivate(struct gb_interface *intf)
+{
+	if (!intf->active)
+		return;
+
+	trace_gb_interface_deactivate(intf);
+
+	/* Abort any ongoing mode switch. */
+	if (intf->mode_switch)
+		complete(&intf->mode_switch_completion);
+
+	gb_interface_route_destroy(intf);
+	gb_interface_hibernate_link(intf);
+	gb_interface_unipro_set(intf, false);
+	gb_interface_refclk_set(intf, false);
+	gb_interface_vsys_set(intf, false);
+
+	intf->active = false;
+}
+
+/*
+ * Enable an interface by enabling its control connection, fetching the
+ * manifest and other information over it, and finally registering its child
+ * devices.
+ *
+ * Locking: Caller holds the interface mutex.
+ */
+int gb_interface_enable(struct gb_interface *intf)
+{
+	struct gb_control *control;
+	struct gb_bundle *bundle, *tmp;
+	int ret, size;
+	void *manifest;
+
+	ret = gb_interface_read_and_clear_init_status(intf);
+	if (ret) {
+		dev_err(&intf->dev, "failed to clear init status: %d\n", ret);
+		return ret;
+	}
+
+	/* Establish control connection */
+	control = gb_control_create(intf);
+	if (IS_ERR(control)) {
+		dev_err(&intf->dev, "failed to create control device: %ld\n",
+				PTR_ERR(control));
+		return PTR_ERR(control);
+	}
+	intf->control = control;
+
+	ret = gb_control_enable(intf->control);
+	if (ret)
+		goto err_put_control;
+
+	/* Get manifest size using control protocol on CPort */
+	size = gb_control_get_manifest_size_operation(intf);
+	if (size <= 0) {
+		dev_err(&intf->dev, "failed to get manifest size: %d\n", size);
+
+		if (size)
+			ret = size;
+		else
+			ret =  -EINVAL;
+
+		goto err_disable_control;
+	}
+
+	manifest = kmalloc(size, GFP_KERNEL);
+	if (!manifest) {
+		ret = -ENOMEM;
+		goto err_disable_control;
+	}
+
+	/* Get manifest using control protocol on CPort */
+	ret = gb_control_get_manifest_operation(intf, manifest, size);
+	if (ret) {
+		dev_err(&intf->dev, "failed to get manifest: %d\n", ret);
+		goto err_free_manifest;
+	}
+
+	/*
+	 * Parse the manifest and build up our data structures representing
+	 * what's in it.
+	 */
+	if (!gb_manifest_parse(intf, manifest, size)) {
+		dev_err(&intf->dev, "failed to parse manifest\n");
+		ret = -EINVAL;
+		goto err_destroy_bundles;
+	}
+
+	ret = gb_control_get_bundle_versions(intf->control);
+	if (ret)
+		goto err_destroy_bundles;
+
+	ret = gb_timesync_interface_add(intf);
+	if (ret) {
+		dev_err(&intf->dev, "failed to add to timesync: %d\n", ret);
+		goto err_destroy_bundles;
+	}
+
+	/* Register the control device and any bundles */
+	ret = gb_control_add(intf->control);
+	if (ret)
+		goto err_remove_timesync;
+
+	pm_runtime_use_autosuspend(&intf->dev);
+	pm_runtime_get_noresume(&intf->dev);
+	pm_runtime_set_active(&intf->dev);
+	pm_runtime_enable(&intf->dev);
+
+	list_for_each_entry_safe_reverse(bundle, tmp, &intf->bundles, links) {
+		ret = gb_bundle_add(bundle);
+		if (ret) {
+			gb_bundle_destroy(bundle);
+			continue;
+		}
+	}
+
+	kfree(manifest);
+
+	intf->enabled = true;
+
+	pm_runtime_put(&intf->dev);
+
+	trace_gb_interface_enable(intf);
+
+	return 0;
+
+err_remove_timesync:
+	gb_timesync_interface_remove(intf);
+err_destroy_bundles:
+	list_for_each_entry_safe(bundle, tmp, &intf->bundles, links)
+		gb_bundle_destroy(bundle);
+err_free_manifest:
+	kfree(manifest);
+err_disable_control:
+	gb_control_disable(intf->control);
+err_put_control:
+	gb_control_put(intf->control);
+	intf->control = NULL;
+
+	return ret;
+}
+
+/*
+ * Disable an interface and destroy its bundles.
+ *
+ * Locking: Caller holds the interface mutex.
+ */
+void gb_interface_disable(struct gb_interface *intf)
+{
+	struct gb_bundle *bundle;
+	struct gb_bundle *next;
+
+	if (!intf->enabled)
+		return;
+
+	trace_gb_interface_disable(intf);
+
+	pm_runtime_get_sync(&intf->dev);
+
+	/* Set disconnected flag to avoid I/O during connection tear down. */
+	if (intf->quirks & GB_INTERFACE_QUIRK_FORCED_DISABLE)
+		intf->disconnected = true;
+
+	list_for_each_entry_safe(bundle, next, &intf->bundles, links)
+		gb_bundle_destroy(bundle);
+
+	if (!intf->mode_switch && !intf->disconnected)
+		gb_control_interface_deactivate_prepare(intf->control);
+
+	gb_control_del(intf->control);
+	gb_timesync_interface_remove(intf);
+	gb_control_disable(intf->control);
+	gb_control_put(intf->control);
+	intf->control = NULL;
+
+	intf->enabled = false;
+
+	pm_runtime_disable(&intf->dev);
+	pm_runtime_set_suspended(&intf->dev);
+	pm_runtime_dont_use_autosuspend(&intf->dev);
+	pm_runtime_put_noidle(&intf->dev);
+}
+
+/* Enable TimeSync on an Interface control connection. */
+int gb_interface_timesync_enable(struct gb_interface *intf, u8 count,
+				 u64 frame_time, u32 strobe_delay, u32 refclk)
+{
+	return gb_control_timesync_enable(intf->control, count,
+					  frame_time, strobe_delay,
+					  refclk);
+}
+
+/* Disable TimeSync on an Interface control connection. */
+int gb_interface_timesync_disable(struct gb_interface *intf)
+{
+	return gb_control_timesync_disable(intf->control);
+}
+
+/* Transmit the Authoritative FrameTime via an Interface control connection. */
+int gb_interface_timesync_authoritative(struct gb_interface *intf,
+					u64 *frame_time)
+{
+	return gb_control_timesync_authoritative(intf->control,
+						frame_time);
+}
+
+/* Register an interface. */
+int gb_interface_add(struct gb_interface *intf)
+{
+	int ret;
+
+	ret = device_add(&intf->dev);
+	if (ret) {
+		dev_err(&intf->dev, "failed to register interface: %d\n", ret);
+		return ret;
+	}
+
+	trace_gb_interface_add(intf);
+
+	dev_info(&intf->dev, "Interface added (%s)\n",
+			gb_interface_type_string(intf));
+
+	switch (intf->type) {
+	case GB_INTERFACE_TYPE_GREYBUS:
+		dev_info(&intf->dev, "GMP VID=0x%08x, PID=0x%08x\n",
+				intf->vendor_id, intf->product_id);
+		/* fall-through */
+	case GB_INTERFACE_TYPE_UNIPRO:
+		dev_info(&intf->dev, "DDBL1 Manufacturer=0x%08x, Product=0x%08x\n",
+				intf->ddbl1_manufacturer_id,
+				intf->ddbl1_product_id);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+/* Deregister an interface. */
+void gb_interface_del(struct gb_interface *intf)
+{
+	if (device_is_registered(&intf->dev)) {
+		trace_gb_interface_del(intf);
+
+		device_del(&intf->dev);
+		dev_info(&intf->dev, "Interface removed\n");
+	}
+}
+
+void gb_interface_put(struct gb_interface *intf)
+{
+	put_device(&intf->dev);
+}
drivers/greybus/interface.h new file +88 -0

88 lines not on this page · raw message

author maintainer
#33 lore raw

PATCH 3/32 greybus: operations logic

In reply to Greg KH

This is the Greybus operations code, handling the logic for them.

---
 drivers/greybus/operation.c | 1239 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/greybus/operation.h |  210 +++++++
 2 files changed, 1449 insertions(+)
drivers/greybus/operation.c new file +1239 -0
@@ -0,0 +1,1239 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2014-2015 Google Inc.
+ * Copyright 2014-2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include "greybus.h"
+#include "greybus_trace.h"
+
+static struct kmem_cache *gb_operation_cache;
+static struct kmem_cache *gb_message_cache;
+
+/* Workqueue to handle Greybus operation completions. */
+static struct workqueue_struct *gb_operation_completion_wq;
+
+/* Wait queue for synchronous cancellations. */
+static DECLARE_WAIT_QUEUE_HEAD(gb_operation_cancellation_queue);
+
+/*
+ * Protects updates to operation->errno.
+ */
+static DEFINE_SPINLOCK(gb_operations_lock);
+
+static int gb_operation_response_send(struct gb_operation *operation,
+					int errno);
+
+/*
+ * Increment operation active count and add to connection list unless the
+ * connection is going away.
+ *
+ * Caller holds operation reference.
+ */
+static int gb_operation_get_active(struct gb_operation *operation)
+{
+	struct gb_connection *connection = operation->connection;
+	unsigned long flags;
+
+	spin_lock_irqsave(&connection->lock, flags);
+	switch (connection->state) {
+	case GB_CONNECTION_STATE_ENABLED:
+		break;
+	case GB_CONNECTION_STATE_ENABLED_TX:
+		if (gb_operation_is_incoming(operation))
+			goto err_unlock;
+		break;
+	case GB_CONNECTION_STATE_DISCONNECTING:
+		if (!gb_operation_is_core(operation))
+			goto err_unlock;
+		break;
+	default:
+		goto err_unlock;
+	}
+
+	if (operation->active++ == 0)
+		list_add_tail(&operation->links, &connection->operations);
+
+	trace_gb_operation_get_active(operation);
+
+	spin_unlock_irqrestore(&connection->lock, flags);
+
+	return 0;
+
+err_unlock:
+	spin_unlock_irqrestore(&connection->lock, flags);
+
+	return -ENOTCONN;
+}
+
+/* Caller holds operation reference. */
+static void gb_operation_put_active(struct gb_operation *operation)
+{
+	struct gb_connection *connection = operation->connection;
+	unsigned long flags;
+
+	spin_lock_irqsave(&connection->lock, flags);
+
+	trace_gb_operation_put_active(operation);
+
+	if (--operation->active == 0) {
+		list_del(&operation->links);
+		if (atomic_read(&operation->waiters))
+			wake_up(&gb_operation_cancellation_queue);
+	}
+	spin_unlock_irqrestore(&connection->lock, flags);
+}
+
+static bool gb_operation_is_active(struct gb_operation *operation)
+{
+	struct gb_connection *connection = operation->connection;
+	unsigned long flags;
+	bool ret;
+
+	spin_lock_irqsave(&connection->lock, flags);
+	ret = operation->active;
+	spin_unlock_irqrestore(&connection->lock, flags);
+
+	return ret;
+}
+
+/*
+ * Set an operation's result.
+ *
+ * Initially an outgoing operation's errno value is -EBADR.
+ * If no error occurs before sending the request message the only
+ * valid value operation->errno can be set to is -EINPROGRESS,
+ * indicating the request has been (or rather is about to be) sent.
+ * At that point nobody should be looking at the result until the
+ * response arrives.
+ *
+ * The first time the result gets set after the request has been
+ * sent, that result "sticks."  That is, if two concurrent threads
+ * race to set the result, the first one wins.  The return value
+ * tells the caller whether its result was recorded; if not the
+ * caller has nothing more to do.
+ *
+ * The result value -EILSEQ is reserved to signal an implementation
+ * error; if it's ever observed, the code performing the request has
+ * done something fundamentally wrong.  It is an error to try to set
+ * the result to -EBADR, and attempts to do so result in a warning,
+ * and -EILSEQ is used instead.  Similarly, the only valid result
+ * value to set for an operation in initial state is -EINPROGRESS.
+ * Attempts to do otherwise will also record a (successful) -EILSEQ
+ * operation result.
+ */
+static bool gb_operation_result_set(struct gb_operation *operation, int result)
+{
+	unsigned long flags;
+	int prev;
+
+	if (result == -EINPROGRESS) {
+		/*
+		 * -EINPROGRESS is used to indicate the request is
+		 * in flight.  It should be the first result value
+		 * set after the initial -EBADR.  Issue a warning
+		 * and record an implementation error if it's
+		 * set at any other time.
+		 */
+		spin_lock_irqsave(&gb_operations_lock, flags);
+		prev = operation->errno;
+		if (prev == -EBADR)
+			operation->errno = result;
+		else
+			operation->errno = -EILSEQ;
+		spin_unlock_irqrestore(&gb_operations_lock, flags);
+		WARN_ON(prev != -EBADR);
+
+		return true;
+	}
+
+	/*
+	 * The first result value set after a request has been sent
+	 * will be the final result of the operation.  Subsequent
+	 * attempts to set the result are ignored.
+	 *
+	 * Note that -EBADR is a reserved "initial state" result
+	 * value.  Attempts to set this value result in a warning,
+	 * and the result code is set to -EILSEQ instead.
+	 */
+	if (WARN_ON(result == -EBADR))
+		result = -EILSEQ; /* Nobody should be setting -EBADR */
+
+	spin_lock_irqsave(&gb_operations_lock, flags);
+	prev = operation->errno;
+	if (prev == -EINPROGRESS)
+		operation->errno = result;	/* First and final result */
+	spin_unlock_irqrestore(&gb_operations_lock, flags);
+
+	return prev == -EINPROGRESS;
+}
+
+int gb_operation_result(struct gb_operation *operation)
+{
+	int result = operation->errno;
+
+	WARN_ON(result == -EBADR);
+	WARN_ON(result == -EINPROGRESS);
+
+	return result;
+}
+EXPORT_SYMBOL_GPL(gb_operation_result);
+
+/*
+ * Looks up an outgoing operation on a connection and returns a refcounted
+ * pointer if found, or NULL otherwise.
+ */
+static struct gb_operation *
+gb_operation_find_outgoing(struct gb_connection *connection, u16 operation_id)
+{
+	struct gb_operation *operation;
+	unsigned long flags;
+	bool found = false;
+
+	spin_lock_irqsave(&connection->lock, flags);
+	list_for_each_entry(operation, &connection->operations, links)
+		if (operation->id == operation_id &&
+				!gb_operation_is_incoming(operation)) {
+			gb_operation_get(operation);
+			found = true;
+			break;
+		}
+	spin_unlock_irqrestore(&connection->lock, flags);
+
+	return found ? operation : NULL;
+}
+
+static int gb_message_send(struct gb_message *message, gfp_t gfp)
+{
+	struct gb_connection *connection = message->operation->connection;
+
+	trace_gb_message_send(message);
+	return connection->hd->driver->message_send(connection->hd,
+					connection->hd_cport_id,
+					message,
+					gfp);
+}
+
+/*
+ * Cancel a message we have passed to the host device layer to be sent.
+ */
+static void gb_message_cancel(struct gb_message *message)
+{
+	struct gb_host_device *hd = message->operation->connection->hd;
+
+	hd->driver->message_cancel(message);
+}
+
+static void gb_operation_request_handle(struct gb_operation *operation)
+{
+	struct gb_connection *connection = operation->connection;
+	int status;
+	int ret;
+
+	if (connection->handler) {
+		status = connection->handler(operation);
+	} else {
+		dev_err(&connection->hd->dev,
+			"%s: unexpected incoming request of type 0x%02x\n",
+			connection->name, operation->type);
+
+		status = -EPROTONOSUPPORT;
+	}
+
+	ret = gb_operation_response_send(operation, status);
+	if (ret) {
+		dev_err(&connection->hd->dev,
+			"%s: failed to send response %d for type 0x%02x: %d\n",
+			connection->name, status, operation->type, ret);
+		return;
+	}
+}
+
+/*
+ * Process operation work.
+ *
+ * For incoming requests, call the protocol request handler. The operation
+ * result should be -EINPROGRESS at this point.
+ *
+ * For outgoing requests, the operation result value should have
+ * been set before queueing this.  The operation callback function
+ * allows the original requester to know the request has completed
+ * and its result is available.
+ */
+static void gb_operation_work(struct work_struct *work)
+{
+	struct gb_operation *operation;
+
+	operation = container_of(work, struct gb_operation, work);
+
+	if (gb_operation_is_incoming(operation))
+		gb_operation_request_handle(operation);
+	else
+		operation->callback(operation);
+
+	gb_operation_put_active(operation);
+	gb_operation_put(operation);
+}
+
+static void gb_operation_message_init(struct gb_host_device *hd,
+				struct gb_message *message, u16 operation_id,
+				size_t payload_size, u8 type)
+{
+	struct gb_operation_msg_hdr *header;
+
+	header = message->buffer;
+
+	message->header = header;
+	message->payload = payload_size ? header + 1 : NULL;
+	message->payload_size = payload_size;
+
+	/*
+	 * The type supplied for incoming message buffers will be
+	 * GB_REQUEST_TYPE_INVALID. Such buffers will be overwritten by
+	 * arriving data so there's no need to initialize the message header.
+	 */
+	if (type != GB_REQUEST_TYPE_INVALID) {
+		u16 message_size = (u16)(sizeof(*header) + payload_size);
+
+		/*
+		 * For a request, the operation id gets filled in
+		 * when the message is sent.  For a response, it
+		 * will be copied from the request by the caller.
+		 *
+		 * The result field in a request message must be
+		 * zero.  It will be set just prior to sending for
+		 * a response.
+		 */
+		header->size = cpu_to_le16(message_size);
+		header->operation_id = 0;
+		header->type = type;
+		header->result = 0;
+	}
+}
+
+/*
+ * Allocate a message to be used for an operation request or response.
+ * Both types of message contain a common header.  The request message
+ * for an outgoing operation is outbound, as is the response message
+ * for an incoming operation.  The message header for an outbound
+ * message is partially initialized here.
+ *
+ * The headers for inbound messages don't need to be initialized;
+ * they'll be filled in by arriving data.
+ *
+ * Our message buffers have the following layout:
+ *	message header  \_ these combined are
+ *	message payload /  the message size
+ */
+static struct gb_message *
+gb_operation_message_alloc(struct gb_host_device *hd, u8 type,
+				size_t payload_size, gfp_t gfp_flags)
+{
+	struct gb_message *message;
+	struct gb_operation_msg_hdr *header;
+	size_t message_size = payload_size + sizeof(*header);
+
+	if (message_size > hd->buffer_size_max) {
+		dev_warn(&hd->dev, "requested message size too big (%zu > %zu)\n",
+				message_size, hd->buffer_size_max);
+		return NULL;
+	}
+
+	/* Allocate the message structure and buffer. */
+	message = kmem_cache_zalloc(gb_message_cache, gfp_flags);
+	if (!message)
+		return NULL;
+
+	message->buffer = kzalloc(message_size, gfp_flags);
+	if (!message->buffer)
+		goto err_free_message;
+
+	/* Initialize the message.  Operation id is filled in later. */
+	gb_operation_message_init(hd, message, 0, payload_size, type);
+
+	return message;
+
+err_free_message:
+	kmem_cache_free(gb_message_cache, message);
+
+	return NULL;
+}
+
+static void gb_operation_message_free(struct gb_message *message)
+{
+	kfree(message->buffer);
+	kmem_cache_free(gb_message_cache, message);
+}
+
+/*
+ * Map an enum gb_operation_status value (which is represented in a
+ * message as a single byte) to an appropriate Linux negative errno.
+ */
+static int gb_operation_status_map(u8 status)
+{
+	switch (status) {
+	case GB_OP_SUCCESS:
+		return 0;
+	case GB_OP_INTERRUPTED:
+		return -EINTR;
+	case GB_OP_TIMEOUT:
+		return -ETIMEDOUT;
+	case GB_OP_NO_MEMORY:
+		return -ENOMEM;
+	case GB_OP_PROTOCOL_BAD:
+		return -EPROTONOSUPPORT;
+	case GB_OP_OVERFLOW:
+		return -EMSGSIZE;
+	case GB_OP_INVALID:
+		return -EINVAL;
+	case GB_OP_RETRY:
+		return -EAGAIN;
+	case GB_OP_NONEXISTENT:
+		return -ENODEV;
+	case GB_OP_MALFUNCTION:
+		return -EILSEQ;
+	case GB_OP_UNKNOWN_ERROR:
+	default:
+		return -EIO;
+	}
+}
+
+/*
+ * Map a Linux errno value (from operation->errno) into the value
+ * that should represent it in a response message status sent
+ * over the wire.  Returns an enum gb_operation_status value (which
+ * is represented in a message as a single byte).
+ */
+static u8 gb_operation_errno_map(int errno)
+{
+	switch (errno) {
+	case 0:
+		return GB_OP_SUCCESS;
+	case -EINTR:
+		return GB_OP_INTERRUPTED;
+	case -ETIMEDOUT:
+		return GB_OP_TIMEOUT;
+	case -ENOMEM:
+		return GB_OP_NO_MEMORY;
+	case -EPROTONOSUPPORT:
+		return GB_OP_PROTOCOL_BAD;
+	case -EMSGSIZE:
+		return GB_OP_OVERFLOW;	/* Could be underflow too */
+	case -EINVAL:
+		return GB_OP_INVALID;
+	case -EAGAIN:
+		return GB_OP_RETRY;
+	case -EILSEQ:
+		return GB_OP_MALFUNCTION;
+	case -ENODEV:
+		return GB_OP_NONEXISTENT;
+	case -EIO:
+	default:
+		return GB_OP_UNKNOWN_ERROR;
+	}
+}
+
+bool gb_operation_response_alloc(struct gb_operation *operation,
+					size_t response_size, gfp_t gfp)
+{
+	struct gb_host_device *hd = operation->connection->hd;
+	struct gb_operation_msg_hdr *request_header;
+	struct gb_message *response;
+	u8 type;
+
+	type = operation->type | GB_MESSAGE_TYPE_RESPONSE;
+	response = gb_operation_message_alloc(hd, type, response_size, gfp);
+	if (!response)
+		return false;
+	response->operation = operation;
+
+	/*
+	 * Size and type get initialized when the message is
+	 * allocated.  The errno will be set before sending.  All
+	 * that's left is the operation id, which we copy from the
+	 * request message header (as-is, in little-endian order).
+	 */
+	request_header = operation->request->header;
+	response->header->operation_id = request_header->operation_id;
+	operation->response = response;
+
+	return true;
+}
+EXPORT_SYMBOL_GPL(gb_operation_response_alloc);
+
+/*
+ * Create a Greybus operation to be sent over the given connection.
+ * The request buffer will be big enough for a payload of the given
+ * size.
+ *
+ * For outgoing requests, the request message's header will be
+ * initialized with the type of the request and the message size.
+ * Outgoing operations must also specify the response buffer size,
+ * which must be sufficient to hold all expected response data.  The
+ * response message header will eventually be overwritten, so there's
+ * no need to initialize it here.
+ *
+ * Request messages for incoming operations can arrive in interrupt
+ * context, so they must be allocated with GFP_ATOMIC.  In this case
+ * the request buffer will be immediately overwritten, so there is
+ * no need to initialize the message header.  Responsibility for
+ * allocating a response buffer lies with the incoming request
+ * handler for a protocol.  So we don't allocate that here.
+ *
+ * Returns a pointer to the new operation or a null pointer if an
+ * error occurs.
+ */
+static struct gb_operation *
+gb_operation_create_common(struct gb_connection *connection, u8 type,
+				size_t request_size, size_t response_size,
+				unsigned long op_flags, gfp_t gfp_flags)
+{
+	struct gb_host_device *hd = connection->hd;
+	struct gb_operation *operation;
+
+	operation = kmem_cache_zalloc(gb_operation_cache, gfp_flags);
+	if (!operation)
+		return NULL;
+	operation->connection = connection;
+
+	operation->request = gb_operation_message_alloc(hd, type, request_size,
+							gfp_flags);
+	if (!operation->request)
+		goto err_cache;
+	operation->request->operation = operation;
+
+	/* Allocate the response buffer for outgoing operations */
+	if (!(op_flags & GB_OPERATION_FLAG_INCOMING)) {
+		if (!gb_operation_response_alloc(operation, response_size,
+						 gfp_flags)) {
+			goto err_request;
+		}
+	}
+
+	operation->flags = op_flags;
+	operation->type = type;
+	operation->errno = -EBADR;  /* Initial value--means "never set" */
+
+	INIT_WORK(&operation->work, gb_operation_work);
+	init_completion(&operation->completion);
+	kref_init(&operation->kref);
+	atomic_set(&operation->waiters, 0);
+
+	return operation;
+
+err_request:
+	gb_operation_message_free(operation->request);
+err_cache:
+	kmem_cache_free(gb_operation_cache, operation);
+
+	return NULL;
+}
+
+/*
+ * Create a new operation associated with the given connection.  The
+ * request and response sizes provided are the number of bytes
+ * required to hold the request/response payload only.  Both of
+ * these are allowed to be 0.  Note that 0x00 is reserved as an
+ * invalid operation type for all protocols, and this is enforced
+ * here.
+ */
+struct gb_operation *
+gb_operation_create_flags(struct gb_connection *connection,
+				u8 type, size_t request_size,
+				size_t response_size, unsigned long flags,
+				gfp_t gfp)
+{
+	struct gb_operation *operation;
+
+	if (WARN_ON_ONCE(type == GB_REQUEST_TYPE_INVALID))
+		return NULL;
+	if (WARN_ON_ONCE(type & GB_MESSAGE_TYPE_RESPONSE))
+		type &= ~GB_MESSAGE_TYPE_RESPONSE;
+
+	if (WARN_ON_ONCE(flags & ~GB_OPERATION_FLAG_USER_MASK))
+		flags &= GB_OPERATION_FLAG_USER_MASK;
+
+	operation = gb_operation_create_common(connection, type,
+						request_size, response_size,
+						flags, gfp);
+	if (operation)
+		trace_gb_operation_create(operation);
+
+	return operation;
+}
+EXPORT_SYMBOL_GPL(gb_operation_create_flags);
+
+struct gb_operation *
+gb_operation_create_core(struct gb_connection *connection,
+				u8 type, size_t request_size,
+				size_t response_size, unsigned long flags,
+				gfp_t gfp)
+{
+	struct gb_operation *operation;
+
+	flags |= GB_OPERATION_FLAG_CORE;
+
+	operation = gb_operation_create_common(connection, type,
+						request_size, response_size,
+						flags, gfp);
+	if (operation)
+		trace_gb_operation_create_core(operation);
+
+	return operation;
+}
+/* Do not export this function. */
+
+size_t gb_operation_get_payload_size_max(struct gb_connection *connection)
+{
+	struct gb_host_device *hd = connection->hd;
+
+	return hd->buffer_size_max - sizeof(struct gb_operation_msg_hdr);
+}
+EXPORT_SYMBOL_GPL(gb_operation_get_payload_size_max);
+
+static struct gb_operation *
+gb_operation_create_incoming(struct gb_connection *connection, u16 id,
+				u8 type, void *data, size_t size)
+{
+	struct gb_operation *operation;
+	size_t request_size;
+	unsigned long flags = GB_OPERATION_FLAG_INCOMING;
+
+	/* Caller has made sure we at least have a message header. */
+	request_size = size - sizeof(struct gb_operation_msg_hdr);
+
+	if (!id)
+		flags |= GB_OPERATION_FLAG_UNIDIRECTIONAL;
+
+	operation = gb_operation_create_common(connection, type,
+						request_size,
+						GB_REQUEST_TYPE_INVALID,
+						flags, GFP_ATOMIC);
+	if (!operation)
+		return NULL;
+
+	operation->id = id;
+	memcpy(operation->request->header, data, size);
+	trace_gb_operation_create_incoming(operation);
+
+	return operation;
+}
+
+/*
+ * Get an additional reference on an operation.
+ */
+void gb_operation_get(struct gb_operation *operation)
+{
+	kref_get(&operation->kref);
+}
+EXPORT_SYMBOL_GPL(gb_operation_get);
+
+/*
+ * Destroy a previously created operation.
+ */
+static void _gb_operation_destroy(struct kref *kref)
+{
+	struct gb_operation *operation;
+
+	operation = container_of(kref, struct gb_operation, kref);
+
+	trace_gb_operation_destroy(operation);
+
+	if (operation->response)
+		gb_operation_message_free(operation->response);
+	gb_operation_message_free(operation->request);
+
+	kmem_cache_free(gb_operation_cache, operation);
+}
+
+/*
+ * Drop a reference on an operation, and destroy it when the last
+ * one is gone.
+ */
+void gb_operation_put(struct gb_operation *operation)
+{
+	if (WARN_ON(!operation))
+		return;
+
+	kref_put(&operation->kref, _gb_operation_destroy);
+}
+EXPORT_SYMBOL_GPL(gb_operation_put);
+
+/* Tell the requester we're done */
+static void gb_operation_sync_callback(struct gb_operation *operation)
+{
+	complete(&operation->completion);
+}
+
+/**
+ * gb_operation_request_send() - send an operation request message
+ * @operation:	the operation to initiate
+ * @callback:	the operation completion callback
+ * @gfp:	the memory flags to use for any allocations
+ *
+ * The caller has filled in any payload so the request message is ready to go.
+ * The callback function supplied will be called when the response message has
+ * arrived, a unidirectional request has been sent, or the operation is
+ * cancelled, indicating that the operation is complete. The callback function
+ * can fetch the result of the operation using gb_operation_result() if
+ * desired.
+ *
+ * Return: 0 if the request was successfully queued in the host-driver queues,
+ * or a negative errno.
+ */
+int gb_operation_request_send(struct gb_operation *operation,
+				gb_operation_callback callback,
+				gfp_t gfp)
+{
+	struct gb_connection *connection = operation->connection;
+	struct gb_operation_msg_hdr *header;
+	unsigned int cycle;
+	int ret;
+
+	if (gb_connection_is_offloaded(connection))
+		return -EBUSY;
+
+	if (!callback)
+		return -EINVAL;
+
+	/*
+	 * Record the callback function, which is executed in
+	 * non-atomic (workqueue) context when the final result
+	 * of an operation has been set.
+	 */
+	operation->callback = callback;
+
+	/*
+	 * Assign the operation's id, and store it in the request header.
+	 * Zero is a reserved operation id for unidirectional operations.
+	 */
+	if (gb_operation_is_unidirectional(operation)) {
+		operation->id = 0;
+	} else {
+		cycle = (unsigned int)atomic_inc_return(&connection->op_cycle);
+		operation->id = (u16)(cycle % U16_MAX + 1);
+	}
+
+	header = operation->request->header;
+	header->operation_id = cpu_to_le16(operation->id);
+
+	gb_operation_result_set(operation, -EINPROGRESS);
+
+	/*
+	 * Get an extra reference on the operation. It'll be dropped when the
+	 * operation completes.
+	 */
+	gb_operation_get(operation);
+	ret = gb_operation_get_active(operation);
+	if (ret)
+		goto err_put;
+
+	ret = gb_message_send(operation->request, gfp);
+	if (ret)
+		goto err_put_active;
+
+	return 0;
+
+err_put_active:
+	gb_operation_put_active(operation);
+err_put:
+	gb_operation_put(operation);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_operation_request_send);
+
+/*
+ * Send a synchronous operation.  This function is expected to
+ * block, returning only when the response has arrived, (or when an
+ * error is detected.  The return value is the result of the
+ * operation.
+ */
+int gb_operation_request_send_sync_timeout(struct gb_operation *operation,
+						unsigned int timeout)
+{
+	int ret;
+	unsigned long timeout_jiffies;
+
+	ret = gb_operation_request_send(operation, gb_operation_sync_callback,
+					GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	if (timeout)
+		timeout_jiffies = msecs_to_jiffies(timeout);
+	else
+		timeout_jiffies = MAX_SCHEDULE_TIMEOUT;
+
+	ret = wait_for_completion_interruptible_timeout(&operation->completion,
+							timeout_jiffies);
+	if (ret < 0) {
+		/* Cancel the operation if interrupted */
+		gb_operation_cancel(operation, -ECANCELED);
+	} else if (ret == 0) {
+		/* Cancel the operation if op timed out */
+		gb_operation_cancel(operation, -ETIMEDOUT);
+	}
+
+	return gb_operation_result(operation);
+}
+EXPORT_SYMBOL_GPL(gb_operation_request_send_sync_timeout);
+
+/*
+ * Send a response for an incoming operation request.  A non-zero
+ * errno indicates a failed operation.
+ *
+ * If there is any response payload, the incoming request handler is
+ * responsible for allocating the response message.  Otherwise the
+ * it can simply supply the result errno; this function will
+ * allocate the response message if necessary.
+ */
+static int gb_operation_response_send(struct gb_operation *operation,
+					int errno)
+{
+	struct gb_connection *connection = operation->connection;
+	int ret;
+
+	if (!operation->response &&
+			!gb_operation_is_unidirectional(operation)) {
+		if (!gb_operation_response_alloc(operation, 0, GFP_KERNEL))
+			return -ENOMEM;
+	}
+
+	/* Record the result */
+	if (!gb_operation_result_set(operation, errno)) {
+		dev_err(&connection->hd->dev, "request result already set\n");
+		return -EIO;	/* Shouldn't happen */
+	}
+
+	/* Sender of request does not care about response. */
+	if (gb_operation_is_unidirectional(operation))
+		return 0;
+
+	/* Reference will be dropped when message has been sent. */
+	gb_operation_get(operation);
+	ret = gb_operation_get_active(operation);
+	if (ret)
+		goto err_put;
+
+	/* Fill in the response header and send it */
+	operation->response->header->result = gb_operation_errno_map(errno);
+
+	ret = gb_message_send(operation->response, GFP_KERNEL);
+	if (ret)
+		goto err_put_active;
+
+	return 0;
+
+err_put_active:
+	gb_operation_put_active(operation);
+err_put:
+	gb_operation_put(operation);
+
+	return ret;
+}
+
+/*
+ * This function is called when a message send request has completed.
+ */
+void greybus_message_sent(struct gb_host_device *hd,
+					struct gb_message *message, int status)
+{
+	struct gb_operation *operation = message->operation;
+	struct gb_connection *connection = operation->connection;
+
+	/*
+	 * If the message was a response, we just need to drop our
+	 * reference to the operation.  If an error occurred, report
+	 * it.
+	 *
+	 * For requests, if there's no error and the operation in not
+	 * unidirectional, there's nothing more to do until the response
+	 * arrives. If an error occurred attempting to send it, or if the
+	 * operation is unidrectional, record the result of the operation and
+	 * schedule its completion.
+	 */
+	if (message == operation->response) {
+		if (status) {
+			dev_err(&connection->hd->dev,
+				"%s: error sending response 0x%02x: %d\n",
+				connection->name, operation->type, status);
+		}
+
+		gb_operation_put_active(operation);
+		gb_operation_put(operation);
+	} else if (status || gb_operation_is_unidirectional(operation)) {
+		if (gb_operation_result_set(operation, status)) {
+			queue_work(gb_operation_completion_wq,
+					&operation->work);
+		}
+	}
+}
+EXPORT_SYMBOL_GPL(greybus_message_sent);
+
+/*
+ * We've received data on a connection, and it doesn't look like a
+ * response, so we assume it's a request.
+ *
+ * This is called in interrupt context, so just copy the incoming
+ * data into the request buffer and handle the rest via workqueue.
+ */
+static void gb_connection_recv_request(struct gb_connection *connection,
+				const struct gb_operation_msg_hdr *header,
+				void *data, size_t size)
+{
+	struct gb_operation *operation;
+	u16 operation_id;
+	u8 type;
+	int ret;
+
+	operation_id = le16_to_cpu(header->operation_id);
+	type = header->type;
+
+	operation = gb_operation_create_incoming(connection, operation_id,
+						type, data, size);
+	if (!operation) {
+		dev_err(&connection->hd->dev,
+			"%s: can't create incoming operation\n",
+			connection->name);
+		return;
+	}
+
+	ret = gb_operation_get_active(operation);
+	if (ret) {
+		gb_operation_put(operation);
+		return;
+	}
+	trace_gb_message_recv_request(operation->request);
+
+	/*
+	 * The initial reference to the operation will be dropped when the
+	 * request handler returns.
+	 */
+	if (gb_operation_result_set(operation, -EINPROGRESS))
+		queue_work(connection->wq, &operation->work);
+}
+
+/*
+ * We've received data that appears to be an operation response
+ * message.  Look up the operation, and record that we've received
+ * its response.
+ *
+ * This is called in interrupt context, so just copy the incoming
+ * data into the response buffer and handle the rest via workqueue.
+ */
+static void gb_connection_recv_response(struct gb_connection *connection,
+				const struct gb_operation_msg_hdr *header,
+				void *data, size_t size)
+{
+	struct gb_operation *operation;
+	struct gb_message *message;
+	size_t message_size;
+	u16 operation_id;
+	int errno;
+
+	operation_id = le16_to_cpu(header->operation_id);
+
+	if (!operation_id) {
+		dev_err_ratelimited(&connection->hd->dev,
+				"%s: invalid response id 0 received\n",
+				connection->name);
+		return;
+	}
+
+	operation = gb_operation_find_outgoing(connection, operation_id);
+	if (!operation) {
+		dev_err_ratelimited(&connection->hd->dev,
+				"%s: unexpected response id 0x%04x received\n",
+				connection->name, operation_id);
+		return;
+	}
+
+	errno = gb_operation_status_map(header->result);
+	message = operation->response;
+	message_size = sizeof(*header) + message->payload_size;
+	if (!errno && size > message_size) {
+		dev_err_ratelimited(&connection->hd->dev,
+				"%s: malformed response 0x%02x received (%zu > %zu)\n",
+				connection->name, header->type,
+				size, message_size);
+		errno = -EMSGSIZE;
+	} else if (!errno && size < message_size) {
+		if (gb_operation_short_response_allowed(operation)) {
+			message->payload_size = size - sizeof(*header);
+		} else {
+			dev_err_ratelimited(&connection->hd->dev,
+					"%s: short response 0x%02x received (%zu < %zu)\n",
+					connection->name, header->type,
+					size, message_size);
+			errno = -EMSGSIZE;
+		}
+	}
+
+	/* We must ignore the payload if a bad status is returned */
+	if (errno)
+		size = sizeof(*header);
+
+	/* The rest will be handled in work queue context */
+	if (gb_operation_result_set(operation, errno)) {
+		memcpy(message->buffer, data, size);
+
+		trace_gb_message_recv_response(message);
+
+		queue_work(gb_operation_completion_wq, &operation->work);
+	}
+
+	gb_operation_put(operation);
+}
+
+/*
+ * Handle data arriving on a connection.  As soon as we return the
+ * supplied data buffer will be reused (so unless we do something
+ * with, it's effectively dropped).
+ */
+void gb_connection_recv(struct gb_connection *connection,
+				void *data, size_t size)
+{
+	struct gb_operation_msg_hdr header;
+	struct device *dev = &connection->hd->dev;
+	size_t msg_size;
+
+	if (connection->state == GB_CONNECTION_STATE_DISABLED ||
+			gb_connection_is_offloaded(connection)) {
+		dev_warn_ratelimited(dev, "%s: dropping %zu received bytes\n",
+				connection->name, size);
+		return;
+	}
+
+	if (size < sizeof(header)) {
+		dev_err_ratelimited(dev, "%s: short message received\n",
+				connection->name);
+		return;
+	}
+
+	/* Use memcpy as data may be unaligned */
+	memcpy(&header, data, sizeof(header));
+	msg_size = le16_to_cpu(header.size);
+	if (size < msg_size) {
+		dev_err_ratelimited(dev,
+				"%s: incomplete message 0x%04x of type 0x%02x received (%zu < %zu)\n",
+				connection->name,
+				le16_to_cpu(header.operation_id),
+				header.type, size, msg_size);
+		return;		/* XXX Should still complete operation */
+	}
+
+	if (header.type & GB_MESSAGE_TYPE_RESPONSE) {
+		gb_connection_recv_response(connection,	&header, data,
+						msg_size);
+	} else {
+		gb_connection_recv_request(connection, &header, data,
+						msg_size);
+	}
+}
+
+/*
+ * Cancel an outgoing operation synchronously, and record the given error to
+ * indicate why.
+ */
+void gb_operation_cancel(struct gb_operation *operation, int errno)
+{
+	if (WARN_ON(gb_operation_is_incoming(operation)))
+		return;
+
+	if (gb_operation_result_set(operation, errno)) {
+		gb_message_cancel(operation->request);
+		queue_work(gb_operation_completion_wq, &operation->work);
+	}
+	trace_gb_message_cancel_outgoing(operation->request);
+
+	atomic_inc(&operation->waiters);
+	wait_event(gb_operation_cancellation_queue,
+			!gb_operation_is_active(operation));
+	atomic_dec(&operation->waiters);
+}
+EXPORT_SYMBOL_GPL(gb_operation_cancel);
+
+/*
+ * Cancel an incoming operation synchronously. Called during connection tear
+ * down.
+ */
+void gb_operation_cancel_incoming(struct gb_operation *operation, int errno)
+{
+	if (WARN_ON(!gb_operation_is_incoming(operation)))
+		return;
+
+	if (!gb_operation_is_unidirectional(operation)) {
+		/*
+		 * Make sure the request handler has submitted the response
+		 * before cancelling it.
+		 */
+		flush_work(&operation->work);
+		if (!gb_operation_result_set(operation, errno))
+			gb_message_cancel(operation->response);
+	}
+	trace_gb_message_cancel_incoming(operation->response);
+
+	atomic_inc(&operation->waiters);
+	wait_event(gb_operation_cancellation_queue,
+			!gb_operation_is_active(operation));
+	atomic_dec(&operation->waiters);
+}
+
+/**
+ * gb_operation_sync_timeout() - implement a "simple" synchronous operation
+ * @connection: the Greybus connection to send this to
+ * @type: the type of operation to send
+ * @request: pointer to a memory buffer to copy the request from
+ * @request_size: size of @request
+ * @response: pointer to a memory buffer to copy the response to
+ * @response_size: the size of @response.
+ * @timeout: operation timeout in milliseconds
+ *
+ * This function implements a simple synchronous Greybus operation.  It sends
+ * the provided operation request and waits (sleeps) until the corresponding
+ * operation response message has been successfully received, or an error
+ * occurs.  @request and @response are buffers to hold the request and response
+ * data respectively, and if they are not NULL, their size must be specified in
+ * @request_size and @response_size.
+ *
+ * If a response payload is to come back, and @response is not NULL,
+ * @response_size number of bytes will be copied into @response if the operation
+ * is successful.
+ *
+ * If there is an error, the response buffer is left alone.
+ */
+int gb_operation_sync_timeout(struct gb_connection *connection, int type,
+				void *request, int request_size,
+				void *response, int response_size,
+				unsigned int timeout)
+{
+	struct gb_operation *operation;
+	int ret;
+
+	if ((response_size && !response) ||
+	    (request_size && !request))
+		return -EINVAL;
+
+	operation = gb_operation_create(connection, type,
+					request_size, response_size,
+					GFP_KERNEL);
+	if (!operation)
+		return -ENOMEM;
+
+	if (request_size)
+		memcpy(operation->request->payload, request, request_size);
+
+	ret = gb_operation_request_send_sync_timeout(operation, timeout);
+	if (ret) {
+		dev_err(&connection->hd->dev,
+			"%s: synchronous operation id 0x%04x of type 0x%02x failed: %d\n",
+			connection->name, operation->id, type, ret);
+	} else {
+		if (response_size) {
+			memcpy(response, operation->response->payload,
+			       response_size);
+		}
+	}
+
+	gb_operation_put(operation);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_operation_sync_timeout);
+
+/**
+ * gb_operation_unidirectional_timeout() - initiate a unidirectional operation
+ * @connection:		connection to use
+ * @type:		type of operation to send
+ * @request:		memory buffer to copy the request from
+ * @request_size:	size of @request
+ * @timeout:		send timeout in milliseconds
+ *
+ * Initiate a unidirectional operation by sending a request message and
+ * waiting for it to be acknowledged as sent by the host device.
+ *
+ * Note that successful send of a unidirectional operation does not imply that
+ * the request as actually reached the remote end of the connection.
+ */
+int gb_operation_unidirectional_timeout(struct gb_connection *connection,
+				int type, void *request, int request_size,
+				unsigned int timeout)
+{
+	struct gb_operation *operation;
+	int ret;
+
+	if (request_size && !request)
+		return -EINVAL;
+
+	operation = gb_operation_create_flags(connection, type,
+					request_size, 0,
+					GB_OPERATION_FLAG_UNIDIRECTIONAL,
+					GFP_KERNEL);
+	if (!operation)
+		return -ENOMEM;
+
+	if (request_size)
+		memcpy(operation->request->payload, request, request_size);
+
+	ret = gb_operation_request_send_sync_timeout(operation, timeout);
+	if (ret) {
+		dev_err(&connection->hd->dev,
+			"%s: unidirectional operation of type 0x%02x failed: %d\n",
+			connection->name, type, ret);
+	}
+
+	gb_operation_put(operation);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_operation_unidirectional_timeout);
+
+int __init gb_operation_init(void)
+{
+	gb_message_cache = kmem_cache_create("gb_message_cache",
+				sizeof(struct gb_message), 0, 0, NULL);
+	if (!gb_message_cache)
+		return -ENOMEM;
+
+	gb_operation_cache = kmem_cache_create("gb_operation_cache",
+				sizeof(struct gb_operation), 0, 0, NULL);
+	if (!gb_operation_cache)
+		goto err_destroy_message_cache;
+
+	gb_operation_completion_wq = alloc_workqueue("greybus_completion",
+				0, 0);
+	if (!gb_operation_completion_wq)
+		goto err_destroy_operation_cache;
+
+	return 0;
+
+err_destroy_operation_cache:
+	kmem_cache_destroy(gb_operation_cache);
+	gb_operation_cache = NULL;
+err_destroy_message_cache:
+	kmem_cache_destroy(gb_message_cache);
+	gb_message_cache = NULL;
+
+	return -ENOMEM;
+}
+
+void gb_operation_exit(void)
+{
+	destroy_workqueue(gb_operation_completion_wq);
+	gb_operation_completion_wq = NULL;
+	kmem_cache_destroy(gb_operation_cache);
+	gb_operation_cache = NULL;
+	kmem_cache_destroy(gb_message_cache);
+	gb_message_cache = NULL;
+}
drivers/greybus/operation.h new file +210 -0

210 lines not on this page · raw message

author maintainer
#34 lore raw

PATCH 4/32 greybus: host driver framework

In reply to Greg KH

This is the Greybus host driver framework files and logic.

---
 drivers/greybus/hd.c |  257 +++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/greybus/hd.h |   90 +++++++++++++++++
 2 files changed, 347 insertions(+)
drivers/greybus/hd.c new file +257 -0
@@ -0,0 +1,257 @@
+/*
+ * Greybus Host Device
+ *
+ * Copyright 2014-2015 Google Inc.
+ * Copyright 2014-2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include "greybus.h"
+#include "greybus_trace.h"
+
+EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_create);
+EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_release);
+EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_add);
+EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_del);
+EXPORT_TRACEPOINT_SYMBOL_GPL(gb_hd_in);
+EXPORT_TRACEPOINT_SYMBOL_GPL(gb_message_submit);
+
+static struct ida gb_hd_bus_id_map;
+
+int gb_hd_output(struct gb_host_device *hd, void *req, u16 size, u8 cmd,
+		 bool async)
+{
+	if (!hd || !hd->driver || !hd->driver->output)
+		return -EINVAL;
+	return hd->driver->output(hd, req, size, cmd, async);
+}
+EXPORT_SYMBOL_GPL(gb_hd_output);
+
+static ssize_t bus_id_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct gb_host_device *hd = to_gb_host_device(dev);
+
+	return sprintf(buf, "%d\n", hd->bus_id);
+}
+static DEVICE_ATTR_RO(bus_id);
+
+static struct attribute *bus_attrs[] = {
+	&dev_attr_bus_id.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(bus);
+
+int gb_hd_cport_reserve(struct gb_host_device *hd, u16 cport_id)
+{
+	struct ida *id_map = &hd->cport_id_map;
+	int ret;
+
+	ret = ida_simple_get(id_map, cport_id, cport_id + 1, GFP_KERNEL);
+	if (ret < 0) {
+		dev_err(&hd->dev, "failed to reserve cport %u\n", cport_id);
+		return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gb_hd_cport_reserve);
+
+void gb_hd_cport_release_reserved(struct gb_host_device *hd, u16 cport_id)
+{
+	struct ida *id_map = &hd->cport_id_map;
+
+	ida_simple_remove(id_map, cport_id);
+}
+EXPORT_SYMBOL_GPL(gb_hd_cport_release_reserved);
+
+/* Locking: Caller guarantees serialisation */
+int gb_hd_cport_allocate(struct gb_host_device *hd, int cport_id,
+				unsigned long flags)
+{
+	struct ida *id_map = &hd->cport_id_map;
+	int ida_start, ida_end;
+
+	if (hd->driver->cport_allocate)
+		return hd->driver->cport_allocate(hd, cport_id, flags);
+
+	if (cport_id < 0) {
+		ida_start = 0;
+		ida_end = hd->num_cports;
+	} else if (cport_id < hd->num_cports) {
+		ida_start = cport_id;
+		ida_end = cport_id + 1;
+	} else {
+		dev_err(&hd->dev, "cport %d not available\n", cport_id);
+		return -EINVAL;
+	}
+
+	return ida_simple_get(id_map, ida_start, ida_end, GFP_KERNEL);
+}
+
+/* Locking: Caller guarantees serialisation */
+void gb_hd_cport_release(struct gb_host_device *hd, u16 cport_id)
+{
+	if (hd->driver->cport_release) {
+		hd->driver->cport_release(hd, cport_id);
+		return;
+	}
+
+	ida_simple_remove(&hd->cport_id_map, cport_id);
+}
+
+static void gb_hd_release(struct device *dev)
+{
+	struct gb_host_device *hd = to_gb_host_device(dev);
+
+	trace_gb_hd_release(hd);
+
+	if (hd->svc)
+		gb_svc_put(hd->svc);
+	ida_simple_remove(&gb_hd_bus_id_map, hd->bus_id);
+	ida_destroy(&hd->cport_id_map);
+	kfree(hd);
+}
+
+struct device_type greybus_hd_type = {
+	.name		= "greybus_host_device",
+	.release	= gb_hd_release,
+};
+
+struct gb_host_device *gb_hd_create(struct gb_hd_driver *driver,
+					struct device *parent,
+					size_t buffer_size_max,
+					size_t num_cports)
+{
+	struct gb_host_device *hd;
+	int ret;
+
+	/*
+	 * Validate that the driver implements all of the callbacks
+	 * so that we don't have to every time we make them.
+	 */
+	if ((!driver->message_send) || (!driver->message_cancel)) {
+		dev_err(parent, "mandatory hd-callbacks missing\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (buffer_size_max < GB_OPERATION_MESSAGE_SIZE_MIN) {
+		dev_err(parent, "greybus host-device buffers too small\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (num_cports == 0 || num_cports > CPORT_ID_MAX + 1) {
+		dev_err(parent, "Invalid number of CPorts: %zu\n", num_cports);
+		return ERR_PTR(-EINVAL);
+	}
+
+	/*
+	 * Make sure to never allocate messages larger than what the Greybus
+	 * protocol supports.
+	 */
+	if (buffer_size_max > GB_OPERATION_MESSAGE_SIZE_MAX) {
+		dev_warn(parent, "limiting buffer size to %u\n",
+			 GB_OPERATION_MESSAGE_SIZE_MAX);
+		buffer_size_max = GB_OPERATION_MESSAGE_SIZE_MAX;
+	}
+
+	hd = kzalloc(sizeof(*hd) + driver->hd_priv_size, GFP_KERNEL);
+	if (!hd)
+		return ERR_PTR(-ENOMEM);
+
+	ret = ida_simple_get(&gb_hd_bus_id_map, 1, 0, GFP_KERNEL);
+	if (ret < 0) {
+		kfree(hd);
+		return ERR_PTR(ret);
+	}
+	hd->bus_id = ret;
+
+	hd->driver = driver;
+	INIT_LIST_HEAD(&hd->modules);
+	INIT_LIST_HEAD(&hd->connections);
+	ida_init(&hd->cport_id_map);
+	hd->buffer_size_max = buffer_size_max;
+	hd->num_cports = num_cports;
+
+	hd->dev.parent = parent;
+	hd->dev.bus = &greybus_bus_type;
+	hd->dev.type = &greybus_hd_type;
+	hd->dev.groups = bus_groups;
+	hd->dev.dma_mask = hd->dev.parent->dma_mask;
+	device_initialize(&hd->dev);
+	dev_set_name(&hd->dev, "greybus%d", hd->bus_id);
+
+	trace_gb_hd_create(hd);
+
+	hd->svc = gb_svc_create(hd);
+	if (!hd->svc) {
+		dev_err(&hd->dev, "failed to create svc\n");
+		put_device(&hd->dev);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	return hd;
+}
+EXPORT_SYMBOL_GPL(gb_hd_create);
+
+int gb_hd_add(struct gb_host_device *hd)
+{
+	int ret;
+
+	ret = device_add(&hd->dev);
+	if (ret)
+		return ret;
+
+	ret = gb_svc_add(hd->svc);
+	if (ret) {
+		device_del(&hd->dev);
+		return ret;
+	}
+
+	trace_gb_hd_add(hd);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gb_hd_add);
+
+void gb_hd_del(struct gb_host_device *hd)
+{
+	trace_gb_hd_del(hd);
+
+	/*
+	 * Tear down the svc and flush any on-going hotplug processing before
+	 * removing the remaining interfaces.
+	 */
+	gb_svc_del(hd->svc);
+
+	device_del(&hd->dev);
+}
+EXPORT_SYMBOL_GPL(gb_hd_del);
+
+void gb_hd_shutdown(struct gb_host_device *hd)
+{
+	gb_svc_del(hd->svc);
+}
+EXPORT_SYMBOL_GPL(gb_hd_shutdown);
+
+void gb_hd_put(struct gb_host_device *hd)
+{
+	put_device(&hd->dev);
+}
+EXPORT_SYMBOL_GPL(gb_hd_put);
+
+int __init gb_hd_init(void)
+{
+	ida_init(&gb_hd_bus_id_map);
+
+	return 0;
+}
+
+void gb_hd_exit(void)
+{
+	ida_destroy(&gb_hd_bus_id_map);
+}
drivers/greybus/hd.h new file +90 -0

90 lines not on this page · raw message

author maintainer
#35 lore raw

PATCH 5/32 greybus: trace.h

In reply to Greg KH

This implements the Greybus trace macros that the system uses to see what is going on when debugging.

---
 drivers/greybus/greybus_trace.h |  531 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 531 insertions(+)
drivers/greybus/greybus_trace.h new file +531 -0
@@ -0,0 +1,531 @@
+/*
+ * Greybus driver and device API
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM greybus
+
+#if !defined(_TRACE_GREYBUS_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_GREYBUS_H
+
+#include <linux/tracepoint.h>
+
+struct gb_message;
+struct gb_operation;
+struct gb_connection;
+struct gb_bundle;
+struct gb_host_device;
+
+DECLARE_EVENT_CLASS(gb_message,
+
+	TP_PROTO(struct gb_message *message),
+
+	TP_ARGS(message),
+
+	TP_STRUCT__entry(
+		__field(u16, size)
+		__field(u16, operation_id)
+		__field(u8, type)
+		__field(u8, result)
+	),
+
+	TP_fast_assign(
+		__entry->size = le16_to_cpu(message->header->size);
+		__entry->operation_id =
+			le16_to_cpu(message->header->operation_id);
+		__entry->type = message->header->type;
+		__entry->result = message->header->result;
+	),
+
+	TP_printk("size=%hu operation_id=0x%04x type=0x%02x result=0x%02x",
+		  __entry->size, __entry->operation_id,
+		  __entry->type, __entry->result)
+);
+
+#define DEFINE_MESSAGE_EVENT(name)					\
+		DEFINE_EVENT(gb_message, name,				\
+				TP_PROTO(struct gb_message *message),	\
+				TP_ARGS(message))
+
+/*
+ * Occurs immediately before calling a host device's message_send()
+ * method.
+ */
+DEFINE_MESSAGE_EVENT(gb_message_send);
+
+/*
+ * Occurs after an incoming request message has been received
+ */
+DEFINE_MESSAGE_EVENT(gb_message_recv_request);
+
+/*
+ * Occurs after an incoming response message has been received,
+ * after its matching request has been found.
+ */
+DEFINE_MESSAGE_EVENT(gb_message_recv_response);
+
+/*
+ * Occurs after an operation has been canceled, possibly before the
+ * cancellation is complete.
+ */
+DEFINE_MESSAGE_EVENT(gb_message_cancel_outgoing);
+
+/*
+ * Occurs when an incoming request is cancelled; if the response has
+ * been queued for sending, this occurs after it is sent.
+ */
+DEFINE_MESSAGE_EVENT(gb_message_cancel_incoming);
+
+/*
+ * Occurs in the host driver message_send() function just prior to
+ * handing off the data to be processed by hardware.
+ */
+DEFINE_MESSAGE_EVENT(gb_message_submit);
+
+#undef DEFINE_MESSAGE_EVENT
+
+DECLARE_EVENT_CLASS(gb_operation,
+
+	TP_PROTO(struct gb_operation *operation),
+
+	TP_ARGS(operation),
+
+	TP_STRUCT__entry(
+		__field(u16, cport_id)	/* CPort of HD side of connection */
+		__field(u16, id)	/* Operation ID */
+		__field(u8, type)
+		__field(unsigned long, flags)
+		__field(int, active)
+		__field(int, waiters)
+		__field(int, errno)
+	),
+
+	TP_fast_assign(
+		__entry->cport_id = operation->connection->hd_cport_id;
+		__entry->id = operation->id;
+		__entry->type = operation->type;
+		__entry->flags = operation->flags;
+		__entry->active = operation->active;
+		__entry->waiters = atomic_read(&operation->waiters);
+		__entry->errno = operation->errno;
+	),
+
+	TP_printk("id=%04x type=0x%02x cport_id=%04x flags=0x%lx active=%d waiters=%d errno=%d",
+		  __entry->id, __entry->cport_id, __entry->type, __entry->flags,
+		  __entry->active, __entry->waiters, __entry->errno)
+);
+
+#define DEFINE_OPERATION_EVENT(name)					\
+		DEFINE_EVENT(gb_operation, name,			\
+				TP_PROTO(struct gb_operation *operation), \
+				TP_ARGS(operation))
+
+/*
+ * Occurs after a new operation is created for an outgoing request
+ * has been successfully created.
+ */
+DEFINE_OPERATION_EVENT(gb_operation_create);
+
+/*
+ * Occurs after a new core operation has been created.
+ */
+DEFINE_OPERATION_EVENT(gb_operation_create_core);
+
+/*
+ * Occurs after a new operation has been created for an incoming
+ * request has been successfully created and initialized.
+ */
+DEFINE_OPERATION_EVENT(gb_operation_create_incoming);
+
+/*
+ * Occurs when the last reference to an operation has been dropped,
+ * prior to freeing resources.
+ */
+DEFINE_OPERATION_EVENT(gb_operation_destroy);
+
+/*
+ * Occurs when an operation has been marked active, after updating
+ * its active count.
+ */
+DEFINE_OPERATION_EVENT(gb_operation_get_active);
+
+/*
+ * Occurs when an operation has been marked active, before updating
+ * its active count.
+ */
+DEFINE_OPERATION_EVENT(gb_operation_put_active);
+
+#undef DEFINE_OPERATION_EVENT
+
+DECLARE_EVENT_CLASS(gb_connection,
+
+	TP_PROTO(struct gb_connection *connection),
+
+	TP_ARGS(connection),
+
+	TP_STRUCT__entry(
+		__field(int, hd_bus_id)
+		__field(u8, bundle_id)
+		/* name contains "hd_cport_id/intf_id:cport_id" */
+		__dynamic_array(char, name, sizeof(connection->name))
+		__field(enum gb_connection_state, state)
+		__field(unsigned long, flags)
+	),
+
+	TP_fast_assign(
+		__entry->hd_bus_id = connection->hd->bus_id;
+		__entry->bundle_id = connection->bundle ?
+				connection->bundle->id : BUNDLE_ID_NONE;
+		memcpy(__get_str(name), connection->name,
+					sizeof(connection->name));
+		__entry->state = connection->state;
+		__entry->flags = connection->flags;
+	),
+
+	TP_printk("hd_bus_id=%d bundle_id=0x%02x name=\"%s\" state=%u flags=0x%lx",
+		  __entry->hd_bus_id, __entry->bundle_id, __get_str(name),
+		  (unsigned int)__entry->state, __entry->flags)
+);
+
+#define DEFINE_CONNECTION_EVENT(name)					\
+		DEFINE_EVENT(gb_connection, name,			\
+				TP_PROTO(struct gb_connection *connection), \
+				TP_ARGS(connection))
+
+/*
+ * Occurs after a new connection is successfully created.
+ */
+DEFINE_CONNECTION_EVENT(gb_connection_create);
+
+/*
+ * Occurs when the last reference to a connection has been dropped,
+ * before its resources are freed.
+ */
+DEFINE_CONNECTION_EVENT(gb_connection_release);
+
+/*
+ * Occurs when a new reference to connection is added, currently
+ * only when a message over the connection is received.
+ */
+DEFINE_CONNECTION_EVENT(gb_connection_get);
+
+/*
+ * Occurs when a new reference to connection is dropped, after a
+ * a received message is handled, or when the connection is
+ * destroyed.
+ */
+DEFINE_CONNECTION_EVENT(gb_connection_put);
+
+/*
+ * Occurs when a request to enable a connection is made, either for
+ * transmit only, or for both transmit and receive.
+ */
+DEFINE_CONNECTION_EVENT(gb_connection_enable);
+
+/*
+ * Occurs when a request to disable a connection is made, either for
+ * receive only, or for both transmit and receive.  Also occurs when
+ * a request to forcefully disable a connection is made.
+ */
+DEFINE_CONNECTION_EVENT(gb_connection_disable);
+
+#undef DEFINE_CONNECTION_EVENT
+
+DECLARE_EVENT_CLASS(gb_bundle,
+
+	TP_PROTO(struct gb_bundle *bundle),
+
+	TP_ARGS(bundle),
+
+	TP_STRUCT__entry(
+		__field(u8, intf_id)
+		__field(u8, id)
+		__field(u8, class)
+		__field(size_t, num_cports)
+	),
+
+	TP_fast_assign(
+		__entry->intf_id = bundle->intf->interface_id;
+		__entry->id = bundle->id;
+		__entry->class = bundle->class;
+		__entry->num_cports = bundle->num_cports;
+	),
+
+	TP_printk("intf_id=0x%02x id=%02x class=0x%02x num_cports=%zu",
+		  __entry->intf_id, __entry->id, __entry->class,
+		  __entry->num_cports)
+);
+
+#define DEFINE_BUNDLE_EVENT(name)					\
+		DEFINE_EVENT(gb_bundle, name,			\
+				TP_PROTO(struct gb_bundle *bundle), \
+				TP_ARGS(bundle))
+
+/*
+ * Occurs after a new bundle is successfully created.
+ */
+DEFINE_BUNDLE_EVENT(gb_bundle_create);
+
+/*
+ * Occurs when the last reference to a bundle has been dropped,
+ * before its resources are freed.
+ */
+DEFINE_BUNDLE_EVENT(gb_bundle_release);
+
+/*
+ * Occurs when a bundle is added to an interface when the interface
+ * is enabled.
+ */
+DEFINE_BUNDLE_EVENT(gb_bundle_add);
+
+/*
+ * Occurs when a registered bundle gets destroyed, normally at the
+ * time an interface is disabled.
+ */
+DEFINE_BUNDLE_EVENT(gb_bundle_destroy);
+
+#undef DEFINE_BUNDLE_EVENT
+
+DECLARE_EVENT_CLASS(gb_interface,
+
+	TP_PROTO(struct gb_interface *intf),
+
+	TP_ARGS(intf),
+
+	TP_STRUCT__entry(
+		__field(u8, module_id)
+		__field(u8, id)		/* Interface id */
+		__field(u8, device_id)
+		__field(int, disconnected)	/* bool */
+		__field(int, ejected)		/* bool */
+		__field(int, active)		/* bool */
+		__field(int, enabled)		/* bool */
+		__field(int, mode_switch)	/* bool */
+	),
+
+	TP_fast_assign(
+		__entry->module_id = intf->module->module_id;
+		__entry->id = intf->interface_id;
+		__entry->device_id = intf->device_id;
+		__entry->disconnected = intf->disconnected;
+		__entry->ejected = intf->ejected;
+		__entry->active = intf->active;
+		__entry->enabled = intf->enabled;
+		__entry->mode_switch = intf->mode_switch;
+	),
+
+	TP_printk("intf_id=%hhu device_id=%hhu module_id=%hhu D=%d J=%d A=%d E=%d M=%d",
+		__entry->id, __entry->device_id, __entry->module_id,
+		__entry->disconnected, __entry->ejected, __entry->active,
+		__entry->enabled, __entry->mode_switch)
+);
+
+#define DEFINE_INTERFACE_EVENT(name)					\
+		DEFINE_EVENT(gb_interface, name,			\
+				TP_PROTO(struct gb_interface *intf),	\
+				TP_ARGS(intf))
+
+/*
+ * Occurs after a new interface is successfully created.
+ */
+DEFINE_INTERFACE_EVENT(gb_interface_create);
+
+/*
+ * Occurs after the last reference to an interface has been dropped.
+ */
+DEFINE_INTERFACE_EVENT(gb_interface_release);
+
+/*
+ * Occurs after an interface been registerd.
+ */
+DEFINE_INTERFACE_EVENT(gb_interface_add);
+
+/*
+ * Occurs when a registered interface gets deregisterd.
+ */
+DEFINE_INTERFACE_EVENT(gb_interface_del);
+
+/*
+ * Occurs when a registered interface has been successfully
+ * activated.
+ */
+DEFINE_INTERFACE_EVENT(gb_interface_activate);
+
+/*
+ * Occurs when an activated interface is being deactivated.
+ */
+DEFINE_INTERFACE_EVENT(gb_interface_deactivate);
+
+/*
+ * Occurs when an interface has been successfully enabled.
+ */
+DEFINE_INTERFACE_EVENT(gb_interface_enable);
+
+/*
+ * Occurs when an enabled interface is being disabled.
+ */
+DEFINE_INTERFACE_EVENT(gb_interface_disable);
+
+#undef DEFINE_INTERFACE_EVENT
+
+DECLARE_EVENT_CLASS(gb_module,
+
+	TP_PROTO(struct gb_module *module),
+
+	TP_ARGS(module),
+
+	TP_STRUCT__entry(
+		__field(int, hd_bus_id)
+		__field(u8, module_id)
+		__field(size_t, num_interfaces)
+		__field(int, disconnected)	/* bool */
+	),
+
+	TP_fast_assign(
+		__entry->hd_bus_id = module->hd->bus_id;
+		__entry->module_id = module->module_id;
+		__entry->num_interfaces = module->num_interfaces;
+		__entry->disconnected = module->disconnected;
+	),
+
+	TP_printk("hd_bus_id=%d module_id=%hhu num_interfaces=%zu disconnected=%d",
+		__entry->hd_bus_id, __entry->module_id,
+		__entry->num_interfaces, __entry->disconnected)
+);
+
+#define DEFINE_MODULE_EVENT(name)					\
+		DEFINE_EVENT(gb_module, name,				\
+				TP_PROTO(struct gb_module *module),	\
+				TP_ARGS(module))
+
+/*
+ * Occurs after a new module is successfully created, before
+ * creating any of its interfaces.
+ */
+DEFINE_MODULE_EVENT(gb_module_create);
+
+/*
+ * Occurs after the last reference to a module has been dropped.
+ */
+DEFINE_MODULE_EVENT(gb_module_release);
+
+/*
+ * Occurs after a module is successfully created, before registering
+ * any of its interfaces.
+ */
+DEFINE_MODULE_EVENT(gb_module_add);
+
+/*
+ * Occurs when a module is deleted, before deregistering its
+ * interfaces.
+ */
+DEFINE_MODULE_EVENT(gb_module_del);
+
+#undef DEFINE_MODULE_EVENT
+
+DECLARE_EVENT_CLASS(gb_host_device,
+
+	TP_PROTO(struct gb_host_device *hd),
+
+	TP_ARGS(hd),
+
+	TP_STRUCT__entry(
+		__field(int, bus_id)
+		__field(size_t, num_cports)
+		__field(size_t, buffer_size_max)
+	),
+
+	TP_fast_assign(
+		__entry->bus_id = hd->bus_id;
+		__entry->num_cports = hd->num_cports;
+		__entry->buffer_size_max = hd->buffer_size_max;
+	),
+
+	TP_printk("bus_id=%d num_cports=%zu mtu=%zu",
+		__entry->bus_id, __entry->num_cports,
+		__entry->buffer_size_max)
+);
+
+#define DEFINE_HD_EVENT(name)						\
+		DEFINE_EVENT(gb_host_device, name,			\
+				TP_PROTO(struct gb_host_device *hd),	\
+				TP_ARGS(hd))
+
+/*
+ * Occurs after a new host device is successfully created, before
+ * its SVC has been set up.
+ */
+DEFINE_HD_EVENT(gb_hd_create);
+
+/*
+ * Occurs after the last reference to a host device has been
+ * dropped.
+ */
+DEFINE_HD_EVENT(gb_hd_release);
+
+/*
+ * Occurs after a new host device has been added, after the
+ * connection to its SVC has been enabled.
+ */
+DEFINE_HD_EVENT(gb_hd_add);
+
+/*
+ * Occurs when a host device is being disconnected from the AP USB
+ * host controller.
+ */
+DEFINE_HD_EVENT(gb_hd_del);
+
+/*
+ * Occurs when a host device has passed received data to the Greybus
+ * core, after it has been determined it is destined for a valid
+ * CPort.
+ */
+DEFINE_HD_EVENT(gb_hd_in);
+
+#undef DEFINE_HD_EVENT
+
+/*
+ * Occurs on a TimeSync synchronization event or a TimeSync ping event.
+ */
+TRACE_EVENT(gb_timesync_irq,
+
+	TP_PROTO(u8 ping, u8 strobe, u8 count, u64 frame_time),
+
+	TP_ARGS(ping, strobe, count, frame_time),
+
+	TP_STRUCT__entry(
+		__field(u8, ping)
+		__field(u8, strobe)
+		__field(u8, count)
+		__field(u64, frame_time)
+	),
+
+	TP_fast_assign(
+		__entry->ping = ping;
+		__entry->strobe = strobe;
+		__entry->count = count;
+		__entry->frame_time = frame_time;
+	),
+
+	TP_printk("%s %d/%d frame-time %llu\n",
+		  __entry->ping ? "ping" : "strobe", __entry->strobe,
+		  __entry->count, __entry->frame_time)
+);
+
+#endif /* _TRACE_GREYBUS_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+/*
+ * TRACE_INCLUDE_FILE is not needed if the filename and TRACE_SYSTEM are equal
+ */
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE greybus_trace
+#include <trace/define_trace.h>
+
author maintainer
#36 lore raw

PATCH 6/32 greybus: svc driver/watchdog

In reply to Greg KH

This is the Greybus SVC (a switch) driver. It includes a simple watchdog that if it detects that there is no more network activity on the Unipro system, userspace will be told to restart the system.

---
 drivers/greybus/svc.c          | 1486 +++++++++++++++++++++++++++++++++++++++++
 drivers/greybus/svc.h          |  109 +++
 drivers/greybus/svc_watchdog.c |  198 +++++
 3 files changed, 1793 insertions(+)
drivers/greybus/svc.c new file +1486 -0
@@ -0,0 +1,1486 @@
+/*
+ * SVC Greybus driver.
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/workqueue.h>
+
+#include "greybus.h"
+
+#define SVC_INTF_EJECT_TIMEOUT		9000
+#define SVC_INTF_ACTIVATE_TIMEOUT	6000
+#define SVC_INTF_RESUME_TIMEOUT		3000
+
+struct gb_svc_deferred_request {
+	struct work_struct work;
+	struct gb_operation *operation;
+};
+
+
+static int gb_svc_queue_deferred_request(struct gb_operation *operation);
+
+static ssize_t endo_id_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct gb_svc *svc = to_gb_svc(dev);
+
+	return sprintf(buf, "0x%04x\n", svc->endo_id);
+}
+static DEVICE_ATTR_RO(endo_id);
+
+static ssize_t ap_intf_id_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct gb_svc *svc = to_gb_svc(dev);
+
+	return sprintf(buf, "%u\n", svc->ap_intf_id);
+}
+static DEVICE_ATTR_RO(ap_intf_id);
+
+// FIXME
+// This is a hack, we need to do this "right" and clean the interface up
+// properly, not just forcibly yank the thing out of the system and hope for the
+// best.  But for now, people want their modules to come out without having to
+// throw the thing to the ground or get out a screwdriver.
+static ssize_t intf_eject_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t len)
+{
+	struct gb_svc *svc = to_gb_svc(dev);
+	unsigned short intf_id;
+	int ret;
+
+	ret = kstrtou16(buf, 10, &intf_id);
+	if (ret < 0)
+		return ret;
+
+	dev_warn(dev, "Forcibly trying to eject interface %d\n", intf_id);
+
+	ret = gb_svc_intf_eject(svc, intf_id);
+	if (ret < 0)
+		return ret;
+
+	return len;
+}
+static DEVICE_ATTR_WO(intf_eject);
+
+static ssize_t watchdog_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct gb_svc *svc = to_gb_svc(dev);
+
+	return sprintf(buf, "%s\n",
+		       gb_svc_watchdog_enabled(svc) ? "enabled" : "disabled");
+}
+
+static ssize_t watchdog_store(struct device *dev,
+			      struct device_attribute *attr, const char *buf,
+			      size_t len)
+{
+	struct gb_svc *svc = to_gb_svc(dev);
+	int retval;
+	bool user_request;
+
+	retval = strtobool(buf, &user_request);
+	if (retval)
+		return retval;
+
+	if (user_request)
+		retval = gb_svc_watchdog_enable(svc);
+	else
+		retval = gb_svc_watchdog_disable(svc);
+	if (retval)
+		return retval;
+	return len;
+}
+static DEVICE_ATTR_RW(watchdog);
+
+static ssize_t watchdog_action_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct gb_svc *svc = to_gb_svc(dev);
+
+	if (svc->action == GB_SVC_WATCHDOG_BITE_PANIC_KERNEL)
+		return sprintf(buf, "panic\n");
+	else if (svc->action == GB_SVC_WATCHDOG_BITE_RESET_UNIPRO)
+		return sprintf(buf, "reset\n");
+
+	return -EINVAL;
+}
+
+static ssize_t watchdog_action_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t len)
+{
+	struct gb_svc *svc = to_gb_svc(dev);
+
+	if (sysfs_streq(buf, "panic"))
+		svc->action = GB_SVC_WATCHDOG_BITE_PANIC_KERNEL;
+	else if (sysfs_streq(buf, "reset"))
+		svc->action = GB_SVC_WATCHDOG_BITE_RESET_UNIPRO;
+	else
+		return -EINVAL;
+
+	return len;
+}
+static DEVICE_ATTR_RW(watchdog_action);
+
+static int gb_svc_pwrmon_rail_count_get(struct gb_svc *svc, u8 *value)
+{
+	struct gb_svc_pwrmon_rail_count_get_response response;
+	int ret;
+
+	ret = gb_operation_sync(svc->connection,
+				GB_SVC_TYPE_PWRMON_RAIL_COUNT_GET, NULL, 0,
+				&response, sizeof(response));
+	if (ret) {
+		dev_err(&svc->dev, "failed to get rail count: %d\n", ret);
+		return ret;
+	}
+
+	*value = response.rail_count;
+
+	return 0;
+}
+
+static int gb_svc_pwrmon_rail_names_get(struct gb_svc *svc,
+		struct gb_svc_pwrmon_rail_names_get_response *response,
+		size_t bufsize)
+{
+	int ret;
+
+	ret = gb_operation_sync(svc->connection,
+				GB_SVC_TYPE_PWRMON_RAIL_NAMES_GET, NULL, 0,
+				response, bufsize);
+	if (ret) {
+		dev_err(&svc->dev, "failed to get rail names: %d\n", ret);
+		return ret;
+	}
+
+	if (response->status != GB_SVC_OP_SUCCESS) {
+		dev_err(&svc->dev,
+			"SVC error while getting rail names: %u\n",
+			response->status);
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+
+static int gb_svc_pwrmon_sample_get(struct gb_svc *svc, u8 rail_id,
+				    u8 measurement_type, u32 *value)
+{
+	struct gb_svc_pwrmon_sample_get_request request;
+	struct gb_svc_pwrmon_sample_get_response response;
+	int ret;
+
+	request.rail_id = rail_id;
+	request.measurement_type = measurement_type;
+
+	ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_PWRMON_SAMPLE_GET,
+				&request, sizeof(request),
+				&response, sizeof(response));
+	if (ret) {
+		dev_err(&svc->dev, "failed to get rail sample: %d\n", ret);
+		return ret;
+	}
+
+	if (response.result) {
+		dev_err(&svc->dev,
+			"UniPro error while getting rail power sample (%d %d): %d\n",
+			rail_id, measurement_type, response.result);
+		switch (response.result) {
+		case GB_SVC_PWRMON_GET_SAMPLE_INVAL:
+			return -EINVAL;
+		case GB_SVC_PWRMON_GET_SAMPLE_NOSUPP:
+			return -ENOMSG;
+		default:
+			return -EREMOTEIO;
+		}
+	}
+
+	*value = le32_to_cpu(response.measurement);
+
+	return 0;
+}
+
+int gb_svc_pwrmon_intf_sample_get(struct gb_svc *svc, u8 intf_id,
+				  u8 measurement_type, u32 *value)
+{
+	struct gb_svc_pwrmon_intf_sample_get_request request;
+	struct gb_svc_pwrmon_intf_sample_get_response response;
+	int ret;
+
+	request.intf_id = intf_id;
+	request.measurement_type = measurement_type;
+
+	ret = gb_operation_sync(svc->connection,
+				GB_SVC_TYPE_PWRMON_INTF_SAMPLE_GET,
+				&request, sizeof(request),
+				&response, sizeof(response));
+	if (ret) {
+		dev_err(&svc->dev, "failed to get intf sample: %d\n", ret);
+		return ret;
+	}
+
+	if (response.result) {
+		dev_err(&svc->dev,
+			"UniPro error while getting intf power sample (%d %d): %d\n",
+			intf_id, measurement_type, response.result);
+		switch (response.result) {
+		case GB_SVC_PWRMON_GET_SAMPLE_INVAL:
+			return -EINVAL;
+		case GB_SVC_PWRMON_GET_SAMPLE_NOSUPP:
+			return -ENOMSG;
+		default:
+			return -EREMOTEIO;
+		}
+	}
+
+	*value = le32_to_cpu(response.measurement);
+
+	return 0;
+}
+
+static struct attribute *svc_attrs[] = {
+	&dev_attr_endo_id.attr,
+	&dev_attr_ap_intf_id.attr,
+	&dev_attr_intf_eject.attr,
+	&dev_attr_watchdog.attr,
+	&dev_attr_watchdog_action.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(svc);
+
+int gb_svc_intf_device_id(struct gb_svc *svc, u8 intf_id, u8 device_id)
+{
+	struct gb_svc_intf_device_id_request request;
+
+	request.intf_id = intf_id;
+	request.device_id = device_id;
+
+	return gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_DEVICE_ID,
+				 &request, sizeof(request), NULL, 0);
+}
+
+int gb_svc_intf_eject(struct gb_svc *svc, u8 intf_id)
+{
+	struct gb_svc_intf_eject_request request;
+	int ret;
+
+	request.intf_id = intf_id;
+
+	/*
+	 * The pulse width for module release in svc is long so we need to
+	 * increase the timeout so the operation will not return to soon.
+	 */
+	ret = gb_operation_sync_timeout(svc->connection,
+					GB_SVC_TYPE_INTF_EJECT, &request,
+					sizeof(request), NULL, 0,
+					SVC_INTF_EJECT_TIMEOUT);
+	if (ret) {
+		dev_err(&svc->dev, "failed to eject interface %u\n", intf_id);
+		return ret;
+	}
+
+	return 0;
+}
+
+int gb_svc_intf_vsys_set(struct gb_svc *svc, u8 intf_id, bool enable)
+{
+	struct gb_svc_intf_vsys_request request;
+	struct gb_svc_intf_vsys_response response;
+	int type, ret;
+
+	request.intf_id = intf_id;
+
+	if (enable)
+		type = GB_SVC_TYPE_INTF_VSYS_ENABLE;
+	else
+		type = GB_SVC_TYPE_INTF_VSYS_DISABLE;
+
+	ret = gb_operation_sync(svc->connection, type,
+			&request, sizeof(request),
+			&response, sizeof(response));
+	if (ret < 0)
+		return ret;
+	if (response.result_code != GB_SVC_INTF_VSYS_OK)
+		return -EREMOTEIO;
+	return 0;
+}
+
+int gb_svc_intf_refclk_set(struct gb_svc *svc, u8 intf_id, bool enable)
+{
+	struct gb_svc_intf_refclk_request request;
+	struct gb_svc_intf_refclk_response response;
+	int type, ret;
+
+	request.intf_id = intf_id;
+
+	if (enable)
+		type = GB_SVC_TYPE_INTF_REFCLK_ENABLE;
+	else
+		type = GB_SVC_TYPE_INTF_REFCLK_DISABLE;
+
+	ret = gb_operation_sync(svc->connection, type,
+			&request, sizeof(request),
+			&response, sizeof(response));
+	if (ret < 0)
+		return ret;
+	if (response.result_code != GB_SVC_INTF_REFCLK_OK)
+		return -EREMOTEIO;
+	return 0;
+}
+
+int gb_svc_intf_unipro_set(struct gb_svc *svc, u8 intf_id, bool enable)
+{
+	struct gb_svc_intf_unipro_request request;
+	struct gb_svc_intf_unipro_response response;
+	int type, ret;
+
+	request.intf_id = intf_id;
+
+	if (enable)
+		type = GB_SVC_TYPE_INTF_UNIPRO_ENABLE;
+	else
+		type = GB_SVC_TYPE_INTF_UNIPRO_DISABLE;
+
+	ret = gb_operation_sync(svc->connection, type,
+			&request, sizeof(request),
+			&response, sizeof(response));
+	if (ret < 0)
+		return ret;
+	if (response.result_code != GB_SVC_INTF_UNIPRO_OK)
+		return -EREMOTEIO;
+	return 0;
+}
+
+int gb_svc_intf_activate(struct gb_svc *svc, u8 intf_id, u8 *intf_type)
+{
+	struct gb_svc_intf_activate_request request;
+	struct gb_svc_intf_activate_response response;
+	int ret;
+
+	request.intf_id = intf_id;
+
+	ret = gb_operation_sync_timeout(svc->connection,
+			GB_SVC_TYPE_INTF_ACTIVATE,
+			&request, sizeof(request),
+			&response, sizeof(response),
+			SVC_INTF_ACTIVATE_TIMEOUT);
+	if (ret < 0)
+		return ret;
+	if (response.status != GB_SVC_OP_SUCCESS) {
+		dev_err(&svc->dev, "failed to activate interface %u: %u\n",
+				intf_id, response.status);
+		return -EREMOTEIO;
+	}
+
+	*intf_type = response.intf_type;
+
+	return 0;
+}
+
+int gb_svc_intf_resume(struct gb_svc *svc, u8 intf_id)
+{
+	struct gb_svc_intf_resume_request request;
+	struct gb_svc_intf_resume_response response;
+	int ret;
+
+	request.intf_id = intf_id;
+
+	ret = gb_operation_sync_timeout(svc->connection,
+					GB_SVC_TYPE_INTF_RESUME,
+					&request, sizeof(request),
+					&response, sizeof(response),
+					SVC_INTF_RESUME_TIMEOUT);
+	if (ret < 0) {
+		dev_err(&svc->dev, "failed to send interface resume %u: %d\n",
+			intf_id, ret);
+		return ret;
+	}
+
+	if (response.status != GB_SVC_OP_SUCCESS) {
+		dev_err(&svc->dev, "failed to resume interface %u: %u\n",
+			intf_id, response.status);
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+
+int gb_svc_dme_peer_get(struct gb_svc *svc, u8 intf_id, u16 attr, u16 selector,
+			u32 *value)
+{
+	struct gb_svc_dme_peer_get_request request;
+	struct gb_svc_dme_peer_get_response response;
+	u16 result;
+	int ret;
+
+	request.intf_id = intf_id;
+	request.attr = cpu_to_le16(attr);
+	request.selector = cpu_to_le16(selector);
+
+	ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_DME_PEER_GET,
+				&request, sizeof(request),
+				&response, sizeof(response));
+	if (ret) {
+		dev_err(&svc->dev, "failed to get DME attribute (%u 0x%04x %u): %d\n",
+				intf_id, attr, selector, ret);
+		return ret;
+	}
+
+	result = le16_to_cpu(response.result_code);
+	if (result) {
+		dev_err(&svc->dev, "UniPro error while getting DME attribute (%u 0x%04x %u): %u\n",
+				intf_id, attr, selector, result);
+		return -EREMOTEIO;
+	}
+
+	if (value)
+		*value = le32_to_cpu(response.attr_value);
+
+	return 0;
+}
+
+int gb_svc_dme_peer_set(struct gb_svc *svc, u8 intf_id, u16 attr, u16 selector,
+			u32 value)
+{
+	struct gb_svc_dme_peer_set_request request;
+	struct gb_svc_dme_peer_set_response response;
+	u16 result;
+	int ret;
+
+	request.intf_id = intf_id;
+	request.attr = cpu_to_le16(attr);
+	request.selector = cpu_to_le16(selector);
+	request.value = cpu_to_le32(value);
+
+	ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_DME_PEER_SET,
+				&request, sizeof(request),
+				&response, sizeof(response));
+	if (ret) {
+		dev_err(&svc->dev, "failed to set DME attribute (%u 0x%04x %u %u): %d\n",
+				intf_id, attr, selector, value, ret);
+		return ret;
+	}
+
+	result = le16_to_cpu(response.result_code);
+	if (result) {
+		dev_err(&svc->dev, "UniPro error while setting DME attribute (%u 0x%04x %u %u): %u\n",
+				intf_id, attr, selector, value, result);
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+
+int gb_svc_connection_create(struct gb_svc *svc,
+				u8 intf1_id, u16 cport1_id,
+				u8 intf2_id, u16 cport2_id,
+				u8 cport_flags)
+{
+	struct gb_svc_conn_create_request request;
+
+	request.intf1_id = intf1_id;
+	request.cport1_id = cpu_to_le16(cport1_id);
+	request.intf2_id = intf2_id;
+	request.cport2_id = cpu_to_le16(cport2_id);
+	request.tc = 0;		/* TC0 */
+	request.flags = cport_flags;
+
+	return gb_operation_sync(svc->connection, GB_SVC_TYPE_CONN_CREATE,
+				 &request, sizeof(request), NULL, 0);
+}
+
+void gb_svc_connection_destroy(struct gb_svc *svc, u8 intf1_id, u16 cport1_id,
+			       u8 intf2_id, u16 cport2_id)
+{
+	struct gb_svc_conn_destroy_request request;
+	struct gb_connection *connection = svc->connection;
+	int ret;
+
+	request.intf1_id = intf1_id;
+	request.cport1_id = cpu_to_le16(cport1_id);
+	request.intf2_id = intf2_id;
+	request.cport2_id = cpu_to_le16(cport2_id);
+
+	ret = gb_operation_sync(connection, GB_SVC_TYPE_CONN_DESTROY,
+				&request, sizeof(request), NULL, 0);
+	if (ret) {
+		dev_err(&svc->dev, "failed to destroy connection (%u:%u %u:%u): %d\n",
+				intf1_id, cport1_id, intf2_id, cport2_id, ret);
+	}
+}
+
+int gb_svc_timesync_enable(struct gb_svc *svc, u8 count, u64 frame_time,
+			   u32 strobe_delay, u32 refclk)
+{
+	struct gb_connection *connection = svc->connection;
+	struct gb_svc_timesync_enable_request request;
+
+	request.count = count;
+	request.frame_time = cpu_to_le64(frame_time);
+	request.strobe_delay = cpu_to_le32(strobe_delay);
+	request.refclk = cpu_to_le32(refclk);
+	return gb_operation_sync(connection,
+				 GB_SVC_TYPE_TIMESYNC_ENABLE,
+				 &request, sizeof(request), NULL, 0);
+}
+
+int gb_svc_timesync_disable(struct gb_svc *svc)
+{
+	struct gb_connection *connection = svc->connection;
+
+	return gb_operation_sync(connection,
+				 GB_SVC_TYPE_TIMESYNC_DISABLE,
+				 NULL, 0, NULL, 0);
+}
+
+int gb_svc_timesync_authoritative(struct gb_svc *svc, u64 *frame_time)
+{
+	struct gb_connection *connection = svc->connection;
+	struct gb_svc_timesync_authoritative_response response;
+	int ret, i;
+
+	ret = gb_operation_sync(connection,
+				GB_SVC_TYPE_TIMESYNC_AUTHORITATIVE, NULL, 0,
+				&response, sizeof(response));
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < GB_TIMESYNC_MAX_STROBES; i++)
+		frame_time[i] = le64_to_cpu(response.frame_time[i]);
+	return 0;
+}
+
+int gb_svc_timesync_ping(struct gb_svc *svc, u64 *frame_time)
+{
+	struct gb_connection *connection = svc->connection;
+	struct gb_svc_timesync_ping_response response;
+	int ret;
+
+	ret = gb_operation_sync(connection,
+				GB_SVC_TYPE_TIMESYNC_PING,
+				NULL, 0,
+				&response, sizeof(response));
+	if (ret < 0)
+		return ret;
+
+	*frame_time = le64_to_cpu(response.frame_time);
+	return 0;
+}
+
+int gb_svc_timesync_wake_pins_acquire(struct gb_svc *svc, u32 strobe_mask)
+{
+	struct gb_connection *connection = svc->connection;
+	struct gb_svc_timesync_wake_pins_acquire_request request;
+
+	request.strobe_mask = cpu_to_le32(strobe_mask);
+	return gb_operation_sync(connection,
+				 GB_SVC_TYPE_TIMESYNC_WAKE_PINS_ACQUIRE,
+				 &request, sizeof(request),
+				 NULL, 0);
+}
+
+int gb_svc_timesync_wake_pins_release(struct gb_svc *svc)
+{
+	struct gb_connection *connection = svc->connection;
+
+	return gb_operation_sync(connection,
+				 GB_SVC_TYPE_TIMESYNC_WAKE_PINS_RELEASE,
+				 NULL, 0, NULL, 0);
+}
+
+/* Creates bi-directional routes between the devices */
+int gb_svc_route_create(struct gb_svc *svc, u8 intf1_id, u8 dev1_id,
+			       u8 intf2_id, u8 dev2_id)
+{
+	struct gb_svc_route_create_request request;
+
+	request.intf1_id = intf1_id;
+	request.dev1_id = dev1_id;
+	request.intf2_id = intf2_id;
+	request.dev2_id = dev2_id;
+
+	return gb_operation_sync(svc->connection, GB_SVC_TYPE_ROUTE_CREATE,
+				 &request, sizeof(request), NULL, 0);
+}
+
+/* Destroys bi-directional routes between the devices */
+void gb_svc_route_destroy(struct gb_svc *svc, u8 intf1_id, u8 intf2_id)
+{
+	struct gb_svc_route_destroy_request request;
+	int ret;
+
+	request.intf1_id = intf1_id;
+	request.intf2_id = intf2_id;
+
+	ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_ROUTE_DESTROY,
+				&request, sizeof(request), NULL, 0);
+	if (ret) {
+		dev_err(&svc->dev, "failed to destroy route (%u %u): %d\n",
+				intf1_id, intf2_id, ret);
+	}
+}
+
+int gb_svc_intf_set_power_mode(struct gb_svc *svc, u8 intf_id, u8 hs_series,
+			       u8 tx_mode, u8 tx_gear, u8 tx_nlanes,
+			       u8 tx_amplitude, u8 tx_hs_equalizer,
+			       u8 rx_mode, u8 rx_gear, u8 rx_nlanes,
+			       u8 flags, u32 quirks,
+			       struct gb_svc_l2_timer_cfg *local,
+			       struct gb_svc_l2_timer_cfg *remote)
+{
+	struct gb_svc_intf_set_pwrm_request request;
+	struct gb_svc_intf_set_pwrm_response response;
+	int ret;
+	u16 result_code;
+
+	memset(&request, 0, sizeof(request));
+
+	request.intf_id = intf_id;
+	request.hs_series = hs_series;
+	request.tx_mode = tx_mode;
+	request.tx_gear = tx_gear;
+	request.tx_nlanes = tx_nlanes;
+	request.tx_amplitude = tx_amplitude;
+	request.tx_hs_equalizer = tx_hs_equalizer;
+	request.rx_mode = rx_mode;
+	request.rx_gear = rx_gear;
+	request.rx_nlanes = rx_nlanes;
+	request.flags = flags;
+	request.quirks = cpu_to_le32(quirks);
+	if (local)
+		request.local_l2timerdata = *local;
+	if (remote)
+		request.remote_l2timerdata = *remote;
+
+	ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_SET_PWRM,
+				&request, sizeof(request),
+				&response, sizeof(response));
+	if (ret < 0)
+		return ret;
+
+	result_code = response.result_code;
+	if (result_code != GB_SVC_SETPWRM_PWR_LOCAL) {
+		dev_err(&svc->dev, "set power mode = %d\n", result_code);
+		return -EIO;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gb_svc_intf_set_power_mode);
+
+int gb_svc_intf_set_power_mode_hibernate(struct gb_svc *svc, u8 intf_id)
+{
+	struct gb_svc_intf_set_pwrm_request request;
+	struct gb_svc_intf_set_pwrm_response response;
+	int ret;
+	u16 result_code;
+
+	memset(&request, 0, sizeof(request));
+
+	request.intf_id = intf_id;
+	request.hs_series = GB_SVC_UNIPRO_HS_SERIES_A;
+	request.tx_mode = GB_SVC_UNIPRO_HIBERNATE_MODE;
+	request.rx_mode = GB_SVC_UNIPRO_HIBERNATE_MODE;
+
+	ret = gb_operation_sync(svc->connection, GB_SVC_TYPE_INTF_SET_PWRM,
+				&request, sizeof(request),
+				&response, sizeof(response));
+	if (ret < 0) {
+		dev_err(&svc->dev,
+			"failed to send set power mode operation to interface %u: %d\n",
+			intf_id, ret);
+		return ret;
+	}
+
+	result_code = response.result_code;
+	if (result_code != GB_SVC_SETPWRM_PWR_OK) {
+		dev_err(&svc->dev,
+			"failed to hibernate the link for interface %u: %u\n",
+			intf_id, result_code);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int gb_svc_ping(struct gb_svc *svc)
+{
+	return gb_operation_sync_timeout(svc->connection, GB_SVC_TYPE_PING,
+					 NULL, 0, NULL, 0,
+					 GB_OPERATION_TIMEOUT_DEFAULT * 2);
+}
+
+static int gb_svc_version_request(struct gb_operation *op)
+{
+	struct gb_connection *connection = op->connection;
+	struct gb_svc *svc = gb_connection_get_data(connection);
+	struct gb_svc_version_request *request;
+	struct gb_svc_version_response *response;
+
+	if (op->request->payload_size < sizeof(*request)) {
+		dev_err(&svc->dev, "short version request (%zu < %zu)\n",
+				op->request->payload_size,
+				sizeof(*request));
+		return -EINVAL;
+	}
+
+	request = op->request->payload;
+
+	if (request->major > GB_SVC_VERSION_MAJOR) {
+		dev_warn(&svc->dev, "unsupported major version (%u > %u)\n",
+				request->major, GB_SVC_VERSION_MAJOR);
+		return -ENOTSUPP;
+	}
+
+	svc->protocol_major = request->major;
+	svc->protocol_minor = request->minor;
+
+	if (!gb_operation_response_alloc(op, sizeof(*response), GFP_KERNEL))
+		return -ENOMEM;
+
+	response = op->response->payload;
+	response->major = svc->protocol_major;
+	response->minor = svc->protocol_minor;
+
+	return 0;
+}
+
+static ssize_t pwr_debugfs_voltage_read(struct file *file, char __user *buf,
+					size_t len, loff_t *offset)
+{
+	struct svc_debugfs_pwrmon_rail *pwrmon_rails = file->f_inode->i_private;
+	struct gb_svc *svc = pwrmon_rails->svc;
+	int ret, desc;
+	u32 value;
+	char buff[16];
+
+	ret = gb_svc_pwrmon_sample_get(svc, pwrmon_rails->id,
+				       GB_SVC_PWRMON_TYPE_VOL, &value);
+	if (ret) {
+		dev_err(&svc->dev,
+			"failed to get voltage sample %u: %d\n",
+			pwrmon_rails->id, ret);
+		return ret;
+	}
+
+	desc = scnprintf(buff, sizeof(buff), "%u\n", value);
+
+	return simple_read_from_buffer(buf, len, offset, buff, desc);
+}
+
+static ssize_t pwr_debugfs_current_read(struct file *file, char __user *buf,
+					size_t len, loff_t *offset)
+{
+	struct svc_debugfs_pwrmon_rail *pwrmon_rails = file->f_inode->i_private;
+	struct gb_svc *svc = pwrmon_rails->svc;
+	int ret, desc;
+	u32 value;
+	char buff[16];
+
+	ret = gb_svc_pwrmon_sample_get(svc, pwrmon_rails->id,
+				       GB_SVC_PWRMON_TYPE_CURR, &value);
+	if (ret) {
+		dev_err(&svc->dev,
+			"failed to get current sample %u: %d\n",
+			pwrmon_rails->id, ret);
+		return ret;
+	}
+
+	desc = scnprintf(buff, sizeof(buff), "%u\n", value);
+
+	return simple_read_from_buffer(buf, len, offset, buff, desc);
+}
+
+static ssize_t pwr_debugfs_power_read(struct file *file, char __user *buf,
+				      size_t len, loff_t *offset)
+{
+	struct svc_debugfs_pwrmon_rail *pwrmon_rails = file->f_inode->i_private;
+	struct gb_svc *svc = pwrmon_rails->svc;
+	int ret, desc;
+	u32 value;
+	char buff[16];
+
+	ret = gb_svc_pwrmon_sample_get(svc, pwrmon_rails->id,
+				       GB_SVC_PWRMON_TYPE_PWR, &value);
+	if (ret) {
+		dev_err(&svc->dev, "failed to get power sample %u: %d\n",
+			pwrmon_rails->id, ret);
+		return ret;
+	}
+
+	desc = scnprintf(buff, sizeof(buff), "%u\n", value);
+
+	return simple_read_from_buffer(buf, len, offset, buff, desc);
+}
+
+static const struct file_operations pwrmon_debugfs_voltage_fops = {
+	.read		= pwr_debugfs_voltage_read,
+};
+
+static const struct file_operations pwrmon_debugfs_current_fops = {
+	.read		= pwr_debugfs_current_read,
+};
+
+static const struct file_operations pwrmon_debugfs_power_fops = {
+	.read		= pwr_debugfs_power_read,
+};
+
+static void gb_svc_pwrmon_debugfs_init(struct gb_svc *svc)
+{
+	int i;
+	size_t bufsize;
+	struct dentry *dent;
+	struct gb_svc_pwrmon_rail_names_get_response *rail_names;
+	u8 rail_count;
+
+	dent = debugfs_create_dir("pwrmon", svc->debugfs_dentry);
+	if (IS_ERR_OR_NULL(dent))
+		return;
+
+	if (gb_svc_pwrmon_rail_count_get(svc, &rail_count))
+		goto err_pwrmon_debugfs;
+
+	if (!rail_count || rail_count > GB_SVC_PWRMON_MAX_RAIL_COUNT)
+		goto err_pwrmon_debugfs;
+
+	bufsize = sizeof(*rail_names) +
+		GB_SVC_PWRMON_RAIL_NAME_BUFSIZE * rail_count;
+
+	rail_names = kzalloc(bufsize, GFP_KERNEL);
+	if (!rail_names)
+		goto err_pwrmon_debugfs;
+
+	svc->pwrmon_rails = kcalloc(rail_count, sizeof(*svc->pwrmon_rails),
+				    GFP_KERNEL);
+	if (!svc->pwrmon_rails)
+		goto err_pwrmon_debugfs_free;
+
+	if (gb_svc_pwrmon_rail_names_get(svc, rail_names, bufsize))
+		goto err_pwrmon_debugfs_free;
+
+	for (i = 0; i < rail_count; i++) {
+		struct dentry *dir;
+		struct svc_debugfs_pwrmon_rail *rail = &svc->pwrmon_rails[i];
+		char fname[GB_SVC_PWRMON_RAIL_NAME_BUFSIZE];
+
+		snprintf(fname, sizeof(fname), "%s",
+			 (char *)&rail_names->name[i]);
+
+		rail->id = i;
+		rail->svc = svc;
+
+		dir = debugfs_create_dir(fname, dent);
+		debugfs_create_file("voltage_now", S_IRUGO, dir, rail,
+				    &pwrmon_debugfs_voltage_fops);
+		debugfs_create_file("current_now", S_IRUGO, dir, rail,
+				    &pwrmon_debugfs_current_fops);
+		debugfs_create_file("power_now", S_IRUGO, dir, rail,
+				    &pwrmon_debugfs_power_fops);
+	}
+
+	kfree(rail_names);
+	return;
+
+err_pwrmon_debugfs_free:
+	kfree(rail_names);
+	kfree(svc->pwrmon_rails);
+	svc->pwrmon_rails = NULL;
+
+err_pwrmon_debugfs:
+	debugfs_remove(dent);
+}
+
+static void gb_svc_debugfs_init(struct gb_svc *svc)
+{
+	svc->debugfs_dentry = debugfs_create_dir(dev_name(&svc->dev),
+						 gb_debugfs_get());
+	gb_svc_pwrmon_debugfs_init(svc);
+}
+
+static void gb_svc_debugfs_exit(struct gb_svc *svc)
+{
+	debugfs_remove_recursive(svc->debugfs_dentry);
+	kfree(svc->pwrmon_rails);
+	svc->pwrmon_rails = NULL;
+}
+
+static int gb_svc_hello(struct gb_operation *op)
+{
+	struct gb_connection *connection = op->connection;
+	struct gb_svc *svc = gb_connection_get_data(connection);
+	struct gb_svc_hello_request *hello_request;
+	int ret;
+
+	if (op->request->payload_size < sizeof(*hello_request)) {
+		dev_warn(&svc->dev, "short hello request (%zu < %zu)\n",
+				op->request->payload_size,
+				sizeof(*hello_request));
+		return -EINVAL;
+	}
+
+	hello_request = op->request->payload;
+	svc->endo_id = le16_to_cpu(hello_request->endo_id);
+	svc->ap_intf_id = hello_request->interface_id;
+
+	ret = device_add(&svc->dev);
+	if (ret) {
+		dev_err(&svc->dev, "failed to register svc device: %d\n", ret);
+		return ret;
+	}
+
+	ret = gb_svc_watchdog_create(svc);
+	if (ret) {
+		dev_err(&svc->dev, "failed to create watchdog: %d\n", ret);
+		goto err_unregister_device;
+	}
+
+	gb_svc_debugfs_init(svc);
+
+	ret = gb_timesync_svc_add(svc);
+	if (ret) {
+		dev_err(&svc->dev, "failed to add SVC to timesync: %d\n", ret);
+		gb_svc_debugfs_exit(svc);
+		goto err_unregister_device;
+	}
+
+	return gb_svc_queue_deferred_request(op);
+
+err_unregister_device:
+	gb_svc_watchdog_destroy(svc);
+	device_del(&svc->dev);
+	return ret;
+}
+
+static struct gb_interface *gb_svc_interface_lookup(struct gb_svc *svc,
+							u8 intf_id)
+{
+	struct gb_host_device *hd = svc->hd;
+	struct gb_module *module;
+	size_t num_interfaces;
+	u8 module_id;
+
+	list_for_each_entry(module, &hd->modules, hd_node) {
+		module_id = module->module_id;
+		num_interfaces = module->num_interfaces;
+
+		if (intf_id >= module_id &&
+				intf_id < module_id + num_interfaces) {
+			return module->interfaces[intf_id - module_id];
+		}
+	}
+
+	return NULL;
+}
+
+static struct gb_module *gb_svc_module_lookup(struct gb_svc *svc, u8 module_id)
+{
+	struct gb_host_device *hd = svc->hd;
+	struct gb_module *module;
+
+	list_for_each_entry(module, &hd->modules, hd_node) {
+		if (module->module_id == module_id)
+			return module;
+	}
+
+	return NULL;
+}
+
+static void gb_svc_process_hello_deferred(struct gb_operation *operation)
+{
+	struct gb_connection *connection = operation->connection;
+	struct gb_svc *svc = gb_connection_get_data(connection);
+	int ret;
+
+	/*
+	 * XXX This is a hack/work-around to reconfigure the APBridgeA-Switch
+	 * link to PWM G2, 1 Lane, Slow Auto, so that it has sufficient
+	 * bandwidth for 3 audio streams plus boot-over-UniPro of a hot-plugged
+	 * module.
+	 *
+	 * The code should be removed once SW-2217, Heuristic for UniPro
+	 * Power Mode Changes is resolved.
+	 */
+	ret = gb_svc_intf_set_power_mode(svc, svc->ap_intf_id,
+					GB_SVC_UNIPRO_HS_SERIES_A,
+					GB_SVC_UNIPRO_SLOW_AUTO_MODE,
+					2, 1,
+					GB_SVC_SMALL_AMPLITUDE, GB_SVC_NO_DE_EMPHASIS,
+					GB_SVC_UNIPRO_SLOW_AUTO_MODE,
+					2, 1,
+					0, 0,
+					NULL, NULL);
+
+	if (ret)
+		dev_warn(&svc->dev,
+			"power mode change failed on AP to switch link: %d\n",
+			ret);
+}
+
+static void gb_svc_process_module_inserted(struct gb_operation *operation)
+{
+	struct gb_svc_module_inserted_request *request;
+	struct gb_connection *connection = operation->connection;
+	struct gb_svc *svc = gb_connection_get_data(connection);
+	struct gb_host_device *hd = svc->hd;
+	struct gb_module *module;
+	size_t num_interfaces;
+	u8 module_id;
+	u16 flags;
+	int ret;
+
+	/* The request message size has already been verified. */
+	request = operation->request->payload;
+	module_id = request->primary_intf_id;
+	num_interfaces = request->intf_count;
+	flags = le16_to_cpu(request->flags);
+
+	dev_dbg(&svc->dev, "%s - id = %u, num_interfaces = %zu, flags = 0x%04x\n",
+			__func__, module_id, num_interfaces, flags);
+
+	if (flags & GB_SVC_MODULE_INSERTED_FLAG_NO_PRIMARY) {
+		dev_warn(&svc->dev, "no primary interface detected on module %u\n",
+				module_id);
+	}
+
+	module = gb_svc_module_lookup(svc, module_id);
+	if (module) {
+		dev_warn(&svc->dev, "unexpected module-inserted event %u\n",
+				module_id);
+		return;
+	}
+
+	module = gb_module_create(hd, module_id, num_interfaces);
+	if (!module) {
+		dev_err(&svc->dev, "failed to create module\n");
+		return;
+	}
+
+	ret = gb_module_add(module);
+	if (ret) {
+		gb_module_put(module);
+		return;
+	}
+
+	list_add(&module->hd_node, &hd->modules);
+}
+
+static void gb_svc_process_module_removed(struct gb_operation *operation)
+{
+	struct gb_svc_module_removed_request *request;
+	struct gb_connection *connection = operation->connection;
+	struct gb_svc *svc = gb_connection_get_data(connection);
+	struct gb_module *module;
+	u8 module_id;
+
+	/* The request message size has already been verified. */
+	request = operation->request->payload;
+	module_id = request->primary_intf_id;
+
+	dev_dbg(&svc->dev, "%s - id = %u\n", __func__, module_id);
+
+	module = gb_svc_module_lookup(svc, module_id);
+	if (!module) {
+		dev_warn(&svc->dev, "unexpected module-removed event %u\n",
+				module_id);
+		return;
+	}
+
+	module->disconnected = true;
+
+	gb_module_del(module);
+	list_del(&module->hd_node);
+	gb_module_put(module);
+}
+
+static void gb_svc_process_intf_oops(struct gb_operation *operation)
+{
+	struct gb_svc_intf_oops_request *request;
+	struct gb_connection *connection = operation->connection;
+	struct gb_svc *svc = gb_connection_get_data(connection);
+	struct gb_interface *intf;
+	u8 intf_id;
+	u8 reason;
+
+	/* The request message size has already been verified. */
+	request = operation->request->payload;
+	intf_id = request->intf_id;
+	reason = request->reason;
+
+	intf = gb_svc_interface_lookup(svc, intf_id);
+	if (!intf) {
+		dev_warn(&svc->dev, "unexpected interface-oops event %u\n",
+			 intf_id);
+		return;
+	}
+
+	dev_info(&svc->dev, "Deactivating interface %u, interface oops reason = %u\n",
+		 intf_id, reason);
+
+	mutex_lock(&intf->mutex);
+	intf->disconnected = true;
+	gb_interface_disable(intf);
+	gb_interface_deactivate(intf);
+	mutex_unlock(&intf->mutex);
+}
+
+static void gb_svc_process_intf_mailbox_event(struct gb_operation *operation)
+{
+	struct gb_svc_intf_mailbox_event_request *request;
+	struct gb_connection *connection = operation->connection;
+	struct gb_svc *svc = gb_connection_get_data(connection);
+	struct gb_interface *intf;
+	u8 intf_id;
+	u16 result_code;
+	u32 mailbox;
+
+	/* The request message size has already been verified. */
+	request = operation->request->payload;
+	intf_id = request->intf_id;
+	result_code = le16_to_cpu(request->result_code);
+	mailbox = le32_to_cpu(request->mailbox);
+
+	dev_dbg(&svc->dev, "%s - id = %u, result = 0x%04x, mailbox = 0x%08x\n",
+			__func__, intf_id, result_code, mailbox);
+
+	intf = gb_svc_interface_lookup(svc, intf_id);
+	if (!intf) {
+		dev_warn(&svc->dev, "unexpected mailbox event %u\n", intf_id);
+		return;
+	}
+
+	gb_interface_mailbox_event(intf, result_code, mailbox);
+}
+
+static void gb_svc_process_deferred_request(struct work_struct *work)
+{
+	struct gb_svc_deferred_request *dr;
+	struct gb_operation *operation;
+	struct gb_svc *svc;
+	u8 type;
+
+	dr = container_of(work, struct gb_svc_deferred_request, work);
+	operation = dr->operation;
+	svc = gb_connection_get_data(operation->connection);
+	type = operation->request->header->type;
+
+	switch (type) {
+	case GB_SVC_TYPE_SVC_HELLO:
+		gb_svc_process_hello_deferred(operation);
+		break;
+	case GB_SVC_TYPE_MODULE_INSERTED:
+		gb_svc_process_module_inserted(operation);
+		break;
+	case GB_SVC_TYPE_MODULE_REMOVED:
+		gb_svc_process_module_removed(operation);
+		break;
+	case GB_SVC_TYPE_INTF_MAILBOX_EVENT:
+		gb_svc_process_intf_mailbox_event(operation);
+		break;
+	case GB_SVC_TYPE_INTF_OOPS:
+		gb_svc_process_intf_oops(operation);
+		break;
+	default:
+		dev_err(&svc->dev, "bad deferred request type: 0x%02x\n", type);
+	}
+
+	gb_operation_put(operation);
+	kfree(dr);
+}
+
+static int gb_svc_queue_deferred_request(struct gb_operation *operation)
+{
+	struct gb_svc *svc = gb_connection_get_data(operation->connection);
+	struct gb_svc_deferred_request *dr;
+
+	dr = kmalloc(sizeof(*dr), GFP_KERNEL);
+	if (!dr)
+		return -ENOMEM;
+
+	gb_operation_get(operation);
+
+	dr->operation = operation;
+	INIT_WORK(&dr->work, gb_svc_process_deferred_request);
+
+	queue_work(svc->wq, &dr->work);
+
+	return 0;
+}
+
+static int gb_svc_intf_reset_recv(struct gb_operation *op)
+{
+	struct gb_svc *svc = gb_connection_get_data(op->connection);
+	struct gb_message *request = op->request;
+	struct gb_svc_intf_reset_request *reset;
+	u8 intf_id;
+
+	if (request->payload_size < sizeof(*reset)) {
+		dev_warn(&svc->dev, "short reset request received (%zu < %zu)\n",
+				request->payload_size, sizeof(*reset));
+		return -EINVAL;
+	}
+	reset = request->payload;
+
+	intf_id = reset->intf_id;
+
+	/* FIXME Reset the interface here */
+
+	return 0;
+}
+
+static int gb_svc_module_inserted_recv(struct gb_operation *op)
+{
+	struct gb_svc *svc = gb_connection_get_data(op->connection);
+	struct gb_svc_module_inserted_request *request;
+
+	if (op->request->payload_size < sizeof(*request)) {
+		dev_warn(&svc->dev, "short module-inserted request received (%zu < %zu)\n",
+				op->request->payload_size, sizeof(*request));
+		return -EINVAL;
+	}
+
+	request = op->request->payload;
+
+	dev_dbg(&svc->dev, "%s - id = %u\n", __func__,
+			request->primary_intf_id);
+
+	return gb_svc_queue_deferred_request(op);
+}
+
+static int gb_svc_module_removed_recv(struct gb_operation *op)
+{
+	struct gb_svc *svc = gb_connection_get_data(op->connection);
+	struct gb_svc_module_removed_request *request;
+
+	if (op->request->payload_size < sizeof(*request)) {
+		dev_warn(&svc->dev, "short module-removed request received (%zu < %zu)\n",
+				op->request->payload_size, sizeof(*request));
+		return -EINVAL;
+	}
+
+	request = op->request->payload;
+
+	dev_dbg(&svc->dev, "%s - id = %u\n", __func__,
+			request->primary_intf_id);
+
+	return gb_svc_queue_deferred_request(op);
+}
+
+static int gb_svc_intf_oops_recv(struct gb_operation *op)
+{
+	struct gb_svc *svc = gb_connection_get_data(op->connection);
+	struct gb_svc_intf_oops_request *request;
+
+	if (op->request->payload_size < sizeof(*request)) {
+		dev_warn(&svc->dev, "short intf-oops request received (%zu < %zu)\n",
+			 op->request->payload_size, sizeof(*request));
+		return -EINVAL;
+	}
+
+	return gb_svc_queue_deferred_request(op);
+}
+
+static int gb_svc_intf_mailbox_event_recv(struct gb_operation *op)
+{
+	struct gb_svc *svc = gb_connection_get_data(op->connection);
+	struct gb_svc_intf_mailbox_event_request *request;
+
+	if (op->request->payload_size < sizeof(*request)) {
+		dev_warn(&svc->dev, "short mailbox request received (%zu < %zu)\n",
+				op->request->payload_size, sizeof(*request));
+		return -EINVAL;
+	}
+
+	request = op->request->payload;
+
+	dev_dbg(&svc->dev, "%s - id = %u\n", __func__, request->intf_id);
+
+	return gb_svc_queue_deferred_request(op);
+}
+
+static int gb_svc_request_handler(struct gb_operation *op)
+{
+	struct gb_connection *connection = op->connection;
+	struct gb_svc *svc = gb_connection_get_data(connection);
+	u8 type = op->type;
+	int ret = 0;
+
+	/*
+	 * SVC requests need to follow a specific order (at least initially) and
+	 * below code takes care of enforcing that. The expected order is:
+	 * - PROTOCOL_VERSION
+	 * - SVC_HELLO
+	 * - Any other request, but the earlier two.
+	 *
+	 * Incoming requests are guaranteed to be serialized and so we don't
+	 * need to protect 'state' for any races.
+	 */
+	switch (type) {
+	case GB_SVC_TYPE_PROTOCOL_VERSION:
+		if (svc->state != GB_SVC_STATE_RESET)
+			ret = -EINVAL;
+		break;
+	case GB_SVC_TYPE_SVC_HELLO:
+		if (svc->state != GB_SVC_STATE_PROTOCOL_VERSION)
+			ret = -EINVAL;
+		break;
+	default:
+		if (svc->state != GB_SVC_STATE_SVC_HELLO)
+			ret = -EINVAL;
+		break;
+	}
+
+	if (ret) {
+		dev_warn(&svc->dev, "unexpected request 0x%02x received (state %u)\n",
+				type, svc->state);
+		return ret;
+	}
+
+	switch (type) {
+	case GB_SVC_TYPE_PROTOCOL_VERSION:
+		ret = gb_svc_version_request(op);
+		if (!ret)
+			svc->state = GB_SVC_STATE_PROTOCOL_VERSION;
+		return ret;
+	case GB_SVC_TYPE_SVC_HELLO:
+		ret = gb_svc_hello(op);
+		if (!ret)
+			svc->state = GB_SVC_STATE_SVC_HELLO;
+		return ret;
+	case GB_SVC_TYPE_INTF_RESET:
+		return gb_svc_intf_reset_recv(op);
+	case GB_SVC_TYPE_MODULE_INSERTED:
+		return gb_svc_module_inserted_recv(op);
+	case GB_SVC_TYPE_MODULE_REMOVED:
+		return gb_svc_module_removed_recv(op);
+	case GB_SVC_TYPE_INTF_MAILBOX_EVENT:
+		return gb_svc_intf_mailbox_event_recv(op);
+	case GB_SVC_TYPE_INTF_OOPS:
+		return gb_svc_intf_oops_recv(op);
+	default:
+		dev_warn(&svc->dev, "unsupported request 0x%02x\n", type);
+		return -EINVAL;
+	}
+}
+
+static void gb_svc_release(struct device *dev)
+{
+	struct gb_svc *svc = to_gb_svc(dev);
+
+	if (svc->connection)
+		gb_connection_destroy(svc->connection);
+	ida_destroy(&svc->device_id_map);
+	destroy_workqueue(svc->wq);
+	kfree(svc);
+}
+
+struct device_type greybus_svc_type = {
+	.name		= "greybus_svc",
+	.release	= gb_svc_release,
+};
+
+struct gb_svc *gb_svc_create(struct gb_host_device *hd)
+{
+	struct gb_svc *svc;
+
+	svc = kzalloc(sizeof(*svc), GFP_KERNEL);
+	if (!svc)
+		return NULL;
+
+	svc->wq = alloc_workqueue("%s:svc", WQ_UNBOUND, 1, dev_name(&hd->dev));
+	if (!svc->wq) {
+		kfree(svc);
+		return NULL;
+	}
+
+	svc->dev.parent = &hd->dev;
+	svc->dev.bus = &greybus_bus_type;
+	svc->dev.type = &greybus_svc_type;
+	svc->dev.groups = svc_groups;
+	svc->dev.dma_mask = svc->dev.parent->dma_mask;
+	device_initialize(&svc->dev);
+
+	dev_set_name(&svc->dev, "%d-svc", hd->bus_id);
+
+	ida_init(&svc->device_id_map);
+	svc->state = GB_SVC_STATE_RESET;
+	svc->hd = hd;
+
+	svc->connection = gb_connection_create_static(hd, GB_SVC_CPORT_ID,
+						gb_svc_request_handler);
+	if (IS_ERR(svc->connection)) {
+		dev_err(&svc->dev, "failed to create connection: %ld\n",
+				PTR_ERR(svc->connection));
+		goto err_put_device;
+	}
+
+	gb_connection_set_data(svc->connection, svc);
+
+	return svc;
+
+err_put_device:
+	put_device(&svc->dev);
+	return NULL;
+}
+
+int gb_svc_add(struct gb_svc *svc)
+{
+	int ret;
+
+	/*
+	 * The SVC protocol is currently driven by the SVC, so the SVC device
+	 * is added from the connection request handler when enough
+	 * information has been received.
+	 */
+	ret = gb_connection_enable(svc->connection);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void gb_svc_remove_modules(struct gb_svc *svc)
+{
+	struct gb_host_device *hd = svc->hd;
+	struct gb_module *module, *tmp;
+
+	list_for_each_entry_safe(module, tmp, &hd->modules, hd_node) {
+		gb_module_del(module);
+		list_del(&module->hd_node);
+		gb_module_put(module);
+	}
+}
+
+void gb_svc_del(struct gb_svc *svc)
+{
+	gb_connection_disable_rx(svc->connection);
+
+	/*
+	 * The SVC device may have been registered from the request handler.
+	 */
+	if (device_is_registered(&svc->dev)) {
+		gb_timesync_svc_remove(svc);
+		gb_svc_debugfs_exit(svc);
+		gb_svc_watchdog_destroy(svc);
+		device_del(&svc->dev);
+	}
+
+	flush_workqueue(svc->wq);
+
+	gb_svc_remove_modules(svc);
+
+	gb_connection_disable(svc->connection);
+}
+
+void gb_svc_put(struct gb_svc *svc)
+{
+	put_device(&svc->dev);
+}
drivers/greybus/svc.h new file +109 -0

109 lines not on this page · raw message

drivers/greybus/svc_watchdog.c new file +198 -0

198 lines not on this page · raw message

author maintainer
#37 lore raw

PATCH 7/32 greybus: core code

In reply to Greg KH

This is the core Greybus code. It implements the greybus protocol and is the base of where all other greybus drivers tie into.

---
 drivers/greybus/arpc.h                   |  109 +++
 drivers/greybus/authentication.c         |  429 ++++++++++++++
 drivers/greybus/bundle.c                 |  253 ++++++++
 drivers/greybus/bundle.h                 |   90 ++
 drivers/greybus/connection.c             |  938 +++++++++++++++++++++++++++++++
 drivers/greybus/connection.h             |  129 ++++
 drivers/greybus/control.c                |  635 ++++++++++++++++++++
 drivers/greybus/control.h                |   65 ++
 drivers/greybus/core.c                   |  361 +++++++++++
 drivers/greybus/debugfs.c                |   31 +
 drivers/greybus/greybus.h                |  154 +++++
 drivers/greybus/greybus_authentication.h |  120 +++
 drivers/greybus/greybus_id.h             |   26 
 drivers/greybus/greybus_manifest.h       |  177 +++++
 drivers/greybus/manifest.c               |  535 +++++++++++++++++
 drivers/greybus/manifest.h               |   16 
 drivers/greybus/module.c                 |  238 +++++++
 drivers/greybus/module.h                 |   34 +
 18 files changed, 4340 insertions(+)
drivers/greybus/arpc.h new file +109 -0
@@ -0,0 +1,109 @@
+/*
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 version 2 for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of Google Inc. or Linaro Ltd. nor the names of
+ *    its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. OR
+ * LINARO LTD. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __ARPC_H
+#define __ARPC_H
+
+/* APBridgeA RPC (ARPC) */
+
+enum arpc_result {
+	ARPC_SUCCESS		= 0x00,
+	ARPC_NO_MEMORY		= 0x01,
+	ARPC_INVALID		= 0x02,
+	ARPC_TIMEOUT		= 0x03,
+	ARPC_UNKNOWN_ERROR	= 0xff,
+};
+
+struct arpc_request_message {
+	__le16	id;		/* RPC unique id */
+	__le16	size;		/* Size in bytes of header + payload */
+	__u8	type;		/* RPC type */
+	__u8	data[0];	/* ARPC data */
+} __packed;
+
+struct arpc_response_message {
+	__le16	id;		/* RPC unique id */
+	__u8	result;		/* Result of RPC */
+} __packed;
+
+
+/* ARPC requests */
+#define ARPC_TYPE_CPORT_CONNECTED		0x01
+#define ARPC_TYPE_CPORT_QUIESCE			0x02
+#define ARPC_TYPE_CPORT_CLEAR			0x03
+#define ARPC_TYPE_CPORT_FLUSH			0x04
+#define ARPC_TYPE_CPORT_SHUTDOWN		0x05
+
+struct arpc_cport_connected_req {
+	__le16 cport_id;
+} __packed;
+
+struct arpc_cport_quiesce_req {
+	__le16 cport_id;
+	__le16 peer_space;
+	__le16 timeout;
+} __packed;
+
+struct arpc_cport_clear_req {
+	__le16 cport_id;
+} __packed;
+
+struct arpc_cport_flush_req {
+	__le16 cport_id;
+} __packed;
+
+struct arpc_cport_shutdown_req {
+	__le16 cport_id;
+	__le16 timeout;
+	__u8 phase;
+} __packed;
+
+#endif	/* __ARPC_H */
drivers/greybus/authentication.c new file +429 -0

429 lines not on this page · raw message

drivers/greybus/bundle.c new file +253 -0

253 lines not on this page · raw message

drivers/greybus/bundle.h new file +90 -0

90 lines not on this page · raw message

drivers/greybus/connection.c new file +938 -0

938 lines not on this page · raw message

drivers/greybus/connection.h new file +129 -0

129 lines not on this page · raw message

drivers/greybus/control.c new file +635 -0

635 lines not on this page · raw message

drivers/greybus/control.h new file +65 -0

65 lines not on this page · raw message

drivers/greybus/core.c new file +361 -0

352 lines not on this page · raw message

drivers/greybus/debugfs.c new file +31 -0

Lines of this file are not shown. View the raw message.

drivers/greybus/greybus.h new file +154 -0

154 lines not on this page · raw message

drivers/greybus/greybus_authentication.h new file +120 -0

120 lines not on this page · raw message

drivers/greybus/greybus_id.h new file +26 -0

Lines of this file are not shown. View the raw message.

drivers/greybus/greybus_manifest.h new file +177 -0

177 lines not on this page · raw message

drivers/greybus/manifest.c new file +535 -0

535 lines not on this page · raw message

drivers/greybus/manifest.h new file +16 -0

Lines of this file are not shown. View the raw message.

drivers/greybus/module.c new file +238 -0

238 lines not on this page · raw message

drivers/greybus/module.h new file +34 -0

Lines of this file are not shown. View the raw message.

The rest of this diff is not shown. View the raw message.

Part of the diff is not shown here. View the raw message.

author maintainer
#38 lore raw

PATCH 8/32 greybus: bootrom driver

In reply to Greg KH

This driver implements the Greybus Bootrom protocol.

---
 drivers/greybus/bootrom.c |  524 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 524 insertions(+)
drivers/greybus/bootrom.c new file +524 -0
@@ -0,0 +1,524 @@
+/*
+ * BOOTROM Greybus driver.
+ *
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/firmware.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+#include "greybus.h"
+#include "firmware.h"
+
+/* Timeout, in jiffies, within which the next request must be received */
+#define NEXT_REQ_TIMEOUT_MS	1000
+
+/*
+ * FIXME: Reduce this timeout once svc core handles parallel processing of
+ * events from the SVC, which are handled sequentially today.
+ */
+#define MODE_SWITCH_TIMEOUT_MS	10000
+
+enum next_request_type {
+	NEXT_REQ_FIRMWARE_SIZE,
+	NEXT_REQ_GET_FIRMWARE,
+	NEXT_REQ_READY_TO_BOOT,
+	NEXT_REQ_MODE_SWITCH,
+};
+
+struct gb_bootrom {
+	struct gb_connection	*connection;
+	const struct firmware	*fw;
+	u8			protocol_major;
+	u8			protocol_minor;
+	enum next_request_type	next_request;
+	struct delayed_work	dwork;
+	struct mutex		mutex; /* Protects bootrom->fw */
+};
+
+static void free_firmware(struct gb_bootrom *bootrom)
+{
+	if (!bootrom->fw)
+		return;
+
+	release_firmware(bootrom->fw);
+	bootrom->fw = NULL;
+}
+
+static void gb_bootrom_timedout(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct gb_bootrom *bootrom = container_of(dwork, struct gb_bootrom, dwork);
+	struct device *dev = &bootrom->connection->bundle->dev;
+	const char *reason;
+
+	switch (bootrom->next_request) {
+	case NEXT_REQ_FIRMWARE_SIZE:
+		reason = "Firmware Size Request";
+		break;
+	case NEXT_REQ_GET_FIRMWARE:
+		reason = "Get Firmware Request";
+		break;
+	case NEXT_REQ_READY_TO_BOOT:
+		reason = "Ready to Boot Request";
+		break;
+	case NEXT_REQ_MODE_SWITCH:
+		reason = "Interface Mode Switch";
+		break;
+	default:
+		reason = NULL;
+		dev_err(dev, "Invalid next-request: %u", bootrom->next_request);
+		break;
+	}
+
+	dev_err(dev, "Timed out waiting for %s from the Module\n", reason);
+
+	mutex_lock(&bootrom->mutex);
+	free_firmware(bootrom);
+	mutex_unlock(&bootrom->mutex);
+
+	/* TODO: Power-off Module ? */
+}
+
+static void gb_bootrom_set_timeout(struct gb_bootrom *bootrom,
+			enum next_request_type next, unsigned long timeout)
+{
+	bootrom->next_request = next;
+	schedule_delayed_work(&bootrom->dwork, msecs_to_jiffies(timeout));
+}
+
+static void gb_bootrom_cancel_timeout(struct gb_bootrom *bootrom)
+{
+	cancel_delayed_work_sync(&bootrom->dwork);
+}
+
+/*
+ * The es2 chip doesn't have VID/PID programmed into the hardware and we need to
+ * hack that up to distinguish different modules and their firmware blobs.
+ *
+ * This fetches VID/PID (over bootrom protocol) for es2 chip only, when VID/PID
+ * already sent during hotplug are 0.
+ *
+ * Otherwise, we keep intf->vendor_id/product_id same as what's passed
+ * during hotplug.
+ */
+static void bootrom_es2_fixup_vid_pid(struct gb_bootrom *bootrom)
+{
+	struct gb_bootrom_get_vid_pid_response response;
+	struct gb_connection *connection = bootrom->connection;
+	struct gb_interface *intf = connection->bundle->intf;
+	int ret;
+
+	if (!(intf->quirks & GB_INTERFACE_QUIRK_NO_GMP_IDS))
+		return;
+
+	ret = gb_operation_sync(connection, GB_BOOTROM_TYPE_GET_VID_PID,
+				NULL, 0, &response, sizeof(response));
+	if (ret) {
+		dev_err(&connection->bundle->dev,
+			"Bootrom get vid/pid operation failed (%d)\n", ret);
+		return;
+	}
+
+	/*
+	 * NOTE: This is hacked, so that the same values of VID/PID can be used
+	 * by next firmware level as well. The uevent for bootrom will still
+	 * have VID/PID as 0, though after this point the sysfs files will start
+	 * showing the updated values. But yeah, that's a bit racy as the same
+	 * sysfs files would be showing 0 before this point.
+	 */
+	intf->vendor_id = le32_to_cpu(response.vendor_id);
+	intf->product_id = le32_to_cpu(response.product_id);
+
+	dev_dbg(&connection->bundle->dev, "Bootrom got vid (0x%x)/pid (0x%x)\n",
+		intf->vendor_id, intf->product_id);
+}
+
+/* This returns path of the firmware blob on the disk */
+static int find_firmware(struct gb_bootrom *bootrom, u8 stage)
+{
+	struct gb_connection *connection = bootrom->connection;
+	struct gb_interface *intf = connection->bundle->intf;
+	char firmware_name[49];
+	int rc;
+
+	/* Already have a firmware, free it */
+	free_firmware(bootrom);
+
+	/* Bootrom protocol is only supported for loading Stage 2 firmware */
+	if (stage != 2) {
+		dev_err(&connection->bundle->dev, "Invalid boot stage: %u\n",
+			stage);
+		return -EINVAL;
+	}
+
+	/*
+	 * Create firmware name
+	 *
+	 * XXX Name it properly..
+	 */
+	snprintf(firmware_name, sizeof(firmware_name),
+		 FW_NAME_PREFIX "%08x_%08x_%08x_%08x_s2l.tftf",
+		 intf->ddbl1_manufacturer_id, intf->ddbl1_product_id,
+		 intf->vendor_id, intf->product_id);
+
+	// FIXME:
+	// Turn to dev_dbg later after everyone has valid bootloaders with good
+	// ids, but leave this as dev_info for now to make it easier to track
+	// down "empty" vid/pid modules.
+	dev_info(&connection->bundle->dev, "Firmware file '%s' requested\n",
+		 firmware_name);
+
+	rc = request_firmware(&bootrom->fw, firmware_name,
+		&connection->bundle->dev);
+	if (rc) {
+		dev_err(&connection->bundle->dev,
+			"failed to find %s firmware (%d)\n", firmware_name, rc);
+	}
+
+	return rc;
+}
+
+static int gb_bootrom_firmware_size_request(struct gb_operation *op)
+{
+	struct gb_bootrom *bootrom = gb_connection_get_data(op->connection);
+	struct gb_bootrom_firmware_size_request *size_request = op->request->payload;
+	struct gb_bootrom_firmware_size_response *size_response;
+	struct device *dev = &op->connection->bundle->dev;
+	int ret;
+
+	/* Disable timeouts */
+	gb_bootrom_cancel_timeout(bootrom);
+
+	if (op->request->payload_size != sizeof(*size_request)) {
+		dev_err(dev, "%s: illegal size of firmware size request (%zu != %zu)\n",
+			__func__, op->request->payload_size,
+			sizeof(*size_request));
+		ret = -EINVAL;
+		goto queue_work;
+	}
+
+	mutex_lock(&bootrom->mutex);
+
+	ret = find_firmware(bootrom, size_request->stage);
+	if (ret)
+		goto unlock;
+
+	if (!gb_operation_response_alloc(op, sizeof(*size_response),
+					 GFP_KERNEL)) {
+		dev_err(dev, "%s: error allocating response\n", __func__);
+		free_firmware(bootrom);
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	size_response = op->response->payload;
+	size_response->size = cpu_to_le32(bootrom->fw->size);
+
+	dev_dbg(dev, "%s: firmware size %d bytes\n", __func__, size_response->size);
+
+unlock:
+	mutex_unlock(&bootrom->mutex);
+
+queue_work:
+	if (!ret) {
+		/* Refresh timeout */
+		gb_bootrom_set_timeout(bootrom, NEXT_REQ_GET_FIRMWARE,
+				       NEXT_REQ_TIMEOUT_MS);
+	}
+
+	return ret;
+}
+
+static int gb_bootrom_get_firmware(struct gb_operation *op)
+{
+	struct gb_bootrom *bootrom = gb_connection_get_data(op->connection);
+	const struct firmware *fw;
+	struct gb_bootrom_get_firmware_request *firmware_request;
+	struct gb_bootrom_get_firmware_response *firmware_response;
+	struct device *dev = &op->connection->bundle->dev;
+	unsigned int offset, size;
+	enum next_request_type next_request;
+	int ret = 0;
+
+	/* Disable timeouts */
+	gb_bootrom_cancel_timeout(bootrom);
+
+	if (op->request->payload_size != sizeof(*firmware_request)) {
+		dev_err(dev, "%s: Illegal size of get firmware request (%zu %zu)\n",
+			__func__, op->request->payload_size,
+			sizeof(*firmware_request));
+		ret = -EINVAL;
+		goto queue_work;
+	}
+
+	mutex_lock(&bootrom->mutex);
+
+	fw = bootrom->fw;
+	if (!fw) {
+		dev_err(dev, "%s: firmware not available\n", __func__);
+		ret = -EINVAL;
+		goto unlock;
+	}
+
+	firmware_request = op->request->payload;
+	offset = le32_to_cpu(firmware_request->offset);
+	size = le32_to_cpu(firmware_request->size);
+
+	if (offset >= fw->size || size > fw->size - offset) {
+		dev_warn(dev, "bad firmware request (offs = %u, size = %u)\n",
+				offset, size);
+		ret = -EINVAL;
+		goto unlock;
+	}
+
+	if (!gb_operation_response_alloc(op, sizeof(*firmware_response) + size,
+					 GFP_KERNEL)) {
+		dev_err(dev, "%s: error allocating response\n", __func__);
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	firmware_response = op->response->payload;
+	memcpy(firmware_response->data, fw->data + offset, size);
+
+	dev_dbg(dev, "responding with firmware (offs = %u, size = %u)\n", offset,
+		size);
+
+unlock:
+	mutex_unlock(&bootrom->mutex);
+
+queue_work:
+	/* Refresh timeout */
+	if (!ret && (offset + size == fw->size))
+		next_request = NEXT_REQ_READY_TO_BOOT;
+	else
+		next_request = NEXT_REQ_GET_FIRMWARE;
+
+	gb_bootrom_set_timeout(bootrom, next_request, NEXT_REQ_TIMEOUT_MS);
+
+	return ret;
+}
+
+static int gb_bootrom_ready_to_boot(struct gb_operation *op)
+{
+	struct gb_connection *connection = op->connection;
+	struct gb_bootrom *bootrom = gb_connection_get_data(connection);
+	struct gb_bootrom_ready_to_boot_request *rtb_request;
+	struct device *dev = &connection->bundle->dev;
+	u8 status;
+	int ret = 0;
+
+	/* Disable timeouts */
+	gb_bootrom_cancel_timeout(bootrom);
+
+	if (op->request->payload_size != sizeof(*rtb_request)) {
+		dev_err(dev, "%s: Illegal size of ready to boot request (%zu %zu)\n",
+			__func__, op->request->payload_size,
+			sizeof(*rtb_request));
+		ret = -EINVAL;
+		goto queue_work;
+	}
+
+	rtb_request = op->request->payload;
+	status = rtb_request->status;
+
+	/* Return error if the blob was invalid */
+	if (status == GB_BOOTROM_BOOT_STATUS_INVALID) {
+		ret = -EINVAL;
+		goto queue_work;
+	}
+
+	/*
+	 * XXX Should we return error for insecure firmware?
+	 */
+	dev_dbg(dev, "ready to boot: 0x%x, 0\n", status);
+
+queue_work:
+	/*
+	 * Refresh timeout, the Interface shall load the new personality and
+	 * send a new hotplug request, which shall get rid of the bootrom
+	 * connection. As that can take some time, increase the timeout a bit.
+	 */
+	gb_bootrom_set_timeout(bootrom, NEXT_REQ_MODE_SWITCH,
+			       MODE_SWITCH_TIMEOUT_MS);
+
+	return ret;
+}
+
+static int gb_bootrom_request_handler(struct gb_operation *op)
+{
+	u8 type = op->type;
+
+	switch (type) {
+	case GB_BOOTROM_TYPE_FIRMWARE_SIZE:
+		return gb_bootrom_firmware_size_request(op);
+	case GB_BOOTROM_TYPE_GET_FIRMWARE:
+		return gb_bootrom_get_firmware(op);
+	case GB_BOOTROM_TYPE_READY_TO_BOOT:
+		return gb_bootrom_ready_to_boot(op);
+	default:
+		dev_err(&op->connection->bundle->dev,
+			"unsupported request: %u\n", type);
+		return -EINVAL;
+	}
+}
+
+static int gb_bootrom_get_version(struct gb_bootrom *bootrom)
+{
+	struct gb_bundle *bundle = bootrom->connection->bundle;
+	struct gb_bootrom_version_request request;
+	struct gb_bootrom_version_response response;
+	int ret;
+
+	request.major = GB_BOOTROM_VERSION_MAJOR;
+	request.minor = GB_BOOTROM_VERSION_MINOR;
+
+	ret = gb_operation_sync(bootrom->connection,
+				GB_BOOTROM_TYPE_VERSION,
+				&request, sizeof(request), &response,
+				sizeof(response));
+	if (ret) {
+		dev_err(&bundle->dev,
+				"failed to get protocol version: %d\n",
+				ret);
+		return ret;
+	}
+
+	if (response.major > request.major) {
+		dev_err(&bundle->dev,
+				"unsupported major protocol version (%u > %u)\n",
+				response.major, request.major);
+		return -ENOTSUPP;
+	}
+
+	bootrom->protocol_major = response.major;
+	bootrom->protocol_minor = response.minor;
+
+	dev_dbg(&bundle->dev, "%s - %u.%u\n", __func__, response.major,
+			response.minor);
+
+	return 0;
+}
+
+static int gb_bootrom_probe(struct gb_bundle *bundle,
+					const struct greybus_bundle_id *id)
+{
+	struct greybus_descriptor_cport *cport_desc;
+	struct gb_connection *connection;
+	struct gb_bootrom *bootrom;
+	int ret;
+
+	if (bundle->num_cports != 1)
+		return -ENODEV;
+
+	cport_desc = &bundle->cport_desc[0];
+	if (cport_desc->protocol_id != GREYBUS_PROTOCOL_BOOTROM)
+		return -ENODEV;
+
+	bootrom = kzalloc(sizeof(*bootrom), GFP_KERNEL);
+	if (!bootrom)
+		return -ENOMEM;
+
+	connection = gb_connection_create(bundle,
+						le16_to_cpu(cport_desc->id),
+						gb_bootrom_request_handler);
+	if (IS_ERR(connection)) {
+		ret = PTR_ERR(connection);
+		goto err_free_bootrom;
+	}
+
+	gb_connection_set_data(connection, bootrom);
+
+	bootrom->connection = connection;
+
+	mutex_init(&bootrom->mutex);
+	INIT_DELAYED_WORK(&bootrom->dwork, gb_bootrom_timedout);
+	greybus_set_drvdata(bundle, bootrom);
+
+	ret = gb_connection_enable_tx(connection);
+	if (ret)
+		goto err_connection_destroy;
+
+	ret = gb_bootrom_get_version(bootrom);
+	if (ret)
+		goto err_connection_disable;
+
+	bootrom_es2_fixup_vid_pid(bootrom);
+
+	ret = gb_connection_enable(connection);
+	if (ret)
+		goto err_connection_disable;
+
+	/* Refresh timeout */
+	gb_bootrom_set_timeout(bootrom, NEXT_REQ_FIRMWARE_SIZE,
+			       NEXT_REQ_TIMEOUT_MS);
+
+	/* Tell bootrom we're ready. */
+	ret = gb_operation_sync(connection, GB_BOOTROM_TYPE_AP_READY, NULL, 0,
+				NULL, 0);
+	if (ret) {
+		dev_err(&connection->bundle->dev,
+				"failed to send AP READY: %d\n", ret);
+		goto err_cancel_timeout;
+	}
+
+	dev_dbg(&bundle->dev, "AP_READY sent\n");
+
+	return 0;
+
+err_cancel_timeout:
+	gb_bootrom_cancel_timeout(bootrom);
+err_connection_disable:
+	gb_connection_disable(connection);
+err_connection_destroy:
+	gb_connection_destroy(connection);
+err_free_bootrom:
+	kfree(bootrom);
+
+	return ret;
+}
+
+static void gb_bootrom_disconnect(struct gb_bundle *bundle)
+{
+	struct gb_bootrom *bootrom = greybus_get_drvdata(bundle);
+
+	dev_dbg(&bundle->dev, "%s\n", __func__);
+
+	gb_connection_disable(bootrom->connection);
+
+	/* Disable timeouts */
+	gb_bootrom_cancel_timeout(bootrom);
+
+	/*
+	 * Release firmware:
+	 *
+	 * As the connection and the delayed work are already disabled, we don't
+	 * need to lock access to bootrom->fw here.
+	 */
+	free_firmware(bootrom);
+
+	gb_connection_destroy(bootrom->connection);
+	kfree(bootrom);
+}
+
+static const struct greybus_bundle_id gb_bootrom_id_table[] = {
+	{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_BOOTROM) },
+	{ }
+};
+
+static struct greybus_driver gb_bootrom_driver = {
+	.name		= "bootrom",
+	.probe		= gb_bootrom_probe,
+	.disconnect	= gb_bootrom_disconnect,
+	.id_table	= gb_bootrom_id_table,
+};
+
+module_greybus_driver(gb_bootrom_driver);
+
+MODULE_LICENSE("GPL v2");
author maintainer
#39 lore raw

PATCH 9/32 greybus: firmware download class driver

In reply to Greg KH

This driver implements the Greybus firmware download protocol.

---
 drivers/greybus/Documentation/firmware/authenticate.c      |  139 ++
 drivers/greybus/Documentation/firmware/firmware-management |  333 ++++++
 drivers/greybus/Documentation/firmware/firmware.c          |  262 ++++
 drivers/greybus/firmware.h                                 |   42 
 drivers/greybus/fw-core.c                                  |  312 +++++
 drivers/greybus/fw-download.c                              |  465 ++++++++
 drivers/greybus/fw-management.c                            |  721 +++++++++++++
 drivers/greybus/greybus_firmware.h                         |  120 ++
 8 files changed, 2394 insertions(+)
drivers/greybus/Documentation/firmware/authenticate.c new file +139 -0
@@ -0,0 +1,139 @@
+/*
+ * Sample code to test CAP protocol
+ *
+ * This file is provided under a dual BSD/GPLv2 license.  When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 version 2 for more details.
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Google Inc. All rights reserved.
+ * Copyright(c) 2016 Linaro Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *  * Neither the name of Google Inc. or Linaro Ltd. nor the names of
+ *    its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written
+ *    permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. OR
+ * LINARO LTD. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "../../greybus_authentication.h"
+
+struct cap_ioc_get_endpoint_uid uid;
+struct cap_ioc_get_ims_certificate cert = {
+	.certificate_class = 0,
+	.certificate_id = 0,
+};
+
+struct cap_ioc_authenticate authenticate = {
+	.auth_type = 0,
+	.challenge = {0},
+};
+
+int main(int argc, char *argv[])
+{
+	unsigned int timeout = 10000;
+	char *capdev;
+	int fd, ret;
+
+	/* Make sure arguments are correct */
+	if (argc != 2) {
+		printf("\nUsage: ./firmware <Path of the gb-cap-X dev>\n");
+		return 0;
+	}
+
+	capdev = argv[1];
+
+	printf("Opening %s authentication device\n", capdev);
+
+	fd = open(capdev, O_RDWR);
+	if (fd < 0) {
+		printf("Failed to open: %s\n", capdev);
+		return -1;
+	}
+
+	/* Get UID */
+	printf("Get UID\n");
+
+	ret = ioctl(fd, CAP_IOC_GET_ENDPOINT_UID, &uid);
+	if (ret < 0) {
+		printf("Failed to get UID: %s (%d)\n", capdev, ret);
+		ret = -1;
+		goto close_fd;
+	}
+
+	printf("UID received: 0x%llx\n", *(long long unsigned int *)(uid.uid));
+
+	/* Get certificate */
+	printf("Get IMS certificate\n");
+
+	ret = ioctl(fd, CAP_IOC_GET_IMS_CERTIFICATE, &cert);
+	if (ret < 0) {
+		printf("Failed to get IMS certificate: %s (%d)\n", capdev, ret);
+		ret = -1;
+		goto close_fd;
+	}
+
+	printf("IMS Certificate size: %d\n", cert.cert_size);
+
+	/* Authenticate */
+	printf("Authenticate module\n");
+
+	memcpy(authenticate.uid, uid.uid, 8);
+
+	ret = ioctl(fd, CAP_IOC_AUTHENTICATE, &authenticate);
+	if (ret < 0) {
+		printf("Failed to authenticate module: %s (%d)\n", capdev, ret);
+		ret = -1;
+		goto close_fd;
+	}
+
+	printf("Authenticated, result (%02x), sig-size (%02x)\n",
+		authenticate.result_code, authenticate.signature_size);
+
+close_fd:
+	close(fd);
+
+	return ret;
+}
drivers/greybus/Documentation/firmware/firmware-management new file +333 -0

333 lines not on this page · raw message

drivers/greybus/Documentation/firmware/firmware.c new file +262 -0

262 lines not on this page · raw message

drivers/greybus/firmware.h new file +42 -0

42 lines not on this page · raw message

drivers/greybus/fw-core.c new file +312 -0

312 lines not on this page · raw message

drivers/greybus/fw-download.c new file +465 -0

465 lines not on this page · raw message

drivers/greybus/fw-management.c new file +721 -0

721 lines not on this page · raw message

drivers/greybus/greybus_firmware.h new file +120 -0

120 lines not on this page · raw message

author maintainer
#40 lore raw

PATCH 10/32 greybus: audio driver

In reply to Greg KH

This driver implements the Greybus audio protocol.

---
 drivers/greybus/audio_apbridgea.c       |  207 ++++
 drivers/greybus/audio_apbridgea.h       |  156 +++
 drivers/greybus/audio_codec.c           | 1132 +++++++++++++++++++++++++
 drivers/greybus/audio_codec.h           |  283 ++++++
 drivers/greybus/audio_gb.c              |  228 +++++
 drivers/greybus/audio_manager.c         |  184 ++++
 drivers/greybus/audio_manager.h         |   83 +
 drivers/greybus/audio_manager_module.c  |  258 +++++
 drivers/greybus/audio_manager_private.h |   28 
 drivers/greybus/audio_manager_sysfs.c   |  102 ++
 drivers/greybus/audio_module.c          |  482 ++++++++++
 drivers/greybus/audio_topology.c        | 1442 ++++++++++++++++++++++++++++++++
 12 files changed, 4585 insertions(+)
drivers/greybus/audio_apbridgea.c new file +207 -0
@@ -0,0 +1,207 @@
+/*
+ * Greybus Audio Device Class Protocol helpers
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include "greybus.h"
+#include "greybus_protocols.h"
+#include "audio_apbridgea.h"
+#include "audio_codec.h"
+
+int gb_audio_apbridgea_set_config(struct gb_connection *connection,
+				  __u16 i2s_port, __u32 format, __u32 rate,
+				  __u32 mclk_freq)
+{
+	struct audio_apbridgea_set_config_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_CONFIG;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+	req.format = cpu_to_le32(format);
+	req.rate = cpu_to_le32(rate);
+	req.mclk_freq = cpu_to_le32(mclk_freq);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_config);
+
+int gb_audio_apbridgea_register_cport(struct gb_connection *connection,
+				      __u16 i2s_port, __u16 cportid,
+				      __u8 direction)
+{
+	struct audio_apbridgea_register_cport_request req;
+	int ret;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_REGISTER_CPORT;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+	req.cport = cpu_to_le16(cportid);
+	req.direction = direction;
+
+	ret = gb_pm_runtime_get_sync(connection->bundle);
+	if (ret)
+		return ret;
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_register_cport);
+
+int gb_audio_apbridgea_unregister_cport(struct gb_connection *connection,
+					__u16 i2s_port, __u16 cportid,
+					__u8 direction)
+{
+	struct audio_apbridgea_unregister_cport_request req;
+	int ret;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_UNREGISTER_CPORT;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+	req.cport = cpu_to_le16(cportid);
+	req.direction = direction;
+
+	ret = gb_hd_output(connection->hd, &req, sizeof(req),
+			   GB_APB_REQUEST_AUDIO_CONTROL, true);
+
+	gb_pm_runtime_put_autosuspend(connection->bundle);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_unregister_cport);
+
+int gb_audio_apbridgea_set_tx_data_size(struct gb_connection *connection,
+					__u16 i2s_port, __u16 size)
+{
+	struct audio_apbridgea_set_tx_data_size_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_TX_DATA_SIZE;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+	req.size = cpu_to_le16(size);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_tx_data_size);
+
+int gb_audio_apbridgea_prepare_tx(struct gb_connection *connection,
+				  __u16 i2s_port)
+{
+	struct audio_apbridgea_prepare_tx_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_PREPARE_TX;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_prepare_tx);
+
+int gb_audio_apbridgea_start_tx(struct gb_connection *connection,
+				__u16 i2s_port, __u64 timestamp)
+{
+	struct audio_apbridgea_start_tx_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_START_TX;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+	req.timestamp = cpu_to_le64(timestamp);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_start_tx);
+
+int gb_audio_apbridgea_stop_tx(struct gb_connection *connection, __u16 i2s_port)
+{
+	struct audio_apbridgea_stop_tx_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_STOP_TX;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_stop_tx);
+
+int gb_audio_apbridgea_shutdown_tx(struct gb_connection *connection,
+				   __u16 i2s_port)
+{
+	struct audio_apbridgea_shutdown_tx_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_SHUTDOWN_TX;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_shutdown_tx);
+
+int gb_audio_apbridgea_set_rx_data_size(struct gb_connection *connection,
+					__u16 i2s_port, __u16 size)
+{
+	struct audio_apbridgea_set_rx_data_size_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_RX_DATA_SIZE;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+	req.size = cpu_to_le16(size);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_rx_data_size);
+
+int gb_audio_apbridgea_prepare_rx(struct gb_connection *connection,
+				  __u16 i2s_port)
+{
+	struct audio_apbridgea_prepare_rx_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_PREPARE_RX;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_prepare_rx);
+
+int gb_audio_apbridgea_start_rx(struct gb_connection *connection,
+				__u16 i2s_port)
+{
+	struct audio_apbridgea_start_rx_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_START_RX;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_start_rx);
+
+int gb_audio_apbridgea_stop_rx(struct gb_connection *connection, __u16 i2s_port)
+{
+	struct audio_apbridgea_stop_rx_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_STOP_RX;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_stop_rx);
+
+int gb_audio_apbridgea_shutdown_rx(struct gb_connection *connection,
+				   __u16 i2s_port)
+{
+	struct audio_apbridgea_shutdown_rx_request req;
+
+	req.hdr.type = AUDIO_APBRIDGEA_TYPE_SHUTDOWN_RX;
+	req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+	return gb_hd_output(connection->hd, &req, sizeof(req),
+			    GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_shutdown_rx);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("greybus:audio-apbridgea");
+MODULE_DESCRIPTION("Greybus Special APBridgeA Audio Protocol library");
+MODULE_AUTHOR("Mark Greer <[email protected]>");
drivers/greybus/audio_apbridgea.h new file +156 -0

156 lines not on this page · raw message

drivers/greybus/audio_codec.c new file +1132 -0

1,132 lines not on this page · raw message

drivers/greybus/audio_codec.h new file +283 -0

283 lines not on this page · raw message

drivers/greybus/audio_gb.c new file +228 -0

228 lines not on this page · raw message

drivers/greybus/audio_manager.c new file +184 -0

184 lines not on this page · raw message

drivers/greybus/audio_manager.h new file +83 -0

83 lines not on this page · raw message

drivers/greybus/audio_manager_module.c new file +258 -0

258 lines not on this page · raw message

drivers/greybus/audio_manager_private.h new file +28 -0
@@ -0,0 +1,28 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef _GB_AUDIO_MANAGER_PRIVATE_H_
+#define _GB_AUDIO_MANAGER_PRIVATE_H_
+
+#include <linux/kobject.h>
+
+#include "audio_manager.h"
+
+int gb_audio_manager_module_create(
+	struct gb_audio_manager_module **module,
+	struct kset *manager_kset,
+	int id, struct gb_audio_manager_module_descriptor *desc);
+
+/* module destroyed via kobject_put */
+
+void gb_audio_manager_module_dump(struct gb_audio_manager_module *module);
+
+/* sysfs control */
+void gb_audio_manager_sysfs_init(struct kobject *kobj);
+
+#endif /* _GB_AUDIO_MANAGER_PRIVATE_H_ */
drivers/greybus/audio_manager_sysfs.c new file +102 -0

102 lines not on this page · raw message

drivers/greybus/audio_module.c new file +482 -0

339 lines not on this page · raw message

drivers/greybus/audio_topology.c new file +1442 -0

1,442 lines not on this page · raw message

The rest of this diff is not shown. View the raw message.

Part of the diff is not shown here. View the raw message.

author maintainer
#41 lore raw

PATCH 11/32 greybus: camera driver

In reply to Greg KH

This driver implements the Greybus camera protocol.

---
 drivers/greybus/camera.c    | 1400 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/greybus/gb-camera.h |  127 +++
 2 files changed, 1527 insertions(+)
drivers/greybus/camera.c new file +1400 -0
@@ -0,0 +1,1400 @@
+/*
+ * Greybus Camera protocol driver.
+ *
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+
+#include "gb-camera.h"
+#include "greybus.h"
+#include "greybus_protocols.h"
+
+enum gb_camera_debugs_buffer_id {
+	GB_CAMERA_DEBUGFS_BUFFER_CAPABILITIES,
+	GB_CAMERA_DEBUGFS_BUFFER_STREAMS,
+	GB_CAMERA_DEBUGFS_BUFFER_CAPTURE,
+	GB_CAMERA_DEBUGFS_BUFFER_FLUSH,
+	GB_CAMERA_DEBUGFS_BUFFER_MAX,
+};
+
+struct gb_camera_debugfs_buffer {
+	char data[PAGE_SIZE];
+	size_t length;
+};
+
+enum gb_camera_state {
+	GB_CAMERA_STATE_UNCONFIGURED,
+	GB_CAMERA_STATE_CONFIGURED,
+};
+
+/**
+ * struct gb_camera - A Greybus Camera Device
+ * @connection: the greybus connection for camera management
+ * @data_connection: the greybus connection for camera data
+ * @data_cport_id: the data CPort ID on the module side
+ * @mutex: protects the connection and state fields
+ * @state: the current module state
+ * @debugfs: debugfs entries for camera protocol operations testing
+ * @module: Greybus camera module registered to HOST processor.
+ */
+struct gb_camera {
+	struct gb_bundle *bundle;
+	struct gb_connection *connection;
+	struct gb_connection *data_connection;
+	u16 data_cport_id;
+
+	struct mutex mutex;
+	enum gb_camera_state state;
+
+	struct {
+		struct dentry *root;
+		struct gb_camera_debugfs_buffer *buffers;
+	} debugfs;
+
+	struct gb_camera_module module;
+};
+
+struct gb_camera_stream_config {
+	unsigned int width;
+	unsigned int height;
+	unsigned int format;
+	unsigned int vc;
+	unsigned int dt[2];
+	unsigned int max_size;
+};
+
+struct gb_camera_fmt_info {
+	enum v4l2_mbus_pixelcode mbus_code;
+	unsigned int gb_format;
+	unsigned int bpp;
+};
+
+/* GB format to media code map */
+static const struct gb_camera_fmt_info gb_fmt_info[] = {
+	{
+		.mbus_code = V4L2_MBUS_FMT_UYVY8_1X16,
+		.gb_format = 0x01,
+		.bpp	   = 16,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_NV12_1x8,
+		.gb_format = 0x12,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_NV21_1x8,
+		.gb_format = 0x13,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_YU12_1x8,
+		.gb_format = 0x16,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_YV12_1x8,
+		.gb_format = 0x17,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_JPEG_1X8,
+		.gb_format = 0x40,
+		.bpp	   = 0,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_GB_CAM_METADATA_1X8,
+		.gb_format = 0x41,
+		.bpp	   = 0,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_GB_CAM_DEBUG_DATA_1X8,
+		.gb_format = 0x42,
+		.bpp	   = 0,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SBGGR10_1X10,
+		.gb_format = 0x80,
+		.bpp	   = 10,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SGBRG10_1X10,
+		.gb_format = 0x81,
+		.bpp	   = 10,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SGRBG10_1X10,
+		.gb_format = 0x82,
+		.bpp	   = 10,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SRGGB10_1X10,
+		.gb_format = 0x83,
+		.bpp	   = 10,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SBGGR12_1X12,
+		.gb_format = 0x84,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SGBRG12_1X12,
+		.gb_format = 0x85,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SGRBG12_1X12,
+		.gb_format = 0x86,
+		.bpp	   = 12,
+	},
+	{
+		.mbus_code = V4L2_MBUS_FMT_SRGGB12_1X12,
+		.gb_format = 0x87,
+		.bpp	   = 12,
+	},
+};
+
+static const struct gb_camera_fmt_info *gb_camera_get_format_info(u16 gb_fmt)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(gb_fmt_info); i++) {
+		if (gb_fmt_info[i].gb_format == gb_fmt)
+			return &gb_fmt_info[i];
+	}
+
+	return NULL;
+}
+
+#define ES2_APB_CDSI0_CPORT		16
+#define ES2_APB_CDSI1_CPORT		17
+
+#define GB_CAMERA_MAX_SETTINGS_SIZE	8192
+
+#define gcam_dbg(gcam, format...)	dev_dbg(&gcam->bundle->dev, format)
+#define gcam_info(gcam, format...)	dev_info(&gcam->bundle->dev, format)
+#define gcam_err(gcam, format...)	dev_err(&gcam->bundle->dev, format)
+
+static int gb_camera_operation_sync_flags(struct gb_connection *connection,
+					  int type, unsigned int flags,
+					  void *request, size_t request_size,
+					  void *response, size_t *response_size)
+{
+	struct gb_operation *operation;
+	int ret;
+
+	operation = gb_operation_create_flags(connection, type, request_size,
+					      *response_size, flags,
+					      GFP_KERNEL);
+	if (!operation)
+		return  -ENOMEM;
+
+	if (request_size)
+		memcpy(operation->request->payload, request, request_size);
+
+	ret = gb_operation_request_send_sync(operation);
+	if (ret) {
+		dev_err(&connection->hd->dev,
+			"%s: synchronous operation of type 0x%02x failed: %d\n",
+			connection->name, type, ret);
+	} else {
+		*response_size = operation->response->payload_size;
+
+		if (operation->response->payload_size)
+			memcpy(response, operation->response->payload,
+			       operation->response->payload_size);
+	}
+
+	gb_operation_put(operation);
+
+	return ret;
+}
+
+static int gb_camera_get_max_pkt_size(struct gb_camera *gcam,
+		struct gb_camera_configure_streams_response *resp)
+{
+	unsigned int max_pkt_size = 0;
+	unsigned int i;
+
+	for (i = 0; i < resp->num_streams; i++) {
+		struct gb_camera_stream_config_response *cfg = &resp->config[i];
+		const struct gb_camera_fmt_info *fmt_info;
+		unsigned int pkt_size;
+
+		fmt_info = gb_camera_get_format_info(cfg->format);
+		if (!fmt_info) {
+			gcam_err(gcam, "unsupported greybus image format: %d\n",
+				 cfg->format);
+			return -EIO;
+		}
+
+		if (fmt_info->bpp == 0) {
+			pkt_size = le32_to_cpu(cfg->max_pkt_size);
+
+			if (pkt_size == 0) {
+				gcam_err(gcam,
+					 "Stream %u: invalid zero maximum packet size\n",
+					 i);
+				return -EIO;
+			}
+		} else {
+			pkt_size = le16_to_cpu(cfg->width) * fmt_info->bpp / 8;
+
+			if (pkt_size != le32_to_cpu(cfg->max_pkt_size)) {
+				gcam_err(gcam,
+					 "Stream %u: maximum packet size mismatch (%u/%u)\n",
+					 i, pkt_size, cfg->max_pkt_size);
+				return -EIO;
+			}
+		}
+
+		max_pkt_size = max(pkt_size, max_pkt_size);
+	}
+
+	return max_pkt_size;
+}
+
+/*
+ * Validate the stream configuration response verifying padding is correctly
+ * set and the returned number of streams is supported
+ */
+static const int gb_camera_configure_streams_validate_response(
+		struct gb_camera *gcam,
+		struct gb_camera_configure_streams_response *resp,
+		unsigned int nstreams)
+{
+	unsigned int i;
+
+	/* Validate the returned response structure */
+	if (resp->padding[0] || resp->padding[1]) {
+		gcam_err(gcam, "response padding != 0\n");
+		return -EIO;
+	}
+
+	if (resp->num_streams > nstreams) {
+		gcam_err(gcam, "got #streams %u > request %u\n",
+			 resp->num_streams, nstreams);
+		return -EIO;
+	}
+
+	for (i = 0; i < resp->num_streams; i++) {
+		struct gb_camera_stream_config_response *cfg = &resp->config[i];
+		if (cfg->padding) {
+			gcam_err(gcam, "stream #%u padding != 0\n", i);
+			return -EIO;
+		}
+	}
+
+	return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware Configuration
+ */
+
+static int gb_camera_set_intf_power_mode(struct gb_camera *gcam, u8 intf_id,
+					 bool hs)
+{
+	struct gb_svc *svc = gcam->connection->hd->svc;
+	int ret;
+
+	if (hs)
+		ret = gb_svc_intf_set_power_mode(svc, intf_id,
+						 GB_SVC_UNIPRO_HS_SERIES_A,
+						 GB_SVC_UNIPRO_FAST_MODE, 2, 2,
+						 GB_SVC_SMALL_AMPLITUDE,
+						 GB_SVC_NO_DE_EMPHASIS,
+						 GB_SVC_UNIPRO_FAST_MODE, 2, 2,
+						 GB_SVC_PWRM_RXTERMINATION |
+						 GB_SVC_PWRM_TXTERMINATION, 0,
+						 NULL, NULL);
+	else
+		ret = gb_svc_intf_set_power_mode(svc, intf_id,
+						 GB_SVC_UNIPRO_HS_SERIES_A,
+						 GB_SVC_UNIPRO_SLOW_AUTO_MODE,
+						 2, 1,
+						 GB_SVC_SMALL_AMPLITUDE,
+						 GB_SVC_NO_DE_EMPHASIS,
+						 GB_SVC_UNIPRO_SLOW_AUTO_MODE,
+						 2, 1,
+						 0, 0,
+						 NULL, NULL);
+
+	return ret;
+}
+
+static int gb_camera_set_power_mode(struct gb_camera *gcam, bool hs)
+{
+	struct gb_interface *intf = gcam->connection->intf;
+	struct gb_svc *svc = gcam->connection->hd->svc;
+	int ret;
+
+	ret = gb_camera_set_intf_power_mode(gcam, intf->interface_id, hs);
+	if (ret < 0) {
+		gcam_err(gcam, "failed to set module interface to %s (%d)\n",
+			 hs ? "HS" : "PWM", ret);
+		return ret;
+	}
+
+	ret = gb_camera_set_intf_power_mode(gcam, svc->ap_intf_id, hs);
+	if (ret < 0) {
+		gb_camera_set_intf_power_mode(gcam, intf->interface_id, !hs);
+		gcam_err(gcam, "failed to set AP interface to %s (%d)\n",
+			 hs ? "HS" : "PWM", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+struct ap_csi_config_request {
+	__u8 csi_id;
+	__u8 flags;
+#define GB_CAMERA_CSI_FLAG_CLOCK_CONTINUOUS 0x01
+	__u8 num_lanes;
+	__u8 padding;
+	__le32 csi_clk_freq;
+	__le32 max_pkt_size;
+} __packed;
+
+/*
+ * TODO: Compute the number of lanes dynamically based on bandwidth
+ * requirements.
+ */
+#define GB_CAMERA_CSI_NUM_DATA_LANES		4
+
+#define GB_CAMERA_CSI_CLK_FREQ_MAX		999000000U
+#define GB_CAMERA_CSI_CLK_FREQ_MIN		100000000U
+#define GB_CAMERA_CSI_CLK_FREQ_MARGIN		150000000U
+
+static int gb_camera_setup_data_connection(struct gb_camera *gcam,
+		struct gb_camera_configure_streams_response *resp,
+		struct gb_camera_csi_params *csi_params)
+{
+	struct ap_csi_config_request csi_cfg;
+	struct gb_connection *conn;
+	unsigned int clk_freq;
+	int ret;
+
+	/*
+	 * Create the data connection between the camera module data CPort and
+	 * APB CDSI1. The CDSI1 CPort ID is hardcoded by the ES2 bridge.
+	 */
+	conn = gb_connection_create_offloaded(gcam->bundle, gcam->data_cport_id,
+					      GB_CONNECTION_FLAG_NO_FLOWCTRL |
+					      GB_CONNECTION_FLAG_CDSI1);
+	if (IS_ERR(conn))
+		return PTR_ERR(conn);
+
+	gcam->data_connection = conn;
+	gb_connection_set_data(conn, gcam);
+
+	ret = gb_connection_enable(conn);
+	if (ret)
+		goto error_conn_destroy;
+
+	/* Set the UniPro link to high speed mode. */
+	ret = gb_camera_set_power_mode(gcam, true);
+	if (ret < 0)
+		goto error_conn_disable;
+
+	/*
+	 * Configure the APB-A CSI-2 transmitter.
+	 *
+	 * Hardcode the number of lanes to 4 and compute the bus clock frequency
+	 * based on the module bandwidth requirements with a safety margin.
+	 */
+	memset(&csi_cfg, 0, sizeof(csi_cfg));
+	csi_cfg.csi_id = 1;
+	csi_cfg.flags = 0;
+	csi_cfg.num_lanes = GB_CAMERA_CSI_NUM_DATA_LANES;
+
+	clk_freq = resp->data_rate / 2 / GB_CAMERA_CSI_NUM_DATA_LANES;
+	clk_freq = clamp(clk_freq + GB_CAMERA_CSI_CLK_FREQ_MARGIN,
+			 GB_CAMERA_CSI_CLK_FREQ_MIN,
+			 GB_CAMERA_CSI_CLK_FREQ_MAX);
+	csi_cfg.csi_clk_freq = clk_freq;
+
+	ret = gb_camera_get_max_pkt_size(gcam, resp);
+	if (ret < 0) {
+		ret = -EIO;
+		goto error_power;
+	}
+	csi_cfg.max_pkt_size = ret;
+
+	ret = gb_hd_output(gcam->connection->hd, &csi_cfg,
+			   sizeof(csi_cfg),
+			   GB_APB_REQUEST_CSI_TX_CONTROL, false);
+	if (ret < 0) {
+		gcam_err(gcam, "failed to start the CSI transmitter\n");
+		goto error_power;
+	}
+
+	if (csi_params) {
+		csi_params->clk_freq = csi_cfg.csi_clk_freq;
+		csi_params->num_lanes = csi_cfg.num_lanes;
+	}
+
+	return 0;
+
+error_power:
+	gb_camera_set_power_mode(gcam, false);
+error_conn_disable:
+	gb_connection_disable(gcam->data_connection);
+error_conn_destroy:
+	gb_connection_destroy(gcam->data_connection);
+	gcam->data_connection = NULL;
+	return ret;
+}
+
+static void gb_camera_teardown_data_connection(struct gb_camera *gcam)
+{
+	struct ap_csi_config_request csi_cfg;
+	int ret;
+
+	/* Stop the APB1 CSI transmitter. */
+	memset(&csi_cfg, 0, sizeof(csi_cfg));
+	csi_cfg.csi_id = 1;
+
+	ret = gb_hd_output(gcam->connection->hd, &csi_cfg,
+			   sizeof(csi_cfg),
+			   GB_APB_REQUEST_CSI_TX_CONTROL, false);
+
+	if (ret < 0)
+		gcam_err(gcam, "failed to stop the CSI transmitter\n");
+
+	/* Set the UniPro link to low speed mode. */
+	gb_camera_set_power_mode(gcam, false);
+
+	/* Destroy the data connection. */
+	gb_connection_disable(gcam->data_connection);
+	gb_connection_destroy(gcam->data_connection);
+	gcam->data_connection = NULL;
+}
+
+/* -----------------------------------------------------------------------------
+ * Camera Protocol Operations
+ */
+
+static int gb_camera_capabilities(struct gb_camera *gcam,
+				  u8 *capabilities, size_t *size)
+{
+	int ret;
+
+	ret = gb_pm_runtime_get_sync(gcam->bundle);
+	if (ret)
+		return ret;
+
+	mutex_lock(&gcam->mutex);
+
+	if (!gcam->connection) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	ret = gb_camera_operation_sync_flags(gcam->connection,
+					     GB_CAMERA_TYPE_CAPABILITIES,
+					     GB_OPERATION_FLAG_SHORT_RESPONSE,
+					     NULL, 0,
+					     (void *)capabilities, size);
+	if (ret)
+		gcam_err(gcam, "failed to retrieve capabilities: %d\n", ret);
+
+done:
+	mutex_unlock(&gcam->mutex);
+
+	gb_pm_runtime_put_autosuspend(gcam->bundle);
+
+	return ret;
+}
+
+static int gb_camera_configure_streams(struct gb_camera *gcam,
+				       unsigned int *num_streams,
+				       unsigned int *flags,
+				       struct gb_camera_stream_config *streams,
+				       struct gb_camera_csi_params *csi_params)
+{
+	struct gb_camera_configure_streams_request *req;
+	struct gb_camera_configure_streams_response *resp;
+	unsigned int nstreams = *num_streams;
+	unsigned int i;
+	size_t req_size;
+	size_t resp_size;
+	int ret;
+
+	if (nstreams > GB_CAMERA_MAX_STREAMS)
+		return -EINVAL;
+
+	req_size = sizeof(*req) + nstreams * sizeof(req->config[0]);
+	resp_size = sizeof(*resp) + nstreams * sizeof(resp->config[0]);
+
+	req = kmalloc(req_size, GFP_KERNEL);
+	resp = kmalloc(resp_size, GFP_KERNEL);
+	if (!req || !resp) {
+		kfree(req);
+		kfree(resp);
+		return -ENOMEM;
+	}
+
+	req->num_streams = nstreams;
+	req->flags = *flags;
+	req->padding = 0;
+
+	for (i = 0; i < nstreams; ++i) {
+		struct gb_camera_stream_config_request *cfg = &req->config[i];
+
+		cfg->width = cpu_to_le16(streams[i].width);
+		cfg->height = cpu_to_le16(streams[i].height);
+		cfg->format = cpu_to_le16(streams[i].format);
+		cfg->padding = 0;
+	}
+
+	mutex_lock(&gcam->mutex);
+
+	ret = gb_pm_runtime_get_sync(gcam->bundle);
+	if (ret)
+		goto done_skip_pm_put;
+
+	if (!gcam->connection) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	ret = gb_camera_operation_sync_flags(gcam->connection,
+					     GB_CAMERA_TYPE_CONFIGURE_STREAMS,
+					     GB_OPERATION_FLAG_SHORT_RESPONSE,
+					     req, req_size,
+					     resp, &resp_size);
+	if (ret < 0)
+		goto done;
+
+	ret = gb_camera_configure_streams_validate_response(gcam, resp,
+							    nstreams);
+	if (ret < 0)
+		goto done;
+
+	*flags = resp->flags;
+	*num_streams = resp->num_streams;
+
+	for (i = 0; i < resp->num_streams; ++i) {
+		struct gb_camera_stream_config_response *cfg = &resp->config[i];
+
+		streams[i].width = le16_to_cpu(cfg->width);
+		streams[i].height = le16_to_cpu(cfg->height);
+		streams[i].format = le16_to_cpu(cfg->format);
+		streams[i].vc = cfg->virtual_channel;
+		streams[i].dt[0] = cfg->data_type[0];
+		streams[i].dt[1] = cfg->data_type[1];
+		streams[i].max_size = le32_to_cpu(cfg->max_size);
+	}
+
+	if ((resp->flags & GB_CAMERA_CONFIGURE_STREAMS_ADJUSTED) ||
+	    (req->flags & GB_CAMERA_CONFIGURE_STREAMS_TEST_ONLY))
+		goto done;
+
+	if (gcam->state == GB_CAMERA_STATE_CONFIGURED) {
+		gb_camera_teardown_data_connection(gcam);
+		gcam->state = GB_CAMERA_STATE_UNCONFIGURED;
+
+		/*
+		 * When unconfiguring streams release the PM runtime reference
+		 * that was acquired when streams were configured. The bundle
+		 * won't be suspended until the PM runtime reference acquired at
+		 * the beginning of this function gets released right before
+		 * returning.
+		 */
+		gb_pm_runtime_put_noidle(gcam->bundle);
+	}
+
+	if (resp->num_streams == 0)
+		goto done;
+
+	/*
+	 * Make sure the bundle won't be suspended until streams get
+	 * unconfigured after the stream is configured successfully
+	 */
+	gb_pm_runtime_get_noresume(gcam->bundle);
+
+	/* Setup CSI-2 connection from APB-A to AP */
+	ret = gb_camera_setup_data_connection(gcam, resp, csi_params);
+	if (ret < 0) {
+		memset(req, 0, sizeof(*req));
+		gb_operation_sync(gcam->connection,
+				  GB_CAMERA_TYPE_CONFIGURE_STREAMS,
+				  req, sizeof(*req),
+				  resp, sizeof(*resp));
+		*flags = 0;
+		*num_streams = 0;
+		gb_pm_runtime_put_noidle(gcam->bundle);
+		goto done;
+	}
+
+	gcam->state = GB_CAMERA_STATE_CONFIGURED;
+
+done:
+	gb_pm_runtime_put_autosuspend(gcam->bundle);
+
+done_skip_pm_put:
+	mutex_unlock(&gcam->mutex);
+	kfree(req);
+	kfree(resp);
+	return ret;
+}
+
+static int gb_camera_capture(struct gb_camera *gcam, u32 request_id,
+			     unsigned int streams, unsigned int num_frames,
+			     size_t settings_size, const void *settings)
+{
+	struct gb_camera_capture_request *req;
+	size_t req_size;
+	int ret;
+
+	if (settings_size > GB_CAMERA_MAX_SETTINGS_SIZE)
+		return -EINVAL;
+
+	req_size = sizeof(*req) + settings_size;
+	req = kmalloc(req_size, GFP_KERNEL);
+	if (!req)
+		return -ENOMEM;
+
+	req->request_id = cpu_to_le32(request_id);
+	req->streams = streams;
+	req->padding = 0;
+	req->num_frames = cpu_to_le16(num_frames);
+	memcpy(req->settings, settings, settings_size);
+
+	mutex_lock(&gcam->mutex);
+
+	if (!gcam->connection) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	ret = gb_operation_sync(gcam->connection, GB_CAMERA_TYPE_CAPTURE,
+				req, req_size, NULL, 0);
+done:
+	mutex_unlock(&gcam->mutex);
+
+	kfree(req);
+
+	return ret;
+}
+
+static int gb_camera_flush(struct gb_camera *gcam, u32 *request_id)
+{
+	struct gb_camera_flush_response resp;
+	int ret;
+
+	mutex_lock(&gcam->mutex);
+
+	if (!gcam->connection) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	ret = gb_operation_sync(gcam->connection, GB_CAMERA_TYPE_FLUSH, NULL, 0,
+				&resp, sizeof(resp));
+
+	if (ret < 0)
+		goto done;
+
+	if (request_id)
+		*request_id = le32_to_cpu(resp.request_id);
+
+done:
+	mutex_unlock(&gcam->mutex);
+
+	return ret;
+}
+
+static int gb_camera_request_handler(struct gb_operation *op)
+{
+	struct gb_camera *gcam = gb_connection_get_data(op->connection);
+	struct gb_camera_metadata_request *payload;
+	struct gb_message *request;
+
+	if (op->type != GB_CAMERA_TYPE_METADATA) {
+		gcam_err(gcam, "Unsupported unsolicited event: %u\n", op->type);
+		return -EINVAL;
+	}
+
+	request = op->request;
+
+	if (request->payload_size < sizeof(*payload)) {
+		gcam_err(gcam, "Wrong event size received (%zu < %zu)\n",
+			 request->payload_size, sizeof(*payload));
+		return -EINVAL;
+	}
+
+	payload = request->payload;
+
+	gcam_dbg(gcam, "received metadata for request %u, frame %u, stream %u\n",
+		 payload->request_id, payload->frame_number, payload->stream);
+
+	return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Interface with HOST gmp camera.
+ */
+static unsigned int gb_camera_mbus_to_gb(enum v4l2_mbus_pixelcode mbus_code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(gb_fmt_info); i++) {
+		if (gb_fmt_info[i].mbus_code == mbus_code)
+			return gb_fmt_info[i].gb_format;
+	}
+	return gb_fmt_info[0].gb_format;
+}
+
+static enum v4l2_mbus_pixelcode gb_camera_gb_to_mbus(u16 gb_fmt)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(gb_fmt_info); i++) {
+		if (gb_fmt_info[i].gb_format == gb_fmt)
+			return gb_fmt_info[i].mbus_code;
+	}
+	return gb_fmt_info[0].mbus_code;
+}
+
+static ssize_t gb_camera_op_capabilities(void *priv, char *data, size_t len)
+{
+	struct gb_camera *gcam = priv;
+	size_t capabilities_len = len;
+	int ret;
+
+	ret = gb_camera_capabilities(gcam, data, &capabilities_len);
+	if (ret)
+		return ret;
+
+	return capabilities_len;
+}
+
+static int gb_camera_op_configure_streams(void *priv, unsigned int *nstreams,
+		unsigned int *flags, struct gb_camera_stream *streams,
+		struct gb_camera_csi_params *csi_params)
+{
+	struct gb_camera *gcam = priv;
+	struct gb_camera_stream_config *gb_streams;
+	unsigned int gb_flags = 0;
+	unsigned int gb_nstreams = *nstreams;
+	unsigned int i;
+	int ret;
+
+	if (gb_nstreams > GB_CAMERA_MAX_STREAMS)
+		return -EINVAL;
+
+	gb_streams = kzalloc(gb_nstreams * sizeof(*gb_streams), GFP_KERNEL);
+	if (!gb_streams)
+		return -ENOMEM;
+
+	for (i = 0; i < gb_nstreams; i++) {
+		gb_streams[i].width = streams[i].width;
+		gb_streams[i].height = streams[i].height;
+		gb_streams[i].format =
+			gb_camera_mbus_to_gb(streams[i].pixel_code);
+	}
+
+	if (*flags & GB_CAMERA_IN_FLAG_TEST)
+		gb_flags |= GB_CAMERA_CONFIGURE_STREAMS_TEST_ONLY;
+
+	ret = gb_camera_configure_streams(gcam, &gb_nstreams,
+					  &gb_flags, gb_streams, csi_params);
+	if (ret < 0)
+		goto done;
+	if (gb_nstreams > *nstreams) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	*flags = 0;
+	if (gb_flags & GB_CAMERA_CONFIGURE_STREAMS_ADJUSTED)
+		*flags |= GB_CAMERA_OUT_FLAG_ADJUSTED;
+
+	for (i = 0; i < gb_nstreams; i++) {
+		streams[i].width = gb_streams[i].width;
+		streams[i].height = gb_streams[i].height;
+		streams[i].vc = gb_streams[i].vc;
+		streams[i].dt[0] = gb_streams[i].dt[0];
+		streams[i].dt[1] = gb_streams[i].dt[1];
+		streams[i].max_size = gb_streams[i].max_size;
+		streams[i].pixel_code =
+			gb_camera_gb_to_mbus(gb_streams[i].format);
+	}
+	*nstreams = gb_nstreams;
+
+done:
+	kfree(gb_streams);
+	return ret;
+}
+
+static int gb_camera_op_capture(void *priv, u32 request_id,
+		unsigned int streams, unsigned int num_frames,
+		size_t settings_size, const void *settings)
+{
+	struct gb_camera *gcam = priv;
+
+	return gb_camera_capture(gcam, request_id, streams, num_frames,
+				 settings_size, settings);
+}
+
+static int gb_camera_op_flush(void *priv, u32 *request_id)
+{
+	struct gb_camera *gcam = priv;
+
+	return gb_camera_flush(gcam, request_id);
+}
+
+static const struct gb_camera_ops gb_cam_ops = {
+	.capabilities = gb_camera_op_capabilities,
+	.configure_streams = gb_camera_op_configure_streams,
+	.capture = gb_camera_op_capture,
+	.flush = gb_camera_op_flush,
+};
+
+/* -----------------------------------------------------------------------------
+ * DebugFS
+ */
+
+static ssize_t gb_camera_debugfs_capabilities(struct gb_camera *gcam,
+		char *buf, size_t len)
+{
+	struct gb_camera_debugfs_buffer *buffer =
+		&gcam->debugfs.buffers[GB_CAMERA_DEBUGFS_BUFFER_CAPABILITIES];
+	size_t size = 1024;
+	unsigned int i;
+	u8 *caps;
+	int ret;
+
+	caps = kmalloc(size, GFP_KERNEL);
+	if (!caps)
+		return -ENOMEM;
+
+	ret = gb_camera_capabilities(gcam, caps, &size);
+	if (ret < 0)
+		goto done;
+
+	/*
+	 * hex_dump_to_buffer() doesn't return the number of bytes dumped prior
+	 * to v4.0, we need our own implementation :-(
+	 */
+	buffer->length = 0;
+
+	for (i = 0; i < size; i += 16) {
+		unsigned int nbytes = min_t(unsigned int, size - i, 16);
+
+		buffer->length += sprintf(buffer->data + buffer->length,
+					  "%*ph\n", nbytes, caps + i);
+	}
+
+done:
+	kfree(caps);
+	return ret;
+}
+
+static ssize_t gb_camera_debugfs_configure_streams(struct gb_camera *gcam,
+		char *buf, size_t len)
+{
+	struct gb_camera_debugfs_buffer *buffer =
+		&gcam->debugfs.buffers[GB_CAMERA_DEBUGFS_BUFFER_STREAMS];
+	struct gb_camera_stream_config *streams;
+	unsigned int nstreams;
+	unsigned int flags;
+	unsigned int i;
+	char *token;
+	int ret;
+
+	/* Retrieve number of streams to configure */
+	token = strsep(&buf, ";");
+	if (token == NULL)
+		return -EINVAL;
+
+	ret = kstrtouint(token, 10, &nstreams);
+	if (ret < 0)
+		return ret;
+
+	if (nstreams > GB_CAMERA_MAX_STREAMS)
+		return -EINVAL;
+
+	token = strsep(&buf, ";");
+	if (token == NULL)
+		return -EINVAL;
+
+	ret = kstrtouint(token, 10, &flags);
+	if (ret < 0)
+		return ret;
+
+	/* For each stream to configure parse width, height and format */
+	streams = kzalloc(nstreams * sizeof(*streams), GFP_KERNEL);
+	if (!streams)
+		return -ENOMEM;
+
+	for (i = 0; i < nstreams; ++i) {
+		struct gb_camera_stream_config *stream = &streams[i];
+
+		/* width */
+		token = strsep(&buf, ";");
+		if (token == NULL) {
+			ret = -EINVAL;
+			goto done;
+		}
+		ret = kstrtouint(token, 10, &stream->width);
+		if (ret < 0)
+			goto done;
+
+		/* height */
+		token = strsep(&buf, ";");
+		if (token == NULL)
+			goto done;
+
+		ret = kstrtouint(token, 10, &stream->height);
+		if (ret < 0)
+			goto done;
+
+		/* Image format code */
+		token = strsep(&buf, ";");
+		if (token == NULL)
+			goto done;
+
+		ret = kstrtouint(token, 16, &stream->format);
+		if (ret < 0)
+			goto done;
+	}
+
+	ret = gb_camera_configure_streams(gcam, &nstreams, &flags, streams,
+					  NULL);
+	if (ret < 0)
+		goto done;
+
+	buffer->length = sprintf(buffer->data, "%u;%u;", nstreams, flags);
+
+	for (i = 0; i < nstreams; ++i) {
+		struct gb_camera_stream_config *stream = &streams[i];
+
+		buffer->length += sprintf(buffer->data + buffer->length,
+					  "%u;%u;%u;%u;%u;%u;%u;",
+					  stream->width, stream->height,
+					  stream->format, stream->vc,
+					  stream->dt[0], stream->dt[1],
+					  stream->max_size);
+	}
+
+	ret = len;
+
+done:
+	kfree(streams);
+	return ret;
+};
+
+static ssize_t gb_camera_debugfs_capture(struct gb_camera *gcam,
+		char *buf, size_t len)
+{
+	unsigned int request_id;
+	unsigned int streams_mask;
+	unsigned int num_frames;
+	char *token;
+	int ret;
+
+	/* Request id */
+	token = strsep(&buf, ";");
+	if (token == NULL)
+		return -EINVAL;
+	ret = kstrtouint(token, 10, &request_id);
+	if (ret < 0)
+		return ret;
+
+	/* Stream mask */
+	token = strsep(&buf, ";");
+	if (token == NULL)
+		return -EINVAL;
+	ret = kstrtouint(token, 16, &streams_mask);
+	if (ret < 0)
+		return ret;
+
+	/* number of frames */
+	token = strsep(&buf, ";");
+	if (token == NULL)
+		return -EINVAL;
+	ret = kstrtouint(token, 10, &num_frames);
+	if (ret < 0)
+		return ret;
+
+	ret = gb_camera_capture(gcam, request_id, streams_mask, num_frames, 0,
+				NULL);
+	if (ret < 0)
+		return ret;
+
+	return len;
+}
+
+static ssize_t gb_camera_debugfs_flush(struct gb_camera *gcam,
+		char *buf, size_t len)
+{
+	struct gb_camera_debugfs_buffer *buffer =
+		&gcam->debugfs.buffers[GB_CAMERA_DEBUGFS_BUFFER_FLUSH];
+	unsigned int req_id;
+	int ret;
+
+	ret = gb_camera_flush(gcam, &req_id);
+	if (ret < 0)
+		return ret;
+
+	buffer->length = sprintf(buffer->data, "%u", req_id);
+
+	return len;
+}
+
+struct gb_camera_debugfs_entry {
+	const char *name;
+	unsigned int mask;
+	unsigned int buffer;
+	ssize_t (*execute)(struct gb_camera *gcam, char *buf, size_t len);
+};
+
+static const struct gb_camera_debugfs_entry gb_camera_debugfs_entries[] = {
+	{
+		.name = "capabilities",
+		.mask = S_IFREG | S_IRUGO,
+		.buffer = GB_CAMERA_DEBUGFS_BUFFER_CAPABILITIES,
+		.execute = gb_camera_debugfs_capabilities,
+	}, {
+		.name = "configure_streams",
+		.mask = S_IFREG | S_IRUGO | S_IWUGO,
+		.buffer = GB_CAMERA_DEBUGFS_BUFFER_STREAMS,
+		.execute = gb_camera_debugfs_configure_streams,
+	}, {
+		.name = "capture",
+		.mask = S_IFREG | S_IRUGO | S_IWUGO,
+		.buffer = GB_CAMERA_DEBUGFS_BUFFER_CAPTURE,
+		.execute = gb_camera_debugfs_capture,
+	}, {
+		.name = "flush",
+		.mask = S_IFREG | S_IRUGO | S_IWUGO,
+		.buffer = GB_CAMERA_DEBUGFS_BUFFER_FLUSH,
+		.execute = gb_camera_debugfs_flush,
+	},
+};
+
+static ssize_t gb_camera_debugfs_read(struct file *file, char __user *buf,
+				      size_t len, loff_t *offset)
+{
+	const struct gb_camera_debugfs_entry *op = file->private_data;
+	struct gb_camera *gcam = file->f_inode->i_private;
+	struct gb_camera_debugfs_buffer *buffer;
+	ssize_t ret;
+
+	/* For read-only entries the operation is triggered by a read. */
+	if (!(op->mask & S_IWUGO)) {
+		ret = op->execute(gcam, NULL, 0);
+		if (ret < 0)
+			return ret;
+	}
+
+	buffer = &gcam->debugfs.buffers[op->buffer];
+
+	return simple_read_from_buffer(buf, len, offset, buffer->data,
+				       buffer->length);
+}
+
+static ssize_t gb_camera_debugfs_write(struct file *file,
+				       const char __user *buf, size_t len,
+				       loff_t *offset)
+{
+	const struct gb_camera_debugfs_entry *op = file->private_data;
+	struct gb_camera *gcam = file->f_inode->i_private;
+	ssize_t ret;
+	char *kbuf;
+
+	if (len > 1024)
+	       return -EINVAL;
+
+	kbuf = kmalloc(len + 1, GFP_KERNEL);
+	if (kbuf == NULL)
+		return -ENOMEM;
+
+	if (copy_from_user(kbuf, buf, len)) {
+		ret = -EFAULT;
+		goto done;
+	}
+
+	kbuf[len] = '\0';
+
+	ret = op->execute(gcam, kbuf, len);
+
+done:
+	kfree(kbuf);
+	return ret;
+}
+
+static int gb_camera_debugfs_open(struct inode *inode, struct file *file)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(gb_camera_debugfs_entries); ++i) {
+		const struct gb_camera_debugfs_entry *entry =
+			&gb_camera_debugfs_entries[i];
+
+		if (!strcmp(file->f_path.dentry->d_iname, entry->name)) {
+			file->private_data = (void *)entry;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static const struct file_operations gb_camera_debugfs_ops = {
+	.open = gb_camera_debugfs_open,
+	.read = gb_camera_debugfs_read,
+	.write = gb_camera_debugfs_write,
+};
+
+static int gb_camera_debugfs_init(struct gb_camera *gcam)
+{
+	struct gb_connection *connection = gcam->connection;
+	char dirname[27];
+	unsigned int i;
+
+	/*
+	 * Create root debugfs entry and a file entry for each camera operation.
+	 */
+	snprintf(dirname, 27, "camera-%u.%u", connection->intf->interface_id,
+		 gcam->bundle->id);
+
+	gcam->debugfs.root = debugfs_create_dir(dirname, gb_debugfs_get());
+	if (IS_ERR(gcam->debugfs.root)) {
+		gcam_err(gcam, "debugfs root create failed (%ld)\n",
+			 PTR_ERR(gcam->debugfs.root));
+		return PTR_ERR(gcam->debugfs.root);
+	}
+
+	gcam->debugfs.buffers = vmalloc(sizeof(*gcam->debugfs.buffers) *
+					GB_CAMERA_DEBUGFS_BUFFER_MAX);
+	if (!gcam->debugfs.buffers)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(gb_camera_debugfs_entries); ++i) {
+		const struct gb_camera_debugfs_entry *entry =
+			&gb_camera_debugfs_entries[i];
+		struct dentry *dentry;
+
+		gcam->debugfs.buffers[i].length = 0;
+
+		dentry = debugfs_create_file(entry->name, entry->mask,
+					     gcam->debugfs.root, gcam,
+					     &gb_camera_debugfs_ops);
+		if (IS_ERR(dentry)) {
+			gcam_err(gcam,
+				 "debugfs operation %s create failed (%ld)\n",
+				 entry->name, PTR_ERR(dentry));
+			return PTR_ERR(dentry);
+		}
+	}
+
+	return 0;
+}
+
+static void gb_camera_debugfs_cleanup(struct gb_camera *gcam)
+{
+	debugfs_remove_recursive(gcam->debugfs.root);
+
+	vfree(gcam->debugfs.buffers);
+}
+
+/* -----------------------------------------------------------------------------
+ * Init & Cleanup
+ */
+
+static void gb_camera_cleanup(struct gb_camera *gcam)
+{
+	gb_camera_debugfs_cleanup(gcam);
+
+	mutex_lock(&gcam->mutex);
+	if (gcam->data_connection) {
+		gb_connection_disable(gcam->data_connection);
+		gb_connection_destroy(gcam->data_connection);
+		gcam->data_connection = NULL;
+	}
+
+	if (gcam->connection) {
+		gb_connection_disable(gcam->connection);
+		gb_connection_destroy(gcam->connection);
+		gcam->connection = NULL;
+	}
+	mutex_unlock(&gcam->mutex);
+}
+
+static void gb_camera_release_module(struct kref *ref)
+{
+	struct gb_camera_module *cam_mod =
+		container_of(ref, struct gb_camera_module, refcount);
+	kfree(cam_mod->priv);
+}
+
+static int gb_camera_probe(struct gb_bundle *bundle,
+			   const struct greybus_bundle_id *id)
+{
+	struct gb_connection *conn;
+	struct gb_camera *gcam;
+	u16 mgmt_cport_id = 0;
+	u16 data_cport_id = 0;
+	unsigned int i;
+	int ret;
+
+	/*
+	 * The camera bundle must contain exactly two CPorts, one for the
+	 * camera management protocol and one for the camera data protocol.
+	 */
+	if (bundle->num_cports != 2)
+		return -ENODEV;
+
+	for (i = 0; i < bundle->num_cports; ++i) {
+		struct greybus_descriptor_cport *desc = &bundle->cport_desc[i];
+
+		switch (desc->protocol_id) {
+		case GREYBUS_PROTOCOL_CAMERA_MGMT:
+			mgmt_cport_id = le16_to_cpu(desc->id);
+			break;
+		case GREYBUS_PROTOCOL_CAMERA_DATA:
+			data_cport_id = le16_to_cpu(desc->id);
+			break;
+		default:
+			return -ENODEV;
+		}
+	}
+
+	if (!mgmt_cport_id || !data_cport_id)
+		return -ENODEV;
+
+	gcam = kzalloc(sizeof(*gcam), GFP_KERNEL);
+	if (!gcam)
+		return -ENOMEM;
+
+	mutex_init(&gcam->mutex);
+
+	gcam->bundle = bundle;
+	gcam->state = GB_CAMERA_STATE_UNCONFIGURED;
+	gcam->data_cport_id = data_cport_id;
+
+	conn = gb_connection_create(bundle, mgmt_cport_id,
+				    gb_camera_request_handler);
+	if (IS_ERR(conn)) {
+		ret = PTR_ERR(conn);
+		goto error;
+	}
+
+	gcam->connection = conn;
+	gb_connection_set_data(conn, gcam);
+
+	ret = gb_connection_enable(conn);
+	if (ret)
+		goto error;
+
+	ret = gb_camera_debugfs_init(gcam);
+	if (ret < 0)
+		goto error;
+
+	gcam->module.priv = gcam;
+	gcam->module.ops = &gb_cam_ops;
+	gcam->module.interface_id = gcam->connection->intf->interface_id;
+	gcam->module.release = gb_camera_release_module;
+	ret = gb_camera_register(&gcam->module);
+	if (ret < 0)
+		goto error;
+
+	greybus_set_drvdata(bundle, gcam);
+
+	gb_pm_runtime_put_autosuspend(gcam->bundle);
+
+	return 0;
+
+error:
+	gb_camera_cleanup(gcam);
+	kfree(gcam);
+	return ret;
+}
+
+static void gb_camera_disconnect(struct gb_bundle *bundle)
+{
+	struct gb_camera *gcam = greybus_get_drvdata(bundle);
+	int ret;
+
+	ret = gb_pm_runtime_get_sync(bundle);
+	if (ret)
+		gb_pm_runtime_get_noresume(bundle);
+
+	gb_camera_cleanup(gcam);
+	gb_camera_unregister(&gcam->module);
+}
+
+static const struct greybus_bundle_id gb_camera_id_table[] = {
+	{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_CAMERA) },
+	{ },
+};
+
+#ifdef CONFIG_PM
+static int gb_camera_suspend(struct device *dev)
+{
+	struct gb_bundle *bundle = to_gb_bundle(dev);
+	struct gb_camera *gcam = greybus_get_drvdata(bundle);
+
+	if (gcam->data_connection)
+		gb_connection_disable(gcam->data_connection);
+
+	gb_connection_disable(gcam->connection);
+
+	return 0;
+}
+
+static int gb_camera_resume(struct device *dev)
+{
+	struct gb_bundle *bundle = to_gb_bundle(dev);
+	struct gb_camera *gcam = greybus_get_drvdata(bundle);
+	int ret;
+
+	ret = gb_connection_enable(gcam->connection);
+	if (ret) {
+		gcam_err(gcam, "failed to enable connection: %d\n", ret);
+		return ret;
+	}
+
+	if (gcam->data_connection) {
+		ret = gb_connection_enable(gcam->data_connection);
+		if (ret) {
+			gcam_err(gcam,
+				 "failed to enable data connection: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_camera_pm_ops = {
+	SET_RUNTIME_PM_OPS(gb_camera_suspend, gb_camera_resume, NULL)
+};
+
+static struct greybus_driver gb_camera_driver = {
+	.name		= "camera",
+	.probe		= gb_camera_probe,
+	.disconnect	= gb_camera_disconnect,
+	.id_table	= gb_camera_id_table,
+	.driver.pm	= &gb_camera_pm_ops,
+};
+
+module_greybus_driver(gb_camera_driver);
+
+MODULE_LICENSE("GPL v2");
drivers/greybus/gb-camera.h new file +127 -0

127 lines not on this page · raw message

author maintainer
#42 lore raw

PATCH 12/32 greybus: es2 host driver

In reply to Greg KH

This is a Greybus host driver for the ES2/3 bridge chip from Toshiba.

---
 drivers/greybus/es2.c | 1597 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1597 insertions(+)
drivers/greybus/es2.c new file +1597 -0
@@ -0,0 +1,1597 @@
+/*
+ * Greybus "AP" USB driver for "ES2" controller chips
+ *
+ * Copyright 2014-2015 Google Inc.
+ * Copyright 2014-2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+#include <linux/kthread.h>
+#include <linux/sizes.h>
+#include <linux/usb.h>
+#include <linux/kfifo.h>
+#include <linux/debugfs.h>
+#include <linux/list.h>
+#include <asm/unaligned.h>
+
+#include "arpc.h"
+#include "greybus.h"
+#include "greybus_trace.h"
+#include "connection.h"
+
+
+/* Default timeout for USB vendor requests. */
+#define ES2_USB_CTRL_TIMEOUT	500
+
+/* Default timeout for ARPC CPort requests */
+#define ES2_ARPC_CPORT_TIMEOUT	500
+
+/* Fixed CPort numbers */
+#define ES2_CPORT_CDSI0		16
+#define ES2_CPORT_CDSI1		17
+
+/* Memory sizes for the buffers sent to/from the ES2 controller */
+#define ES2_GBUF_MSG_SIZE_MAX	2048
+
+/* Memory sizes for the ARPC buffers */
+#define ARPC_OUT_SIZE_MAX	U16_MAX
+#define ARPC_IN_SIZE_MAX	128
+
+static const struct usb_device_id id_table[] = {
+	{ USB_DEVICE(0x18d1, 0x1eaf) },
+	{ },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+#define APB1_LOG_SIZE		SZ_16K
+
+/*
+ * Number of CPort IN urbs in flight at any point in time.
+ * Adjust if we are having stalls in the USB buffer due to not enough urbs in
+ * flight.
+ */
+#define NUM_CPORT_IN_URB	4
+
+/* Number of CPort OUT urbs in flight at any point in time.
+ * Adjust if we get messages saying we are out of urbs in the system log.
+ */
+#define NUM_CPORT_OUT_URB	8
+
+/*
+ * Number of ARPC in urbs in flight at any point in time.
+ */
+#define NUM_ARPC_IN_URB		2
+
+/*
+ * @endpoint: bulk in endpoint for CPort data
+ * @urb: array of urbs for the CPort in messages
+ * @buffer: array of buffers for the @cport_in_urb urbs
+ */
+struct es2_cport_in {
+	__u8 endpoint;
+	struct urb *urb[NUM_CPORT_IN_URB];
+	u8 *buffer[NUM_CPORT_IN_URB];
+};
+
+/**
+ * es2_ap_dev - ES2 USB Bridge to AP structure
+ * @usb_dev: pointer to the USB device we are.
+ * @usb_intf: pointer to the USB interface we are bound to.
+ * @hd: pointer to our gb_host_device structure
+
+ * @cport_in: endpoint, urbs and buffer for cport in messages
+ * @cport_out_endpoint: endpoint for for cport out messages
+ * @cport_out_urb: array of urbs for the CPort out messages
+ * @cport_out_urb_busy: array of flags to see if the @cport_out_urb is busy or
+ *			not.
+ * @cport_out_urb_cancelled: array of flags indicating whether the
+ *			corresponding @cport_out_urb is being cancelled
+ * @cport_out_urb_lock: locks the @cport_out_urb_busy "list"
+ *
+ * @apb_log_task: task pointer for logging thread
+ * @apb_log_dentry: file system entry for the log file interface
+ * @apb_log_enable_dentry: file system entry for enabling logging
+ * @apb_log_fifo: kernel FIFO to carry logged data
+ * @arpc_urb: array of urbs for the ARPC in messages
+ * @arpc_buffer: array of buffers for the @arpc_urb urbs
+ * @arpc_endpoint_in: bulk in endpoint for APBridgeA RPC
+ * @arpc_id_cycle: gives an unique id to ARPC
+ * @arpc_lock: locks ARPC list
+ * @arpcs: list of in progress ARPCs
+ */
+struct es2_ap_dev {
+	struct usb_device *usb_dev;
+	struct usb_interface *usb_intf;
+	struct gb_host_device *hd;
+
+	struct es2_cport_in cport_in;
+	__u8 cport_out_endpoint;
+	struct urb *cport_out_urb[NUM_CPORT_OUT_URB];
+	bool cport_out_urb_busy[NUM_CPORT_OUT_URB];
+	bool cport_out_urb_cancelled[NUM_CPORT_OUT_URB];
+	spinlock_t cport_out_urb_lock;
+
+	bool cdsi1_in_use;
+
+	struct task_struct *apb_log_task;
+	struct dentry *apb_log_dentry;
+	struct dentry *apb_log_enable_dentry;
+	DECLARE_KFIFO(apb_log_fifo, char, APB1_LOG_SIZE);
+
+	__u8 arpc_endpoint_in;
+	struct urb *arpc_urb[NUM_ARPC_IN_URB];
+	u8 *arpc_buffer[NUM_ARPC_IN_URB];
+
+	int arpc_id_cycle;
+	spinlock_t arpc_lock;
+	struct list_head arpcs;
+};
+
+/**
+ * timesync_enable_request - Enable timesync in an APBridge
+ * @count: number of TimeSync Pulses to expect
+ * @frame_time: the initial FrameTime at the first TimeSync Pulse
+ * @strobe_delay: the expected delay in microseconds between each TimeSync Pulse
+ * @refclk: The AP mandated reference clock to run FrameTime at
+ */
+struct timesync_enable_request {
+	__u8	count;
+	__le64	frame_time;
+	__le32	strobe_delay;
+	__le32	refclk;
+} __packed;
+
+/**
+ * timesync_authoritative_request - Transmit authoritative FrameTime to APBridge
+ * @frame_time: An array of authoritative FrameTimes provided by the SVC
+ *              and relayed to the APBridge by the AP
+ */
+struct timesync_authoritative_request {
+	__le64	frame_time[GB_TIMESYNC_MAX_STROBES];
+} __packed;
+
+struct arpc {
+	struct list_head list;
+	struct arpc_request_message *req;
+	struct arpc_response_message *resp;
+	struct completion response_received;
+	bool active;
+};
+
+static inline struct es2_ap_dev *hd_to_es2(struct gb_host_device *hd)
+{
+	return (struct es2_ap_dev *)&hd->hd_priv;
+}
+
+static void cport_out_callback(struct urb *urb);
+static void usb_log_enable(struct es2_ap_dev *es2);
+static void usb_log_disable(struct es2_ap_dev *es2);
+static int arpc_sync(struct es2_ap_dev *es2, u8 type, void *payload,
+		     size_t size, int *result, unsigned int timeout);
+
+static int output_sync(struct es2_ap_dev *es2, void *req, u16 size, u8 cmd)
+{
+	struct usb_device *udev = es2->usb_dev;
+	u8 *data;
+	int retval;
+
+	data = kmalloc(size, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	memcpy(data, req, size);
+
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				 cmd,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE,
+				 0, 0, data, size, ES2_USB_CTRL_TIMEOUT);
+	if (retval < 0)
+		dev_err(&udev->dev, "%s: return error %d\n", __func__, retval);
+	else
+		retval = 0;
+
+	kfree(data);
+	return retval;
+}
+
+static void ap_urb_complete(struct urb *urb)
+{
+	struct usb_ctrlrequest *dr = urb->context;
+
+	kfree(dr);
+	usb_free_urb(urb);
+}
+
+static int output_async(struct es2_ap_dev *es2, void *req, u16 size, u8 cmd)
+{
+	struct usb_device *udev = es2->usb_dev;
+	struct urb *urb;
+	struct usb_ctrlrequest *dr;
+	u8 *buf;
+	int retval;
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!urb)
+		return -ENOMEM;
+
+	dr = kmalloc(sizeof(*dr) + size, GFP_ATOMIC);
+	if (!dr) {
+		usb_free_urb(urb);
+		return -ENOMEM;
+	}
+
+	buf = (u8 *)dr + sizeof(*dr);
+	memcpy(buf, req, size);
+
+	dr->bRequest = cmd;
+	dr->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE;
+	dr->wValue = 0;
+	dr->wIndex = 0;
+	dr->wLength = cpu_to_le16(size);
+
+	usb_fill_control_urb(urb, udev, usb_sndctrlpipe(udev, 0),
+			     (unsigned char *)dr, buf, size,
+			     ap_urb_complete, dr);
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval) {
+		usb_free_urb(urb);
+		kfree(dr);
+	}
+	return retval;
+}
+
+static int output(struct gb_host_device *hd, void *req, u16 size, u8 cmd,
+		     bool async)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+
+	if (async)
+		return output_async(es2, req, size, cmd);
+
+	return output_sync(es2, req, size, cmd);
+}
+
+static int es2_cport_in_enable(struct es2_ap_dev *es2,
+				struct es2_cport_in *cport_in)
+{
+	struct urb *urb;
+	int ret;
+	int i;
+
+	for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
+		urb = cport_in->urb[i];
+
+		ret = usb_submit_urb(urb, GFP_KERNEL);
+		if (ret) {
+			dev_err(&es2->usb_dev->dev,
+					"failed to submit in-urb: %d\n", ret);
+			goto err_kill_urbs;
+		}
+	}
+
+	return 0;
+
+err_kill_urbs:
+	for (--i; i >= 0; --i) {
+		urb = cport_in->urb[i];
+		usb_kill_urb(urb);
+	}
+
+	return ret;
+}
+
+static void es2_cport_in_disable(struct es2_ap_dev *es2,
+				struct es2_cport_in *cport_in)
+{
+	struct urb *urb;
+	int i;
+
+	for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
+		urb = cport_in->urb[i];
+		usb_kill_urb(urb);
+	}
+}
+
+static int es2_arpc_in_enable(struct es2_ap_dev *es2)
+{
+	struct urb *urb;
+	int ret;
+	int i;
+
+	for (i = 0; i < NUM_ARPC_IN_URB; ++i) {
+		urb = es2->arpc_urb[i];
+
+		ret = usb_submit_urb(urb, GFP_KERNEL);
+		if (ret) {
+			dev_err(&es2->usb_dev->dev,
+				"failed to submit arpc in-urb: %d\n", ret);
+			goto err_kill_urbs;
+		}
+	}
+
+	return 0;
+
+err_kill_urbs:
+	for (--i; i >= 0; --i) {
+		urb = es2->arpc_urb[i];
+		usb_kill_urb(urb);
+	}
+
+	return ret;
+}
+
+static void es2_arpc_in_disable(struct es2_ap_dev *es2)
+{
+	struct urb *urb;
+	int i;
+
+	for (i = 0; i < NUM_ARPC_IN_URB; ++i) {
+		urb = es2->arpc_urb[i];
+		usb_kill_urb(urb);
+	}
+}
+
+static struct urb *next_free_urb(struct es2_ap_dev *es2, gfp_t gfp_mask)
+{
+	struct urb *urb = NULL;
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
+
+	/* Look in our pool of allocated urbs first, as that's the "fastest" */
+	for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
+		if (es2->cport_out_urb_busy[i] == false &&
+				es2->cport_out_urb_cancelled[i] == false) {
+			es2->cport_out_urb_busy[i] = true;
+			urb = es2->cport_out_urb[i];
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
+	if (urb)
+		return urb;
+
+	/*
+	 * Crap, pool is empty, complain to the syslog and go allocate one
+	 * dynamically as we have to succeed.
+	 */
+	dev_dbg(&es2->usb_dev->dev,
+		"No free CPort OUT urbs, having to dynamically allocate one!\n");
+	return usb_alloc_urb(0, gfp_mask);
+}
+
+static void free_urb(struct es2_ap_dev *es2, struct urb *urb)
+{
+	unsigned long flags;
+	int i;
+	/*
+	 * See if this was an urb in our pool, if so mark it "free", otherwise
+	 * we need to free it ourselves.
+	 */
+	spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
+	for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
+		if (urb == es2->cport_out_urb[i]) {
+			es2->cport_out_urb_busy[i] = false;
+			urb = NULL;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
+
+	/* If urb is not NULL, then we need to free this urb */
+	usb_free_urb(urb);
+}
+
+/*
+ * We (ab)use the operation-message header pad bytes to transfer the
+ * cport id in order to minimise overhead.
+ */
+static void
+gb_message_cport_pack(struct gb_operation_msg_hdr *header, u16 cport_id)
+{
+	header->pad[0] = cport_id;
+}
+
+/* Clear the pad bytes used for the CPort id */
+static void gb_message_cport_clear(struct gb_operation_msg_hdr *header)
+{
+	header->pad[0] = 0;
+}
+
+/* Extract the CPort id packed into the header, and clear it */
+static u16 gb_message_cport_unpack(struct gb_operation_msg_hdr *header)
+{
+	u16 cport_id = header->pad[0];
+
+	gb_message_cport_clear(header);
+
+	return cport_id;
+}
+
+/*
+ * Returns zero if the message was successfully queued, or a negative errno
+ * otherwise.
+ */
+static int message_send(struct gb_host_device *hd, u16 cport_id,
+			struct gb_message *message, gfp_t gfp_mask)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct usb_device *udev = es2->usb_dev;
+	size_t buffer_size;
+	int retval;
+	struct urb *urb;
+	unsigned long flags;
+
+	/*
+	 * The data actually transferred will include an indication
+	 * of where the data should be sent.  Do one last check of
+	 * the target CPort id before filling it in.
+	 */
+	if (!cport_id_valid(hd, cport_id)) {
+		dev_err(&udev->dev, "invalid cport %u\n", cport_id);
+		return -EINVAL;
+	}
+
+	/* Find a free urb */
+	urb = next_free_urb(es2, gfp_mask);
+	if (!urb)
+		return -ENOMEM;
+
+	spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
+	message->hcpriv = urb;
+	spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
+
+	/* Pack the cport id into the message header */
+	gb_message_cport_pack(message->header, cport_id);
+
+	buffer_size = sizeof(*message->header) + message->payload_size;
+
+	usb_fill_bulk_urb(urb, udev,
+			  usb_sndbulkpipe(udev,
+					  es2->cport_out_endpoint),
+			  message->buffer, buffer_size,
+			  cport_out_callback, message);
+	urb->transfer_flags |= URB_ZERO_PACKET;
+
+	trace_gb_message_submit(message);
+
+	retval = usb_submit_urb(urb, gfp_mask);
+	if (retval) {
+		dev_err(&udev->dev, "failed to submit out-urb: %d\n", retval);
+
+		spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
+		message->hcpriv = NULL;
+		spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
+
+		free_urb(es2, urb);
+		gb_message_cport_clear(message->header);
+
+		return retval;
+	}
+
+	return 0;
+}
+
+/*
+ * Can not be called in atomic context.
+ */
+static void message_cancel(struct gb_message *message)
+{
+	struct gb_host_device *hd = message->operation->connection->hd;
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct urb *urb;
+	int i;
+
+	might_sleep();
+
+	spin_lock_irq(&es2->cport_out_urb_lock);
+	urb = message->hcpriv;
+
+	/* Prevent dynamically allocated urb from being deallocated. */
+	usb_get_urb(urb);
+
+	/* Prevent pre-allocated urb from being reused. */
+	for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
+		if (urb == es2->cport_out_urb[i]) {
+			es2->cport_out_urb_cancelled[i] = true;
+			break;
+		}
+	}
+	spin_unlock_irq(&es2->cport_out_urb_lock);
+
+	usb_kill_urb(urb);
+
+	if (i < NUM_CPORT_OUT_URB) {
+		spin_lock_irq(&es2->cport_out_urb_lock);
+		es2->cport_out_urb_cancelled[i] = false;
+		spin_unlock_irq(&es2->cport_out_urb_lock);
+	}
+
+	usb_free_urb(urb);
+}
+
+static int es2_cport_allocate(struct gb_host_device *hd, int cport_id,
+				unsigned long flags)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct ida *id_map = &hd->cport_id_map;
+	int ida_start, ida_end;
+
+	switch (cport_id) {
+	case ES2_CPORT_CDSI0:
+	case ES2_CPORT_CDSI1:
+		dev_err(&hd->dev, "cport %d not available\n", cport_id);
+		return -EBUSY;
+	}
+
+	if (flags & GB_CONNECTION_FLAG_OFFLOADED &&
+			flags & GB_CONNECTION_FLAG_CDSI1) {
+		if (es2->cdsi1_in_use) {
+			dev_err(&hd->dev, "CDSI1 already in use\n");
+			return -EBUSY;
+		}
+
+		es2->cdsi1_in_use = true;
+
+		return ES2_CPORT_CDSI1;
+	}
+
+	if (cport_id < 0) {
+		ida_start = 0;
+		ida_end = hd->num_cports;
+	} else if (cport_id < hd->num_cports) {
+		ida_start = cport_id;
+		ida_end = cport_id + 1;
+	} else {
+		dev_err(&hd->dev, "cport %d not available\n", cport_id);
+		return -EINVAL;
+	}
+
+	return ida_simple_get(id_map, ida_start, ida_end, GFP_KERNEL);
+}
+
+static void es2_cport_release(struct gb_host_device *hd, u16 cport_id)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+
+	switch (cport_id) {
+	case ES2_CPORT_CDSI1:
+		es2->cdsi1_in_use = false;
+		return;
+	}
+
+	ida_simple_remove(&hd->cport_id_map, cport_id);
+}
+
+static int cport_enable(struct gb_host_device *hd, u16 cport_id,
+			unsigned long flags)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct usb_device *udev = es2->usb_dev;
+	struct gb_apb_request_cport_flags *req;
+	u32 connection_flags;
+	int ret;
+
+	req = kzalloc(sizeof(*req), GFP_KERNEL);
+	if (!req)
+		return -ENOMEM;
+
+	connection_flags = 0;
+	if (flags & GB_CONNECTION_FLAG_CONTROL)
+		connection_flags |= GB_APB_CPORT_FLAG_CONTROL;
+	if (flags & GB_CONNECTION_FLAG_HIGH_PRIO)
+		connection_flags |= GB_APB_CPORT_FLAG_HIGH_PRIO;
+
+	req->flags = cpu_to_le32(connection_flags);
+
+	dev_dbg(&hd->dev, "%s - cport = %u, flags = %02x\n", __func__,
+			cport_id, connection_flags);
+
+	ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				GB_APB_REQUEST_CPORT_FLAGS,
+				USB_DIR_OUT | USB_TYPE_VENDOR |
+				USB_RECIP_INTERFACE, cport_id, 0,
+				req, sizeof(*req), ES2_USB_CTRL_TIMEOUT);
+	if (ret != sizeof(*req)) {
+		dev_err(&udev->dev, "failed to set cport flags for port %d\n",
+				cport_id);
+		if (ret >= 0)
+			ret = -EIO;
+
+		goto out;
+	}
+
+	ret = 0;
+out:
+	kfree(req);
+
+	return ret;
+}
+
+static int es2_cport_connected(struct gb_host_device *hd, u16 cport_id)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct device *dev = &es2->usb_dev->dev;
+	struct arpc_cport_connected_req req;
+	int ret;
+
+	req.cport_id = cpu_to_le16(cport_id);
+	ret = arpc_sync(es2, ARPC_TYPE_CPORT_CONNECTED, &req, sizeof(req),
+			NULL, ES2_ARPC_CPORT_TIMEOUT);
+	if (ret) {
+		dev_err(dev, "failed to set connected state for cport %u: %d\n",
+				cport_id, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int es2_cport_flush(struct gb_host_device *hd, u16 cport_id)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct device *dev = &es2->usb_dev->dev;
+	struct arpc_cport_flush_req req;
+	int ret;
+
+	req.cport_id = cpu_to_le16(cport_id);
+	ret = arpc_sync(es2, ARPC_TYPE_CPORT_FLUSH, &req, sizeof(req),
+			NULL, ES2_ARPC_CPORT_TIMEOUT);
+	if (ret) {
+		dev_err(dev, "failed to flush cport %u: %d\n", cport_id, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int es2_cport_shutdown(struct gb_host_device *hd, u16 cport_id,
+				u8 phase, unsigned int timeout)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct device *dev = &es2->usb_dev->dev;
+	struct arpc_cport_shutdown_req req;
+	int result;
+	int ret;
+
+	if (timeout > U16_MAX)
+		return -EINVAL;
+
+	req.cport_id = cpu_to_le16(cport_id);
+	req.timeout = cpu_to_le16(timeout);
+	req.phase = phase;
+	ret = arpc_sync(es2, ARPC_TYPE_CPORT_SHUTDOWN, &req, sizeof(req),
+			&result, ES2_ARPC_CPORT_TIMEOUT + timeout);
+	if (ret) {
+		dev_err(dev, "failed to send shutdown over cport %u: %d (%d)\n",
+				cport_id, ret, result);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int es2_cport_quiesce(struct gb_host_device *hd, u16 cport_id,
+				size_t peer_space, unsigned int timeout)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct device *dev = &es2->usb_dev->dev;
+	struct arpc_cport_quiesce_req req;
+	int result;
+	int ret;
+
+	if (peer_space > U16_MAX)
+		return -EINVAL;
+
+	if (timeout > U16_MAX)
+		return -EINVAL;
+
+	req.cport_id = cpu_to_le16(cport_id);
+	req.peer_space = cpu_to_le16(peer_space);
+	req.timeout = cpu_to_le16(timeout);
+	ret = arpc_sync(es2, ARPC_TYPE_CPORT_QUIESCE, &req, sizeof(req),
+			&result, ES2_ARPC_CPORT_TIMEOUT + timeout);
+	if (ret) {
+		dev_err(dev, "failed to quiesce cport %u: %d (%d)\n",
+				cport_id, ret, result);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int es2_cport_clear(struct gb_host_device *hd, u16 cport_id)
+{
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct device *dev = &es2->usb_dev->dev;
+	struct arpc_cport_clear_req req;
+	int ret;
+
+	req.cport_id = cpu_to_le16(cport_id);
+	ret = arpc_sync(es2, ARPC_TYPE_CPORT_CLEAR, &req, sizeof(req),
+			NULL, ES2_ARPC_CPORT_TIMEOUT);
+	if (ret) {
+		dev_err(dev, "failed to clear cport %u: %d\n", cport_id, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int latency_tag_enable(struct gb_host_device *hd, u16 cport_id)
+{
+	int retval;
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct usb_device *udev = es2->usb_dev;
+
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				 GB_APB_REQUEST_LATENCY_TAG_EN,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE, cport_id, 0, NULL,
+				 0, ES2_USB_CTRL_TIMEOUT);
+
+	if (retval < 0)
+		dev_err(&udev->dev, "Cannot enable latency tag for cport %d\n",
+			cport_id);
+	return retval;
+}
+
+static int latency_tag_disable(struct gb_host_device *hd, u16 cport_id)
+{
+	int retval;
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct usb_device *udev = es2->usb_dev;
+
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				 GB_APB_REQUEST_LATENCY_TAG_DIS,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE, cport_id, 0, NULL,
+				 0, ES2_USB_CTRL_TIMEOUT);
+
+	if (retval < 0)
+		dev_err(&udev->dev, "Cannot disable latency tag for cport %d\n",
+			cport_id);
+	return retval;
+}
+
+static int timesync_enable(struct gb_host_device *hd, u8 count,
+			   u64 frame_time, u32 strobe_delay, u32 refclk)
+{
+	int retval;
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct usb_device *udev = es2->usb_dev;
+	struct gb_control_timesync_enable_request *request;
+
+	request = kzalloc(sizeof(*request), GFP_KERNEL);
+	if (!request)
+		return -ENOMEM;
+
+	request->count = count;
+	request->frame_time = cpu_to_le64(frame_time);
+	request->strobe_delay = cpu_to_le32(strobe_delay);
+	request->refclk = cpu_to_le32(refclk);
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				 GB_APB_REQUEST_TIMESYNC_ENABLE,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE, 0, 0, request,
+				 sizeof(*request), ES2_USB_CTRL_TIMEOUT);
+	if (retval < 0)
+		dev_err(&udev->dev, "Cannot enable timesync %d\n", retval);
+
+	kfree(request);
+	return retval;
+}
+
+static int timesync_disable(struct gb_host_device *hd)
+{
+	int retval;
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct usb_device *udev = es2->usb_dev;
+
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				 GB_APB_REQUEST_TIMESYNC_DISABLE,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE, 0, 0, NULL,
+				 0, ES2_USB_CTRL_TIMEOUT);
+	if (retval < 0)
+		dev_err(&udev->dev, "Cannot disable timesync %d\n", retval);
+
+	return retval;
+}
+
+static int timesync_authoritative(struct gb_host_device *hd, u64 *frame_time)
+{
+	int retval, i;
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct usb_device *udev = es2->usb_dev;
+	struct timesync_authoritative_request *request;
+
+	request = kzalloc(sizeof(*request), GFP_KERNEL);
+	if (!request)
+		return -ENOMEM;
+
+	for (i = 0; i < GB_TIMESYNC_MAX_STROBES; i++)
+		request->frame_time[i] = cpu_to_le64(frame_time[i]);
+
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				 GB_APB_REQUEST_TIMESYNC_AUTHORITATIVE,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE, 0, 0, request,
+				 sizeof(*request), ES2_USB_CTRL_TIMEOUT);
+	if (retval < 0)
+		dev_err(&udev->dev, "Cannot timesync authoritative out %d\n", retval);
+
+	kfree(request);
+	return retval;
+}
+
+static int timesync_get_last_event(struct gb_host_device *hd, u64 *frame_time)
+{
+	int retval;
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	struct usb_device *udev = es2->usb_dev;
+	__le64 *response_frame_time;
+
+	response_frame_time = kzalloc(sizeof(*response_frame_time), GFP_KERNEL);
+	if (!response_frame_time)
+		return -ENOMEM;
+
+	retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+				 GB_APB_REQUEST_TIMESYNC_GET_LAST_EVENT,
+				 USB_DIR_IN | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE, 0, 0, response_frame_time,
+				 sizeof(*response_frame_time),
+				 ES2_USB_CTRL_TIMEOUT);
+
+	if (retval != sizeof(*response_frame_time)) {
+		dev_err(&udev->dev, "Cannot get last TimeSync event: %d\n",
+			retval);
+
+		if (retval >= 0)
+			retval = -EIO;
+
+		goto out;
+	}
+	*frame_time = le64_to_cpu(*response_frame_time);
+	retval = 0;
+out:
+	kfree(response_frame_time);
+	return retval;
+}
+
+static struct gb_hd_driver es2_driver = {
+	.hd_priv_size			= sizeof(struct es2_ap_dev),
+	.message_send			= message_send,
+	.message_cancel			= message_cancel,
+	.cport_allocate			= es2_cport_allocate,
+	.cport_release			= es2_cport_release,
+	.cport_enable			= cport_enable,
+	.cport_connected		= es2_cport_connected,
+	.cport_flush			= es2_cport_flush,
+	.cport_shutdown			= es2_cport_shutdown,
+	.cport_quiesce			= es2_cport_quiesce,
+	.cport_clear			= es2_cport_clear,
+	.latency_tag_enable		= latency_tag_enable,
+	.latency_tag_disable		= latency_tag_disable,
+	.output				= output,
+	.timesync_enable		= timesync_enable,
+	.timesync_disable		= timesync_disable,
+	.timesync_authoritative		= timesync_authoritative,
+	.timesync_get_last_event	= timesync_get_last_event,
+};
+
+/* Common function to report consistent warnings based on URB status */
+static int check_urb_status(struct urb *urb)
+{
+	struct device *dev = &urb->dev->dev;
+	int status = urb->status;
+
+	switch (status) {
+	case 0:
+		return 0;
+
+	case -EOVERFLOW:
+		dev_err(dev, "%s: overflow actual length is %d\n",
+			__func__, urb->actual_length);
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+	case -EILSEQ:
+	case -EPROTO:
+		/* device is gone, stop sending */
+		return status;
+	}
+	dev_err(dev, "%s: unknown status %d\n", __func__, status);
+
+	return -EAGAIN;
+}
+
+static void es2_destroy(struct es2_ap_dev *es2)
+{
+	struct usb_device *udev;
+	struct urb *urb;
+	int i;
+
+	debugfs_remove(es2->apb_log_enable_dentry);
+	usb_log_disable(es2);
+
+	/* Tear down everything! */
+	for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
+		urb = es2->cport_out_urb[i];
+		usb_kill_urb(urb);
+		usb_free_urb(urb);
+		es2->cport_out_urb[i] = NULL;
+		es2->cport_out_urb_busy[i] = false;	/* just to be anal */
+	}
+
+	for (i = 0; i < NUM_ARPC_IN_URB; ++i) {
+		usb_free_urb(es2->arpc_urb[i]);
+		kfree(es2->arpc_buffer[i]);
+		es2->arpc_buffer[i] = NULL;
+	}
+
+	for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
+		usb_free_urb(es2->cport_in.urb[i]);
+		kfree(es2->cport_in.buffer[i]);
+		es2->cport_in.buffer[i] = NULL;
+	}
+
+	/* release reserved CDSI0 and CDSI1 cports */
+	gb_hd_cport_release_reserved(es2->hd, ES2_CPORT_CDSI1);
+	gb_hd_cport_release_reserved(es2->hd, ES2_CPORT_CDSI0);
+
+	udev = es2->usb_dev;
+	gb_hd_put(es2->hd);
+
+	usb_put_dev(udev);
+}
+
+static void cport_in_callback(struct urb *urb)
+{
+	struct gb_host_device *hd = urb->context;
+	struct device *dev = &urb->dev->dev;
+	struct gb_operation_msg_hdr *header;
+	int status = check_urb_status(urb);
+	int retval;
+	u16 cport_id;
+
+	if (status) {
+		if ((status == -EAGAIN) || (status == -EPROTO))
+			goto exit;
+
+		/* The urb is being unlinked */
+		if (status == -ENOENT || status == -ESHUTDOWN)
+			return;
+
+		dev_err(dev, "urb cport in error %d (dropped)\n", status);
+		return;
+	}
+
+	if (urb->actual_length < sizeof(*header)) {
+		dev_err(dev, "short message received\n");
+		goto exit;
+	}
+
+	/* Extract the CPort id, which is packed in the message header */
+	header = urb->transfer_buffer;
+	cport_id = gb_message_cport_unpack(header);
+
+	if (cport_id_valid(hd, cport_id)) {
+		greybus_data_rcvd(hd, cport_id, urb->transfer_buffer,
+							urb->actual_length);
+	} else {
+		dev_err(dev, "invalid cport id %u received\n", cport_id);
+	}
+exit:
+	/* put our urb back in the request pool */
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval)
+		dev_err(dev, "failed to resubmit in-urb: %d\n", retval);
+}
+
+static void cport_out_callback(struct urb *urb)
+{
+	struct gb_message *message = urb->context;
+	struct gb_host_device *hd = message->operation->connection->hd;
+	struct es2_ap_dev *es2 = hd_to_es2(hd);
+	int status = check_urb_status(urb);
+	unsigned long flags;
+
+	gb_message_cport_clear(message->header);
+
+	spin_lock_irqsave(&es2->cport_out_urb_lock, flags);
+	message->hcpriv = NULL;
+	spin_unlock_irqrestore(&es2->cport_out_urb_lock, flags);
+
+	/*
+	 * Tell the submitter that the message send (attempt) is
+	 * complete, and report the status.
+	 */
+	greybus_message_sent(hd, message, status);
+
+	free_urb(es2, urb);
+}
+
+static struct arpc *arpc_alloc(void *payload, u16 size, u8 type)
+{
+	struct arpc *rpc;
+
+	if (size + sizeof(*rpc->req) > ARPC_OUT_SIZE_MAX)
+		return NULL;
+
+	rpc = kzalloc(sizeof(*rpc), GFP_KERNEL);
+	if (!rpc)
+		return NULL;
+
+	INIT_LIST_HEAD(&rpc->list);
+	rpc->req = kzalloc(sizeof(*rpc->req) + size, GFP_KERNEL);
+	if (!rpc->req)
+		goto err_free_rpc;
+
+	rpc->resp = kzalloc(sizeof(*rpc->resp), GFP_KERNEL);
+	if (!rpc->resp)
+		goto err_free_req;
+
+	rpc->req->type = type;
+	rpc->req->size = cpu_to_le16(sizeof(rpc->req) + size);
+	memcpy(rpc->req->data, payload, size);
+
+	init_completion(&rpc->response_received);
+
+	return rpc;
+
+err_free_req:
+	kfree(rpc->req);
+err_free_rpc:
+	kfree(rpc);
+
+	return NULL;
+}
+
+static void arpc_free(struct arpc *rpc)
+{
+	kfree(rpc->req);
+	kfree(rpc->resp);
+	kfree(rpc);
+}
+
+static struct arpc *arpc_find(struct es2_ap_dev *es2, __le16 id)
+{
+	struct arpc *rpc;
+
+	list_for_each_entry(rpc, &es2->arpcs, list) {
+		if (rpc->req->id == id)
+			return rpc;
+	}
+
+	return NULL;
+}
+
+static void arpc_add(struct es2_ap_dev *es2, struct arpc *rpc)
+{
+	rpc->active = true;
+	rpc->req->id = cpu_to_le16(es2->arpc_id_cycle++);
+	list_add_tail(&rpc->list, &es2->arpcs);
+}
+
+static void arpc_del(struct es2_ap_dev *es2, struct arpc *rpc)
+{
+	if (rpc->active) {
+		rpc->active = false;
+		list_del(&rpc->list);
+	}
+}
+
+static int arpc_send(struct es2_ap_dev *es2, struct arpc *rpc, int timeout)
+{
+	struct usb_device *udev = es2->usb_dev;
+	int retval;
+
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				 GB_APB_REQUEST_ARPC_RUN,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE,
+				 0, 0,
+				 rpc->req, le16_to_cpu(rpc->req->size),
+				 ES2_USB_CTRL_TIMEOUT);
+	if (retval != le16_to_cpu(rpc->req->size)) {
+		dev_err(&udev->dev,
+			"failed to send ARPC request %d: %d\n",
+			rpc->req->type, retval);
+		if (retval > 0)
+			retval = -EIO;
+		return retval;
+	}
+
+	return 0;
+}
+
+static int arpc_sync(struct es2_ap_dev *es2, u8 type, void *payload,
+		     size_t size, int *result, unsigned int timeout)
+{
+	struct arpc *rpc;
+	unsigned long flags;
+	int retval;
+
+	if (result)
+		*result = 0;
+
+	rpc = arpc_alloc(payload, size, type);
+	if (!rpc)
+		return -ENOMEM;
+
+	spin_lock_irqsave(&es2->arpc_lock, flags);
+	arpc_add(es2, rpc);
+	spin_unlock_irqrestore(&es2->arpc_lock, flags);
+
+	retval = arpc_send(es2, rpc, timeout);
+	if (retval)
+		goto out_arpc_del;
+
+	retval = wait_for_completion_interruptible_timeout(
+						&rpc->response_received,
+						msecs_to_jiffies(timeout));
+	if (retval <= 0) {
+		if (!retval)
+			retval = -ETIMEDOUT;
+		goto out_arpc_del;
+	}
+
+	if (rpc->resp->result) {
+		retval = -EREMOTEIO;
+		if (result)
+			*result = rpc->resp->result;
+	} else {
+		retval = 0;
+	}
+
+out_arpc_del:
+	spin_lock_irqsave(&es2->arpc_lock, flags);
+	arpc_del(es2, rpc);
+	spin_unlock_irqrestore(&es2->arpc_lock, flags);
+	arpc_free(rpc);
+
+	if (retval < 0 && retval != -EREMOTEIO) {
+		dev_err(&es2->usb_dev->dev,
+			"failed to execute ARPC: %d\n", retval);
+	}
+
+	return retval;
+}
+
+static void arpc_in_callback(struct urb *urb)
+{
+	struct es2_ap_dev *es2 = urb->context;
+	struct device *dev = &urb->dev->dev;
+	int status = check_urb_status(urb);
+	struct arpc *rpc;
+	struct arpc_response_message *resp;
+	unsigned long flags;
+	int retval;
+
+	if (status) {
+		if ((status == -EAGAIN) || (status == -EPROTO))
+			goto exit;
+
+		/* The urb is being unlinked */
+		if (status == -ENOENT || status == -ESHUTDOWN)
+			return;
+
+		dev_err(dev, "arpc in-urb error %d (dropped)\n", status);
+		return;
+	}
+
+	if (urb->actual_length < sizeof(*resp)) {
+		dev_err(dev, "short aprc response received\n");
+		goto exit;
+	}
+
+	resp = urb->transfer_buffer;
+	spin_lock_irqsave(&es2->arpc_lock, flags);
+	rpc = arpc_find(es2, resp->id);
+	if (!rpc) {
+		dev_err(dev, "invalid arpc response id received: %u\n",
+			le16_to_cpu(resp->id));
+		spin_unlock_irqrestore(&es2->arpc_lock, flags);
+		goto exit;
+	}
+
+	arpc_del(es2, rpc);
+	memcpy(rpc->resp, resp, sizeof(*resp));
+	complete(&rpc->response_received);
+	spin_unlock_irqrestore(&es2->arpc_lock, flags);
+
+exit:
+	/* put our urb back in the request pool */
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval)
+		dev_err(dev, "failed to resubmit arpc in-urb: %d\n", retval);
+}
+
+#define APB1_LOG_MSG_SIZE	64
+static void apb_log_get(struct es2_ap_dev *es2, char *buf)
+{
+	int retval;
+
+	do {
+		retval = usb_control_msg(es2->usb_dev,
+					usb_rcvctrlpipe(es2->usb_dev, 0),
+					GB_APB_REQUEST_LOG,
+					USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+					0x00, 0x00,
+					buf,
+					APB1_LOG_MSG_SIZE,
+					ES2_USB_CTRL_TIMEOUT);
+		if (retval > 0)
+			kfifo_in(&es2->apb_log_fifo, buf, retval);
+	} while (retval > 0);
+}
+
+static int apb_log_poll(void *data)
+{
+	struct es2_ap_dev *es2 = data;
+	char *buf;
+
+	buf = kmalloc(APB1_LOG_MSG_SIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	while (!kthread_should_stop()) {
+		msleep(1000);
+		apb_log_get(es2, buf);
+	}
+
+	kfree(buf);
+
+	return 0;
+}
+
+static ssize_t apb_log_read(struct file *f, char __user *buf,
+				size_t count, loff_t *ppos)
+{
+	struct es2_ap_dev *es2 = f->f_inode->i_private;
+	ssize_t ret;
+	size_t copied;
+	char *tmp_buf;
+
+	if (count > APB1_LOG_SIZE)
+		count = APB1_LOG_SIZE;
+
+	tmp_buf = kmalloc(count, GFP_KERNEL);
+	if (!tmp_buf)
+		return -ENOMEM;
+
+	copied = kfifo_out(&es2->apb_log_fifo, tmp_buf, count);
+	ret = simple_read_from_buffer(buf, count, ppos, tmp_buf, copied);
+
+	kfree(tmp_buf);
+
+	return ret;
+}
+
+static const struct file_operations apb_log_fops = {
+	.read	= apb_log_read,
+};
+
+static void usb_log_enable(struct es2_ap_dev *es2)
+{
+	if (!IS_ERR_OR_NULL(es2->apb_log_task))
+		return;
+
+	/* get log from APB1 */
+	es2->apb_log_task = kthread_run(apb_log_poll, es2, "apb_log");
+	if (IS_ERR(es2->apb_log_task))
+		return;
+	/* XXX We will need to rename this per APB */
+	es2->apb_log_dentry = debugfs_create_file("apb_log", S_IRUGO,
+						gb_debugfs_get(), es2,
+						&apb_log_fops);
+}
+
+static void usb_log_disable(struct es2_ap_dev *es2)
+{
+	if (IS_ERR_OR_NULL(es2->apb_log_task))
+		return;
+
+	debugfs_remove(es2->apb_log_dentry);
+	es2->apb_log_dentry = NULL;
+
+	kthread_stop(es2->apb_log_task);
+	es2->apb_log_task = NULL;
+}
+
+static ssize_t apb_log_enable_read(struct file *f, char __user *buf,
+				size_t count, loff_t *ppos)
+{
+	struct es2_ap_dev *es2 = f->f_inode->i_private;
+	int enable = !IS_ERR_OR_NULL(es2->apb_log_task);
+	char tmp_buf[3];
+
+	sprintf(tmp_buf, "%d\n", enable);
+	return simple_read_from_buffer(buf, count, ppos, tmp_buf, 3);
+}
+
+static ssize_t apb_log_enable_write(struct file *f, const char __user *buf,
+				size_t count, loff_t *ppos)
+{
+	int enable;
+	ssize_t retval;
+	struct es2_ap_dev *es2 = f->f_inode->i_private;
+
+	retval = kstrtoint_from_user(buf, count, 10, &enable);
+	if (retval)
+		return retval;
+
+	if (enable)
+		usb_log_enable(es2);
+	else
+		usb_log_disable(es2);
+
+	return count;
+}
+
+static const struct file_operations apb_log_enable_fops = {
+	.read	= apb_log_enable_read,
+	.write	= apb_log_enable_write,
+};
+
+static int apb_get_cport_count(struct usb_device *udev)
+{
+	int retval;
+	__le16 *cport_count;
+
+	cport_count = kzalloc(sizeof(*cport_count), GFP_KERNEL);
+	if (!cport_count)
+		return -ENOMEM;
+
+	retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+				 GB_APB_REQUEST_CPORT_COUNT,
+				 USB_DIR_IN | USB_TYPE_VENDOR |
+				 USB_RECIP_INTERFACE, 0, 0, cport_count,
+				 sizeof(*cport_count), ES2_USB_CTRL_TIMEOUT);
+	if (retval != sizeof(*cport_count)) {
+		dev_err(&udev->dev, "Cannot retrieve CPort count: %d\n",
+			retval);
+
+		if (retval >= 0)
+			retval = -EIO;
+
+		goto out;
+	}
+
+	retval = le16_to_cpu(*cport_count);
+
+	/* We need to fit a CPort ID in one byte of a message header */
+	if (retval > U8_MAX) {
+		retval = U8_MAX;
+		dev_warn(&udev->dev, "Limiting number of CPorts to U8_MAX\n");
+	}
+
+out:
+	kfree(cport_count);
+	return retval;
+}
+
+/*
+ * The ES2 USB Bridge device has 15 endpoints
+ * 1 Control - usual USB stuff + AP -> APBridgeA messages
+ * 7 Bulk IN - CPort data in
+ * 7 Bulk OUT - CPort data out
+ */
+static int ap_probe(struct usb_interface *interface,
+		    const struct usb_device_id *id)
+{
+	struct es2_ap_dev *es2;
+	struct gb_host_device *hd;
+	struct usb_device *udev;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+	__u8 ep_addr;
+	int retval;
+	int i;
+	int num_cports;
+	bool bulk_out_found = false;
+	bool bulk_in_found = false;
+	bool arpc_in_found = false;
+
+	udev = usb_get_dev(interface_to_usbdev(interface));
+
+	num_cports = apb_get_cport_count(udev);
+	if (num_cports < 0) {
+		usb_put_dev(udev);
+		dev_err(&udev->dev, "Cannot retrieve CPort count: %d\n",
+			num_cports);
+		return num_cports;
+	}
+
+	hd = gb_hd_create(&es2_driver, &udev->dev, ES2_GBUF_MSG_SIZE_MAX,
+				num_cports);
+	if (IS_ERR(hd)) {
+		usb_put_dev(udev);
+		return PTR_ERR(hd);
+	}
+
+	es2 = hd_to_es2(hd);
+	es2->hd = hd;
+	es2->usb_intf = interface;
+	es2->usb_dev = udev;
+	spin_lock_init(&es2->cport_out_urb_lock);
+	INIT_KFIFO(es2->apb_log_fifo);
+	usb_set_intfdata(interface, es2);
+
+	/*
+	 * Reserve the CDSI0 and CDSI1 CPorts so they won't be allocated
+	 * dynamically.
+	 */
+	retval = gb_hd_cport_reserve(hd, ES2_CPORT_CDSI0);
+	if (retval)
+		goto error;
+	retval = gb_hd_cport_reserve(hd, ES2_CPORT_CDSI1);
+	if (retval)
+		goto error;
+
+	/* find all bulk endpoints */
+	iface_desc = interface->cur_altsetting;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		endpoint = &iface_desc->endpoint[i].desc;
+		ep_addr = endpoint->bEndpointAddress;
+
+		if (usb_endpoint_is_bulk_in(endpoint)) {
+			if (!bulk_in_found) {
+				es2->cport_in.endpoint = ep_addr;
+				bulk_in_found = true;
+			} else if (!arpc_in_found) {
+				es2->arpc_endpoint_in = ep_addr;
+				arpc_in_found = true;
+			} else {
+				dev_warn(&udev->dev,
+					 "Unused bulk IN endpoint found: 0x%02x\n",
+					 ep_addr);
+			}
+			continue;
+		}
+		if (usb_endpoint_is_bulk_out(endpoint)) {
+			if (!bulk_out_found) {
+				es2->cport_out_endpoint = ep_addr;
+				bulk_out_found = true;
+			} else {
+				dev_warn(&udev->dev,
+					 "Unused bulk OUT endpoint found: 0x%02x\n",
+					 ep_addr);
+			}
+			continue;
+		}
+		dev_warn(&udev->dev,
+			 "Unknown endpoint type found, address 0x%02x\n",
+			 ep_addr);
+	}
+	if (!bulk_in_found || !arpc_in_found || !bulk_out_found) {
+		dev_err(&udev->dev, "Not enough endpoints found in device, aborting!\n");
+		retval = -ENODEV;
+		goto error;
+	}
+
+	/* Allocate buffers for our cport in messages */
+	for (i = 0; i < NUM_CPORT_IN_URB; ++i) {
+		struct urb *urb;
+		u8 *buffer;
+
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!urb) {
+			retval = -ENOMEM;
+			goto error;
+		}
+		es2->cport_in.urb[i] = urb;
+
+		buffer = kmalloc(ES2_GBUF_MSG_SIZE_MAX, GFP_KERNEL);
+		if (!buffer) {
+			retval = -ENOMEM;
+			goto error;
+		}
+
+		usb_fill_bulk_urb(urb, udev,
+				  usb_rcvbulkpipe(udev, es2->cport_in.endpoint),
+				  buffer, ES2_GBUF_MSG_SIZE_MAX,
+				  cport_in_callback, hd);
+
+		es2->cport_in.buffer[i] = buffer;
+	}
+
+	/* Allocate buffers for ARPC in messages */
+	for (i = 0; i < NUM_ARPC_IN_URB; ++i) {
+		struct urb *urb;
+		u8 *buffer;
+
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!urb) {
+			retval = -ENOMEM;
+			goto error;
+		}
+		es2->arpc_urb[i] = urb;
+
+		buffer = kmalloc(ARPC_IN_SIZE_MAX, GFP_KERNEL);
+		if (!buffer) {
+			retval = -ENOMEM;
+			goto error;
+		}
+
+		usb_fill_bulk_urb(urb, udev,
+				  usb_rcvbulkpipe(udev,
+						  es2->arpc_endpoint_in),
+				  buffer, ARPC_IN_SIZE_MAX,
+				  arpc_in_callback, es2);
+
+		es2->arpc_buffer[i] = buffer;
+	}
+
+	/* Allocate urbs for our CPort OUT messages */
+	for (i = 0; i < NUM_CPORT_OUT_URB; ++i) {
+		struct urb *urb;
+
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!urb) {
+			retval = -ENOMEM;
+			goto error;
+		}
+
+		es2->cport_out_urb[i] = urb;
+		es2->cport_out_urb_busy[i] = false;	/* just to be anal */
+	}
+
+	/* XXX We will need to rename this per APB */
+	es2->apb_log_enable_dentry = debugfs_create_file("apb_log_enable",
+							(S_IWUSR | S_IRUGO),
+							gb_debugfs_get(), es2,
+							&apb_log_enable_fops);
+
+	INIT_LIST_HEAD(&es2->arpcs);
+	spin_lock_init(&es2->arpc_lock);
+
+	if (es2_arpc_in_enable(es2))
+		goto error;
+
+	retval = gb_hd_add(hd);
+	if (retval)
+		goto err_disable_arpc_in;
+
+	retval = es2_cport_in_enable(es2, &es2->cport_in);
+	if (retval)
+		goto err_hd_del;
+
+	return 0;
+
+err_hd_del:
+	gb_hd_del(hd);
+err_disable_arpc_in:
+	es2_arpc_in_disable(es2);
+error:
+	es2_destroy(es2);
+
+	return retval;
+}
+
+static void ap_disconnect(struct usb_interface *interface)
+{
+	struct es2_ap_dev *es2 = usb_get_intfdata(interface);
+
+	gb_hd_del(es2->hd);
+
+	es2_cport_in_disable(es2, &es2->cport_in);
+	es2_arpc_in_disable(es2);
+
+	es2_destroy(es2);
+}
+
+static struct usb_driver es2_ap_driver = {
+	.name =		"es2_ap_driver",
+	.probe =	ap_probe,
+	.disconnect =	ap_disconnect,
+	.id_table =	id_table,
+	.soft_unbind =	1,
+};
+
+module_usb_driver(es2_ap_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Greg Kroah-Hartman <[email protected]>");
#43 lore raw

Re: [patch 12/32] greybus: es2 host driver

Quoted 5 lines · Greg KH

This is a Greybus host driver for the ES2/3 bridge chip from Toshiba.

---

drivers/greybus/es2.c line 73

Quoted 9 lines
drivers/greybus/es2.c new file +9 -0
+/*
+ * @endpoint: bulk in endpoint for CPort data
+ * @urb: array of urbs for the CPort in messages
+ * @buffer: array of buffers for the @cport_in_urb urbs
+ */
+struct es2_cport_in {
+	__u8 endpoint;
+	struct urb *urb[NUM_CPORT_IN_URB];
+	u8 *buffer[NUM_CPORT_IN_URB];

Underscored versions of __u8 and friends are unneccessary here... as shown later in the struct. Makes sense to fix in whole file.
pavel

Signature · 2 lines
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
author maintainer
#44 lore raw

PATCH 13/32 greybus: HID driver

In reply to Greg KH

This driver implements the Greybus HID protocol.

---
 drivers/greybus/hid.c |  536 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 536 insertions(+)
drivers/greybus/hid.c new file +536 -0
@@ -0,0 +1,536 @@
+/*
+ * HID class driver for the Greybus.
+ *
+ * Copyright 2014 Google Inc.
+ * Copyright 2014 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/bitops.h>
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include "greybus.h"
+
+/* Greybus HID device's structure */
+struct gb_hid {
+	struct gb_bundle *bundle;
+	struct gb_connection		*connection;
+
+	struct hid_device		*hid;
+	struct gb_hid_desc_response	hdesc;
+
+	unsigned long			flags;
+#define GB_HID_STARTED			0x01
+#define GB_HID_READ_PENDING		0x04
+
+	unsigned int			bufsize;
+	char				*inbuf;
+};
+
+static DEFINE_MUTEX(gb_hid_open_mutex);
+
+/* Routines to get controller's information over greybus */
+
+/* Operations performed on greybus */
+static int gb_hid_get_desc(struct gb_hid *ghid)
+{
+	return gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_DESC, NULL,
+				 0, &ghid->hdesc, sizeof(ghid->hdesc));
+}
+
+static int gb_hid_get_report_desc(struct gb_hid *ghid, char *rdesc)
+{
+	int ret;
+
+	ret = gb_pm_runtime_get_sync(ghid->bundle);
+	if (ret)
+		return ret;
+
+	ret = gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_REPORT_DESC,
+				 NULL, 0, rdesc,
+				 le16_to_cpu(ghid->hdesc.wReportDescLength));
+
+	gb_pm_runtime_put_autosuspend(ghid->bundle);
+
+	return ret;
+}
+
+static int gb_hid_set_power(struct gb_hid *ghid, int type)
+{
+	int ret;
+
+	ret = gb_pm_runtime_get_sync(ghid->bundle);
+	if (ret)
+		return ret;
+
+	ret = gb_operation_sync(ghid->connection, type, NULL, 0, NULL, 0);
+
+	gb_pm_runtime_put_autosuspend(ghid->bundle);
+
+	return ret;
+}
+
+static int gb_hid_get_report(struct gb_hid *ghid, u8 report_type, u8 report_id,
+			     unsigned char *buf, int len)
+{
+	struct gb_hid_get_report_request request;
+	int ret;
+
+	ret = gb_pm_runtime_get_sync(ghid->bundle);
+	if (ret)
+		return ret;
+
+	request.report_type = report_type;
+	request.report_id = report_id;
+
+	ret = gb_operation_sync(ghid->connection, GB_HID_TYPE_GET_REPORT,
+				 &request, sizeof(request), buf, len);
+
+	gb_pm_runtime_put_autosuspend(ghid->bundle);
+
+	return ret;
+}
+
+static int gb_hid_set_report(struct gb_hid *ghid, u8 report_type, u8 report_id,
+			     unsigned char *buf, int len)
+{
+	struct gb_hid_set_report_request *request;
+	struct gb_operation *operation;
+	int ret, size = sizeof(*request) + len - 1;
+
+	ret = gb_pm_runtime_get_sync(ghid->bundle);
+	if (ret)
+		return ret;
+
+	operation = gb_operation_create(ghid->connection,
+					GB_HID_TYPE_SET_REPORT, size, 0,
+					GFP_KERNEL);
+	if (!operation) {
+		gb_pm_runtime_put_autosuspend(ghid->bundle);
+		return -ENOMEM;
+	}
+
+	request = operation->request->payload;
+	request->report_type = report_type;
+	request->report_id = report_id;
+	memcpy(request->report, buf, len);
+
+	ret = gb_operation_request_send_sync(operation);
+	if (ret) {
+		dev_err(&operation->connection->bundle->dev,
+			"failed to set report: %d\n", ret);
+	} else {
+		ret = len;
+	}
+
+	gb_operation_put(operation);
+	gb_pm_runtime_put_autosuspend(ghid->bundle);
+
+	return ret;
+}
+
+static int gb_hid_request_handler(struct gb_operation *op)
+{
+	struct gb_connection *connection = op->connection;
+	struct gb_hid *ghid = gb_connection_get_data(connection);
+	struct gb_hid_input_report_request *request = op->request->payload;
+
+	if (op->type != GB_HID_TYPE_IRQ_EVENT) {
+		dev_err(&connection->bundle->dev,
+			"unsupported unsolicited request\n");
+		return -EINVAL;
+	}
+
+	if (test_bit(GB_HID_STARTED, &ghid->flags))
+		hid_input_report(ghid->hid, HID_INPUT_REPORT,
+				 request->report, op->request->payload_size, 1);
+
+	return 0;
+}
+
+static int gb_hid_report_len(struct hid_report *report)
+{
+	return ((report->size - 1) >> 3) + 1 +
+		report->device->report_enum[report->type].numbered;
+}
+
+static void gb_hid_find_max_report(struct hid_device *hid, unsigned int type,
+				   unsigned int *max)
+{
+	struct hid_report *report;
+	unsigned int size;
+
+	list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
+		size = gb_hid_report_len(report);
+		if (*max < size)
+			*max = size;
+	}
+}
+
+static void gb_hid_free_buffers(struct gb_hid *ghid)
+{
+	kfree(ghid->inbuf);
+	ghid->inbuf = NULL;
+	ghid->bufsize = 0;
+}
+
+static int gb_hid_alloc_buffers(struct gb_hid *ghid, size_t bufsize)
+{
+	ghid->inbuf = kzalloc(bufsize, GFP_KERNEL);
+	if (!ghid->inbuf)
+		return -ENOMEM;
+
+	ghid->bufsize = bufsize;
+
+	return 0;
+}
+
+/* Routines dealing with reports */
+static void gb_hid_init_report(struct gb_hid *ghid, struct hid_report *report)
+{
+	unsigned int size;
+
+	size = gb_hid_report_len(report);
+	if (gb_hid_get_report(ghid, report->type, report->id, ghid->inbuf,
+			      size))
+		return;
+
+	/*
+	 * hid->driver_lock is held as we are in probe function,
+	 * we just need to setup the input fields, so using
+	 * hid_report_raw_event is safe.
+	 */
+	hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, size, 1);
+}
+
+static void gb_hid_init_reports(struct gb_hid *ghid)
+{
+	struct hid_device *hid = ghid->hid;
+	struct hid_report *report;
+
+	list_for_each_entry(report,
+		&hid->report_enum[HID_INPUT_REPORT].report_list, list)
+		gb_hid_init_report(ghid, report);
+
+	list_for_each_entry(report,
+		&hid->report_enum[HID_FEATURE_REPORT].report_list, list)
+		gb_hid_init_report(ghid, report);
+}
+
+static int __gb_hid_get_raw_report(struct hid_device *hid,
+		unsigned char report_number, __u8 *buf, size_t count,
+		unsigned char report_type)
+{
+	struct gb_hid *ghid = hid->driver_data;
+	int ret;
+
+	if (report_type == HID_OUTPUT_REPORT)
+		return -EINVAL;
+
+	ret = gb_hid_get_report(ghid, report_type, report_number, buf, count);
+	if (!ret)
+		ret = count;
+
+	return ret;
+}
+
+static int __gb_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
+				      size_t len, unsigned char report_type)
+{
+	struct gb_hid *ghid = hid->driver_data;
+	int report_id = buf[0];
+	int ret;
+
+	if (report_type == HID_INPUT_REPORT)
+		return -EINVAL;
+
+	if (report_id) {
+		buf++;
+		len--;
+	}
+
+	ret = gb_hid_set_report(ghid, report_type, report_id, buf, len);
+	if (report_id && ret >= 0)
+		ret++; /* add report_id to the number of transfered bytes */
+
+	return 0;
+}
+
+static int gb_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
+			       __u8 *buf, size_t len, unsigned char rtype,
+			       int reqtype)
+{
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		return __gb_hid_get_raw_report(hid, reportnum, buf, len, rtype);
+	case HID_REQ_SET_REPORT:
+		if (buf[0] != reportnum)
+			return -EINVAL;
+		return __gb_hid_output_raw_report(hid, buf, len, rtype);
+	default:
+		return -EIO;
+	}
+}
+
+/* HID Callbacks */
+static int gb_hid_parse(struct hid_device *hid)
+{
+	struct gb_hid *ghid = hid->driver_data;
+	unsigned int rsize;
+	char *rdesc;
+	int ret;
+
+	rsize = le16_to_cpu(ghid->hdesc.wReportDescLength);
+	if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
+		dbg_hid("weird size of report descriptor (%u)\n", rsize);
+		return -EINVAL;
+	}
+
+	rdesc = kzalloc(rsize, GFP_KERNEL);
+	if (!rdesc) {
+		dbg_hid("couldn't allocate rdesc memory\n");
+		return -ENOMEM;
+	}
+
+	ret = gb_hid_get_report_desc(ghid, rdesc);
+	if (ret) {
+		hid_err(hid, "reading report descriptor failed\n");
+		goto free_rdesc;
+	}
+
+	ret = hid_parse_report(hid, rdesc, rsize);
+	if (ret)
+		dbg_hid("parsing report descriptor failed\n");
+
+free_rdesc:
+	kfree(rdesc);
+
+	return ret;
+}
+
+static int gb_hid_start(struct hid_device *hid)
+{
+	struct gb_hid *ghid = hid->driver_data;
+	unsigned int bufsize = HID_MIN_BUFFER_SIZE;
+	int ret;
+
+	gb_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize);
+	gb_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize);
+	gb_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize);
+
+	if (bufsize > HID_MAX_BUFFER_SIZE)
+		bufsize = HID_MAX_BUFFER_SIZE;
+
+	ret = gb_hid_alloc_buffers(ghid, bufsize);
+	if (ret)
+		return ret;
+
+	if (!(hid->quirks & HID_QUIRK_NO_INIT_REPORTS))
+		gb_hid_init_reports(ghid);
+
+	return 0;
+}
+
+static void gb_hid_stop(struct hid_device *hid)
+{
+	struct gb_hid *ghid = hid->driver_data;
+
+	gb_hid_free_buffers(ghid);
+}
+
+static int gb_hid_open(struct hid_device *hid)
+{
+	struct gb_hid *ghid = hid->driver_data;
+	int ret = 0;
+
+	mutex_lock(&gb_hid_open_mutex);
+	if (!hid->open++) {
+		ret = gb_hid_set_power(ghid, GB_HID_TYPE_PWR_ON);
+		if (ret < 0)
+			hid->open--;
+		else
+			set_bit(GB_HID_STARTED, &ghid->flags);
+	}
+	mutex_unlock(&gb_hid_open_mutex);
+
+	return ret;
+}
+
+static void gb_hid_close(struct hid_device *hid)
+{
+	struct gb_hid *ghid = hid->driver_data;
+	int ret;
+
+	/*
+	 * Protecting hid->open to make sure we don't restart data acquistion
+	 * due to a resumption we no longer care about..
+	 */
+	mutex_lock(&gb_hid_open_mutex);
+	if (!--hid->open) {
+		clear_bit(GB_HID_STARTED, &ghid->flags);
+
+		/* Save some power */
+		ret = gb_hid_set_power(ghid, GB_HID_TYPE_PWR_OFF);
+		if (ret)
+			dev_err(&ghid->connection->bundle->dev,
+				"failed to power off (%d)\n", ret);
+	}
+	mutex_unlock(&gb_hid_open_mutex);
+}
+
+static int gb_hid_power(struct hid_device *hid, int lvl)
+{
+	struct gb_hid *ghid = hid->driver_data;
+
+	switch (lvl) {
+	case PM_HINT_FULLON:
+		return gb_hid_set_power(ghid, GB_HID_TYPE_PWR_ON);
+	case PM_HINT_NORMAL:
+		return gb_hid_set_power(ghid, GB_HID_TYPE_PWR_OFF);
+	}
+
+	return 0;
+}
+
+/* HID structure to pass callbacks */
+static struct hid_ll_driver gb_hid_ll_driver = {
+	.parse = gb_hid_parse,
+	.start = gb_hid_start,
+	.stop = gb_hid_stop,
+	.open = gb_hid_open,
+	.close = gb_hid_close,
+	.power = gb_hid_power,
+	.raw_request = gb_hid_raw_request,
+};
+
+static int gb_hid_init(struct gb_hid *ghid)
+{
+	struct hid_device *hid = ghid->hid;
+	int ret;
+
+	ret = gb_hid_get_desc(ghid);
+	if (ret)
+		return ret;
+
+	hid->version = le16_to_cpu(ghid->hdesc.bcdHID);
+	hid->vendor = le16_to_cpu(ghid->hdesc.wVendorID);
+	hid->product = le16_to_cpu(ghid->hdesc.wProductID);
+	hid->country = ghid->hdesc.bCountryCode;
+
+	hid->driver_data = ghid;
+	hid->ll_driver = &gb_hid_ll_driver;
+	hid->dev.parent = &ghid->connection->bundle->dev;
+//	hid->bus = BUS_GREYBUS; /* Need a bustype for GREYBUS in <linux/input.h> */
+
+	/* Set HID device's name */
+	snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X",
+		 dev_name(&ghid->connection->bundle->dev),
+		 hid->vendor, hid->product);
+
+	return 0;
+}
+
+static int gb_hid_probe(struct gb_bundle *bundle,
+			const struct greybus_bundle_id *id)
+{
+	struct greybus_descriptor_cport *cport_desc;
+	struct gb_connection *connection;
+	struct hid_device *hid;
+	struct gb_hid *ghid;
+	int ret;
+
+	if (bundle->num_cports != 1)
+		return -ENODEV;
+
+	cport_desc = &bundle->cport_desc[0];
+	if (cport_desc->protocol_id != GREYBUS_PROTOCOL_HID)
+		return -ENODEV;
+
+	ghid = kzalloc(sizeof(*ghid), GFP_KERNEL);
+	if (!ghid)
+		return -ENOMEM;
+
+	connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
+						gb_hid_request_handler);
+	if (IS_ERR(connection)) {
+		ret = PTR_ERR(connection);
+		goto err_free_ghid;
+	}
+
+	gb_connection_set_data(connection, ghid);
+	ghid->connection = connection;
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid)) {
+		ret = PTR_ERR(hid);
+		goto err_connection_destroy;
+	}
+
+	ghid->hid = hid;
+	ghid->bundle = bundle;
+
+	greybus_set_drvdata(bundle, ghid);
+
+	ret = gb_connection_enable(connection);
+	if (ret)
+		goto err_destroy_hid;
+
+	ret = gb_hid_init(ghid);
+	if (ret)
+		goto err_connection_disable;
+
+	ret = hid_add_device(hid);
+	if (ret) {
+		hid_err(hid, "can't add hid device: %d\n", ret);
+		goto err_connection_disable;
+	}
+
+	gb_pm_runtime_put_autosuspend(bundle);
+
+	return 0;
+
+err_connection_disable:
+	gb_connection_disable(connection);
+err_destroy_hid:
+	hid_destroy_device(hid);
+err_connection_destroy:
+	gb_connection_destroy(connection);
+err_free_ghid:
+	kfree(ghid);
+
+	return ret;
+}
+
+static void gb_hid_disconnect(struct gb_bundle *bundle)
+{
+	struct gb_hid *ghid = greybus_get_drvdata(bundle);
+
+	if (gb_pm_runtime_get_sync(bundle))
+		gb_pm_runtime_get_noresume(bundle);
+
+	hid_destroy_device(ghid->hid);
+	gb_connection_disable(ghid->connection);
+	gb_connection_destroy(ghid->connection);
+	kfree(ghid);
+}
+
+static const struct greybus_bundle_id gb_hid_id_table[] = {
+	{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_HID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(greybus, gb_hid_id_table);
+
+static struct greybus_driver gb_hid_driver = {
+	.name		= "hid",
+	.probe		= gb_hid_probe,
+	.disconnect	= gb_hid_disconnect,
+	.id_table	= gb_hid_id_table,
+};
+module_greybus_driver(gb_hid_driver);
+
+MODULE_LICENSE("GPL v2");