1
0
Fork 0

Char/Misc patches for 4.13-rc1

Here is the "big" char/misc driver patchset for 4.13-rc1.
 
 Lots of stuff in here, a large thunderbolt update, w1 driver header
 reorg, the new mux driver subsystem, google firmware driver updates, and
 a raft of other smaller things.  Full details in the shortlog.
 
 All of these have been in linux-next for a while with the only reported
 issue being a merge problem with this tree and the jc-docs tree in the
 w1 documentation area.  The fix should be obvious for what to do when it
 happens, if not, we can send a follow-up patch for it afterward.
 
 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 -----BEGIN PGP SIGNATURE-----
 
 iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCWVpXKA8cZ3JlZ0Brcm9h
 aC5jb20ACgkQMUfUDdst+ynLrQCdG9SxRjAbOd6pT9Fr2NAzpUG84YsAoLw+I3iO
 EMi60UXWqAFJbtVMS9Aj
 =yrSq
 -----END PGP SIGNATURE-----

Merge tag 'char-misc-4.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc

Pull char/misc updates from Greg KH:
 "Here is the "big" char/misc driver patchset for 4.13-rc1.

  Lots of stuff in here, a large thunderbolt update, w1 driver header
  reorg, the new mux driver subsystem, google firmware driver updates,
  and a raft of other smaller things. Full details in the shortlog.

  All of these have been in linux-next for a while with the only
  reported issue being a merge problem with this tree and the jc-docs
  tree in the w1 documentation area"

* tag 'char-misc-4.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (147 commits)
  misc: apds990x: Use sysfs_match_string() helper
  mei: drop unreachable code in mei_start
  mei: validate the message header only in first fragment.
  DocBook: w1: Update W1 file locations and names in DocBook
  mux: adg792a: always require I2C support
  nvmem: rockchip-efuse: add support for rk322x-efuse
  nvmem: core: add locking to nvmem_find_cell
  nvmem: core: Call put_device() in nvmem_unregister()
  nvmem: core: fix leaks on registration errors
  nvmem: correct Broadcom OTP controller driver writes
  w1: Add subsystem kernel public interface
  drivers/fsi: Add module license to core driver
  drivers/fsi: Use asynchronous slave mode
  drivers/fsi: Add hub master support
  drivers/fsi: Add SCOM FSI client device driver
  drivers/fsi/gpio: Add tracepoints for GPIO master
  drivers/fsi: Add GPIO based FSI master
  drivers/fsi: Document FSI master sysfs files in ABI
  drivers/fsi: Add error handling for slave
  drivers/fsi: Add tracepoints for low-level operations
  ...
hifive-unleashed-5.1
Linus Torvalds 2017-07-03 20:55:59 -07:00
commit f4dd029ee0
167 changed files with 12820 additions and 1631 deletions

View File

@ -0,0 +1,38 @@
What: /sys/bus/platform/devices/fsi-master/rescan
Date: May 2017
KernelVersion: 4.12
Contact: cbostic@linux.vnet.ibm.com
Description:
Initiates a FSI master scan for all connected slave devices
on its links.
What: /sys/bus/platform/devices/fsi-master/break
Date: May 2017
KernelVersion: 4.12
Contact: cbostic@linux.vnet.ibm.com
Description:
Sends an FSI BREAK command on a master's communication
link to any connnected slaves. A BREAK resets connected
device's logic and preps it to receive further commands
from the master.
What: /sys/bus/platform/devices/fsi-master/slave@00:00/term
Date: May 2017
KernelVersion: 4.12
Contact: cbostic@linux.vnet.ibm.com
Description:
Sends an FSI terminate command from the master to its
connected slave. A terminate resets the slave's state machines
that control access to the internally connected engines. In
addition the slave freezes its internal error register for
debugging purposes. This command is also needed to abort any
ongoing operation in case of an expired 'Master Time Out'
timer.
What: /sys/bus/platform/devices/fsi-master/slave@00:00/raw
Date: May 2017
KernelVersion: 4.12
Contact: cbostic@linux.vnet.ibm.com
Description:
Provides a means of reading/writing a 32 bit value from/to a
specified FSI bus address.

View File

@ -0,0 +1,110 @@
What: /sys/bus/thunderbolt/devices/.../domainX/security
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute holds current Thunderbolt security level
set by the system BIOS. Possible values are:
none: All devices are automatically authorized
user: Devices are only authorized based on writing
appropriate value to the authorized attribute
secure: Require devices that support secure connect at
minimum. User needs to authorize each device.
dponly: Automatically tunnel Display port (and USB). No
PCIe tunnels are created.
What: /sys/bus/thunderbolt/devices/.../authorized
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute is used to authorize Thunderbolt devices
after they have been connected. If the device is not
authorized, no devices such as PCIe and Display port are
available to the system.
Contents of this attribute will be 0 when the device is not
yet authorized.
Possible values are supported:
1: The device will be authorized and connected
When key attribute contains 32 byte hex string the possible
values are:
1: The 32 byte hex string is added to the device NVM and
the device is authorized.
2: Send a challenge based on the 32 byte hex string. If the
challenge response from device is valid, the device is
authorized. In case of failure errno will be ENOKEY if
the device did not contain a key at all, and
EKEYREJECTED if the challenge response did not match.
What: /sys/bus/thunderbolt/devices/.../key
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: When a devices supports Thunderbolt secure connect it will
have this attribute. Writing 32 byte hex string changes
authorization to use the secure connection method instead.
What: /sys/bus/thunderbolt/devices/.../device
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute contains id of this device extracted from
the device DROM.
What: /sys/bus/thunderbolt/devices/.../device_name
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute contains name of this device extracted from
the device DROM.
What: /sys/bus/thunderbolt/devices/.../vendor
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute contains vendor id of this device extracted
from the device DROM.
What: /sys/bus/thunderbolt/devices/.../vendor_name
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute contains vendor name of this device extracted
from the device DROM.
What: /sys/bus/thunderbolt/devices/.../unique_id
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: This attribute contains unique_id string of this device.
This is either read from hardware registers (UUID on
newer hardware) or based on UID from the device DROM.
Can be used to uniquely identify particular device.
What: /sys/bus/thunderbolt/devices/.../nvm_version
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: If the device has upgradeable firmware the version
number is available here. Format: %x.%x, major.minor.
If the device is in safe mode reading the file returns
-ENODATA instead as the NVM version is not available.
What: /sys/bus/thunderbolt/devices/.../nvm_authenticate
Date: Sep 2017
KernelVersion: 4.13
Contact: thunderbolt-software@lists.01.org
Description: When new NVM image is written to the non-active NVM
area (through non_activeX NVMem device), the
authentication procedure is started by writing 1 to
this file. If everything goes well, the device is
restarted with the new NVM firmware. If the image
verification fails an error code is returned instead.
When read holds status of the last authentication
operation if an error occurred during the process. This
is directly the status value from the DMA configuration
based mailbox before the device is power cycled. Writing
0 here clears the status.

View File

@ -0,0 +1,16 @@
What: /sys/class/mux/
Date: April 2017
KernelVersion: 4.13
Contact: Peter Rosin <peda@axentia.se>
Description:
The mux/ class sub-directory belongs to the Generic MUX
Framework and provides a sysfs interface for using MUX
controllers.
What: /sys/class/mux/muxchipN/
Date: April 2017
KernelVersion: 4.13
Contact: Peter Rosin <peda@axentia.se>
Description:
A /sys/class/mux/muxchipN directory is created for each
probed MUX chip where N is a simple enumeration.

View File

@ -51,9 +51,9 @@
<sect1 id="w1_internal_api">
<title>W1 API internal to the kernel</title>
<sect2 id="w1.h">
<title>drivers/w1/w1.h</title>
<para>W1 core functions.</para>
!Idrivers/w1/w1.h
<title>include/linux/w1.h</title>
<para>W1 kernel API functions.</para>
!Iinclude/linux/w1.h
</sect2>
<sect2 id="w1.c">
@ -62,18 +62,18 @@
!Idrivers/w1/w1.c
</sect2>
<sect2 id="w1_family.h">
<title>drivers/w1/w1_family.h</title>
<para>Allows registering device family operations.</para>
!Idrivers/w1/w1_family.h
</sect2>
<sect2 id="w1_family.c">
<title>drivers/w1/w1_family.c</title>
<para>Allows registering device family operations.</para>
!Edrivers/w1/w1_family.c
</sect2>
<sect2 id="w1_internal.h">
<title>drivers/w1/w1_internal.h</title>
<para>W1 internal initialization for master devices.</para>
!Idrivers/w1/w1_internal.h
</sect2>
<sect2 id="w1_int.c">
<title>drivers/w1/w1_int.c</title>
<para>W1 internal initialization for master devices.</para>

View File

@ -369,8 +369,10 @@
237 = /dev/loop-control Loopback control device
238 = /dev/vhost-net Host kernel accelerator for virtio net
239 = /dev/uhid User-space I/O driver support for HID subsystem
240 = /dev/userio Serio driver testing device
241 = /dev/vhost-vsock Host kernel driver for virtio vsock
240-254 Reserved for local use
242-254 Reserved for local use
255 Reserved for MISC_DYNAMIC_MINOR
11 char Raw keyboard device (Linux/SPARC only)

View File

@ -61,6 +61,7 @@ configure specific aspects of kernel behavior to your liking.
java
ras
pm/index
thunderbolt
.. only:: subproject and html

View File

@ -649,6 +649,13 @@
/proc/<pid>/coredump_filter.
See also Documentation/filesystems/proc.txt.
coresight_cpu_debug.enable
[ARM,ARM64]
Format: <bool>
Enable/disable the CPU sampling based debugging.
0: default value, disable debugging
1: enable debugging at boot time
cpuidle.off=1 [CPU_IDLE]
disable the cpuidle sub-system

View File

@ -0,0 +1,199 @@
=============
Thunderbolt
=============
The interface presented here is not meant for end users. Instead there
should be a userspace tool that handles all the low-level details, keeps
database of the authorized devices and prompts user for new connections.
More details about the sysfs interface for Thunderbolt devices can be
found in ``Documentation/ABI/testing/sysfs-bus-thunderbolt``.
Those users who just want to connect any device without any sort of
manual work, can add following line to
``/etc/udev/rules.d/99-local.rules``::
ACTION=="add", SUBSYSTEM=="thunderbolt", ATTR{authorized}=="0", ATTR{authorized}="1"
This will authorize all devices automatically when they appear. However,
keep in mind that this bypasses the security levels and makes the system
vulnerable to DMA attacks.
Security levels and how to use them
-----------------------------------
Starting from Intel Falcon Ridge Thunderbolt controller there are 4
security levels available. The reason for these is the fact that the
connected devices can be DMA masters and thus read contents of the host
memory without CPU and OS knowing about it. There are ways to prevent
this by setting up an IOMMU but it is not always available for various
reasons.
The security levels are as follows:
none
All devices are automatically connected by the firmware. No user
approval is needed. In BIOS settings this is typically called
*Legacy mode*.
user
User is asked whether the device is allowed to be connected.
Based on the device identification information available through
``/sys/bus/thunderbolt/devices``. user then can do the decision.
In BIOS settings this is typically called *Unique ID*.
secure
User is asked whether the device is allowed to be connected. In
addition to UUID the device (if it supports secure connect) is sent
a challenge that should match the expected one based on a random key
written to ``key`` sysfs attribute. In BIOS settings this is
typically called *One time saved key*.
dponly
The firmware automatically creates tunnels for Display Port and
USB. No PCIe tunneling is done. In BIOS settings this is
typically called *Display Port Only*.
The current security level can be read from
``/sys/bus/thunderbolt/devices/domainX/security`` where ``domainX`` is
the Thunderbolt domain the host controller manages. There is typically
one domain per Thunderbolt host controller.
If the security level reads as ``user`` or ``secure`` the connected
device must be authorized by the user before PCIe tunnels are created
(e.g the PCIe device appears).
Each Thunderbolt device plugged in will appear in sysfs under
``/sys/bus/thunderbolt/devices``. The device directory carries
information that can be used to identify the particular device,
including its name and UUID.
Authorizing devices when security level is ``user`` or ``secure``
-----------------------------------------------------------------
When a device is plugged in it will appear in sysfs as follows::
/sys/bus/thunderbolt/devices/0-1/authorized - 0
/sys/bus/thunderbolt/devices/0-1/device - 0x8004
/sys/bus/thunderbolt/devices/0-1/device_name - Thunderbolt to FireWire Adapter
/sys/bus/thunderbolt/devices/0-1/vendor - 0x1
/sys/bus/thunderbolt/devices/0-1/vendor_name - Apple, Inc.
/sys/bus/thunderbolt/devices/0-1/unique_id - e0376f00-0300-0100-ffff-ffffffffffff
The ``authorized`` attribute reads 0 which means no PCIe tunnels are
created yet. The user can authorize the device by simply::
# echo 1 > /sys/bus/thunderbolt/devices/0-1/authorized
This will create the PCIe tunnels and the device is now connected.
If the device supports secure connect, and the domain security level is
set to ``secure``, it has an additional attribute ``key`` which can hold
a random 32 byte value used for authorization and challenging the device in
future connects::
/sys/bus/thunderbolt/devices/0-3/authorized - 0
/sys/bus/thunderbolt/devices/0-3/device - 0x305
/sys/bus/thunderbolt/devices/0-3/device_name - AKiTiO Thunder3 PCIe Box
/sys/bus/thunderbolt/devices/0-3/key -
/sys/bus/thunderbolt/devices/0-3/vendor - 0x41
/sys/bus/thunderbolt/devices/0-3/vendor_name - inXtron
/sys/bus/thunderbolt/devices/0-3/unique_id - dc010000-0000-8508-a22d-32ca6421cb16
Notice the key is empty by default.
If the user does not want to use secure connect it can just ``echo 1``
to the ``authorized`` attribute and the PCIe tunnels will be created in
the same way than in ``user`` security level.
If the user wants to use secure connect, the first time the device is
plugged a key needs to be created and send to the device::
# key=$(openssl rand -hex 32)
# echo $key > /sys/bus/thunderbolt/devices/0-3/key
# echo 1 > /sys/bus/thunderbolt/devices/0-3/authorized
Now the device is connected (PCIe tunnels are created) and in addition
the key is stored on the device NVM.
Next time the device is plugged in the user can verify (challenge) the
device using the same key::
# echo $key > /sys/bus/thunderbolt/devices/0-3/key
# echo 2 > /sys/bus/thunderbolt/devices/0-3/authorized
If the challenge the device returns back matches the one we expect based
on the key, the device is connected and the PCIe tunnels are created.
However, if the challenge failed no tunnels are created and error is
returned to the user.
If the user still wants to connect the device it can either approve
the device without a key or write new key and write 1 to the
``authorized`` file to get the new key stored on the device NVM.
Upgrading NVM on Thunderbolt device or host
-------------------------------------------
Since most of the functionality is handled in a firmware running on a
host controller or a device, it is important that the firmware can be
upgraded to the latest where possible bugs in it have been fixed.
Typically OEMs provide this firmware from their support site.
There is also a central site which has links where to download firmwares
for some machines:
`Thunderbolt Updates <https://thunderbolttechnology.net/updates>`_
Before you upgrade firmware on a device or host, please make sure it is
the suitable. Failing to do that may render the device (or host) in a
state where it cannot be used properly anymore without special tools!
Host NVM upgrade on Apple Macs is not supported.
Once the NVM image has been downloaded, you need to plug in a
Thunderbolt device so that the host controller appears. It does not
matter which device is connected (unless you are upgrading NVM on a
device - then you need to connect that particular device).
Note OEM-specific method to power the controller up ("force power") may
be available for your system in which case there is no need to plug in a
Thunderbolt device.
After that we can write the firmware to the non-active parts of the NVM
of the host or device. As an example here is how Intel NUC6i7KYK (Skull
Canyon) Thunderbolt controller NVM is upgraded::
# dd if=KYK_TBT_FW_0018.bin of=/sys/bus/thunderbolt/devices/0-0/nvm_non_active0/nvmem
Once the operation completes we can trigger NVM authentication and
upgrade process as follows::
# echo 1 > /sys/bus/thunderbolt/devices/0-0/nvm_authenticate
If no errors are returned, the host controller shortly disappears. Once
it comes back the driver notices it and initiates a full power cycle.
After a while the host controller appears again and this time it should
be fully functional.
We can verify that the new NVM firmware is active by running following
commands::
# cat /sys/bus/thunderbolt/devices/0-0/nvm_authenticate
0x0
# cat /sys/bus/thunderbolt/devices/0-0/nvm_version
18.0
If ``nvm_authenticate`` contains anything else than 0x0 it is the error
code from the last authentication cycle, which means the authentication
of the NVM image failed.
Note names of the NVMem devices ``nvm_activeN`` and ``nvm_non_activeN``
depends on the order they are registered in the NVMem subsystem. N in
the name is the identifier added by the NVMem subsystem.
Upgrading NVM when host controller is in safe mode
--------------------------------------------------
If the existing NVM is not properly authenticated (or is missing) the
host controller goes into safe mode which means that only available
functionality is flashing new NVM image. When in this mode the reading
``nvm_version`` fails with ``ENODATA`` and the device identification
information is missing.
To recover from this mode, one needs to flash a valid NVM image to the
host host controller in the same way it is done in the previous chapter.

View File

@ -0,0 +1,49 @@
* CoreSight CPU Debug Component:
CoreSight CPU debug component are compliant with the ARMv8 architecture
reference manual (ARM DDI 0487A.k) Chapter 'Part H: External debug'. The
external debug module is mainly used for two modes: self-hosted debug and
external debug, and it can be accessed from mmio region from Coresight
and eventually the debug module connects with CPU for debugging. And the
debug module provides sample-based profiling extension, which can be used
to sample CPU program counter, secure state and exception level, etc;
usually every CPU has one dedicated debug module to be connected.
Required properties:
- compatible : should be "arm,coresight-cpu-debug"; supplemented with
"arm,primecell" since this driver is using the AMBA bus
interface.
- reg : physical base address and length of the register set.
- clocks : the clock associated to this component.
- clock-names : the name of the clock referenced by the code. Since we are
using the AMBA framework, the name of the clock providing
the interconnect should be "apb_pclk" and the clock is
mandatory. The interface between the debug logic and the
processor core is clocked by the internal CPU clock, so it
is enabled with CPU clock by default.
- cpu : the CPU phandle the debug module is affined to. When omitted
the module is considered to belong to CPU0.
Optional properties:
- power-domains: a phandle to the debug power domain. We use "power-domains"
binding to turn on the debug logic if it has own dedicated
power domain and if necessary to use "cpuidle.off=1" or
"nohlt" in the kernel command line or sysfs node to
constrain idle states to ensure registers in the CPU power
domain are accessible.
Example:
debug@f6590000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf6590000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu0>;
};

View File

@ -0,0 +1,24 @@
Device-tree bindings for gpio-based FSI master driver
-----------------------------------------------------
Required properties:
- compatible = "fsi-master-gpio";
- clock-gpios = <gpio-descriptor>; : GPIO for FSI clock
- data-gpios = <gpio-descriptor>; : GPIO for FSI data signal
Optional properties:
- enable-gpios = <gpio-descriptor>; : GPIO for enable signal
- trans-gpios = <gpio-descriptor>; : GPIO for voltage translator enable
- mux-gpios = <gpio-descriptor>; : GPIO for pin multiplexing with other
functions (eg, external FSI masters)
Examples:
fsi-master {
compatible = "fsi-master-gpio", "fsi-master";
clock-gpios = <&gpio 0>;
data-gpios = <&gpio 1>;
enable-gpios = <&gpio 2>;
trans-gpios = <&gpio 3>;
mux-gpios = <&gpio 4>;
}

View File

@ -0,0 +1,99 @@
General Purpose I2C Bus Mux
This binding describes an I2C bus multiplexer that uses a mux controller
from the mux subsystem to route the I2C signals.
.-----. .-----.
| dev | | dev |
.------------. '-----' '-----'
| SoC | | |
| | .--------+--------'
| .------. | .------+ child bus A, on MUX value set to 0
| | I2C |-|--| Mux |
| '------' | '--+---+ child bus B, on MUX value set to 1
| .------. | | '----------+--------+--------.
| | MUX- | | | | | |
| | Ctrl |-|-----+ .-----. .-----. .-----.
| '------' | | dev | | dev | | dev |
'------------' '-----' '-----' '-----'
Required properties:
- compatible: i2c-mux
- i2c-parent: The phandle of the I2C bus that this multiplexer's master-side
port is connected to.
- mux-controls: The phandle of the mux controller to use for operating the
mux.
* Standard I2C mux properties. See i2c-mux.txt in this directory.
* I2C child bus nodes. See i2c-mux.txt in this directory. The sub-bus number
is also the mux-controller state described in ../mux/mux-controller.txt
Optional properties:
- mux-locked: If present, explicitly allow unrelated I2C transactions on the
parent I2C adapter at these times:
+ during setup of the multiplexer
+ between setup of the multiplexer and the child bus I2C transaction
+ between the child bus I2C transaction and releasing of the multiplexer
+ during releasing of the multiplexer
However, I2C transactions to devices behind all I2C multiplexers connected
to the same parent adapter that this multiplexer is connected to are blocked
for the full duration of the complete multiplexed I2C transaction (i.e.
including the times covered by the above list).
If mux-locked is not present, the multiplexer is assumed to be parent-locked.
This means that no unrelated I2C transactions are allowed on the parent I2C
adapter for the complete multiplexed I2C transaction.
The properties of mux-locked and parent-locked multiplexers are discussed
in more detail in Documentation/i2c/i2c-topology.
For each i2c child node, an I2C child bus will be created. They will
be numbered based on their order in the device tree.
Whenever an access is made to a device on a child bus, the value set
in the relevant node's reg property will be set as the state in the
mux controller.
Example:
mux: mux-controller {
compatible = "gpio-mux";
#mux-control-cells = <0>;
mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
<&pioA 1 GPIO_ACTIVE_HIGH>;
};
i2c-mux {
compatible = "i2c-mux";
mux-locked;
i2c-parent = <&i2c1>;
mux-controls = <&mux>;
#address-cells = <1>;
#size-cells = <0>;
i2c@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
ssd1307: oled@3c {
compatible = "solomon,ssd1307fb-i2c";
reg = <0x3c>;
pwms = <&pwm 4 3000>;
reset-gpios = <&gpio2 7 1>;
reset-active-low;
};
};
i2c@3 {
reg = <3>;
#address-cells = <1>;
#size-cells = <0>;
pca9555: pca9555@20 {
compatible = "nxp,pca9555";
gpio-controller;
#gpio-cells = <2>;
reg = <0x20>;
};
};
};

View File

@ -0,0 +1,39 @@
I/O channel multiplexer bindings
If a multiplexer is used to select which hardware signal is fed to
e.g. an ADC channel, these bindings describe that situation.
Required properties:
- compatible : "io-channel-mux"
- io-channels : Channel node of the parent channel that has multiplexed
input.
- io-channel-names : Should be "parent".
- #address-cells = <1>;
- #size-cells = <0>;
- mux-controls : Mux controller node to use for operating the mux
- channels : List of strings, labeling the mux controller states.
For each non-empty string in the channels property, an io-channel will
be created. The number of this io-channel is the same as the index into
the list of strings in the channels property, and also matches the mux
controller state. The mux controller state is described in
../mux/mux-controller.txt
Example:
mux: mux-controller {
compatible = "mux-gpio";
#mux-control-cells = <0>;
mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
<&pioA 1 GPIO_ACTIVE_HIGH>;
};
adc-mux {
compatible = "io-channel-mux";
io-channels = <&adc 0>;
io-channel-names = "parent";
mux-controls = <&mux>;
channels = "sync", "in", "system-regulator";
};

View File

@ -0,0 +1,75 @@
Bindings for Analog Devices ADG792A/G Triple 4:1 Multiplexers
Required properties:
- compatible : "adi,adg792a" or "adi,adg792g"
- #mux-control-cells : <0> if parallel (the three muxes are bound together
with a single mux controller controlling all three muxes), or <1> if
not (one mux controller for each mux).
* Standard mux-controller bindings as described in mux-controller.txt
Optional properties for ADG792G:
- gpio-controller : if present, #gpio-cells below is required.
- #gpio-cells : should be <2>
- First cell is the GPO line number, i.e. 0 or 1
- Second cell is used to specify active high (0)
or active low (1)
Optional properties:
- idle-state : if present, array of states that the mux controllers will have
when idle. The special state MUX_IDLE_AS_IS is the default and
MUX_IDLE_DISCONNECT is also supported.
States 0 through 3 correspond to signals A through D in the datasheet.
Example:
/*
* Three independent mux controllers (of which one is used).
* Mux 0 is disconnected when idle, mux 1 idles in the previously
* selected state and mux 2 idles with signal B.
*/
&i2c0 {
mux: mux-controller@50 {
compatible = "adi,adg792a";
reg = <0x50>;
#mux-control-cells = <1>;
idle-state = <MUX_IDLE_DISCONNECT MUX_IDLE_AS_IS 1>;
};
};
adc-mux {
compatible = "io-channel-mux";
io-channels = <&adc 0>;
io-channel-names = "parent";
mux-controls = <&mux 2>;
channels = "sync-1", "", "out";
};
/*
* Three parallel muxes with one mux controller, useful e.g. if
* the adc is differential, thus needing two signals to be muxed
* simultaneously for correct operation.
*/
&i2c0 {
pmux: mux-controller@50 {
compatible = "adi,adg792a";
reg = <0x50>;
#mux-control-cells = <0>;
idle-state = <1>;
};
};
diff-adc-mux {
compatible = "io-channel-mux";
io-channels = <&adc 0>;
io-channel-names = "parent";
mux-controls = <&pmux>;
channels = "sync-1", "", "out";
};

View File

@ -0,0 +1,69 @@
GPIO-based multiplexer controller bindings
Define what GPIO pins are used to control a multiplexer. Or several
multiplexers, if the same pins control more than one multiplexer.
Required properties:
- compatible : "gpio-mux"
- mux-gpios : list of gpios used to control the multiplexer, least
significant bit first.
- #mux-control-cells : <0>
* Standard mux-controller bindings as decribed in mux-controller.txt
Optional properties:
- idle-state : if present, the state the mux will have when idle. The
special state MUX_IDLE_AS_IS is the default.
The multiplexer state is defined as the number represented by the
multiplexer GPIO pins, where the first pin is the least significant
bit. An active pin is a binary 1, an inactive pin is a binary 0.
Example:
mux: mux-controller {
compatible = "gpio-mux";
#mux-control-cells = <0>;
mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
<&pioA 1 GPIO_ACTIVE_HIGH>;
};
adc-mux {
compatible = "io-channel-mux";
io-channels = <&adc 0>;
io-channel-names = "parent";
mux-controls = <&mux>;
channels = "sync-1", "in", "out", "sync-2";
};
i2c-mux {
compatible = "i2c-mux";
i2c-parent = <&i2c1>;
mux-controls = <&mux>;
#address-cells = <1>;
#size-cells = <0>;
i2c@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
ssd1307: oled@3c {
/* ... */
};
};
i2c@3 {
reg = <3>;
#address-cells = <1>;
#size-cells = <0>;
pca9555: pca9555@20 {
/* ... */
};
};
};

View File

@ -0,0 +1,60 @@
MMIO register bitfield-based multiplexer controller bindings
Define register bitfields to be used to control multiplexers. The parent
device tree node must be a syscon node to provide register access.
Required properties:
- compatible : "mmio-mux"
- #mux-control-cells : <1>
- mux-reg-masks : an array of register offset and pre-shifted bitfield mask
pairs, each describing a single mux control.
* Standard mux-controller bindings as decribed in mux-controller.txt
Optional properties:
- idle-states : if present, the state the muxes will have when idle. The
special state MUX_IDLE_AS_IS is the default.
The multiplexer state of each multiplexer is defined as the value of the
bitfield described by the corresponding register offset and bitfield mask pair
in the mux-reg-masks array, accessed through the parent syscon.
Example:
syscon {
compatible = "syscon";
mux: mux-controller {
compatible = "mmio-mux";
#mux-control-cells = <1>;
mux-reg-masks = <0x3 0x30>, /* 0: reg 0x3, bits 5:4 */
<0x3 0x40>, /* 1: reg 0x3, bit 6 */
idle-states = <MUX_IDLE_AS_IS>, <0>;
};
};
video-mux {
compatible = "video-mux";
mux-controls = <&mux 0>;
ports {
/* inputs 0..3 */
port@0 {
reg = <0>;
};
port@1 {
reg = <1>;
};
port@2 {
reg = <2>;
};
port@3 {
reg = <3>;
};
/* output */
port@4 {
reg = <4>;
};
};
};

View File

@ -0,0 +1,157 @@
Common multiplexer controller bindings
======================================
A multiplexer (or mux) controller will have one, or several, consumer devices
that uses the mux controller. Thus, a mux controller can possibly control
several parallel multiplexers. Presumably there will be at least one
multiplexer needed by each consumer, but a single mux controller can of course
control several multiplexers for a single consumer.
A mux controller provides a number of states to its consumers, and the state
space is a simple zero-based enumeration. I.e. 0-1 for a 2-way multiplexer,
0-7 for an 8-way multiplexer, etc.
Consumers
---------
Mux controller consumers should specify a list of mux controllers that they
want to use with a property containing a 'mux-ctrl-list':
mux-ctrl-list ::= <single-mux-ctrl> [mux-ctrl-list]
single-mux-ctrl ::= <mux-ctrl-phandle> [mux-ctrl-specifier]
mux-ctrl-phandle : phandle to mux controller node
mux-ctrl-specifier : array of #mux-control-cells specifying the
given mux controller (controller specific)
Mux controller properties should be named "mux-controls". The exact meaning of
each mux controller property must be documented in the device tree binding for
each consumer. An optional property "mux-control-names" may contain a list of
strings to label each of the mux controllers listed in the "mux-controls"
property.
Drivers for devices that use more than a single mux controller can use the
"mux-control-names" property to map the name of the requested mux controller
to an index into the list given by the "mux-controls" property.
mux-ctrl-specifier typically encodes the chip-relative mux controller number.
If the mux controller chip only provides a single mux controller, the
mux-ctrl-specifier can typically be left out.
Example:
/* One consumer of a 2-way mux controller (one GPIO-line) */
mux: mux-controller {
compatible = "gpio-mux";
#mux-control-cells = <0>;
mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>;
};
adc-mux {
compatible = "io-channel-mux";
io-channels = <&adc 0>;
io-channel-names = "parent";
mux-controls = <&mux>;
mux-control-names = "adc";
channels = "sync", "in";
};
Note that in the example above, specifying the "mux-control-names" is redundant
because there is only one mux controller in the list. However, if the driver
for the consumer node in fact asks for a named mux controller, that name is of
course still required.
/*
* Two consumers (one for an ADC line and one for an i2c bus) of
* parallel 4-way multiplexers controlled by the same two GPIO-lines.
*/
mux: mux-controller {
compatible = "gpio-mux";
#mux-control-cells = <0>;
mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
<&pioA 1 GPIO_ACTIVE_HIGH>;
};
adc-mux {
compatible = "io-channel-mux";
io-channels = <&adc 0>;
io-channel-names = "parent";
mux-controls = <&mux>;
channels = "sync-1", "in", "out", "sync-2";
};
i2c-mux {
compatible = "i2c-mux";
i2c-parent = <&i2c1>;
mux-controls = <&mux>;
#address-cells = <1>;
#size-cells = <0>;
i2c@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
ssd1307: oled@3c {
/* ... */
};
};
i2c@3 {
reg = <3>;
#address-cells = <1>;
#size-cells = <0>;
pca9555: pca9555@20 {
/* ... */
};
};
};
Mux controller nodes
--------------------
Mux controller nodes must specify the number of cells used for the
specifier using the '#mux-control-cells' property.
Optionally, mux controller nodes can also specify the state the mux should
have when it is idle. The idle-state property is used for this. If the
idle-state is not present, the mux controller is typically left as is when
it is idle. For multiplexer chips that expose several mux controllers, the
idle-state property is an array with one idle state for each mux controller.
The special value (-1) may be used to indicate that the mux should be left
as is when it is idle. This is the default, but can still be useful for
mux controller chips with more than one mux controller, particularly when
there is a need to "step past" a mux controller and set some other idle
state for a mux controller with a higher index.
Some mux controllers have the ability to disconnect the input/output of the
multiplexer. Using this disconnected high-impedance state as the idle state
is indicated with idle state (-2).
These constants are available in
#include <dt-bindings/mux/mux.h>
as MUX_IDLE_AS_IS (-1) and MUX_IDLE_DISCONNECT (-2).
An example mux controller node look like this (the adg972a chip is a triple
4-way multiplexer):
mux: mux-controller@50 {
compatible = "adi,adg792a";
reg = <0x50>;
#mux-control-cells = <1>;
idle-state = <MUX_IDLE_DISCONNECT MUX_IDLE_AS_IS 2>;
};

View File

@ -4,6 +4,7 @@ Required properties:
- compatible: Should be one of the following.
- "rockchip,rk3066a-efuse" - for RK3066a SoCs.
- "rockchip,rk3188-efuse" - for RK3188 SoCs.
- "rockchip,rk322x-efuse" - for RK322x SoCs.
- "rockchip,rk3288-efuse" - for RK3288 SoCs.
- "rockchip,rk3399-efuse" - for RK3399 SoCs.
- reg: Should contain the registers location and exact eFuse size

View File

@ -337,7 +337,12 @@ MEM
devm_kzalloc()
MFD
devm_mfd_add_devices()
devm_mfd_add_devices()
MUX
devm_mux_chip_alloc()
devm_mux_chip_register()
devm_mux_control_get()
PER-CPU MEM
devm_alloc_percpu()

View File

@ -0,0 +1,175 @@
Coresight CPU Debug Module
==========================
Author: Leo Yan <leo.yan@linaro.org>
Date: April 5th, 2017
Introduction
------------
Coresight CPU debug module is defined in ARMv8-a architecture reference manual
(ARM DDI 0487A.k) Chapter 'Part H: External debug', the CPU can integrate
debug module and it is mainly used for two modes: self-hosted debug and
external debug. Usually the external debug mode is well known as the external
debugger connects with SoC from JTAG port; on the other hand the program can
explore debugging method which rely on self-hosted debug mode, this document
is to focus on this part.
The debug module provides sample-based profiling extension, which can be used
to sample CPU program counter, secure state and exception level, etc; usually
every CPU has one dedicated debug module to be connected. Based on self-hosted
debug mechanism, Linux kernel can access these related registers from mmio
region when the kernel panic happens. The callback notifier for kernel panic
will dump related registers for every CPU; finally this is good for assistant
analysis for panic.
Implementation
--------------
- During driver registration, it uses EDDEVID and EDDEVID1 - two device ID
registers to decide if sample-based profiling is implemented or not. On some
platforms this hardware feature is fully or partially implemented; and if
this feature is not supported then registration will fail.
- At the time this documentation was written, the debug driver mainly relies on
information gathered by the kernel panic callback notifier from three
sampling registers: EDPCSR, EDVIDSR and EDCIDSR: from EDPCSR we can get
program counter; EDVIDSR has information for secure state, exception level,
bit width, etc; EDCIDSR is context ID value which contains the sampled value
of CONTEXTIDR_EL1.
- The driver supports a CPU running in either AArch64 or AArch32 mode. The
registers naming convention is a bit different between them, AArch64 uses
'ED' for register prefix (ARM DDI 0487A.k, chapter H9.1) and AArch32 uses
'DBG' as prefix (ARM DDI 0487A.k, chapter G5.1). The driver is unified to
use AArch64 naming convention.
- ARMv8-a (ARM DDI 0487A.k) and ARMv7-a (ARM DDI 0406C.b) have different
register bits definition. So the driver consolidates two difference:
If PCSROffset=0b0000, on ARMv8-a the feature of EDPCSR is not implemented;
but ARMv7-a defines "PCSR samples are offset by a value that depends on the
instruction set state". For ARMv7-a, the driver checks furthermore if CPU
runs with ARM or thumb instruction set and calibrate PCSR value, the
detailed description for offset is in ARMv7-a ARM (ARM DDI 0406C.b) chapter
C11.11.34 "DBGPCSR, Program Counter Sampling Register".
If PCSROffset=0b0010, ARMv8-a defines "EDPCSR implemented, and samples have
no offset applied and do not sample the instruction set state in AArch32
state". So on ARMv8 if EDDEVID1.PCSROffset is 0b0010 and the CPU operates
in AArch32 state, EDPCSR is not sampled; when the CPU operates in AArch64
state EDPCSR is sampled and no offset are applied.
Clock and power domain
----------------------
Before accessing debug registers, we should ensure the clock and power domain
have been enabled properly. In ARMv8-a ARM (ARM DDI 0487A.k) chapter 'H9.1
Debug registers', the debug registers are spread into two domains: the debug
domain and the CPU domain.
+---------------+
| |
| |
+----------+--+ |
dbg_clock -->| |**| |<-- cpu_clock
| Debug |**| CPU |
dbg_power_domain -->| |**| |<-- cpu_power_domain
+----------+--+ |
| |
| |
+---------------+
For debug domain, the user uses DT binding "clocks" and "power-domains" to
specify the corresponding clock source and power supply for the debug logic.
The driver calls the pm_runtime_{put|get} operations as needed to handle the
debug power domain.
For CPU domain, the different SoC designs have different power management
schemes and finally this heavily impacts external debug module. So we can
divide into below cases:
- On systems with a sane power controller which can behave correctly with
respect to CPU power domain, the CPU power domain can be controlled by
register EDPRCR in driver. The driver firstly writes bit EDPRCR.COREPURQ
to power up the CPU, and then writes bit EDPRCR.CORENPDRQ for emulation
of CPU power down. As result, this can ensure the CPU power domain is
powered on properly during the period when access debug related registers;
- Some designs will power down an entire cluster if all CPUs on the cluster
are powered down - including the parts of the debug registers that should
remain powered in the debug power domain. The bits in EDPRCR are not
respected in these cases, so these designs do not support debug over
power down in the way that the CoreSight / Debug designers anticipated.
This means that even checking EDPRSR has the potential to cause a bus hang
if the target register is unpowered.
In this case, accessing to the debug registers while they are not powered
is a recipe for disaster; so we need preventing CPU low power states at boot
time or when user enable module at the run time. Please see chapter
"How to use the module" for detailed usage info for this.
Device Tree Bindings
--------------------
See Documentation/devicetree/bindings/arm/coresight-cpu-debug.txt for details.
How to use the module
---------------------
If you want to enable debugging functionality at boot time, you can add
"coresight_cpu_debug.enable=1" to the kernel command line parameter.
The driver also can work as module, so can enable the debugging when insmod
module:
# insmod coresight_cpu_debug.ko debug=1
When boot time or insmod module you have not enabled the debugging, the driver
uses the debugfs file system to provide a knob to dynamically enable or disable
debugging:
To enable it, write a '1' into /sys/kernel/debug/coresight_cpu_debug/enable:
# echo 1 > /sys/kernel/debug/coresight_cpu_debug/enable
To disable it, write a '0' into /sys/kernel/debug/coresight_cpu_debug/enable:
# echo 0 > /sys/kernel/debug/coresight_cpu_debug/enable
As explained in chapter "Clock and power domain", if you are working on one
platform which has idle states to power off debug logic and the power
controller cannot work well for the request from EDPRCR, then you should
firstly constraint CPU idle states before enable CPU debugging feature; so can
ensure the accessing to debug logic.
If you want to limit idle states at boot time, you can use "nohlt" or
"cpuidle.off=1" in the kernel command line.
At the runtime you can disable idle states with below methods:
Set latency request to /dev/cpu_dma_latency to disable all CPUs specific idle
states (if latency = 0uS then disable all idle states):
# echo "what_ever_latency_you_need_in_uS" > /dev/cpu_dma_latency
Disable specific CPU's specific idle state:
# echo 1 > /sys/devices/system/cpu/cpu$cpu/cpuidle/state$state/disable
Output format
-------------
Here is an example of the debugging output format:
ARM external debug module:
coresight-cpu-debug 850000.debug: CPU[0]:
coresight-cpu-debug 850000.debug: EDPRSR: 00000001 (Power:On DLK:Unlock)
coresight-cpu-debug 850000.debug: EDPCSR: [<ffff00000808e9bc>] handle_IPI+0x174/0x1d8
coresight-cpu-debug 850000.debug: EDCIDSR: 00000000
coresight-cpu-debug 850000.debug: EDVIDSR: 90000000 (State:Non-secure Mode:EL1/0 Width:64bits VMID:0)
coresight-cpu-debug 852000.debug: CPU[1]:
coresight-cpu-debug 852000.debug: EDPRSR: 00000001 (Power:On DLK:Unlock)
coresight-cpu-debug 852000.debug: EDPCSR: [<ffff0000087fab34>] debug_notifier_call+0x23c/0x358
coresight-cpu-debug 852000.debug: EDCIDSR: 00000000
coresight-cpu-debug 852000.debug: EDVIDSR: 90000000 (State:Non-secure Mode:EL1/0 Width:64bits VMID:0)

View File

@ -1207,7 +1207,9 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
F: drivers/hwtracing/coresight/*
F: Documentation/trace/coresight.txt
F: Documentation/trace/coresight-cpu-debug.txt
F: Documentation/devicetree/bindings/arm/coresight.txt
F: Documentation/devicetree/bindings/arm/coresight-cpu-debug.txt
F: Documentation/ABI/testing/sysfs-bus-coresight-devices-*
F: tools/perf/arch/arm/util/pmu.c
F: tools/perf/arch/arm/util/auxtrace.c
@ -6489,6 +6491,13 @@ F: Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector
F: Documentation/devicetree/bindings/iio/adc/envelope-detector.txt
F: drivers/iio/adc/envelope-detector.c
IIO MULTIPLEXER
M: Peter Rosin <peda@axentia.se>
L: linux-iio@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
F: drivers/iio/multiplexer/iio-mux.c
IIO SUBSYSTEM AND DRIVERS
M: Jonathan Cameron <jic23@kernel.org>
R: Hartmut Knaack <knaack.h@gmx.de>
@ -8724,6 +8733,15 @@ S: Orphan
F: drivers/mmc/host/mmc_spi.c
F: include/linux/spi/mmc_spi.h
MULTIPLEXER SUBSYSTEM
M: Peter Rosin <peda@axentia.se>
S: Maintained
F: Documentation/ABI/testing/mux/sysfs-class-mux*
F: Documentation/devicetree/bindings/mux/
F: include/linux/dt-bindings/mux/
F: include/linux/mux/
F: drivers/mux/
MULTISOUND SOUND DRIVER
M: Andrew Veliath <andrewtv@usa.net>
S: Maintained
@ -11336,6 +11354,9 @@ F: Documentation/tee.txt
THUNDERBOLT DRIVER
M: Andreas Noever <andreas.noever@gmail.com>
M: Michael Jamet <michael.jamet@intel.com>
M: Mika Westerberg <mika.westerberg@linux.intel.com>
M: Yehezkel Bernat <yehezkel.bernat@intel.com>
S: Maintained
F: drivers/thunderbolt/
@ -13789,6 +13810,7 @@ M: Evgeniy Polyakov <zbr@ioremap.net>
S: Maintained
F: Documentation/w1/
F: drivers/w1/
F: include/linux/w1.h
W83791D HARDWARE MONITORING DRIVER
M: Marc Hulsman <m.hulsman@tudelft.nl>

View File

@ -887,5 +887,69 @@
};
};
};
debug@f6590000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf6590000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu0>;
};
debug@f6592000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf6592000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu1>;
};
debug@f6594000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf6594000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu2>;
};
debug@f6596000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf6596000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu3>;
};
debug@f65d0000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf65d0000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu4>;
};
debug@f65d2000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf65d2000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu5>;
};
debug@f65d4000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf65d4000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu6>;
};
debug@f65d6000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0 0xf65d6000 0 0x1000>;
clocks = <&sys_ctrl HI6220_DAPB_CLK>;
clock-names = "apb_pclk";
cpu = <&cpu7>;
};
};
};

View File

@ -1116,6 +1116,38 @@
};
};
debug@850000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0x850000 0x1000>;
clocks = <&rpmcc RPM_QDSS_CLK>;
clock-names = "apb_pclk";
cpu = <&CPU0>;
};
debug@852000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0x852000 0x1000>;
clocks = <&rpmcc RPM_QDSS_CLK>;
clock-names = "apb_pclk";
cpu = <&CPU1>;
};
debug@854000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0x854000 0x1000>;
clocks = <&rpmcc RPM_QDSS_CLK>;
clock-names = "apb_pclk";
cpu = <&CPU2>;
};
debug@856000 {
compatible = "arm,coresight-cpu-debug","arm,primecell";
reg = <0x856000 0x1000>;
clocks = <&rpmcc RPM_QDSS_CLK>;
clock-names = "apb_pclk";
cpu = <&CPU3>;
};
etm@85c000 {
compatible = "arm,coresight-etm4x", "arm,primecell";
reg = <0x85c000 0x1000>;

View File

@ -136,7 +136,6 @@ static inline void vmbus_signal_eom(struct hv_message *msg, u32 old_msg_type)
}
}
#define hv_get_current_tick(tick) rdmsrl(HV_X64_MSR_TIME_REF_COUNT, tick)
#define hv_init_timer(timer, tick) wrmsrl(timer, tick)
#define hv_init_timer_config(config, val) wrmsrl(config, val)

View File

@ -206,4 +206,6 @@ source "drivers/fsi/Kconfig"
source "drivers/tee/Kconfig"
source "drivers/mux/Kconfig"
endmenu

View File

@ -181,3 +181,4 @@ obj-$(CONFIG_NVMEM) += nvmem/
obj-$(CONFIG_FPGA) += fpga/
obj-$(CONFIG_FSI) += fsi/
obj-$(CONFIG_TEE) += tee/
obj-$(CONFIG_MULTIPLEXER) += mux/

View File

@ -1345,14 +1345,11 @@ static inline void input_state_falling(struct logical_input *input)
static void panel_process_inputs(void)
{
struct list_head *item;
struct logical_input *input;
keypressed = 0;
inputs_stable = 1;
list_for_each(item, &logical_inputs) {
input = list_entry(item, struct logical_input, list);
list_for_each_entry(input, &logical_inputs, list) {
switch (input->state) {
case INPUT_ST_LOW:
if ((phys_curr & input->mask) != input->value)

View File

@ -26,12 +26,52 @@
/* CBMEM firmware console log descriptor. */
struct cbmem_cons {
u32 buffer_size;
u32 buffer_cursor;
u8 buffer_body[0];
u32 size_dont_access_after_boot;
u32 cursor;
u8 body[0];
} __packed;
#define CURSOR_MASK ((1 << 28) - 1)
#define OVERFLOW (1 << 31)
static struct cbmem_cons __iomem *cbmem_console;
static u32 cbmem_console_size;
/*
* The cbmem_console structure is read again on every access because it may
* change at any time if runtime firmware logs new messages. This may rarely
* lead to race conditions where the firmware overwrites the beginning of the
* ring buffer with more lines after we have already read |cursor|. It should be
* rare and harmless enough that we don't spend extra effort working around it.
*/
static ssize_t memconsole_coreboot_read(char *buf, loff_t pos, size_t count)
{
u32 cursor = cbmem_console->cursor & CURSOR_MASK;
u32 flags = cbmem_console->cursor & ~CURSOR_MASK;
u32 size = cbmem_console_size;
struct seg { /* describes ring buffer segments in logical order */
u32 phys; /* physical offset from start of mem buffer */
u32 len; /* length of segment */
} seg[2] = { {0}, {0} };
size_t done = 0;
int i;
if (flags & OVERFLOW) {
if (cursor > size) /* Shouldn't really happen, but... */
cursor = 0;
seg[0] = (struct seg){.phys = cursor, .len = size - cursor};
seg[1] = (struct seg){.phys = 0, .len = cursor};
} else {
seg[0] = (struct seg){.phys = 0, .len = min(cursor, size)};
}
for (i = 0; i < ARRAY_SIZE(seg) && count > done; i++) {
done += memory_read_from_buffer(buf + done, count - done, &pos,
cbmem_console->body + seg[i].phys, seg[i].len);
pos -= seg[i].len;
}
return done;
}
static int memconsole_coreboot_init(phys_addr_t physaddr)
{
@ -42,17 +82,17 @@ static int memconsole_coreboot_init(phys_addr_t physaddr)
if (!tmp_cbmc)
return -ENOMEM;
/* Read size only once to prevent overrun attack through /dev/mem. */
cbmem_console_size = tmp_cbmc->size_dont_access_after_boot;
cbmem_console = memremap(physaddr,
tmp_cbmc->buffer_size + sizeof(*cbmem_console),
cbmem_console_size + sizeof(*cbmem_console),
MEMREMAP_WB);
memunmap(tmp_cbmc);
if (!cbmem_console)
return -ENOMEM;
memconsole_setup(cbmem_console->buffer_body,
min(cbmem_console->buffer_cursor, cbmem_console->buffer_size));
memconsole_setup(memconsole_coreboot_read);
return 0;
}

View File

@ -48,6 +48,15 @@ struct biosmemcon_ebda {
};
} __packed;
static char *memconsole_baseaddr;
static size_t memconsole_length;
static ssize_t memconsole_read(char *buf, loff_t pos, size_t count)
{
return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr,
memconsole_length);
}
static void found_v1_header(struct biosmemcon_ebda *hdr)
{
pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n",
@ -56,7 +65,9 @@ static void found_v1_header(struct biosmemcon_ebda *hdr)
hdr->v1.buffer_addr, hdr->v1.start,
hdr->v1.end, hdr->v1.num_chars);
memconsole_setup(phys_to_virt(hdr->v1.buffer_addr), hdr->v1.num_chars);
memconsole_baseaddr = phys_to_virt(hdr->v1.buffer_addr);
memconsole_length = hdr->v1.num_chars;
memconsole_setup(memconsole_read);
}
static void found_v2_header(struct biosmemcon_ebda *hdr)
@ -67,8 +78,9 @@ static void found_v2_header(struct biosmemcon_ebda *hdr)
hdr->v2.buffer_addr, hdr->v2.start,
hdr->v2.end, hdr->v2.num_bytes);
memconsole_setup(phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start),
hdr->v2.end - hdr->v2.start);
memconsole_baseaddr = phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start);
memconsole_length = hdr->v2.end - hdr->v2.start;
memconsole_setup(memconsole_read);
}
/*

View File

@ -22,15 +22,15 @@
#include "memconsole.h"
static char *memconsole_baseaddr;
static size_t memconsole_length;
static ssize_t (*memconsole_read_func)(char *, loff_t, size_t);
static ssize_t memconsole_read(struct file *filp, struct kobject *kobp,
struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count)
{
return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr,
memconsole_length);
if (WARN_ON_ONCE(!memconsole_read_func))
return -EIO;
return memconsole_read_func(buf, pos, count);
}
static struct bin_attribute memconsole_bin_attr = {
@ -38,16 +38,14 @@ static struct bin_attribute memconsole_bin_attr = {
.read = memconsole_read,
};
void memconsole_setup(void *baseaddr, size_t length)
void memconsole_setup(ssize_t (*read_func)(char *, loff_t, size_t))
{
memconsole_baseaddr = baseaddr;
memconsole_length = length;
memconsole_read_func = read_func;
}
EXPORT_SYMBOL(memconsole_setup);
int memconsole_sysfs_init(void)
{
memconsole_bin_attr.size = memconsole_length;
return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr);
}
EXPORT_SYMBOL(memconsole_sysfs_init);

View File

@ -18,13 +18,14 @@
#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H
#define __FIRMWARE_GOOGLE_MEMCONSOLE_H
#include <linux/types.h>
/*
* memconsole_setup
*
* Initialize the memory console from raw (virtual) base
* address and length.
* Initialize the memory console, passing the function to handle read accesses.
*/
void memconsole_setup(void *baseaddr, size_t length);
void memconsole_setup(ssize_t (*read_func)(char *, loff_t, size_t));
/*
* memconsole_sysfs_init

View File

@ -118,14 +118,13 @@ static int vpd_section_attrib_add(const u8 *key, s32 key_len,
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->key = kzalloc(key_len + 1, GFP_KERNEL);
info->key = kstrndup(key, key_len, GFP_KERNEL);
if (!info->key) {
ret = -ENOMEM;
goto free_info;
}
memcpy(info->key, key, key_len);
sysfs_bin_attr_init(&info->bin_attr);
info->bin_attr.attr.name = info->key;
info->bin_attr.attr.mode = 0444;
@ -191,8 +190,7 @@ static int vpd_section_create_attribs(struct vpd_section *sec)
static int vpd_section_init(const char *name, struct vpd_section *sec,
phys_addr_t physaddr, size_t size)
{
int ret;
int raw_len;
int err;
sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
if (!sec->baseaddr)
@ -201,10 +199,11 @@ static int vpd_section_init(const char *name, struct vpd_section *sec,
sec->name = name;
/* We want to export the raw partion with name ${name}_raw */
raw_len = strlen(name) + 5;
sec->raw_name = kzalloc(raw_len, GFP_KERNEL);
strncpy(sec->raw_name, name, raw_len);
strncat(sec->raw_name, "_raw", raw_len);
sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
if (!sec->raw_name) {
err = -ENOMEM;
goto err_iounmap;
}
sysfs_bin_attr_init(&sec->bin_attr);
sec->bin_attr.attr.name = sec->raw_name;
@ -213,14 +212,14 @@ static int vpd_section_init(const char *name, struct vpd_section *sec,
sec->bin_attr.read = vpd_section_read;
sec->bin_attr.private = sec;
ret = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
if (ret)
goto free_sec;
err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
if (err)
goto err_free_raw_name;
sec->kobj = kobject_create_and_add(name, vpd_kobj);
if (!sec->kobj) {
ret = -EINVAL;
goto sysfs_remove;
err = -EINVAL;
goto err_sysfs_remove;
}
INIT_LIST_HEAD(&sec->attribs);
@ -230,14 +229,13 @@ static int vpd_section_init(const char *name, struct vpd_section *sec,
return 0;
sysfs_remove:
err_sysfs_remove:
sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
free_sec:
err_free_raw_name:
kfree(sec->raw_name);
err_iounmap:
iounmap(sec->baseaddr);
return ret;
return err;
}
static int vpd_section_destroy(struct vpd_section *sec)
@ -319,9 +317,6 @@ static int __init vpd_platform_init(void)
if (!vpd_kobj)
return -ENOMEM;
memset(&ro_vpd, 0, sizeof(ro_vpd));
memset(&rw_vpd, 0, sizeof(rw_vpd));
platform_driver_register(&vpd_driver);
return 0;

View File

@ -6,7 +6,33 @@ menu "FSI support"
config FSI
tristate "FSI support"
select CRC4
---help---
FSI - the FRU Support Interface - is a simple bus for low-level
access to POWER-based hardware.
if FSI
config FSI_MASTER_GPIO
tristate "GPIO-based FSI master"
depends on GPIOLIB
select CRC4
---help---
This option enables a FSI master driver using GPIO lines.
config FSI_MASTER_HUB
tristate "FSI hub master"
---help---
This option enables a FSI hub master driver. Hub is a type of FSI
master that is connected to the upstream master via a slave. Hubs
allow chaining of FSI links to an arbitrary depth. This allows for
a high target device fanout.
config FSI_SCOM
tristate "SCOM FSI client device driver"
---help---
This option enables an FSI based SCOM device driver.
endif
endmenu

View File

@ -1,2 +1,5 @@
obj-$(CONFIG_FSI) += fsi-core.o
obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o
obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
obj-$(CONFIG_FSI_SCOM) += fsi-scom.o

View File

@ -13,9 +13,830 @@
* GNU General Public License for more details.
*/
#include <linux/crc4.h>
#include <linux/device.h>
#include <linux/fsi.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/bitops.h>
#include "fsi-master.h"
#define CREATE_TRACE_POINTS
#include <trace/events/fsi.h>
#define FSI_SLAVE_CONF_NEXT_MASK GENMASK(31, 31)
#define FSI_SLAVE_CONF_SLOTS_MASK GENMASK(23, 16)
#define FSI_SLAVE_CONF_SLOTS_SHIFT 16
#define FSI_SLAVE_CONF_VERSION_MASK GENMASK(15, 12)
#define FSI_SLAVE_CONF_VERSION_SHIFT 12
#define FSI_SLAVE_CONF_TYPE_MASK GENMASK(11, 4)
#define FSI_SLAVE_CONF_TYPE_SHIFT 4
#define FSI_SLAVE_CONF_CRC_SHIFT 4
#define FSI_SLAVE_CONF_CRC_MASK GENMASK(3, 0)
#define FSI_SLAVE_CONF_DATA_BITS 28
#define FSI_PEEK_BASE 0x410
static const int engine_page_size = 0x400;
#define FSI_SLAVE_BASE 0x800
/*
* FSI slave engine control register offsets
*/
#define FSI_SMODE 0x0 /* R/W: Mode register */
#define FSI_SISC 0x8 /* R/W: Interrupt condition */
#define FSI_SSTAT 0x14 /* R : Slave status */
#define FSI_LLMODE 0x100 /* R/W: Link layer mode register */
/*
* SMODE fields
*/
#define FSI_SMODE_WSC 0x80000000 /* Warm start done */
#define FSI_SMODE_ECRC 0x20000000 /* Hw CRC check */
#define FSI_SMODE_SID_SHIFT 24 /* ID shift */
#define FSI_SMODE_SID_MASK 3 /* ID Mask */
#define FSI_SMODE_ED_SHIFT 20 /* Echo delay shift */
#define FSI_SMODE_ED_MASK 0xf /* Echo delay mask */
#define FSI_SMODE_SD_SHIFT 16 /* Send delay shift */
#define FSI_SMODE_SD_MASK 0xf /* Send delay mask */
#define FSI_SMODE_LBCRR_SHIFT 8 /* Clk ratio shift */
#define FSI_SMODE_LBCRR_MASK 0xf /* Clk ratio mask */
/*
* LLMODE fields
*/
#define FSI_LLMODE_ASYNC 0x1
#define FSI_SLAVE_SIZE_23b 0x800000
static DEFINE_IDA(master_ida);
struct fsi_slave {
struct device dev;
struct fsi_master *master;
int id;
int link;
uint32_t size; /* size of slave address space */
};
#define to_fsi_master(d) container_of(d, struct fsi_master, dev)
#define to_fsi_slave(d) container_of(d, struct fsi_slave, dev)
static const int slave_retries = 2;
static int discard_errors;
static int fsi_master_read(struct fsi_master *master, int link,
uint8_t slave_id, uint32_t addr, void *val, size_t size);
static int fsi_master_write(struct fsi_master *master, int link,
uint8_t slave_id, uint32_t addr, const void *val, size_t size);
static int fsi_master_break(struct fsi_master *master, int link);
/*
* fsi_device_read() / fsi_device_write() / fsi_device_peek()
*
* FSI endpoint-device support
*
* Read / write / peek accessors for a client
*
* Parameters:
* dev: Structure passed to FSI client device drivers on probe().
* addr: FSI address of given device. Client should pass in its base address
* plus desired offset to access its register space.
* val: For read/peek this is the value read at the specified address. For
* write this is value to write to the specified address.
* The data in val must be FSI bus endian (big endian).
* size: Size in bytes of the operation. Sizes supported are 1, 2 and 4 bytes.
* Addresses must be aligned on size boundaries or an error will result.
*/
int fsi_device_read(struct fsi_device *dev, uint32_t addr, void *val,
size_t size)
{
if (addr > dev->size || size > dev->size || addr > dev->size - size)
return -EINVAL;
return fsi_slave_read(dev->slave, dev->addr + addr, val, size);
}
EXPORT_SYMBOL_GPL(fsi_device_read);
int fsi_device_write(struct fsi_device *dev, uint32_t addr, const void *val,
size_t size)
{
if (addr > dev->size || size > dev->size || addr > dev->size - size)
return -EINVAL;
return fsi_slave_write(dev->slave, dev->addr + addr, val, size);
}
EXPORT_SYMBOL_GPL(fsi_device_write);
int fsi_device_peek(struct fsi_device *dev, void *val)
{
uint32_t addr = FSI_PEEK_BASE + ((dev->unit - 2) * sizeof(uint32_t));
return fsi_slave_read(dev->slave, addr, val, sizeof(uint32_t));
}
static void fsi_device_release(struct device *_device)
{
struct fsi_device *device = to_fsi_dev(_device);
kfree(device);
}
static struct fsi_device *fsi_create_device(struct fsi_slave *slave)
{
struct fsi_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return NULL;
dev->dev.parent = &slave->dev;
dev->dev.bus = &fsi_bus_type;
dev->dev.release = fsi_device_release;
return dev;
}
/* FSI slave support */
static int fsi_slave_calc_addr(struct fsi_slave *slave, uint32_t *addrp,
uint8_t *idp)
{
uint32_t addr = *addrp;
uint8_t id = *idp;
if (addr > slave->size)
return -EINVAL;
/* For 23 bit addressing, we encode the extra two bits in the slave
* id (and the slave's actual ID needs to be 0).
*/
if (addr > 0x1fffff) {
if (slave->id != 0)
return -EINVAL;
id = (addr >> 21) & 0x3;
addr &= 0x1fffff;
}
*addrp = addr;
*idp = id;
return 0;
}
int fsi_slave_report_and_clear_errors(struct fsi_slave *slave)
{
struct fsi_master *master = slave->master;
uint32_t irq, stat;
int rc, link;
uint8_t id;
link = slave->link;
id = slave->id;
rc = fsi_master_read(master, link, id, FSI_SLAVE_BASE + FSI_SISC,
&irq, sizeof(irq));
if (rc)
return rc;
rc = fsi_master_read(master, link, id, FSI_SLAVE_BASE + FSI_SSTAT,
&stat, sizeof(stat));
if (rc)
return rc;
dev_info(&slave->dev, "status: 0x%08x, sisc: 0x%08x\n",
be32_to_cpu(stat), be32_to_cpu(irq));
/* clear interrupts */
return fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SISC,
&irq, sizeof(irq));
}
static int fsi_slave_set_smode(struct fsi_master *master, int link, int id);
int fsi_slave_handle_error(struct fsi_slave *slave, bool write, uint32_t addr,
size_t size)
{
struct fsi_master *master = slave->master;
int rc, link;
uint32_t reg;
uint8_t id;
if (discard_errors)
return -1;
link = slave->link;
id = slave->id;
dev_dbg(&slave->dev, "handling error on %s to 0x%08x[%zd]",
write ? "write" : "read", addr, size);
/* try a simple clear of error conditions, which may fail if we've lost
* communication with the slave
*/
rc = fsi_slave_report_and_clear_errors(slave);
if (!rc)
return 0;
/* send a TERM and retry */
if (master->term) {
rc = master->term(master, link, id);
if (!rc) {
rc = fsi_master_read(master, link, id, 0,
&reg, sizeof(reg));
if (!rc)
rc = fsi_slave_report_and_clear_errors(slave);
if (!rc)
return 0;
}
}
/* getting serious, reset the slave via BREAK */
rc = fsi_master_break(master, link);
if (rc)
return rc;
rc = fsi_slave_set_smode(master, link, id);
if (rc)
return rc;
return fsi_slave_report_and_clear_errors(slave);
}
int fsi_slave_read(struct fsi_slave *slave, uint32_t addr,
void *val, size_t size)
{
uint8_t id = slave->id;
int rc, err_rc, i;
rc = fsi_slave_calc_addr(slave, &addr, &id);
if (rc)
return rc;
for (i = 0; i < slave_retries; i++) {
rc = fsi_master_read(slave->master, slave->link,
id, addr, val, size);
if (!rc)
break;
err_rc = fsi_slave_handle_error(slave, false, addr, size);
if (err_rc)
break;
}
return rc;
}
EXPORT_SYMBOL_GPL(fsi_slave_read);
int fsi_slave_write(struct fsi_slave *slave, uint32_t addr,
const void *val, size_t size)
{
uint8_t id = slave->id;
int rc, err_rc, i;
rc = fsi_slave_calc_addr(slave, &addr, &id);
if (rc)
return rc;
for (i = 0; i < slave_retries; i++) {
rc = fsi_master_write(slave->master, slave->link,
id, addr, val, size);
if (!rc)
break;
err_rc = fsi_slave_handle_error(slave, true, addr, size);
if (err_rc)
break;
}
return rc;
}
EXPORT_SYMBOL_GPL(fsi_slave_write);
extern int fsi_slave_claim_range(struct fsi_slave *slave,
uint32_t addr, uint32_t size)
{
if (addr + size < addr)
return -EINVAL;
if (addr + size > slave->size)
return -EINVAL;
/* todo: check for overlapping claims */
return 0;
}
EXPORT_SYMBOL_GPL(fsi_slave_claim_range);
extern void fsi_slave_release_range(struct fsi_slave *slave,
uint32_t addr, uint32_t size)
{
}
EXPORT_SYMBOL_GPL(fsi_slave_release_range);
static int fsi_slave_scan(struct fsi_slave *slave)
{
uint32_t engine_addr;
uint32_t conf;
int rc, i;
/*
* scan engines
*
* We keep the peek mode and slave engines for the core; so start
* at the third slot in the configuration table. We also need to
* skip the chip ID entry at the start of the address space.
*/
engine_addr = engine_page_size * 3;
for (i = 2; i < engine_page_size / sizeof(uint32_t); i++) {
uint8_t slots, version, type, crc;
struct fsi_device *dev;
rc = fsi_slave_read(slave, (i + 1) * sizeof(conf),
&conf, sizeof(conf));
if (rc) {
dev_warn(&slave->dev,
"error reading slave registers\n");
return -1;
}
conf = be32_to_cpu(conf);
crc = crc4(0, conf, 32);
if (crc) {
dev_warn(&slave->dev,
"crc error in slave register at 0x%04x\n",
i);
return -1;
}
slots = (conf & FSI_SLAVE_CONF_SLOTS_MASK)
>> FSI_SLAVE_CONF_SLOTS_SHIFT;
version = (conf & FSI_SLAVE_CONF_VERSION_MASK)
>> FSI_SLAVE_CONF_VERSION_SHIFT;
type = (conf & FSI_SLAVE_CONF_TYPE_MASK)
>> FSI_SLAVE_CONF_TYPE_SHIFT;
/*
* Unused address areas are marked by a zero type value; this
* skips the defined address areas
*/
if (type != 0 && slots != 0) {
/* create device */
dev = fsi_create_device(slave);
if (!dev)
return -ENOMEM;
dev->slave = slave;
dev->engine_type = type;
dev->version = version;
dev->unit = i;
dev->addr = engine_addr;
dev->size = slots * engine_page_size;
dev_dbg(&slave->dev,
"engine[%i]: type %x, version %x, addr %x size %x\n",
dev->unit, dev->engine_type, version,
dev->addr, dev->size);
dev_set_name(&dev->dev, "%02x:%02x:%02x:%02x",
slave->master->idx, slave->link,
slave->id, i - 2);
rc = device_register(&dev->dev);
if (rc) {
dev_warn(&slave->dev, "add failed: %d\n", rc);
put_device(&dev->dev);
}
}
engine_addr += slots * engine_page_size;
if (!(conf & FSI_SLAVE_CONF_NEXT_MASK))
break;
}
return 0;
}
static ssize_t fsi_slave_sysfs_raw_read(struct file *file,
struct kobject *kobj, struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct fsi_slave *slave = to_fsi_slave(kobj_to_dev(kobj));
size_t total_len, read_len;
int rc;
if (off < 0)
return -EINVAL;
if (off > 0xffffffff || count > 0xffffffff || off + count > 0xffffffff)
return -EINVAL;
for (total_len = 0; total_len < count; total_len += read_len) {
read_len = min_t(size_t, count, 4);
read_len -= off & 0x3;
rc = fsi_slave_read(slave, off, buf + total_len, read_len);
if (rc)
return rc;
off += read_len;
}
return count;
}
static ssize_t fsi_slave_sysfs_raw_write(struct file *file,
struct kobject *kobj, struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct fsi_slave *slave = to_fsi_slave(kobj_to_dev(kobj));
size_t total_len, write_len;
int rc;
if (off < 0)
return -EINVAL;
if (off > 0xffffffff || count > 0xffffffff || off + count > 0xffffffff)
return -EINVAL;
for (total_len = 0; total_len < count; total_len += write_len) {
write_len = min_t(size_t, count, 4);
write_len -= off & 0x3;
rc = fsi_slave_write(slave, off, buf + total_len, write_len);
if (rc)
return rc;
off += write_len;
}
return count;
}
static struct bin_attribute fsi_slave_raw_attr = {
.attr = {
.name = "raw",
.mode = 0600,
},
.size = 0,
.read = fsi_slave_sysfs_raw_read,
.write = fsi_slave_sysfs_raw_write,
};
static ssize_t fsi_slave_sysfs_term_write(struct file *file,
struct kobject *kobj, struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct fsi_slave *slave = to_fsi_slave(kobj_to_dev(kobj));
struct fsi_master *master = slave->master;
if (!master->term)
return -ENODEV;
master->term(master, slave->link, slave->id);
return count;
}
static struct bin_attribute fsi_slave_term_attr = {
.attr = {
.name = "term",
.mode = 0200,
},
.size = 0,
.write = fsi_slave_sysfs_term_write,
};
/* Encode slave local bus echo delay */
static inline uint32_t fsi_smode_echodly(int x)
{
return (x & FSI_SMODE_ED_MASK) << FSI_SMODE_ED_SHIFT;
}
/* Encode slave local bus send delay */
static inline uint32_t fsi_smode_senddly(int x)
{
return (x & FSI_SMODE_SD_MASK) << FSI_SMODE_SD_SHIFT;
}
/* Encode slave local bus clock rate ratio */
static inline uint32_t fsi_smode_lbcrr(int x)
{
return (x & FSI_SMODE_LBCRR_MASK) << FSI_SMODE_LBCRR_SHIFT;
}
/* Encode slave ID */
static inline uint32_t fsi_smode_sid(int x)
{
return (x & FSI_SMODE_SID_MASK) << FSI_SMODE_SID_SHIFT;
}
static const uint32_t fsi_slave_smode(int id)
{
return FSI_SMODE_WSC | FSI_SMODE_ECRC
| fsi_smode_sid(id)
| fsi_smode_echodly(0xf) | fsi_smode_senddly(0xf)
| fsi_smode_lbcrr(0x8);
}
static int fsi_slave_set_smode(struct fsi_master *master, int link, int id)
{
uint32_t smode;
/* set our smode register with the slave ID field to 0; this enables
* extended slave addressing
*/
smode = fsi_slave_smode(id);
smode = cpu_to_be32(smode);
return fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SMODE,
&smode, sizeof(smode));
}
static void fsi_slave_release(struct device *dev)
{
struct fsi_slave *slave = to_fsi_slave(dev);
kfree(slave);
}
static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id)
{
uint32_t chip_id, llmode;
struct fsi_slave *slave;
uint8_t crc;
int rc;
/* Currently, we only support single slaves on a link, and use the
* full 23-bit address range
*/
if (id != 0)
return -EINVAL;
rc = fsi_master_read(master, link, id, 0, &chip_id, sizeof(chip_id));
if (rc) {
dev_dbg(&master->dev, "can't read slave %02x:%02x %d\n",
link, id, rc);
return -ENODEV;
}
chip_id = be32_to_cpu(chip_id);
crc = crc4(0, chip_id, 32);
if (crc) {
dev_warn(&master->dev, "slave %02x:%02x invalid chip id CRC!\n",
link, id);
return -EIO;
}
dev_info(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n",
chip_id, master->idx, link, id);
rc = fsi_slave_set_smode(master, link, id);
if (rc) {
dev_warn(&master->dev,
"can't set smode on slave:%02x:%02x %d\n",
link, id, rc);
return -ENODEV;
}
/* If we're behind a master that doesn't provide a self-running bus
* clock, put the slave into async mode
*/
if (master->flags & FSI_MASTER_FLAG_SWCLOCK) {
llmode = cpu_to_be32(FSI_LLMODE_ASYNC);
rc = fsi_master_write(master, link, id,
FSI_SLAVE_BASE + FSI_LLMODE,
&llmode, sizeof(llmode));
if (rc)
dev_warn(&master->dev,
"can't set llmode on slave:%02x:%02x %d\n",
link, id, rc);
}
/* We can communicate with a slave; create the slave device and
* register.
*/
slave = kzalloc(sizeof(*slave), GFP_KERNEL);
if (!slave)
return -ENOMEM;
slave->master = master;
slave->dev.parent = &master->dev;
slave->dev.release = fsi_slave_release;
slave->link = link;
slave->id = id;
slave->size = FSI_SLAVE_SIZE_23b;
dev_set_name(&slave->dev, "slave@%02x:%02x", link, id);
rc = device_register(&slave->dev);
if (rc < 0) {
dev_warn(&master->dev, "failed to create slave device: %d\n",
rc);
put_device(&slave->dev);
return rc;
}
rc = device_create_bin_file(&slave->dev, &fsi_slave_raw_attr);
if (rc)
dev_warn(&slave->dev, "failed to create raw attr: %d\n", rc);
rc = device_create_bin_file(&slave->dev, &fsi_slave_term_attr);
if (rc)
dev_warn(&slave->dev, "failed to create term attr: %d\n", rc);
rc = fsi_slave_scan(slave);
if (rc)
dev_dbg(&master->dev, "failed during slave scan with: %d\n",
rc);
return rc;
}
/* FSI master support */
static int fsi_check_access(uint32_t addr, size_t size)
{
if (size != 1 && size != 2 && size != 4)
return -EINVAL;
if ((addr & 0x3) != (size & 0x3))
return -EINVAL;
return 0;
}
static int fsi_master_read(struct fsi_master *master, int link,
uint8_t slave_id, uint32_t addr, void *val, size_t size)
{
int rc;
trace_fsi_master_read(master, link, slave_id, addr, size);
rc = fsi_check_access(addr, size);
if (!rc)
rc = master->read(master, link, slave_id, addr, val, size);
trace_fsi_master_rw_result(master, link, slave_id, addr, size,
false, val, rc);
return rc;
}
static int fsi_master_write(struct fsi_master *master, int link,
uint8_t slave_id, uint32_t addr, const void *val, size_t size)
{
int rc;
trace_fsi_master_write(master, link, slave_id, addr, size, val);
rc = fsi_check_access(addr, size);
if (!rc)
rc = master->write(master, link, slave_id, addr, val, size);
trace_fsi_master_rw_result(master, link, slave_id, addr, size,
true, val, rc);
return rc;
}
static int fsi_master_link_enable(struct fsi_master *master, int link)
{
if (master->link_enable)
return master->link_enable(master, link);
return 0;
}
/*
* Issue a break command on this link
*/
static int fsi_master_break(struct fsi_master *master, int link)
{
trace_fsi_master_break(master, link);
if (master->send_break)
return master->send_break(master, link);
return 0;
}
static int fsi_master_scan(struct fsi_master *master)
{
int link, rc;
for (link = 0; link < master->n_links; link++) {
rc = fsi_master_link_enable(master, link);
if (rc) {
dev_dbg(&master->dev,
"enable link %d failed: %d\n", link, rc);
continue;
}
rc = fsi_master_break(master, link);
if (rc) {
dev_dbg(&master->dev,
"break to link %d failed: %d\n", link, rc);
continue;
}
fsi_slave_init(master, link, 0);
}
return 0;
}
static int fsi_slave_remove_device(struct device *dev, void *arg)
{
device_unregister(dev);
return 0;
}
static int fsi_master_remove_slave(struct device *dev, void *arg)
{
device_for_each_child(dev, NULL, fsi_slave_remove_device);
device_unregister(dev);
return 0;
}
static void fsi_master_unscan(struct fsi_master *master)
{
device_for_each_child(&master->dev, NULL, fsi_master_remove_slave);
}
static ssize_t master_rescan_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct fsi_master *master = to_fsi_master(dev);
int rc;
fsi_master_unscan(master);
rc = fsi_master_scan(master);
if (rc < 0)
return rc;
return count;
}
static DEVICE_ATTR(rescan, 0200, NULL, master_rescan_store);
static ssize_t master_break_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct fsi_master *master = to_fsi_master(dev);
fsi_master_break(master, 0);
return count;
}
static DEVICE_ATTR(break, 0200, NULL, master_break_store);
int fsi_master_register(struct fsi_master *master)
{
int rc;
if (!master)
return -EINVAL;
master->idx = ida_simple_get(&master_ida, 0, INT_MAX, GFP_KERNEL);
dev_set_name(&master->dev, "fsi%d", master->idx);
rc = device_register(&master->dev);
if (rc) {
ida_simple_remove(&master_ida, master->idx);
return rc;
}
rc = device_create_file(&master->dev, &dev_attr_rescan);
if (rc) {
device_unregister(&master->dev);
ida_simple_remove(&master_ida, master->idx);
return rc;
}
rc = device_create_file(&master->dev, &dev_attr_break);
if (rc) {
device_unregister(&master->dev);
ida_simple_remove(&master_ida, master->idx);
return rc;
}
fsi_master_scan(master);
return 0;
}
EXPORT_SYMBOL_GPL(fsi_master_register);
void fsi_master_unregister(struct fsi_master *master)
{
if (master->idx >= 0) {
ida_simple_remove(&master_ida, master->idx);
master->idx = -1;
}
fsi_master_unscan(master);
device_unregister(&master->dev);
}
EXPORT_SYMBOL_GPL(fsi_master_unregister);
/* FSI core & Linux bus type definitions */
@ -39,6 +860,23 @@ static int fsi_bus_match(struct device *dev, struct device_driver *drv)
return 0;
}
int fsi_driver_register(struct fsi_driver *fsi_drv)
{
if (!fsi_drv)
return -EINVAL;
if (!fsi_drv->id_table)
return -EINVAL;
return driver_register(&fsi_drv->drv);
}
EXPORT_SYMBOL_GPL(fsi_driver_register);
void fsi_driver_unregister(struct fsi_driver *fsi_drv)
{
driver_unregister(&fsi_drv->drv);
}
EXPORT_SYMBOL_GPL(fsi_driver_unregister);
struct bus_type fsi_bus_type = {
.name = "fsi",
.match = fsi_bus_match,
@ -57,3 +895,6 @@ static void fsi_exit(void)
module_init(fsi_init);
module_exit(fsi_exit);
module_param(discard_errors, int, 0664);
MODULE_LICENSE("GPL");
MODULE_PARM_DESC(discard_errors, "Don't invoke error handling on bus accesses");

View File

@ -0,0 +1,604 @@
/*
* A FSI master controller, using a simple GPIO bit-banging interface
*/
#include <linux/crc4.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fsi.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include "fsi-master.h"
#define FSI_GPIO_STD_DLY 1 /* Standard pin delay in nS */
#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */
#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */
#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */
#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */
#define FSI_INIT_CLOCKS 5000 /* Clock out any old data */
#define FSI_GPIO_STD_DELAY 10 /* Standard GPIO delay in nS */
/* todo: adjust down as low as */
/* possible or eliminate */
#define FSI_GPIO_CMD_DPOLL 0x2
#define FSI_GPIO_CMD_TERM 0x3f
#define FSI_GPIO_CMD_ABS_AR 0x4
#define FSI_GPIO_DPOLL_CLOCKS 100 /* < 21 will cause slave to hang */
/* Bus errors */
#define FSI_GPIO_ERR_BUSY 1 /* Slave stuck in busy state */
#define FSI_GPIO_RESP_ERRA 2 /* Any (misc) Error */
#define FSI_GPIO_RESP_ERRC 3 /* Slave reports master CRC error */
#define FSI_GPIO_MTOE 4 /* Master time out error */
#define FSI_GPIO_CRC_INVAL 5 /* Master reports slave CRC error */
/* Normal slave responses */
#define FSI_GPIO_RESP_BUSY 1
#define FSI_GPIO_RESP_ACK 0
#define FSI_GPIO_RESP_ACKD 4
#define FSI_GPIO_MAX_BUSY 100
#define FSI_GPIO_MTOE_COUNT 1000
#define FSI_GPIO_DRAIN_BITS 20
#define FSI_GPIO_CRC_SIZE 4
#define FSI_GPIO_MSG_ID_SIZE 2
#define FSI_GPIO_MSG_RESPID_SIZE 2
#define FSI_GPIO_PRIME_SLAVE_CLOCKS 100
struct fsi_master_gpio {
struct fsi_master master;
struct device *dev;
spinlock_t cmd_lock; /* Lock for commands */
struct gpio_desc *gpio_clk;
struct gpio_desc *gpio_data;
struct gpio_desc *gpio_trans; /* Voltage translator */
struct gpio_desc *gpio_enable; /* FSI enable */
struct gpio_desc *gpio_mux; /* Mux control */
};
#define CREATE_TRACE_POINTS
#include <trace/events/fsi_master_gpio.h>
#define to_fsi_master_gpio(m) container_of(m, struct fsi_master_gpio, master)
struct fsi_gpio_msg {
uint64_t msg;
uint8_t bits;
};
static void clock_toggle(struct fsi_master_gpio *master, int count)
{
int i;
for (i = 0; i < count; i++) {
ndelay(FSI_GPIO_STD_DLY);
gpiod_set_value(master->gpio_clk, 0);
ndelay(FSI_GPIO_STD_DLY);
gpiod_set_value(master->gpio_clk, 1);
}
}
static int sda_in(struct fsi_master_gpio *master)
{
int in;
ndelay(FSI_GPIO_STD_DLY);
in = gpiod_get_value(master->gpio_data);
return in ? 1 : 0;
}
static void sda_out(struct fsi_master_gpio *master, int value)
{
gpiod_set_value(master->gpio_data, value);
}
static void set_sda_input(struct fsi_master_gpio *master)
{
gpiod_direction_input(master->gpio_data);
gpiod_set_value(master->gpio_trans, 0);
}
static void set_sda_output(struct fsi_master_gpio *master, int value)
{
gpiod_set_value(master->gpio_trans, 1);
gpiod_direction_output(master->gpio_data, value);
}
static void clock_zeros(struct fsi_master_gpio *master, int count)
{
set_sda_output(master, 1);
clock_toggle(master, count);
}
static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg *msg,
uint8_t num_bits)
{
uint8_t bit, in_bit;
set_sda_input(master);
for (bit = 0; bit < num_bits; bit++) {
clock_toggle(master, 1);
in_bit = sda_in(master);
msg->msg <<= 1;
msg->msg |= ~in_bit & 0x1; /* Data is active low */
}
msg->bits += num_bits;
trace_fsi_master_gpio_in(master, num_bits, msg->msg);
}
static void serial_out(struct fsi_master_gpio *master,
const struct fsi_gpio_msg *cmd)
{
uint8_t bit;
uint64_t msg = ~cmd->msg; /* Data is active low */
uint64_t sda_mask = 0x1ULL << (cmd->bits - 1);
uint64_t last_bit = ~0;
int next_bit;
trace_fsi_master_gpio_out(master, cmd->bits, cmd->msg);
if (!cmd->bits) {
dev_warn(master->dev, "trying to output 0 bits\n");
return;
}
set_sda_output(master, 0);
/* Send the start bit */
sda_out(master, 0);
clock_toggle(master, 1);
/* Send the message */
for (bit = 0; bit < cmd->bits; bit++) {
next_bit = (msg & sda_mask) >> (cmd->bits - 1);
if (last_bit ^ next_bit) {
sda_out(master, next_bit);
last_bit = next_bit;
}
clock_toggle(master, 1);
msg <<= 1;
}
}
static void msg_push_bits(struct fsi_gpio_msg *msg, uint64_t data, int bits)
{
msg->msg <<= bits;
msg->msg |= data & ((1ull << bits) - 1);
msg->bits += bits;
}
static void msg_push_crc(struct fsi_gpio_msg *msg)
{
uint8_t crc;
int top;
top = msg->bits & 0x3;
/* start bit, and any non-aligned top bits */
crc = crc4(0, 1 << top | msg->msg >> (msg->bits - top), top + 1);
/* aligned bits */
crc = crc4(crc, msg->msg, msg->bits - top);
msg_push_bits(msg, crc, 4);
}
/*
* Encode an Absolute Address command
*/
static void build_abs_ar_command(struct fsi_gpio_msg *cmd,
uint8_t id, uint32_t addr, size_t size, const void *data)
{
bool write = !!data;
uint8_t ds;
int i;
cmd->bits = 0;
cmd->msg = 0;
msg_push_bits(cmd, id, 2);
msg_push_bits(cmd, FSI_GPIO_CMD_ABS_AR, 3);
msg_push_bits(cmd, write ? 0 : 1, 1);
/*
* The read/write size is encoded in the lower bits of the address
* (as it must be naturally-aligned), and the following ds bit.
*
* size addr:1 addr:0 ds
* 1 x x 0
* 2 x 0 1
* 4 0 1 1
*
*/
ds = size > 1 ? 1 : 0;
addr &= ~(size - 1);
if (size == 4)
addr |= 1;
msg_push_bits(cmd, addr & ((1 << 21) - 1), 21);
msg_push_bits(cmd, ds, 1);
for (i = 0; write && i < size; i++)
msg_push_bits(cmd, ((uint8_t *)data)[i], 8);
msg_push_crc(cmd);
}
static void build_dpoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id)
{
cmd->bits = 0;
cmd->msg = 0;
msg_push_bits(cmd, slave_id, 2);
msg_push_bits(cmd, FSI_GPIO_CMD_DPOLL, 3);
msg_push_crc(cmd);
}
static void echo_delay(struct fsi_master_gpio *master)
{
set_sda_output(master, 1);
clock_toggle(master, FSI_ECHO_DELAY_CLOCKS);
}
static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id)
{
cmd->bits = 0;
cmd->msg = 0;
msg_push_bits(cmd, slave_id, 2);
msg_push_bits(cmd, FSI_GPIO_CMD_TERM, 6);
msg_push_crc(cmd);
}
/*
* Store information on master errors so handler can detect and clean
* up the bus
*/
static void fsi_master_gpio_error(struct fsi_master_gpio *master, int error)
{
}
static int read_one_response(struct fsi_master_gpio *master,
uint8_t data_size, struct fsi_gpio_msg *msgp, uint8_t *tagp)
{
struct fsi_gpio_msg msg;
uint8_t id, tag;
uint32_t crc;
int i;
/* wait for the start bit */
for (i = 0; i < FSI_GPIO_MTOE_COUNT; i++) {
msg.bits = 0;
msg.msg = 0;
serial_in(master, &msg, 1);
if (msg.msg)
break;
}
if (i == FSI_GPIO_MTOE_COUNT) {
dev_dbg(master->dev,
"Master time out waiting for response\n");
fsi_master_gpio_error(master, FSI_GPIO_MTOE);
return -EIO;
}
msg.bits = 0;
msg.msg = 0;
/* Read slave ID & response tag */
serial_in(master, &msg, 4);
id = (msg.msg >> FSI_GPIO_MSG_RESPID_SIZE) & 0x3;
tag = msg.msg & 0x3;
/* If we have an ACK and we're expecting data, clock the data in too */
if (tag == FSI_GPIO_RESP_ACK && data_size)
serial_in(master, &msg, data_size * 8);
/* read CRC */
serial_in(master, &msg, FSI_GPIO_CRC_SIZE);
/* we have a whole message now; check CRC */
crc = crc4(0, 1, 1);
crc = crc4(crc, msg.msg, msg.bits);
if (crc) {
dev_dbg(master->dev, "ERR response CRC\n");
fsi_master_gpio_error(master, FSI_GPIO_CRC_INVAL);
return -EIO;
}
if (msgp)
*msgp = msg;
if (tagp)
*tagp = tag;
return 0;
}
static int issue_term(struct fsi_master_gpio *master, uint8_t slave)
{
struct fsi_gpio_msg cmd;
uint8_t tag;
int rc;
build_term_command(&cmd, slave);
serial_out(master, &cmd);
echo_delay(master);
rc = read_one_response(master, 0, NULL, &tag);
if (rc < 0) {
dev_err(master->dev,
"TERM failed; lost communication with slave\n");
return -EIO;
} else if (tag != FSI_GPIO_RESP_ACK) {
dev_err(master->dev, "TERM failed; response %d\n", tag);
return -EIO;
}
return 0;
}
static int poll_for_response(struct fsi_master_gpio *master,
uint8_t slave, uint8_t size, void *data)
{
struct fsi_gpio_msg response, cmd;
int busy_count = 0, rc, i;
uint8_t tag;
uint8_t *data_byte = data;
retry:
rc = read_one_response(master, size, &response, &tag);
if (rc)
return rc;
switch (tag) {
case FSI_GPIO_RESP_ACK:
if (size && data) {
uint64_t val = response.msg;
/* clear crc & mask */
val >>= 4;
val &= (1ull << (size * 8)) - 1;
for (i = 0; i < size; i++) {
data_byte[size-i-1] = val;
val >>= 8;
}
}
break;
case FSI_GPIO_RESP_BUSY:
/*
* Its necessary to clock slave before issuing
* d-poll, not indicated in the hardware protocol
* spec. < 20 clocks causes slave to hang, 21 ok.
*/
clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS);
if (busy_count++ < FSI_GPIO_MAX_BUSY) {
build_dpoll_command(&cmd, slave);
serial_out(master, &cmd);
echo_delay(master);
goto retry;
}
dev_warn(master->dev,
"ERR slave is stuck in busy state, issuing TERM\n");
issue_term(master, slave);
rc = -EIO;
break;
case FSI_GPIO_RESP_ERRA:
case FSI_GPIO_RESP_ERRC:
dev_dbg(master->dev, "ERR%c received: 0x%x\n",
tag == FSI_GPIO_RESP_ERRA ? 'A' : 'C',
(int)response.msg);
fsi_master_gpio_error(master, response.msg);
rc = -EIO;
break;
}
/* Clock the slave enough to be ready for next operation */
clock_zeros(master, FSI_GPIO_PRIME_SLAVE_CLOCKS);
return rc;
}
static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave,
struct fsi_gpio_msg *cmd, size_t resp_len, void *resp)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&master->cmd_lock, flags);
serial_out(master, cmd);
echo_delay(master);
rc = poll_for_response(master, slave, resp_len, resp);
spin_unlock_irqrestore(&master->cmd_lock, flags);
return rc;
}
static int fsi_master_gpio_read(struct fsi_master *_master, int link,
uint8_t id, uint32_t addr, void *val, size_t size)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
struct fsi_gpio_msg cmd;
if (link != 0)
return -ENODEV;
build_abs_ar_command(&cmd, id, addr, size, NULL);
return fsi_master_gpio_xfer(master, id, &cmd, size, val);
}
static int fsi_master_gpio_write(struct fsi_master *_master, int link,
uint8_t id, uint32_t addr, const void *val, size_t size)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
struct fsi_gpio_msg cmd;
if (link != 0)
return -ENODEV;
build_abs_ar_command(&cmd, id, addr, size, val);
return fsi_master_gpio_xfer(master, id, &cmd, 0, NULL);
}
static int fsi_master_gpio_term(struct fsi_master *_master,
int link, uint8_t id)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
struct fsi_gpio_msg cmd;
if (link != 0)
return -ENODEV;
build_term_command(&cmd, id);
return fsi_master_gpio_xfer(master, id, &cmd, 0, NULL);
}
static int fsi_master_gpio_break(struct fsi_master *_master, int link)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
if (link != 0)
return -ENODEV;
trace_fsi_master_gpio_break(master);
set_sda_output(master, 1);
sda_out(master, 1);
clock_toggle(master, FSI_PRE_BREAK_CLOCKS);
sda_out(master, 0);
clock_toggle(master, FSI_BREAK_CLOCKS);
echo_delay(master);
sda_out(master, 1);
clock_toggle(master, FSI_POST_BREAK_CLOCKS);
/* Wait for logic reset to take effect */
udelay(200);
return 0;
}
static void fsi_master_gpio_init(struct fsi_master_gpio *master)
{
gpiod_direction_output(master->gpio_mux, 1);
gpiod_direction_output(master->gpio_trans, 1);
gpiod_direction_output(master->gpio_enable, 1);
gpiod_direction_output(master->gpio_clk, 1);
gpiod_direction_output(master->gpio_data, 1);
/* todo: evaluate if clocks can be reduced */
clock_zeros(master, FSI_INIT_CLOCKS);
}
static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
if (link != 0)
return -ENODEV;
gpiod_set_value(master->gpio_enable, 1);
return 0;
}
static int fsi_master_gpio_probe(struct platform_device *pdev)
{
struct fsi_master_gpio *master;
struct gpio_desc *gpio;
master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
if (!master)
return -ENOMEM;
master->dev = &pdev->dev;
master->master.dev.parent = master->dev;
gpio = devm_gpiod_get(&pdev->dev, "clock", 0);
if (IS_ERR(gpio)) {
dev_err(&pdev->dev, "failed to get clock gpio\n");
return PTR_ERR(gpio);
}
master->gpio_clk = gpio;
gpio = devm_gpiod_get(&pdev->dev, "data", 0);
if (IS_ERR(gpio)) {
dev_err(&pdev->dev, "failed to get data gpio\n");
return PTR_ERR(gpio);
}
master->gpio_data = gpio;
/* Optional GPIOs */
gpio = devm_gpiod_get_optional(&pdev->dev, "trans", 0);
if (IS_ERR(gpio)) {
dev_err(&pdev->dev, "failed to get trans gpio\n");
return PTR_ERR(gpio);
}
master->gpio_trans = gpio;
gpio = devm_gpiod_get_optional(&pdev->dev, "enable", 0);
if (IS_ERR(gpio)) {
dev_err(&pdev->dev, "failed to get enable gpio\n");
return PTR_ERR(gpio);
}
master->gpio_enable = gpio;
gpio = devm_gpiod_get_optional(&pdev->dev, "mux", 0);
if (IS_ERR(gpio)) {
dev_err(&pdev->dev, "failed to get mux gpio\n");
return PTR_ERR(gpio);
}
master->gpio_mux = gpio;
master->master.n_links = 1;
master->master.flags = FSI_MASTER_FLAG_SWCLOCK;
master->master.read = fsi_master_gpio_read;
master->master.write = fsi_master_gpio_write;
master->master.term = fsi_master_gpio_term;
master->master.send_break = fsi_master_gpio_break;
master->master.link_enable = fsi_master_gpio_link_enable;
platform_set_drvdata(pdev, master);
spin_lock_init(&master->cmd_lock);
fsi_master_gpio_init(master);
return fsi_master_register(&master->master);
}
static int fsi_master_gpio_remove(struct platform_device *pdev)
{
struct fsi_master_gpio *master = platform_get_drvdata(pdev);
devm_gpiod_put(&pdev->dev, master->gpio_clk);
devm_gpiod_put(&pdev->dev, master->gpio_data);
if (master->gpio_trans)
devm_gpiod_put(&pdev->dev, master->gpio_trans);
if (master->gpio_enable)
devm_gpiod_put(&pdev->dev, master->gpio_enable);
if (master->gpio_mux)
devm_gpiod_put(&pdev->dev, master->gpio_mux);
fsi_master_unregister(&master->master);
return 0;
}
static const struct of_device_id fsi_master_gpio_match[] = {
{ .compatible = "fsi-master-gpio" },
{ },
};
static struct platform_driver fsi_master_gpio_driver = {
.driver = {
.name = "fsi-master-gpio",
.of_match_table = fsi_master_gpio_match,
},
.probe = fsi_master_gpio_probe,
.remove = fsi_master_gpio_remove,
};
module_platform_driver(fsi_master_gpio_driver);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,327 @@
/*
* FSI hub master driver
*
* Copyright (C) IBM Corporation 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/delay.h>
#include <linux/fsi.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "fsi-master.h"
/* Control Registers */
#define FSI_MMODE 0x0 /* R/W: mode */
#define FSI_MDLYR 0x4 /* R/W: delay */
#define FSI_MCRSP 0x8 /* R/W: clock rate */
#define FSI_MENP0 0x10 /* R/W: enable */
#define FSI_MLEVP0 0x18 /* R: plug detect */
#define FSI_MSENP0 0x18 /* S: Set enable */
#define FSI_MCENP0 0x20 /* C: Clear enable */
#define FSI_MAEB 0x70 /* R: Error address */
#define FSI_MVER 0x74 /* R: master version/type */
#define FSI_MRESP0 0xd0 /* W: Port reset */
#define FSI_MESRB0 0x1d0 /* R: Master error status */
#define FSI_MRESB0 0x1d0 /* W: Reset bridge */
#define FSI_MECTRL 0x2e0 /* W: Error control */
/* MMODE: Mode control */
#define FSI_MMODE_EIP 0x80000000 /* Enable interrupt polling */
#define FSI_MMODE_ECRC 0x40000000 /* Enable error recovery */
#define FSI_MMODE_EPC 0x10000000 /* Enable parity checking */
#define FSI_MMODE_P8_TO_LSB 0x00000010 /* Timeout value LSB */
/* MSB=1, LSB=0 is 0.8 ms */
/* MSB=0, LSB=1 is 0.9 ms */
#define FSI_MMODE_CRS0SHFT 18 /* Clk rate selection 0 shift */
#define FSI_MMODE_CRS0MASK 0x3ff /* Clk rate selection 0 mask */
#define FSI_MMODE_CRS1SHFT 8 /* Clk rate selection 1 shift */
#define FSI_MMODE_CRS1MASK 0x3ff /* Clk rate selection 1 mask */
/* MRESB: Reset brindge */
#define FSI_MRESB_RST_GEN 0x80000000 /* General reset */
#define FSI_MRESB_RST_ERR 0x40000000 /* Error Reset */
/* MRESB: Reset port */
#define FSI_MRESP_RST_ALL_MASTER 0x20000000 /* Reset all FSI masters */
#define FSI_MRESP_RST_ALL_LINK 0x10000000 /* Reset all FSI port contr. */
#define FSI_MRESP_RST_MCR 0x08000000 /* Reset FSI master reg. */
#define FSI_MRESP_RST_PYE 0x04000000 /* Reset FSI parity error */
#define FSI_MRESP_RST_ALL 0xfc000000 /* Reset any error */
/* MECTRL: Error control */
#define FSI_MECTRL_EOAE 0x8000 /* Enable machine check when */
/* master 0 in error */
#define FSI_MECTRL_P8_AUTO_TERM 0x4000 /* Auto terminate */
#define FSI_ENGID_HUB_MASTER 0x1c
#define FSI_HUB_LINK_OFFSET 0x80000
#define FSI_HUB_LINK_SIZE 0x80000
#define FSI_HUB_MASTER_MAX_LINKS 8
#define FSI_LINK_ENABLE_SETUP_TIME 10 /* in mS */
/*
* FSI hub master support
*
* A hub master increases the number of potential target devices that the
* primary FSI master can access. For each link a primary master supports,
* each of those links can in turn be chained to a hub master with multiple
* links of its own.
*
* The hub is controlled by a set of control registers exposed as a regular fsi
* device (the hub->upstream device), and provides access to the downstream FSI
* bus as through an address range on the slave itself (->addr and ->size).
*
* [This differs from "cascaded" masters, which expose the entire downstream
* bus entirely through the fsi device address range, and so have a smaller
* accessible address space.]
*/
struct fsi_master_hub {
struct fsi_master master;
struct fsi_device *upstream;
uint32_t addr, size; /* slave-relative addr of */
/* master address space */
};
#define to_fsi_master_hub(m) container_of(m, struct fsi_master_hub, master)
static int hub_master_read(struct fsi_master *master, int link,
uint8_t id, uint32_t addr, void *val, size_t size)
{
struct fsi_master_hub *hub = to_fsi_master_hub(master);
if (id != 0)
return -EINVAL;
addr += hub->addr + (link * FSI_HUB_LINK_SIZE);
return fsi_slave_read(hub->upstream->slave, addr, val, size);
}
static int hub_master_write(struct fsi_master *master, int link,
uint8_t id, uint32_t addr, const void *val, size_t size)
{
struct fsi_master_hub *hub = to_fsi_master_hub(master);
if (id != 0)
return -EINVAL;
addr += hub->addr + (link * FSI_HUB_LINK_SIZE);
return fsi_slave_write(hub->upstream->slave, addr, val, size);
}
static int hub_master_break(struct fsi_master *master, int link)
{
uint32_t addr, cmd;
addr = 0x4;
cmd = cpu_to_be32(0xc0de0000);
return hub_master_write(master, link, 0, addr, &cmd, sizeof(cmd));
}
static int hub_master_link_enable(struct fsi_master *master, int link)
{
struct fsi_master_hub *hub = to_fsi_master_hub(master);
int idx, bit;
__be32 reg;
int rc;
idx = link / 32;
bit = link % 32;
reg = cpu_to_be32(0x80000000 >> bit);
rc = fsi_device_write(hub->upstream, FSI_MSENP0 + (4 * idx), &reg, 4);
mdelay(FSI_LINK_ENABLE_SETUP_TIME);
fsi_device_read(hub->upstream, FSI_MENP0 + (4 * idx), &reg, 4);
return rc;
}
static void hub_master_release(struct device *dev)
{
struct fsi_master_hub *hub = to_fsi_master_hub(dev_to_fsi_master(dev));
kfree(hub);
}
/* mmode encoders */
static inline u32 fsi_mmode_crs0(u32 x)
{
return (x & FSI_MMODE_CRS0MASK) << FSI_MMODE_CRS0SHFT;
}
static inline u32 fsi_mmode_crs1(u32 x)
{
return (x & FSI_MMODE_CRS1MASK) << FSI_MMODE_CRS1SHFT;
}
static int hub_master_init(struct fsi_master_hub *hub)
{
struct fsi_device *dev = hub->upstream;
__be32 reg;
int rc;
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK
| FSI_MRESP_RST_MCR | FSI_MRESP_RST_PYE);
rc = fsi_device_write(dev, FSI_MRESP0, &reg, sizeof(reg));
if (rc)
return rc;
/* Initialize the MFSI (hub master) engine */
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK
| FSI_MRESP_RST_MCR | FSI_MRESP_RST_PYE);
rc = fsi_device_write(dev, FSI_MRESP0, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(FSI_MECTRL_EOAE | FSI_MECTRL_P8_AUTO_TERM);
rc = fsi_device_write(dev, FSI_MECTRL, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(FSI_MMODE_EIP | FSI_MMODE_ECRC | FSI_MMODE_EPC
| fsi_mmode_crs0(1) | fsi_mmode_crs1(1)
| FSI_MMODE_P8_TO_LSB);
rc = fsi_device_write(dev, FSI_MMODE, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(0xffff0000);
rc = fsi_device_write(dev, FSI_MDLYR, &reg, sizeof(reg));
if (rc)
return rc;
reg = ~0;
rc = fsi_device_write(dev, FSI_MSENP0, &reg, sizeof(reg));
if (rc)
return rc;
/* Leave enabled long enough for master logic to set up */
mdelay(FSI_LINK_ENABLE_SETUP_TIME);
rc = fsi_device_write(dev, FSI_MCENP0, &reg, sizeof(reg));
if (rc)
return rc;
rc = fsi_device_read(dev, FSI_MAEB, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK);
rc = fsi_device_write(dev, FSI_MRESP0, &reg, sizeof(reg));
if (rc)
return rc;
rc = fsi_device_read(dev, FSI_MLEVP0, &reg, sizeof(reg));
if (rc)
return rc;
/* Reset the master bridge */
reg = cpu_to_be32(FSI_MRESB_RST_GEN);
rc = fsi_device_write(dev, FSI_MRESB0, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(FSI_MRESB_RST_ERR);
return fsi_device_write(dev, FSI_MRESB0, &reg, sizeof(reg));
}
static int hub_master_probe(struct device *dev)
{
struct fsi_device *fsi_dev = to_fsi_dev(dev);
struct fsi_master_hub *hub;
uint32_t reg, links;
__be32 __reg;
int rc;
rc = fsi_device_read(fsi_dev, FSI_MVER, &__reg, sizeof(__reg));
if (rc)
return rc;
reg = be32_to_cpu(__reg);
links = (reg >> 8) & 0xff;
dev_info(dev, "hub version %08x (%d links)\n", reg, links);
rc = fsi_slave_claim_range(fsi_dev->slave, FSI_HUB_LINK_OFFSET,
FSI_HUB_LINK_SIZE * links);
if (rc) {
dev_err(dev, "can't claim slave address range for links");
return rc;
}
hub = kzalloc(sizeof(*hub), GFP_KERNEL);
if (!hub) {
rc = -ENOMEM;
goto err_release;
}
hub->addr = FSI_HUB_LINK_OFFSET;
hub->size = FSI_HUB_LINK_SIZE * links;
hub->upstream = fsi_dev;
hub->master.dev.parent = dev;
hub->master.dev.release = hub_master_release;
hub->master.n_links = links;
hub->master.read = hub_master_read;
hub->master.write = hub_master_write;
hub->master.send_break = hub_master_break;
hub->master.link_enable = hub_master_link_enable;
dev_set_drvdata(dev, hub);
hub_master_init(hub);
rc = fsi_master_register(&hub->master);
if (!rc)
return 0;
kfree(hub);
err_release:
fsi_slave_release_range(fsi_dev->slave, FSI_HUB_LINK_OFFSET,
FSI_HUB_LINK_SIZE * links);
return rc;
}
static int hub_master_remove(struct device *dev)
{
struct fsi_master_hub *hub = dev_get_drvdata(dev);
fsi_master_unregister(&hub->master);
fsi_slave_release_range(hub->upstream->slave, hub->addr, hub->size);
return 0;
}
static struct fsi_device_id hub_master_ids[] = {
{
.engine_type = FSI_ENGID_HUB_MASTER,
.version = FSI_VERSION_ANY,
},
{ 0 }
};
static struct fsi_driver hub_master_driver = {
.id_table = hub_master_ids,
.drv = {
.name = "fsi-master-hub",
.bus = &fsi_bus_type,
.probe = hub_master_probe,
.remove = hub_master_remove,
}
};
module_fsi_driver(hub_master_driver);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,43 @@
/*
* FSI master definitions. These comprise the core <--> master interface,
* to allow the core to interact with the (hardware-specific) masters.
*
* Copyright (C) IBM Corporation 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef DRIVERS_FSI_MASTER_H
#define DRIVERS_FSI_MASTER_H
#include <linux/device.h>
#define FSI_MASTER_FLAG_SWCLOCK 0x1
struct fsi_master {
struct device dev;
int idx;
int n_links;
int flags;
int (*read)(struct fsi_master *, int link, uint8_t id,
uint32_t addr, void *val, size_t size);
int (*write)(struct fsi_master *, int link, uint8_t id,
uint32_t addr, const void *val, size_t size);
int (*term)(struct fsi_master *, int link, uint8_t id);
int (*send_break)(struct fsi_master *, int link);
int (*link_enable)(struct fsi_master *, int link);
};
#define dev_to_fsi_master(d) container_of(d, struct fsi_master, dev)
extern int fsi_master_register(struct fsi_master *master);
extern void fsi_master_unregister(struct fsi_master *master);
#endif /* DRIVERS_FSI_MASTER_H */

View File

@ -0,0 +1,263 @@
/*
* SCOM FSI Client device driver
*
* Copyright (C) IBM Corporation 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/fsi.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/list.h>
#include <linux/idr.h>
#define FSI_ENGID_SCOM 0x5
#define SCOM_FSI2PIB_DELAY 50
/* SCOM engine register set */
#define SCOM_DATA0_REG 0x00
#define SCOM_DATA1_REG 0x04
#define SCOM_CMD_REG 0x08
#define SCOM_RESET_REG 0x1C
#define SCOM_RESET_CMD 0x80000000
#define SCOM_WRITE_CMD 0x80000000
struct scom_device {
struct list_head link;
struct fsi_device *fsi_dev;
struct miscdevice mdev;
char name[32];
int idx;
};
#define to_scom_dev(x) container_of((x), struct scom_device, mdev)
static struct list_head scom_devices;
static DEFINE_IDA(scom_ida);
static int put_scom(struct scom_device *scom_dev, uint64_t value,
uint32_t addr)
{
int rc;
uint32_t data;
data = cpu_to_be32(SCOM_RESET_CMD);
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_RESET_REG, &data,
sizeof(uint32_t));
if (rc)
return rc;
data = cpu_to_be32((value >> 32) & 0xffffffff);
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data,
sizeof(uint32_t));
if (rc)
return rc;
data = cpu_to_be32(value & 0xffffffff);
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data,
sizeof(uint32_t));
if (rc)
return rc;
data = cpu_to_be32(SCOM_WRITE_CMD | addr);
return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data,
sizeof(uint32_t));
}
static int get_scom(struct scom_device *scom_dev, uint64_t *value,
uint32_t addr)
{
uint32_t result, data;
int rc;
*value = 0ULL;
data = cpu_to_be32(addr);
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data,
sizeof(uint32_t));
if (rc)
return rc;
rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result,
sizeof(uint32_t));
if (rc)
return rc;
*value |= (uint64_t)cpu_to_be32(result) << 32;
rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result,
sizeof(uint32_t));
if (rc)
return rc;
*value |= cpu_to_be32(result);
return 0;
}
static ssize_t scom_read(struct file *filep, char __user *buf, size_t len,
loff_t *offset)
{
int rc;
struct miscdevice *mdev =
(struct miscdevice *)filep->private_data;
struct scom_device *scom = to_scom_dev(mdev);
struct device *dev = &scom->fsi_dev->dev;
uint64_t val;
if (len != sizeof(uint64_t))
return -EINVAL;
rc = get_scom(scom, &val, *offset);
if (rc) {
dev_dbg(dev, "get_scom fail:%d\n", rc);
return rc;
}
rc = copy_to_user(buf, &val, len);
if (rc)
dev_dbg(dev, "copy to user failed:%d\n", rc);
return rc ? rc : len;
}
static ssize_t scom_write(struct file *filep, const char __user *buf,
size_t len, loff_t *offset)
{
int rc;
struct miscdevice *mdev = filep->private_data;
struct scom_device *scom = to_scom_dev(mdev);
struct device *dev = &scom->fsi_dev->dev;
uint64_t val;
if (len != sizeof(uint64_t))
return -EINVAL;
rc = copy_from_user(&val, buf, len);
if (rc) {
dev_dbg(dev, "copy from user failed:%d\n", rc);
return -EINVAL;
}
rc = put_scom(scom, val, *offset);
if (rc) {
dev_dbg(dev, "put_scom failed with:%d\n", rc);
return rc;
}
return len;
}
static loff_t scom_llseek(struct file *file, loff_t offset, int whence)
{
switch (whence) {
case SEEK_CUR:
break;
case SEEK_SET:
file->f_pos = offset;
break;
default:
return -EINVAL;
}
return offset;
}
static const struct file_operations scom_fops = {
.owner = THIS_MODULE,
.llseek = scom_llseek,
.read = scom_read,
.write = scom_write,
};
static int scom_probe(struct device *dev)
{
struct fsi_device *fsi_dev = to_fsi_dev(dev);
struct scom_device *scom;
scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL);
if (!scom)
return -ENOMEM;
scom->idx = ida_simple_get(&scom_ida, 1, INT_MAX, GFP_KERNEL);
snprintf(scom->name, sizeof(scom->name), "scom%d", scom->idx);
scom->fsi_dev = fsi_dev;
scom->mdev.minor = MISC_DYNAMIC_MINOR;
scom->mdev.fops = &scom_fops;
scom->mdev.name = scom->name;
scom->mdev.parent = dev;
list_add(&scom->link, &scom_devices);
return misc_register(&scom->mdev);
}
static int scom_remove(struct device *dev)
{
struct scom_device *scom, *scom_tmp;
struct fsi_device *fsi_dev = to_fsi_dev(dev);
list_for_each_entry_safe(scom, scom_tmp, &scom_devices, link) {
if (scom->fsi_dev == fsi_dev) {
list_del(&scom->link);
ida_simple_remove(&scom_ida, scom->idx);
misc_deregister(&scom->mdev);
}
}
return 0;
}
static struct fsi_device_id scom_ids[] = {
{
.engine_type = FSI_ENGID_SCOM,
.version = FSI_VERSION_ANY,
},
{ 0 }
};
static struct fsi_driver scom_drv = {
.id_table = scom_ids,
.drv = {
.name = "scom",
.bus = &fsi_bus_type,
.probe = scom_probe,
.remove = scom_remove,
}
};
static int scom_init(void)
{
INIT_LIST_HEAD(&scom_devices);
return fsi_driver_register(&scom_drv);
}
static void scom_exit(void)
{
struct list_head *pos;
struct scom_device *scom;
list_for_each(pos, &scom_devices) {
scom = list_entry(pos, struct scom_device, link);
misc_deregister(&scom->mdev);
devm_kfree(&scom->fsi_dev->dev, scom);
}
fsi_driver_unregister(&scom_drv);
}
module_init(scom_init);
module_exit(scom_exit);
MODULE_LICENSE("GPL");

View File

@ -630,9 +630,13 @@ void vmbus_close(struct vmbus_channel *channel)
*/
list_for_each_safe(cur, tmp, &channel->sc_list) {
cur_channel = list_entry(cur, struct vmbus_channel, sc_list);
if (cur_channel->state != CHANNEL_OPENED_STATE)
continue;
vmbus_close_internal(cur_channel);
if (cur_channel->rescind) {
mutex_lock(&vmbus_connection.channel_mutex);
hv_process_channel_removal(cur_channel,
cur_channel->offermsg.child_relid);
mutex_unlock(&vmbus_connection.channel_mutex);
}
}
/*
* Now close the primary.

View File

@ -428,7 +428,6 @@ void vmbus_free_channels(void)
{
struct vmbus_channel *channel, *tmp;
mutex_lock(&vmbus_connection.channel_mutex);
list_for_each_entry_safe(channel, tmp, &vmbus_connection.chn_list,
listentry) {
/* hv_process_channel_removal() needs this */
@ -436,7 +435,6 @@ void vmbus_free_channels(void)
vmbus_device_unregister(channel->device_obj);
}
mutex_unlock(&vmbus_connection.channel_mutex);
}
/*
@ -483,8 +481,10 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
list_add_tail(&newchannel->sc_list, &channel->sc_list);
channel->num_sc++;
spin_unlock_irqrestore(&channel->lock, flags);
} else
} else {
atomic_dec(&vmbus_connection.offer_in_progress);
goto err_free_chan;
}
}
dev_type = hv_get_dev_type(newchannel);
@ -511,6 +511,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
if (!fnew) {
if (channel->sc_creation_callback != NULL)
channel->sc_creation_callback(newchannel);
atomic_dec(&vmbus_connection.offer_in_progress);
return;
}
@ -532,9 +533,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
* binding which eventually invokes the device driver's AddDevice()
* method.
*/
mutex_lock(&vmbus_connection.channel_mutex);
ret = vmbus_device_register(newchannel->device_obj);
mutex_unlock(&vmbus_connection.channel_mutex);
if (ret != 0) {
pr_err("unable to add child device object (relid %d)\n",
@ -542,6 +541,8 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
kfree(newchannel->device_obj);
goto err_deq_chan;
}
atomic_dec(&vmbus_connection.offer_in_progress);
return;
err_deq_chan:
@ -797,6 +798,7 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr)
newchannel = alloc_channel();
if (!newchannel) {
vmbus_release_relid(offer->child_relid);
atomic_dec(&vmbus_connection.offer_in_progress);
pr_err("Unable to allocate channel object\n");
return;
}
@ -843,16 +845,38 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
rescind = (struct vmbus_channel_rescind_offer *)hdr;
/*
* The offer msg and the corresponding rescind msg
* from the host are guranteed to be ordered -
* offer comes in first and then the rescind.
* Since we process these events in work elements,
* and with preemption, we may end up processing
* the events out of order. Given that we handle these
* work elements on the same CPU, this is possible only
* in the case of preemption. In any case wait here
* until the offer processing has moved beyond the
* point where the channel is discoverable.
*/
while (atomic_read(&vmbus_connection.offer_in_progress) != 0) {
/*
* We wait here until any channel offer is currently
* being processed.
*/
msleep(1);
}
mutex_lock(&vmbus_connection.channel_mutex);
channel = relid2channel(rescind->child_relid);
mutex_unlock(&vmbus_connection.channel_mutex);
if (channel == NULL) {
/*
* This is very impossible, because in
* vmbus_process_offer(), we have already invoked
* vmbus_release_relid() on error.
* We failed in processing the offer message;
* we would have cleaned up the relid in that
* failure path.
*/
goto out;
return;
}
spin_lock_irqsave(&channel->lock, flags);
@ -864,7 +888,7 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
if (channel->device_obj) {
if (channel->chn_rescind_callback) {
channel->chn_rescind_callback(channel);
goto out;
return;
}
/*
* We will have to unregister this device from the
@ -875,13 +899,26 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
vmbus_device_unregister(channel->device_obj);
put_device(dev);
}
} else {
hv_process_channel_removal(channel,
channel->offermsg.child_relid);
}
out:
mutex_unlock(&vmbus_connection.channel_mutex);
if (channel->primary_channel != NULL) {
/*
* Sub-channel is being rescinded. Following is the channel
* close sequence when initiated from the driveri (refer to
* vmbus_close() for details):
* 1. Close all sub-channels first
* 2. Then close the primary channel.
*/
if (channel->state == CHANNEL_OPEN_STATE) {
/*
* The channel is currently not open;
* it is safe for us to cleanup the channel.
*/
mutex_lock(&vmbus_connection.channel_mutex);
hv_process_channel_removal(channel,
channel->offermsg.child_relid);
mutex_unlock(&vmbus_connection.channel_mutex);
}
}
}
void vmbus_hvsock_device_unregister(struct vmbus_channel *channel)

View File

@ -93,10 +93,13 @@ static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo,
* all the CPUs. This is needed for kexec to work correctly where
* the CPU attempting to connect may not be CPU 0.
*/
if (version >= VERSION_WIN8_1)
if (version >= VERSION_WIN8_1) {
msg->target_vcpu = hv_context.vp_index[smp_processor_id()];
else
vmbus_connection.connect_cpu = smp_processor_id();
} else {
msg->target_vcpu = 0;
vmbus_connection.connect_cpu = 0;
}
/*
* Add to list before we send the request since we may
@ -370,7 +373,7 @@ int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep)
break;
case HV_STATUS_INSUFFICIENT_MEMORY:
case HV_STATUS_INSUFFICIENT_BUFFERS:
ret = -ENOMEM;
ret = -ENOBUFS;
break;
case HV_STATUS_SUCCESS:
return ret;
@ -387,7 +390,7 @@ int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep)
else
mdelay(usec / 1000);
if (usec < 256000)
if (retries < 22)
usec *= 2;
}
return ret;

View File

@ -82,10 +82,15 @@ int hv_post_message(union hv_connection_id connection_id,
aligned_msg->message_type = message_type;
aligned_msg->payload_size = payload_size;
memcpy((void *)aligned_msg->payload, payload, payload_size);
put_cpu_ptr(hv_cpu);
status = hv_do_hypercall(HVCALL_POST_MESSAGE, aligned_msg, NULL);
/* Preemption must remain disabled until after the hypercall
* so some other thread can't get scheduled onto this cpu and
* corrupt the per-cpu post_msg_page
*/
put_cpu_ptr(hv_cpu);
return status & 0xFFFF;
}
@ -96,7 +101,7 @@ static int hv_ce_set_next_event(unsigned long delta,
WARN_ON(!clockevent_state_oneshot(evt));
hv_get_current_tick(current_tick);
current_tick = hyperv_cs->read(NULL);
current_tick += delta;
hv_init_timer(HV_X64_MSR_STIMER0_COUNT, current_tick);
return 0;

View File

@ -112,7 +112,7 @@ static void kvp_poll_wrapper(void *channel)
{
/* Transaction is finished, reset the state here to avoid races. */
kvp_transaction.state = HVUTIL_READY;
hv_kvp_onchannelcallback(channel);
tasklet_schedule(&((struct vmbus_channel *)channel)->callback_event);
}
static void kvp_register_done(void)
@ -159,7 +159,7 @@ static void kvp_timeout_func(struct work_struct *dummy)
static void kvp_host_handshake_func(struct work_struct *dummy)
{
hv_poll_channel(kvp_transaction.recv_channel, hv_kvp_onchannelcallback);
tasklet_schedule(&kvp_transaction.recv_channel->callback_event);
}
static int kvp_handle_handshake(struct hv_kvp_msg *msg)
@ -625,16 +625,17 @@ void hv_kvp_onchannelcallback(void *context)
NEGO_IN_PROGRESS,
NEGO_FINISHED} host_negotiatied = NEGO_NOT_STARTED;
if (host_negotiatied == NEGO_NOT_STARTED &&
kvp_transaction.state < HVUTIL_READY) {
if (kvp_transaction.state < HVUTIL_READY) {
/*
* If userspace daemon is not connected and host is asking
* us to negotiate we need to delay to not lose messages.
* This is important for Failover IP setting.
*/
host_negotiatied = NEGO_IN_PROGRESS;
schedule_delayed_work(&kvp_host_handshake_work,
if (host_negotiatied == NEGO_NOT_STARTED) {
host_negotiatied = NEGO_IN_PROGRESS;
schedule_delayed_work(&kvp_host_handshake_work,
HV_UTIL_NEGO_TIMEOUT * HZ);
}
return;
}
if (kvp_transaction.state > HVUTIL_READY)
@ -702,6 +703,7 @@ void hv_kvp_onchannelcallback(void *context)
VM_PKT_DATA_INBAND, 0);
host_negotiatied = NEGO_FINISHED;
hv_poll_channel(kvp_transaction.recv_channel, kvp_poll_wrapper);
}
}

View File

@ -202,27 +202,39 @@ static void shutdown_onchannelcallback(void *context)
/*
* Set the host time in a process context.
*/
static struct work_struct adj_time_work;
struct adj_time_work {
struct work_struct work;
u64 host_time;
u64 ref_time;
u8 flags;
};
/*
* The last time sample, received from the host. PTP device responds to
* requests by using this data and the current partition-wide time reference
* count.
*/
static struct {
u64 host_time;
u64 ref_time;
spinlock_t lock;
} host_ts;
static struct timespec64 hv_get_adj_host_time(void)
{
struct timespec64 ts;
u64 newtime, reftime;
unsigned long flags;
spin_lock_irqsave(&host_ts.lock, flags);
reftime = hyperv_cs->read(hyperv_cs);
newtime = host_ts.host_time + (reftime - host_ts.ref_time);
ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
spin_unlock_irqrestore(&host_ts.lock, flags);
return ts;
}
static void hv_set_host_time(struct work_struct *work)
{
struct adj_time_work *wrk;
struct timespec64 host_ts;
u64 reftime, newtime;
struct timespec64 ts = hv_get_adj_host_time();
wrk = container_of(work, struct adj_time_work, work);
reftime = hyperv_cs->read(hyperv_cs);
newtime = wrk->host_time + (reftime - wrk->ref_time);
host_ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
do_settimeofday64(&host_ts);
do_settimeofday64(&ts);
}
/*
@ -238,62 +250,35 @@ static void hv_set_host_time(struct work_struct *work)
* typically used as a hint to the guest. The guest is under no obligation
* to discipline the clock.
*/
static struct adj_time_work wrk;
/*
* The last time sample, received from the host. PTP device responds to
* requests by using this data and the current partition-wide time reference
* count.
*/
static struct {
u64 host_time;
u64 ref_time;
struct system_time_snapshot snap;
spinlock_t lock;
} host_ts;
static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 adj_flags)
{
unsigned long flags;
u64 cur_reftime;
/*
* This check is safe since we are executing in the
* interrupt context and time synch messages are always
* delivered on the same CPU.
* Save the adjusted time sample from the host and the snapshot
* of the current system time.
*/
if (adj_flags & ICTIMESYNCFLAG_SYNC) {
/* Queue a job to do do_settimeofday64() */
if (work_pending(&wrk.work))
return;
spin_lock_irqsave(&host_ts.lock, flags);
wrk.host_time = hosttime;
wrk.ref_time = reftime;
wrk.flags = adj_flags;
schedule_work(&wrk.work);
} else {
/*
* Save the adjusted time sample from the host and the snapshot
* of the current system time for PTP device.
*/
spin_lock_irqsave(&host_ts.lock, flags);
cur_reftime = hyperv_cs->read(hyperv_cs);
host_ts.host_time = hosttime;
host_ts.ref_time = cur_reftime;
cur_reftime = hyperv_cs->read(hyperv_cs);
host_ts.host_time = hosttime;
host_ts.ref_time = cur_reftime;
ktime_get_snapshot(&host_ts.snap);
/*
* TimeSync v4 messages contain reference time (guest's Hyper-V
* clocksource read when the time sample was generated), we can
* improve the precision by adding the delta between now and the
* time of generation. For older protocols we set
* reftime == cur_reftime on call.
*/
host_ts.host_time += (cur_reftime - reftime);
/*
* TimeSync v4 messages contain reference time (guest's Hyper-V
* clocksource read when the time sample was generated), we can
* improve the precision by adding the delta between now and the
* time of generation.
*/
if (ts_srv_version > TS_VERSION_3)
host_ts.host_time += (cur_reftime - reftime);
spin_unlock_irqrestore(&host_ts.lock, flags);
spin_unlock_irqrestore(&host_ts.lock, flags);
}
/* Schedule work to do do_settimeofday64() */
if (adj_flags & ICTIMESYNCFLAG_SYNC)
schedule_work(&adj_time_work);
}
/*
@ -341,8 +326,8 @@ static void timesync_onchannelcallback(void *context)
sizeof(struct vmbuspipe_hdr) +
sizeof(struct icmsg_hdr)];
adj_guesttime(timedatap->parenttime,
0,
timedatap->flags);
hyperv_cs->read(hyperv_cs),
timedatap->flags);
}
}
@ -526,58 +511,17 @@ static int hv_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
static int hv_ptp_gettime(struct ptp_clock_info *info, struct timespec64 *ts)
{
unsigned long flags;
u64 newtime, reftime;
spin_lock_irqsave(&host_ts.lock, flags);
reftime = hyperv_cs->read(hyperv_cs);
newtime = host_ts.host_time + (reftime - host_ts.ref_time);
*ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
spin_unlock_irqrestore(&host_ts.lock, flags);
*ts = hv_get_adj_host_time();
return 0;
}
static int hv_ptp_get_syncdevicetime(ktime_t *device,
struct system_counterval_t *system,
void *ctx)
{
system->cs = hyperv_cs;
system->cycles = host_ts.ref_time;
*device = ns_to_ktime((host_ts.host_time - WLTIMEDELTA) * 100);
return 0;
}
static int hv_ptp_getcrosststamp(struct ptp_clock_info *ptp,
struct system_device_crosststamp *xtstamp)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&host_ts.lock, flags);
/*
* host_ts contains the last time sample from the host and the snapshot
* of system time. We don't need to calculate the time delta between
* the reception and now as get_device_system_crosststamp() does the
* required interpolation.
*/
ret = get_device_system_crosststamp(hv_ptp_get_syncdevicetime,
NULL, &host_ts.snap, xtstamp);
spin_unlock_irqrestore(&host_ts.lock, flags);
return ret;
}
static struct ptp_clock_info ptp_hyperv_info = {
.name = "hyperv",
.enable = hv_ptp_enable,
.adjtime = hv_ptp_adjtime,
.adjfreq = hv_ptp_adjfreq,
.gettime64 = hv_ptp_gettime,
.getcrosststamp = hv_ptp_getcrosststamp,
.settime64 = hv_ptp_settime,
.owner = THIS_MODULE,
};
@ -592,7 +536,7 @@ static int hv_timesync_init(struct hv_util_service *srv)
spin_lock_init(&host_ts.lock);
INIT_WORK(&wrk.work, hv_set_host_time);
INIT_WORK(&adj_time_work, hv_set_host_time);
/*
* ptp_clock_register() returns NULL when CONFIG_PTP_1588_CLOCK is
@ -613,7 +557,7 @@ static void hv_timesync_deinit(void)
{
if (hv_ptp_clock)
ptp_clock_unregister(hv_ptp_clock);
cancel_work_sync(&wrk.work);
cancel_work_sync(&adj_time_work);
}
static int __init init_hyperv_utils(void)

View File

@ -303,6 +303,13 @@ enum vmbus_connect_state {
#define MAX_SIZE_CHANNEL_MESSAGE HV_MESSAGE_PAYLOAD_BYTE_COUNT
struct vmbus_connection {
/*
* CPU on which the initial host contact was made.
*/
int connect_cpu;
atomic_t offer_in_progress;
enum vmbus_connect_state conn_state;
atomic_t next_gpadl_handle;
@ -411,6 +418,10 @@ static inline void hv_poll_channel(struct vmbus_channel *channel,
if (!channel)
return;
if (in_interrupt() && (channel->target_cpu == smp_processor_id())) {
cb(channel);
return;
}
smp_call_function_single(channel->target_cpu, cb, channel, true);
}

View File

@ -608,40 +608,6 @@ static void vmbus_free_dynids(struct hv_driver *drv)
spin_unlock(&drv->dynids.lock);
}
/* Parse string of form: 1b4e28ba-2fa1-11d2-883f-b9a761bde3f */
static int get_uuid_le(const char *str, uuid_le *uu)
{
unsigned int b[16];
int i;
if (strlen(str) < 37)
return -1;
for (i = 0; i < 36; i++) {
switch (i) {
case 8: case 13: case 18: case 23:
if (str[i] != '-')
return -1;
break;
default:
if (!isxdigit(str[i]))
return -1;
}
}
/* unparse little endian output byte order */
if (sscanf(str,
"%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x",
&b[3], &b[2], &b[1], &b[0],
&b[5], &b[4], &b[7], &b[6], &b[8], &b[9],
&b[10], &b[11], &b[12], &b[13], &b[14], &b[15]) != 16)
return -1;
for (i = 0; i < 16; i++)
uu->b[i] = b[i];
return 0;
}
/*
* store_new_id - sysfs frontend to vmbus_add_dynid()
*
@ -651,11 +617,12 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
size_t count)
{
struct hv_driver *drv = drv_to_hv_drv(driver);
uuid_le guid = NULL_UUID_LE;
uuid_le guid;
ssize_t retval;
if (get_uuid_le(buf, &guid) != 0)
return -EINVAL;
retval = uuid_le_to_bin(buf, &guid);
if (retval)
return retval;
if (hv_vmbus_get_id(drv, &guid))
return -EEXIST;
@ -677,12 +644,14 @@ static ssize_t remove_id_store(struct device_driver *driver, const char *buf,
{
struct hv_driver *drv = drv_to_hv_drv(driver);
struct vmbus_dynid *dynid, *n;
uuid_le guid = NULL_UUID_LE;
size_t retval = -ENODEV;
uuid_le guid;
ssize_t retval;
if (get_uuid_le(buf, &guid))
return -EINVAL;
retval = uuid_le_to_bin(buf, &guid);
if (retval)
return retval;
retval = -ENODEV;
spin_lock(&drv->dynids.lock);
list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) {
struct hv_vmbus_device_id *id = &dynid->id;
@ -798,8 +767,10 @@ static void vmbus_device_release(struct device *device)
struct hv_device *hv_dev = device_to_hv_device(device);
struct vmbus_channel *channel = hv_dev->channel;
mutex_lock(&vmbus_connection.channel_mutex);
hv_process_channel_removal(channel,
channel->offermsg.child_relid);
mutex_unlock(&vmbus_connection.channel_mutex);
kfree(hv_dev);
}
@ -877,7 +848,32 @@ void vmbus_on_msg_dpc(unsigned long data)
INIT_WORK(&ctx->work, vmbus_onmessage_work);
memcpy(&ctx->msg, msg, sizeof(*msg));
queue_work(vmbus_connection.work_queue, &ctx->work);
/*
* The host can generate a rescind message while we
* may still be handling the original offer. We deal with
* this condition by ensuring the processing is done on the
* same CPU.
*/
switch (hdr->msgtype) {
case CHANNELMSG_RESCIND_CHANNELOFFER:
/*
* If we are handling the rescind message;
* schedule the work on the global work queue.
*/
schedule_work_on(vmbus_connection.connect_cpu,
&ctx->work);
break;
case CHANNELMSG_OFFERCHANNEL:
atomic_inc(&vmbus_connection.offer_in_progress);
queue_work_on(vmbus_connection.connect_cpu,
vmbus_connection.work_queue,
&ctx->work);
break;
default:
queue_work(vmbus_connection.work_queue, &ctx->work);
}
} else
entry->message_handler(hdr);

View File

@ -89,4 +89,18 @@ config CORESIGHT_STM
logging useful software events or data coming from various entities
in the system, possibly running different OSs
config CORESIGHT_CPU_DEBUG
tristate "CoreSight CPU Debug driver"
depends on ARM || ARM64
depends on DEBUG_FS
help
This driver provides support for coresight debugging module. This
is primarily used to dump sample-based profiling registers when
system triggers panic, the driver will parse context registers so
can quickly get to know program counter (PC), secure state,
exception level, etc. Before use debugging functionality, platform
needs to ensure the clock domain and power domain are enabled
properly, please refer Documentation/trace/coresight-cpu-debug.txt
for detailed description and the example for usage.
endif

View File

@ -16,3 +16,4 @@ obj-$(CONFIG_CORESIGHT_SOURCE_ETM4X) += coresight-etm4x.o \
coresight-etm4x-sysfs.o
obj-$(CONFIG_CORESIGHT_QCOM_REPLICATOR) += coresight-replicator-qcom.o
obj-$(CONFIG_CORESIGHT_STM) += coresight-stm.o
obj-$(CONFIG_CORESIGHT_CPU_DEBUG) += coresight-cpu-debug.o

View File

@ -0,0 +1,700 @@
/*
* Copyright (c) 2017 Linaro Limited. All rights reserved.
*
* Author: Leo Yan <leo.yan@linaro.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/amba/bus.h>
#include <linux/coresight.h>
#include <linux/cpu.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/pm_qos.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include "coresight-priv.h"
#define EDPCSR 0x0A0
#define EDCIDSR 0x0A4
#define EDVIDSR 0x0A8
#define EDPCSR_HI 0x0AC
#define EDOSLAR 0x300
#define EDPRCR 0x310
#define EDPRSR 0x314
#define EDDEVID1 0xFC4
#define EDDEVID 0xFC8
#define EDPCSR_PROHIBITED 0xFFFFFFFF
/* bits definition for EDPCSR */
#define EDPCSR_THUMB BIT(0)
#define EDPCSR_ARM_INST_MASK GENMASK(31, 2)
#define EDPCSR_THUMB_INST_MASK GENMASK(31, 1)
/* bits definition for EDPRCR */
#define EDPRCR_COREPURQ BIT(3)
#define EDPRCR_CORENPDRQ BIT(0)
/* bits definition for EDPRSR */
#define EDPRSR_DLK BIT(6)
#define EDPRSR_PU BIT(0)
/* bits definition for EDVIDSR */
#define EDVIDSR_NS BIT(31)
#define EDVIDSR_E2 BIT(30)
#define EDVIDSR_E3 BIT(29)
#define EDVIDSR_HV BIT(28)
#define EDVIDSR_VMID GENMASK(7, 0)
/*
* bits definition for EDDEVID1:PSCROffset
*
* NOTE: armv8 and armv7 have different definition for the register,
* so consolidate the bits definition as below:
*
* 0b0000 - Sample offset applies based on the instruction state, we
* rely on EDDEVID to check if EDPCSR is implemented or not
* 0b0001 - No offset applies.
* 0b0010 - No offset applies, but do not use in AArch32 mode
*
*/
#define EDDEVID1_PCSR_OFFSET_MASK GENMASK(3, 0)
#define EDDEVID1_PCSR_OFFSET_INS_SET (0x0)
#define EDDEVID1_PCSR_NO_OFFSET_DIS_AARCH32 (0x2)
/* bits definition for EDDEVID */
#define EDDEVID_PCSAMPLE_MODE GENMASK(3, 0)
#define EDDEVID_IMPL_EDPCSR (0x1)
#define EDDEVID_IMPL_EDPCSR_EDCIDSR (0x2)
#define EDDEVID_IMPL_FULL (0x3)
#define DEBUG_WAIT_SLEEP 1000
#define DEBUG_WAIT_TIMEOUT 32000
struct debug_drvdata {
void __iomem *base;
struct device *dev;
int cpu;
bool edpcsr_present;
bool edcidsr_present;
bool edvidsr_present;
bool pc_has_offset;
u32 edpcsr;
u32 edpcsr_hi;
u32 edprsr;
u32 edvidsr;
u32 edcidsr;
};
static DEFINE_MUTEX(debug_lock);
static DEFINE_PER_CPU(struct debug_drvdata *, debug_drvdata);
static int debug_count;
static struct dentry *debug_debugfs_dir;
static bool debug_enable;
module_param_named(enable, debug_enable, bool, 0600);
MODULE_PARM_DESC(enable, "Control to enable coresight CPU debug functionality");
static void debug_os_unlock(struct debug_drvdata *drvdata)
{
/* Unlocks the debug registers */
writel_relaxed(0x0, drvdata->base + EDOSLAR);
/* Make sure the registers are unlocked before accessing */
wmb();
}
/*
* According to ARM DDI 0487A.k, before access external debug
* registers should firstly check the access permission; if any
* below condition has been met then cannot access debug
* registers to avoid lockup issue:
*
* - CPU power domain is powered off;
* - The OS Double Lock is locked;
*
* By checking EDPRSR can get to know if meet these conditions.
*/
static bool debug_access_permitted(struct debug_drvdata *drvdata)
{
/* CPU is powered off */
if (!(drvdata->edprsr & EDPRSR_PU))
return false;
/* The OS Double Lock is locked */
if (drvdata->edprsr & EDPRSR_DLK)
return false;
return true;
}
static void debug_force_cpu_powered_up(struct debug_drvdata *drvdata)
{
u32 edprcr;
try_again:
/*
* Send request to power management controller and assert
* DBGPWRUPREQ signal; if power management controller has
* sane implementation, it should enable CPU power domain
* in case CPU is in low power state.
*/
edprcr = readl_relaxed(drvdata->base + EDPRCR);
edprcr |= EDPRCR_COREPURQ;
writel_relaxed(edprcr, drvdata->base + EDPRCR);
/* Wait for CPU to be powered up (timeout~=32ms) */
if (readx_poll_timeout_atomic(readl_relaxed, drvdata->base + EDPRSR,
drvdata->edprsr, (drvdata->edprsr & EDPRSR_PU),
DEBUG_WAIT_SLEEP, DEBUG_WAIT_TIMEOUT)) {
/*
* Unfortunately the CPU cannot be powered up, so return
* back and later has no permission to access other
* registers. For this case, should disable CPU low power
* states to ensure CPU power domain is enabled!
*/
dev_err(drvdata->dev, "%s: power up request for CPU%d failed\n",
__func__, drvdata->cpu);
return;
}
/*
* At this point the CPU is powered up, so set the no powerdown
* request bit so we don't lose power and emulate power down.
*/
edprcr = readl_relaxed(drvdata->base + EDPRCR);
edprcr |= EDPRCR_COREPURQ | EDPRCR_CORENPDRQ;
writel_relaxed(edprcr, drvdata->base + EDPRCR);
drvdata->edprsr = readl_relaxed(drvdata->base + EDPRSR);
/* The core power domain got switched off on use, try again */
if (unlikely(!(drvdata->edprsr & EDPRSR_PU)))
goto try_again;
}
static void debug_read_regs(struct debug_drvdata *drvdata)
{
u32 save_edprcr;
CS_UNLOCK(drvdata->base);
/* Unlock os lock */
debug_os_unlock(drvdata);
/* Save EDPRCR register */
save_edprcr = readl_relaxed(drvdata->base + EDPRCR);
/*
* Ensure CPU power domain is enabled to let registers
* are accessiable.
*/
debug_force_cpu_powered_up(drvdata);
if (!debug_access_permitted(drvdata))
goto out;
drvdata->edpcsr = readl_relaxed(drvdata->base + EDPCSR);
/*
* As described in ARM DDI 0487A.k, if the processing
* element (PE) is in debug state, or sample-based
* profiling is prohibited, EDPCSR reads as 0xFFFFFFFF;
* EDCIDSR, EDVIDSR and EDPCSR_HI registers also become
* UNKNOWN state. So directly bail out for this case.
*/
if (drvdata->edpcsr == EDPCSR_PROHIBITED)
goto out;
/*
* A read of the EDPCSR normally has the side-effect of
* indirectly writing to EDCIDSR, EDVIDSR and EDPCSR_HI;
* at this point it's safe to read value from them.
*/
if (IS_ENABLED(CONFIG_64BIT))
drvdata->edpcsr_hi = readl_relaxed(drvdata->base + EDPCSR_HI);
if (drvdata->edcidsr_present)
drvdata->edcidsr = readl_relaxed(drvdata->base + EDCIDSR);
if (drvdata->edvidsr_present)
drvdata->edvidsr = readl_relaxed(drvdata->base + EDVIDSR);
out:
/* Restore EDPRCR register */
writel_relaxed(save_edprcr, drvdata->base + EDPRCR);
CS_LOCK(drvdata->base);
}
#ifdef CONFIG_64BIT
static unsigned long debug_adjust_pc(struct debug_drvdata *drvdata)
{
return (unsigned long)drvdata->edpcsr_hi << 32 |
(unsigned long)drvdata->edpcsr;
}
#else
static unsigned long debug_adjust_pc(struct debug_drvdata *drvdata)
{
unsigned long arm_inst_offset = 0, thumb_inst_offset = 0;
unsigned long pc;
pc = (unsigned long)drvdata->edpcsr;
if (drvdata->pc_has_offset) {
arm_inst_offset = 8;
thumb_inst_offset = 4;
}
/* Handle thumb instruction */
if (pc & EDPCSR_THUMB) {
pc = (pc & EDPCSR_THUMB_INST_MASK) - thumb_inst_offset;
return pc;
}
/*
* Handle arm instruction offset, if the arm instruction
* is not 4 byte alignment then it's possible the case
* for implementation defined; keep original value for this
* case and print info for notice.
*/
if (pc & BIT(1))
dev_emerg(drvdata->dev,
"Instruction offset is implementation defined\n");
else
pc = (pc & EDPCSR_ARM_INST_MASK) - arm_inst_offset;
return pc;
}
#endif
static void debug_dump_regs(struct debug_drvdata *drvdata)
{
struct device *dev = drvdata->dev;
unsigned long pc;
dev_emerg(dev, " EDPRSR: %08x (Power:%s DLK:%s)\n",
drvdata->edprsr,
drvdata->edprsr & EDPRSR_PU ? "On" : "Off",
drvdata->edprsr & EDPRSR_DLK ? "Lock" : "Unlock");
if (!debug_access_permitted(drvdata)) {
dev_emerg(dev, "No permission to access debug registers!\n");
return;
}
if (drvdata->edpcsr == EDPCSR_PROHIBITED) {
dev_emerg(dev, "CPU is in Debug state or profiling is prohibited!\n");
return;
}
pc = debug_adjust_pc(drvdata);
dev_emerg(dev, " EDPCSR: [<%p>] %pS\n", (void *)pc, (void *)pc);
if (drvdata->edcidsr_present)
dev_emerg(dev, " EDCIDSR: %08x\n", drvdata->edcidsr);
if (drvdata->edvidsr_present)
dev_emerg(dev, " EDVIDSR: %08x (State:%s Mode:%s Width:%dbits VMID:%x)\n",
drvdata->edvidsr,
drvdata->edvidsr & EDVIDSR_NS ?
"Non-secure" : "Secure",
drvdata->edvidsr & EDVIDSR_E3 ? "EL3" :
(drvdata->edvidsr & EDVIDSR_E2 ?
"EL2" : "EL1/0"),
drvdata->edvidsr & EDVIDSR_HV ? 64 : 32,
drvdata->edvidsr & (u32)EDVIDSR_VMID);
}
static void debug_init_arch_data(void *info)
{
struct debug_drvdata *drvdata = info;
u32 mode, pcsr_offset;
u32 eddevid, eddevid1;
CS_UNLOCK(drvdata->base);
/* Read device info */
eddevid = readl_relaxed(drvdata->base + EDDEVID);
eddevid1 = readl_relaxed(drvdata->base + EDDEVID1);
CS_LOCK(drvdata->base);
/* Parse implementation feature */
mode = eddevid & EDDEVID_PCSAMPLE_MODE;
pcsr_offset = eddevid1 & EDDEVID1_PCSR_OFFSET_MASK;
drvdata->edpcsr_present = false;
drvdata->edcidsr_present = false;
drvdata->edvidsr_present = false;
drvdata->pc_has_offset = false;
switch (mode) {
case EDDEVID_IMPL_FULL:
drvdata->edvidsr_present = true;
/* Fall through */
case EDDEVID_IMPL_EDPCSR_EDCIDSR:
drvdata->edcidsr_present = true;
/* Fall through */
case EDDEVID_IMPL_EDPCSR:
/*
* In ARM DDI 0487A.k, the EDDEVID1.PCSROffset is used to
* define if has the offset for PC sampling value; if read
* back EDDEVID1.PCSROffset == 0x2, then this means the debug
* module does not sample the instruction set state when
* armv8 CPU in AArch32 state.
*/
drvdata->edpcsr_present =
((IS_ENABLED(CONFIG_64BIT) && pcsr_offset != 0) ||
(pcsr_offset != EDDEVID1_PCSR_NO_OFFSET_DIS_AARCH32));
drvdata->pc_has_offset =
(pcsr_offset == EDDEVID1_PCSR_OFFSET_INS_SET);
break;
default:
break;
}
}
/*
* Dump out information on panic.
*/
static int debug_notifier_call(struct notifier_block *self,
unsigned long v, void *p)
{
int cpu;
struct debug_drvdata *drvdata;
mutex_lock(&debug_lock);
/* Bail out if the functionality is disabled */
if (!debug_enable)
goto skip_dump;
pr_emerg("ARM external debug module:\n");
for_each_possible_cpu(cpu) {
drvdata = per_cpu(debug_drvdata, cpu);
if (!drvdata)
continue;
dev_emerg(drvdata->dev, "CPU[%d]:\n", drvdata->cpu);
debug_read_regs(drvdata);
debug_dump_regs(drvdata);
}
skip_dump:
mutex_unlock(&debug_lock);
return 0;
}
static struct notifier_block debug_notifier = {
.notifier_call = debug_notifier_call,
};
static int debug_enable_func(void)
{
struct debug_drvdata *drvdata;
int cpu, ret = 0;
cpumask_t mask;
/*
* Use cpumask to track which debug power domains have
* been powered on and use it to handle failure case.
*/
cpumask_clear(&mask);
for_each_possible_cpu(cpu) {
drvdata = per_cpu(debug_drvdata, cpu);
if (!drvdata)
continue;
ret = pm_runtime_get_sync(drvdata->dev);
if (ret < 0)
goto err;
else
cpumask_set_cpu(cpu, &mask);
}
return 0;
err:
/*
* If pm_runtime_get_sync() has failed, need rollback on
* all the other CPUs that have been enabled before that.
*/
for_each_cpu(cpu, &mask) {
drvdata = per_cpu(debug_drvdata, cpu);
pm_runtime_put_noidle(drvdata->dev);
}
return ret;
}
static int debug_disable_func(void)
{
struct debug_drvdata *drvdata;
int cpu, ret, err = 0;
/*
* Disable debug power domains, records the error and keep
* circling through all other CPUs when an error has been
* encountered.
*/
for_each_possible_cpu(cpu) {
drvdata = per_cpu(debug_drvdata, cpu);
if (!drvdata)
continue;
ret = pm_runtime_put(drvdata->dev);
if (ret < 0)
err = ret;
}
return err;
}
static ssize_t debug_func_knob_write(struct file *f,
const char __user *buf, size_t count, loff_t *ppos)
{
u8 val;
int ret;
ret = kstrtou8_from_user(buf, count, 2, &val);
if (ret)
return ret;
mutex_lock(&debug_lock);
if (val == debug_enable)
goto out;
if (val)
ret = debug_enable_func();
else
ret = debug_disable_func();
if (ret) {
pr_err("%s: unable to %s debug function: %d\n",
__func__, val ? "enable" : "disable", ret);
goto err;
}
debug_enable = val;
out:
ret = count;
err:
mutex_unlock(&debug_lock);
return ret;
}
static ssize_t debug_func_knob_read(struct file *f,
char __user *ubuf, size_t count, loff_t *ppos)
{
ssize_t ret;
char buf[3];
mutex_lock(&debug_lock);
snprintf(buf, sizeof(buf), "%d\n", debug_enable);
mutex_unlock(&debug_lock);
ret = simple_read_from_buffer(ubuf, count, ppos, buf, sizeof(buf));
return ret;
}
static const struct file_operations debug_func_knob_fops = {
.open = simple_open,
.read = debug_func_knob_read,
.write = debug_func_knob_write,
};
static int debug_func_init(void)
{
struct dentry *file;
int ret;
/* Create debugfs node */
debug_debugfs_dir = debugfs_create_dir("coresight_cpu_debug", NULL);
if (!debug_debugfs_dir) {
pr_err("%s: unable to create debugfs directory\n", __func__);
return -ENOMEM;
}
file = debugfs_create_file("enable", 0644, debug_debugfs_dir, NULL,
&debug_func_knob_fops);
if (!file) {
pr_err("%s: unable to create enable knob file\n", __func__);
ret = -ENOMEM;
goto err;
}
/* Register function to be called for panic */
ret = atomic_notifier_chain_register(&panic_notifier_list,
&debug_notifier);
if (ret) {
pr_err("%s: unable to register notifier: %d\n",
__func__, ret);
goto err;
}
return 0;
err:
debugfs_remove_recursive(debug_debugfs_dir);
return ret;
}
static void debug_func_exit(void)
{
atomic_notifier_chain_unregister(&panic_notifier_list,
&debug_notifier);
debugfs_remove_recursive(debug_debugfs_dir);
}
static int debug_probe(struct amba_device *adev, const struct amba_id *id)
{
void __iomem *base;
struct device *dev = &adev->dev;
struct debug_drvdata *drvdata;
struct resource *res = &adev->res;
struct device_node *np = adev->dev.of_node;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
drvdata->cpu = np ? of_coresight_get_cpu(np) : 0;
if (per_cpu(debug_drvdata, drvdata->cpu)) {
dev_err(dev, "CPU%d drvdata has already been initialized\n",
drvdata->cpu);
return -EBUSY;
}
drvdata->dev = &adev->dev;
amba_set_drvdata(adev, drvdata);
/* Validity for the resource is already checked by the AMBA core */
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
drvdata->base = base;
get_online_cpus();
per_cpu(debug_drvdata, drvdata->cpu) = drvdata;
ret = smp_call_function_single(drvdata->cpu, debug_init_arch_data,
drvdata, 1);
put_online_cpus();
if (ret) {
dev_err(dev, "CPU%d debug arch init failed\n", drvdata->cpu);
goto err;
}
if (!drvdata->edpcsr_present) {
dev_err(dev, "CPU%d sample-based profiling isn't implemented\n",
drvdata->cpu);
ret = -ENXIO;
goto err;
}
if (!debug_count++) {
ret = debug_func_init();
if (ret)
goto err_func_init;
}
mutex_lock(&debug_lock);
/* Turn off debug power domain if debugging is disabled */
if (!debug_enable)
pm_runtime_put(dev);
mutex_unlock(&debug_lock);
dev_info(dev, "Coresight debug-CPU%d initialized\n", drvdata->cpu);
return 0;
err_func_init:
debug_count--;
err:
per_cpu(debug_drvdata, drvdata->cpu) = NULL;
return ret;
}
static int debug_remove(struct amba_device *adev)
{
struct device *dev = &adev->dev;
struct debug_drvdata *drvdata = amba_get_drvdata(adev);
per_cpu(debug_drvdata, drvdata->cpu) = NULL;
mutex_lock(&debug_lock);
/* Turn off debug power domain before rmmod the module */
if (debug_enable)
pm_runtime_put(dev);
mutex_unlock(&debug_lock);
if (!--debug_count)
debug_func_exit();
return 0;
}
static struct amba_id debug_ids[] = {
{ /* Debug for Cortex-A53 */
.id = 0x000bbd03,
.mask = 0x000fffff,
},
{ /* Debug for Cortex-A57 */
.id = 0x000bbd07,
.mask = 0x000fffff,
},
{ /* Debug for Cortex-A72 */
.id = 0x000bbd08,
.mask = 0x000fffff,
},
{ 0, 0 },
};
static struct amba_driver debug_driver = {
.drv = {
.name = "coresight-cpu-debug",
.suppress_bind_attrs = true,
},
.probe = debug_probe,
.remove = debug_remove,
.id_table = debug_ids,
};
module_amba_driver(debug_driver);
MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>");
MODULE_DESCRIPTION("ARM Coresight CPU Debug Driver");
MODULE_LICENSE("GPL");

View File

@ -375,7 +375,7 @@ static void etb_update_buffer(struct coresight_device *csdev,
/*
* Entries should be aligned to the frame size. If they are not
* go back to the last alignement point to give decoding tools a
* go back to the last alignment point to give decoding tools a
* chance to fix things.
*/
if (write_ptr % ETB_FRAME_SIZE_WORDS) {
@ -675,11 +675,8 @@ static int etb_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->buf = devm_kzalloc(dev,
drvdata->buffer_depth * 4, GFP_KERNEL);
if (!drvdata->buf) {
dev_err(dev, "Failed to allocate %u bytes for buffer data\n",
drvdata->buffer_depth * 4);
if (!drvdata->buf)
return -ENOMEM;
}
desc.type = CORESIGHT_DEV_TYPE_SINK;
desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;

View File

@ -201,6 +201,7 @@ static void *etm_setup_aux(int event_cpu, void **pages,
event_data = alloc_event_data(event_cpu);
if (!event_data)
return NULL;
INIT_WORK(&event_data->work, free_event_data);
/*
* In theory nothing prevent tracers in a trace session from being
@ -217,8 +218,6 @@ static void *etm_setup_aux(int event_cpu, void **pages,
if (!sink)
goto err;
INIT_WORK(&event_data->work, free_event_data);
mask = &event_data->mask;
/* Setup the path for each CPU in a trace session */

View File

@ -166,9 +166,6 @@ out:
if (!used)
kfree(buf);
if (!ret)
dev_info(drvdata->dev, "TMC-ETB/ETF enabled\n");
return ret;
}
@ -204,15 +201,27 @@ out:
static int tmc_enable_etf_sink(struct coresight_device *csdev, u32 mode)
{
int ret;
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
switch (mode) {
case CS_MODE_SYSFS:
return tmc_enable_etf_sink_sysfs(csdev);
ret = tmc_enable_etf_sink_sysfs(csdev);
break;
case CS_MODE_PERF:
return tmc_enable_etf_sink_perf(csdev);
ret = tmc_enable_etf_sink_perf(csdev);
break;
/* We shouldn't be here */
default:
ret = -EINVAL;
break;
}
/* We shouldn't be here */
return -EINVAL;
if (ret)
return ret;
dev_info(drvdata->dev, "TMC-ETB/ETF enabled\n");
return 0;
}
static void tmc_disable_etf_sink(struct coresight_device *csdev)
@ -273,7 +282,7 @@ static void tmc_disable_etf_link(struct coresight_device *csdev,
drvdata->mode = CS_MODE_DISABLED;
spin_unlock_irqrestore(&drvdata->spinlock, flags);
dev_info(drvdata->dev, "TMC disabled\n");
dev_info(drvdata->dev, "TMC-ETF disabled\n");
}
static void *tmc_alloc_etf_buffer(struct coresight_device *csdev, int cpu,

View File

@ -362,6 +362,13 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
desc.type = CORESIGHT_DEV_TYPE_SINK;
desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;
desc.ops = &tmc_etr_cs_ops;
/*
* ETR configuration uses a 40-bit AXI master in place of
* the embedded SRAM of ETB/ETF.
*/
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40));
if (ret)
goto out;
} else {
desc.type = CORESIGHT_DEV_TYPE_LINKSINK;
desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_FIFO;

View File

@ -253,14 +253,22 @@ static int coresight_enable_source(struct coresight_device *csdev, u32 mode)
return 0;
}
static void coresight_disable_source(struct coresight_device *csdev)
/**
* coresight_disable_source - Drop the reference count by 1 and disable
* the device if there are no users left.
*
* @csdev - The coresight device to disable
*
* Returns true if the device has been disabled.
*/
static bool coresight_disable_source(struct coresight_device *csdev)
{
if (atomic_dec_return(csdev->refcnt) == 0) {
if (source_ops(csdev)->disable) {
if (source_ops(csdev)->disable)
source_ops(csdev)->disable(csdev, NULL);
csdev->enable = false;
}
csdev->enable = false;
}
return !csdev->enable;
}
void coresight_disable_path(struct list_head *path)
@ -550,6 +558,9 @@ int coresight_enable(struct coresight_device *csdev)
int cpu, ret = 0;
struct coresight_device *sink;
struct list_head *path;
enum coresight_dev_subtype_source subtype;
subtype = csdev->subtype.source_subtype;
mutex_lock(&coresight_mutex);
@ -557,8 +568,16 @@ int coresight_enable(struct coresight_device *csdev)
if (ret)
goto out;
if (csdev->enable)
if (csdev->enable) {
/*
* There could be multiple applications driving the software
* source. So keep the refcount for each such user when the
* source is already enabled.
*/
if (subtype == CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE)
atomic_inc(csdev->refcnt);
goto out;
}
/*
* Search for a valid sink for this session but don't reset the
@ -585,7 +604,7 @@ int coresight_enable(struct coresight_device *csdev)
if (ret)
goto err_source;
switch (csdev->subtype.source_subtype) {
switch (subtype) {
case CORESIGHT_DEV_SUBTYPE_SOURCE_PROC:
/*
* When working from sysFS it is important to keep track
@ -629,7 +648,7 @@ void coresight_disable(struct coresight_device *csdev)
if (ret)
goto out;
if (!csdev->enable)
if (!csdev->enable || !coresight_disable_source(csdev))
goto out;
switch (csdev->subtype.source_subtype) {
@ -647,7 +666,6 @@ void coresight_disable(struct coresight_device *csdev)
break;
}
coresight_disable_source(csdev);
coresight_disable_path(path);
coresight_release_path(path);

View File

@ -52,7 +52,7 @@ of_coresight_get_endpoint_device(struct device_node *endpoint)
endpoint, of_dev_node_match);
}
static void of_coresight_get_ports(struct device_node *node,
static void of_coresight_get_ports(const struct device_node *node,
int *nr_inport, int *nr_outport)
{
struct device_node *ep = NULL;
@ -101,14 +101,40 @@ static int of_coresight_alloc_memory(struct device *dev,
return 0;
}
struct coresight_platform_data *of_get_coresight_platform_data(
struct device *dev, struct device_node *node)
int of_coresight_get_cpu(const struct device_node *node)
{
int i = 0, ret = 0, cpu;
int cpu;
bool found;
struct device_node *dn, *np;
dn = of_parse_phandle(node, "cpu", 0);
/* Affinity defaults to CPU0 */
if (!dn)
return 0;
for_each_possible_cpu(cpu) {
np = of_cpu_device_node_get(cpu);
found = (dn == np);
of_node_put(np);
if (found)
break;
}
of_node_put(dn);
/* Affinity to CPU0 if no cpu nodes are found */
return found ? cpu : 0;
}
EXPORT_SYMBOL_GPL(of_coresight_get_cpu);
struct coresight_platform_data *
of_get_coresight_platform_data(struct device *dev,
const struct device_node *node)
{
int i = 0, ret = 0;
struct coresight_platform_data *pdata;
struct of_endpoint endpoint, rendpoint;
struct device *rdev;
struct device_node *dn;
struct device_node *ep = NULL;
struct device_node *rparent = NULL;
struct device_node *rport = NULL;
@ -175,16 +201,7 @@ struct coresight_platform_data *of_get_coresight_platform_data(
} while (ep);
}
/* Affinity defaults to CPU0 */
pdata->cpu = 0;
dn = of_parse_phandle(node, "cpu", 0);
for (cpu = 0; dn && cpu < nr_cpu_ids; cpu++) {
if (dn == of_get_cpu_node(cpu, NULL)) {
pdata->cpu = cpu;
break;
}
}
of_node_put(dn);
pdata->cpu = of_coresight_get_cpu(node);
return pdata;
}

View File

@ -30,6 +30,19 @@ config I2C_MUX_GPIO
This driver can also be built as a module. If so, the module
will be called i2c-mux-gpio.
config I2C_MUX_GPMUX
tristate "General Purpose I2C multiplexer"
select MULTIPLEXER
depends on OF || COMPILE_TEST
help
If you say yes to this option, support will be included for a
general purpose I2C multiplexer. This driver provides access to
I2C busses connected through a MUX, which in turn is controlled
by a MUX-controller from the MUX subsystem.
This driver can also be built as a module. If so, the module
will be called i2c-mux-gpmux.
config I2C_MUX_LTC4306
tristate "LTC LTC4306/5 I2C multiplexer"
select GPIOLIB

View File

@ -6,6 +6,7 @@ obj-$(CONFIG_I2C_ARB_GPIO_CHALLENGE) += i2c-arb-gpio-challenge.o
obj-$(CONFIG_I2C_DEMUX_PINCTRL) += i2c-demux-pinctrl.o
obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o
obj-$(CONFIG_I2C_MUX_GPMUX) += i2c-mux-gpmux.o
obj-$(CONFIG_I2C_MUX_LTC4306) += i2c-mux-ltc4306.o
obj-$(CONFIG_I2C_MUX_MLXCPLD) += i2c-mux-mlxcpld.o
obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o

View File

@ -0,0 +1,173 @@
/*
* General Purpose I2C multiplexer
*
* Copyright (C) 2017 Axentia Technologies AB
*
* Author: Peter Rosin <peda@axentia.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/i2c.h>
#include <linux/i2c-mux.h>
#include <linux/module.h>
#include <linux/mux/consumer.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
struct mux {
struct mux_control *control;
bool do_not_deselect;
};
static int i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
{
struct mux *mux = i2c_mux_priv(muxc);
int ret;
ret = mux_control_select(mux->control, chan);
mux->do_not_deselect = ret < 0;
return ret;
}
static int i2c_mux_deselect(struct i2c_mux_core *muxc, u32 chan)
{
struct mux *mux = i2c_mux_priv(muxc);
if (mux->do_not_deselect)
return 0;
return mux_control_deselect(mux->control);
}
static struct i2c_adapter *mux_parent_adapter(struct device *dev)
{
struct device_node *np = dev->of_node;
struct device_node *parent_np;
struct i2c_adapter *parent;
parent_np = of_parse_phandle(np, "i2c-parent", 0);
if (!parent_np) {
dev_err(dev, "Cannot parse i2c-parent\n");
return ERR_PTR(-ENODEV);
}
parent = of_find_i2c_adapter_by_node(parent_np);
of_node_put(parent_np);
if (!parent)
return ERR_PTR(-EPROBE_DEFER);
return parent;
}
static const struct of_device_id i2c_mux_of_match[] = {
{ .compatible = "i2c-mux", },
{},
};
MODULE_DEVICE_TABLE(of, i2c_mux_of_match);
static int i2c_mux_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *child;
struct i2c_mux_core *muxc;
struct mux *mux;
struct i2c_adapter *parent;
int children;
int ret;
if (!np)
return -ENODEV;
mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
if (!mux)
return -ENOMEM;
mux->control = devm_mux_control_get(dev, NULL);
if (IS_ERR(mux->control)) {
if (PTR_ERR(mux->control) != -EPROBE_DEFER)
dev_err(dev, "failed to get control-mux\n");
return PTR_ERR(mux->control);
}
parent = mux_parent_adapter(dev);
if (IS_ERR(parent)) {
if (PTR_ERR(parent) != -EPROBE_DEFER)
dev_err(dev, "failed to get i2c-parent adapter\n");
return PTR_ERR(parent);
}
children = of_get_child_count(np);
muxc = i2c_mux_alloc(parent, dev, children, 0, 0,
i2c_mux_select, i2c_mux_deselect);
if (!muxc) {
ret = -ENOMEM;
goto err_parent;
}
muxc->priv = mux;
platform_set_drvdata(pdev, muxc);
muxc->mux_locked = of_property_read_bool(np, "mux-locked");
for_each_child_of_node(np, child) {
u32 chan;
ret = of_property_read_u32(child, "reg", &chan);
if (ret < 0) {
dev_err(dev, "no reg property for node '%s'\n",
child->name);
goto err_children;
}
if (chan >= mux_control_states(mux->control)) {
dev_err(dev, "invalid reg %u\n", chan);
ret = -EINVAL;
goto err_children;
}
ret = i2c_mux_add_adapter(muxc, 0, chan, 0);
if (ret)
goto err_children;
}
dev_info(dev, "%d-port mux on %s adapter\n", children, parent->name);
return 0;
err_children:
i2c_mux_del_adapters(muxc);
err_parent:
i2c_put_adapter(parent);
return ret;
}
static int i2c_mux_remove(struct platform_device *pdev)
{
struct i2c_mux_core *muxc = platform_get_drvdata(pdev);
i2c_mux_del_adapters(muxc);
i2c_put_adapter(muxc->parent);
return 0;
}
static struct platform_driver i2c_mux_driver = {
.probe = i2c_mux_probe,
.remove = i2c_mux_remove,
.driver = {
.name = "i2c-mux-gpmux",
.of_match_table = i2c_mux_of_match,
},
};
module_platform_driver(i2c_mux_driver);
MODULE_DESCRIPTION("General Purpose I2C multiplexer driver");
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
MODULE_LICENSE("GPL v2");

View File

@ -83,6 +83,7 @@ source "drivers/iio/humidity/Kconfig"
source "drivers/iio/imu/Kconfig"
source "drivers/iio/light/Kconfig"
source "drivers/iio/magnetometer/Kconfig"
source "drivers/iio/multiplexer/Kconfig"
source "drivers/iio/orientation/Kconfig"
if IIO_TRIGGER
source "drivers/iio/trigger/Kconfig"

View File

@ -28,6 +28,7 @@ obj-y += humidity/
obj-y += imu/
obj-y += light/
obj-y += magnetometer/
obj-y += multiplexer/
obj-y += orientation/
obj-y += potentiometer/
obj-y += potentiostat/

View File

@ -867,3 +867,63 @@ err_unlock:
return ret;
}
EXPORT_SYMBOL_GPL(iio_write_channel_raw);
unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan)
{
const struct iio_chan_spec_ext_info *ext_info;
unsigned int i = 0;
if (!chan->channel->ext_info)
return i;
for (ext_info = chan->channel->ext_info; ext_info->name; ext_info++)
++i;
return i;
}
EXPORT_SYMBOL_GPL(iio_get_channel_ext_info_count);
static const struct iio_chan_spec_ext_info *iio_lookup_ext_info(
const struct iio_channel *chan,
const char *attr)
{
const struct iio_chan_spec_ext_info *ext_info;
if (!chan->channel->ext_info)
return NULL;
for (ext_info = chan->channel->ext_info; ext_info->name; ++ext_info) {
if (!strcmp(attr, ext_info->name))
return ext_info;
}
return NULL;
}
ssize_t iio_read_channel_ext_info(struct iio_channel *chan,
const char *attr, char *buf)
{
const struct iio_chan_spec_ext_info *ext_info;
ext_info = iio_lookup_ext_info(chan, attr);
if (!ext_info)
return -EINVAL;
return ext_info->read(chan->indio_dev, ext_info->private,
chan->channel, buf);
}
EXPORT_SYMBOL_GPL(iio_read_channel_ext_info);
ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
const char *buf, size_t len)
{
const struct iio_chan_spec_ext_info *ext_info;
ext_info = iio_lookup_ext_info(chan, attr);
if (!ext_info)
return -EINVAL;
return ext_info->write(chan->indio_dev, ext_info->private,
chan->channel, buf, len);
}
EXPORT_SYMBOL_GPL(iio_write_channel_ext_info);

View File

@ -0,0 +1,18 @@
#
# Multiplexer drivers
#
# When adding new entries keep the list in alphabetical order
menu "Multiplexers"
config IIO_MUX
tristate "IIO multiplexer driver"
select MULTIPLEXER
depends on OF || COMPILE_TEST
help
Say yes here to build support for the IIO multiplexer.
To compile this driver as a module, choose M here: the
module will be called iio-mux.
endmenu

View File

@ -0,0 +1,6 @@
#
# Makefile for industrial I/O multiplexer drivers
#
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_IIO_MUX) += iio-mux.o

View File

@ -0,0 +1,459 @@
/*
* IIO multiplexer driver
*
* Copyright (C) 2017 Axentia Technologies AB
*
* Author: Peter Rosin <peda@axentia.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/err.h>
#include <linux/iio/consumer.h>
#include <linux/iio/iio.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/mux/consumer.h>
#include <linux/of.h>
#include <linux/platform_device.h>
struct mux_ext_info_cache {
char *data;
ssize_t size;
};
struct mux_child {
struct mux_ext_info_cache *ext_info_cache;
};
struct mux {
int cached_state;
struct mux_control *control;
struct iio_channel *parent;
struct iio_dev *indio_dev;
struct iio_chan_spec *chan;
struct iio_chan_spec_ext_info *ext_info;
struct mux_child *child;
};
static int iio_mux_select(struct mux *mux, int idx)
{
struct mux_child *child = &mux->child[idx];
struct iio_chan_spec const *chan = &mux->chan[idx];
int ret;
int i;
ret = mux_control_select(mux->control, chan->channel);
if (ret < 0) {
mux->cached_state = -1;
return ret;
}
if (mux->cached_state == chan->channel)
return 0;
if (chan->ext_info) {
for (i = 0; chan->ext_info[i].name; ++i) {
const char *attr = chan->ext_info[i].name;
struct mux_ext_info_cache *cache;
cache = &child->ext_info_cache[i];
if (cache->size < 0)
continue;
ret = iio_write_channel_ext_info(mux->parent, attr,
cache->data,
cache->size);
if (ret < 0) {
mux_control_deselect(mux->control);
mux->cached_state = -1;
return ret;
}
}
}
mux->cached_state = chan->channel;
return 0;
}
static void iio_mux_deselect(struct mux *mux)
{
mux_control_deselect(mux->control);
}
static int mux_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct mux *mux = iio_priv(indio_dev);
int idx = chan - mux->chan;
int ret;
ret = iio_mux_select(mux, idx);
if (ret < 0)
return ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = iio_read_channel_raw(mux->parent, val);
break;
case IIO_CHAN_INFO_SCALE:
ret = iio_read_channel_scale(mux->parent, val, val2);
break;
default:
ret = -EINVAL;
}
iio_mux_deselect(mux);
return ret;
}
static int mux_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type, int *length,
long mask)
{
struct mux *mux = iio_priv(indio_dev);
int idx = chan - mux->chan;
int ret;
ret = iio_mux_select(mux, idx);
if (ret < 0)
return ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
*type = IIO_VAL_INT;
ret = iio_read_avail_channel_raw(mux->parent, vals, length);
break;
default:
ret = -EINVAL;
}
iio_mux_deselect(mux);
return ret;
}
static int mux_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct mux *mux = iio_priv(indio_dev);
int idx = chan - mux->chan;
int ret;
ret = iio_mux_select(mux, idx);
if (ret < 0)
return ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = iio_write_channel_raw(mux->parent, val);
break;
default:
ret = -EINVAL;
}
iio_mux_deselect(mux);
return ret;
}
static const struct iio_info mux_info = {
.read_raw = mux_read_raw,
.read_avail = mux_read_avail,
.write_raw = mux_write_raw,
.driver_module = THIS_MODULE,
};
static ssize_t mux_read_ext_info(struct iio_dev *indio_dev, uintptr_t private,
struct iio_chan_spec const *chan, char *buf)
{
struct mux *mux = iio_priv(indio_dev);
int idx = chan - mux->chan;
ssize_t ret;
ret = iio_mux_select(mux, idx);
if (ret < 0)
return ret;
ret = iio_read_channel_ext_info(mux->parent,
mux->ext_info[private].name,
buf);
iio_mux_deselect(mux);
return ret;
}
static ssize_t mux_write_ext_info(struct iio_dev *indio_dev, uintptr_t private,
struct iio_chan_spec const *chan,
const char *buf, size_t len)
{
struct device *dev = indio_dev->dev.parent;
struct mux *mux = iio_priv(indio_dev);
int idx = chan - mux->chan;
char *new;
ssize_t ret;
if (len >= PAGE_SIZE)
return -EINVAL;
ret = iio_mux_select(mux, idx);
if (ret < 0)
return ret;
new = devm_kmemdup(dev, buf, len + 1, GFP_KERNEL);
if (!new) {
iio_mux_deselect(mux);
return -ENOMEM;
}
new[len] = 0;
ret = iio_write_channel_ext_info(mux->parent,
mux->ext_info[private].name,
buf, len);
if (ret < 0) {
iio_mux_deselect(mux);
devm_kfree(dev, new);
return ret;
}
devm_kfree(dev, mux->child[idx].ext_info_cache[private].data);
mux->child[idx].ext_info_cache[private].data = new;
mux->child[idx].ext_info_cache[private].size = len;
iio_mux_deselect(mux);
return ret;
}
static int mux_configure_channel(struct device *dev, struct mux *mux,
u32 state, const char *label, int idx)
{
struct mux_child *child = &mux->child[idx];
struct iio_chan_spec *chan = &mux->chan[idx];
struct iio_chan_spec const *pchan = mux->parent->channel;
char *page = NULL;
int num_ext_info;
int i;
int ret;
chan->indexed = 1;
chan->output = pchan->output;
chan->datasheet_name = label;
chan->ext_info = mux->ext_info;
ret = iio_get_channel_type(mux->parent, &chan->type);
if (ret < 0) {
dev_err(dev, "failed to get parent channel type\n");
return ret;
}
if (iio_channel_has_info(pchan, IIO_CHAN_INFO_RAW))
chan->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW);
if (iio_channel_has_info(pchan, IIO_CHAN_INFO_SCALE))
chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE);
if (iio_channel_has_available(pchan, IIO_CHAN_INFO_RAW))
chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW);
if (state >= mux_control_states(mux->control)) {
dev_err(dev, "too many channels\n");
return -EINVAL;
}
chan->channel = state;
num_ext_info = iio_get_channel_ext_info_count(mux->parent);
if (num_ext_info) {
page = devm_kzalloc(dev, PAGE_SIZE, GFP_KERNEL);
if (!page)
return -ENOMEM;
}
child->ext_info_cache = devm_kzalloc(dev,
sizeof(*child->ext_info_cache) *
num_ext_info, GFP_KERNEL);
for (i = 0; i < num_ext_info; ++i) {
child->ext_info_cache[i].size = -1;
if (!pchan->ext_info[i].write)
continue;
if (!pchan->ext_info[i].read)
continue;
ret = iio_read_channel_ext_info(mux->parent,
mux->ext_info[i].name,
page);
if (ret < 0) {
dev_err(dev, "failed to get ext_info '%s'\n",
pchan->ext_info[i].name);
return ret;
}
if (ret >= PAGE_SIZE) {
dev_err(dev, "too large ext_info '%s'\n",
pchan->ext_info[i].name);
return -EINVAL;
}
child->ext_info_cache[i].data = devm_kmemdup(dev, page, ret + 1,
GFP_KERNEL);
child->ext_info_cache[i].data[ret] = 0;
child->ext_info_cache[i].size = ret;
}
if (page)
devm_kfree(dev, page);
return 0;
}
/*
* Same as of_property_for_each_string(), but also keeps track of the
* index of each string.
*/
#define of_property_for_each_string_index(np, propname, prop, s, i) \
for (prop = of_find_property(np, propname, NULL), \
s = of_prop_next_string(prop, NULL), \
i = 0; \
s; \
s = of_prop_next_string(prop, s), \
i++)
static int mux_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct iio_dev *indio_dev;
struct iio_channel *parent;
struct mux *mux;
struct property *prop;
const char *label;
u32 state;
int sizeof_ext_info;
int children;
int sizeof_priv;
int i;
int ret;
if (!np)
return -ENODEV;
parent = devm_iio_channel_get(dev, "parent");
if (IS_ERR(parent)) {
if (PTR_ERR(parent) != -EPROBE_DEFER)
dev_err(dev, "failed to get parent channel\n");
return PTR_ERR(parent);
}
sizeof_ext_info = iio_get_channel_ext_info_count(parent);
if (sizeof_ext_info) {
sizeof_ext_info += 1; /* one extra entry for the sentinel */
sizeof_ext_info *= sizeof(*mux->ext_info);
}
children = 0;
of_property_for_each_string(np, "channels", prop, label) {
if (*label)
children++;
}
if (children <= 0) {
dev_err(dev, "not even a single child\n");
return -EINVAL;
}
sizeof_priv = sizeof(*mux);
sizeof_priv += sizeof(*mux->child) * children;
sizeof_priv += sizeof(*mux->chan) * children;
sizeof_priv += sizeof_ext_info;
indio_dev = devm_iio_device_alloc(dev, sizeof_priv);
if (!indio_dev)
return -ENOMEM;
mux = iio_priv(indio_dev);
mux->child = (struct mux_child *)(mux + 1);
mux->chan = (struct iio_chan_spec *)(mux->child + children);
platform_set_drvdata(pdev, indio_dev);
mux->parent = parent;
mux->cached_state = -1;
indio_dev->name = dev_name(dev);
indio_dev->dev.parent = dev;
indio_dev->info = &mux_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = mux->chan;
indio_dev->num_channels = children;
if (sizeof_ext_info) {
mux->ext_info = devm_kmemdup(dev,
parent->channel->ext_info,
sizeof_ext_info, GFP_KERNEL);
if (!mux->ext_info)
return -ENOMEM;
for (i = 0; mux->ext_info[i].name; ++i) {
if (parent->channel->ext_info[i].read)
mux->ext_info[i].read = mux_read_ext_info;
if (parent->channel->ext_info[i].write)
mux->ext_info[i].write = mux_write_ext_info;
mux->ext_info[i].private = i;
}
}
mux->control = devm_mux_control_get(dev, NULL);
if (IS_ERR(mux->control)) {
if (PTR_ERR(mux->control) != -EPROBE_DEFER)
dev_err(dev, "failed to get control-mux\n");
return PTR_ERR(mux->control);
}
i = 0;
of_property_for_each_string_index(np, "channels", prop, label, state) {
if (!*label)
continue;
ret = mux_configure_channel(dev, mux, state, label, i++);
if (ret < 0)
return ret;
}
ret = devm_iio_device_register(dev, indio_dev);
if (ret) {
dev_err(dev, "failed to register iio device\n");
return ret;
}
return 0;
}
static const struct of_device_id mux_match[] = {
{ .compatible = "io-channel-mux" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mux_match);
static struct platform_driver mux_driver = {
.probe = mux_probe,
.driver = {
.name = "iio-mux",
.of_match_table = mux_match,
},
};
module_platform_driver(mux_driver);
MODULE_DESCRIPTION("IIO multiplexer driver");
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
MODULE_LICENSE("GPL v2");

View File

@ -212,7 +212,7 @@ struct ipack_bus_device *ipack_bus_register(struct device *parent, int slots,
int bus_nr;
struct ipack_bus_device *bus;
bus = kzalloc(sizeof(struct ipack_bus_device), GFP_KERNEL);
bus = kzalloc(sizeof(*bus), GFP_KERNEL);
if (!bus)
return NULL;
@ -402,7 +402,6 @@ static int ipack_device_read_id(struct ipack_device *dev)
* ID ROM contents */
dev->id = kmalloc(dev->id_avail, GFP_KERNEL);
if (!dev->id) {
dev_err(&dev->dev, "dev->id alloc failed.\n");
ret = -ENOMEM;
goto out;
}

View File

@ -357,7 +357,10 @@ static int aemif_probe(struct platform_device *pdev)
return PTR_ERR(aemif->clk);
}
clk_prepare_enable(aemif->clk);
ret = clk_prepare_enable(aemif->clk);
if (ret)
return ret;
aemif->clk_rate = clk_get_rate(aemif->clk) / MSEC_PER_SEC;
if (of_device_is_compatible(np, "ti,da850-aemif"))

View File

@ -490,6 +490,14 @@ config ASPEED_LPC_CTRL
ioctl()s, the driver also provides a read/write interface to a BMC ram
region where the host LPC read/write region can be buffered.
config ASPEED_LPC_SNOOP
tristate "Aspeed ast2500 HOST LPC snoop support"
depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
help
Provides a driver to control the LPC snoop interface which
allows the BMC to listen on and save the data written by
the host to an arbitrary LPC I/O port.
config PCI_ENDPOINT_TEST
depends on PCI
select CRC32

View File

@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o
obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o

View File

@ -32,7 +32,7 @@
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/i2c/apds990x.h>
#include <linux/platform_data/apds990x.h>
/* Register map */
#define APDS990X_ENABLE 0x00 /* Enable of states and interrupts */
@ -841,7 +841,7 @@ static ssize_t apds990x_prox_enable_store(struct device *dev,
static DEVICE_ATTR(prox0_raw_en, S_IRUGO | S_IWUSR, apds990x_prox_enable_show,
apds990x_prox_enable_store);
static const char reporting_modes[][9] = {"trigger", "periodic"};
static const char *reporting_modes[] = {"trigger", "periodic"};
static ssize_t apds990x_prox_reporting_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
@ -856,13 +856,13 @@ static ssize_t apds990x_prox_reporting_mode_store(struct device *dev,
const char *buf, size_t len)
{
struct apds990x_chip *chip = dev_get_drvdata(dev);
int ret;
if (sysfs_streq(buf, reporting_modes[0]))
chip->prox_continuous_mode = 0;
else if (sysfs_streq(buf, reporting_modes[1]))
chip->prox_continuous_mode = 1;
else
return -EINVAL;
ret = sysfs_match_string(reporting_modes, buf);
if (ret < 0)
return ret;
chip->prox_continuous_mode = ret;
return len;
}

View File

@ -0,0 +1,261 @@
/*
* Copyright 2017 Google Inc
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Provides a simple driver to control the ASPEED LPC snoop interface which
* allows the BMC to listen on and save the data written by
* the host to an arbitrary LPC I/O port.
*
* Typically used by the BMC to "watch" host boot progress via port
* 0x80 writes made by the BIOS during the boot process.
*/
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/kfifo.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#define DEVICE_NAME "aspeed-lpc-snoop"
#define NUM_SNOOP_CHANNELS 2
#define SNOOP_FIFO_SIZE 2048
#define HICR5 0x0
#define HICR5_EN_SNP0W BIT(0)
#define HICR5_ENINT_SNP0W BIT(1)
#define HICR5_EN_SNP1W BIT(2)
#define HICR5_ENINT_SNP1W BIT(3)
#define HICR6 0x4
#define HICR6_STR_SNP0W BIT(0)
#define HICR6_STR_SNP1W BIT(1)
#define SNPWADR 0x10
#define SNPWADR_CH0_MASK GENMASK(15, 0)
#define SNPWADR_CH0_SHIFT 0
#define SNPWADR_CH1_MASK GENMASK(31, 16)
#define SNPWADR_CH1_SHIFT 16
#define SNPWDR 0x14
#define SNPWDR_CH0_MASK GENMASK(7, 0)
#define SNPWDR_CH0_SHIFT 0
#define SNPWDR_CH1_MASK GENMASK(15, 8)
#define SNPWDR_CH1_SHIFT 8
#define HICRB 0x80
#define HICRB_ENSNP0D BIT(14)
#define HICRB_ENSNP1D BIT(15)
struct aspeed_lpc_snoop {
struct regmap *regmap;
int irq;
struct kfifo snoop_fifo[NUM_SNOOP_CHANNELS];
};
/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */
static void put_fifo_with_discard(struct kfifo *fifo, u8 val)
{
if (!kfifo_initialized(fifo))
return;
if (kfifo_is_full(fifo))
kfifo_skip(fifo);
kfifo_put(fifo, val);
}
static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg)
{
struct aspeed_lpc_snoop *lpc_snoop = arg;
u32 reg, data;
if (regmap_read(lpc_snoop->regmap, HICR6, &reg))
return IRQ_NONE;
/* Check if one of the snoop channels is interrupting */
reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W);
if (!reg)
return IRQ_NONE;
/* Ack pending IRQs */
regmap_write(lpc_snoop->regmap, HICR6, reg);
/* Read and save most recent snoop'ed data byte to FIFO */
regmap_read(lpc_snoop->regmap, SNPWDR, &data);
if (reg & HICR6_STR_SNP0W) {
u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT;
put_fifo_with_discard(&lpc_snoop->snoop_fifo[0], val);
}
if (reg & HICR6_STR_SNP1W) {
u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT;
put_fifo_with_discard(&lpc_snoop->snoop_fifo[1], val);
}
return IRQ_HANDLED;
}
static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int rc;
lpc_snoop->irq = platform_get_irq(pdev, 0);
if (!lpc_snoop->irq)
return -ENODEV;
rc = devm_request_irq(dev, lpc_snoop->irq,
aspeed_lpc_snoop_irq, IRQF_SHARED,
DEVICE_NAME, lpc_snoop);
if (rc < 0) {
dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq);
lpc_snoop->irq = 0;
return rc;
}
return 0;
}
static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
int channel, u16 lpc_port)
{
int rc = 0;
u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en;
/* Create FIFO datastructure */
rc = kfifo_alloc(&lpc_snoop->snoop_fifo[channel],
SNOOP_FIFO_SIZE, GFP_KERNEL);
if (rc)
return rc;
/* Enable LPC snoop channel at requested port */
switch (channel) {
case 0:
hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W;
snpwadr_mask = SNPWADR_CH0_MASK;
snpwadr_shift = SNPWADR_CH0_SHIFT;
hicrb_en = HICRB_ENSNP0D;
break;
case 1:
hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W;
snpwadr_mask = SNPWADR_CH1_MASK;
snpwadr_shift = SNPWADR_CH1_SHIFT;
hicrb_en = HICRB_ENSNP1D;
break;
default:
return -EINVAL;
}
regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en);
regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask,
lpc_port << snpwadr_shift);
regmap_update_bits(lpc_snoop->regmap, HICRB, hicrb_en, hicrb_en);
return rc;
}
static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
int channel)
{
switch (channel) {
case 0:
regmap_update_bits(lpc_snoop->regmap, HICR5,
HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
0);
break;
case 1:
regmap_update_bits(lpc_snoop->regmap, HICR5,
HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
0);
break;
default:
return;
}
kfifo_free(&lpc_snoop->snoop_fifo[channel]);
}
static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
{
struct aspeed_lpc_snoop *lpc_snoop;
struct device *dev;
u32 port;
int rc;
dev = &pdev->dev;
lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL);
if (!lpc_snoop)
return -ENOMEM;
lpc_snoop->regmap = syscon_node_to_regmap(
pdev->dev.parent->of_node);
if (IS_ERR(lpc_snoop->regmap)) {
dev_err(dev, "Couldn't get regmap\n");
return -ENODEV;
}
dev_set_drvdata(&pdev->dev, lpc_snoop);
rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port);
if (rc) {
dev_err(dev, "no snoop ports configured\n");
return -ENODEV;
}
rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev);
if (rc)
return rc;
rc = aspeed_lpc_enable_snoop(lpc_snoop, 0, port);
if (rc)
return rc;
/* Configuration of 2nd snoop channel port is optional */
if (of_property_read_u32_index(dev->of_node, "snoop-ports",
1, &port) == 0) {
rc = aspeed_lpc_enable_snoop(lpc_snoop, 1, port);
if (rc)
aspeed_lpc_disable_snoop(lpc_snoop, 0);
}
return rc;
}
static int aspeed_lpc_snoop_remove(struct platform_device *pdev)
{
struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev);
/* Disable both snoop channels */
aspeed_lpc_disable_snoop(lpc_snoop, 0);
aspeed_lpc_disable_snoop(lpc_snoop, 1);
return 0;
}
static const struct of_device_id aspeed_lpc_snoop_match[] = {
{ .compatible = "aspeed,ast2500-lpc-snoop" },
{ },
};
static struct platform_driver aspeed_lpc_snoop_driver = {
.driver = {
.name = DEVICE_NAME,
.of_match_table = aspeed_lpc_snoop_match,
},
.probe = aspeed_lpc_snoop_probe,
.remove = aspeed_lpc_snoop_remove,
};
module_platform_driver(aspeed_lpc_snoop_driver);
MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Robert Lippert <rlippert@google.com>");
MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");

View File

@ -27,7 +27,7 @@
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/i2c/bh1770glc.h>
#include <linux/platform_data/bh1770glc.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/workqueue.h>

View File

@ -1040,7 +1040,7 @@ static void mei_cl_bus_dev_init(struct mei_device *bus,
*
* @bus: mei device
*/
void mei_cl_bus_rescan(struct mei_device *bus)
static void mei_cl_bus_rescan(struct mei_device *bus)
{
struct mei_cl_device *cldev, *n;
struct mei_me_client *me_cl;

View File

@ -65,7 +65,7 @@
#define HBM_MAJOR_VERSION_DOT 2
/*
* MEI version with notifcation support
* MEI version with notification support
*/
#define HBM_MINOR_VERSION_EV 0
#define HBM_MAJOR_VERSION_EV 2

View File

@ -215,12 +215,6 @@ int mei_start(struct mei_device *dev)
}
} while (ret);
/* we cannot start the device w/o hbm start message completed */
if (dev->dev_state == MEI_DEV_DISABLED) {
dev_err(dev->dev, "reset failed");
goto err;
}
if (mei_hbm_start_wait(dev)) {
dev_err(dev->dev, "HBM haven't started");
goto err;

View File

@ -235,6 +235,17 @@ static inline bool hdr_is_fixed(struct mei_msg_hdr *mei_hdr)
return mei_hdr->host_addr == 0 && mei_hdr->me_addr != 0;
}
static inline int hdr_is_valid(u32 msg_hdr)
{
struct mei_msg_hdr *mei_hdr;
mei_hdr = (struct mei_msg_hdr *)&msg_hdr;
if (!msg_hdr || mei_hdr->reserved)
return -EBADMSG;
return 0;
}
/**
* mei_irq_read_handler - bottom half read routine after ISR to
* handle the read processing.
@ -256,17 +267,18 @@ int mei_irq_read_handler(struct mei_device *dev,
dev->rd_msg_hdr = mei_read_hdr(dev);
(*slots)--;
dev_dbg(dev->dev, "slots =%08x.\n", *slots);
}
mei_hdr = (struct mei_msg_hdr *) &dev->rd_msg_hdr;
dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr));
if (mei_hdr->reserved || !dev->rd_msg_hdr) {
dev_err(dev->dev, "corrupted message header 0x%08X\n",
ret = hdr_is_valid(dev->rd_msg_hdr);
if (ret) {
dev_err(dev->dev, "corrupted message header 0x%08X\n",
dev->rd_msg_hdr);
ret = -EBADMSG;
goto end;
goto end;
}
}
mei_hdr = (struct mei_msg_hdr *)&dev->rd_msg_hdr;
dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr));
if (mei_slots2data(*slots) < mei_hdr->length) {
dev_err(dev->dev, "less data available than length=%08x.\n",
*slots);

View File

@ -306,7 +306,6 @@ struct mei_hw_ops {
};
/* MEI bus API*/
void mei_cl_bus_rescan(struct mei_device *bus);
void mei_cl_bus_rescan_work(struct work_struct *work);
void mei_cl_bus_dev_fixup(struct mei_cl_device *dev);
ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,

View File

@ -19,6 +19,7 @@
#include <linux/mm.h>
#include <linux/sram.h>
#include <asm/fncpy.h>
#include <asm/set_memory.h>
#include "sram.h"
@ -58,20 +59,32 @@ int sram_add_protect_exec(struct sram_partition *part)
* @src: Source address for the data to copy
* @size: Size of copy to perform, which starting from dst, must reside in pool
*
* Return: Address for copied data that can safely be called through function
* pointer, or NULL if problem.
*
* This helper function allows sram driver to act as central control location
* of 'protect-exec' pools which are normal sram pools but are always set
* read-only and executable except when copying data to them, at which point
* they are set to read-write non-executable, to make sure no memory is
* writeable and executable at the same time. This region must be page-aligned
* and is checked during probe, otherwise page attribute manipulation would
* not be possible.
* not be possible. Care must be taken to only call the returned address as
* dst address is not guaranteed to be safely callable.
*
* NOTE: This function uses the fncpy macro to move code to the executable
* region. Some architectures have strict requirements for relocating
* executable code, so fncpy is a macro that must be defined by any arch
* making use of this functionality that guarantees a safe copy of exec
* data and returns a safe address that can be called as a C function
* pointer.
*/
int sram_exec_copy(struct gen_pool *pool, void *dst, void *src,
size_t size)
void *sram_exec_copy(struct gen_pool *pool, void *dst, void *src,
size_t size)
{
struct sram_partition *part = NULL, *p;
unsigned long base;
int pages;
void *dst_cpy;
mutex_lock(&exec_pool_list_mutex);
list_for_each_entry(p, &exec_pool_list, list) {
@ -81,10 +94,10 @@ int sram_exec_copy(struct gen_pool *pool, void *dst, void *src,
mutex_unlock(&exec_pool_list_mutex);
if (!part)
return -EINVAL;
return NULL;
if (!addr_in_gen_pool(pool, (unsigned long)dst, size))
return -EINVAL;
return NULL;
base = (unsigned long)part->base;
pages = PAGE_ALIGN(size) / PAGE_SIZE;
@ -94,13 +107,13 @@ int sram_exec_copy(struct gen_pool *pool, void *dst, void *src,
set_memory_nx((unsigned long)base, pages);
set_memory_rw((unsigned long)base, pages);
memcpy(dst, src, size);
dst_cpy = fncpy(dst, src, size);
set_memory_ro((unsigned long)base, pages);
set_memory_x((unsigned long)base, pages);
mutex_unlock(&part->lock);
return 0;
return dst_cpy;
}
EXPORT_SYMBOL_GPL(sram_exec_copy);

View File

@ -0,0 +1,59 @@
#
# Multiplexer devices
#
menuconfig MULTIPLEXER
tristate "Multiplexer subsystem"
help
Multiplexer controller subsystem. Multiplexers are used in a
variety of settings, and this subsystem abstracts their use
so that the rest of the kernel sees a common interface. When
multiple parallel multiplexers are controlled by one single
multiplexer controller, this subsystem also coordinates the
multiplexer accesses.
To compile the subsystem as a module, choose M here: the module will
be called mux-core.
if MULTIPLEXER
config MUX_ADG792A
tristate "Analog Devices ADG792A/ADG792G Multiplexers"
depends on I2C
help
ADG792A and ADG792G Wide Bandwidth Triple 4:1 Multiplexers
The driver supports both operating the three multiplexers in
parallel and operating them independently.
To compile the driver as a module, choose M here: the module will
be called mux-adg792a.
config MUX_GPIO
tristate "GPIO-controlled Multiplexer"
depends on GPIOLIB || COMPILE_TEST
help
GPIO-controlled Multiplexer controller.
The driver builds a single multiplexer controller using a number
of gpio pins. For N pins, there will be 2^N possible multiplexer
states. The GPIO pins can be connected (by the hardware) to several
multiplexers, which in that case will be operated in parallel.
To compile the driver as a module, choose M here: the module will
be called mux-gpio.
config MUX_MMIO
tristate "MMIO register bitfield-controlled Multiplexer"
depends on (OF && MFD_SYSCON) || COMPILE_TEST
help
MMIO register bitfield-controlled Multiplexer controller.
The driver builds multiplexer controllers for bitfields in a syscon
register. For N bit wide bitfields, there will be 2^N possible
multiplexer states.
To compile the driver as a module, choose M here: the module will
be called mux-mmio.
endif

View File

@ -0,0 +1,8 @@
#
# Makefile for multiplexer devices.
#
obj-$(CONFIG_MULTIPLEXER) += mux-core.o
obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o
obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
obj-$(CONFIG_MUX_MMIO) += mux-mmio.o

View File

@ -0,0 +1,157 @@
/*
* Multiplexer driver for Analog Devices ADG792A/G Triple 4:1 mux
*
* Copyright (C) 2017 Axentia Technologies AB
*
* Author: Peter Rosin <peda@axentia.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mux/driver.h>
#include <linux/property.h>
#define ADG792A_LDSW BIT(0)
#define ADG792A_RESETB BIT(1)
#define ADG792A_DISABLE(mux) (0x50 | (mux))
#define ADG792A_DISABLE_ALL (0x5f)
#define ADG792A_MUX(mux, state) (0xc0 | (((mux) + 1) << 2) | (state))
#define ADG792A_MUX_ALL(state) (0xc0 | (state))
static int adg792a_write_cmd(struct i2c_client *i2c, u8 cmd, int reset)
{
u8 data = ADG792A_RESETB | ADG792A_LDSW;
/* ADG792A_RESETB is active low, the chip resets when it is zero. */
if (reset)
data &= ~ADG792A_RESETB;
return i2c_smbus_write_byte_data(i2c, cmd, data);
}
static int adg792a_set(struct mux_control *mux, int state)
{
struct i2c_client *i2c = to_i2c_client(mux->chip->dev.parent);
u8 cmd;
if (mux->chip->controllers == 1) {
/* parallel mux controller operation */
if (state == MUX_IDLE_DISCONNECT)
cmd = ADG792A_DISABLE_ALL;
else
cmd = ADG792A_MUX_ALL(state);
} else {
unsigned int controller = mux_control_get_index(mux);
if (state == MUX_IDLE_DISCONNECT)
cmd = ADG792A_DISABLE(controller);
else
cmd = ADG792A_MUX(controller, state);
}
return adg792a_write_cmd(i2c, cmd, 0);
}
static const struct mux_control_ops adg792a_ops = {
.set = adg792a_set,
};
static int adg792a_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct device *dev = &i2c->dev;
struct mux_chip *mux_chip;
s32 idle_state[3];
u32 cells;
int ret;
int i;
if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
ret = device_property_read_u32(dev, "#mux-control-cells", &cells);
if (ret < 0)
return ret;
if (cells >= 2)
return -EINVAL;
mux_chip = devm_mux_chip_alloc(dev, cells ? 3 : 1, 0);
if (IS_ERR(mux_chip))
return PTR_ERR(mux_chip);
mux_chip->ops = &adg792a_ops;
ret = adg792a_write_cmd(i2c, ADG792A_DISABLE_ALL, 1);
if (ret < 0)
return ret;
ret = device_property_read_u32_array(dev, "idle-state",
(u32 *)idle_state,
mux_chip->controllers);
if (ret < 0) {
idle_state[0] = MUX_IDLE_AS_IS;
idle_state[1] = MUX_IDLE_AS_IS;
idle_state[2] = MUX_IDLE_AS_IS;
}
for (i = 0; i < mux_chip->controllers; ++i) {
struct mux_control *mux = &mux_chip->mux[i];
mux->states = 4;
switch (idle_state[i]) {
case MUX_IDLE_DISCONNECT:
case MUX_IDLE_AS_IS:
case 0 ... 4:
mux->idle_state = idle_state[i];
break;
default:
dev_err(dev, "invalid idle-state %d\n", idle_state[i]);
return -EINVAL;
}
}
ret = devm_mux_chip_register(dev, mux_chip);
if (ret < 0)
return ret;
if (cells)
dev_info(dev, "3x single pole quadruple throw muxes registered\n");
else
dev_info(dev, "triple pole quadruple throw mux registered\n");
return 0;
}
static const struct i2c_device_id adg792a_id[] = {
{ .name = "adg792a", },
{ .name = "adg792g", },
{ }
};
MODULE_DEVICE_TABLE(i2c, adg792a_id);
static const struct of_device_id adg792a_of_match[] = {
{ .compatible = "adi,adg792a", },
{ .compatible = "adi,adg792g", },
{ }
};
MODULE_DEVICE_TABLE(of, adg792a_of_match);
static struct i2c_driver adg792a_driver = {
.driver = {
.name = "adg792a",
.of_match_table = of_match_ptr(adg792a_of_match),
},
.probe = adg792a_probe,
.id_table = adg792a_id,
};
module_i2c_driver(adg792a_driver);
MODULE_DESCRIPTION("Analog Devices ADG792A/G Triple 4:1 mux driver");
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,547 @@
/*
* Multiplexer subsystem
*
* Copyright (C) 2017 Axentia Technologies AB
*
* Author: Peter Rosin <peda@axentia.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define pr_fmt(fmt) "mux-core: " fmt
#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mux/consumer.h>
#include <linux/mux/driver.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
/*
* The idle-as-is "state" is not an actual state that may be selected, it
* only implies that the state should not be changed. So, use that state
* as indication that the cached state of the multiplexer is unknown.
*/
#define MUX_CACHE_UNKNOWN MUX_IDLE_AS_IS
static struct class mux_class = {
.name = "mux",
.owner = THIS_MODULE,
};
static DEFINE_IDA(mux_ida);
static int __init mux_init(void)
{
ida_init(&mux_ida);
return class_register(&mux_class);
}
static void __exit mux_exit(void)
{
class_register(&mux_class);
ida_destroy(&mux_ida);
}
static void mux_chip_release(struct device *dev)
{
struct mux_chip *mux_chip = to_mux_chip(dev);
ida_simple_remove(&mux_ida, mux_chip->id);
kfree(mux_chip);
}
static struct device_type mux_type = {
.name = "mux-chip",
.release = mux_chip_release,
};
/**
* mux_chip_alloc() - Allocate a mux-chip.
* @dev: The parent device implementing the mux interface.
* @controllers: The number of mux controllers to allocate for this chip.
* @sizeof_priv: Size of extra memory area for private use by the caller.
*
* After allocating the mux-chip with the desired number of mux controllers
* but before registering the chip, the mux driver is required to configure
* the number of valid mux states in the mux_chip->mux[N].states members and
* the desired idle state in the returned mux_chip->mux[N].idle_state members.
* The default idle state is MUX_IDLE_AS_IS. The mux driver also needs to
* provide a pointer to the operations struct in the mux_chip->ops member
* before registering the mux-chip with mux_chip_register.
*
* Return: A pointer to the new mux-chip, or an ERR_PTR with a negative errno.
*/
struct mux_chip *mux_chip_alloc(struct device *dev,
unsigned int controllers, size_t sizeof_priv)
{
struct mux_chip *mux_chip;
int i;
if (WARN_ON(!dev || !controllers))
return ERR_PTR(-EINVAL);
mux_chip = kzalloc(sizeof(*mux_chip) +
controllers * sizeof(*mux_chip->mux) +
sizeof_priv, GFP_KERNEL);
if (!mux_chip)
return ERR_PTR(-ENOMEM);
mux_chip->mux = (struct mux_control *)(mux_chip + 1);
mux_chip->dev.class = &mux_class;
mux_chip->dev.type = &mux_type;
mux_chip->dev.parent = dev;
mux_chip->dev.of_node = dev->of_node;
dev_set_drvdata(&mux_chip->dev, mux_chip);
mux_chip->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
if (mux_chip->id < 0) {
int err = mux_chip->id;
pr_err("muxchipX failed to get a device id\n");
kfree(mux_chip);
return ERR_PTR(err);
}
dev_set_name(&mux_chip->dev, "muxchip%d", mux_chip->id);
mux_chip->controllers = controllers;
for (i = 0; i < controllers; ++i) {
struct mux_control *mux = &mux_chip->mux[i];
mux->chip = mux_chip;
sema_init(&mux->lock, 1);
mux->cached_state = MUX_CACHE_UNKNOWN;
mux->idle_state = MUX_IDLE_AS_IS;
}
device_initialize(&mux_chip->dev);
return mux_chip;
}
EXPORT_SYMBOL_GPL(mux_chip_alloc);
static int mux_control_set(struct mux_control *mux, int state)
{
int ret = mux->chip->ops->set(mux, state);
mux->cached_state = ret < 0 ? MUX_CACHE_UNKNOWN : state;
return ret;
}
/**
* mux_chip_register() - Register a mux-chip, thus readying the controllers
* for use.
* @mux_chip: The mux-chip to register.
*
* Do not retry registration of the same mux-chip on failure. You should
* instead put it away with mux_chip_free() and allocate a new one, if you
* for some reason would like to retry registration.
*
* Return: Zero on success or a negative errno on error.
*/
int mux_chip_register(struct mux_chip *mux_chip)
{
int i;
int ret;
for (i = 0; i < mux_chip->controllers; ++i) {
struct mux_control *mux = &mux_chip->mux[i];
if (mux->idle_state == mux->cached_state)
continue;
ret = mux_control_set(mux, mux->idle_state);
if (ret < 0) {
dev_err(&mux_chip->dev, "unable to set idle state\n");
return ret;
}
}
ret = device_add(&mux_chip->dev);
if (ret < 0)
dev_err(&mux_chip->dev,
"device_add failed in %s: %d\n", __func__, ret);
return ret;
}
EXPORT_SYMBOL_GPL(mux_chip_register);
/**
* mux_chip_unregister() - Take the mux-chip off-line.
* @mux_chip: The mux-chip to unregister.
*
* mux_chip_unregister() reverses the effects of mux_chip_register().
* But not completely, you should not try to call mux_chip_register()
* on a mux-chip that has been registered before.
*/
void mux_chip_unregister(struct mux_chip *mux_chip)
{
device_del(&mux_chip->dev);
}
EXPORT_SYMBOL_GPL(mux_chip_unregister);
/**
* mux_chip_free() - Free the mux-chip for good.
* @mux_chip: The mux-chip to free.
*
* mux_chip_free() reverses the effects of mux_chip_alloc().
*/
void mux_chip_free(struct mux_chip *mux_chip)
{
if (!mux_chip)
return;
put_device(&mux_chip->dev);
}
EXPORT_SYMBOL_GPL(mux_chip_free);
static void devm_mux_chip_release(struct device *dev, void *res)
{
struct mux_chip *mux_chip = *(struct mux_chip **)res;
mux_chip_free(mux_chip);
}
/**
* devm_mux_chip_alloc() - Resource-managed version of mux_chip_alloc().
* @dev: The parent device implementing the mux interface.
* @controllers: The number of mux controllers to allocate for this chip.
* @sizeof_priv: Size of extra memory area for private use by the caller.
*
* See mux_chip_alloc() for more details.
*
* Return: A pointer to the new mux-chip, or an ERR_PTR with a negative errno.
*/
struct mux_chip *devm_mux_chip_alloc(struct device *dev,
unsigned int controllers,
size_t sizeof_priv)
{
struct mux_chip **ptr, *mux_chip;
ptr = devres_alloc(devm_mux_chip_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
mux_chip = mux_chip_alloc(dev, controllers, sizeof_priv);
if (IS_ERR(mux_chip)) {
devres_free(ptr);
return mux_chip;
}
*ptr = mux_chip;
devres_add(dev, ptr);
return mux_chip;
}
EXPORT_SYMBOL_GPL(devm_mux_chip_alloc);
static void devm_mux_chip_reg_release(struct device *dev, void *res)
{
struct mux_chip *mux_chip = *(struct mux_chip **)res;
mux_chip_unregister(mux_chip);
}
/**
* devm_mux_chip_register() - Resource-managed version mux_chip_register().
* @dev: The parent device implementing the mux interface.
* @mux_chip: The mux-chip to register.
*
* See mux_chip_register() for more details.
*
* Return: Zero on success or a negative errno on error.
*/
int devm_mux_chip_register(struct device *dev,
struct mux_chip *mux_chip)
{
struct mux_chip **ptr;
int res;
ptr = devres_alloc(devm_mux_chip_reg_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
res = mux_chip_register(mux_chip);
if (res) {
devres_free(ptr);
return res;
}
*ptr = mux_chip;
devres_add(dev, ptr);
return res;
}
EXPORT_SYMBOL_GPL(devm_mux_chip_register);
/**
* mux_control_states() - Query the number of multiplexer states.
* @mux: The mux-control to query.
*
* Return: The number of multiplexer states.
*/
unsigned int mux_control_states(struct mux_control *mux)
{
return mux->states;
}
EXPORT_SYMBOL_GPL(mux_control_states);
/*
* The mux->lock must be down when calling this function.
*/
static int __mux_control_select(struct mux_control *mux, int state)
{
int ret;
if (WARN_ON(state < 0 || state >= mux->states))
return -EINVAL;
if (mux->cached_state == state)
return 0;
ret = mux_control_set(mux, state);
if (ret >= 0)
return 0;
/* The mux update failed, try to revert if appropriate... */
if (mux->idle_state != MUX_IDLE_AS_IS)
mux_control_set(mux, mux->idle_state);
return ret;
}
/**
* mux_control_select() - Select the given multiplexer state.
* @mux: The mux-control to request a change of state from.
* @state: The new requested state.
*
* On successfully selecting the mux-control state, it will be locked until
* there is a call to mux_control_deselect(). If the mux-control is already
* selected when mux_control_select() is called, the caller will be blocked
* until mux_control_deselect() is called (by someone else).
*
* Therefore, make sure to call mux_control_deselect() when the operation is
* complete and the mux-control is free for others to use, but do not call
* mux_control_deselect() if mux_control_select() fails.
*
* Return: 0 when the mux-control state has the requested state or a negative
* errno on error.
*/
int mux_control_select(struct mux_control *mux, unsigned int state)
{
int ret;
ret = down_killable(&mux->lock);
if (ret < 0)
return ret;
ret = __mux_control_select(mux, state);
if (ret < 0)
up(&mux->lock);
return ret;
}
EXPORT_SYMBOL_GPL(mux_control_select);
/**
* mux_control_try_select() - Try to select the given multiplexer state.
* @mux: The mux-control to request a change of state from.
* @state: The new requested state.
*
* On successfully selecting the mux-control state, it will be locked until
* mux_control_deselect() called.
*
* Therefore, make sure to call mux_control_deselect() when the operation is
* complete and the mux-control is free for others to use, but do not call
* mux_control_deselect() if mux_control_try_select() fails.
*
* Return: 0 when the mux-control state has the requested state or a negative
* errno on error. Specifically -EBUSY if the mux-control is contended.
*/
int mux_control_try_select(struct mux_control *mux, unsigned int state)
{
int ret;
if (down_trylock(&mux->lock))
return -EBUSY;
ret = __mux_control_select(mux, state);
if (ret < 0)
up(&mux->lock);
return ret;
}
EXPORT_SYMBOL_GPL(mux_control_try_select);
/**
* mux_control_deselect() - Deselect the previously selected multiplexer state.
* @mux: The mux-control to deselect.
*
* It is required that a single call is made to mux_control_deselect() for
* each and every successful call made to either of mux_control_select() or
* mux_control_try_select().
*
* Return: 0 on success and a negative errno on error. An error can only
* occur if the mux has an idle state. Note that even if an error occurs, the
* mux-control is unlocked and is thus free for the next access.
*/
int mux_control_deselect(struct mux_control *mux)
{
int ret = 0;
if (mux->idle_state != MUX_IDLE_AS_IS &&
mux->idle_state != mux->cached_state)
ret = mux_control_set(mux, mux->idle_state);
up(&mux->lock);
return ret;
}
EXPORT_SYMBOL_GPL(mux_control_deselect);
static int of_dev_node_match(struct device *dev, const void *data)
{
return dev->of_node == data;
}
static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
{
struct device *dev;
dev = class_find_device(&mux_class, NULL, np, of_dev_node_match);
return dev ? to_mux_chip(dev) : NULL;
}
/**
* mux_control_get() - Get the mux-control for a device.
* @dev: The device that needs a mux-control.
* @mux_name: The name identifying the mux-control.
*
* Return: A pointer to the mux-control, or an ERR_PTR with a negative errno.
*/
struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
{
struct device_node *np = dev->of_node;
struct of_phandle_args args;
struct mux_chip *mux_chip;
unsigned int controller;
int index = 0;
int ret;
if (mux_name) {
index = of_property_match_string(np, "mux-control-names",
mux_name);
if (index < 0) {
dev_err(dev, "mux controller '%s' not found\n",
mux_name);
return ERR_PTR(index);
}
}
ret = of_parse_phandle_with_args(np,
"mux-controls", "#mux-control-cells",
index, &args);
if (ret) {
dev_err(dev, "%s: failed to get mux-control %s(%i)\n",
np->full_name, mux_name ?: "", index);
return ERR_PTR(ret);
}
mux_chip = of_find_mux_chip_by_node(args.np);
of_node_put(args.np);
if (!mux_chip)
return ERR_PTR(-EPROBE_DEFER);
if (args.args_count > 1 ||
(!args.args_count && (mux_chip->controllers > 1))) {
dev_err(dev, "%s: wrong #mux-control-cells for %s\n",
np->full_name, args.np->full_name);
return ERR_PTR(-EINVAL);
}
controller = 0;
if (args.args_count)
controller = args.args[0];
if (controller >= mux_chip->controllers) {
dev_err(dev, "%s: bad mux controller %u specified in %s\n",
np->full_name, controller, args.np->full_name);
return ERR_PTR(-EINVAL);
}
get_device(&mux_chip->dev);
return &mux_chip->mux[controller];
}
EXPORT_SYMBOL_GPL(mux_control_get);
/**
* mux_control_put() - Put away the mux-control for good.
* @mux: The mux-control to put away.
*
* mux_control_put() reverses the effects of mux_control_get().
*/
void mux_control_put(struct mux_control *mux)
{
put_device(&mux->chip->dev);
}
EXPORT_SYMBOL_GPL(mux_control_put);
static void devm_mux_control_release(struct device *dev, void *res)
{
struct mux_control *mux = *(struct mux_control **)res;
mux_control_put(mux);
}
/**
* devm_mux_control_get() - Get the mux-control for a device, with resource
* management.
* @dev: The device that needs a mux-control.
* @mux_name: The name identifying the mux-control.
*
* Return: Pointer to the mux-control, or an ERR_PTR with a negative errno.
*/
struct mux_control *devm_mux_control_get(struct device *dev,
const char *mux_name)
{
struct mux_control **ptr, *mux;
ptr = devres_alloc(devm_mux_control_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
mux = mux_control_get(dev, mux_name);
if (IS_ERR(mux)) {
devres_free(ptr);
return mux;
}
*ptr = mux;
devres_add(dev, ptr);
return mux;
}
EXPORT_SYMBOL_GPL(devm_mux_control_get);
/*
* Using subsys_initcall instead of module_init here to try to ensure - for
* the non-modular case - that the subsystem is initialized when mux consumers
* and mux controllers start to use it.
* For the modular case, the ordering is ensured with module dependencies.
*/
subsys_initcall(mux_init);
module_exit(mux_exit);
MODULE_DESCRIPTION("Multiplexer subsystem");
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,114 @@
/*
* GPIO-controlled multiplexer driver
*
* Copyright (C) 2017 Axentia Technologies AB
*
* Author: Peter Rosin <peda@axentia.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/mux/driver.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/property.h>
struct mux_gpio {
struct gpio_descs *gpios;
int *val;
};
static int mux_gpio_set(struct mux_control *mux, int state)
{
struct mux_gpio *mux_gpio = mux_chip_priv(mux->chip);
int i;
for (i = 0; i < mux_gpio->gpios->ndescs; i++)
mux_gpio->val[i] = (state >> i) & 1;
gpiod_set_array_value_cansleep(mux_gpio->gpios->ndescs,
mux_gpio->gpios->desc,
mux_gpio->val);
return 0;
}
static const struct mux_control_ops mux_gpio_ops = {
.set = mux_gpio_set,
};
static const struct of_device_id mux_gpio_dt_ids[] = {
{ .compatible = "gpio-mux", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
static int mux_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mux_chip *mux_chip;
struct mux_gpio *mux_gpio;
int pins;
s32 idle_state;
int ret;
pins = gpiod_count(dev, "mux");
if (pins < 0)
return pins;
mux_chip = devm_mux_chip_alloc(dev, 1, sizeof(*mux_gpio) +
pins * sizeof(*mux_gpio->val));
if (IS_ERR(mux_chip))
return PTR_ERR(mux_chip);
mux_gpio = mux_chip_priv(mux_chip);
mux_gpio->val = (int *)(mux_gpio + 1);
mux_chip->ops = &mux_gpio_ops;
mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
if (IS_ERR(mux_gpio->gpios)) {
ret = PTR_ERR(mux_gpio->gpios);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to get gpios\n");
return ret;
}
WARN_ON(pins != mux_gpio->gpios->ndescs);
mux_chip->mux->states = 1 << pins;
ret = device_property_read_u32(dev, "idle-state", (u32 *)&idle_state);
if (ret >= 0 && idle_state != MUX_IDLE_AS_IS) {
if (idle_state < 0 || idle_state >= mux_chip->mux->states) {
dev_err(dev, "invalid idle-state %u\n", idle_state);
return -EINVAL;
}
mux_chip->mux->idle_state = idle_state;
}
ret = devm_mux_chip_register(dev, mux_chip);
if (ret < 0)
return ret;
dev_info(dev, "%u-way mux-controller registered\n",
mux_chip->mux->states);
return 0;
}
static struct platform_driver mux_gpio_driver = {
.driver = {
.name = "gpio-mux",
.of_match_table = of_match_ptr(mux_gpio_dt_ids),
},
.probe = mux_gpio_probe,
};
module_platform_driver(mux_gpio_driver);
MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,141 @@
/*
* MMIO register bitfield-controlled multiplexer driver
*
* Copyright (C) 2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/mux/driver.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
static int mux_mmio_set(struct mux_control *mux, int state)
{
struct regmap_field **fields = mux_chip_priv(mux->chip);
return regmap_field_write(fields[mux_control_get_index(mux)], state);
}
static const struct mux_control_ops mux_mmio_ops = {
.set = mux_mmio_set,
};
static const struct of_device_id mux_mmio_dt_ids[] = {
{ .compatible = "mmio-mux", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mux_mmio_dt_ids);
static int mux_mmio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct regmap_field **fields;
struct mux_chip *mux_chip;
struct regmap *regmap;
int num_fields;
int ret;
int i;
regmap = syscon_node_to_regmap(np->parent);
if (IS_ERR(regmap)) {
ret = PTR_ERR(regmap);
dev_err(dev, "failed to get regmap: %d\n", ret);
return ret;
}
ret = of_property_count_u32_elems(np, "mux-reg-masks");
if (ret == 0 || ret % 2)
ret = -EINVAL;
if (ret < 0) {
dev_err(dev, "mux-reg-masks property missing or invalid: %d\n",
ret);
return ret;
}
num_fields = ret / 2;
mux_chip = devm_mux_chip_alloc(dev, num_fields, num_fields *
sizeof(*fields));
if (IS_ERR(mux_chip))
return PTR_ERR(mux_chip);
fields = mux_chip_priv(mux_chip);
for (i = 0; i < num_fields; i++) {
struct mux_control *mux = &mux_chip->mux[i];
struct reg_field field;
s32 idle_state = MUX_IDLE_AS_IS;
u32 reg, mask;
int bits;
ret = of_property_read_u32_index(np, "mux-reg-masks",
2 * i, &reg);
if (!ret)
ret = of_property_read_u32_index(np, "mux-reg-masks",
2 * i + 1, &mask);
if (ret < 0) {
dev_err(dev, "bitfield %d: failed to read mux-reg-masks property: %d\n",
i, ret);
return ret;
}
field.reg = reg;
field.msb = fls(mask) - 1;
field.lsb = ffs(mask) - 1;
if (mask != GENMASK(field.msb, field.lsb)) {
dev_err(dev, "bitfield %d: invalid mask 0x%x\n",
i, mask);
return -EINVAL;
}
fields[i] = devm_regmap_field_alloc(dev, regmap, field);
if (IS_ERR(fields[i])) {
ret = PTR_ERR(fields[i]);
dev_err(dev, "bitfield %d: failed allocate: %d\n",
i, ret);
return ret;
}
bits = 1 + field.msb - field.lsb;
mux->states = 1 << bits;
of_property_read_u32_index(np, "idle-states", i,
(u32 *)&idle_state);
if (idle_state != MUX_IDLE_AS_IS) {
if (idle_state < 0 || idle_state >= mux->states) {
dev_err(dev, "bitfield: %d: out of range idle state %d\n",
i, idle_state);
return -EINVAL;
}
mux->idle_state = idle_state;
}
}
mux_chip->ops = &mux_mmio_ops;
return devm_mux_chip_register(dev, mux_chip);
}
static struct platform_driver mux_mmio_driver = {
.driver = {
.name = "mmio-mux",
.of_match_table = of_match_ptr(mux_mmio_dt_ids),
},
.probe = mux_mmio_probe,
};
module_platform_driver(mux_mmio_driver);
MODULE_DESCRIPTION("MMIO register bitfield-controlled multiplexer driver");
MODULE_AUTHOR("Philipp Zabel <p.zabel@pengutronix.de>");
MODULE_LICENSE("GPL v2");

View File

@ -34,7 +34,7 @@
#define OTPC_CMD_READ 0x0
#define OTPC_CMD_OTP_PROG_ENABLE 0x2
#define OTPC_CMD_OTP_PROG_DISABLE 0x3
#define OTPC_CMD_PROGRAM 0xA
#define OTPC_CMD_PROGRAM 0x8
/* OTPC Status Bits */
#define OTPC_STAT_CMD_DONE BIT(1)
@ -209,7 +209,7 @@ static int bcm_otpc_write(void *context, unsigned int offset, void *val,
set_command(priv->base, OTPC_CMD_PROGRAM);
set_cpu_address(priv->base, address++);
for (i = 0; i < priv->map->otpc_row_size; i++) {
writel(*buf, priv->base + priv->map->data_r_offset[i]);
writel(*buf, priv->base + priv->map->data_w_offset[i]);
buf++;
bytes_written += sizeof(*buf);
}

View File

@ -287,9 +287,15 @@ static struct nvmem_cell *nvmem_find_cell(const char *cell_id)
{
struct nvmem_cell *p;
mutex_lock(&nvmem_cells_mutex);
list_for_each_entry(p, &nvmem_cells, node)
if (p && !strcmp(p->name, cell_id))
if (p && !strcmp(p->name, cell_id)) {
mutex_unlock(&nvmem_cells_mutex);
return p;
}
mutex_unlock(&nvmem_cells_mutex);
return NULL;
}
@ -489,21 +495,24 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
rval = device_add(&nvmem->dev);
if (rval)
goto out;
goto err_put_device;
if (config->compat) {
rval = nvmem_setup_compat(nvmem, config);
if (rval)
goto out;
goto err_device_del;
}
if (config->cells)
nvmem_add_cells(nvmem, config);
return nvmem;
out:
ida_simple_remove(&nvmem_ida, nvmem->id);
kfree(nvmem);
err_device_del:
device_del(&nvmem->dev);
err_put_device:
put_device(&nvmem->dev);
return ERR_PTR(rval);
}
EXPORT_SYMBOL_GPL(nvmem_register);
@ -529,6 +538,7 @@ int nvmem_unregister(struct nvmem_device *nvmem)
nvmem_device_remove_all_cells(nvmem);
device_del(&nvmem->dev);
put_device(&nvmem->dev);
return 0;
}

View File

@ -169,6 +169,10 @@ static const struct of_device_id rockchip_efuse_match[] = {
.compatible = "rockchip,rk3188-efuse",
.data = (void *)&rockchip_rk3288_efuse_read,
},
{
.compatible = "rockchip,rk322x-efuse",
.data = (void *)&rockchip_rk3288_efuse_read,
},
{
.compatible = "rockchip,rk3288-efuse",
.data = (void *)&rockchip_rk3288_efuse_read,

View File

@ -266,7 +266,7 @@ struct goldfish_pipe_dev {
unsigned char __iomem *base;
};
struct goldfish_pipe_dev pipe_dev[1] = {};
static struct goldfish_pipe_dev pipe_dev[1] = {};
static int goldfish_cmd_locked(struct goldfish_pipe *pipe, enum PipeCmdCode cmd)
{

View File

@ -28,7 +28,7 @@
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include "../../w1/w1.h"
#include <linux/w1.h>
#include "../../w1/slaves/w1_ds2760.h"
struct ds2760_device_info {

View File

@ -21,7 +21,7 @@
#include <linux/power_supply.h>
#include <linux/idr.h>
#include "../../w1/w1.h"
#include <linux/w1.h>
#include "../../w1/slaves/w1_ds2780.h"
/* Current unit measurement in uA for a 1 milli-ohm sense resistor */

View File

@ -19,7 +19,7 @@
#include <linux/power_supply.h>
#include <linux/idr.h>
#include "../../w1/w1.h"
#include <linux/w1.h>
#include "../../w1/slaves/w1_ds2781.h"
/* Current unit measurement in uA for a 1 milli-ohm sense resistor */

View File

@ -2,9 +2,7 @@
# PPS support configuration
#
menu "PPS support"
config PPS
menuconfig PPS
tristate "PPS support"
---help---
PPS (Pulse Per Second) is a special pulse provided by some GPS
@ -20,10 +18,10 @@ config PPS
To compile this driver as a module, choose M here: the module
will be called pps_core.ko.
if PPS
config PPS_DEBUG
bool "PPS debugging messages"
depends on PPS
help
Say Y here if you want the PPS support to produce a bunch of debug
messages to the system log. Select this if you are having a
@ -31,17 +29,13 @@ config PPS_DEBUG
config NTP_PPS
bool "PPS kernel consumer support"
depends on !NO_HZ_COMMON
depends on PPS && !NO_HZ_COMMON
help
This option adds support for direct in-kernel time
synchronization using an external PPS signal.
It doesn't work on tickless systems at the moment.
endif
source drivers/pps/clients/Kconfig
source drivers/pps/generators/Kconfig
endmenu

View File

@ -2,12 +2,12 @@
# PPS clients configuration
#
if PPS
comment "PPS clients support"
depends on PPS
config PPS_CLIENT_KTIMER
tristate "Kernel timer client (Testing client, use for debug)"
depends on PPS
help
If you say yes here you get support for a PPS debugging client
which uses a kernel timer to generate the PPS signal.
@ -37,5 +37,3 @@ config PPS_CLIENT_GPIO
GPIO. To be useful you must also register a platform device
specifying the GPIO pin and other options, usually in your board
setup.
endif

View File

@ -3,10 +3,11 @@
#
comment "PPS generators support"
depends on PPS
config PPS_GENERATOR_PARPORT
tristate "Parallel port PPS signal generator"
depends on PARPORT && BROKEN
depends on PPS && PARPORT && BROKEN
help
If you say yes here you get support for a PPS signal generator which
utilizes STROBE pin of a parallel port to send PPS signals. It uses

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,16 @@
menuconfig THUNDERBOLT
tristate "Thunderbolt support for Apple devices"
tristate "Thunderbolt support"
depends on PCI
depends on X86 || COMPILE_TEST
select APPLE_PROPERTIES if EFI_STUB && X86
select CRC32
select CRYPTO
select CRYPTO_HASH
select NVMEM
help
Cactus Ridge Thunderbolt Controller driver
This driver is required if you want to hotplug Thunderbolt devices on
Apple hardware.
Device chaining is currently not supported.
Thunderbolt Controller driver. This driver is required if you
want to hotplug Thunderbolt devices on Apple hardware or on PCs
with Intel Falcon Ridge or newer.
To compile this driver a module, choose M here. The module will be
called thunderbolt.

View File

@ -1,3 +1,3 @@
obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o
thunderbolt-objs += domain.o dma_port.o icm.o

View File

@ -9,6 +9,8 @@
#include "tb.h"
#define CAP_OFFSET_MAX 0xff
#define VSE_CAP_OFFSET_MAX 0xffff
struct tb_cap_any {
union {
@ -18,99 +20,110 @@ struct tb_cap_any {
};
} __packed;
static bool tb_cap_is_basic(struct tb_cap_any *cap)
/**
* tb_port_find_cap() - Find port capability
* @port: Port to find the capability for
* @cap: Capability to look
*
* Returns offset to start of capability or %-ENOENT if no such
* capability was found. Negative errno is returned if there was an
* error.
*/
int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
{
/* basic.cap is u8. This checks only the lower 8 bit of cap. */
return cap->basic.cap != 5;
}
u32 offset;
static bool tb_cap_is_long(struct tb_cap_any *cap)
{
return !tb_cap_is_basic(cap)
&& cap->extended_short.next == 0
&& cap->extended_short.length == 0;
}
static enum tb_cap tb_cap(struct tb_cap_any *cap)
{
if (tb_cap_is_basic(cap))
return cap->basic.cap;
else
/* extended_short/long have cap at the same offset. */
return cap->extended_short.cap;
}
static u32 tb_cap_next(struct tb_cap_any *cap, u32 offset)
{
int next;
if (offset == 1) {
/*
* The first pointer is part of the switch header and always
* a simple pointer.
*/
next = cap->basic.next;
} else {
/*
* Somehow Intel decided to use 3 different types of capability
* headers. It is not like anyone could have predicted that
* single byte offsets are not enough...
*/
if (tb_cap_is_basic(cap))
next = cap->basic.next;
else if (!tb_cap_is_long(cap))
next = cap->extended_short.next;
else
next = cap->extended_long.next;
}
/*
* "Hey, we could terminate some capability lists with a null offset
* and others with a pointer to the last element." - "Great idea!"
* DP out adapters claim to implement TMU capability but in
* reality they do not so we hard code the adapter specific
* capability offset here.
*/
if (next == offset)
return 0;
return next;
if (port->config.type == TB_TYPE_DP_HDMI_OUT)
offset = 0x39;
else
offset = 0x1;
do {
struct tb_cap_any header;
int ret;
ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
if (ret)
return ret;
if (header.basic.cap == cap)
return offset;
offset = header.basic.next;
} while (offset);
return -ENOENT;
}
static int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
{
int offset = sw->config.first_cap_offset;
while (offset > 0 && offset < CAP_OFFSET_MAX) {
struct tb_cap_any header;
int ret;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
if (ret)
return ret;
if (header.basic.cap == cap)
return offset;
offset = header.basic.next;
}
return -ENOENT;
}
/**
* tb_find_cap() - find a capability
* tb_switch_find_vse_cap() - Find switch vendor specific capability
* @sw: Switch to find the capability for
* @vsec: Vendor specific capability to look
*
* Return: Returns a positive offset if the capability was found and 0 if not.
* Returns an error code on failure.
* Functions enumerates vendor specific capabilities (VSEC) of a switch
* and returns offset when capability matching @vsec is found. If no
* such capability is found returns %-ENOENT. In case of error returns
* negative errno.
*/
int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap)
int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
{
u32 offset = 1;
struct tb_cap_any header;
int res;
int retries = 10;
while (retries--) {
res = tb_port_read(port, &header, space, offset, 1);
if (res) {
/* Intel needs some help with linked lists. */
if (space == TB_CFG_PORT && offset == 0xa
&& port->config.type == TB_TYPE_DP_HDMI_OUT) {
offset = 0x39;
continue;
}
return res;
}
if (offset != 1) {
if (tb_cap(&header) == cap)
int offset;
offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE);
if (offset < 0)
return offset;
while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) {
int ret;
ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
if (ret)
return ret;
/*
* Extended vendor specific capabilities come in two
* flavors: short and long. The latter is used when
* offset is over 0xff.
*/
if (offset >= CAP_OFFSET_MAX) {
if (header.extended_long.vsec_id == vsec)
return offset;
if (tb_cap_is_long(&header)) {
/* tb_cap_extended_long is 2 dwords */
res = tb_port_read(port, &header, space,
offset, 2);
if (res)
return res;
}
offset = header.extended_long.next;
} else {
if (header.extended_short.vsec_id == vsec)
return offset;
if (!header.extended_short.length)
return -ENOENT;
offset = header.extended_short.next;
}
offset = tb_cap_next(&header, offset);
if (!offset)
return 0;
}
tb_port_WARN(port,
"run out of retries while looking for cap %#x in config space %d, last offset: %#x\n",
cap, space, offset);
return -EIO;
return -ENOENT;
}

View File

@ -5,22 +5,17 @@
*/
#include <linux/crc32.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/dmapool.h>
#include <linux/workqueue.h>
#include <linux/kfifo.h>
#include "ctl.h"
struct ctl_pkg {
struct tb_ctl *ctl;
void *buffer;
struct ring_frame frame;
};
#define TB_CTL_RX_PKG_COUNT 10
#define TB_CTL_RX_PKG_COUNT 10
#define TB_CTL_RETRIES 4
/**
* struct tb_cfg - thunderbolt control channel
@ -32,10 +27,11 @@ struct tb_ctl {
struct dma_pool *frame_pool;
struct ctl_pkg *rx_packets[TB_CTL_RX_PKG_COUNT];
DECLARE_KFIFO(response_fifo, struct ctl_pkg*, 16);
struct completion response_ready;
struct mutex request_queue_lock;
struct list_head request_queue;
bool running;
hotplug_cb callback;
event_cb callback;
void *callback_data;
};
@ -52,102 +48,124 @@ struct tb_ctl {
#define tb_ctl_info(ctl, format, arg...) \
dev_info(&(ctl)->nhi->pdev->dev, format, ## arg)
#define tb_ctl_dbg(ctl, format, arg...) \
dev_dbg(&(ctl)->nhi->pdev->dev, format, ## arg)
/* configuration packets definitions */
static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue);
/* Serializes access to request kref_get/put */
static DEFINE_MUTEX(tb_cfg_request_lock);
enum tb_cfg_pkg_type {
TB_CFG_PKG_READ = 1,
TB_CFG_PKG_WRITE = 2,
TB_CFG_PKG_ERROR = 3,
TB_CFG_PKG_NOTIFY_ACK = 4,
TB_CFG_PKG_EVENT = 5,
TB_CFG_PKG_XDOMAIN_REQ = 6,
TB_CFG_PKG_XDOMAIN_RESP = 7,
TB_CFG_PKG_OVERRIDE = 8,
TB_CFG_PKG_RESET = 9,
TB_CFG_PKG_PREPARE_TO_SLEEP = 0xd,
};
/**
* tb_cfg_request_alloc() - Allocates a new config request
*
* This is refcounted object so when you are done with this, call
* tb_cfg_request_put() to it.
*/
struct tb_cfg_request *tb_cfg_request_alloc(void)
{
struct tb_cfg_request *req;
/* common header */
struct tb_cfg_header {
u32 route_hi:22;
u32 unknown:10; /* highest order bit is set on replies */
u32 route_lo;
} __packed;
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return NULL;
/* additional header for read/write packets */
struct tb_cfg_address {
u32 offset:13; /* in dwords */
u32 length:6; /* in dwords */
u32 port:6;
enum tb_cfg_space space:2;
u32 seq:2; /* sequence number */
u32 zero:3;
} __packed;
kref_init(&req->kref);
/* TB_CFG_PKG_READ, response for TB_CFG_PKG_WRITE */
struct cfg_read_pkg {
struct tb_cfg_header header;
struct tb_cfg_address addr;
} __packed;
return req;
}
/* TB_CFG_PKG_WRITE, response for TB_CFG_PKG_READ */
struct cfg_write_pkg {
struct tb_cfg_header header;
struct tb_cfg_address addr;
u32 data[64]; /* maximum size, tb_cfg_address.length has 6 bits */
} __packed;
/**
* tb_cfg_request_get() - Increase refcount of a request
* @req: Request whose refcount is increased
*/
void tb_cfg_request_get(struct tb_cfg_request *req)
{
mutex_lock(&tb_cfg_request_lock);
kref_get(&req->kref);
mutex_unlock(&tb_cfg_request_lock);
}
/* TB_CFG_PKG_ERROR */
struct cfg_error_pkg {
struct tb_cfg_header header;
enum tb_cfg_error error:4;
u32 zero1:4;
u32 port:6;
u32 zero2:2; /* Both should be zero, still they are different fields. */
u32 zero3:16;
} __packed;
static void tb_cfg_request_destroy(struct kref *kref)
{
struct tb_cfg_request *req = container_of(kref, typeof(*req), kref);
/* TB_CFG_PKG_EVENT */
struct cfg_event_pkg {
struct tb_cfg_header header;
u32 port:6;
u32 zero:25;
bool unplug:1;
} __packed;
kfree(req);
}
/* TB_CFG_PKG_RESET */
struct cfg_reset_pkg {
struct tb_cfg_header header;
} __packed;
/**
* tb_cfg_request_put() - Decrease refcount and possibly release the request
* @req: Request whose refcount is decreased
*
* Call this function when you are done with the request. When refcount
* goes to %0 the object is released.
*/
void tb_cfg_request_put(struct tb_cfg_request *req)
{
mutex_lock(&tb_cfg_request_lock);
kref_put(&req->kref, tb_cfg_request_destroy);
mutex_unlock(&tb_cfg_request_lock);
}
/* TB_CFG_PKG_PREPARE_TO_SLEEP */
struct cfg_pts_pkg {
struct tb_cfg_header header;
u32 data;
} __packed;
static int tb_cfg_request_enqueue(struct tb_ctl *ctl,
struct tb_cfg_request *req)
{
WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags));
WARN_ON(req->ctl);
mutex_lock(&ctl->request_queue_lock);
if (!ctl->running) {
mutex_unlock(&ctl->request_queue_lock);
return -ENOTCONN;
}
req->ctl = ctl;
list_add_tail(&req->list, &ctl->request_queue);
set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
mutex_unlock(&ctl->request_queue_lock);
return 0;
}
static void tb_cfg_request_dequeue(struct tb_cfg_request *req)
{
struct tb_ctl *ctl = req->ctl;
mutex_lock(&ctl->request_queue_lock);
list_del(&req->list);
clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags))
wake_up(&tb_cfg_request_cancel_queue);
mutex_unlock(&ctl->request_queue_lock);
}
static bool tb_cfg_request_is_active(struct tb_cfg_request *req)
{
return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags);
}
static struct tb_cfg_request *
tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg)
{
struct tb_cfg_request *req;
bool found = false;
mutex_lock(&pkg->ctl->request_queue_lock);
list_for_each_entry(req, &pkg->ctl->request_queue, list) {
tb_cfg_request_get(req);
if (req->match(req, pkg)) {
found = true;
break;
}
tb_cfg_request_put(req);
}
mutex_unlock(&pkg->ctl->request_queue_lock);
return found ? req : NULL;
}
/* utility functions */
static u64 get_route(struct tb_cfg_header header)
{
return (u64) header.route_hi << 32 | header.route_lo;
}
static struct tb_cfg_header make_header(u64 route)
{
struct tb_cfg_header header = {
.route_hi = route >> 32,
.route_lo = route,
};
/* check for overflow, route_hi is not 32 bits! */
WARN_ON(get_route(header) != route);
return header;
}
static int check_header(struct ctl_pkg *pkg, u32 len, enum tb_cfg_pkg_type type,
u64 route)
static int check_header(const struct ctl_pkg *pkg, u32 len,
enum tb_cfg_pkg_type type, u64 route)
{
struct tb_cfg_header *header = pkg->buffer;
@ -167,9 +185,9 @@ static int check_header(struct ctl_pkg *pkg, u32 len, enum tb_cfg_pkg_type type,
if (WARN(header->unknown != 1 << 9,
"header->unknown is %#x\n", header->unknown))
return -EIO;
if (WARN(route != get_route(*header),
if (WARN(route != tb_cfg_get_route(header),
"wrong route (expected %llx, got %llx)",
route, get_route(*header)))
route, tb_cfg_get_route(header)))
return -EIO;
return 0;
}
@ -189,8 +207,6 @@ static int check_config_address(struct tb_cfg_address addr,
if (WARN(length != addr.length, "wrong space (expected %x, got %x\n)",
length, addr.length))
return -EIO;
if (WARN(addr.seq, "addr.seq is %#x\n", addr.seq))
return -EIO;
/*
* We cannot check addr->port as it is set to the upstream port of the
* sender.
@ -198,14 +214,14 @@ static int check_config_address(struct tb_cfg_address addr,
return 0;
}
static struct tb_cfg_result decode_error(struct ctl_pkg *response)
static struct tb_cfg_result decode_error(const struct ctl_pkg *response)
{
struct cfg_error_pkg *pkg = response->buffer;
struct tb_cfg_result res = { 0 };
res.response_route = get_route(pkg->header);
res.response_route = tb_cfg_get_route(&pkg->header);
res.response_port = 0;
res.err = check_header(response, sizeof(*pkg), TB_CFG_PKG_ERROR,
get_route(pkg->header));
tb_cfg_get_route(&pkg->header));
if (res.err)
return res;
@ -219,7 +235,7 @@ static struct tb_cfg_result decode_error(struct ctl_pkg *response)
}
static struct tb_cfg_result parse_header(struct ctl_pkg *pkg, u32 len,
static struct tb_cfg_result parse_header(const struct ctl_pkg *pkg, u32 len,
enum tb_cfg_pkg_type type, u64 route)
{
struct tb_cfg_header *header = pkg->buffer;
@ -229,7 +245,7 @@ static struct tb_cfg_result parse_header(struct ctl_pkg *pkg, u32 len,
return decode_error(pkg);
res.response_port = 0; /* will be updated later for cfg_read/write */
res.response_route = get_route(*header);
res.response_route = tb_cfg_get_route(header);
res.err = check_header(pkg, len, type, route);
return res;
}
@ -273,7 +289,7 @@ static void tb_cfg_print_error(struct tb_ctl *ctl,
}
}
static void cpu_to_be32_array(__be32 *dst, u32 *src, size_t len)
static void cpu_to_be32_array(__be32 *dst, const u32 *src, size_t len)
{
int i;
for (i = 0; i < len; i++)
@ -287,7 +303,7 @@ static void be32_to_cpu_array(u32 *dst, __be32 *src, size_t len)
dst[i] = be32_to_cpu(src[i]);
}
static __be32 tb_crc(void *data, size_t len)
static __be32 tb_crc(const void *data, size_t len)
{
return cpu_to_be32(~__crc32c_le(~0, data, len));
}
@ -333,7 +349,7 @@ static void tb_ctl_tx_callback(struct tb_ring *ring, struct ring_frame *frame,
*
* Return: Returns 0 on success or an error code on failure.
*/
static int tb_ctl_tx(struct tb_ctl *ctl, void *data, size_t len,
static int tb_ctl_tx(struct tb_ctl *ctl, const void *data, size_t len,
enum tb_cfg_pkg_type type)
{
int res;
@ -364,24 +380,12 @@ static int tb_ctl_tx(struct tb_ctl *ctl, void *data, size_t len,
}
/**
* tb_ctl_handle_plug_event() - acknowledge a plug event, invoke ctl->callback
* tb_ctl_handle_event() - acknowledge a plug event, invoke ctl->callback
*/
static void tb_ctl_handle_plug_event(struct tb_ctl *ctl,
struct ctl_pkg *response)
static void tb_ctl_handle_event(struct tb_ctl *ctl, enum tb_cfg_pkg_type type,
struct ctl_pkg *pkg, size_t size)
{
struct cfg_event_pkg *pkg = response->buffer;
u64 route = get_route(pkg->header);
if (check_header(response, sizeof(*pkg), TB_CFG_PKG_EVENT, route)) {
tb_ctl_warn(ctl, "malformed TB_CFG_PKG_EVENT\n");
return;
}
if (tb_cfg_error(ctl, route, pkg->port, TB_CFG_ERROR_ACK_PLUG_EVENT))
tb_ctl_warn(ctl, "could not ack plug event on %llx:%x\n",
route, pkg->port);
WARN(pkg->zero, "pkg->zero is %#x\n", pkg->zero);
ctl->callback(ctl->callback_data, route, pkg->port, pkg->unplug);
ctl->callback(ctl->callback_data, type, pkg->buffer, size);
}
static void tb_ctl_rx_submit(struct ctl_pkg *pkg)
@ -394,10 +398,30 @@ static void tb_ctl_rx_submit(struct ctl_pkg *pkg)
*/
}
static int tb_async_error(const struct ctl_pkg *pkg)
{
const struct cfg_error_pkg *error = (const struct cfg_error_pkg *)pkg;
if (pkg->frame.eof != TB_CFG_PKG_ERROR)
return false;
switch (error->error) {
case TB_CFG_ERROR_LINK_ERROR:
case TB_CFG_ERROR_HEC_ERROR_DETECTED:
case TB_CFG_ERROR_FLOW_CONTROL_ERROR:
return true;
default:
return false;
}
}
static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
bool canceled)
{
struct ctl_pkg *pkg = container_of(frame, typeof(*pkg), frame);
struct tb_cfg_request *req;
__be32 crc32;
if (canceled)
return; /*
@ -412,55 +436,168 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame,
}
frame->size -= 4; /* remove checksum */
if (*(__be32 *) (pkg->buffer + frame->size)
!= tb_crc(pkg->buffer, frame->size)) {
tb_ctl_err(pkg->ctl,
"RX: checksum mismatch, dropping packet\n");
goto rx;
}
crc32 = tb_crc(pkg->buffer, frame->size);
be32_to_cpu_array(pkg->buffer, pkg->buffer, frame->size / 4);
if (frame->eof == TB_CFG_PKG_EVENT) {
tb_ctl_handle_plug_event(pkg->ctl, pkg);
switch (frame->eof) {
case TB_CFG_PKG_READ:
case TB_CFG_PKG_WRITE:
case TB_CFG_PKG_ERROR:
case TB_CFG_PKG_OVERRIDE:
case TB_CFG_PKG_RESET:
if (*(__be32 *)(pkg->buffer + frame->size) != crc32) {
tb_ctl_err(pkg->ctl,
"RX: checksum mismatch, dropping packet\n");
goto rx;
}
if (tb_async_error(pkg)) {
tb_ctl_handle_event(pkg->ctl, frame->eof,
pkg, frame->size);
goto rx;
}
break;
case TB_CFG_PKG_EVENT:
if (*(__be32 *)(pkg->buffer + frame->size) != crc32) {
tb_ctl_err(pkg->ctl,
"RX: checksum mismatch, dropping packet\n");
goto rx;
}
/* Fall through */
case TB_CFG_PKG_ICM_EVENT:
tb_ctl_handle_event(pkg->ctl, frame->eof, pkg, frame->size);
goto rx;
default:
break;
}
if (!kfifo_put(&pkg->ctl->response_fifo, pkg)) {
tb_ctl_err(pkg->ctl, "RX: fifo is full\n");
goto rx;
/*
* The received packet will be processed only if there is an
* active request and that the packet is what is expected. This
* prevents packets such as replies coming after timeout has
* triggered from messing with the active requests.
*/
req = tb_cfg_request_find(pkg->ctl, pkg);
if (req) {
if (req->copy(req, pkg))
schedule_work(&req->work);
tb_cfg_request_put(req);
}
complete(&pkg->ctl->response_ready);
return;
rx:
tb_ctl_rx_submit(pkg);
}
/**
* tb_ctl_rx() - receive a packet from the control channel
*/
static struct tb_cfg_result tb_ctl_rx(struct tb_ctl *ctl, void *buffer,
size_t length, int timeout_msec,
u64 route, enum tb_cfg_pkg_type type)
static void tb_cfg_request_work(struct work_struct *work)
{
struct tb_cfg_result res;
struct ctl_pkg *pkg;
struct tb_cfg_request *req = container_of(work, typeof(*req), work);
if (!wait_for_completion_timeout(&ctl->response_ready,
msecs_to_jiffies(timeout_msec))) {
tb_ctl_WARN(ctl, "RX: timeout\n");
return (struct tb_cfg_result) { .err = -ETIMEDOUT };
}
if (!kfifo_get(&ctl->response_fifo, &pkg)) {
tb_ctl_WARN(ctl, "empty kfifo\n");
return (struct tb_cfg_result) { .err = -EIO };
}
if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags))
req->callback(req->callback_data);
res = parse_header(pkg, length, type, route);
if (!res.err)
memcpy(buffer, pkg->buffer, length);
tb_ctl_rx_submit(pkg);
return res;
tb_cfg_request_dequeue(req);
tb_cfg_request_put(req);
}
/**
* tb_cfg_request() - Start control request not waiting for it to complete
* @ctl: Control channel to use
* @req: Request to start
* @callback: Callback called when the request is completed
* @callback_data: Data to be passed to @callback
*
* This queues @req on the given control channel without waiting for it
* to complete. When the request completes @callback is called.
*/
int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
void (*callback)(void *), void *callback_data)
{
int ret;
req->flags = 0;
req->callback = callback;
req->callback_data = callback_data;
INIT_WORK(&req->work, tb_cfg_request_work);
INIT_LIST_HEAD(&req->list);
tb_cfg_request_get(req);
ret = tb_cfg_request_enqueue(ctl, req);
if (ret)
goto err_put;
ret = tb_ctl_tx(ctl, req->request, req->request_size,
req->request_type);
if (ret)
goto err_dequeue;
if (!req->response)
schedule_work(&req->work);
return 0;
err_dequeue:
tb_cfg_request_dequeue(req);
err_put:
tb_cfg_request_put(req);
return ret;
}
/**
* tb_cfg_request_cancel() - Cancel a control request
* @req: Request to cancel
* @err: Error to assign to the request
*
* This function can be used to cancel ongoing request. It will wait
* until the request is not active anymore.
*/
void tb_cfg_request_cancel(struct tb_cfg_request *req, int err)
{
set_bit(TB_CFG_REQUEST_CANCELED, &req->flags);
schedule_work(&req->work);
wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req));
req->result.err = err;
}
static void tb_cfg_request_complete(void *data)
{
complete(data);
}
/**
* tb_cfg_request_sync() - Start control request and wait until it completes
* @ctl: Control channel to use
* @req: Request to start
* @timeout_msec: Timeout how long to wait @req to complete
*
* Starts a control request and waits until it completes. If timeout
* triggers the request is canceled before function returns. Note the
* caller needs to make sure only one message for given switch is active
* at a time.
*/
struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
struct tb_cfg_request *req,
int timeout_msec)
{
unsigned long timeout = msecs_to_jiffies(timeout_msec);
struct tb_cfg_result res = { 0 };
DECLARE_COMPLETION_ONSTACK(done);
int ret;
ret = tb_cfg_request(ctl, req, tb_cfg_request_complete, &done);
if (ret) {
res.err = ret;
return res;
}
if (!wait_for_completion_timeout(&done, timeout))
tb_cfg_request_cancel(req, -ETIMEDOUT);
flush_work(&req->work);
return req->result;
}
/* public interface, alloc/start/stop/free */
@ -471,7 +608,7 @@ static struct tb_cfg_result tb_ctl_rx(struct tb_ctl *ctl, void *buffer,
*
* Return: Returns a pointer on success or NULL on failure.
*/
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, hotplug_cb cb, void *cb_data)
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data)
{
int i;
struct tb_ctl *ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
@ -481,18 +618,18 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, hotplug_cb cb, void *cb_data)
ctl->callback = cb;
ctl->callback_data = cb_data;
init_completion(&ctl->response_ready);
INIT_KFIFO(ctl->response_fifo);
mutex_init(&ctl->request_queue_lock);
INIT_LIST_HEAD(&ctl->request_queue);
ctl->frame_pool = dma_pool_create("thunderbolt_ctl", &nhi->pdev->dev,
TB_FRAME_SIZE, 4, 0);
if (!ctl->frame_pool)
goto err;
ctl->tx = ring_alloc_tx(nhi, 0, 10);
ctl->tx = ring_alloc_tx(nhi, 0, 10, RING_FLAG_NO_SUSPEND);
if (!ctl->tx)
goto err;
ctl->rx = ring_alloc_rx(nhi, 0, 10);
ctl->rx = ring_alloc_rx(nhi, 0, 10, RING_FLAG_NO_SUSPEND);
if (!ctl->rx)
goto err;
@ -520,6 +657,10 @@ err:
void tb_ctl_free(struct tb_ctl *ctl)
{
int i;
if (!ctl)
return;
if (ctl->rx)
ring_free(ctl->rx);
if (ctl->tx)
@ -546,6 +687,8 @@ void tb_ctl_start(struct tb_ctl *ctl)
ring_start(ctl->rx);
for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++)
tb_ctl_rx_submit(ctl->rx_packets[i]);
ctl->running = true;
}
/**
@ -558,12 +701,16 @@ void tb_ctl_start(struct tb_ctl *ctl)
*/
void tb_ctl_stop(struct tb_ctl *ctl)
{
mutex_lock(&ctl->request_queue_lock);
ctl->running = false;
mutex_unlock(&ctl->request_queue_lock);
ring_stop(ctl->rx);
ring_stop(ctl->tx);
if (!kfifo_is_empty(&ctl->response_fifo))
tb_ctl_WARN(ctl, "dangling response in response_fifo\n");
kfifo_reset(&ctl->response_fifo);
if (!list_empty(&ctl->request_queue))
tb_ctl_WARN(ctl, "dangling request in request_queue\n");
INIT_LIST_HEAD(&ctl->request_queue);
tb_ctl_info(ctl, "control channel stopped\n");
}
@ -578,7 +725,7 @@ int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
enum tb_cfg_error error)
{
struct cfg_error_pkg pkg = {
.header = make_header(route),
.header = tb_cfg_make_header(route),
.port = port,
.error = error,
};
@ -586,6 +733,49 @@ int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_ERROR);
}
static bool tb_cfg_match(const struct tb_cfg_request *req,
const struct ctl_pkg *pkg)
{
u64 route = tb_cfg_get_route(pkg->buffer) & ~BIT_ULL(63);
if (pkg->frame.eof == TB_CFG_PKG_ERROR)
return true;
if (pkg->frame.eof != req->response_type)
return false;
if (route != tb_cfg_get_route(req->request))
return false;
if (pkg->frame.size != req->response_size)
return false;
if (pkg->frame.eof == TB_CFG_PKG_READ ||
pkg->frame.eof == TB_CFG_PKG_WRITE) {
const struct cfg_read_pkg *req_hdr = req->request;
const struct cfg_read_pkg *res_hdr = pkg->buffer;
if (req_hdr->addr.seq != res_hdr->addr.seq)
return false;
}
return true;
}
static bool tb_cfg_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
{
struct tb_cfg_result res;
/* Now make sure it is in expected format */
res = parse_header(pkg, req->response_size, req->response_type,
tb_cfg_get_route(req->request));
if (!res.err)
memcpy(req->response, pkg->buffer, req->response_size);
req->result = res;
/* Always complete when first response is received */
return true;
}
/**
* tb_cfg_reset() - send a reset packet and wait for a response
*
@ -596,16 +786,31 @@ int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
int timeout_msec)
{
int err;
struct cfg_reset_pkg request = { .header = make_header(route) };
struct cfg_reset_pkg request = { .header = tb_cfg_make_header(route) };
struct tb_cfg_result res = { 0 };
struct tb_cfg_header reply;
struct tb_cfg_request *req;
err = tb_ctl_tx(ctl, &request, sizeof(request), TB_CFG_PKG_RESET);
if (err)
return (struct tb_cfg_result) { .err = err };
req = tb_cfg_request_alloc();
if (!req) {
res.err = -ENOMEM;
return res;
}
return tb_ctl_rx(ctl, &reply, sizeof(reply), timeout_msec, route,
TB_CFG_PKG_RESET);
req->match = tb_cfg_match;
req->copy = tb_cfg_copy;
req->request = &request;
req->request_size = sizeof(request);
req->request_type = TB_CFG_PKG_RESET;
req->response = &reply;
req->response_size = sizeof(reply);
req->response_type = sizeof(TB_CFG_PKG_RESET);
res = tb_cfg_request_sync(ctl, req, timeout_msec);
tb_cfg_request_put(req);
return res;
}
/**
@ -619,7 +824,7 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
{
struct tb_cfg_result res = { 0 };
struct cfg_read_pkg request = {
.header = make_header(route),
.header = tb_cfg_make_header(route),
.addr = {
.port = port,
.space = space,
@ -628,13 +833,39 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
},
};
struct cfg_write_pkg reply;
int retries = 0;
res.err = tb_ctl_tx(ctl, &request, sizeof(request), TB_CFG_PKG_READ);
if (res.err)
return res;
while (retries < TB_CTL_RETRIES) {
struct tb_cfg_request *req;
req = tb_cfg_request_alloc();
if (!req) {
res.err = -ENOMEM;
return res;
}
request.addr.seq = retries++;
req->match = tb_cfg_match;
req->copy = tb_cfg_copy;
req->request = &request;
req->request_size = sizeof(request);
req->request_type = TB_CFG_PKG_READ;
req->response = &reply;
req->response_size = 12 + 4 * length;
req->response_type = TB_CFG_PKG_READ;
res = tb_cfg_request_sync(ctl, req, timeout_msec);
tb_cfg_request_put(req);
if (res.err != -ETIMEDOUT)
break;
/* Wait a bit (arbitrary time) until we send a retry */
usleep_range(10, 100);
}
res = tb_ctl_rx(ctl, &reply, 12 + 4 * length, timeout_msec, route,
TB_CFG_PKG_READ);
if (res.err)
return res;
@ -650,13 +881,13 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
*
* Offset and length are in dwords.
*/
struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, void *buffer,
struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, const void *buffer,
u64 route, u32 port, enum tb_cfg_space space,
u32 offset, u32 length, int timeout_msec)
{
struct tb_cfg_result res = { 0 };
struct cfg_write_pkg request = {
.header = make_header(route),
.header = tb_cfg_make_header(route),
.addr = {
.port = port,
.space = space,
@ -665,15 +896,41 @@ struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, void *buffer,
},
};
struct cfg_read_pkg reply;
int retries = 0;
memcpy(&request.data, buffer, length * 4);
res.err = tb_ctl_tx(ctl, &request, 12 + 4 * length, TB_CFG_PKG_WRITE);
if (res.err)
return res;
while (retries < TB_CTL_RETRIES) {
struct tb_cfg_request *req;
req = tb_cfg_request_alloc();
if (!req) {
res.err = -ENOMEM;
return res;
}
request.addr.seq = retries++;
req->match = tb_cfg_match;
req->copy = tb_cfg_copy;
req->request = &request;
req->request_size = 12 + 4 * length;
req->request_type = TB_CFG_PKG_WRITE;
req->response = &reply;
req->response_size = sizeof(reply);
req->response_type = TB_CFG_PKG_WRITE;
res = tb_cfg_request_sync(ctl, req, timeout_msec);
tb_cfg_request_put(req);
if (res.err != -ETIMEDOUT)
break;
/* Wait a bit (arbitrary time) until we send a retry */
usleep_range(10, 100);
}
res = tb_ctl_rx(ctl, &reply, sizeof(reply), timeout_msec, route,
TB_CFG_PKG_WRITE);
if (res.err)
return res;
@ -687,24 +944,52 @@ int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
{
struct tb_cfg_result res = tb_cfg_read_raw(ctl, buffer, route, port,
space, offset, length, TB_CFG_DEFAULT_TIMEOUT);
if (res.err == 1) {
switch (res.err) {
case 0:
/* Success */
break;
case 1:
/* Thunderbolt error, tb_error holds the actual number */
tb_cfg_print_error(ctl, &res);
return -EIO;
case -ETIMEDOUT:
tb_ctl_warn(ctl, "timeout reading config space %u from %#x\n",
space, offset);
break;
default:
WARN(1, "tb_cfg_read: %d\n", res.err);
break;
}
WARN(res.err, "tb_cfg_read: %d\n", res.err);
return res.err;
}
int tb_cfg_write(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port,
enum tb_cfg_space space, u32 offset, u32 length)
{
struct tb_cfg_result res = tb_cfg_write_raw(ctl, buffer, route, port,
space, offset, length, TB_CFG_DEFAULT_TIMEOUT);
if (res.err == 1) {
switch (res.err) {
case 0:
/* Success */
break;
case 1:
/* Thunderbolt error, tb_error holds the actual number */
tb_cfg_print_error(ctl, &res);
return -EIO;
case -ETIMEDOUT:
tb_ctl_warn(ctl, "timeout writing config space %u to %#x\n",
space, offset);
break;
default:
WARN(1, "tb_cfg_write: %d\n", res.err);
break;
}
WARN(res.err, "tb_cfg_write: %d\n", res.err);
return res.err;
}

View File

@ -7,14 +7,18 @@
#ifndef _TB_CFG
#define _TB_CFG
#include <linux/kref.h>
#include "nhi.h"
#include "tb_msgs.h"
/* control channel */
struct tb_ctl;
typedef void (*hotplug_cb)(void *data, u64 route, u8 port, bool unplug);
typedef void (*event_cb)(void *data, enum tb_cfg_pkg_type type,
const void *buf, size_t size);
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, hotplug_cb cb, void *cb_data);
struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data);
void tb_ctl_start(struct tb_ctl *ctl);
void tb_ctl_stop(struct tb_ctl *ctl);
void tb_ctl_free(struct tb_ctl *ctl);
@ -23,21 +27,6 @@ void tb_ctl_free(struct tb_ctl *ctl);
#define TB_CFG_DEFAULT_TIMEOUT 5000 /* msec */
enum tb_cfg_space {
TB_CFG_HOPS = 0,
TB_CFG_PORT = 1,
TB_CFG_SWITCH = 2,
TB_CFG_COUNTERS = 3,
};
enum tb_cfg_error {
TB_CFG_ERROR_PORT_NOT_CONNECTED = 0,
TB_CFG_ERROR_INVALID_CONFIG_SPACE = 2,
TB_CFG_ERROR_NO_SUCH_PORT = 4,
TB_CFG_ERROR_ACK_PLUG_EVENT = 7, /* send as reply to TB_CFG_PKG_EVENT */
TB_CFG_ERROR_LOOP = 8,
};
struct tb_cfg_result {
u64 response_route;
u32 response_port; /*
@ -52,6 +41,84 @@ struct tb_cfg_result {
enum tb_cfg_error tb_error; /* valid if err == 1 */
};
struct ctl_pkg {
struct tb_ctl *ctl;
void *buffer;
struct ring_frame frame;
};
/**
* struct tb_cfg_request - Control channel request
* @kref: Reference count
* @ctl: Pointer to the control channel structure. Only set when the
* request is queued.
* @request_size: Size of the request packet (in bytes)
* @request_type: Type of the request packet
* @response: Response is stored here
* @response_size: Maximum size of one response packet
* @response_type: Expected type of the response packet
* @npackets: Number of packets expected to be returned with this request
* @match: Function used to match the incoming packet
* @copy: Function used to copy the incoming packet to @response
* @callback: Callback called when the request is finished successfully
* @callback_data: Data to be passed to @callback
* @flags: Flags for the request
* @work: Work item used to complete the request
* @result: Result after the request has been completed
* @list: Requests are queued using this field
*
* An arbitrary request over Thunderbolt control channel. For standard
* control channel message, one should use tb_cfg_read/write() and
* friends if possible.
*/
struct tb_cfg_request {
struct kref kref;
struct tb_ctl *ctl;
const void *request;
size_t request_size;
enum tb_cfg_pkg_type request_type;
void *response;
size_t response_size;
enum tb_cfg_pkg_type response_type;
size_t npackets;
bool (*match)(const struct tb_cfg_request *req,
const struct ctl_pkg *pkg);
bool (*copy)(struct tb_cfg_request *req, const struct ctl_pkg *pkg);
void (*callback)(void *callback_data);
void *callback_data;
unsigned long flags;
struct work_struct work;
struct tb_cfg_result result;
struct list_head list;
};
#define TB_CFG_REQUEST_ACTIVE 0
#define TB_CFG_REQUEST_CANCELED 1
struct tb_cfg_request *tb_cfg_request_alloc(void);
void tb_cfg_request_get(struct tb_cfg_request *req);
void tb_cfg_request_put(struct tb_cfg_request *req);
int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req,
void (*callback)(void *), void *callback_data);
void tb_cfg_request_cancel(struct tb_cfg_request *req, int err);
struct tb_cfg_result tb_cfg_request_sync(struct tb_ctl *ctl,
struct tb_cfg_request *req, int timeout_msec);
static inline u64 tb_cfg_get_route(const struct tb_cfg_header *header)
{
return (u64) header->route_hi << 32 | header->route_lo;
}
static inline struct tb_cfg_header tb_cfg_make_header(u64 route)
{
struct tb_cfg_header header = {
.route_hi = route >> 32,
.route_lo = route,
};
/* check for overflow, route_hi is not 32 bits! */
WARN_ON(tb_cfg_get_route(&header) != route);
return header;
}
int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
enum tb_cfg_error error);
@ -61,13 +128,13 @@ struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
u64 route, u32 port,
enum tb_cfg_space space, u32 offset,
u32 length, int timeout_msec);
struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, void *buffer,
struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, const void *buffer,
u64 route, u32 port,
enum tb_cfg_space space, u32 offset,
u32 length, int timeout_msec);
int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
enum tb_cfg_space space, u32 offset, u32 length);
int tb_cfg_write(struct tb_ctl *ctl, void *buffer, u64 route, u32 port,
int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port,
enum tb_cfg_space space, u32 offset, u32 length);
int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route);

View File

@ -0,0 +1,524 @@
/*
* Thunderbolt DMA configuration based mailbox support
*
* Copyright (C) 2017, Intel Corporation
* Authors: Michael Jamet <michael.jamet@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include "dma_port.h"
#include "tb_regs.h"
#define DMA_PORT_CAP 0x3e
#define MAIL_DATA 1
#define MAIL_DATA_DWORDS 16
#define MAIL_IN 17
#define MAIL_IN_CMD_SHIFT 28
#define MAIL_IN_CMD_MASK GENMASK(31, 28)
#define MAIL_IN_CMD_FLASH_WRITE 0x0
#define MAIL_IN_CMD_FLASH_UPDATE_AUTH 0x1
#define MAIL_IN_CMD_FLASH_READ 0x2
#define MAIL_IN_CMD_POWER_CYCLE 0x4
#define MAIL_IN_DWORDS_SHIFT 24
#define MAIL_IN_DWORDS_MASK GENMASK(27, 24)
#define MAIL_IN_ADDRESS_SHIFT 2
#define MAIL_IN_ADDRESS_MASK GENMASK(23, 2)
#define MAIL_IN_CSS BIT(1)
#define MAIL_IN_OP_REQUEST BIT(0)
#define MAIL_OUT 18
#define MAIL_OUT_STATUS_RESPONSE BIT(29)
#define MAIL_OUT_STATUS_CMD_SHIFT 4
#define MAIL_OUT_STATUS_CMD_MASK GENMASK(7, 4)
#define MAIL_OUT_STATUS_MASK GENMASK(3, 0)
#define MAIL_OUT_STATUS_COMPLETED 0
#define MAIL_OUT_STATUS_ERR_AUTH 1
#define MAIL_OUT_STATUS_ERR_ACCESS 2
#define DMA_PORT_TIMEOUT 5000 /* ms */
#define DMA_PORT_RETRIES 3
/**
* struct tb_dma_port - DMA control port
* @sw: Switch the DMA port belongs to
* @port: Switch port number where DMA capability is found
* @base: Start offset of the mailbox registers
* @buf: Temporary buffer to store a single block
*/
struct tb_dma_port {
struct tb_switch *sw;
u8 port;
u32 base;
u8 *buf;
};
/*
* When the switch is in safe mode it supports very little functionality
* so we don't validate that much here.
*/
static bool dma_port_match(const struct tb_cfg_request *req,
const struct ctl_pkg *pkg)
{
u64 route = tb_cfg_get_route(pkg->buffer) & ~BIT_ULL(63);
if (pkg->frame.eof == TB_CFG_PKG_ERROR)
return true;
if (pkg->frame.eof != req->response_type)
return false;
if (route != tb_cfg_get_route(req->request))
return false;
if (pkg->frame.size != req->response_size)
return false;
return true;
}
static bool dma_port_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
{
memcpy(req->response, pkg->buffer, req->response_size);
return true;
}
static int dma_port_read(struct tb_ctl *ctl, void *buffer, u64 route,
u32 port, u32 offset, u32 length, int timeout_msec)
{
struct cfg_read_pkg request = {
.header = tb_cfg_make_header(route),
.addr = {
.seq = 1,
.port = port,
.space = TB_CFG_PORT,
.offset = offset,
.length = length,
},
};
struct tb_cfg_request *req;
struct cfg_write_pkg reply;
struct tb_cfg_result res;
req = tb_cfg_request_alloc();
if (!req)
return -ENOMEM;
req->match = dma_port_match;
req->copy = dma_port_copy;
req->request = &request;
req->request_size = sizeof(request);
req->request_type = TB_CFG_PKG_READ;
req->response = &reply;
req->response_size = 12 + 4 * length;
req->response_type = TB_CFG_PKG_READ;
res = tb_cfg_request_sync(ctl, req, timeout_msec);
tb_cfg_request_put(req);
if (res.err)
return res.err;
memcpy(buffer, &reply.data, 4 * length);
return 0;
}
static int dma_port_write(struct tb_ctl *ctl, const void *buffer, u64 route,
u32 port, u32 offset, u32 length, int timeout_msec)
{
struct cfg_write_pkg request = {
.header = tb_cfg_make_header(route),
.addr = {
.seq = 1,
.port = port,
.space = TB_CFG_PORT,
.offset = offset,
.length = length,
},
};
struct tb_cfg_request *req;
struct cfg_read_pkg reply;
struct tb_cfg_result res;
memcpy(&request.data, buffer, length * 4);
req = tb_cfg_request_alloc();
if (!req)
return -ENOMEM;
req->match = dma_port_match;
req->copy = dma_port_copy;
req->request = &request;
req->request_size = 12 + 4 * length;
req->request_type = TB_CFG_PKG_WRITE;
req->response = &reply;
req->response_size = sizeof(reply);
req->response_type = TB_CFG_PKG_WRITE;
res = tb_cfg_request_sync(ctl, req, timeout_msec);
tb_cfg_request_put(req);
return res.err;
}
static int dma_find_port(struct tb_switch *sw)
{
int port, ret;
u32 type;
/*
* The DMA (NHI) port is either 3 or 5 depending on the
* controller. Try both starting from 5 which is more common.
*/
port = 5;
ret = dma_port_read(sw->tb->ctl, &type, tb_route(sw), port, 2, 1,
DMA_PORT_TIMEOUT);
if (!ret && (type & 0xffffff) == TB_TYPE_NHI)
return port;
port = 3;
ret = dma_port_read(sw->tb->ctl, &type, tb_route(sw), port, 2, 1,
DMA_PORT_TIMEOUT);
if (!ret && (type & 0xffffff) == TB_TYPE_NHI)
return port;
return -ENODEV;
}
/**
* dma_port_alloc() - Finds DMA control port from a switch pointed by route
* @sw: Switch from where find the DMA port
*
* Function checks if the switch NHI port supports DMA configuration
* based mailbox capability and if it does, allocates and initializes
* DMA port structure. Returns %NULL if the capabity was not found.
*
* The DMA control port is functional also when the switch is in safe
* mode.
*/
struct tb_dma_port *dma_port_alloc(struct tb_switch *sw)
{
struct tb_dma_port *dma;
int port;
port = dma_find_port(sw);
if (port < 0)
return NULL;
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
if (!dma)
return NULL;
dma->buf = kmalloc_array(MAIL_DATA_DWORDS, sizeof(u32), GFP_KERNEL);
if (!dma->buf) {
kfree(dma);
return NULL;
}
dma->sw = sw;
dma->port = port;
dma->base = DMA_PORT_CAP;
return dma;
}
/**
* dma_port_free() - Release DMA control port structure
* @dma: DMA control port
*/
void dma_port_free(struct tb_dma_port *dma)
{
if (dma) {
kfree(dma->buf);
kfree(dma);
}
}
static int dma_port_wait_for_completion(struct tb_dma_port *dma,
unsigned int timeout)
{
unsigned long end = jiffies + msecs_to_jiffies(timeout);
struct tb_switch *sw = dma->sw;
do {
int ret;
u32 in;
ret = dma_port_read(sw->tb->ctl, &in, tb_route(sw), dma->port,
dma->base + MAIL_IN, 1, 50);
if (ret) {
if (ret != -ETIMEDOUT)
return ret;
} else if (!(in & MAIL_IN_OP_REQUEST)) {
return 0;
}
usleep_range(50, 100);
} while (time_before(jiffies, end));
return -ETIMEDOUT;
}
static int status_to_errno(u32 status)
{
switch (status & MAIL_OUT_STATUS_MASK) {
case MAIL_OUT_STATUS_COMPLETED:
return 0;
case MAIL_OUT_STATUS_ERR_AUTH:
return -EINVAL;
case MAIL_OUT_STATUS_ERR_ACCESS:
return -EACCES;
}
return -EIO;
}
static int dma_port_request(struct tb_dma_port *dma, u32 in,
unsigned int timeout)
{
struct tb_switch *sw = dma->sw;
u32 out;
int ret;
ret = dma_port_write(sw->tb->ctl, &in, tb_route(sw), dma->port,
dma->base + MAIL_IN, 1, DMA_PORT_TIMEOUT);
if (ret)
return ret;
ret = dma_port_wait_for_completion(dma, timeout);
if (ret)
return ret;
ret = dma_port_read(sw->tb->ctl, &out, tb_route(sw), dma->port,
dma->base + MAIL_OUT, 1, DMA_PORT_TIMEOUT);
if (ret)
return ret;
return status_to_errno(out);
}
static int dma_port_flash_read_block(struct tb_dma_port *dma, u32 address,
void *buf, u32 size)
{
struct tb_switch *sw = dma->sw;
u32 in, dwaddress, dwords;
int ret;
dwaddress = address / 4;
dwords = size / 4;
in = MAIL_IN_CMD_FLASH_READ << MAIL_IN_CMD_SHIFT;
if (dwords < MAIL_DATA_DWORDS)
in |= (dwords << MAIL_IN_DWORDS_SHIFT) & MAIL_IN_DWORDS_MASK;
in |= (dwaddress << MAIL_IN_ADDRESS_SHIFT) & MAIL_IN_ADDRESS_MASK;
in |= MAIL_IN_OP_REQUEST;
ret = dma_port_request(dma, in, DMA_PORT_TIMEOUT);
if (ret)
return ret;
return dma_port_read(sw->tb->ctl, buf, tb_route(sw), dma->port,
dma->base + MAIL_DATA, dwords, DMA_PORT_TIMEOUT);
}
static int dma_port_flash_write_block(struct tb_dma_port *dma, u32 address,
const void *buf, u32 size)
{
struct tb_switch *sw = dma->sw;
u32 in, dwaddress, dwords;
int ret;
dwords = size / 4;
/* Write the block to MAIL_DATA registers */
ret = dma_port_write(sw->tb->ctl, buf, tb_route(sw), dma->port,
dma->base + MAIL_DATA, dwords, DMA_PORT_TIMEOUT);
in = MAIL_IN_CMD_FLASH_WRITE << MAIL_IN_CMD_SHIFT;
/* CSS header write is always done to the same magic address */
if (address >= DMA_PORT_CSS_ADDRESS) {
dwaddress = DMA_PORT_CSS_ADDRESS;
in |= MAIL_IN_CSS;
} else {
dwaddress = address / 4;
}
in |= ((dwords - 1) << MAIL_IN_DWORDS_SHIFT) & MAIL_IN_DWORDS_MASK;
in |= (dwaddress << MAIL_IN_ADDRESS_SHIFT) & MAIL_IN_ADDRESS_MASK;
in |= MAIL_IN_OP_REQUEST;
return dma_port_request(dma, in, DMA_PORT_TIMEOUT);
}
/**
* dma_port_flash_read() - Read from active flash region
* @dma: DMA control port
* @address: Address relative to the start of active region
* @buf: Buffer where the data is read
* @size: Size of the buffer
*/
int dma_port_flash_read(struct tb_dma_port *dma, unsigned int address,
void *buf, size_t size)
{
unsigned int retries = DMA_PORT_RETRIES;
unsigned int offset;
offset = address & 3;
address = address & ~3;
do {
u32 nbytes = min_t(u32, size, MAIL_DATA_DWORDS * 4);
int ret;
ret = dma_port_flash_read_block(dma, address, dma->buf,
ALIGN(nbytes, 4));
if (ret) {
if (ret == -ETIMEDOUT) {
if (retries--)
continue;
ret = -EIO;
}
return ret;
}
memcpy(buf, dma->buf + offset, nbytes);
size -= nbytes;
address += nbytes;
buf += nbytes;
} while (size > 0);
return 0;
}
/**
* dma_port_flash_write() - Write to non-active flash region
* @dma: DMA control port
* @address: Address relative to the start of non-active region
* @buf: Data to write
* @size: Size of the buffer
*
* Writes block of data to the non-active flash region of the switch. If
* the address is given as %DMA_PORT_CSS_ADDRESS the block is written
* using CSS command.
*/
int dma_port_flash_write(struct tb_dma_port *dma, unsigned int address,
const void *buf, size_t size)
{
unsigned int retries = DMA_PORT_RETRIES;
unsigned int offset;
if (address >= DMA_PORT_CSS_ADDRESS) {
offset = 0;
if (size > DMA_PORT_CSS_MAX_SIZE)
return -E2BIG;
} else {
offset = address & 3;
address = address & ~3;
}
do {
u32 nbytes = min_t(u32, size, MAIL_DATA_DWORDS * 4);
int ret;
memcpy(dma->buf + offset, buf, nbytes);
ret = dma_port_flash_write_block(dma, address, buf, nbytes);
if (ret) {
if (ret == -ETIMEDOUT) {
if (retries--)
continue;
ret = -EIO;
}
return ret;
}
size -= nbytes;
address += nbytes;
buf += nbytes;
} while (size > 0);
return 0;
}
/**
* dma_port_flash_update_auth() - Starts flash authenticate cycle
* @dma: DMA control port
*
* Starts the flash update authentication cycle. If the image in the
* non-active area was valid, the switch starts upgrade process where
* active and non-active area get swapped in the end. Caller should call
* dma_port_flash_update_auth_status() to get status of this command.
* This is because if the switch in question is root switch the
* thunderbolt host controller gets reset as well.
*/
int dma_port_flash_update_auth(struct tb_dma_port *dma)
{
u32 in;
in = MAIL_IN_CMD_FLASH_UPDATE_AUTH << MAIL_IN_CMD_SHIFT;
in |= MAIL_IN_OP_REQUEST;
return dma_port_request(dma, in, 150);
}
/**
* dma_port_flash_update_auth_status() - Reads status of update auth command
* @dma: DMA control port
* @status: Status code of the operation
*
* The function checks if there is status available from the last update
* auth command. Returns %0 if there is no status and no further
* action is required. If there is status, %1 is returned instead and
* @status holds the failure code.
*
* Negative return means there was an error reading status from the
* switch.
*/
int dma_port_flash_update_auth_status(struct tb_dma_port *dma, u32 *status)
{
struct tb_switch *sw = dma->sw;
u32 out, cmd;
int ret;
ret = dma_port_read(sw->tb->ctl, &out, tb_route(sw), dma->port,
dma->base + MAIL_OUT, 1, DMA_PORT_TIMEOUT);
if (ret)
return ret;
/* Check if the status relates to flash update auth */
cmd = (out & MAIL_OUT_STATUS_CMD_MASK) >> MAIL_OUT_STATUS_CMD_SHIFT;
if (cmd == MAIL_IN_CMD_FLASH_UPDATE_AUTH) {
if (status)
*status = out & MAIL_OUT_STATUS_MASK;
/* Reset is needed in any case */
return 1;
}
return 0;
}
/**
* dma_port_power_cycle() - Power cycles the switch
* @dma: DMA control port
*
* Triggers power cycle to the switch.
*/
int dma_port_power_cycle(struct tb_dma_port *dma)
{
u32 in;
in = MAIL_IN_CMD_POWER_CYCLE << MAIL_IN_CMD_SHIFT;
in |= MAIL_IN_OP_REQUEST;
return dma_port_request(dma, in, 150);
}

Some files were not shown because too many files have changed in this diff Show More