mirror of https://gitee.com/openkylin/linux.git
char/misc patches for 4.12-rc1
Here is the big set of new char/misc driver drivers and features for 4.12-rc1. There's lots of new drivers added this time around, new firmware drivers from Google, more auxdisplay drivers, extcon drivers, fpga drivers, and a bunch of other driver updates. Nothing major, except if you happen to have the hardware for these drivers, and then you will be happy :) All of these have been in linux-next for a while with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCWQvAgg8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+yknsACgzkAeyz16Z97J3UTaeejbR7nKUCAAoKY4WEHY 8O9f9pr9gj8GMBwxeZQa =OIfB -----END PGP SIGNATURE----- Merge tag 'char-misc-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc Pull char/misc driver updates from Greg KH: "Here is the big set of new char/misc driver drivers and features for 4.12-rc1. There's lots of new drivers added this time around, new firmware drivers from Google, more auxdisplay drivers, extcon drivers, fpga drivers, and a bunch of other driver updates. Nothing major, except if you happen to have the hardware for these drivers, and then you will be happy :) All of these have been in linux-next for a while with no reported issues" * tag 'char-misc-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (136 commits) firmware: google memconsole: Fix return value check in platform_memconsole_init() firmware: Google VPD: Fix return value check in vpd_platform_init() goldfish_pipe: fix build warning about using too much stack. goldfish_pipe: An implementation of more parallel pipe fpga fr br: update supported version numbers fpga: region: release FPGA region reference in error path fpga altera-hps2fpga: disable/unprepare clock on error in alt_fpga_bridge_probe() mei: drop the TODO from samples firmware: Google VPD sysfs driver firmware: Google VPD: import lib_vpd source files misc: lkdtm: Add volatile to intentional NULL pointer reference eeprom: idt_89hpesx: Add OF device ID table misc: ds1682: Add OF device ID table misc: tsl2550: Add OF device ID table w1: Remove unneeded use of assert() and remove w1_log.h w1: Use kernel common min() implementation uio_mf624: Align memory regions to page size and set correct offsets uio_mf624: Refactor memory info initialization uio: Allow handling of non page-aligned memory regions hangcheck-timer: Fix typo in comment ...
This commit is contained in:
commit
af82455f7d
|
@ -128,9 +128,6 @@
|
|||
</sect1>
|
||||
<sect1 id="Device_model_support"><title>Device model support</title>
|
||||
!Idrivers/rapidio/rio-driver.c
|
||||
</sect1>
|
||||
<sect1 id="Sysfs_support"><title>Sysfs support</title>
|
||||
!Idrivers/rapidio/rio-sysfs.c
|
||||
</sect1>
|
||||
<sect1 id="PPC32_support"><title>PPC32 support</title>
|
||||
!Iarch/powerpc/sysdev/fsl_rio.c
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
DT bindings for the Hitachi HD44780 Character LCD Controller
|
||||
|
||||
The Hitachi HD44780 Character LCD Controller is commonly used on character LCDs
|
||||
that can display one or more lines of text. It exposes an M6800 bus interface,
|
||||
which can be used in either 4-bit or 8-bit mode.
|
||||
|
||||
Required properties:
|
||||
- compatible: Must contain "hit,hd44780",
|
||||
- data-gpios: Must contain an array of either 4 or 8 GPIO specifiers,
|
||||
referring to the GPIO pins connected to the data signal lines DB0-DB7
|
||||
(8-bit mode) or DB4-DB7 (4-bit mode) of the LCD Controller's bus interface,
|
||||
- enable-gpios: Must contain a GPIO specifier, referring to the GPIO pin
|
||||
connected to the "E" (Enable) signal line of the LCD Controller's bus
|
||||
interface,
|
||||
- rs-gpios: Must contain a GPIO specifier, referring to the GPIO pin
|
||||
connected to the "RS" (Register Select) signal line of the LCD Controller's
|
||||
bus interface,
|
||||
- display-height-chars: Height of the display, in character cells,
|
||||
- display-width-chars: Width of the display, in character cells.
|
||||
|
||||
Optional properties:
|
||||
- rw-gpios: Must contain a GPIO specifier, referring to the GPIO pin
|
||||
connected to the "RW" (Read/Write) signal line of the LCD Controller's bus
|
||||
interface,
|
||||
- backlight-gpios: Must contain a GPIO specifier, referring to the GPIO pin
|
||||
used for enabling the LCD's backlight,
|
||||
- internal-buffer-width: Internal buffer width (default is 40 for displays
|
||||
with 1 or 2 lines, and display-width-chars for displays with more than 2
|
||||
lines).
|
||||
|
||||
Example:
|
||||
|
||||
auxdisplay {
|
||||
compatible = "hit,hd44780";
|
||||
|
||||
data-gpios = <&hc595 0 GPIO_ACTIVE_HIGH>,
|
||||
<&hc595 1 GPIO_ACTIVE_HIGH>,
|
||||
<&hc595 2 GPIO_ACTIVE_HIGH>,
|
||||
<&hc595 3 GPIO_ACTIVE_HIGH>;
|
||||
enable-gpios = <&hc595 4 GPIO_ACTIVE_HIGH>;
|
||||
rs-gpios = <&hc595 5 GPIO_ACTIVE_HIGH>;
|
||||
|
||||
display-height-chars = <2>;
|
||||
display-width-chars = <16>;
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
COREBOOT firmware information
|
||||
|
||||
The device tree node to communicate the location of coreboot's memory-resident
|
||||
bookkeeping structures to the kernel. Since coreboot itself cannot boot a
|
||||
device-tree-based kernel (yet), this node needs to be inserted by a
|
||||
second-stage bootloader (a coreboot "payload").
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "coreboot"
|
||||
- reg: Address and length of the following two memory regions, in order:
|
||||
1.) The coreboot table. This is a list of variable-sized descriptors
|
||||
that contain various compile- and run-time generated firmware
|
||||
parameters. It is identified by the magic string "LBIO" in its first
|
||||
four bytes.
|
||||
See coreboot's src/commonlib/include/commonlib/coreboot_tables.h for
|
||||
details.
|
||||
2.) The CBMEM area. This is a downward-growing memory region used by
|
||||
coreboot to dynamically allocate data structures that remain resident.
|
||||
It may or may not include the coreboot table as one of its members. It
|
||||
is identified by a root node descriptor with the magic number
|
||||
0xc0389481 that resides in the topmost 8 bytes of the area.
|
||||
See coreboot's src/include/imd.h for details.
|
||||
|
||||
Example:
|
||||
firmware {
|
||||
ranges;
|
||||
|
||||
coreboot {
|
||||
compatible = "coreboot";
|
||||
reg = <0xfdfea000 0x264>,
|
||||
<0xfdfea000 0x16000>;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
Altera Arria10 Partial Reconfiguration IP
|
||||
|
||||
Required properties:
|
||||
- compatible : should contain "altr,a10-pr-ip"
|
||||
- reg : base address and size for memory mapped io.
|
||||
|
||||
Example:
|
||||
|
||||
fpga_mgr: fpga-mgr@ff20c000 {
|
||||
compatible = "altr,a10-pr-ip";
|
||||
reg = <0xff20c000 0x10>;
|
||||
};
|
|
@ -186,6 +186,7 @@ Optional properties:
|
|||
otherwise full reconfiguration is done.
|
||||
- external-fpga-config : boolean, set if the FPGA has already been configured
|
||||
prior to OS boot up.
|
||||
- encrypted-fpga-config : boolean, set if the bitstream is encrypted
|
||||
- region-unfreeze-timeout-us : The maximum time in microseconds to wait for
|
||||
bridges to successfully become enabled after the region has been
|
||||
programmed.
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
Lattice iCE40 FPGA Manager
|
||||
|
||||
Required properties:
|
||||
- compatible: Should contain "lattice,ice40-fpga-mgr"
|
||||
- reg: SPI chip select
|
||||
- spi-max-frequency: Maximum SPI frequency (>=1000000, <=25000000)
|
||||
- cdone-gpios: GPIO input connected to CDONE pin
|
||||
- reset-gpios: Active-low GPIO output connected to CRESET_B pin. Note
|
||||
that unless the GPIO is held low during startup, the
|
||||
FPGA will enter Master SPI mode and drive SCK with a
|
||||
clock signal potentially jamming other devices on the
|
||||
bus until the firmware is loaded.
|
||||
|
||||
Example:
|
||||
fpga: fpga@0 {
|
||||
compatible = "lattice,ice40-fpga-mgr";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <1000000>;
|
||||
cdone-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>;
|
||||
reset-gpios = <&gpio 22 GPIO_ACTIVE_LOW>;
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
Xilinx Slave Serial SPI FPGA Manager
|
||||
|
||||
Xilinx Spartan-6 FPGAs support a method of loading the bitstream over
|
||||
what is referred to as "slave serial" interface.
|
||||
The slave serial link is not technically SPI, and might require extra
|
||||
circuits in order to play nicely with other SPI slaves on the same bus.
|
||||
|
||||
See https://www.xilinx.com/support/documentation/user_guides/ug380.pdf
|
||||
|
||||
Required properties:
|
||||
- compatible: should contain "xlnx,fpga-slave-serial"
|
||||
- reg: spi chip select of the FPGA
|
||||
- prog_b-gpios: config pin (referred to as PROGRAM_B in the manual)
|
||||
- done-gpios: config status pin (referred to as DONE in the manual)
|
||||
|
||||
Example for full FPGA configuration:
|
||||
|
||||
fpga-region0 {
|
||||
compatible = "fpga-region";
|
||||
fpga-mgr = <&fpga_mgr_spi>;
|
||||
#address-cells = <0x1>;
|
||||
#size-cells = <0x1>;
|
||||
};
|
||||
|
||||
spi1: spi@10680 {
|
||||
compatible = "marvell,armada-xp-spi", "marvell,orion-spi";
|
||||
pinctrl-0 = <&spi0_pins>;
|
||||
pinctrl-names = "default";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
cell-index = <1>;
|
||||
interrupts = <92>;
|
||||
clocks = <&coreclk 0>;
|
||||
status = "okay";
|
||||
|
||||
fpga_mgr_spi: fpga-mgr@0 {
|
||||
compatible = "xlnx,fpga-slave-serial";
|
||||
spi-max-frequency = <60000000>;
|
||||
spi-cpha;
|
||||
reg = <0>;
|
||||
done-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
|
||||
prog_b-gpios = <&gpio0 29 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
};
|
|
@ -1,7 +1,11 @@
|
|||
Allwinner sunxi-sid
|
||||
|
||||
Required properties:
|
||||
- compatible: "allwinner,sun4i-a10-sid" or "allwinner,sun7i-a20-sid"
|
||||
- compatible: Should be one of the following:
|
||||
"allwinner,sun4i-a10-sid"
|
||||
"allwinner,sun7i-a20-sid"
|
||||
"allwinner,sun8i-h3-sid"
|
||||
|
||||
- reg: Should contain registers location and length
|
||||
|
||||
= Data cells =
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
Freescale i.MX IC Identification Module (IIM) device tree bindings
|
||||
|
||||
This binding represents the IC Identification Module (IIM) found on
|
||||
i.MX25, i.MX27, i.MX31, i.MX35, i.MX51 and i.MX53 SoCs.
|
||||
|
||||
Required properties:
|
||||
- compatible: should be one of
|
||||
"fsl,imx25-iim", "fsl,imx27-iim",
|
||||
"fsl,imx31-iim", "fsl,imx35-iim",
|
||||
"fsl,imx51-iim", "fsl,imx53-iim",
|
||||
- reg: Should contain the register base and length.
|
||||
- interrupts: Should contain the interrupt for the IIM
|
||||
- clocks: Should contain a phandle pointing to the gated peripheral clock.
|
||||
|
||||
Example:
|
||||
|
||||
iim: iim@63f98000 {
|
||||
compatible = "fsl,imx53-iim", "fsl,imx27-iim";
|
||||
reg = <0x63f98000 0x4000>;
|
||||
interrupts = <69>;
|
||||
clocks = <&clks IMX5_CLK_IIM_GATE>;
|
||||
};
|
|
@ -9,14 +9,19 @@ Required properties:
|
|||
"fsl,imx6sl-ocotp" (i.MX6SL), or
|
||||
"fsl,imx6sx-ocotp" (i.MX6SX),
|
||||
"fsl,imx6ul-ocotp" (i.MX6UL),
|
||||
"fsl,imx7d-ocotp" (i.MX7D/S),
|
||||
followed by "syscon".
|
||||
- reg: Should contain the register base and length.
|
||||
- clocks: Should contain a phandle pointing to the gated peripheral clock.
|
||||
|
||||
Optional properties:
|
||||
- read-only: disable write access
|
||||
|
||||
Example:
|
||||
|
||||
ocotp: ocotp@021bc000 {
|
||||
compatible = "fsl,imx6q-ocotp", "syscon";
|
||||
reg = <0x021bc000 0x4000>;
|
||||
clocks = <&clks IMX6QDL_CLK_IIM>;
|
||||
read-only;
|
||||
};
|
||||
|
|
|
@ -6,36 +6,15 @@ Driver registration
|
|||
|
||||
As with other subsystems within the Linux kernel, VME device drivers register
|
||||
with the VME subsystem, typically called from the devices init routine. This is
|
||||
achieved via a call to the following function:
|
||||
achieved via a call to :c:func:`vme_register_driver`.
|
||||
|
||||
.. code-block:: c
|
||||
A pointer to a structure of type :c:type:`struct vme_driver <vme_driver>` must
|
||||
be provided to the registration function. Along with the maximum number of
|
||||
devices your driver is able to support.
|
||||
|
||||
int vme_register_driver (struct vme_driver *driver, unsigned int ndevs);
|
||||
|
||||
If driver registration is successful this function returns zero, if an error
|
||||
occurred a negative error code will be returned.
|
||||
|
||||
A pointer to a structure of type 'vme_driver' must be provided to the
|
||||
registration function. Along with ndevs, which is the number of devices your
|
||||
driver is able to support. The structure is as follows:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vme_driver {
|
||||
struct list_head node;
|
||||
const char *name;
|
||||
int (*match)(struct vme_dev *);
|
||||
int (*probe)(struct vme_dev *);
|
||||
int (*remove)(struct vme_dev *);
|
||||
void (*shutdown)(void);
|
||||
struct device_driver driver;
|
||||
struct list_head devices;
|
||||
unsigned int ndev;
|
||||
};
|
||||
|
||||
At the minimum, the '.name', '.match' and '.probe' elements of this structure
|
||||
should be correctly set. The '.name' element is a pointer to a string holding
|
||||
the device driver's name.
|
||||
At the minimum, the '.name', '.match' and '.probe' elements of
|
||||
:c:type:`struct vme_driver <vme_driver>` should be correctly set. The '.name'
|
||||
element is a pointer to a string holding the device driver's name.
|
||||
|
||||
The '.match' function allows control over which VME devices should be registered
|
||||
with the driver. The match function should return 1 if a device should be
|
||||
|
@ -54,29 +33,16 @@ the number of devices probed to one:
|
|||
}
|
||||
|
||||
The '.probe' element should contain a pointer to the probe routine. The
|
||||
probe routine is passed a 'struct vme_dev' pointer as an argument. The
|
||||
'struct vme_dev' structure looks like the following:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vme_dev {
|
||||
int num;
|
||||
struct vme_bridge *bridge;
|
||||
struct device dev;
|
||||
struct list_head drv_list;
|
||||
struct list_head bridge_list;
|
||||
};
|
||||
probe routine is passed a :c:type:`struct vme_dev <vme_dev>` pointer as an
|
||||
argument.
|
||||
|
||||
Here, the 'num' field refers to the sequential device ID for this specific
|
||||
driver. The bridge number (or bus number) can be accessed using
|
||||
dev->bridge->num.
|
||||
|
||||
A function is also provided to unregister the driver from the VME core and is
|
||||
usually called from the device driver's exit routine:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void vme_unregister_driver (struct vme_driver *driver);
|
||||
A function is also provided to unregister the driver from the VME core called
|
||||
:c:func:`vme_unregister_driver` and should usually be called from the device
|
||||
driver's exit routine.
|
||||
|
||||
|
||||
Resource management
|
||||
|
@ -90,47 +56,29 @@ driver is called. The probe routine is passed a pointer to the devices
|
|||
device structure. This pointer should be saved, it will be required for
|
||||
requesting VME resources.
|
||||
|
||||
The driver can request ownership of one or more master windows, slave windows
|
||||
and/or dma channels. Rather than allowing the device driver to request a
|
||||
specific window or DMA channel (which may be used by a different driver) this
|
||||
driver allows a resource to be assigned based on the required attributes of the
|
||||
driver in question:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vme_resource * vme_master_request(struct vme_dev *dev,
|
||||
u32 aspace, u32 cycle, u32 width);
|
||||
|
||||
struct vme_resource * vme_slave_request(struct vme_dev *dev, u32 aspace,
|
||||
u32 cycle);
|
||||
|
||||
struct vme_resource *vme_dma_request(struct vme_dev *dev, u32 route);
|
||||
|
||||
For slave windows these attributes are split into the VME address spaces that
|
||||
need to be accessed in 'aspace' and VME bus cycle types required in 'cycle'.
|
||||
Master windows add a further set of attributes in 'width' specifying the
|
||||
required data transfer widths. These attributes are defined as bitmasks and as
|
||||
such any combination of the attributes can be requested for a single window,
|
||||
the core will assign a window that meets the requirements, returning a pointer
|
||||
of type vme_resource that should be used to identify the allocated resource
|
||||
when it is used. For DMA controllers, the request function requires the
|
||||
potential direction of any transfers to be provided in the route attributes.
|
||||
This is typically VME-to-MEM and/or MEM-to-VME, though some hardware can
|
||||
support VME-to-VME and MEM-to-MEM transfers as well as test pattern generation.
|
||||
If an unallocated window fitting the requirements can not be found a NULL
|
||||
pointer will be returned.
|
||||
The driver can request ownership of one or more master windows
|
||||
(:c:func:`vme_master_request`), slave windows (:c:func:`vme_slave_request`)
|
||||
and/or dma channels (:c:func:`vme_dma_request`). Rather than allowing the device
|
||||
driver to request a specific window or DMA channel (which may be used by a
|
||||
different driver) the API allows a resource to be assigned based on the required
|
||||
attributes of the driver in question. For slave windows these attributes are
|
||||
split into the VME address spaces that need to be accessed in 'aspace' and VME
|
||||
bus cycle types required in 'cycle'. Master windows add a further set of
|
||||
attributes in 'width' specifying the required data transfer widths. These
|
||||
attributes are defined as bitmasks and as such any combination of the
|
||||
attributes can be requested for a single window, the core will assign a window
|
||||
that meets the requirements, returning a pointer of type vme_resource that
|
||||
should be used to identify the allocated resource when it is used. For DMA
|
||||
controllers, the request function requires the potential direction of any
|
||||
transfers to be provided in the route attributes. This is typically VME-to-MEM
|
||||
and/or MEM-to-VME, though some hardware can support VME-to-VME and MEM-to-MEM
|
||||
transfers as well as test pattern generation. If an unallocated window fitting
|
||||
the requirements can not be found a NULL pointer will be returned.
|
||||
|
||||
Functions are also provided to free window allocations once they are no longer
|
||||
required. These functions should be passed the pointer to the resource provided
|
||||
during resource allocation:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void vme_master_free(struct vme_resource *res);
|
||||
|
||||
void vme_slave_free(struct vme_resource *res);
|
||||
|
||||
void vme_dma_free(struct vme_resource *res);
|
||||
required. These functions (:c:func:`vme_master_free`, :c:func:`vme_slave_free`
|
||||
and :c:func:`vme_dma_free`) should be passed the pointer to the resource
|
||||
provided during resource allocation.
|
||||
|
||||
|
||||
Master windows
|
||||
|
@ -144,61 +92,22 @@ the underlying chipset. A window must be configured before it can be used.
|
|||
Master window configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once a master window has been assigned the following functions can be used to
|
||||
configure it and retrieve the current settings:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_master_set (struct vme_resource *res, int enabled,
|
||||
unsigned long long base, unsigned long long size, u32 aspace,
|
||||
u32 cycle, u32 width);
|
||||
|
||||
int vme_master_get (struct vme_resource *res, int *enabled,
|
||||
unsigned long long *base, unsigned long long *size, u32 *aspace,
|
||||
u32 *cycle, u32 *width);
|
||||
|
||||
The address spaces, transfer widths and cycle types are the same as described
|
||||
Once a master window has been assigned :c:func:`vme_master_set` can be used to
|
||||
configure it and :c:func:`vme_master_get` to retrieve the current settings. The
|
||||
address spaces, transfer widths and cycle types are the same as described
|
||||
under resource management, however some of the options are mutually exclusive.
|
||||
For example, only one address space may be specified.
|
||||
|
||||
These functions return 0 on success or an error code should the call fail.
|
||||
|
||||
|
||||
Master window access
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following functions can be used to read from and write to configured master
|
||||
windows. These functions return the number of bytes copied:
|
||||
The function :c:func:`vme_master_read` can be used to read from and
|
||||
:c:func:`vme_master_write` used to write to configured master windows.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ssize_t vme_master_read(struct vme_resource *res, void *buf,
|
||||
size_t count, loff_t offset);
|
||||
|
||||
ssize_t vme_master_write(struct vme_resource *res, void *buf,
|
||||
size_t count, loff_t offset);
|
||||
|
||||
In addition to simple reads and writes, a function is provided to do a
|
||||
read-modify-write transaction. This function returns the original value of the
|
||||
VME bus location :
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
unsigned int vme_master_rmw (struct vme_resource *res,
|
||||
unsigned int mask, unsigned int compare, unsigned int swap,
|
||||
loff_t offset);
|
||||
|
||||
This functions by reading the offset, applying the mask. If the bits selected in
|
||||
the mask match with the values of the corresponding bits in the compare field,
|
||||
the value of swap is written the specified offset.
|
||||
|
||||
Parts of a VME window can be mapped into user space memory using the following
|
||||
function:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_master_mmap(struct vme_resource *resource,
|
||||
struct vm_area_struct *vma)
|
||||
In addition to simple reads and writes, :c:func:`vme_master_rmw` is provided to
|
||||
do a read-modify-write transaction. Parts of a VME window can also be mapped
|
||||
into user space memory using :c:func:`vme_master_mmap`.
|
||||
|
||||
|
||||
Slave windows
|
||||
|
@ -213,41 +122,23 @@ it can be used.
|
|||
Slave window configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once a slave window has been assigned the following functions can be used to
|
||||
configure it and retrieve the current settings:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_slave_set (struct vme_resource *res, int enabled,
|
||||
unsigned long long base, unsigned long long size,
|
||||
dma_addr_t mem, u32 aspace, u32 cycle);
|
||||
|
||||
int vme_slave_get (struct vme_resource *res, int *enabled,
|
||||
unsigned long long *base, unsigned long long *size,
|
||||
dma_addr_t *mem, u32 *aspace, u32 *cycle);
|
||||
Once a slave window has been assigned :c:func:`vme_slave_set` can be used to
|
||||
configure it and :c:func:`vme_slave_get` to retrieve the current settings.
|
||||
|
||||
The address spaces, transfer widths and cycle types are the same as described
|
||||
under resource management, however some of the options are mutually exclusive.
|
||||
For example, only one address space may be specified.
|
||||
|
||||
These functions return 0 on success or an error code should the call fail.
|
||||
|
||||
|
||||
Slave window buffer allocation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Functions are provided to allow the user to allocate and free a contiguous
|
||||
buffers which will be accessible by the VME bridge. These functions do not have
|
||||
to be used, other methods can be used to allocate a buffer, though care must be
|
||||
taken to ensure that they are contiguous and accessible by the VME bridge:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void * vme_alloc_consistent(struct vme_resource *res, size_t size,
|
||||
dma_addr_t *mem);
|
||||
|
||||
void vme_free_consistent(struct vme_resource *res, size_t size,
|
||||
void *virt, dma_addr_t mem);
|
||||
Functions are provided to allow the user to allocate
|
||||
(:c:func:`vme_alloc_consistent`) and free (:c:func:`vme_free_consistent`)
|
||||
contiguous buffers which will be accessible by the VME bridge. These functions
|
||||
do not have to be used, other methods can be used to allocate a buffer, though
|
||||
care must be taken to ensure that they are contiguous and accessible by the VME
|
||||
bridge.
|
||||
|
||||
|
||||
Slave window access
|
||||
|
@ -269,29 +160,18 @@ executed, reused and destroyed.
|
|||
List Management
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The following functions are provided to create and destroy DMA lists. Execution
|
||||
of a list will not automatically destroy the list, thus enabling a list to be
|
||||
reused for repetitive tasks:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vme_dma_list *vme_new_dma_list(struct vme_resource *res);
|
||||
|
||||
int vme_dma_list_free(struct vme_dma_list *list);
|
||||
The function :c:func:`vme_new_dma_list` is provided to create and
|
||||
:c:func:`vme_dma_list_free` to destroy DMA lists. Execution of a list will not
|
||||
automatically destroy the list, thus enabling a list to be reused for repetitive
|
||||
tasks.
|
||||
|
||||
|
||||
List Population
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
An item can be added to a list using the following function ( the source and
|
||||
An item can be added to a list using :c:func:`vme_dma_list_add` (the source and
|
||||
destination attributes need to be created before calling this function, this is
|
||||
covered under "Transfer Attributes"):
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_dma_list_add(struct vme_dma_list *list,
|
||||
struct vme_dma_attr *src, struct vme_dma_attr *dest,
|
||||
size_t count);
|
||||
covered under "Transfer Attributes").
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -310,41 +190,19 @@ an item to a list. This is due to the diverse attributes required for each type
|
|||
of source and destination. There are functions to create attributes for PCI, VME
|
||||
and pattern sources and destinations (where appropriate):
|
||||
|
||||
Pattern source:
|
||||
- PCI source or destination: :c:func:`vme_dma_pci_attribute`
|
||||
- VME source or destination: :c:func:`vme_dma_vme_attribute`
|
||||
- Pattern source: :c:func:`vme_dma_pattern_attribute`
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vme_dma_attr *vme_dma_pattern_attribute(u32 pattern, u32 type);
|
||||
|
||||
PCI source or destination:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vme_dma_attr *vme_dma_pci_attribute(dma_addr_t mem);
|
||||
|
||||
VME source or destination:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vme_dma_attr *vme_dma_vme_attribute(unsigned long long base,
|
||||
u32 aspace, u32 cycle, u32 width);
|
||||
|
||||
The following function should be used to free an attribute:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void vme_dma_free_attribute(struct vme_dma_attr *attr);
|
||||
The function :c:func:`vme_dma_free_attribute` should be used to free an
|
||||
attribute.
|
||||
|
||||
|
||||
List Execution
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
The following function queues a list for execution. The function will return
|
||||
once the list has been executed:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_dma_list_exec(struct vme_dma_list *list);
|
||||
The function :c:func:`vme_dma_list_exec` queues a list for execution and will
|
||||
return once the list has been executed.
|
||||
|
||||
|
||||
Interrupts
|
||||
|
@ -358,20 +216,13 @@ specific VME level and status IDs.
|
|||
Attaching Interrupt Handlers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following functions can be used to attach and free a specific VME level and
|
||||
status ID combination. Any given combination can only be assigned a single
|
||||
callback function. A void pointer parameter is provided, the value of which is
|
||||
passed to the callback function, the use of this pointer is user undefined:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_irq_request(struct vme_dev *dev, int level, int statid,
|
||||
void (*callback)(int, int, void *), void *priv);
|
||||
|
||||
void vme_irq_free(struct vme_dev *dev, int level, int statid);
|
||||
|
||||
The callback parameters are as follows. Care must be taken in writing a callback
|
||||
function, callback functions run in interrupt context:
|
||||
The function :c:func:`vme_irq_request` can be used to attach and
|
||||
:c:func:`vme_irq_free` to free a specific VME level and status ID combination.
|
||||
Any given combination can only be assigned a single callback function. A void
|
||||
pointer parameter is provided, the value of which is passed to the callback
|
||||
function, the use of this pointer is user undefined. The callback parameters are
|
||||
as follows. Care must be taken in writing a callback function, callback
|
||||
functions run in interrupt context:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
|
@ -381,12 +232,8 @@ function, callback functions run in interrupt context:
|
|||
Interrupt Generation
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following function can be used to generate a VME interrupt at a given VME
|
||||
level and VME status ID:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_irq_generate(struct vme_dev *dev, int level, int statid);
|
||||
The function :c:func:`vme_irq_generate` can be used to generate a VME interrupt
|
||||
at a given VME level and VME status ID.
|
||||
|
||||
|
||||
Location monitors
|
||||
|
@ -399,54 +246,29 @@ monitor.
|
|||
Location Monitor Management
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following functions are provided to request the use of a block of location
|
||||
monitors and to free them after they are no longer required:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vme_resource * vme_lm_request(struct vme_dev *dev);
|
||||
|
||||
void vme_lm_free(struct vme_resource * res);
|
||||
|
||||
Each block may provide a number of location monitors, monitoring adjacent
|
||||
locations. The following function can be used to determine how many locations
|
||||
are provided:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_lm_count(struct vme_resource * res);
|
||||
The function :c:func:`vme_lm_request` is provided to request the use of a block
|
||||
of location monitors and :c:func:`vme_lm_free` to free them after they are no
|
||||
longer required. Each block may provide a number of location monitors,
|
||||
monitoring adjacent locations. The function :c:func:`vme_lm_count` can be used
|
||||
to determine how many locations are provided.
|
||||
|
||||
|
||||
Location Monitor Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once a bank of location monitors has been allocated, the following functions
|
||||
are provided to configure the location and mode of the location monitor:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_lm_set(struct vme_resource *res, unsigned long long base,
|
||||
u32 aspace, u32 cycle);
|
||||
|
||||
int vme_lm_get(struct vme_resource *res, unsigned long long *base,
|
||||
u32 *aspace, u32 *cycle);
|
||||
Once a bank of location monitors has been allocated, the function
|
||||
:c:func:`vme_lm_set` is provided to configure the location and mode of the
|
||||
location monitor. The function :c:func:`vme_lm_get` can be used to retrieve
|
||||
existing settings.
|
||||
|
||||
|
||||
Location Monitor Use
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following functions allow a callback to be attached and detached from each
|
||||
location monitor location. Each location monitor can monitor a number of
|
||||
adjacent locations:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_lm_attach(struct vme_resource *res, int num,
|
||||
void (*callback)(void *));
|
||||
|
||||
int vme_lm_detach(struct vme_resource *res, int num);
|
||||
|
||||
The callback function is declared as follows.
|
||||
The function :c:func:`vme_lm_attach` enables a callback to be attached and
|
||||
:c:func:`vme_lm_detach` allows on to be detached from each location monitor
|
||||
location. Each location monitor can monitor a number of adjacent locations. The
|
||||
callback function is declared as follows.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
|
@ -456,19 +278,20 @@ The callback function is declared as follows.
|
|||
Slot Detection
|
||||
--------------
|
||||
|
||||
This function returns the slot ID of the provided bridge.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_slot_num(struct vme_dev *dev);
|
||||
The function :c:func:`vme_slot_num` returns the slot ID of the provided bridge.
|
||||
|
||||
|
||||
Bus Detection
|
||||
-------------
|
||||
|
||||
This function returns the bus ID of the provided bridge.
|
||||
The function :c:func:`vme_bus_num` returns the bus ID of the provided bridge.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int vme_bus_num(struct vme_dev *dev);
|
||||
VME API
|
||||
-------
|
||||
|
||||
.. kernel-doc:: include/linux/vme.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/vme/vme.c
|
||||
:export:
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
|
||||
Staging/Android Switch Class Porting Guide
|
||||
(linux/drivers/staging/android/switch)
|
||||
(c) Copyright 2012 Samsung Electronics
|
||||
|
||||
AUTHORS
|
||||
MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
|
||||
/*****************************************************************
|
||||
* CHAPTER 1. *
|
||||
* PORTING SWITCH CLASS DEVICE DRIVERS *
|
||||
*****************************************************************/
|
||||
|
||||
****** STEP 1. Basic Functionality
|
||||
No extcon extended feature, but switch features only.
|
||||
|
||||
- struct switch_dev (fed to switch_dev_register/unregister)
|
||||
@name: no change
|
||||
@dev: no change
|
||||
@index: drop (not used in switch device driver side anyway)
|
||||
@state: no change
|
||||
If you have used @state with magic numbers, keep it
|
||||
at this step.
|
||||
@print_name: no change but type change (switch_dev->extcon_dev)
|
||||
@print_state: no change but type change (switch_dev->extcon_dev)
|
||||
|
||||
- switch_dev_register(sdev, dev)
|
||||
=> extcon_dev_register(edev)
|
||||
: type change (sdev->edev)
|
||||
: remove second param('dev'). if edev has parent device, should store
|
||||
'dev' to 'edev.dev.parent' before registering extcon device
|
||||
- switch_dev_unregister(sdev)
|
||||
=> extcon_dev_unregister(edev)
|
||||
: no change but type change (sdev->edev)
|
||||
- switch_get_state(sdev)
|
||||
=> extcon_get_state(edev)
|
||||
: no change but type change (sdev->edev) and (return: int->u32)
|
||||
- switch_set_state(sdev, state)
|
||||
=> extcon_set_state(edev, state)
|
||||
: no change but type change (sdev->edev) and (state: int->u32)
|
||||
|
||||
With this changes, the ex-switch extcon class device works as it once
|
||||
worked as switch class device. However, it will now have additional
|
||||
interfaces (both ABI and in-kernel API) and different ABI locations.
|
||||
However, if CONFIG_ANDROID is enabled without CONFIG_ANDROID_SWITCH,
|
||||
/sys/class/switch/* will be symbolically linked to /sys/class/extcon/
|
||||
so that they are still compatible with legacy userspace processes.
|
||||
|
||||
****** STEP 2. Multistate (no more magic numbers in state value)
|
||||
Extcon's extended features for switch device drivers with
|
||||
complex features usually required magic numbers in state
|
||||
value of switch_dev. With extcon, such magic numbers that
|
||||
support multiple cables are no more required or supported.
|
||||
|
||||
1. Define cable names at edev->supported_cable.
|
||||
2. (Recommended) remove print_state callback.
|
||||
3. Use extcon_get_cable_state_(edev, index) or
|
||||
extcon_get_cable_state(edev, cable_name) instead of
|
||||
extcon_get_state(edev) if you intend to get a state of a specific
|
||||
cable. Same for set_state. This way, you can remove the usage of
|
||||
magic numbers in state value.
|
||||
4. Use extcon_update_state() if you are updating specific bits of
|
||||
the state value.
|
||||
|
||||
Example: a switch device driver w/ magic numbers for two cables.
|
||||
"0x00": no cables connected.
|
||||
"0x01": cable 1 connected
|
||||
"0x02": cable 2 connected
|
||||
"0x03": cable 1 and 2 connected
|
||||
1. edev->supported_cable = {"1", "2", NULL};
|
||||
2. edev->print_state = NULL;
|
||||
3. extcon_get_cable_state_(edev, 0) shows cable 1's state.
|
||||
extcon_get_cable_state(edev, "1") shows cable 1's state.
|
||||
extcon_set_cable_state_(edev, 1) sets cable 2's state.
|
||||
extcon_set_cable_state(edev, "2") sets cable 2's state
|
||||
4. extcon_update_state(edev, 0x01, 0) sets the least bit's 0.
|
||||
|
||||
****** STEP 3. Notify other device drivers
|
||||
|
||||
You can notify others of the cable attach/detach events with
|
||||
notifier chains.
|
||||
|
||||
At the side of other device drivers (the extcon device itself
|
||||
does not need to get notified of its own events), there are two
|
||||
methods to register notifier_block for cable events:
|
||||
(a) for a specific cable or (b) for every cable.
|
||||
|
||||
(a) extcon_register_interest(obj, extcon_name, cable_name, nb)
|
||||
Example: want to get news of "MAX8997_MUIC"'s "USB" cable
|
||||
|
||||
obj = kzalloc(sizeof(struct extcon_specific_cable_nb),
|
||||
GFP_KERNEL);
|
||||
nb->notifier_call = the_callback_to_handle_usb;
|
||||
|
||||
extcon_register_intereset(obj, "MAX8997_MUIC", "USB", nb);
|
||||
|
||||
(b) extcon_register_notifier(edev, nb)
|
||||
Call nb for any changes in edev.
|
||||
|
||||
Please note that in order to properly behave with method (a),
|
||||
the extcon device driver should support multistate feature (STEP 2).
|
||||
|
||||
****** STEP 4. Inter-cable relation (mutually exclusive)
|
||||
|
||||
You can provide inter-cable mutually exclusiveness information
|
||||
for an extcon device. When cables A and B are declared to be mutually
|
||||
exclusive, the two cables cannot be in ATTACHED state simulteneously.
|
||||
|
||||
|
||||
/*****************************************************************
|
||||
* CHAPTER 2. *
|
||||
* PORTING USERSPACE w/ SWITCH CLASS DEVICE SUPPORT *
|
||||
*****************************************************************/
|
||||
|
||||
****** ABI Location
|
||||
|
||||
If "CONFIG_ANDROID" is enabled, /sys/class/switch/* are created
|
||||
as symbolic links to /sys/class/extcon/*.
|
||||
|
||||
The two files of switch class, name and state, are provided with
|
||||
extcon, too. When the multistate support (STEP 2 of CHAPTER 1.) is
|
||||
not enabled or print_state callback is supplied, the output of
|
||||
state ABI is same with switch class.
|
|
@ -2,7 +2,11 @@
|
|||
- This file
|
||||
w1_therm
|
||||
- The Maxim/Dallas Semiconductor ds18*20 temperature sensor.
|
||||
w1_ds2413
|
||||
- The Maxim/Dallas Semiconductor ds2413 dual channel addressable switch.
|
||||
w1_ds2423
|
||||
- The Maxim/Dallas Semiconductor ds2423 counter device.
|
||||
w1_ds2438
|
||||
- The Maxim/Dallas Semiconductor ds2438 smart battery monitor.
|
||||
w1_ds28e04
|
||||
- The Maxim/Dallas Semiconductor ds28e04 eeprom.
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
Kernel driver w1_ds2413
|
||||
=======================
|
||||
|
||||
Supported chips:
|
||||
* Maxim DS2413 1-Wire Dual Channel Addressable Switch
|
||||
|
||||
supported family codes:
|
||||
W1_FAMILY_DS2413 0x3A
|
||||
|
||||
Author: Mariusz Bialonczyk <manio@skyboo.net>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The DS2413 chip has two open-drain outputs (PIO A and PIO B).
|
||||
Support is provided through the sysfs files "output" and "state".
|
||||
|
||||
Reading state
|
||||
-------------
|
||||
The "state" file provides one-byte value which is in the same format as for
|
||||
the chip PIO_ACCESS_READ command (refer the datasheet for details):
|
||||
|
||||
Bit 0: PIOA Pin State
|
||||
Bit 1: PIOA Output Latch State
|
||||
Bit 2: PIOB Pin State
|
||||
Bit 3: PIOB Output Latch State
|
||||
Bit 4-7: Complement of Bit 3 to Bit 0 (verified by the kernel module)
|
||||
|
||||
This file is readonly.
|
||||
|
||||
Writing output
|
||||
--------------
|
||||
You can set the PIO pins using the "output" file.
|
||||
It is writable, you can write one-byte value to this sysfs file.
|
||||
Similarly the byte format is the same as for the PIO_ACCESS_WRITE command:
|
||||
|
||||
Bit 0: PIOA
|
||||
Bit 1: PIOB
|
||||
Bit 2-7: No matter (driver will set it to "1"s)
|
||||
|
||||
|
||||
The chip has some kind of basic protection against transmission errors.
|
||||
When reading the state, there is a four complement bits.
|
||||
The driver is checking this complement, and when it is wrong then it is
|
||||
returning I/O error.
|
||||
|
||||
When writing output, the master must repeat the PIO Output Data byte in
|
||||
its inverted form and it is waiting for a confirmation.
|
||||
If the write is unsuccessful for three times, the write also returns
|
||||
I/O error.
|
|
@ -0,0 +1,63 @@
|
|||
Kernel driver w1_ds2438
|
||||
=======================
|
||||
|
||||
Supported chips:
|
||||
* Maxim DS2438 Smart Battery Monitor
|
||||
|
||||
supported family codes:
|
||||
W1_FAMILY_DS2438 0x26
|
||||
|
||||
Author: Mariusz Bialonczyk <manio@skyboo.net>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The DS2438 chip provides several functions that are desirable to carry in
|
||||
a battery pack. It also has a 40 bytes of nonvolatile EEPROM.
|
||||
Because the ability of temperature, current and voltage measurement, the chip
|
||||
is also often used in weather stations and applications such as: rain gauge,
|
||||
wind speed/direction measuring, humidity sensing, etc.
|
||||
|
||||
Current support is provided through the following sysfs files (all files
|
||||
except "iad" are readonly):
|
||||
|
||||
"iad"
|
||||
-----
|
||||
This file controls the 'Current A/D Control Bit' (IAD) in the
|
||||
Status/Configuration Register.
|
||||
Writing a zero value will clear the IAD bit and disables the current
|
||||
measurements.
|
||||
Writing value "1" is setting the IAD bit (enables the measurements).
|
||||
The IAD bit is enabled by default in the DS2438.
|
||||
|
||||
When writing to sysfs file bits 2-7 are ignored, so it's safe to write ASCII.
|
||||
An I/O error is returned when there is a problem setting the new value.
|
||||
|
||||
"page0"
|
||||
-------
|
||||
This file provides full 8 bytes of the chip Page 0 (00h).
|
||||
This page contains the most frequently accessed information of the DS2438.
|
||||
Internally when this file is read, the additional CRC byte is also obtained
|
||||
from the slave device. If it is correct, the 8 bytes page data are passed
|
||||
to userspace, otherwise an I/O error is returned.
|
||||
|
||||
"temperature"
|
||||
-------------
|
||||
Opening and reading this file initiates the CONVERT_T (temperature conversion)
|
||||
command of the chip, afterwards the temperature is read from the device
|
||||
registers and provided as an ASCII decimal value.
|
||||
|
||||
Important: The returned value has to be divided by 256 to get a real
|
||||
temperature in degrees Celsius.
|
||||
|
||||
"vad", "vdd"
|
||||
------------
|
||||
Opening and reading this file initiates the CONVERT_V (voltage conversion)
|
||||
command of the chip.
|
||||
|
||||
Depending on a sysfs filename a different input for the A/D will be selected:
|
||||
vad: general purpose A/D input (VAD)
|
||||
vdd: battery input (VDD)
|
||||
|
||||
After the voltage conversion the value is returned as decimal ASCII.
|
||||
Note: The value is in mV, so to get a volts the value has to be divided by 10.
|
|
@ -5161,7 +5161,6 @@ F: include/uapi/linux/firewire*.h
|
|||
F: tools/firewire/
|
||||
|
||||
FIRMWARE LOADER (request_firmware)
|
||||
M: Ming Lei <ming.lei@canonical.com>
|
||||
M: Luis R. Rodriguez <mcgrof@kernel.org>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Maintained
|
||||
|
@ -5191,13 +5190,15 @@ F: include/linux/ipmi-fru.h
|
|||
K: fmc_d.*register
|
||||
|
||||
FPGA MANAGER FRAMEWORK
|
||||
M: Alan Tull <atull@opensource.altera.com>
|
||||
M: Alan Tull <atull@kernel.org>
|
||||
R: Moritz Fischer <moritz.fischer@ettus.com>
|
||||
L: linux-fpga@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/atull/linux-fpga.git
|
||||
F: Documentation/fpga/
|
||||
F: Documentation/devicetree/bindings/fpga/
|
||||
F: drivers/fpga/
|
||||
F: include/linux/fpga/fpga-mgr.h
|
||||
F: include/linux/fpga/
|
||||
W: http://www.rocketboards.org
|
||||
|
||||
FPU EMULATOR
|
||||
|
@ -9131,7 +9132,6 @@ F: drivers/nvme/target/fcloop.c
|
|||
|
||||
NVMEM FRAMEWORK
|
||||
M: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
||||
M: Maxime Ripard <maxime.ripard@free-electrons.com>
|
||||
S: Maintained
|
||||
F: drivers/nvmem/
|
||||
F: Documentation/devicetree/bindings/nvmem/
|
||||
|
|
|
@ -210,6 +210,28 @@ static struct ep93xx_eth_data __initdata ts72xx_eth_data = {
|
|||
.phy_id = 1,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_FPGA_MGR_TS73XX)
|
||||
|
||||
/* Relative to EP93XX_CS1_PHYS_BASE */
|
||||
#define TS73XX_FPGA_LOADER_BASE 0x03c00000
|
||||
|
||||
static struct resource ts73xx_fpga_resources[] = {
|
||||
{
|
||||
.start = EP93XX_CS1_PHYS_BASE + TS73XX_FPGA_LOADER_BASE,
|
||||
.end = EP93XX_CS1_PHYS_BASE + TS73XX_FPGA_LOADER_BASE + 1,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_device ts73xx_fpga_device = {
|
||||
.name = "ts73xx-fpga-mgr",
|
||||
.id = -1,
|
||||
.resource = ts73xx_fpga_resources,
|
||||
.num_resources = ARRAY_SIZE(ts73xx_fpga_resources),
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static void __init ts72xx_init_machine(void)
|
||||
{
|
||||
ep93xx_init_devices();
|
||||
|
@ -218,6 +240,10 @@ static void __init ts72xx_init_machine(void)
|
|||
platform_device_register(&ts72xx_wdt_device);
|
||||
|
||||
ep93xx_register_eth(&ts72xx_eth_data, 1);
|
||||
#if IS_ENABLED(CONFIG_FPGA_MGR_TS73XX)
|
||||
if (board_is_ts7300())
|
||||
platform_device_register(&ts73xx_fpga_device);
|
||||
#endif
|
||||
}
|
||||
|
||||
MACHINE_START(TS72XX, "Technologic Systems TS-72xx SBC")
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#include <linux/vmalloc.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/clockchips.h>
|
||||
|
||||
#include <linux/hyperv.h>
|
||||
|
||||
#ifdef CONFIG_HYPERV_TSCPAGE
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@
|
|||
* Recommend using hypercall for address space switches rather
|
||||
* than MOV to CR3 instruction
|
||||
*/
|
||||
#define HV_X64_MWAIT_RECOMMENDED (1 << 0)
|
||||
#define HV_X64_AS_SWITCH_RECOMMENDED (1 << 0)
|
||||
/* Recommend using hypercall for local TLB flushes rather
|
||||
* than INVLPG or MOV to CR3 instructions */
|
||||
#define HV_X64_LOCAL_TLB_FLUSH_RECOMMENDED (1 << 1)
|
||||
|
@ -147,6 +147,11 @@
|
|||
*/
|
||||
#define HV_X64_RELAXED_TIMING_RECOMMENDED (1 << 5)
|
||||
|
||||
/*
|
||||
* Virtual APIC support
|
||||
*/
|
||||
#define HV_X64_DEPRECATING_AEOI_RECOMMENDED (1 << 9)
|
||||
|
||||
/*
|
||||
* Crash notification flag.
|
||||
*/
|
||||
|
|
|
@ -49,6 +49,9 @@ void hyperv_vector_handler(struct pt_regs *regs)
|
|||
if (vmbus_handler)
|
||||
vmbus_handler();
|
||||
|
||||
if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED)
|
||||
ack_APIC_irq();
|
||||
|
||||
exiting_irq();
|
||||
set_irq_regs(old_regs);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ config ANDROID_BINDER_IPC
|
|||
config ANDROID_BINDER_DEVICES
|
||||
string "Android Binder devices"
|
||||
depends on ANDROID_BINDER_IPC
|
||||
default "binder"
|
||||
default "binder,hwbinder"
|
||||
---help---
|
||||
Default value for the binder.devices parameter.
|
||||
|
||||
|
|
|
@ -13,8 +13,22 @@ menuconfig AUXDISPLAY
|
|||
|
||||
If you say N, all options in this submenu will be skipped and disabled.
|
||||
|
||||
config CHARLCD
|
||||
tristate "Character LCD core support" if COMPILE_TEST
|
||||
|
||||
if AUXDISPLAY
|
||||
|
||||
config HD44780
|
||||
tristate "HD44780 Character LCD support"
|
||||
depends on GPIOLIB || COMPILE_TEST
|
||||
select CHARLCD
|
||||
---help---
|
||||
Enable support for Character LCDs using a HD44780 controller.
|
||||
The LCD is accessible through the /dev/lcd char device (10, 156).
|
||||
This code can either be compiled as a module, or linked into the
|
||||
kernel and started at boot.
|
||||
If you don't understand what all this is about, say N.
|
||||
|
||||
config KS0108
|
||||
tristate "KS0108 LCD Controller"
|
||||
depends on PARPORT_PC
|
||||
|
@ -142,3 +156,293 @@ config HT16K33
|
|||
LED controller driver with keyscan.
|
||||
|
||||
endif # AUXDISPLAY
|
||||
|
||||
config ARM_CHARLCD
|
||||
bool "ARM Ltd. Character LCD Driver"
|
||||
depends on PLAT_VERSATILE
|
||||
help
|
||||
This is a driver for the character LCD found on the ARM Ltd.
|
||||
Versatile and RealView Platform Baseboards. It doesn't do
|
||||
very much more than display the text "ARM Linux" on the first
|
||||
line and the Linux version on the second line, but that's
|
||||
still useful.
|
||||
|
||||
config PANEL
|
||||
tristate "Parallel port LCD/Keypad Panel support"
|
||||
depends on PARPORT
|
||||
select CHARLCD
|
||||
---help---
|
||||
Say Y here if you have an HD44780 or KS-0074 LCD connected to your
|
||||
parallel port. This driver also features 4 and 6-key keypads. The LCD
|
||||
is accessible through the /dev/lcd char device (10, 156), and the
|
||||
keypad through /dev/keypad (10, 185). This code can either be
|
||||
compiled as a module, or linked into the kernel and started at boot.
|
||||
If you don't understand what all this is about, say N.
|
||||
|
||||
if PANEL
|
||||
|
||||
config PANEL_PARPORT
|
||||
int "Default parallel port number (0=LPT1)"
|
||||
range 0 255
|
||||
default "0"
|
||||
---help---
|
||||
This is the index of the parallel port the panel is connected to. One
|
||||
driver instance only supports one parallel port, so if your keypad
|
||||
and LCD are connected to two separate ports, you have to start two
|
||||
modules with different arguments. Numbering starts with '0' for LPT1,
|
||||
and so on.
|
||||
|
||||
config PANEL_PROFILE
|
||||
int "Default panel profile (0-5, 0=custom)"
|
||||
range 0 5
|
||||
default "5"
|
||||
---help---
|
||||
To ease configuration, the driver supports different configuration
|
||||
profiles for past and recent wirings. These profiles can also be
|
||||
used to define an approximative configuration, completed by a few
|
||||
other options. Here are the profiles :
|
||||
|
||||
0 = custom (see further)
|
||||
1 = 2x16 parallel LCD, old keypad
|
||||
2 = 2x16 serial LCD (KS-0074), new keypad
|
||||
3 = 2x16 parallel LCD (Hantronix), no keypad
|
||||
4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad
|
||||
5 = 2x40 parallel LCD (old one), with old keypad
|
||||
|
||||
Custom configurations allow you to define how your display is
|
||||
wired to the parallel port, and how it works. This is only intended
|
||||
for experts.
|
||||
|
||||
config PANEL_KEYPAD
|
||||
depends on PANEL_PROFILE="0"
|
||||
int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)"
|
||||
range 0 3
|
||||
default 0
|
||||
---help---
|
||||
This enables and configures a keypad connected to the parallel port.
|
||||
The keys will be read from character device 10,185. Valid values are :
|
||||
|
||||
0 : do not enable this driver
|
||||
1 : old 6 keys keypad
|
||||
2 : new 6 keys keypad, as used on the server at www.ant-computing.com
|
||||
3 : Nexcom NSA1045's 4 keys keypad
|
||||
|
||||
New profiles can be described in the driver source. The driver also
|
||||
supports simultaneous keys pressed when the keypad supports them.
|
||||
|
||||
config PANEL_LCD
|
||||
depends on PANEL_PROFILE="0"
|
||||
int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)"
|
||||
range 0 5
|
||||
default 0
|
||||
---help---
|
||||
This enables and configures an LCD connected to the parallel port.
|
||||
The driver includes an interpreter for escape codes starting with
|
||||
'\e[L' which are specific to the LCD, and a few ANSI codes. The
|
||||
driver will be registered as character device 10,156, usually
|
||||
under the name '/dev/lcd'. There are a total of 6 supported types :
|
||||
|
||||
0 : do not enable the driver
|
||||
1 : custom configuration and wiring (see further)
|
||||
2 : 2x16 & 2x40 parallel LCD (old wiring)
|
||||
3 : 2x16 serial LCD (KS-0074 based)
|
||||
4 : 2x16 parallel LCD (Hantronix wiring)
|
||||
5 : 2x16 parallel LCD (Nexcom wiring)
|
||||
|
||||
When type '1' is specified, other options will appear to configure
|
||||
more precise aspects (wiring, dimensions, protocol, ...). Please note
|
||||
that those values changed from the 2.4 driver for better consistency.
|
||||
|
||||
config PANEL_LCD_HEIGHT
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Number of lines on the LCD (1-2)"
|
||||
range 1 2
|
||||
default 2
|
||||
---help---
|
||||
This is the number of visible character lines on the LCD in custom profile.
|
||||
It can either be 1 or 2.
|
||||
|
||||
config PANEL_LCD_WIDTH
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Number of characters per line on the LCD (1-40)"
|
||||
range 1 40
|
||||
default 40
|
||||
---help---
|
||||
This is the number of characters per line on the LCD in custom profile.
|
||||
Common values are 16,20,24,40.
|
||||
|
||||
config PANEL_LCD_BWIDTH
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Internal LCD line width (1-40, 40 by default)"
|
||||
range 1 40
|
||||
default 40
|
||||
---help---
|
||||
Most LCDs use a standard controller which supports hardware lines of 40
|
||||
characters, although sometimes only 16, 20 or 24 of them are really wired
|
||||
to the terminal. This results in some non-visible but addressable characters,
|
||||
and is the case for most parallel LCDs. Other LCDs, and some serial ones,
|
||||
however, use the same line width internally as what is visible. The KS0074
|
||||
for example, uses 16 characters per line for 16 visible characters per line.
|
||||
|
||||
This option lets you configure the value used by your LCD in 'custom' profile.
|
||||
If you don't know, put '40' here.
|
||||
|
||||
config PANEL_LCD_HWIDTH
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Hardware LCD line width (1-64, 64 by default)"
|
||||
range 1 64
|
||||
default 64
|
||||
---help---
|
||||
Most LCDs use a single address bit to differentiate line 0 and line 1. Since
|
||||
some of them need to be able to address 40 chars with the lower bits, they
|
||||
often use the immediately superior power of 2, which is 64, to address the
|
||||
next line.
|
||||
|
||||
If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and
|
||||
64 here for a 2x40.
|
||||
|
||||
config PANEL_LCD_CHARSET
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "LCD character set (0=normal, 1=KS0074)"
|
||||
range 0 1
|
||||
default 0
|
||||
---help---
|
||||
Some controllers such as the KS0074 use a somewhat strange character set
|
||||
where many symbols are at unusual places. The driver knows how to map
|
||||
'standard' ASCII characters to the character sets used by these controllers.
|
||||
Valid values are :
|
||||
|
||||
0 : normal (untranslated) character set
|
||||
1 : KS0074 character set
|
||||
|
||||
If you don't know, use the normal one (0).
|
||||
|
||||
config PANEL_LCD_PROTO
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "LCD communication mode (0=parallel 8 bits, 1=serial)"
|
||||
range 0 1
|
||||
default 0
|
||||
---help---
|
||||
This driver now supports any serial or parallel LCD wired to a parallel
|
||||
port. But before assigning signals, the driver needs to know if it will
|
||||
be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires
|
||||
(SDA/SCL), while parallel ones use 2 or 3 wires for the control signals
|
||||
(E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits
|
||||
parallel LCD, and 1 for a serial LCD.
|
||||
|
||||
config PANEL_LCD_PIN_E
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) "
|
||||
range -17 17
|
||||
default 14
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the LCD 'E'
|
||||
signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'E' pin in custom profile is '14' (AUTOFEED).
|
||||
|
||||
config PANEL_LCD_PIN_RS
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) "
|
||||
range -17 17
|
||||
default 17
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the LCD 'RS'
|
||||
signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'RS' pin in custom profile is '17' (SELECT IN).
|
||||
|
||||
config PANEL_LCD_PIN_RW
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) "
|
||||
range -17 17
|
||||
default 16
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the LCD 'RW'
|
||||
signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'RW' pin in custom profile is '16' (INIT).
|
||||
|
||||
config PANEL_LCD_PIN_SCL
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) "
|
||||
range -17 17
|
||||
default 1
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the serial
|
||||
LCD 'SCL' signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'SCL' pin in custom profile is '1' (STROBE).
|
||||
|
||||
config PANEL_LCD_PIN_SDA
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) "
|
||||
range -17 17
|
||||
default 2
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the serial
|
||||
LCD 'SDA' signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'SDA' pin in custom profile is '2' (D0).
|
||||
|
||||
config PANEL_LCD_PIN_BL
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) "
|
||||
range -17 17
|
||||
default 0
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the LCD 'BL' signal
|
||||
has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'BL' pin in custom profile is '0' (uncontrolled).
|
||||
|
||||
config PANEL_CHANGE_MESSAGE
|
||||
bool "Change LCD initialization message ?"
|
||||
default "n"
|
||||
---help---
|
||||
This allows you to replace the boot message indicating the kernel version
|
||||
and the driver version with a custom message. This is useful on appliances
|
||||
where a simple 'Starting system' message can be enough to stop a customer
|
||||
from worrying.
|
||||
|
||||
If you say 'Y' here, you'll be able to choose a message yourself. Otherwise,
|
||||
say 'N' and keep the default message with the version.
|
||||
|
||||
config PANEL_BOOT_MESSAGE
|
||||
depends on PANEL_CHANGE_MESSAGE="y"
|
||||
string "New initialization message"
|
||||
default ""
|
||||
---help---
|
||||
This allows you to replace the boot message indicating the kernel version
|
||||
and the driver version with a custom message. This is useful on appliances
|
||||
where a simple 'Starting system' message can be enough to stop a customer
|
||||
from worrying.
|
||||
|
||||
An empty message will only clear the display at driver init time. Any other
|
||||
printf()-formatted message is valid with newline and escape codes.
|
||||
|
||||
endif # PANEL
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
# Makefile for the kernel auxiliary displays device drivers.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_CHARLCD) += charlcd.o
|
||||
obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
|
||||
obj-$(CONFIG_KS0108) += ks0108.o
|
||||
obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o
|
||||
obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o
|
||||
obj-$(CONFIG_HD44780) += hd44780.o
|
||||
obj-$(CONFIG_HT16K33) += ht16k33.o
|
||||
obj-$(CONFIG_PANEL) += panel.o
|
||||
|
|
|
@ -0,0 +1,818 @@
|
|||
/*
|
||||
* Character LCD driver for Linux
|
||||
*
|
||||
* Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
|
||||
* Copyright (C) 2016-2017 Glider bvba
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <generated/utsrelease.h>
|
||||
|
||||
#include <misc/charlcd.h>
|
||||
|
||||
#define LCD_MINOR 156
|
||||
|
||||
#define DEFAULT_LCD_BWIDTH 40
|
||||
#define DEFAULT_LCD_HWIDTH 64
|
||||
|
||||
/* Keep the backlight on this many seconds for each flash */
|
||||
#define LCD_BL_TEMPO_PERIOD 4
|
||||
|
||||
#define LCD_FLAG_B 0x0004 /* Blink on */
|
||||
#define LCD_FLAG_C 0x0008 /* Cursor on */
|
||||
#define LCD_FLAG_D 0x0010 /* Display on */
|
||||
#define LCD_FLAG_F 0x0020 /* Large font mode */
|
||||
#define LCD_FLAG_N 0x0040 /* 2-rows mode */
|
||||
#define LCD_FLAG_L 0x0080 /* Backlight enabled */
|
||||
|
||||
/* LCD commands */
|
||||
#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
|
||||
|
||||
#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
|
||||
#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
|
||||
|
||||
#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
|
||||
#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
|
||||
#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
|
||||
#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
|
||||
|
||||
#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
|
||||
#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
|
||||
#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
|
||||
|
||||
#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
|
||||
#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
|
||||
#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
|
||||
#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
|
||||
|
||||
#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
|
||||
|
||||
#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
|
||||
|
||||
#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */
|
||||
#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */
|
||||
|
||||
struct charlcd_priv {
|
||||
struct charlcd lcd;
|
||||
|
||||
struct delayed_work bl_work;
|
||||
struct mutex bl_tempo_lock; /* Protects access to bl_tempo */
|
||||
bool bl_tempo;
|
||||
|
||||
bool must_clear;
|
||||
|
||||
/* contains the LCD config state */
|
||||
unsigned long int flags;
|
||||
|
||||
/* Contains the LCD X and Y offset */
|
||||
struct {
|
||||
unsigned long int x;
|
||||
unsigned long int y;
|
||||
} addr;
|
||||
|
||||
/* Current escape sequence and it's length or -1 if outside */
|
||||
struct {
|
||||
char buf[LCD_ESCAPE_LEN + 1];
|
||||
int len;
|
||||
} esc_seq;
|
||||
|
||||
unsigned long long drvdata[0];
|
||||
};
|
||||
|
||||
#define to_priv(p) container_of(p, struct charlcd_priv, lcd)
|
||||
|
||||
/* Device single-open policy control */
|
||||
static atomic_t charlcd_available = ATOMIC_INIT(1);
|
||||
|
||||
/* sleeps that many milliseconds with a reschedule */
|
||||
static void long_sleep(int ms)
|
||||
{
|
||||
if (in_interrupt())
|
||||
mdelay(ms);
|
||||
else
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(ms));
|
||||
}
|
||||
|
||||
/* turn the backlight on or off */
|
||||
static void charlcd_backlight(struct charlcd *lcd, int on)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
|
||||
if (!lcd->ops->backlight)
|
||||
return;
|
||||
|
||||
mutex_lock(&priv->bl_tempo_lock);
|
||||
if (!priv->bl_tempo)
|
||||
lcd->ops->backlight(lcd, on);
|
||||
mutex_unlock(&priv->bl_tempo_lock);
|
||||
}
|
||||
|
||||
static void charlcd_bl_off(struct work_struct *work)
|
||||
{
|
||||
struct delayed_work *dwork = to_delayed_work(work);
|
||||
struct charlcd_priv *priv =
|
||||
container_of(dwork, struct charlcd_priv, bl_work);
|
||||
|
||||
mutex_lock(&priv->bl_tempo_lock);
|
||||
if (priv->bl_tempo) {
|
||||
priv->bl_tempo = false;
|
||||
if (!(priv->flags & LCD_FLAG_L))
|
||||
priv->lcd.ops->backlight(&priv->lcd, 0);
|
||||
}
|
||||
mutex_unlock(&priv->bl_tempo_lock);
|
||||
}
|
||||
|
||||
/* turn the backlight on for a little while */
|
||||
void charlcd_poke(struct charlcd *lcd)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
|
||||
if (!lcd->ops->backlight)
|
||||
return;
|
||||
|
||||
cancel_delayed_work_sync(&priv->bl_work);
|
||||
|
||||
mutex_lock(&priv->bl_tempo_lock);
|
||||
if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
|
||||
lcd->ops->backlight(lcd, 1);
|
||||
priv->bl_tempo = true;
|
||||
schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
|
||||
mutex_unlock(&priv->bl_tempo_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(charlcd_poke);
|
||||
|
||||
static void charlcd_gotoxy(struct charlcd *lcd)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
unsigned int addr;
|
||||
|
||||
/*
|
||||
* we force the cursor to stay at the end of the
|
||||
* line if it wants to go farther
|
||||
*/
|
||||
addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1)
|
||||
: lcd->bwidth - 1;
|
||||
if (priv->addr.y & 1)
|
||||
addr += lcd->hwidth;
|
||||
if (priv->addr.y & 2)
|
||||
addr += lcd->bwidth;
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr);
|
||||
}
|
||||
|
||||
static void charlcd_home(struct charlcd *lcd)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
|
||||
priv->addr.x = 0;
|
||||
priv->addr.y = 0;
|
||||
charlcd_gotoxy(lcd);
|
||||
}
|
||||
|
||||
static void charlcd_print(struct charlcd *lcd, char c)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
|
||||
if (priv->addr.x < lcd->bwidth) {
|
||||
if (lcd->char_conv)
|
||||
c = lcd->char_conv[(unsigned char)c];
|
||||
lcd->ops->write_data(lcd, c);
|
||||
priv->addr.x++;
|
||||
}
|
||||
/* prevents the cursor from wrapping onto the next line */
|
||||
if (priv->addr.x == lcd->bwidth)
|
||||
charlcd_gotoxy(lcd);
|
||||
}
|
||||
|
||||
static void charlcd_clear_fast(struct charlcd *lcd)
|
||||
{
|
||||
int pos;
|
||||
|
||||
charlcd_home(lcd);
|
||||
|
||||
if (lcd->ops->clear_fast)
|
||||
lcd->ops->clear_fast(lcd);
|
||||
else
|
||||
for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++)
|
||||
lcd->ops->write_data(lcd, ' ');
|
||||
|
||||
charlcd_home(lcd);
|
||||
}
|
||||
|
||||
/* clears the display and resets X/Y */
|
||||
static void charlcd_clear_display(struct charlcd *lcd)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR);
|
||||
priv->addr.x = 0;
|
||||
priv->addr.y = 0;
|
||||
/* we must wait a few milliseconds (15) */
|
||||
long_sleep(15);
|
||||
}
|
||||
|
||||
static int charlcd_init_display(struct charlcd *lcd)
|
||||
{
|
||||
void (*write_cmd_raw)(struct charlcd *lcd, int cmd);
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
u8 init;
|
||||
|
||||
if (lcd->ifwidth != 4 && lcd->ifwidth != 8)
|
||||
return -EINVAL;
|
||||
|
||||
priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
|
||||
LCD_FLAG_C | LCD_FLAG_B;
|
||||
|
||||
long_sleep(20); /* wait 20 ms after power-up for the paranoid */
|
||||
|
||||
/*
|
||||
* 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure
|
||||
* the LCD is in 8-bit mode afterwards
|
||||
*/
|
||||
init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS;
|
||||
if (lcd->ifwidth == 4) {
|
||||
init >>= 4;
|
||||
write_cmd_raw = lcd->ops->write_cmd_raw4;
|
||||
} else {
|
||||
write_cmd_raw = lcd->ops->write_cmd;
|
||||
}
|
||||
write_cmd_raw(lcd, init);
|
||||
long_sleep(10);
|
||||
write_cmd_raw(lcd, init);
|
||||
long_sleep(10);
|
||||
write_cmd_raw(lcd, init);
|
||||
long_sleep(10);
|
||||
|
||||
if (lcd->ifwidth == 4) {
|
||||
/* Switch to 4-bit mode, 1 line, small fonts */
|
||||
lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4);
|
||||
long_sleep(10);
|
||||
}
|
||||
|
||||
/* set font height and lines number */
|
||||
lcd->ops->write_cmd(lcd,
|
||||
LCD_CMD_FUNCTION_SET |
|
||||
((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
|
||||
((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
|
||||
((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
|
||||
long_sleep(10);
|
||||
|
||||
/* display off, cursor off, blink off */
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL);
|
||||
long_sleep(10);
|
||||
|
||||
lcd->ops->write_cmd(lcd,
|
||||
LCD_CMD_DISPLAY_CTRL | /* set display mode */
|
||||
((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
|
||||
((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
|
||||
((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
|
||||
|
||||
charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0);
|
||||
|
||||
long_sleep(10);
|
||||
|
||||
/* entry mode set : increment, cursor shifting */
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
|
||||
|
||||
charlcd_clear_display(lcd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* These are the file operation function for user access to /dev/lcd
|
||||
* This function can also be called from inside the kernel, by
|
||||
* setting file and ppos to NULL.
|
||||
*
|
||||
*/
|
||||
|
||||
static inline int handle_lcd_special_code(struct charlcd *lcd)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
|
||||
/* LCD special codes */
|
||||
|
||||
int processed = 0;
|
||||
|
||||
char *esc = priv->esc_seq.buf + 2;
|
||||
int oldflags = priv->flags;
|
||||
|
||||
/* check for display mode flags */
|
||||
switch (*esc) {
|
||||
case 'D': /* Display ON */
|
||||
priv->flags |= LCD_FLAG_D;
|
||||
processed = 1;
|
||||
break;
|
||||
case 'd': /* Display OFF */
|
||||
priv->flags &= ~LCD_FLAG_D;
|
||||
processed = 1;
|
||||
break;
|
||||
case 'C': /* Cursor ON */
|
||||
priv->flags |= LCD_FLAG_C;
|
||||
processed = 1;
|
||||
break;
|
||||
case 'c': /* Cursor OFF */
|
||||
priv->flags &= ~LCD_FLAG_C;
|
||||
processed = 1;
|
||||
break;
|
||||
case 'B': /* Blink ON */
|
||||
priv->flags |= LCD_FLAG_B;
|
||||
processed = 1;
|
||||
break;
|
||||
case 'b': /* Blink OFF */
|
||||
priv->flags &= ~LCD_FLAG_B;
|
||||
processed = 1;
|
||||
break;
|
||||
case '+': /* Back light ON */
|
||||
priv->flags |= LCD_FLAG_L;
|
||||
processed = 1;
|
||||
break;
|
||||
case '-': /* Back light OFF */
|
||||
priv->flags &= ~LCD_FLAG_L;
|
||||
processed = 1;
|
||||
break;
|
||||
case '*': /* Flash back light */
|
||||
charlcd_poke(lcd);
|
||||
processed = 1;
|
||||
break;
|
||||
case 'f': /* Small Font */
|
||||
priv->flags &= ~LCD_FLAG_F;
|
||||
processed = 1;
|
||||
break;
|
||||
case 'F': /* Large Font */
|
||||
priv->flags |= LCD_FLAG_F;
|
||||
processed = 1;
|
||||
break;
|
||||
case 'n': /* One Line */
|
||||
priv->flags &= ~LCD_FLAG_N;
|
||||
processed = 1;
|
||||
break;
|
||||
case 'N': /* Two Lines */
|
||||
priv->flags |= LCD_FLAG_N;
|
||||
break;
|
||||
case 'l': /* Shift Cursor Left */
|
||||
if (priv->addr.x > 0) {
|
||||
/* back one char if not at end of line */
|
||||
if (priv->addr.x < lcd->bwidth)
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
|
||||
priv->addr.x--;
|
||||
}
|
||||
processed = 1;
|
||||
break;
|
||||
case 'r': /* shift cursor right */
|
||||
if (priv->addr.x < lcd->width) {
|
||||
/* allow the cursor to pass the end of the line */
|
||||
if (priv->addr.x < (lcd->bwidth - 1))
|
||||
lcd->ops->write_cmd(lcd,
|
||||
LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT);
|
||||
priv->addr.x++;
|
||||
}
|
||||
processed = 1;
|
||||
break;
|
||||
case 'L': /* shift display left */
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
|
||||
processed = 1;
|
||||
break;
|
||||
case 'R': /* shift display right */
|
||||
lcd->ops->write_cmd(lcd,
|
||||
LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
|
||||
LCD_CMD_SHIFT_RIGHT);
|
||||
processed = 1;
|
||||
break;
|
||||
case 'k': { /* kill end of line */
|
||||
int x;
|
||||
|
||||
for (x = priv->addr.x; x < lcd->bwidth; x++)
|
||||
lcd->ops->write_data(lcd, ' ');
|
||||
|
||||
/* restore cursor position */
|
||||
charlcd_gotoxy(lcd);
|
||||
processed = 1;
|
||||
break;
|
||||
}
|
||||
case 'I': /* reinitialize display */
|
||||
charlcd_init_display(lcd);
|
||||
processed = 1;
|
||||
break;
|
||||
case 'G': {
|
||||
/* Generator : LGcxxxxx...xx; must have <c> between '0'
|
||||
* and '7', representing the numerical ASCII code of the
|
||||
* redefined character, and <xx...xx> a sequence of 16
|
||||
* hex digits representing 8 bytes for each character.
|
||||
* Most LCDs will only use 5 lower bits of the 7 first
|
||||
* bytes.
|
||||
*/
|
||||
|
||||
unsigned char cgbytes[8];
|
||||
unsigned char cgaddr;
|
||||
int cgoffset;
|
||||
int shift;
|
||||
char value;
|
||||
int addr;
|
||||
|
||||
if (!strchr(esc, ';'))
|
||||
break;
|
||||
|
||||
esc++;
|
||||
|
||||
cgaddr = *(esc++) - '0';
|
||||
if (cgaddr > 7) {
|
||||
processed = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
cgoffset = 0;
|
||||
shift = 0;
|
||||
value = 0;
|
||||
while (*esc && cgoffset < 8) {
|
||||
shift ^= 4;
|
||||
if (*esc >= '0' && *esc <= '9') {
|
||||
value |= (*esc - '0') << shift;
|
||||
} else if (*esc >= 'A' && *esc <= 'Z') {
|
||||
value |= (*esc - 'A' + 10) << shift;
|
||||
} else if (*esc >= 'a' && *esc <= 'z') {
|
||||
value |= (*esc - 'a' + 10) << shift;
|
||||
} else {
|
||||
esc++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shift == 0) {
|
||||
cgbytes[cgoffset++] = value;
|
||||
value = 0;
|
||||
}
|
||||
|
||||
esc++;
|
||||
}
|
||||
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
|
||||
for (addr = 0; addr < cgoffset; addr++)
|
||||
lcd->ops->write_data(lcd, cgbytes[addr]);
|
||||
|
||||
/* ensures that we stop writing to CGRAM */
|
||||
charlcd_gotoxy(lcd);
|
||||
processed = 1;
|
||||
break;
|
||||
}
|
||||
case 'x': /* gotoxy : LxXXX[yYYY]; */
|
||||
case 'y': /* gotoxy : LyYYY[xXXX]; */
|
||||
if (!strchr(esc, ';'))
|
||||
break;
|
||||
|
||||
while (*esc) {
|
||||
if (*esc == 'x') {
|
||||
esc++;
|
||||
if (kstrtoul(esc, 10, &priv->addr.x) < 0)
|
||||
break;
|
||||
} else if (*esc == 'y') {
|
||||
esc++;
|
||||
if (kstrtoul(esc, 10, &priv->addr.y) < 0)
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
charlcd_gotoxy(lcd);
|
||||
processed = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TODO: This indent party here got ugly, clean it! */
|
||||
/* Check whether one flag was changed */
|
||||
if (oldflags == priv->flags)
|
||||
return processed;
|
||||
|
||||
/* check whether one of B,C,D flags were changed */
|
||||
if ((oldflags ^ priv->flags) &
|
||||
(LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D))
|
||||
/* set display mode */
|
||||
lcd->ops->write_cmd(lcd,
|
||||
LCD_CMD_DISPLAY_CTRL |
|
||||
((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
|
||||
((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
|
||||
((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
|
||||
/* check whether one of F,N flags was changed */
|
||||
else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N))
|
||||
lcd->ops->write_cmd(lcd,
|
||||
LCD_CMD_FUNCTION_SET |
|
||||
((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
|
||||
((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
|
||||
((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
|
||||
/* check whether L flag was changed */
|
||||
else if ((oldflags ^ priv->flags) & LCD_FLAG_L)
|
||||
charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L));
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
static void charlcd_write_char(struct charlcd *lcd, char c)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
|
||||
/* first, we'll test if we're in escape mode */
|
||||
if ((c != '\n') && priv->esc_seq.len >= 0) {
|
||||
/* yes, let's add this char to the buffer */
|
||||
priv->esc_seq.buf[priv->esc_seq.len++] = c;
|
||||
priv->esc_seq.buf[priv->esc_seq.len] = 0;
|
||||
} else {
|
||||
/* aborts any previous escape sequence */
|
||||
priv->esc_seq.len = -1;
|
||||
|
||||
switch (c) {
|
||||
case LCD_ESCAPE_CHAR:
|
||||
/* start of an escape sequence */
|
||||
priv->esc_seq.len = 0;
|
||||
priv->esc_seq.buf[priv->esc_seq.len] = 0;
|
||||
break;
|
||||
case '\b':
|
||||
/* go back one char and clear it */
|
||||
if (priv->addr.x > 0) {
|
||||
/*
|
||||
* check if we're not at the
|
||||
* end of the line
|
||||
*/
|
||||
if (priv->addr.x < lcd->bwidth)
|
||||
/* back one char */
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
|
||||
priv->addr.x--;
|
||||
}
|
||||
/* replace with a space */
|
||||
lcd->ops->write_data(lcd, ' ');
|
||||
/* back one char again */
|
||||
lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
|
||||
break;
|
||||
case '\014':
|
||||
/* quickly clear the display */
|
||||
charlcd_clear_fast(lcd);
|
||||
break;
|
||||
case '\n':
|
||||
/*
|
||||
* flush the remainder of the current line and
|
||||
* go to the beginning of the next line
|
||||
*/
|
||||
for (; priv->addr.x < lcd->bwidth; priv->addr.x++)
|
||||
lcd->ops->write_data(lcd, ' ');
|
||||
priv->addr.x = 0;
|
||||
priv->addr.y = (priv->addr.y + 1) % lcd->height;
|
||||
charlcd_gotoxy(lcd);
|
||||
break;
|
||||
case '\r':
|
||||
/* go to the beginning of the same line */
|
||||
priv->addr.x = 0;
|
||||
charlcd_gotoxy(lcd);
|
||||
break;
|
||||
case '\t':
|
||||
/* print a space instead of the tab */
|
||||
charlcd_print(lcd, ' ');
|
||||
break;
|
||||
default:
|
||||
/* simply print this char */
|
||||
charlcd_print(lcd, c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* now we'll see if we're in an escape mode and if the current
|
||||
* escape sequence can be understood.
|
||||
*/
|
||||
if (priv->esc_seq.len >= 2) {
|
||||
int processed = 0;
|
||||
|
||||
if (!strcmp(priv->esc_seq.buf, "[2J")) {
|
||||
/* clear the display */
|
||||
charlcd_clear_fast(lcd);
|
||||
processed = 1;
|
||||
} else if (!strcmp(priv->esc_seq.buf, "[H")) {
|
||||
/* cursor to home */
|
||||
charlcd_home(lcd);
|
||||
processed = 1;
|
||||
}
|
||||
/* codes starting with ^[[L */
|
||||
else if ((priv->esc_seq.len >= 3) &&
|
||||
(priv->esc_seq.buf[0] == '[') &&
|
||||
(priv->esc_seq.buf[1] == 'L')) {
|
||||
processed = handle_lcd_special_code(lcd);
|
||||
}
|
||||
|
||||
/* LCD special escape codes */
|
||||
/*
|
||||
* flush the escape sequence if it's been processed
|
||||
* or if it is getting too long.
|
||||
*/
|
||||
if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
|
||||
priv->esc_seq.len = -1;
|
||||
} /* escape codes */
|
||||
}
|
||||
|
||||
static struct charlcd *the_charlcd;
|
||||
|
||||
static ssize_t charlcd_write(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
const char __user *tmp = buf;
|
||||
char c;
|
||||
|
||||
for (; count-- > 0; (*ppos)++, tmp++) {
|
||||
if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
|
||||
/*
|
||||
* let's be a little nice with other processes
|
||||
* that need some CPU
|
||||
*/
|
||||
schedule();
|
||||
|
||||
if (get_user(c, tmp))
|
||||
return -EFAULT;
|
||||
|
||||
charlcd_write_char(the_charlcd, c);
|
||||
}
|
||||
|
||||
return tmp - buf;
|
||||
}
|
||||
|
||||
static int charlcd_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(the_charlcd);
|
||||
|
||||
if (!atomic_dec_and_test(&charlcd_available))
|
||||
return -EBUSY; /* open only once at a time */
|
||||
|
||||
if (file->f_mode & FMODE_READ) /* device is write-only */
|
||||
return -EPERM;
|
||||
|
||||
if (priv->must_clear) {
|
||||
charlcd_clear_display(&priv->lcd);
|
||||
priv->must_clear = false;
|
||||
}
|
||||
return nonseekable_open(inode, file);
|
||||
}
|
||||
|
||||
static int charlcd_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
atomic_inc(&charlcd_available);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations charlcd_fops = {
|
||||
.write = charlcd_write,
|
||||
.open = charlcd_open,
|
||||
.release = charlcd_release,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static struct miscdevice charlcd_dev = {
|
||||
.minor = LCD_MINOR,
|
||||
.name = "lcd",
|
||||
.fops = &charlcd_fops,
|
||||
};
|
||||
|
||||
static void charlcd_puts(struct charlcd *lcd, const char *s)
|
||||
{
|
||||
const char *tmp = s;
|
||||
int count = strlen(s);
|
||||
|
||||
for (; count-- > 0; tmp++) {
|
||||
if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
|
||||
/*
|
||||
* let's be a little nice with other processes
|
||||
* that need some CPU
|
||||
*/
|
||||
schedule();
|
||||
|
||||
charlcd_write_char(lcd, *tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/* initialize the LCD driver */
|
||||
static int charlcd_init(struct charlcd *lcd)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
int ret;
|
||||
|
||||
if (lcd->ops->backlight) {
|
||||
mutex_init(&priv->bl_tempo_lock);
|
||||
INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
|
||||
}
|
||||
|
||||
/*
|
||||
* before this line, we must NOT send anything to the display.
|
||||
* Since charlcd_init_display() needs to write data, we have to
|
||||
* enable mark the LCD initialized just before.
|
||||
*/
|
||||
ret = charlcd_init_display(lcd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* display a short message */
|
||||
#ifdef CONFIG_PANEL_CHANGE_MESSAGE
|
||||
#ifdef CONFIG_PANEL_BOOT_MESSAGE
|
||||
charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE);
|
||||
#endif
|
||||
#else
|
||||
charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\n");
|
||||
#endif
|
||||
/* clear the display on the next device opening */
|
||||
priv->must_clear = true;
|
||||
charlcd_home(lcd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct charlcd *charlcd_alloc(unsigned int drvdata_size)
|
||||
{
|
||||
struct charlcd_priv *priv;
|
||||
struct charlcd *lcd;
|
||||
|
||||
priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL);
|
||||
if (!priv)
|
||||
return NULL;
|
||||
|
||||
priv->esc_seq.len = -1;
|
||||
|
||||
lcd = &priv->lcd;
|
||||
lcd->ifwidth = 8;
|
||||
lcd->bwidth = DEFAULT_LCD_BWIDTH;
|
||||
lcd->hwidth = DEFAULT_LCD_HWIDTH;
|
||||
lcd->drvdata = priv->drvdata;
|
||||
|
||||
return lcd;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(charlcd_alloc);
|
||||
|
||||
static int panel_notify_sys(struct notifier_block *this, unsigned long code,
|
||||
void *unused)
|
||||
{
|
||||
struct charlcd *lcd = the_charlcd;
|
||||
|
||||
switch (code) {
|
||||
case SYS_DOWN:
|
||||
charlcd_puts(lcd,
|
||||
"\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
|
||||
break;
|
||||
case SYS_HALT:
|
||||
charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
|
||||
break;
|
||||
case SYS_POWER_OFF:
|
||||
charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block panel_notifier = {
|
||||
panel_notify_sys,
|
||||
NULL,
|
||||
0
|
||||
};
|
||||
|
||||
int charlcd_register(struct charlcd *lcd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = charlcd_init(lcd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = misc_register(&charlcd_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
the_charlcd = lcd;
|
||||
register_reboot_notifier(&panel_notifier);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(charlcd_register);
|
||||
|
||||
int charlcd_unregister(struct charlcd *lcd)
|
||||
{
|
||||
struct charlcd_priv *priv = to_priv(lcd);
|
||||
|
||||
unregister_reboot_notifier(&panel_notifier);
|
||||
charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
|
||||
misc_deregister(&charlcd_dev);
|
||||
the_charlcd = NULL;
|
||||
if (lcd->ops->backlight) {
|
||||
cancel_delayed_work_sync(&priv->bl_work);
|
||||
priv->lcd.ops->backlight(&priv->lcd, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(charlcd_unregister);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* HD44780 Character LCD driver for Linux
|
||||
*
|
||||
* Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
|
||||
* Copyright (C) 2016-2017 Glider bvba
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <misc/charlcd.h>
|
||||
|
||||
|
||||
enum hd44780_pin {
|
||||
/* Order does matter due to writing to GPIO array subsets! */
|
||||
PIN_DATA0, /* Optional */
|
||||
PIN_DATA1, /* Optional */
|
||||
PIN_DATA2, /* Optional */
|
||||
PIN_DATA3, /* Optional */
|
||||
PIN_DATA4,
|
||||
PIN_DATA5,
|
||||
PIN_DATA6,
|
||||
PIN_DATA7,
|
||||
PIN_CTRL_RS,
|
||||
PIN_CTRL_RW, /* Optional */
|
||||
PIN_CTRL_E,
|
||||
PIN_CTRL_BL, /* Optional */
|
||||
PIN_NUM
|
||||
};
|
||||
|
||||
struct hd44780 {
|
||||
struct gpio_desc *pins[PIN_NUM];
|
||||
};
|
||||
|
||||
static void hd44780_backlight(struct charlcd *lcd, int on)
|
||||
{
|
||||
struct hd44780 *hd = lcd->drvdata;
|
||||
|
||||
if (hd->pins[PIN_CTRL_BL])
|
||||
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_BL], on);
|
||||
}
|
||||
|
||||
static void hd44780_strobe_gpio(struct hd44780 *hd)
|
||||
{
|
||||
/* Maintain the data during 20 us before the strobe */
|
||||
udelay(20);
|
||||
|
||||
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 1);
|
||||
|
||||
/* Maintain the strobe during 40 us */
|
||||
udelay(40);
|
||||
|
||||
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 0);
|
||||
}
|
||||
|
||||
/* write to an LCD panel register in 8 bit GPIO mode */
|
||||
static void hd44780_write_gpio8(struct hd44780 *hd, u8 val, unsigned int rs)
|
||||
{
|
||||
int values[10]; /* for DATA[0-7], RS, RW */
|
||||
unsigned int i, n;
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
values[PIN_DATA0 + i] = !!(val & BIT(i));
|
||||
values[PIN_CTRL_RS] = rs;
|
||||
n = 9;
|
||||
if (hd->pins[PIN_CTRL_RW]) {
|
||||
values[PIN_CTRL_RW] = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
/* Present the data to the port */
|
||||
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA0], values);
|
||||
|
||||
hd44780_strobe_gpio(hd);
|
||||
}
|
||||
|
||||
/* write to an LCD panel register in 4 bit GPIO mode */
|
||||
static void hd44780_write_gpio4(struct hd44780 *hd, u8 val, unsigned int rs)
|
||||
{
|
||||
int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */
|
||||
unsigned int i, n;
|
||||
|
||||
/* High nibble + RS, RW */
|
||||
for (i = 4; i < 8; i++)
|
||||
values[PIN_DATA0 + i] = !!(val & BIT(i));
|
||||
values[PIN_CTRL_RS] = rs;
|
||||
n = 5;
|
||||
if (hd->pins[PIN_CTRL_RW]) {
|
||||
values[PIN_CTRL_RW] = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
/* Present the data to the port */
|
||||
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
|
||||
&values[PIN_DATA4]);
|
||||
|
||||
hd44780_strobe_gpio(hd);
|
||||
|
||||
/* Low nibble */
|
||||
for (i = 0; i < 4; i++)
|
||||
values[PIN_DATA4 + i] = !!(val & BIT(i));
|
||||
|
||||
/* Present the data to the port */
|
||||
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
|
||||
&values[PIN_DATA4]);
|
||||
|
||||
hd44780_strobe_gpio(hd);
|
||||
}
|
||||
|
||||
/* Send a command to the LCD panel in 8 bit GPIO mode */
|
||||
static void hd44780_write_cmd_gpio8(struct charlcd *lcd, int cmd)
|
||||
{
|
||||
struct hd44780 *hd = lcd->drvdata;
|
||||
|
||||
hd44780_write_gpio8(hd, cmd, 0);
|
||||
|
||||
/* The shortest command takes at least 120 us */
|
||||
udelay(120);
|
||||
}
|
||||
|
||||
/* Send data to the LCD panel in 8 bit GPIO mode */
|
||||
static void hd44780_write_data_gpio8(struct charlcd *lcd, int data)
|
||||
{
|
||||
struct hd44780 *hd = lcd->drvdata;
|
||||
|
||||
hd44780_write_gpio8(hd, data, 1);
|
||||
|
||||
/* The shortest data takes at least 45 us */
|
||||
udelay(45);
|
||||
}
|
||||
|
||||
static const struct charlcd_ops hd44780_ops_gpio8 = {
|
||||
.write_cmd = hd44780_write_cmd_gpio8,
|
||||
.write_data = hd44780_write_data_gpio8,
|
||||
.backlight = hd44780_backlight,
|
||||
};
|
||||
|
||||
/* Send a command to the LCD panel in 4 bit GPIO mode */
|
||||
static void hd44780_write_cmd_gpio4(struct charlcd *lcd, int cmd)
|
||||
{
|
||||
struct hd44780 *hd = lcd->drvdata;
|
||||
|
||||
hd44780_write_gpio4(hd, cmd, 0);
|
||||
|
||||
/* The shortest command takes at least 120 us */
|
||||
udelay(120);
|
||||
}
|
||||
|
||||
/* Send 4-bits of a command to the LCD panel in raw 4 bit GPIO mode */
|
||||
static void hd44780_write_cmd_raw_gpio4(struct charlcd *lcd, int cmd)
|
||||
{
|
||||
int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */
|
||||
struct hd44780 *hd = lcd->drvdata;
|
||||
unsigned int i, n;
|
||||
|
||||
/* Command nibble + RS, RW */
|
||||
for (i = 0; i < 4; i++)
|
||||
values[PIN_DATA4 + i] = !!(cmd & BIT(i));
|
||||
values[PIN_CTRL_RS] = 0;
|
||||
n = 5;
|
||||
if (hd->pins[PIN_CTRL_RW]) {
|
||||
values[PIN_CTRL_RW] = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
/* Present the data to the port */
|
||||
gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
|
||||
&values[PIN_DATA4]);
|
||||
|
||||
hd44780_strobe_gpio(hd);
|
||||
}
|
||||
|
||||
/* Send data to the LCD panel in 4 bit GPIO mode */
|
||||
static void hd44780_write_data_gpio4(struct charlcd *lcd, int data)
|
||||
{
|
||||
struct hd44780 *hd = lcd->drvdata;
|
||||
|
||||
hd44780_write_gpio4(hd, data, 1);
|
||||
|
||||
/* The shortest data takes at least 45 us */
|
||||
udelay(45);
|
||||
}
|
||||
|
||||
static const struct charlcd_ops hd44780_ops_gpio4 = {
|
||||
.write_cmd = hd44780_write_cmd_gpio4,
|
||||
.write_cmd_raw4 = hd44780_write_cmd_raw_gpio4,
|
||||
.write_data = hd44780_write_data_gpio4,
|
||||
.backlight = hd44780_backlight,
|
||||
};
|
||||
|
||||
static int hd44780_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
unsigned int i, base;
|
||||
struct charlcd *lcd;
|
||||
struct hd44780 *hd;
|
||||
int ifwidth, ret;
|
||||
|
||||
/* Required pins */
|
||||
ifwidth = gpiod_count(dev, "data");
|
||||
if (ifwidth < 0)
|
||||
return ifwidth;
|
||||
|
||||
switch (ifwidth) {
|
||||
case 4:
|
||||
base = PIN_DATA4;
|
||||
break;
|
||||
case 8:
|
||||
base = PIN_DATA0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
lcd = charlcd_alloc(sizeof(struct hd44780));
|
||||
if (!lcd)
|
||||
return -ENOMEM;
|
||||
|
||||
hd = lcd->drvdata;
|
||||
|
||||
for (i = 0; i < ifwidth; i++) {
|
||||
hd->pins[base + i] = devm_gpiod_get_index(dev, "data", i,
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(hd->pins[base + i])) {
|
||||
ret = PTR_ERR(hd->pins[base + i]);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
hd->pins[PIN_CTRL_E] = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(hd->pins[PIN_CTRL_E])) {
|
||||
ret = PTR_ERR(hd->pins[PIN_CTRL_E]);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hd->pins[PIN_CTRL_RS] = devm_gpiod_get(dev, "rs", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(hd->pins[PIN_CTRL_RS])) {
|
||||
ret = PTR_ERR(hd->pins[PIN_CTRL_RS]);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Optional pins */
|
||||
hd->pins[PIN_CTRL_RW] = devm_gpiod_get_optional(dev, "rw",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(hd->pins[PIN_CTRL_RW])) {
|
||||
ret = PTR_ERR(hd->pins[PIN_CTRL_RW]);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hd->pins[PIN_CTRL_BL] = devm_gpiod_get_optional(dev, "backlight",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(hd->pins[PIN_CTRL_BL])) {
|
||||
ret = PTR_ERR(hd->pins[PIN_CTRL_BL]);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Required properties */
|
||||
ret = device_property_read_u32(dev, "display-height-chars",
|
||||
&lcd->height);
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = device_property_read_u32(dev, "display-width-chars", &lcd->width);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* On displays with more than two rows, the internal buffer width is
|
||||
* usually equal to the display width
|
||||
*/
|
||||
if (lcd->height > 2)
|
||||
lcd->bwidth = lcd->width;
|
||||
|
||||
/* Optional properties */
|
||||
device_property_read_u32(dev, "internal-buffer-width", &lcd->bwidth);
|
||||
|
||||
lcd->ifwidth = ifwidth;
|
||||
lcd->ops = ifwidth == 8 ? &hd44780_ops_gpio8 : &hd44780_ops_gpio4;
|
||||
|
||||
ret = charlcd_register(lcd);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
platform_set_drvdata(pdev, lcd);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
kfree(lcd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hd44780_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct charlcd *lcd = platform_get_drvdata(pdev);
|
||||
|
||||
charlcd_unregister(lcd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id hd44780_of_match[] = {
|
||||
{ .compatible = "hit,hd44780" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, hd44780_of_match);
|
||||
|
||||
static struct platform_driver hd44780_driver = {
|
||||
.probe = hd44780_probe,
|
||||
.remove = hd44780_remove,
|
||||
.driver = {
|
||||
.name = "hd44780",
|
||||
.of_match_table = hd44780_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(hd44780_driver);
|
||||
MODULE_DESCRIPTION("HD44780 Character LCD driver");
|
||||
MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -254,18 +254,22 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad)
|
|||
{
|
||||
const unsigned short *keycodes = keypad->dev->keycode;
|
||||
u16 new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS];
|
||||
u8 data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2];
|
||||
__le16 data[HT16K33_MATRIX_KEYPAD_MAX_COLS];
|
||||
unsigned long bits_changed;
|
||||
int row, col, code;
|
||||
int rc;
|
||||
bool pressed = false;
|
||||
|
||||
if (i2c_smbus_read_i2c_block_data(keypad->client, 0x40, 6, data) != 6) {
|
||||
dev_err(&keypad->client->dev, "Failed to read key data\n");
|
||||
rc = i2c_smbus_read_i2c_block_data(keypad->client, 0x40,
|
||||
sizeof(data), (u8 *)data);
|
||||
if (rc != sizeof(data)) {
|
||||
dev_err(&keypad->client->dev,
|
||||
"Failed to read key data, rc=%d\n", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (col = 0; col < keypad->cols; col++) {
|
||||
new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2];
|
||||
new_state[col] = le16_to_cpu(data[col]);
|
||||
if (new_state[col])
|
||||
pressed = true;
|
||||
bits_changed = keypad->last_key_state[col] ^ new_state[col];
|
||||
|
@ -278,7 +282,7 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad)
|
|||
}
|
||||
}
|
||||
input_sync(keypad->dev);
|
||||
memcpy(keypad->last_key_state, new_state, sizeof(new_state));
|
||||
memcpy(keypad->last_key_state, new_state, sizeof(u16) * keypad->cols);
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
@ -353,6 +357,12 @@ static int ht16k33_keypad_probe(struct i2c_client *client,
|
|||
err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols);
|
||||
if (err)
|
||||
return err;
|
||||
if (rows > HT16K33_MATRIX_KEYPAD_MAX_ROWS ||
|
||||
cols > HT16K33_MATRIX_KEYPAD_MAX_COLS) {
|
||||
dev_err(&client->dev, "%u rows or %u cols out of range in DT\n",
|
||||
rows, cols);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
keypad->rows = rows;
|
||||
keypad->cols = cols;
|
||||
|
|
|
@ -220,6 +220,7 @@ static const struct of_device_id img_ascii_lcd_matches[] = {
|
|||
{ .compatible = "mti,sead3-lcd", .data = &sead3_config },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches);
|
||||
|
||||
/**
|
||||
* img_ascii_lcd_scroll() - scroll the display by a character
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,7 +32,7 @@
|
|||
* timer and 180 seconds for the margin of error. IOW, a timer is set
|
||||
* for 60 seconds. When the timer fires, the callback checks the
|
||||
* actual duration that the timer waited. If the duration exceeds the
|
||||
* alloted time and margin (here 60 + 180, or 240 seconds), the machine
|
||||
* allotted time and margin (here 60 + 180, or 240 seconds), the machine
|
||||
* is restarted. A healthy machine will have the duration match the
|
||||
* expected timeout very closely.
|
||||
*/
|
||||
|
|
|
@ -575,7 +575,7 @@ static inline unsigned long hpet_time_div(struct hpets *hpets,
|
|||
}
|
||||
|
||||
static int
|
||||
hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg,
|
||||
hpet_ioctl_common(struct hpet_dev *devp, unsigned int cmd, unsigned long arg,
|
||||
struct hpet_info *info)
|
||||
{
|
||||
struct hpet_timer __iomem *timer;
|
||||
|
|
|
@ -109,7 +109,7 @@ static const struct file_operations misc_proc_fops = {
|
|||
};
|
||||
#endif
|
||||
|
||||
static int misc_open(struct inode * inode, struct file * file)
|
||||
static int misc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
int minor = iminor(inode);
|
||||
struct miscdevice *c;
|
||||
|
@ -150,7 +150,7 @@ static int misc_open(struct inode * inode, struct file * file)
|
|||
err = 0;
|
||||
replace_fops(file, new_fops);
|
||||
if (file->f_op->open)
|
||||
err = file->f_op->open(inode,file);
|
||||
err = file->f_op->open(inode, file);
|
||||
fail:
|
||||
mutex_unlock(&misc_mtx);
|
||||
return err;
|
||||
|
@ -182,7 +182,7 @@ static const struct file_operations misc_fops = {
|
|||
* failure.
|
||||
*/
|
||||
|
||||
int misc_register(struct miscdevice * misc)
|
||||
int misc_register(struct miscdevice *misc)
|
||||
{
|
||||
dev_t dev;
|
||||
int err = 0;
|
||||
|
@ -194,6 +194,7 @@ int misc_register(struct miscdevice * misc)
|
|||
|
||||
if (is_dynamic) {
|
||||
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
|
||||
|
||||
if (i >= DYNAMIC_MINORS) {
|
||||
err = -EBUSY;
|
||||
goto out;
|
||||
|
@ -287,13 +288,13 @@ static int __init misc_init(void)
|
|||
goto fail_remove;
|
||||
|
||||
err = -EIO;
|
||||
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
|
||||
if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
|
||||
goto fail_printk;
|
||||
misc_class->devnode = misc_devnode;
|
||||
return 0;
|
||||
|
||||
fail_printk:
|
||||
printk("unable to get major %d for misc devices\n", MISC_MAJOR);
|
||||
pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
|
||||
class_destroy(misc_class);
|
||||
fail_remove:
|
||||
if (ret)
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/numa.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <linux/atomic.h>
|
||||
|
@ -89,7 +90,7 @@ static int is_sn2;
|
|||
* protect in fork case where multiple tasks share the vma_data.
|
||||
*/
|
||||
struct vma_data {
|
||||
atomic_t refcnt; /* Number of vmas sharing the data. */
|
||||
refcount_t refcnt; /* Number of vmas sharing the data. */
|
||||
spinlock_t lock; /* Serialize access to this structure. */
|
||||
int count; /* Number of pages allocated. */
|
||||
enum mspec_page_type type; /* Type of pages allocated. */
|
||||
|
@ -144,7 +145,7 @@ mspec_open(struct vm_area_struct *vma)
|
|||
struct vma_data *vdata;
|
||||
|
||||
vdata = vma->vm_private_data;
|
||||
atomic_inc(&vdata->refcnt);
|
||||
refcount_inc(&vdata->refcnt);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -162,7 +163,7 @@ mspec_close(struct vm_area_struct *vma)
|
|||
|
||||
vdata = vma->vm_private_data;
|
||||
|
||||
if (!atomic_dec_and_test(&vdata->refcnt))
|
||||
if (!refcount_dec_and_test(&vdata->refcnt))
|
||||
return;
|
||||
|
||||
last_index = (vdata->vm_end - vdata->vm_start) >> PAGE_SHIFT;
|
||||
|
@ -274,7 +275,7 @@ mspec_mmap(struct file *file, struct vm_area_struct *vma,
|
|||
vdata->vm_end = vma->vm_end;
|
||||
vdata->type = type;
|
||||
spin_lock_init(&vdata->lock);
|
||||
atomic_set(&vdata->refcnt, 1);
|
||||
refcount_set(&vdata->refcnt, 1);
|
||||
vma->vm_private_data = vdata;
|
||||
|
||||
vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
|
||||
|
|
|
@ -218,8 +218,6 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev,
|
|||
cdev_init(&chip->cdevs, &tpmrm_fops);
|
||||
chip->cdev.owner = THIS_MODULE;
|
||||
chip->cdevs.owner = THIS_MODULE;
|
||||
chip->cdev.kobj.parent = &chip->dev.kobj;
|
||||
chip->cdevs.kobj.parent = &chip->devs.kobj;
|
||||
|
||||
chip->work_space.context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
if (!chip->work_space.context_buf) {
|
||||
|
@ -275,45 +273,24 @@ static int tpm_add_char_device(struct tpm_chip *chip)
|
|||
{
|
||||
int rc;
|
||||
|
||||
rc = cdev_add(&chip->cdev, chip->dev.devt, 1);
|
||||
rc = cdev_device_add(&chip->cdev, &chip->dev);
|
||||
if (rc) {
|
||||
dev_err(&chip->dev,
|
||||
"unable to cdev_add() %s, major %d, minor %d, err=%d\n",
|
||||
"unable to cdev_device_add() %s, major %d, minor %d, err=%d\n",
|
||||
dev_name(&chip->dev), MAJOR(chip->dev.devt),
|
||||
MINOR(chip->dev.devt), rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = device_add(&chip->dev);
|
||||
if (rc) {
|
||||
dev_err(&chip->dev,
|
||||
"unable to device_register() %s, major %d, minor %d, err=%d\n",
|
||||
dev_name(&chip->dev), MAJOR(chip->dev.devt),
|
||||
MINOR(chip->dev.devt), rc);
|
||||
|
||||
cdev_del(&chip->cdev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (chip->flags & TPM_CHIP_FLAG_TPM2)
|
||||
rc = cdev_add(&chip->cdevs, chip->devs.devt, 1);
|
||||
if (rc) {
|
||||
dev_err(&chip->dev,
|
||||
"unable to cdev_add() %s, major %d, minor %d, err=%d\n",
|
||||
dev_name(&chip->devs), MAJOR(chip->devs.devt),
|
||||
MINOR(chip->devs.devt), rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (chip->flags & TPM_CHIP_FLAG_TPM2)
|
||||
rc = device_add(&chip->devs);
|
||||
if (rc) {
|
||||
dev_err(&chip->dev,
|
||||
"unable to device_register() %s, major %d, minor %d, err=%d\n",
|
||||
dev_name(&chip->devs), MAJOR(chip->devs.devt),
|
||||
MINOR(chip->devs.devt), rc);
|
||||
cdev_del(&chip->cdevs);
|
||||
return rc;
|
||||
if (chip->flags & TPM_CHIP_FLAG_TPM2) {
|
||||
rc = cdev_device_add(&chip->cdevs, &chip->devs);
|
||||
if (rc) {
|
||||
dev_err(&chip->devs,
|
||||
"unable to cdev_device_add() %s, major %d, minor %d, err=%d\n",
|
||||
dev_name(&chip->devs), MAJOR(chip->devs.devt),
|
||||
MINOR(chip->devs.devt), rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make the chip available. */
|
||||
|
@ -326,8 +303,7 @@ static int tpm_add_char_device(struct tpm_chip *chip)
|
|||
|
||||
static void tpm_del_char_device(struct tpm_chip *chip)
|
||||
{
|
||||
cdev_del(&chip->cdev);
|
||||
device_del(&chip->dev);
|
||||
cdev_device_del(&chip->cdev, &chip->dev);
|
||||
|
||||
/* Make the chip unavailable. */
|
||||
mutex_lock(&idr_lock);
|
||||
|
@ -449,10 +425,8 @@ void tpm_chip_unregister(struct tpm_chip *chip)
|
|||
{
|
||||
tpm_del_legacy_sysfs(chip);
|
||||
tpm_bios_log_teardown(chip);
|
||||
if (chip->flags & TPM_CHIP_FLAG_TPM2) {
|
||||
cdev_del(&chip->cdevs);
|
||||
device_del(&chip->devs);
|
||||
}
|
||||
if (chip->flags & TPM_CHIP_FLAG_TPM2)
|
||||
cdev_device_del(&chip->cdevs, &chip->devs);
|
||||
tpm_del_char_device(chip);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tpm_chip_unregister);
|
||||
|
|
|
@ -2304,7 +2304,7 @@ static int __init init(void)
|
|||
|
||||
pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL);
|
||||
if (!pdrvdata.debugfs_dir)
|
||||
pr_warning("Error creating debugfs dir for virtio-ports\n");
|
||||
pr_warn("Error creating debugfs dir for virtio-ports\n");
|
||||
INIT_LIST_HEAD(&pdrvdata.consoles);
|
||||
INIT_LIST_HEAD(&pdrvdata.portdevs);
|
||||
|
||||
|
|
|
@ -703,13 +703,8 @@ static void dax_dev_release(struct device *dev)
|
|||
kfree(dax_dev);
|
||||
}
|
||||
|
||||
static void unregister_dax_dev(void *dev)
|
||||
static void kill_dax_dev(struct dax_dev *dax_dev)
|
||||
{
|
||||
struct dax_dev *dax_dev = to_dax_dev(dev);
|
||||
struct cdev *cdev = &dax_dev->cdev;
|
||||
|
||||
dev_dbg(dev, "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* Note, rcu is not protecting the liveness of dax_dev, rcu is
|
||||
* ensuring that any fault handlers that might have seen
|
||||
|
@ -720,8 +715,17 @@ static void unregister_dax_dev(void *dev)
|
|||
dax_dev->alive = false;
|
||||
synchronize_srcu(&dax_srcu);
|
||||
unmap_mapping_range(dax_dev->inode->i_mapping, 0, 0, 1);
|
||||
cdev_del(cdev);
|
||||
device_unregister(dev);
|
||||
}
|
||||
|
||||
static void unregister_dax_dev(void *dev)
|
||||
{
|
||||
struct dax_dev *dax_dev = to_dax_dev(dev);
|
||||
|
||||
dev_dbg(dev, "%s\n", __func__);
|
||||
|
||||
kill_dax_dev(dax_dev);
|
||||
cdev_device_del(&dax_dev->cdev, dev);
|
||||
put_device(dev);
|
||||
}
|
||||
|
||||
struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
|
||||
|
@ -772,18 +776,13 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
|
|||
goto err_inode;
|
||||
}
|
||||
|
||||
/* device_initialize() so cdev can reference kobj parent */
|
||||
/* from here on we're committed to teardown via dax_dev_release() */
|
||||
device_initialize(dev);
|
||||
|
||||
cdev = &dax_dev->cdev;
|
||||
cdev_init(cdev, &dax_fops);
|
||||
cdev->owner = parent->driver->owner;
|
||||
cdev->kobj.parent = &dev->kobj;
|
||||
rc = cdev_add(&dax_dev->cdev, dev_t, 1);
|
||||
if (rc)
|
||||
goto err_cdev;
|
||||
|
||||
/* from here on we're committed to teardown via dax_dev_release() */
|
||||
dax_dev->num_resources = count;
|
||||
dax_dev->alive = true;
|
||||
dax_dev->region = dax_region;
|
||||
|
@ -795,8 +794,10 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
|
|||
dev->groups = dax_attribute_groups;
|
||||
dev->release = dax_dev_release;
|
||||
dev_set_name(dev, "dax%d.%d", dax_region->id, dax_dev->id);
|
||||
rc = device_add(dev);
|
||||
|
||||
rc = cdev_device_add(cdev, dev);
|
||||
if (rc) {
|
||||
kill_dax_dev(dax_dev);
|
||||
put_device(dev);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
@ -807,8 +808,6 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
|
|||
|
||||
return dax_dev;
|
||||
|
||||
err_cdev:
|
||||
iput(dax_dev->inode);
|
||||
err_inode:
|
||||
ida_simple_remove(&dax_minor_ida, minor);
|
||||
err_minor:
|
||||
|
|
|
@ -52,6 +52,13 @@ config EXTCON_INTEL_INT3496
|
|||
This ACPI device is typically found on Intel Baytrail or Cherrytrail
|
||||
based tablets, or other Baytrail / Cherrytrail devices.
|
||||
|
||||
config EXTCON_INTEL_CHT_WC
|
||||
tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver"
|
||||
depends on INTEL_SOC_PMIC_CHTWC
|
||||
help
|
||||
Say Y here to enable extcon support for charger detection / control
|
||||
on the Intel Cherrytrail Whiskey Cove PMIC.
|
||||
|
||||
config EXTCON_MAX14577
|
||||
tristate "Maxim MAX14577/77836 EXTCON Support"
|
||||
depends on MFD_MAX14577
|
||||
|
|
|
@ -9,6 +9,7 @@ obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
|
|||
obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
|
||||
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
|
||||
obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
|
||||
obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
|
||||
obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
|
||||
obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o
|
||||
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
|
||||
|
|
|
@ -51,6 +51,9 @@
|
|||
#define HPDET_DEBOUNCE 500
|
||||
#define DEFAULT_MICD_TIMEOUT 2000
|
||||
|
||||
#define ARIZONA_HPDET_WAIT_COUNT 15
|
||||
#define ARIZONA_HPDET_WAIT_DELAY_MS 20
|
||||
|
||||
#define QUICK_HEADPHONE_MAX_OHM 3
|
||||
#define MICROPHONE_MIN_OHM 1257
|
||||
#define MICROPHONE_MAX_OHM 30000
|
||||
|
@ -1049,6 +1052,40 @@ static void arizona_hpdet_work(struct work_struct *work)
|
|||
mutex_unlock(&info->lock);
|
||||
}
|
||||
|
||||
static int arizona_hpdet_wait(struct arizona_extcon_info *info)
|
||||
{
|
||||
struct arizona *arizona = info->arizona;
|
||||
unsigned int val;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < ARIZONA_HPDET_WAIT_COUNT; i++) {
|
||||
ret = regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_2,
|
||||
&val);
|
||||
if (ret) {
|
||||
dev_err(arizona->dev,
|
||||
"Failed to read HPDET state: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (info->hpdet_ip_version) {
|
||||
case 0:
|
||||
if (val & ARIZONA_HP_DONE)
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
if (val & ARIZONA_HP_DONE_B)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
msleep(ARIZONA_HPDET_WAIT_DELAY_MS);
|
||||
}
|
||||
|
||||
dev_warn(arizona->dev, "HPDET did not appear to complete\n");
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static irqreturn_t arizona_jackdet(int irq, void *data)
|
||||
{
|
||||
struct arizona_extcon_info *info = data;
|
||||
|
@ -1155,6 +1192,15 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
|
|||
"Removal report failed: %d\n", ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the jack was removed during a headphone detection we
|
||||
* need to wait for the headphone detection to finish, as
|
||||
* it can not be aborted. We don't want to be able to start
|
||||
* a new headphone detection from a fresh insert until this
|
||||
* one is finished.
|
||||
*/
|
||||
arizona_hpdet_wait(info);
|
||||
|
||||
regmap_update_bits(arizona->regmap,
|
||||
ARIZONA_JACK_DETECT_DEBOUNCE,
|
||||
ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB,
|
||||
|
|
|
@ -0,0 +1,395 @@
|
|||
/*
|
||||
* Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC
|
||||
* Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
|
||||
*
|
||||
* Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
|
||||
* Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/extcon.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/intel_soc_pmic.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define CHT_WC_PHYCTRL 0x5e07
|
||||
|
||||
#define CHT_WC_CHGRCTRL0 0x5e16
|
||||
#define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0)
|
||||
#define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1)
|
||||
#define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2)
|
||||
#define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3)
|
||||
#define CHT_WC_CHGRCTRL0_TTLCK_MASK BIT(4)
|
||||
#define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK BIT(5)
|
||||
#define CHT_WC_CHGRCTRL0_DBPOFF_MASK BIT(6)
|
||||
#define CHT_WC_CHGRCTRL0_WDT_NOKICK BIT(7)
|
||||
|
||||
#define CHT_WC_CHGRCTRL1 0x5e17
|
||||
|
||||
#define CHT_WC_USBSRC 0x5e29
|
||||
#define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0)
|
||||
#define CHT_WC_USBSRC_STS_SUCCESS 2
|
||||
#define CHT_WC_USBSRC_STS_FAIL 3
|
||||
#define CHT_WC_USBSRC_TYPE_SHIFT 2
|
||||
#define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2)
|
||||
#define CHT_WC_USBSRC_TYPE_NONE 0
|
||||
#define CHT_WC_USBSRC_TYPE_SDP 1
|
||||
#define CHT_WC_USBSRC_TYPE_DCP 2
|
||||
#define CHT_WC_USBSRC_TYPE_CDP 3
|
||||
#define CHT_WC_USBSRC_TYPE_ACA 4
|
||||
#define CHT_WC_USBSRC_TYPE_SE1 5
|
||||
#define CHT_WC_USBSRC_TYPE_MHL 6
|
||||
#define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN 7
|
||||
#define CHT_WC_USBSRC_TYPE_OTHER 8
|
||||
#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9
|
||||
|
||||
#define CHT_WC_PWRSRC_IRQ 0x6e03
|
||||
#define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f
|
||||
#define CHT_WC_PWRSRC_STS 0x6e1e
|
||||
#define CHT_WC_PWRSRC_VBUS BIT(0)
|
||||
#define CHT_WC_PWRSRC_DC BIT(1)
|
||||
#define CHT_WC_PWRSRC_BAT BIT(2)
|
||||
#define CHT_WC_PWRSRC_ID_GND BIT(3)
|
||||
#define CHT_WC_PWRSRC_ID_FLOAT BIT(4)
|
||||
|
||||
#define CHT_WC_VBUS_GPIO_CTLO 0x6e2d
|
||||
#define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0)
|
||||
|
||||
enum cht_wc_usb_id {
|
||||
USB_ID_OTG,
|
||||
USB_ID_GND,
|
||||
USB_ID_FLOAT,
|
||||
USB_RID_A,
|
||||
USB_RID_B,
|
||||
USB_RID_C,
|
||||
};
|
||||
|
||||
enum cht_wc_mux_select {
|
||||
MUX_SEL_PMIC = 0,
|
||||
MUX_SEL_SOC,
|
||||
};
|
||||
|
||||
static const unsigned int cht_wc_extcon_cables[] = {
|
||||
EXTCON_USB,
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_CHG_USB_SDP,
|
||||
EXTCON_CHG_USB_CDP,
|
||||
EXTCON_CHG_USB_DCP,
|
||||
EXTCON_CHG_USB_ACA,
|
||||
EXTCON_NONE,
|
||||
};
|
||||
|
||||
struct cht_wc_extcon_data {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct extcon_dev *edev;
|
||||
unsigned int previous_cable;
|
||||
bool usb_host;
|
||||
};
|
||||
|
||||
static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
|
||||
{
|
||||
if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND)
|
||||
return USB_ID_GND;
|
||||
if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT)
|
||||
return USB_ID_FLOAT;
|
||||
|
||||
/*
|
||||
* Once we have iio support for the gpadc we should read the USBID
|
||||
* gpadc channel here and determine ACA role based on that.
|
||||
*/
|
||||
return USB_ID_FLOAT;
|
||||
}
|
||||
|
||||
static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
|
||||
bool ignore_errors)
|
||||
{
|
||||
int ret, usbsrc, status;
|
||||
unsigned long timeout;
|
||||
|
||||
/* Charger detection can take upto 600ms, wait 800ms max. */
|
||||
timeout = jiffies + msecs_to_jiffies(800);
|
||||
do {
|
||||
ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc);
|
||||
if (ret) {
|
||||
dev_err(ext->dev, "Error reading usbsrc: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
status = usbsrc & CHT_WC_USBSRC_STS_MASK;
|
||||
if (status == CHT_WC_USBSRC_STS_SUCCESS ||
|
||||
status == CHT_WC_USBSRC_STS_FAIL)
|
||||
break;
|
||||
|
||||
msleep(50); /* Wait a bit before retrying */
|
||||
} while (time_before(jiffies, timeout));
|
||||
|
||||
if (status != CHT_WC_USBSRC_STS_SUCCESS) {
|
||||
if (ignore_errors)
|
||||
return EXTCON_CHG_USB_SDP; /* Save fallback */
|
||||
|
||||
if (status == CHT_WC_USBSRC_STS_FAIL)
|
||||
dev_warn(ext->dev, "Could not detect charger type\n");
|
||||
else
|
||||
dev_warn(ext->dev, "Timeout detecting charger type\n");
|
||||
return EXTCON_CHG_USB_SDP; /* Save fallback */
|
||||
}
|
||||
|
||||
usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT;
|
||||
switch (usbsrc) {
|
||||
default:
|
||||
dev_warn(ext->dev,
|
||||
"Unhandled charger type %d, defaulting to SDP\n",
|
||||
ret);
|
||||
/* Fall through, treat as SDP */
|
||||
case CHT_WC_USBSRC_TYPE_SDP:
|
||||
case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN:
|
||||
case CHT_WC_USBSRC_TYPE_OTHER:
|
||||
return EXTCON_CHG_USB_SDP;
|
||||
case CHT_WC_USBSRC_TYPE_CDP:
|
||||
return EXTCON_CHG_USB_CDP;
|
||||
case CHT_WC_USBSRC_TYPE_DCP:
|
||||
case CHT_WC_USBSRC_TYPE_DCP_EXTPHY:
|
||||
case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
|
||||
return EXTCON_CHG_USB_DCP;
|
||||
case CHT_WC_USBSRC_TYPE_ACA:
|
||||
return EXTCON_CHG_USB_ACA;
|
||||
}
|
||||
}
|
||||
|
||||
static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state);
|
||||
if (ret)
|
||||
dev_err(ext->dev, "Error writing phyctrl: %d\n", ret);
|
||||
}
|
||||
|
||||
static void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext,
|
||||
bool enable)
|
||||
{
|
||||
int ret, val;
|
||||
|
||||
val = enable ? CHT_WC_VBUS_GPIO_CTLO_OUTPUT : 0;
|
||||
|
||||
/*
|
||||
* The 5V boost converter is enabled through a gpio on the PMIC, since
|
||||
* there currently is no gpio driver we access the gpio reg directly.
|
||||
*/
|
||||
ret = regmap_update_bits(ext->regmap, CHT_WC_VBUS_GPIO_CTLO,
|
||||
CHT_WC_VBUS_GPIO_CTLO_OUTPUT, val);
|
||||
if (ret)
|
||||
dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret);
|
||||
}
|
||||
|
||||
/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
|
||||
static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext,
|
||||
unsigned int cable, bool state)
|
||||
{
|
||||
extcon_set_state_sync(ext->edev, cable, state);
|
||||
if (cable == EXTCON_CHG_USB_SDP)
|
||||
extcon_set_state_sync(ext->edev, EXTCON_USB, state);
|
||||
}
|
||||
|
||||
static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
|
||||
{
|
||||
int ret, pwrsrc_sts, id;
|
||||
unsigned int cable = EXTCON_NONE;
|
||||
/* Ignore errors in host mode, as the 5v boost converter is on then */
|
||||
bool ignore_get_charger_errors = ext->usb_host;
|
||||
|
||||
ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
|
||||
if (ret) {
|
||||
dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
id = cht_wc_extcon_get_id(ext, pwrsrc_sts);
|
||||
if (id == USB_ID_GND) {
|
||||
/* The 5v boost causes a false VBUS / SDP detect, skip */
|
||||
goto charger_det_done;
|
||||
}
|
||||
|
||||
/* Plugged into a host/charger or not connected? */
|
||||
if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) {
|
||||
/* Route D+ and D- to PMIC for future charger detection */
|
||||
cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
|
||||
goto set_state;
|
||||
}
|
||||
|
||||
ret = cht_wc_extcon_get_charger(ext, ignore_get_charger_errors);
|
||||
if (ret >= 0)
|
||||
cable = ret;
|
||||
|
||||
charger_det_done:
|
||||
/* Route D+ and D- to SoC for the host or gadget controller */
|
||||
cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC);
|
||||
|
||||
set_state:
|
||||
if (cable != ext->previous_cable) {
|
||||
cht_wc_extcon_set_state(ext, cable, true);
|
||||
cht_wc_extcon_set_state(ext, ext->previous_cable, false);
|
||||
ext->previous_cable = cable;
|
||||
}
|
||||
|
||||
ext->usb_host = ((id == USB_ID_GND) || (id == USB_RID_A));
|
||||
extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host);
|
||||
}
|
||||
|
||||
static irqreturn_t cht_wc_extcon_isr(int irq, void *data)
|
||||
{
|
||||
struct cht_wc_extcon_data *ext = data;
|
||||
int ret, irqs;
|
||||
|
||||
ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs);
|
||||
if (ret) {
|
||||
dev_err(ext->dev, "Error reading irqs: %d\n", ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
cht_wc_extcon_pwrsrc_event(ext);
|
||||
|
||||
ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs);
|
||||
if (ret) {
|
||||
dev_err(ext->dev, "Error writing irqs: %d\n", ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
|
||||
{
|
||||
int ret, mask, val;
|
||||
|
||||
mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK;
|
||||
val = enable ? mask : 0;
|
||||
ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val);
|
||||
if (ret)
|
||||
dev_err(ext->dev, "Error setting sw control: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cht_wc_extcon_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
|
||||
struct cht_wc_extcon_data *ext;
|
||||
int irq, ret;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL);
|
||||
if (!ext)
|
||||
return -ENOMEM;
|
||||
|
||||
ext->dev = &pdev->dev;
|
||||
ext->regmap = pmic->regmap;
|
||||
ext->previous_cable = EXTCON_NONE;
|
||||
|
||||
/* Initialize extcon device */
|
||||
ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables);
|
||||
if (IS_ERR(ext->edev))
|
||||
return PTR_ERR(ext->edev);
|
||||
|
||||
/*
|
||||
* When a host-cable is detected the BIOS enables an external 5v boost
|
||||
* converter to power connected devices there are 2 problems with this:
|
||||
* 1) This gets seen by the external battery charger as a valid Vbus
|
||||
* supply and it then tries to feed Vsys from this creating a
|
||||
* feedback loop which causes aprox. 300 mA extra battery drain
|
||||
* (and unless we drive the external-charger-disable pin high it
|
||||
* also tries to charge the battery causing even more feedback).
|
||||
* 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply
|
||||
* Since the external battery charger has its own 5v boost converter
|
||||
* which does not have these issues, we simply turn the separate
|
||||
* external 5v boost converter off and leave it off entirely.
|
||||
*/
|
||||
cht_wc_extcon_set_5v_boost(ext, false);
|
||||
|
||||
/* Enable sw control */
|
||||
ret = cht_wc_extcon_sw_control(ext, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Register extcon device */
|
||||
ret = devm_extcon_dev_register(ext->dev, ext->edev);
|
||||
if (ret) {
|
||||
dev_err(ext->dev, "Error registering extcon device: %d\n", ret);
|
||||
goto disable_sw_control;
|
||||
}
|
||||
|
||||
/* Route D+ and D- to PMIC for initial charger detection */
|
||||
cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
|
||||
|
||||
/* Get initial state */
|
||||
cht_wc_extcon_pwrsrc_event(ext);
|
||||
|
||||
ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr,
|
||||
IRQF_ONESHOT, pdev->name, ext);
|
||||
if (ret) {
|
||||
dev_err(ext->dev, "Error requesting interrupt: %d\n", ret);
|
||||
goto disable_sw_control;
|
||||
}
|
||||
|
||||
/* Unmask irqs */
|
||||
ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK,
|
||||
(int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND |
|
||||
CHT_WC_PWRSRC_ID_FLOAT));
|
||||
if (ret) {
|
||||
dev_err(ext->dev, "Error writing irq-mask: %d\n", ret);
|
||||
goto disable_sw_control;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ext);
|
||||
|
||||
return 0;
|
||||
|
||||
disable_sw_control:
|
||||
cht_wc_extcon_sw_control(ext, false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cht_wc_extcon_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev);
|
||||
|
||||
cht_wc_extcon_sw_control(ext, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id cht_wc_extcon_table[] = {
|
||||
{ .name = "cht_wcove_pwrsrc" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table);
|
||||
|
||||
static struct platform_driver cht_wc_extcon_driver = {
|
||||
.probe = cht_wc_extcon_probe,
|
||||
.remove = cht_wc_extcon_remove,
|
||||
.id_table = cht_wc_extcon_table,
|
||||
.driver = {
|
||||
.name = "cht_wcove_pwrsrc",
|
||||
},
|
||||
};
|
||||
module_platform_driver(cht_wc_extcon_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver");
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -413,6 +413,12 @@ static int palmas_usb_resume(struct device *dev)
|
|||
if (palmas_usb->enable_gpio_id_detection)
|
||||
disable_irq_wake(palmas_usb->gpio_id_irq);
|
||||
}
|
||||
|
||||
/* check if GPIO states changed while suspend/resume */
|
||||
if (palmas_usb->enable_gpio_vbus_detection)
|
||||
palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb);
|
||||
palmas_gpio_id_detect(&palmas_usb->wq_detectid.work);
|
||||
|
||||
return 0;
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
|
||||
#define USB_GPIO_DEBOUNCE_MS 20 /* ms */
|
||||
|
@ -111,7 +110,7 @@ static int usb_extcon_probe(struct platform_device *pdev)
|
|||
struct usb_extcon_info *info;
|
||||
int ret;
|
||||
|
||||
if (!np && !ACPI_HANDLE(dev))
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
|
||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||
|
@ -195,7 +194,7 @@ static int usb_extcon_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
device_init_wakeup(dev, true);
|
||||
device_set_wakeup_capable(&pdev->dev, true);
|
||||
|
||||
/* Perform initial detection */
|
||||
usb_extcon_detect_cable(&info->wq_detcable.work);
|
||||
|
@ -282,9 +281,8 @@ static int usb_extcon_resume(struct device *dev)
|
|||
if (info->vbus_gpiod)
|
||||
enable_irq(info->vbus_irq);
|
||||
|
||||
if (!device_may_wakeup(dev))
|
||||
queue_delayed_work(system_power_efficient_wq,
|
||||
&info->wq_detcable, 0);
|
||||
queue_delayed_work(system_power_efficient_wq,
|
||||
&info->wq_detcable, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -230,9 +230,6 @@ struct extcon_cable {
|
|||
};
|
||||
|
||||
static struct class *extcon_class;
|
||||
#if defined(CONFIG_ANDROID)
|
||||
static struct class_compat *switch_class;
|
||||
#endif /* CONFIG_ANDROID */
|
||||
|
||||
static LIST_HEAD(extcon_dev_list);
|
||||
static DEFINE_MUTEX(extcon_dev_list_lock);
|
||||
|
@ -380,7 +377,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
|||
for (i = 0; i < edev->max_supported; i++) {
|
||||
count += sprintf(buf + count, "%s=%d\n",
|
||||
extcon_info[edev->supported_cable[i]].name,
|
||||
!!(edev->state & (1 << i)));
|
||||
!!(edev->state & BIT(i)));
|
||||
}
|
||||
|
||||
return count;
|
||||
|
@ -1032,12 +1029,6 @@ static int create_extcon_class(void)
|
|||
if (IS_ERR(extcon_class))
|
||||
return PTR_ERR(extcon_class);
|
||||
extcon_class->dev_groups = extcon_groups;
|
||||
|
||||
#if defined(CONFIG_ANDROID)
|
||||
switch_class = class_compat_register("switch");
|
||||
if (WARN(!switch_class, "cannot allocate"))
|
||||
return -ENOMEM;
|
||||
#endif /* CONFIG_ANDROID */
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -1259,10 +1250,6 @@ int extcon_dev_register(struct extcon_dev *edev)
|
|||
put_device(&edev->dev);
|
||||
goto err_dev;
|
||||
}
|
||||
#if defined(CONFIG_ANDROID)
|
||||
if (switch_class)
|
||||
ret = class_compat_create_link(switch_class, &edev->dev, NULL);
|
||||
#endif /* CONFIG_ANDROID */
|
||||
|
||||
spin_lock_init(&edev->lock);
|
||||
|
||||
|
@ -1350,10 +1337,6 @@ void extcon_dev_unregister(struct extcon_dev *edev)
|
|||
kfree(edev->cables);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_ANDROID)
|
||||
if (switch_class)
|
||||
class_compat_remove_link(switch_class, &edev->dev, NULL);
|
||||
#endif
|
||||
put_device(&edev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_dev_unregister);
|
||||
|
@ -1424,9 +1407,6 @@ module_init(extcon_class_init);
|
|||
|
||||
static void __exit extcon_class_exit(void)
|
||||
{
|
||||
#if defined(CONFIG_ANDROID)
|
||||
class_compat_unregister(switch_class);
|
||||
#endif
|
||||
class_destroy(extcon_class);
|
||||
}
|
||||
module_exit(extcon_class_exit);
|
||||
|
|
|
@ -124,7 +124,7 @@ static struct fw_node *fw_node_create(u32 sid, int port_count, int color)
|
|||
node->initiated_reset = SELF_ID_PHY_INITIATOR(sid);
|
||||
node->port_count = port_count;
|
||||
|
||||
atomic_set(&node->ref_count, 1);
|
||||
refcount_set(&node->ref_count, 1);
|
||||
INIT_LIST_HEAD(&node->link);
|
||||
|
||||
return node;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/refcount.h>
|
||||
|
||||
struct device;
|
||||
struct fw_card;
|
||||
|
@ -184,7 +184,7 @@ struct fw_node {
|
|||
* local node to this node. */
|
||||
u8 max_depth:4; /* Maximum depth to any leaf node */
|
||||
u8 max_hops:4; /* Max hops in this sub tree */
|
||||
atomic_t ref_count;
|
||||
refcount_t ref_count;
|
||||
|
||||
/* For serializing node topology into a list. */
|
||||
struct list_head link;
|
||||
|
@ -197,14 +197,14 @@ struct fw_node {
|
|||
|
||||
static inline struct fw_node *fw_node_get(struct fw_node *node)
|
||||
{
|
||||
atomic_inc(&node->ref_count);
|
||||
refcount_inc(&node->ref_count);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static inline void fw_node_put(struct fw_node *node)
|
||||
{
|
||||
if (atomic_dec_and_test(&node->ref_count))
|
||||
if (refcount_dec_and_test(&node->ref_count))
|
||||
kfree(node);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
config GOOGLE_FIRMWARE
|
||||
menuconfig GOOGLE_FIRMWARE
|
||||
bool "Google Firmware Drivers"
|
||||
depends on X86
|
||||
default n
|
||||
help
|
||||
These firmware drivers are used by Google's servers. They are
|
||||
only useful if you are working directly on one of their
|
||||
proprietary servers. If in doubt, say "N".
|
||||
|
||||
menu "Google Firmware Drivers"
|
||||
depends on GOOGLE_FIRMWARE
|
||||
if GOOGLE_FIRMWARE
|
||||
|
||||
config GOOGLE_SMI
|
||||
tristate "SMI interface for Google platforms"
|
||||
depends on ACPI && DMI && EFI
|
||||
depends on X86 && ACPI && DMI && EFI
|
||||
select EFI_VARS
|
||||
help
|
||||
Say Y here if you want to enable SMI callbacks for Google
|
||||
|
@ -20,12 +18,57 @@ config GOOGLE_SMI
|
|||
clearing the EFI event log and reading and writing NVRAM
|
||||
variables.
|
||||
|
||||
config GOOGLE_COREBOOT_TABLE
|
||||
tristate
|
||||
depends on GOOGLE_COREBOOT_TABLE_ACPI || GOOGLE_COREBOOT_TABLE_OF
|
||||
|
||||
config GOOGLE_COREBOOT_TABLE_ACPI
|
||||
tristate "Coreboot Table Access - ACPI"
|
||||
depends on ACPI
|
||||
select GOOGLE_COREBOOT_TABLE
|
||||
help
|
||||
This option enables the coreboot_table module, which provides other
|
||||
firmware modules to access to the coreboot table. The coreboot table
|
||||
pointer is accessed through the ACPI "GOOGCB00" object.
|
||||
If unsure say N.
|
||||
|
||||
config GOOGLE_COREBOOT_TABLE_OF
|
||||
tristate "Coreboot Table Access - Device Tree"
|
||||
depends on OF
|
||||
select GOOGLE_COREBOOT_TABLE
|
||||
help
|
||||
This option enable the coreboot_table module, which provide other
|
||||
firmware modules to access coreboot table. The coreboot table pointer
|
||||
is accessed through the device tree node /firmware/coreboot.
|
||||
If unsure say N.
|
||||
|
||||
config GOOGLE_MEMCONSOLE
|
||||
tristate "Firmware Memory Console"
|
||||
depends on DMI
|
||||
tristate
|
||||
depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT
|
||||
|
||||
config GOOGLE_MEMCONSOLE_X86_LEGACY
|
||||
tristate "Firmware Memory Console - X86 Legacy support"
|
||||
depends on X86 && ACPI && DMI
|
||||
select GOOGLE_MEMCONSOLE
|
||||
help
|
||||
This option enables the kernel to search for a firmware log in
|
||||
the EBDA on Google servers. If found, this log is exported to
|
||||
userland in the file /sys/firmware/log.
|
||||
|
||||
endmenu
|
||||
config GOOGLE_MEMCONSOLE_COREBOOT
|
||||
tristate "Firmware Memory Console"
|
||||
depends on GOOGLE_COREBOOT_TABLE
|
||||
select GOOGLE_MEMCONSOLE
|
||||
help
|
||||
This option enables the kernel to search for a firmware log in
|
||||
the coreboot table. If found, this log is exported to userland
|
||||
in the file /sys/firmware/log.
|
||||
|
||||
config GOOGLE_VPD
|
||||
tristate "Vital Product Data"
|
||||
depends on GOOGLE_COREBOOT_TABLE
|
||||
help
|
||||
This option enables the kernel to expose the content of Google VPD
|
||||
under /sys/firmware/vpd.
|
||||
|
||||
endif # GOOGLE_FIRMWARE
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
|
||||
obj-$(CONFIG_GOOGLE_SMI) += gsmi.o
|
||||
obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o
|
||||
obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o
|
||||
obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI) += coreboot_table-acpi.o
|
||||
obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF) += coreboot_table-of.o
|
||||
obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o
|
||||
obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o
|
||||
obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o
|
||||
|
||||
vpd-sysfs-y := vpd.o vpd_decode.o
|
||||
obj-$(CONFIG_GOOGLE_VPD) += vpd-sysfs.o
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* coreboot_table-acpi.c
|
||||
*
|
||||
* Using ACPI to locate Coreboot table and provide coreboot table access.
|
||||
*
|
||||
* 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 v2.0 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/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "coreboot_table.h"
|
||||
|
||||
static int coreboot_table_acpi_probe(struct platform_device *pdev)
|
||||
{
|
||||
phys_addr_t phyaddr;
|
||||
resource_size_t len;
|
||||
struct coreboot_table_header __iomem *header = NULL;
|
||||
struct resource *res;
|
||||
void __iomem *ptr = NULL;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -EINVAL;
|
||||
|
||||
len = resource_size(res);
|
||||
if (!res->start || !len)
|
||||
return -EINVAL;
|
||||
|
||||
phyaddr = res->start;
|
||||
header = ioremap_cache(phyaddr, sizeof(*header));
|
||||
if (header == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ptr = ioremap_cache(phyaddr,
|
||||
header->header_bytes + header->table_bytes);
|
||||
iounmap(header);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
return coreboot_table_init(ptr);
|
||||
}
|
||||
|
||||
static int coreboot_table_acpi_remove(struct platform_device *pdev)
|
||||
{
|
||||
return coreboot_table_exit();
|
||||
}
|
||||
|
||||
static const struct acpi_device_id cros_coreboot_acpi_match[] = {
|
||||
{ "GOOGCB00", 0 },
|
||||
{ "BOOT0000", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match);
|
||||
|
||||
static struct platform_driver coreboot_table_acpi_driver = {
|
||||
.probe = coreboot_table_acpi_probe,
|
||||
.remove = coreboot_table_acpi_remove,
|
||||
.driver = {
|
||||
.name = "coreboot_table_acpi",
|
||||
.acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init coreboot_table_acpi_init(void)
|
||||
{
|
||||
return platform_driver_register(&coreboot_table_acpi_driver);
|
||||
}
|
||||
|
||||
module_init(coreboot_table_acpi_init);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* coreboot_table-of.c
|
||||
*
|
||||
* Coreboot table access through open firmware.
|
||||
*
|
||||
* 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 v2.0 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/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "coreboot_table.h"
|
||||
|
||||
static int coreboot_table_of_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *fw_dn = pdev->dev.of_node;
|
||||
void __iomem *ptr;
|
||||
|
||||
ptr = of_iomap(fw_dn, 0);
|
||||
of_node_put(fw_dn);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
|
||||
return coreboot_table_init(ptr);
|
||||
}
|
||||
|
||||
static int coreboot_table_of_remove(struct platform_device *pdev)
|
||||
{
|
||||
return coreboot_table_exit();
|
||||
}
|
||||
|
||||
static const struct of_device_id coreboot_of_match[] = {
|
||||
{ .compatible = "coreboot" },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver coreboot_table_of_driver = {
|
||||
.probe = coreboot_table_of_probe,
|
||||
.remove = coreboot_table_of_remove,
|
||||
.driver = {
|
||||
.name = "coreboot_table_of",
|
||||
.of_match_table = coreboot_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init platform_coreboot_table_of_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
struct device_node *of_node;
|
||||
|
||||
/* Limit device creation to the presence of /firmware/coreboot node */
|
||||
of_node = of_find_node_by_path("/firmware/coreboot");
|
||||
if (!of_node)
|
||||
return -ENODEV;
|
||||
|
||||
if (!of_match_node(coreboot_of_match, of_node))
|
||||
return -ENODEV;
|
||||
|
||||
pdev = of_platform_device_create(of_node, "coreboot_table_of", NULL);
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
return platform_driver_register(&coreboot_table_of_driver);
|
||||
}
|
||||
|
||||
module_init(platform_coreboot_table_of_init);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* coreboot_table.c
|
||||
*
|
||||
* Module providing coreboot table access.
|
||||
*
|
||||
* 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 v2.0 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/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "coreboot_table.h"
|
||||
|
||||
struct coreboot_table_entry {
|
||||
u32 tag;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
static struct coreboot_table_header __iomem *ptr_header;
|
||||
|
||||
/*
|
||||
* This function parses the coreboot table for an entry that contains the base
|
||||
* address of the given entry tag. The coreboot table consists of a header
|
||||
* directly followed by a number of small, variable-sized entries, which each
|
||||
* contain an identifying tag and their length as the first two fields.
|
||||
*/
|
||||
int coreboot_table_find(int tag, void *data, size_t data_size)
|
||||
{
|
||||
struct coreboot_table_header header;
|
||||
struct coreboot_table_entry entry;
|
||||
void *ptr_entry;
|
||||
int i;
|
||||
|
||||
if (!ptr_header)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
memcpy_fromio(&header, ptr_header, sizeof(header));
|
||||
|
||||
if (strncmp(header.signature, "LBIO", sizeof(header.signature))) {
|
||||
pr_warn("coreboot_table: coreboot table missing or corrupt!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ptr_entry = (void *)ptr_header + header.header_bytes;
|
||||
|
||||
for (i = 0; i < header.table_entries; i++) {
|
||||
memcpy_fromio(&entry, ptr_entry, sizeof(entry));
|
||||
if (entry.tag == tag) {
|
||||
if (data_size < entry.size)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy_fromio(data, ptr_entry, entry.size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ptr_entry += entry.size;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
EXPORT_SYMBOL(coreboot_table_find);
|
||||
|
||||
int coreboot_table_init(void __iomem *ptr)
|
||||
{
|
||||
ptr_header = ptr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(coreboot_table_init);
|
||||
|
||||
int coreboot_table_exit(void)
|
||||
{
|
||||
if (ptr_header)
|
||||
iounmap(ptr_header);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(coreboot_table_exit);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* coreboot_table.h
|
||||
*
|
||||
* Internal header for coreboot table access.
|
||||
*
|
||||
* 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 v2.0 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 __COREBOOT_TABLE_H
|
||||
#define __COREBOOT_TABLE_H
|
||||
|
||||
#include <linux/io.h>
|
||||
|
||||
/* List of coreboot entry structures that is used */
|
||||
struct lb_cbmem_ref {
|
||||
uint32_t tag;
|
||||
uint32_t size;
|
||||
|
||||
uint64_t cbmem_addr;
|
||||
};
|
||||
|
||||
/* Coreboot table header structure */
|
||||
struct coreboot_table_header {
|
||||
char signature[4];
|
||||
u32 header_bytes;
|
||||
u32 header_checksum;
|
||||
u32 table_bytes;
|
||||
u32 table_checksum;
|
||||
u32 table_entries;
|
||||
};
|
||||
|
||||
/* Retrieve coreboot table entry with tag *tag* and copy it to data */
|
||||
int coreboot_table_find(int tag, void *data, size_t data_size);
|
||||
|
||||
/* Initialize coreboot table module given a pointer to iomem */
|
||||
int coreboot_table_init(void __iomem *ptr);
|
||||
|
||||
/* Cleanup coreboot table module */
|
||||
int coreboot_table_exit(void);
|
||||
|
||||
#endif /* __COREBOOT_TABLE_H */
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* memconsole-coreboot.c
|
||||
*
|
||||
* Memory based BIOS console accessed through coreboot table.
|
||||
*
|
||||
* 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 v2.0 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/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "memconsole.h"
|
||||
#include "coreboot_table.h"
|
||||
|
||||
#define CB_TAG_CBMEM_CONSOLE 0x17
|
||||
|
||||
/* CBMEM firmware console log descriptor. */
|
||||
struct cbmem_cons {
|
||||
u32 buffer_size;
|
||||
u32 buffer_cursor;
|
||||
u8 buffer_body[0];
|
||||
} __packed;
|
||||
|
||||
static struct cbmem_cons __iomem *cbmem_console;
|
||||
|
||||
static int memconsole_coreboot_init(phys_addr_t physaddr)
|
||||
{
|
||||
struct cbmem_cons __iomem *tmp_cbmc;
|
||||
|
||||
tmp_cbmc = memremap(physaddr, sizeof(*tmp_cbmc), MEMREMAP_WB);
|
||||
|
||||
if (!tmp_cbmc)
|
||||
return -ENOMEM;
|
||||
|
||||
cbmem_console = memremap(physaddr,
|
||||
tmp_cbmc->buffer_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));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int memconsole_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct lb_cbmem_ref entry;
|
||||
|
||||
ret = coreboot_table_find(CB_TAG_CBMEM_CONSOLE, &entry, sizeof(entry));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = memconsole_coreboot_init(entry.cbmem_addr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return memconsole_sysfs_init();
|
||||
}
|
||||
|
||||
static int memconsole_remove(struct platform_device *pdev)
|
||||
{
|
||||
memconsole_exit();
|
||||
|
||||
if (cbmem_console)
|
||||
memunmap(cbmem_console);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver memconsole_driver = {
|
||||
.probe = memconsole_probe,
|
||||
.remove = memconsole_remove,
|
||||
.driver = {
|
||||
.name = "memconsole",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init platform_memconsole_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
|
||||
pdev = platform_device_register_simple("memconsole", -1, NULL, 0);
|
||||
if (IS_ERR(pdev))
|
||||
return PTR_ERR(pdev);
|
||||
|
||||
platform_driver_register(&memconsole_driver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(platform_memconsole_init);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* memconsole-x86-legacy.c
|
||||
*
|
||||
* EBDA specific parts of the memory based BIOS console.
|
||||
*
|
||||
* 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 v2.0 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/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/mm.h>
|
||||
#include <asm/bios_ebda.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include "memconsole.h"
|
||||
|
||||
#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE
|
||||
#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24))
|
||||
|
||||
struct biosmemcon_ebda {
|
||||
u32 signature;
|
||||
union {
|
||||
struct {
|
||||
u8 enabled;
|
||||
u32 buffer_addr;
|
||||
u16 start;
|
||||
u16 end;
|
||||
u16 num_chars;
|
||||
u8 wrapped;
|
||||
} __packed v1;
|
||||
struct {
|
||||
u32 buffer_addr;
|
||||
/* Misdocumented as number of pages! */
|
||||
u16 num_bytes;
|
||||
u16 start;
|
||||
u16 end;
|
||||
} __packed v2;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
static void found_v1_header(struct biosmemcon_ebda *hdr)
|
||||
{
|
||||
pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n",
|
||||
hdr);
|
||||
pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num = %d\n",
|
||||
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);
|
||||
}
|
||||
|
||||
static void found_v2_header(struct biosmemcon_ebda *hdr)
|
||||
{
|
||||
pr_info("memconsole: BIOS console v2 EBDA structure found at %p\n",
|
||||
hdr);
|
||||
pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num_bytes = %d\n",
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Search through the EBDA for the BIOS Memory Console, and
|
||||
* set the global variables to point to it. Return true if found.
|
||||
*/
|
||||
static bool memconsole_ebda_init(void)
|
||||
{
|
||||
unsigned int address;
|
||||
size_t length, cur;
|
||||
|
||||
address = get_bios_ebda();
|
||||
if (!address) {
|
||||
pr_info("memconsole: BIOS EBDA non-existent.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* EBDA length is byte 0 of EBDA (in KB) */
|
||||
length = *(u8 *)phys_to_virt(address);
|
||||
length <<= 10; /* convert to bytes */
|
||||
|
||||
/*
|
||||
* Search through EBDA for BIOS memory console structure
|
||||
* note: signature is not necessarily dword-aligned
|
||||
*/
|
||||
for (cur = 0; cur < length; cur++) {
|
||||
struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
|
||||
|
||||
/* memconsole v1 */
|
||||
if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
|
||||
found_v1_header(hdr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* memconsole v2 */
|
||||
if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
|
||||
found_v2_header(hdr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pr_info("memconsole: BIOS console EBDA structure not found!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct dmi_system_id memconsole_dmi_table[] __initdata = {
|
||||
{
|
||||
.ident = "Google Board",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
|
||||
|
||||
static bool __init memconsole_find(void)
|
||||
{
|
||||
if (!dmi_check_system(memconsole_dmi_table))
|
||||
return false;
|
||||
|
||||
return memconsole_ebda_init();
|
||||
}
|
||||
|
||||
static int __init memconsole_x86_init(void)
|
||||
{
|
||||
if (!memconsole_find())
|
||||
return -ENODEV;
|
||||
|
||||
return memconsole_sysfs_init();
|
||||
}
|
||||
|
||||
static void __exit memconsole_x86_exit(void)
|
||||
{
|
||||
memconsole_exit();
|
||||
}
|
||||
|
||||
module_init(memconsole_x86_init);
|
||||
module_exit(memconsole_x86_exit);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,66 +1,36 @@
|
|||
/*
|
||||
* memconsole.c
|
||||
*
|
||||
* Infrastructure for importing the BIOS memory based console
|
||||
* into the kernel log ringbuffer.
|
||||
* Architecture-independent parts of the memory based BIOS console.
|
||||
*
|
||||
* Copyright 2010 Google Inc. All rights reserved.
|
||||
* 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 v2.0 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/ctype.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/io.h>
|
||||
#include <asm/bios_ebda.h>
|
||||
|
||||
#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE
|
||||
#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24))
|
||||
#include "memconsole.h"
|
||||
|
||||
struct biosmemcon_ebda {
|
||||
u32 signature;
|
||||
union {
|
||||
struct {
|
||||
u8 enabled;
|
||||
u32 buffer_addr;
|
||||
u16 start;
|
||||
u16 end;
|
||||
u16 num_chars;
|
||||
u8 wrapped;
|
||||
} __packed v1;
|
||||
struct {
|
||||
u32 buffer_addr;
|
||||
/* Misdocumented as number of pages! */
|
||||
u16 num_bytes;
|
||||
u16 start;
|
||||
u16 end;
|
||||
} __packed v2;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
static u32 memconsole_baseaddr;
|
||||
static char *memconsole_baseaddr;
|
||||
static size_t memconsole_length;
|
||||
|
||||
static ssize_t memconsole_read(struct file *filp, struct kobject *kobp,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t pos, size_t count)
|
||||
{
|
||||
char *memconsole;
|
||||
ssize_t ret;
|
||||
|
||||
memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length);
|
||||
if (!memconsole) {
|
||||
pr_err("memconsole: ioremap_cache failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
ret = memory_read_from_buffer(buf, count, &pos, memconsole,
|
||||
memconsole_length);
|
||||
iounmap(memconsole);
|
||||
return ret;
|
||||
return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr,
|
||||
memconsole_length);
|
||||
}
|
||||
|
||||
static struct bin_attribute memconsole_bin_attr = {
|
||||
|
@ -68,104 +38,25 @@ static struct bin_attribute memconsole_bin_attr = {
|
|||
.read = memconsole_read,
|
||||
};
|
||||
|
||||
|
||||
static void __init found_v1_header(struct biosmemcon_ebda *hdr)
|
||||
void memconsole_setup(void *baseaddr, size_t length)
|
||||
{
|
||||
pr_info("BIOS console v1 EBDA structure found at %p\n", hdr);
|
||||
pr_info("BIOS console buffer at 0x%.8x, "
|
||||
"start = %d, end = %d, num = %d\n",
|
||||
hdr->v1.buffer_addr, hdr->v1.start,
|
||||
hdr->v1.end, hdr->v1.num_chars);
|
||||
|
||||
memconsole_length = hdr->v1.num_chars;
|
||||
memconsole_baseaddr = hdr->v1.buffer_addr;
|
||||
memconsole_baseaddr = baseaddr;
|
||||
memconsole_length = length;
|
||||
}
|
||||
EXPORT_SYMBOL(memconsole_setup);
|
||||
|
||||
static void __init found_v2_header(struct biosmemcon_ebda *hdr)
|
||||
int memconsole_sysfs_init(void)
|
||||
{
|
||||
pr_info("BIOS console v2 EBDA structure found at %p\n", hdr);
|
||||
pr_info("BIOS console buffer at 0x%.8x, "
|
||||
"start = %d, end = %d, num_bytes = %d\n",
|
||||
hdr->v2.buffer_addr, hdr->v2.start,
|
||||
hdr->v2.end, hdr->v2.num_bytes);
|
||||
|
||||
memconsole_length = hdr->v2.end - hdr->v2.start;
|
||||
memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search through the EBDA for the BIOS Memory Console, and
|
||||
* set the global variables to point to it. Return true if found.
|
||||
*/
|
||||
static bool __init found_memconsole(void)
|
||||
{
|
||||
unsigned int address;
|
||||
size_t length, cur;
|
||||
|
||||
address = get_bios_ebda();
|
||||
if (!address) {
|
||||
pr_info("BIOS EBDA non-existent.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* EBDA length is byte 0 of EBDA (in KB) */
|
||||
length = *(u8 *)phys_to_virt(address);
|
||||
length <<= 10; /* convert to bytes */
|
||||
|
||||
/*
|
||||
* Search through EBDA for BIOS memory console structure
|
||||
* note: signature is not necessarily dword-aligned
|
||||
*/
|
||||
for (cur = 0; cur < length; cur++) {
|
||||
struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
|
||||
|
||||
/* memconsole v1 */
|
||||
if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
|
||||
found_v1_header(hdr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* memconsole v2 */
|
||||
if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
|
||||
found_v2_header(hdr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pr_info("BIOS console EBDA structure not found!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct dmi_system_id memconsole_dmi_table[] __initdata = {
|
||||
{
|
||||
.ident = "Google Board",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
|
||||
|
||||
static int __init memconsole_init(void)
|
||||
{
|
||||
if (!dmi_check_system(memconsole_dmi_table))
|
||||
return -ENODEV;
|
||||
|
||||
if (!found_memconsole())
|
||||
return -ENODEV;
|
||||
|
||||
memconsole_bin_attr.size = memconsole_length;
|
||||
return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr);
|
||||
}
|
||||
EXPORT_SYMBOL(memconsole_sysfs_init);
|
||||
|
||||
static void __exit memconsole_exit(void)
|
||||
void memconsole_exit(void)
|
||||
{
|
||||
sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr);
|
||||
}
|
||||
|
||||
module_init(memconsole_init);
|
||||
module_exit(memconsole_exit);
|
||||
EXPORT_SYMBOL(memconsole_exit);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* memconsole.h
|
||||
*
|
||||
* Internal headers of the memory based BIOS console.
|
||||
*
|
||||
* 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 v2.0 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 __FIRMWARE_GOOGLE_MEMCONSOLE_H
|
||||
#define __FIRMWARE_GOOGLE_MEMCONSOLE_H
|
||||
|
||||
/*
|
||||
* memconsole_setup
|
||||
*
|
||||
* Initialize the memory console from raw (virtual) base
|
||||
* address and length.
|
||||
*/
|
||||
void memconsole_setup(void *baseaddr, size_t length);
|
||||
|
||||
/*
|
||||
* memconsole_sysfs_init
|
||||
*
|
||||
* Update memory console length and create binary file
|
||||
* for firmware object.
|
||||
*/
|
||||
int memconsole_sysfs_init(void);
|
||||
|
||||
/* memconsole_exit
|
||||
*
|
||||
* Unmap the console buffer.
|
||||
*/
|
||||
void memconsole_exit(void);
|
||||
|
||||
#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* vpd.c
|
||||
*
|
||||
* Driver for exporting VPD content to sysfs.
|
||||
*
|
||||
* 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 v2.0 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/ctype.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include "coreboot_table.h"
|
||||
#include "vpd_decode.h"
|
||||
|
||||
#define CB_TAG_VPD 0x2c
|
||||
#define VPD_CBMEM_MAGIC 0x43524f53
|
||||
|
||||
static struct kobject *vpd_kobj;
|
||||
|
||||
struct vpd_cbmem {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 ro_size;
|
||||
u32 rw_size;
|
||||
u8 blob[0];
|
||||
};
|
||||
|
||||
struct vpd_section {
|
||||
bool enabled;
|
||||
const char *name;
|
||||
char *raw_name; /* the string name_raw */
|
||||
struct kobject *kobj; /* vpd/name directory */
|
||||
char *baseaddr;
|
||||
struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
|
||||
struct list_head attribs; /* key/value in vpd_attrib_info list */
|
||||
};
|
||||
|
||||
struct vpd_attrib_info {
|
||||
char *key;
|
||||
const char *value;
|
||||
struct bin_attribute bin_attr;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static struct vpd_section ro_vpd;
|
||||
static struct vpd_section rw_vpd;
|
||||
|
||||
static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t pos, size_t count)
|
||||
{
|
||||
struct vpd_attrib_info *info = bin_attr->private;
|
||||
|
||||
return memory_read_from_buffer(buf, count, &pos, info->value,
|
||||
info->bin_attr.size);
|
||||
}
|
||||
|
||||
/*
|
||||
* vpd_section_check_key_name()
|
||||
*
|
||||
* The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
|
||||
* old firmware versions may have entries like "S/N" which are problematic when
|
||||
* exporting them as sysfs attributes. These keys present in old firmwares are
|
||||
* ignored.
|
||||
*
|
||||
* Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
|
||||
*
|
||||
* @key: The key name to check
|
||||
* @key_len: key name length
|
||||
*/
|
||||
static int vpd_section_check_key_name(const u8 *key, s32 key_len)
|
||||
{
|
||||
int c;
|
||||
|
||||
while (key_len-- > 0) {
|
||||
c = *key++;
|
||||
|
||||
if (!isalnum(c) && c != '_')
|
||||
return VPD_FAIL;
|
||||
}
|
||||
|
||||
return VPD_OK;
|
||||
}
|
||||
|
||||
static int vpd_section_attrib_add(const u8 *key, s32 key_len,
|
||||
const u8 *value, s32 value_len,
|
||||
void *arg)
|
||||
{
|
||||
int ret;
|
||||
struct vpd_section *sec = arg;
|
||||
struct vpd_attrib_info *info;
|
||||
|
||||
/*
|
||||
* Return VPD_OK immediately to decode next entry if the current key
|
||||
* name contains invalid characters.
|
||||
*/
|
||||
if (vpd_section_check_key_name(key, key_len) != VPD_OK)
|
||||
return VPD_OK;
|
||||
|
||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||
info->key = kzalloc(key_len + 1, GFP_KERNEL);
|
||||
if (!info->key)
|
||||
return -ENOMEM;
|
||||
|
||||
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;
|
||||
info->bin_attr.size = value_len;
|
||||
info->bin_attr.read = vpd_attrib_read;
|
||||
info->bin_attr.private = info;
|
||||
|
||||
info->value = value;
|
||||
|
||||
INIT_LIST_HEAD(&info->list);
|
||||
list_add_tail(&info->list, &sec->attribs);
|
||||
|
||||
ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
|
||||
if (ret) {
|
||||
kfree(info->key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vpd_section_attrib_destroy(struct vpd_section *sec)
|
||||
{
|
||||
struct vpd_attrib_info *info;
|
||||
struct vpd_attrib_info *temp;
|
||||
|
||||
list_for_each_entry_safe(info, temp, &sec->attribs, list) {
|
||||
kfree(info->key);
|
||||
sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
|
||||
kfree(info);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t pos, size_t count)
|
||||
{
|
||||
struct vpd_section *sec = bin_attr->private;
|
||||
|
||||
return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
|
||||
sec->bin_attr.size);
|
||||
}
|
||||
|
||||
static int vpd_section_create_attribs(struct vpd_section *sec)
|
||||
{
|
||||
s32 consumed;
|
||||
int ret;
|
||||
|
||||
consumed = 0;
|
||||
do {
|
||||
ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
|
||||
&consumed, vpd_section_attrib_add, sec);
|
||||
} while (ret == VPD_OK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vpd_section_init(const char *name, struct vpd_section *sec,
|
||||
phys_addr_t physaddr, size_t size)
|
||||
{
|
||||
int ret;
|
||||
int raw_len;
|
||||
|
||||
sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
|
||||
if (!sec->baseaddr)
|
||||
return -ENOMEM;
|
||||
|
||||
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);
|
||||
|
||||
sysfs_bin_attr_init(&sec->bin_attr);
|
||||
sec->bin_attr.attr.name = sec->raw_name;
|
||||
sec->bin_attr.attr.mode = 0444;
|
||||
sec->bin_attr.size = size;
|
||||
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;
|
||||
|
||||
sec->kobj = kobject_create_and_add(name, vpd_kobj);
|
||||
if (!sec->kobj) {
|
||||
ret = -EINVAL;
|
||||
goto sysfs_remove;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&sec->attribs);
|
||||
vpd_section_create_attribs(sec);
|
||||
|
||||
sec->enabled = true;
|
||||
|
||||
return 0;
|
||||
|
||||
sysfs_remove:
|
||||
sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
|
||||
|
||||
free_sec:
|
||||
kfree(sec->raw_name);
|
||||
iounmap(sec->baseaddr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int vpd_section_destroy(struct vpd_section *sec)
|
||||
{
|
||||
if (sec->enabled) {
|
||||
vpd_section_attrib_destroy(sec);
|
||||
kobject_del(sec->kobj);
|
||||
sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
|
||||
kfree(sec->raw_name);
|
||||
iounmap(sec->baseaddr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vpd_sections_init(phys_addr_t physaddr)
|
||||
{
|
||||
struct vpd_cbmem __iomem *temp;
|
||||
struct vpd_cbmem header;
|
||||
int ret = 0;
|
||||
|
||||
temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
|
||||
if (!temp)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem));
|
||||
iounmap(temp);
|
||||
|
||||
if (header.magic != VPD_CBMEM_MAGIC)
|
||||
return -ENODEV;
|
||||
|
||||
if (header.ro_size) {
|
||||
ret = vpd_section_init("ro", &ro_vpd,
|
||||
physaddr + sizeof(struct vpd_cbmem),
|
||||
header.ro_size);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (header.rw_size) {
|
||||
ret = vpd_section_init("rw", &rw_vpd,
|
||||
physaddr + sizeof(struct vpd_cbmem) +
|
||||
header.ro_size, header.rw_size);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vpd_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct lb_cbmem_ref entry;
|
||||
|
||||
ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return vpd_sections_init(entry.cbmem_addr);
|
||||
}
|
||||
|
||||
static struct platform_driver vpd_driver = {
|
||||
.probe = vpd_probe,
|
||||
.driver = {
|
||||
.name = "vpd",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init vpd_platform_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
|
||||
pdev = platform_device_register_simple("vpd", -1, NULL, 0);
|
||||
if (IS_ERR(pdev))
|
||||
return PTR_ERR(pdev);
|
||||
|
||||
vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
|
||||
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;
|
||||
}
|
||||
|
||||
static void __exit vpd_platform_exit(void)
|
||||
{
|
||||
vpd_section_destroy(&ro_vpd);
|
||||
vpd_section_destroy(&rw_vpd);
|
||||
kobject_del(vpd_kobj);
|
||||
}
|
||||
|
||||
module_init(vpd_platform_init);
|
||||
module_exit(vpd_platform_exit);
|
||||
|
||||
MODULE_AUTHOR("Google, Inc.");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* vpd_decode.c
|
||||
*
|
||||
* Google VPD decoding routines.
|
||||
*
|
||||
* 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 v2.0 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/export.h>
|
||||
|
||||
#include "vpd_decode.h"
|
||||
|
||||
static int vpd_decode_len(const s32 max_len, const u8 *in,
|
||||
s32 *length, s32 *decoded_len)
|
||||
{
|
||||
u8 more;
|
||||
int i = 0;
|
||||
|
||||
if (!length || !decoded_len)
|
||||
return VPD_FAIL;
|
||||
|
||||
*length = 0;
|
||||
do {
|
||||
if (i >= max_len)
|
||||
return VPD_FAIL;
|
||||
|
||||
more = in[i] & 0x80;
|
||||
*length <<= 7;
|
||||
*length |= in[i] & 0x7f;
|
||||
++i;
|
||||
} while (more);
|
||||
|
||||
*decoded_len = i;
|
||||
|
||||
return VPD_OK;
|
||||
}
|
||||
|
||||
int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed,
|
||||
vpd_decode_callback callback, void *callback_arg)
|
||||
{
|
||||
int type;
|
||||
int res;
|
||||
s32 key_len;
|
||||
s32 value_len;
|
||||
s32 decoded_len;
|
||||
const u8 *key;
|
||||
const u8 *value;
|
||||
|
||||
/* type */
|
||||
if (*consumed >= max_len)
|
||||
return VPD_FAIL;
|
||||
|
||||
type = input_buf[*consumed];
|
||||
|
||||
switch (type) {
|
||||
case VPD_TYPE_INFO:
|
||||
case VPD_TYPE_STRING:
|
||||
(*consumed)++;
|
||||
|
||||
/* key */
|
||||
res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed],
|
||||
&key_len, &decoded_len);
|
||||
if (res != VPD_OK || *consumed + decoded_len >= max_len)
|
||||
return VPD_FAIL;
|
||||
|
||||
*consumed += decoded_len;
|
||||
key = &input_buf[*consumed];
|
||||
*consumed += key_len;
|
||||
|
||||
/* value */
|
||||
res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed],
|
||||
&value_len, &decoded_len);
|
||||
if (res != VPD_OK || *consumed + decoded_len > max_len)
|
||||
return VPD_FAIL;
|
||||
|
||||
*consumed += decoded_len;
|
||||
value = &input_buf[*consumed];
|
||||
*consumed += value_len;
|
||||
|
||||
if (type == VPD_TYPE_STRING)
|
||||
return callback(key, key_len, value, value_len,
|
||||
callback_arg);
|
||||
break;
|
||||
|
||||
default:
|
||||
return VPD_FAIL;
|
||||
}
|
||||
|
||||
return VPD_OK;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* vpd_decode.h
|
||||
*
|
||||
* Google VPD decoding routines.
|
||||
*
|
||||
* 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 v2.0 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 __VPD_DECODE_H
|
||||
#define __VPD_DECODE_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
enum {
|
||||
VPD_OK = 0,
|
||||
VPD_FAIL,
|
||||
};
|
||||
|
||||
enum {
|
||||
VPD_TYPE_TERMINATOR = 0,
|
||||
VPD_TYPE_STRING,
|
||||
VPD_TYPE_INFO = 0xfe,
|
||||
VPD_TYPE_IMPLICIT_TERMINATOR = 0xff,
|
||||
};
|
||||
|
||||
/* Callback for vpd_decode_string to invoke. */
|
||||
typedef int vpd_decode_callback(const u8 *key, s32 key_len,
|
||||
const u8 *value, s32 value_len,
|
||||
void *arg);
|
||||
|
||||
/*
|
||||
* vpd_decode_string
|
||||
*
|
||||
* Given the encoded string, this function invokes callback with extracted
|
||||
* (key, value). The *consumed will be plused the number of bytes consumed in
|
||||
* this function.
|
||||
*
|
||||
* The input_buf points to the first byte of the input buffer.
|
||||
*
|
||||
* The *consumed starts from 0, which is actually the next byte to be decoded.
|
||||
* It can be non-zero to be used in multiple calls.
|
||||
*
|
||||
* If one entry is successfully decoded, sends it to callback and returns the
|
||||
* result.
|
||||
*/
|
||||
int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed,
|
||||
vpd_decode_callback callback, void *callback_arg);
|
||||
|
||||
#endif /* __VPD_DECODE_H */
|
|
@ -20,6 +20,12 @@ config FPGA_REGION
|
|||
FPGA Regions allow loading FPGA images under control of
|
||||
the Device Tree.
|
||||
|
||||
config FPGA_MGR_ICE40_SPI
|
||||
tristate "Lattice iCE40 SPI"
|
||||
depends on OF && SPI
|
||||
help
|
||||
FPGA manager driver support for Lattice iCE40 FPGAs over SPI.
|
||||
|
||||
config FPGA_MGR_SOCFPGA
|
||||
tristate "Altera SOCFPGA FPGA Manager"
|
||||
depends on ARCH_SOCFPGA || COMPILE_TEST
|
||||
|
@ -33,6 +39,20 @@ config FPGA_MGR_SOCFPGA_A10
|
|||
help
|
||||
FPGA manager driver support for Altera Arria10 SoCFPGA.
|
||||
|
||||
config FPGA_MGR_TS73XX
|
||||
tristate "Technologic Systems TS-73xx SBC FPGA Manager"
|
||||
depends on ARCH_EP93XX && MACH_TS72XX
|
||||
help
|
||||
FPGA manager driver support for the Altera Cyclone II FPGA
|
||||
present on the TS-73xx SBC boards.
|
||||
|
||||
config FPGA_MGR_XILINX_SPI
|
||||
tristate "Xilinx Configuration over Slave Serial (SPI)"
|
||||
depends on SPI
|
||||
help
|
||||
FPGA manager driver support for Xilinx FPGA configuration
|
||||
over slave serial interface.
|
||||
|
||||
config FPGA_MGR_ZYNQ_FPGA
|
||||
tristate "Xilinx Zynq FPGA"
|
||||
depends on ARCH_ZYNQ || COMPILE_TEST
|
||||
|
@ -63,6 +83,28 @@ config ALTERA_FREEZE_BRIDGE
|
|||
isolate one region of the FPGA from the busses while that
|
||||
region is being reprogrammed.
|
||||
|
||||
config ALTERA_PR_IP_CORE
|
||||
tristate "Altera Partial Reconfiguration IP Core"
|
||||
help
|
||||
Core driver support for Altera Partial Reconfiguration IP component
|
||||
|
||||
config ALTERA_PR_IP_CORE_PLAT
|
||||
tristate "Platform support of Altera Partial Reconfiguration IP Core"
|
||||
depends on ALTERA_PR_IP_CORE && OF && HAS_IOMEM
|
||||
help
|
||||
Platform driver support for Altera Partial Reconfiguration IP
|
||||
component
|
||||
|
||||
config XILINX_PR_DECOUPLER
|
||||
tristate "Xilinx LogiCORE PR Decoupler"
|
||||
depends on FPGA_BRIDGE
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Say Y to enable drivers for Xilinx LogiCORE PR Decoupler.
|
||||
The PR Decoupler exists in the FPGA fabric to isolate one
|
||||
region of the FPGA from the busses while that region is
|
||||
being reprogrammed during partial reconfig.
|
||||
|
||||
endif # FPGA
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -6,14 +6,20 @@
|
|||
obj-$(CONFIG_FPGA) += fpga-mgr.o
|
||||
|
||||
# FPGA Manager Drivers
|
||||
obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o
|
||||
obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o
|
||||
obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o
|
||||
obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o
|
||||
obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o
|
||||
obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o
|
||||
obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o
|
||||
obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.o
|
||||
|
||||
# FPGA Bridge Drivers
|
||||
obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o
|
||||
obj-$(CONFIG_SOCFPGA_FPGA_BRIDGE) += altera-hps2fpga.o altera-fpga2sdram.o
|
||||
obj-$(CONFIG_ALTERA_FREEZE_BRIDGE) += altera-freeze-bridge.o
|
||||
obj-$(CONFIG_XILINX_PR_DECOUPLER) += xilinx-pr-decoupler.o
|
||||
|
||||
# High Level Interfaces
|
||||
obj-$(CONFIG_FPGA_REGION) += fpga-region.o
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#define FREEZE_CSR_REG_VERSION 12
|
||||
|
||||
#define FREEZE_CSR_SUPPORTED_VERSION 2
|
||||
#define FREEZE_CSR_OFFICIAL_VERSION 0xad000003
|
||||
|
||||
#define FREEZE_CSR_STATUS_FREEZE_REQ_DONE BIT(0)
|
||||
#define FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE BIT(1)
|
||||
|
@ -203,7 +204,7 @@ static int altera_freeze_br_enable_show(struct fpga_bridge *bridge)
|
|||
return priv->enable;
|
||||
}
|
||||
|
||||
static struct fpga_bridge_ops altera_freeze_br_br_ops = {
|
||||
static const struct fpga_bridge_ops altera_freeze_br_br_ops = {
|
||||
.enable_set = altera_freeze_br_enable_set,
|
||||
.enable_show = altera_freeze_br_enable_show,
|
||||
};
|
||||
|
@ -218,6 +219,7 @@ static int altera_freeze_br_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
void __iomem *base_addr;
|
||||
struct altera_freeze_br_data *priv;
|
||||
struct resource *res;
|
||||
u32 status, revision;
|
||||
|
@ -225,26 +227,32 @@ static int altera_freeze_br_probe(struct platform_device *pdev)
|
|||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base_addr = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(base_addr))
|
||||
return PTR_ERR(base_addr);
|
||||
|
||||
revision = readl(base_addr + FREEZE_CSR_REG_VERSION);
|
||||
if ((revision != FREEZE_CSR_SUPPORTED_VERSION) &&
|
||||
(revision != FREEZE_CSR_OFFICIAL_VERSION)) {
|
||||
dev_err(dev,
|
||||
"%s unexpected revision 0x%x != 0x%x != 0x%x\n",
|
||||
__func__, revision, FREEZE_CSR_SUPPORTED_VERSION,
|
||||
FREEZE_CSR_OFFICIAL_VERSION);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->dev = dev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
priv->base_addr = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(priv->base_addr))
|
||||
return PTR_ERR(priv->base_addr);
|
||||
|
||||
status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET);
|
||||
status = readl(base_addr + FREEZE_CSR_STATUS_OFFSET);
|
||||
if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE)
|
||||
priv->enable = 1;
|
||||
|
||||
revision = readl(priv->base_addr + FREEZE_CSR_REG_VERSION);
|
||||
if (revision != FREEZE_CSR_SUPPORTED_VERSION)
|
||||
dev_warn(dev,
|
||||
"%s Freeze Controller unexpected revision %d != %d\n",
|
||||
__func__, revision, FREEZE_CSR_SUPPORTED_VERSION);
|
||||
priv->base_addr = base_addr;
|
||||
|
||||
return fpga_bridge_register(dev, FREEZE_BRIDGE_NAME,
|
||||
&altera_freeze_br_br_ops, priv);
|
||||
|
|
|
@ -181,15 +181,18 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev)
|
|||
(enable ? "enabling" : "disabling"));
|
||||
|
||||
ret = _alt_hps2fpga_enable_set(priv, enable);
|
||||
if (ret) {
|
||||
fpga_bridge_unregister(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops,
|
||||
priv);
|
||||
ret = fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops,
|
||||
priv);
|
||||
err:
|
||||
if (ret)
|
||||
clk_disable_unprepare(priv->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int alt_fpga_bridge_remove(struct platform_device *pdev)
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Driver for Altera Partial Reconfiguration IP Core
|
||||
*
|
||||
* Copyright (C) 2016-2017 Intel Corporation
|
||||
*
|
||||
* Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation
|
||||
* by Alan Tull <atull@opensource.altera.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/fpga/altera-pr-ip-core.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
static int alt_pr_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
void __iomem *reg_base;
|
||||
struct resource *res;
|
||||
|
||||
/* First mmio base is for register access */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
reg_base = devm_ioremap_resource(dev, res);
|
||||
|
||||
if (IS_ERR(reg_base))
|
||||
return PTR_ERR(reg_base);
|
||||
|
||||
return alt_pr_register(dev, reg_base);
|
||||
}
|
||||
|
||||
static int alt_pr_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
return alt_pr_unregister(dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id alt_pr_of_match[] = {
|
||||
{ .compatible = "altr,a10-pr-ip", },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, alt_pr_of_match);
|
||||
|
||||
static struct platform_driver alt_pr_platform_driver = {
|
||||
.probe = alt_pr_platform_probe,
|
||||
.remove = alt_pr_platform_remove,
|
||||
.driver = {
|
||||
.name = "alt_a10_pr_ip",
|
||||
.of_match_table = alt_pr_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(alt_pr_platform_driver);
|
||||
MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Platform Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Driver for Altera Partial Reconfiguration IP Core
|
||||
*
|
||||
* Copyright (C) 2016-2017 Intel Corporation
|
||||
*
|
||||
* Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation
|
||||
* by Alan Tull <atull@opensource.altera.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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/delay.h>
|
||||
#include <linux/fpga/altera-pr-ip-core.h>
|
||||
#include <linux/fpga/fpga-mgr.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#define ALT_PR_DATA_OFST 0x00
|
||||
#define ALT_PR_CSR_OFST 0x04
|
||||
|
||||
#define ALT_PR_CSR_PR_START BIT(0)
|
||||
#define ALT_PR_CSR_STATUS_SFT 2
|
||||
#define ALT_PR_CSR_STATUS_MSK (7 << ALT_PR_CSR_STATUS_SFT)
|
||||
#define ALT_PR_CSR_STATUS_NRESET (0 << ALT_PR_CSR_STATUS_SFT)
|
||||
#define ALT_PR_CSR_STATUS_PR_ERR (1 << ALT_PR_CSR_STATUS_SFT)
|
||||
#define ALT_PR_CSR_STATUS_CRC_ERR (2 << ALT_PR_CSR_STATUS_SFT)
|
||||
#define ALT_PR_CSR_STATUS_BAD_BITS (3 << ALT_PR_CSR_STATUS_SFT)
|
||||
#define ALT_PR_CSR_STATUS_PR_IN_PROG (4 << ALT_PR_CSR_STATUS_SFT)
|
||||
#define ALT_PR_CSR_STATUS_PR_SUCCESS (5 << ALT_PR_CSR_STATUS_SFT)
|
||||
|
||||
struct alt_pr_priv {
|
||||
void __iomem *reg_base;
|
||||
};
|
||||
|
||||
static enum fpga_mgr_states alt_pr_fpga_state(struct fpga_manager *mgr)
|
||||
{
|
||||
struct alt_pr_priv *priv = mgr->priv;
|
||||
const char *err = "unknown";
|
||||
enum fpga_mgr_states ret = FPGA_MGR_STATE_UNKNOWN;
|
||||
u32 val;
|
||||
|
||||
val = readl(priv->reg_base + ALT_PR_CSR_OFST);
|
||||
|
||||
val &= ALT_PR_CSR_STATUS_MSK;
|
||||
|
||||
switch (val) {
|
||||
case ALT_PR_CSR_STATUS_NRESET:
|
||||
return FPGA_MGR_STATE_RESET;
|
||||
|
||||
case ALT_PR_CSR_STATUS_PR_ERR:
|
||||
err = "pr error";
|
||||
ret = FPGA_MGR_STATE_WRITE_ERR;
|
||||
break;
|
||||
|
||||
case ALT_PR_CSR_STATUS_CRC_ERR:
|
||||
err = "crc error";
|
||||
ret = FPGA_MGR_STATE_WRITE_ERR;
|
||||
break;
|
||||
|
||||
case ALT_PR_CSR_STATUS_BAD_BITS:
|
||||
err = "bad bits";
|
||||
ret = FPGA_MGR_STATE_WRITE_ERR;
|
||||
break;
|
||||
|
||||
case ALT_PR_CSR_STATUS_PR_IN_PROG:
|
||||
return FPGA_MGR_STATE_WRITE;
|
||||
|
||||
case ALT_PR_CSR_STATUS_PR_SUCCESS:
|
||||
return FPGA_MGR_STATE_OPERATING;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dev_err(&mgr->dev, "encountered error code %d (%s) in %s()\n",
|
||||
val, err, __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int alt_pr_fpga_write_init(struct fpga_manager *mgr,
|
||||
struct fpga_image_info *info,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct alt_pr_priv *priv = mgr->priv;
|
||||
u32 val;
|
||||
|
||||
if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
|
||||
dev_err(&mgr->dev, "%s Partial Reconfiguration flag not set\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
val = readl(priv->reg_base + ALT_PR_CSR_OFST);
|
||||
|
||||
if (val & ALT_PR_CSR_PR_START) {
|
||||
dev_err(&mgr->dev,
|
||||
"%s Partial Reconfiguration already started\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(val | ALT_PR_CSR_PR_START, priv->reg_base + ALT_PR_CSR_OFST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int alt_pr_fpga_write(struct fpga_manager *mgr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct alt_pr_priv *priv = mgr->priv;
|
||||
u32 *buffer_32 = (u32 *)buf;
|
||||
size_t i = 0;
|
||||
|
||||
if (count <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Write out the complete 32-bit chunks */
|
||||
while (count >= sizeof(u32)) {
|
||||
writel(buffer_32[i++], priv->reg_base);
|
||||
count -= sizeof(u32);
|
||||
}
|
||||
|
||||
/* Write out remaining non 32-bit chunks */
|
||||
switch (count) {
|
||||
case 3:
|
||||
writel(buffer_32[i++] & 0x00ffffff, priv->reg_base);
|
||||
break;
|
||||
case 2:
|
||||
writel(buffer_32[i++] & 0x0000ffff, priv->reg_base);
|
||||
break;
|
||||
case 1:
|
||||
writel(buffer_32[i++] & 0x000000ff, priv->reg_base);
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
/* This will never happen */
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (alt_pr_fpga_state(mgr) == FPGA_MGR_STATE_WRITE_ERR)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int alt_pr_fpga_write_complete(struct fpga_manager *mgr,
|
||||
struct fpga_image_info *info)
|
||||
{
|
||||
u32 i = 0;
|
||||
|
||||
do {
|
||||
switch (alt_pr_fpga_state(mgr)) {
|
||||
case FPGA_MGR_STATE_WRITE_ERR:
|
||||
return -EIO;
|
||||
|
||||
case FPGA_MGR_STATE_OPERATING:
|
||||
dev_info(&mgr->dev,
|
||||
"successful partial reconfiguration\n");
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
udelay(1);
|
||||
} while (info->config_complete_timeout_us > i++);
|
||||
|
||||
dev_err(&mgr->dev, "timed out waiting for write to complete\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static const struct fpga_manager_ops alt_pr_ops = {
|
||||
.state = alt_pr_fpga_state,
|
||||
.write_init = alt_pr_fpga_write_init,
|
||||
.write = alt_pr_fpga_write,
|
||||
.write_complete = alt_pr_fpga_write_complete,
|
||||
};
|
||||
|
||||
int alt_pr_register(struct device *dev, void __iomem *reg_base)
|
||||
{
|
||||
struct alt_pr_priv *priv;
|
||||
u32 val;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->reg_base = reg_base;
|
||||
|
||||
val = readl(priv->reg_base + ALT_PR_CSR_OFST);
|
||||
|
||||
dev_dbg(dev, "%s status=%d start=%d\n", __func__,
|
||||
(val & ALT_PR_CSR_STATUS_MSK) >> ALT_PR_CSR_STATUS_SFT,
|
||||
(int)(val & ALT_PR_CSR_PR_START));
|
||||
|
||||
return fpga_mgr_register(dev, dev_name(dev), &alt_pr_ops, priv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(alt_pr_register);
|
||||
|
||||
int alt_pr_unregister(struct device *dev)
|
||||
{
|
||||
dev_dbg(dev, "%s\n", __func__);
|
||||
|
||||
fpga_mgr_unregister(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(alt_pr_unregister);
|
||||
|
||||
MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Core");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -27,7 +27,7 @@ static DEFINE_IDA(fpga_bridge_ida);
|
|||
static struct class *fpga_bridge_class;
|
||||
|
||||
/* Lock for adding/removing bridges to linked lists*/
|
||||
spinlock_t bridge_list_lock;
|
||||
static spinlock_t bridge_list_lock;
|
||||
|
||||
static int fpga_bridge_of_node_match(struct device *dev, const void *data)
|
||||
{
|
||||
|
@ -146,11 +146,9 @@ EXPORT_SYMBOL_GPL(fpga_bridge_put);
|
|||
int fpga_bridges_enable(struct list_head *bridge_list)
|
||||
{
|
||||
struct fpga_bridge *bridge;
|
||||
struct list_head *node;
|
||||
int ret;
|
||||
|
||||
list_for_each(node, bridge_list) {
|
||||
bridge = list_entry(node, struct fpga_bridge, node);
|
||||
list_for_each_entry(bridge, bridge_list, node) {
|
||||
ret = fpga_bridge_enable(bridge);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -172,11 +170,9 @@ EXPORT_SYMBOL_GPL(fpga_bridges_enable);
|
|||
int fpga_bridges_disable(struct list_head *bridge_list)
|
||||
{
|
||||
struct fpga_bridge *bridge;
|
||||
struct list_head *node;
|
||||
int ret;
|
||||
|
||||
list_for_each(node, bridge_list) {
|
||||
bridge = list_entry(node, struct fpga_bridge, node);
|
||||
list_for_each_entry(bridge, bridge_list, node) {
|
||||
ret = fpga_bridge_disable(bridge);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -196,13 +192,10 @@ EXPORT_SYMBOL_GPL(fpga_bridges_disable);
|
|||
*/
|
||||
void fpga_bridges_put(struct list_head *bridge_list)
|
||||
{
|
||||
struct fpga_bridge *bridge;
|
||||
struct list_head *node, *next;
|
||||
struct fpga_bridge *bridge, *next;
|
||||
unsigned long flags;
|
||||
|
||||
list_for_each_safe(node, next, bridge_list) {
|
||||
bridge = list_entry(node, struct fpga_bridge, node);
|
||||
|
||||
list_for_each_entry_safe(bridge, next, bridge_list, node) {
|
||||
fpga_bridge_put(bridge);
|
||||
|
||||
spin_lock_irqsave(&bridge_list_lock, flags);
|
||||
|
|
|
@ -361,7 +361,7 @@ static struct attribute *fpga_mgr_attrs[] = {
|
|||
};
|
||||
ATTRIBUTE_GROUPS(fpga_mgr);
|
||||
|
||||
struct fpga_manager *__fpga_mgr_get(struct device *dev)
|
||||
static struct fpga_manager *__fpga_mgr_get(struct device *dev)
|
||||
{
|
||||
struct fpga_manager *mgr;
|
||||
int ret = -ENODEV;
|
||||
|
|
|
@ -245,7 +245,8 @@ static int fpga_region_program_fpga(struct fpga_region *region,
|
|||
mgr = fpga_region_get_manager(region);
|
||||
if (IS_ERR(mgr)) {
|
||||
pr_err("failed to get fpga region manager\n");
|
||||
return PTR_ERR(mgr);
|
||||
ret = PTR_ERR(mgr);
|
||||
goto err_put_region;
|
||||
}
|
||||
|
||||
ret = fpga_region_get_bridges(region, overlay);
|
||||
|
@ -281,6 +282,7 @@ static int fpga_region_program_fpga(struct fpga_region *region,
|
|||
fpga_bridges_put(®ion->bridge_list);
|
||||
err_put_mgr:
|
||||
fpga_mgr_put(mgr);
|
||||
err_put_region:
|
||||
fpga_region_put(region);
|
||||
|
||||
return ret;
|
||||
|
@ -337,8 +339,9 @@ static int child_regions_with_firmware(struct device_node *overlay)
|
|||
* The overlay must add either firmware-name or external-fpga-config property
|
||||
* to the FPGA Region.
|
||||
*
|
||||
* firmware-name : program the FPGA
|
||||
* external-fpga-config : FPGA is already programmed
|
||||
* firmware-name : program the FPGA
|
||||
* external-fpga-config : FPGA is already programmed
|
||||
* encrypted-fpga-config : FPGA bitstream is encrypted
|
||||
*
|
||||
* The overlay can add other FPGA regions, but child FPGA regions cannot have a
|
||||
* firmware-name property since those regions don't exist yet.
|
||||
|
@ -373,6 +376,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region,
|
|||
if (of_property_read_bool(nd->overlay, "external-fpga-config"))
|
||||
info->flags |= FPGA_MGR_EXTERNAL_CONFIG;
|
||||
|
||||
if (of_property_read_bool(nd->overlay, "encrypted-fpga-config"))
|
||||
info->flags |= FPGA_MGR_ENCRYPTED_BITSTREAM;
|
||||
|
||||
of_property_read_string(nd->overlay, "firmware-name", &firmware_name);
|
||||
|
||||
of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us",
|
||||
|
@ -381,6 +387,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region,
|
|||
of_property_read_u32(nd->overlay, "region-freeze-timeout-us",
|
||||
&info->disable_timeout_us);
|
||||
|
||||
of_property_read_u32(nd->overlay, "config-complete-timeout-us",
|
||||
&info->config_complete_timeout_us);
|
||||
|
||||
/* If FPGA was externally programmed, don't specify firmware */
|
||||
if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && firmware_name) {
|
||||
pr_err("error: specified firmware and external-fpga-config");
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* FPGA Manager Driver for Lattice iCE40.
|
||||
*
|
||||
* Copyright (c) 2016 Joel Holdsworth
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* This driver adds support to the FPGA manager for configuring the SRAM of
|
||||
* Lattice iCE40 FPGAs through slave SPI.
|
||||
*/
|
||||
|
||||
#include <linux/fpga/fpga-mgr.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/stringify.h>
|
||||
|
||||
#define ICE40_SPI_MAX_SPEED 25000000 /* Hz */
|
||||
#define ICE40_SPI_MIN_SPEED 1000000 /* Hz */
|
||||
|
||||
#define ICE40_SPI_RESET_DELAY 1 /* us (>200ns) */
|
||||
#define ICE40_SPI_HOUSEKEEPING_DELAY 1200 /* us */
|
||||
|
||||
#define ICE40_SPI_NUM_ACTIVATION_BYTES DIV_ROUND_UP(49, 8)
|
||||
|
||||
struct ice40_fpga_priv {
|
||||
struct spi_device *dev;
|
||||
struct gpio_desc *reset;
|
||||
struct gpio_desc *cdone;
|
||||
};
|
||||
|
||||
static enum fpga_mgr_states ice40_fpga_ops_state(struct fpga_manager *mgr)
|
||||
{
|
||||
struct ice40_fpga_priv *priv = mgr->priv;
|
||||
|
||||
return gpiod_get_value(priv->cdone) ? FPGA_MGR_STATE_OPERATING :
|
||||
FPGA_MGR_STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
static int ice40_fpga_ops_write_init(struct fpga_manager *mgr,
|
||||
struct fpga_image_info *info,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct ice40_fpga_priv *priv = mgr->priv;
|
||||
struct spi_device *dev = priv->dev;
|
||||
struct spi_message message;
|
||||
struct spi_transfer assert_cs_then_reset_delay = {
|
||||
.cs_change = 1,
|
||||
.delay_usecs = ICE40_SPI_RESET_DELAY
|
||||
};
|
||||
struct spi_transfer housekeeping_delay_then_release_cs = {
|
||||
.delay_usecs = ICE40_SPI_HOUSEKEEPING_DELAY
|
||||
};
|
||||
int ret;
|
||||
|
||||
if ((info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
|
||||
dev_err(&dev->dev,
|
||||
"Partial reconfiguration is not supported\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
/* Lock the bus, assert CRESET_B and SS_B and delay >200ns */
|
||||
spi_bus_lock(dev->master);
|
||||
|
||||
gpiod_set_value(priv->reset, 1);
|
||||
|
||||
spi_message_init(&message);
|
||||
spi_message_add_tail(&assert_cs_then_reset_delay, &message);
|
||||
ret = spi_sync_locked(dev, &message);
|
||||
|
||||
/* Come out of reset */
|
||||
gpiod_set_value(priv->reset, 0);
|
||||
|
||||
/* Abort if the chip-select failed */
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* Check CDONE is de-asserted i.e. the FPGA is reset */
|
||||
if (gpiod_get_value(priv->cdone)) {
|
||||
dev_err(&dev->dev, "Device reset failed, CDONE is asserted\n");
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Wait for the housekeeping to complete, and release SS_B */
|
||||
spi_message_init(&message);
|
||||
spi_message_add_tail(&housekeeping_delay_then_release_cs, &message);
|
||||
ret = spi_sync_locked(dev, &message);
|
||||
|
||||
fail:
|
||||
spi_bus_unlock(dev->master);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ice40_fpga_ops_write(struct fpga_manager *mgr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct ice40_fpga_priv *priv = mgr->priv;
|
||||
|
||||
return spi_write(priv->dev, buf, count);
|
||||
}
|
||||
|
||||
static int ice40_fpga_ops_write_complete(struct fpga_manager *mgr,
|
||||
struct fpga_image_info *info)
|
||||
{
|
||||
struct ice40_fpga_priv *priv = mgr->priv;
|
||||
struct spi_device *dev = priv->dev;
|
||||
const u8 padding[ICE40_SPI_NUM_ACTIVATION_BYTES] = {0};
|
||||
|
||||
/* Check CDONE is asserted */
|
||||
if (!gpiod_get_value(priv->cdone)) {
|
||||
dev_err(&dev->dev,
|
||||
"CDONE was not asserted after firmware transfer\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Send of zero-padding to activate the firmware */
|
||||
return spi_write(dev, padding, sizeof(padding));
|
||||
}
|
||||
|
||||
static const struct fpga_manager_ops ice40_fpga_ops = {
|
||||
.state = ice40_fpga_ops_state,
|
||||
.write_init = ice40_fpga_ops_write_init,
|
||||
.write = ice40_fpga_ops_write,
|
||||
.write_complete = ice40_fpga_ops_write_complete,
|
||||
};
|
||||
|
||||
static int ice40_fpga_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
struct ice40_fpga_priv *priv;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->dev = spi;
|
||||
|
||||
/* Check board setup data. */
|
||||
if (spi->max_speed_hz > ICE40_SPI_MAX_SPEED) {
|
||||
dev_err(dev, "SPI speed is too high, maximum speed is "
|
||||
__stringify(ICE40_SPI_MAX_SPEED) "\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (spi->max_speed_hz < ICE40_SPI_MIN_SPEED) {
|
||||
dev_err(dev, "SPI speed is too low, minimum speed is "
|
||||
__stringify(ICE40_SPI_MIN_SPEED) "\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (spi->mode & SPI_CPHA) {
|
||||
dev_err(dev, "Bad SPI mode, CPHA not supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Set up the GPIOs */
|
||||
priv->cdone = devm_gpiod_get(dev, "cdone", GPIOD_IN);
|
||||
if (IS_ERR(priv->cdone)) {
|
||||
ret = PTR_ERR(priv->cdone);
|
||||
dev_err(dev, "Failed to get CDONE GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(priv->reset)) {
|
||||
ret = PTR_ERR(priv->reset);
|
||||
dev_err(dev, "Failed to get CRESET_B GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register with the FPGA manager */
|
||||
return fpga_mgr_register(dev, "Lattice iCE40 FPGA Manager",
|
||||
&ice40_fpga_ops, priv);
|
||||
}
|
||||
|
||||
static int ice40_fpga_remove(struct spi_device *spi)
|
||||
{
|
||||
fpga_mgr_unregister(&spi->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ice40_fpga_of_match[] = {
|
||||
{ .compatible = "lattice,ice40-fpga-mgr", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ice40_fpga_of_match);
|
||||
|
||||
static struct spi_driver ice40_fpga_driver = {
|
||||
.probe = ice40_fpga_probe,
|
||||
.remove = ice40_fpga_remove,
|
||||
.driver = {
|
||||
.name = "ice40spi",
|
||||
.of_match_table = of_match_ptr(ice40_fpga_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_spi_driver(ice40_fpga_driver);
|
||||
|
||||
MODULE_AUTHOR("Joel Holdsworth <joel@airwebreathe.org.uk>");
|
||||
MODULE_DESCRIPTION("Lattice iCE40 FPGA Manager");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Technologic Systems TS-73xx SBC FPGA loader
|
||||
*
|
||||
* Copyright (C) 2016 Florian Fainelli <f.fainelli@gmail.com>
|
||||
*
|
||||
* FPGA Manager Driver for the on-board Altera Cyclone II FPGA found on
|
||||
* TS-7300, heavily based on load_fpga.c in their vendor tree.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* 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/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/fpga/fpga-mgr.h>
|
||||
|
||||
#define TS73XX_FPGA_DATA_REG 0
|
||||
#define TS73XX_FPGA_CONFIG_REG 1
|
||||
|
||||
#define TS73XX_FPGA_WRITE_DONE 0x1
|
||||
#define TS73XX_FPGA_WRITE_DONE_TIMEOUT 1000 /* us */
|
||||
#define TS73XX_FPGA_RESET 0x2
|
||||
#define TS73XX_FPGA_RESET_LOW_DELAY 30 /* us */
|
||||
#define TS73XX_FPGA_RESET_HIGH_DELAY 80 /* us */
|
||||
#define TS73XX_FPGA_LOAD_OK 0x4
|
||||
#define TS73XX_FPGA_CONFIG_LOAD 0x8
|
||||
|
||||
struct ts73xx_fpga_priv {
|
||||
void __iomem *io_base;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static enum fpga_mgr_states ts73xx_fpga_state(struct fpga_manager *mgr)
|
||||
{
|
||||
return FPGA_MGR_STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
static int ts73xx_fpga_write_init(struct fpga_manager *mgr,
|
||||
struct fpga_image_info *info,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct ts73xx_fpga_priv *priv = mgr->priv;
|
||||
|
||||
/* Reset the FPGA */
|
||||
writeb(0, priv->io_base + TS73XX_FPGA_CONFIG_REG);
|
||||
udelay(TS73XX_FPGA_RESET_LOW_DELAY);
|
||||
writeb(TS73XX_FPGA_RESET, priv->io_base + TS73XX_FPGA_CONFIG_REG);
|
||||
udelay(TS73XX_FPGA_RESET_HIGH_DELAY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts73xx_fpga_write(struct fpga_manager *mgr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct ts73xx_fpga_priv *priv = mgr->priv;
|
||||
size_t i = 0;
|
||||
int ret;
|
||||
u8 reg;
|
||||
|
||||
while (count--) {
|
||||
ret = readb_poll_timeout(priv->io_base + TS73XX_FPGA_CONFIG_REG,
|
||||
reg, !(reg & TS73XX_FPGA_WRITE_DONE),
|
||||
1, TS73XX_FPGA_WRITE_DONE_TIMEOUT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
writeb(buf[i], priv->io_base + TS73XX_FPGA_DATA_REG);
|
||||
i++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ts73xx_fpga_write_complete(struct fpga_manager *mgr,
|
||||
struct fpga_image_info *info)
|
||||
{
|
||||
struct ts73xx_fpga_priv *priv = mgr->priv;
|
||||
u8 reg;
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
|
||||
reg |= TS73XX_FPGA_CONFIG_LOAD;
|
||||
writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG);
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
|
||||
reg &= ~TS73XX_FPGA_CONFIG_LOAD;
|
||||
writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG);
|
||||
|
||||
reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
|
||||
if ((reg & TS73XX_FPGA_LOAD_OK) != TS73XX_FPGA_LOAD_OK)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct fpga_manager_ops ts73xx_fpga_ops = {
|
||||
.state = ts73xx_fpga_state,
|
||||
.write_init = ts73xx_fpga_write_init,
|
||||
.write = ts73xx_fpga_write,
|
||||
.write_complete = ts73xx_fpga_write_complete,
|
||||
};
|
||||
|
||||
static int ts73xx_fpga_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *kdev = &pdev->dev;
|
||||
struct ts73xx_fpga_priv *priv;
|
||||
struct resource *res;
|
||||
|
||||
priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->dev = kdev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
priv->io_base = devm_ioremap_resource(kdev, res);
|
||||
if (IS_ERR(priv->io_base)) {
|
||||
dev_err(kdev, "unable to remap registers\n");
|
||||
return PTR_ERR(priv->io_base);
|
||||
}
|
||||
|
||||
return fpga_mgr_register(kdev, "TS-73xx FPGA Manager",
|
||||
&ts73xx_fpga_ops, priv);
|
||||
}
|
||||
|
||||
static int ts73xx_fpga_remove(struct platform_device *pdev)
|
||||
{
|
||||
fpga_mgr_unregister(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ts73xx_fpga_driver = {
|
||||
.driver = {
|
||||
.name = "ts73xx-fpga-mgr",
|
||||
},
|
||||
.probe = ts73xx_fpga_probe,
|
||||
.remove = ts73xx_fpga_remove,
|
||||
};
|
||||
module_platform_driver(ts73xx_fpga_driver);
|
||||
|
||||
MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>");
|
||||
MODULE_DESCRIPTION("TS-73xx FPGA Manager driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2017, National Instruments Corp.
|
||||
* Copyright (c) 2017, Xilix Inc
|
||||
*
|
||||
* FPGA Bridge Driver for the Xilinx LogiCORE Partial Reconfiguration
|
||||
* Decoupler IP Core.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* 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/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fpga/fpga-bridge.h>
|
||||
|
||||
#define CTRL_CMD_DECOUPLE BIT(0)
|
||||
#define CTRL_CMD_COUPLE 0
|
||||
#define CTRL_OFFSET 0
|
||||
|
||||
struct xlnx_pr_decoupler_data {
|
||||
void __iomem *io_base;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static inline void xlnx_pr_decoupler_write(struct xlnx_pr_decoupler_data *d,
|
||||
u32 offset, u32 val)
|
||||
{
|
||||
writel(val, d->io_base + offset);
|
||||
}
|
||||
|
||||
static inline u32 xlnx_pr_decouple_read(const struct xlnx_pr_decoupler_data *d,
|
||||
u32 offset)
|
||||
{
|
||||
return readl(d->io_base + offset);
|
||||
}
|
||||
|
||||
static int xlnx_pr_decoupler_enable_set(struct fpga_bridge *bridge, bool enable)
|
||||
{
|
||||
int err;
|
||||
struct xlnx_pr_decoupler_data *priv = bridge->priv;
|
||||
|
||||
err = clk_enable(priv->clk);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (enable)
|
||||
xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_COUPLE);
|
||||
else
|
||||
xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_DECOUPLE);
|
||||
|
||||
clk_disable(priv->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xlnx_pr_decoupler_enable_show(struct fpga_bridge *bridge)
|
||||
{
|
||||
const struct xlnx_pr_decoupler_data *priv = bridge->priv;
|
||||
u32 status;
|
||||
int err;
|
||||
|
||||
err = clk_enable(priv->clk);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
status = readl(priv->io_base);
|
||||
|
||||
clk_disable(priv->clk);
|
||||
|
||||
return !status;
|
||||
}
|
||||
|
||||
static struct fpga_bridge_ops xlnx_pr_decoupler_br_ops = {
|
||||
.enable_set = xlnx_pr_decoupler_enable_set,
|
||||
.enable_show = xlnx_pr_decoupler_enable_show,
|
||||
};
|
||||
|
||||
static const struct of_device_id xlnx_pr_decoupler_of_match[] = {
|
||||
{ .compatible = "xlnx,pr-decoupler-1.00", },
|
||||
{ .compatible = "xlnx,pr-decoupler", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, xlnx_pr_decoupler_of_match);
|
||||
|
||||
static int xlnx_pr_decoupler_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct xlnx_pr_decoupler_data *priv;
|
||||
int err;
|
||||
struct resource *res;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
priv->io_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(priv->io_base))
|
||||
return PTR_ERR(priv->io_base);
|
||||
|
||||
priv->clk = devm_clk_get(&pdev->dev, "aclk");
|
||||
if (IS_ERR(priv->clk)) {
|
||||
dev_err(&pdev->dev, "input clock not found\n");
|
||||
return PTR_ERR(priv->clk);
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(priv->clk);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "unable to enable clock\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
clk_disable(priv->clk);
|
||||
|
||||
err = fpga_bridge_register(&pdev->dev, "Xilinx PR Decoupler",
|
||||
&xlnx_pr_decoupler_br_ops, priv);
|
||||
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "unable to register Xilinx PR Decoupler");
|
||||
clk_unprepare(priv->clk);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xlnx_pr_decoupler_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct fpga_bridge *bridge = platform_get_drvdata(pdev);
|
||||
struct xlnx_pr_decoupler_data *p = bridge->priv;
|
||||
|
||||
fpga_bridge_unregister(&pdev->dev);
|
||||
|
||||
clk_unprepare(p->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver xlnx_pr_decoupler_driver = {
|
||||
.probe = xlnx_pr_decoupler_probe,
|
||||
.remove = xlnx_pr_decoupler_remove,
|
||||
.driver = {
|
||||
.name = "xlnx_pr_decoupler",
|
||||
.of_match_table = of_match_ptr(xlnx_pr_decoupler_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(xlnx_pr_decoupler_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler");
|
||||
MODULE_AUTHOR("Moritz Fischer <mdf@kernel.org>");
|
||||
MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Xilinx Spartan6 Slave Serial SPI Driver
|
||||
*
|
||||
* Copyright (C) 2017 DENX Software Engineering
|
||||
*
|
||||
* Anatolij Gustschin <agust@denx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* Manage Xilinx FPGA firmware that is loaded over SPI using
|
||||
* the slave serial configuration interface.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fpga/fpga-mgr.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
struct xilinx_spi_conf {
|
||||
struct spi_device *spi;
|
||||
struct gpio_desc *prog_b;
|
||||
struct gpio_desc *done;
|
||||
};
|
||||
|
||||
static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr)
|
||||
{
|
||||
struct xilinx_spi_conf *conf = mgr->priv;
|
||||
|
||||
if (!gpiod_get_value(conf->done))
|
||||
return FPGA_MGR_STATE_RESET;
|
||||
|
||||
return FPGA_MGR_STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
static int xilinx_spi_write_init(struct fpga_manager *mgr,
|
||||
struct fpga_image_info *info,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct xilinx_spi_conf *conf = mgr->priv;
|
||||
const size_t prog_latency_7500us = 7500;
|
||||
const size_t prog_pulse_1us = 1;
|
||||
|
||||
if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
|
||||
dev_err(&mgr->dev, "Partial reconfiguration not supported.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gpiod_set_value(conf->prog_b, 1);
|
||||
|
||||
udelay(prog_pulse_1us); /* min is 500 ns */
|
||||
|
||||
gpiod_set_value(conf->prog_b, 0);
|
||||
|
||||
if (gpiod_get_value(conf->done)) {
|
||||
dev_err(&mgr->dev, "Unexpected DONE pin state...\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* program latency */
|
||||
usleep_range(prog_latency_7500us, prog_latency_7500us + 100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct xilinx_spi_conf *conf = mgr->priv;
|
||||
const char *fw_data = buf;
|
||||
const char *fw_data_end = fw_data + count;
|
||||
|
||||
while (fw_data < fw_data_end) {
|
||||
size_t remaining, stride;
|
||||
int ret;
|
||||
|
||||
remaining = fw_data_end - fw_data;
|
||||
stride = min_t(size_t, remaining, SZ_4K);
|
||||
|
||||
ret = spi_write(conf->spi, fw_data, stride);
|
||||
if (ret) {
|
||||
dev_err(&mgr->dev, "SPI error in firmware write: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
fw_data += stride;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int xilinx_spi_apply_cclk_cycles(struct xilinx_spi_conf *conf)
|
||||
{
|
||||
struct spi_device *spi = conf->spi;
|
||||
const u8 din_data[1] = { 0xff };
|
||||
int ret;
|
||||
|
||||
ret = spi_write(conf->spi, din_data, sizeof(din_data));
|
||||
if (ret)
|
||||
dev_err(&spi->dev, "applying CCLK cycles failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int xilinx_spi_write_complete(struct fpga_manager *mgr,
|
||||
struct fpga_image_info *info)
|
||||
{
|
||||
struct xilinx_spi_conf *conf = mgr->priv;
|
||||
unsigned long timeout;
|
||||
int ret;
|
||||
|
||||
if (gpiod_get_value(conf->done))
|
||||
return xilinx_spi_apply_cclk_cycles(conf);
|
||||
|
||||
timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us);
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
|
||||
ret = xilinx_spi_apply_cclk_cycles(conf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (gpiod_get_value(conf->done))
|
||||
return xilinx_spi_apply_cclk_cycles(conf);
|
||||
}
|
||||
|
||||
dev_err(&mgr->dev, "Timeout after config data transfer.\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static const struct fpga_manager_ops xilinx_spi_ops = {
|
||||
.state = xilinx_spi_state,
|
||||
.write_init = xilinx_spi_write_init,
|
||||
.write = xilinx_spi_write,
|
||||
.write_complete = xilinx_spi_write_complete,
|
||||
};
|
||||
|
||||
static int xilinx_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct xilinx_spi_conf *conf;
|
||||
|
||||
conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL);
|
||||
if (!conf)
|
||||
return -ENOMEM;
|
||||
|
||||
conf->spi = spi;
|
||||
|
||||
/* PROGRAM_B is active low */
|
||||
conf->prog_b = devm_gpiod_get(&spi->dev, "prog_b", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(conf->prog_b)) {
|
||||
dev_err(&spi->dev, "Failed to get PROGRAM_B gpio: %ld\n",
|
||||
PTR_ERR(conf->prog_b));
|
||||
return PTR_ERR(conf->prog_b);
|
||||
}
|
||||
|
||||
conf->done = devm_gpiod_get(&spi->dev, "done", GPIOD_IN);
|
||||
if (IS_ERR(conf->done)) {
|
||||
dev_err(&spi->dev, "Failed to get DONE gpio: %ld\n",
|
||||
PTR_ERR(conf->done));
|
||||
return PTR_ERR(conf->done);
|
||||
}
|
||||
|
||||
return fpga_mgr_register(&spi->dev, "Xilinx Slave Serial FPGA Manager",
|
||||
&xilinx_spi_ops, conf);
|
||||
}
|
||||
|
||||
static int xilinx_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
fpga_mgr_unregister(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id xlnx_spi_of_match[] = {
|
||||
{ .compatible = "xlnx,fpga-slave-serial", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, xlnx_spi_of_match);
|
||||
|
||||
static struct spi_driver xilinx_slave_spi_driver = {
|
||||
.driver = {
|
||||
.name = "xlnx-slave-spi",
|
||||
.of_match_table = of_match_ptr(xlnx_spi_of_match),
|
||||
},
|
||||
.probe = xilinx_spi_probe,
|
||||
.remove = xilinx_spi_remove,
|
||||
};
|
||||
|
||||
module_spi_driver(xilinx_slave_spi_driver)
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>");
|
||||
MODULE_DESCRIPTION("Load Xilinx FPGA firmware over SPI");
|
|
@ -72,6 +72,10 @@
|
|||
#define CTRL_PCAP_PR_MASK BIT(27)
|
||||
/* Enable PCAP */
|
||||
#define CTRL_PCAP_MODE_MASK BIT(26)
|
||||
/* Lower rate to allow decrypt on the fly */
|
||||
#define CTRL_PCAP_RATE_EN_MASK BIT(25)
|
||||
/* System booted in secure mode */
|
||||
#define CTRL_SEC_EN_MASK BIT(7)
|
||||
|
||||
/* Miscellaneous Control Register bit definitions */
|
||||
/* Internal PCAP loopback */
|
||||
|
@ -266,6 +270,17 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
/* check if bitstream is encrypted & and system's still secure */
|
||||
if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) {
|
||||
ctrl = zynq_fpga_read(priv, CTRL_OFFSET);
|
||||
if (!(ctrl & CTRL_SEC_EN_MASK)) {
|
||||
dev_err(&mgr->dev,
|
||||
"System not secure, can't use crypted bitstreams\n");
|
||||
err = -EINVAL;
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
|
||||
/* don't globally reset PL if we're doing partial reconfig */
|
||||
if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
|
||||
if (!zynq_fpga_has_sync(buf, count)) {
|
||||
|
@ -337,12 +352,19 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
|
|||
|
||||
/* set configuration register with following options:
|
||||
* - enable PCAP interface
|
||||
* - set throughput for maximum speed
|
||||
* - set throughput for maximum speed (if bistream not crypted)
|
||||
* - set CPU in user mode
|
||||
*/
|
||||
ctrl = zynq_fpga_read(priv, CTRL_OFFSET);
|
||||
zynq_fpga_write(priv, CTRL_OFFSET,
|
||||
(CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK | ctrl));
|
||||
if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM)
|
||||
zynq_fpga_write(priv, CTRL_OFFSET,
|
||||
(CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK
|
||||
| CTRL_PCAP_RATE_EN_MASK | ctrl));
|
||||
else
|
||||
zynq_fpga_write(priv, CTRL_OFFSET,
|
||||
(CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK
|
||||
| ctrl));
|
||||
|
||||
|
||||
/* We expect that the command queue is empty right now. */
|
||||
status = zynq_fpga_read(priv, STATUS_OFFSET);
|
||||
|
|
|
@ -1035,18 +1035,14 @@ static int gpiochip_setup_dev(struct gpio_device *gdev)
|
|||
|
||||
cdev_init(&gdev->chrdev, &gpio_fileops);
|
||||
gdev->chrdev.owner = THIS_MODULE;
|
||||
gdev->chrdev.kobj.parent = &gdev->dev.kobj;
|
||||
gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id);
|
||||
status = cdev_add(&gdev->chrdev, gdev->dev.devt, 1);
|
||||
if (status < 0)
|
||||
chip_warn(gdev->chip, "failed to add char device %d:%d\n",
|
||||
MAJOR(gpio_devt), gdev->id);
|
||||
else
|
||||
chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n",
|
||||
MAJOR(gpio_devt), gdev->id);
|
||||
status = device_add(&gdev->dev);
|
||||
|
||||
status = cdev_device_add(&gdev->chrdev, &gdev->dev);
|
||||
if (status)
|
||||
goto err_remove_chardev;
|
||||
return status;
|
||||
|
||||
chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n",
|
||||
MAJOR(gpio_devt), gdev->id);
|
||||
|
||||
status = gpiochip_sysfs_register(gdev);
|
||||
if (status)
|
||||
|
@ -1061,9 +1057,7 @@ static int gpiochip_setup_dev(struct gpio_device *gdev)
|
|||
return 0;
|
||||
|
||||
err_remove_device:
|
||||
device_del(&gdev->dev);
|
||||
err_remove_chardev:
|
||||
cdev_del(&gdev->chrdev);
|
||||
cdev_device_del(&gdev->chrdev, &gdev->dev);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -1347,8 +1341,7 @@ void gpiochip_remove(struct gpio_chip *chip)
|
|||
* be removed, else it will be dangling until the last user is
|
||||
* gone.
|
||||
*/
|
||||
cdev_del(&gdev->chrdev);
|
||||
device_del(&gdev->dev);
|
||||
cdev_device_del(&gdev->chrdev, &gdev->dev);
|
||||
put_device(&gdev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(gpiochip_remove);
|
||||
|
|
|
@ -333,7 +333,7 @@ static int create_gpadl_header(void *kbuffer, u32 size,
|
|||
* Gpadl is u32 and we are using a pointer which could
|
||||
* be 64-bit
|
||||
* This is governed by the guest/host protocol and
|
||||
* so the hypervisor gurantees that this is ok.
|
||||
* so the hypervisor guarantees that this is ok.
|
||||
*/
|
||||
for (i = 0; i < pfncurr; i++)
|
||||
gpadl_body->pfn[i] = slow_virt_to_phys(
|
||||
|
@ -380,7 +380,7 @@ static int create_gpadl_header(void *kbuffer, u32 size,
|
|||
}
|
||||
|
||||
/*
|
||||
* vmbus_establish_gpadl - Estabish a GPADL for the specified buffer
|
||||
* vmbus_establish_gpadl - Establish a GPADL for the specified buffer
|
||||
*
|
||||
* @channel: a channel
|
||||
* @kbuffer: from kmalloc or vmalloc
|
||||
|
@ -731,7 +731,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
|
|||
/* Setup the descriptor */
|
||||
desc.type = VM_PKT_DATA_USING_GPA_DIRECT;
|
||||
desc.flags = flags;
|
||||
desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */
|
||||
desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */
|
||||
desc.length8 = (u16)(packetlen_aligned >> 3);
|
||||
desc.transactionid = requestid;
|
||||
desc.rangecount = pagecount;
|
||||
|
@ -792,7 +792,7 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel,
|
|||
/* Setup the descriptor */
|
||||
desc->type = VM_PKT_DATA_USING_GPA_DIRECT;
|
||||
desc->flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED;
|
||||
desc->dataoffset8 = desc_size >> 3; /* in 8-bytes grandularity */
|
||||
desc->dataoffset8 = desc_size >> 3; /* in 8-bytes granularity */
|
||||
desc->length8 = (u16)(packetlen_aligned >> 3);
|
||||
desc->transactionid = requestid;
|
||||
desc->rangecount = 1;
|
||||
|
@ -842,7 +842,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
|
|||
/* Setup the descriptor */
|
||||
desc.type = VM_PKT_DATA_USING_GPA_DIRECT;
|
||||
desc.flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED;
|
||||
desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */
|
||||
desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */
|
||||
desc.length8 = (u16)(packetlen_aligned >> 3);
|
||||
desc.transactionid = requestid;
|
||||
desc.rangecount = 1;
|
||||
|
|
|
@ -1080,30 +1080,30 @@ static void vmbus_onversion_response(
|
|||
}
|
||||
|
||||
/* Channel message dispatch table */
|
||||
struct vmbus_channel_message_table_entry
|
||||
channel_message_table[CHANNELMSG_COUNT] = {
|
||||
{CHANNELMSG_INVALID, 0, NULL},
|
||||
{CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer},
|
||||
{CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind},
|
||||
{CHANNELMSG_REQUESTOFFERS, 0, NULL},
|
||||
{CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered},
|
||||
{CHANNELMSG_OPENCHANNEL, 0, NULL},
|
||||
{CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result},
|
||||
{CHANNELMSG_CLOSECHANNEL, 0, NULL},
|
||||
{CHANNELMSG_GPADL_HEADER, 0, NULL},
|
||||
{CHANNELMSG_GPADL_BODY, 0, NULL},
|
||||
{CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created},
|
||||
{CHANNELMSG_GPADL_TEARDOWN, 0, NULL},
|
||||
{CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown},
|
||||
{CHANNELMSG_RELID_RELEASED, 0, NULL},
|
||||
{CHANNELMSG_INITIATE_CONTACT, 0, NULL},
|
||||
{CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response},
|
||||
{CHANNELMSG_UNLOAD, 0, NULL},
|
||||
{CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response},
|
||||
{CHANNELMSG_18, 0, NULL},
|
||||
{CHANNELMSG_19, 0, NULL},
|
||||
{CHANNELMSG_20, 0, NULL},
|
||||
{CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL},
|
||||
const struct vmbus_channel_message_table_entry
|
||||
channel_message_table[CHANNELMSG_COUNT] = {
|
||||
{ CHANNELMSG_INVALID, 0, NULL },
|
||||
{ CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer },
|
||||
{ CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind },
|
||||
{ CHANNELMSG_REQUESTOFFERS, 0, NULL },
|
||||
{ CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered },
|
||||
{ CHANNELMSG_OPENCHANNEL, 0, NULL },
|
||||
{ CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result },
|
||||
{ CHANNELMSG_CLOSECHANNEL, 0, NULL },
|
||||
{ CHANNELMSG_GPADL_HEADER, 0, NULL },
|
||||
{ CHANNELMSG_GPADL_BODY, 0, NULL },
|
||||
{ CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created },
|
||||
{ CHANNELMSG_GPADL_TEARDOWN, 0, NULL },
|
||||
{ CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown },
|
||||
{ CHANNELMSG_RELID_RELEASED, 0, NULL },
|
||||
{ CHANNELMSG_INITIATE_CONTACT, 0, NULL },
|
||||
{ CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response },
|
||||
{ CHANNELMSG_UNLOAD, 0, NULL },
|
||||
{ CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response },
|
||||
{ CHANNELMSG_18, 0, NULL },
|
||||
{ CHANNELMSG_19, 0, NULL },
|
||||
{ CHANNELMSG_20, 0, NULL },
|
||||
{ CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL },
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -296,44 +296,47 @@ struct vmbus_channel *relid2channel(u32 relid)
|
|||
|
||||
/*
|
||||
* vmbus_on_event - Process a channel event notification
|
||||
*
|
||||
* For batched channels (default) optimize host to guest signaling
|
||||
* by ensuring:
|
||||
* 1. While reading the channel, we disable interrupts from host.
|
||||
* 2. Ensure that we process all posted messages from the host
|
||||
* before returning from this callback.
|
||||
* 3. Once we return, enable signaling from the host. Once this
|
||||
* state is set we check to see if additional packets are
|
||||
* available to read. In this case we repeat the process.
|
||||
* If this tasklet has been running for a long time
|
||||
* then reschedule ourselves.
|
||||
*/
|
||||
void vmbus_on_event(unsigned long data)
|
||||
{
|
||||
struct vmbus_channel *channel = (void *) data;
|
||||
void (*callback_fn)(void *);
|
||||
unsigned long time_limit = jiffies + 2;
|
||||
|
||||
/*
|
||||
* A channel once created is persistent even when there
|
||||
* is no driver handling the device. An unloading driver
|
||||
* sets the onchannel_callback to NULL on the same CPU
|
||||
* as where this interrupt is handled (in an interrupt context).
|
||||
* Thus, checking and invoking the driver specific callback takes
|
||||
* care of orderly unloading of the driver.
|
||||
*/
|
||||
callback_fn = READ_ONCE(channel->onchannel_callback);
|
||||
if (unlikely(callback_fn == NULL))
|
||||
return;
|
||||
do {
|
||||
void (*callback_fn)(void *);
|
||||
|
||||
(*callback_fn)(channel->channel_callback_context);
|
||||
|
||||
if (channel->callback_mode == HV_CALL_BATCHED) {
|
||||
/*
|
||||
* This callback reads the messages sent by the host.
|
||||
* We can optimize host to guest signaling by ensuring:
|
||||
* 1. While reading the channel, we disable interrupts from
|
||||
* host.
|
||||
* 2. Ensure that we process all posted messages from the host
|
||||
* before returning from this callback.
|
||||
* 3. Once we return, enable signaling from the host. Once this
|
||||
* state is set we check to see if additional packets are
|
||||
* available to read. In this case we repeat the process.
|
||||
/* A channel once created is persistent even when
|
||||
* there is no driver handling the device. An
|
||||
* unloading driver sets the onchannel_callback to NULL.
|
||||
*/
|
||||
if (hv_end_read(&channel->inbound) != 0) {
|
||||
hv_begin_read(&channel->inbound);
|
||||
callback_fn = READ_ONCE(channel->onchannel_callback);
|
||||
if (unlikely(callback_fn == NULL))
|
||||
return;
|
||||
|
||||
tasklet_schedule(&channel->callback_event);
|
||||
}
|
||||
}
|
||||
(*callback_fn)(channel->channel_callback_context);
|
||||
|
||||
if (channel->callback_mode != HV_CALL_BATCHED)
|
||||
return;
|
||||
|
||||
if (likely(hv_end_read(&channel->inbound) == 0))
|
||||
return;
|
||||
|
||||
hv_begin_read(&channel->inbound);
|
||||
} while (likely(time_before(jiffies, time_limit)));
|
||||
|
||||
/* The time limit (2 jiffies) has been reached */
|
||||
tasklet_schedule(&channel->callback_event);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -254,7 +254,10 @@ int hv_synic_init(unsigned int cpu)
|
|||
shared_sint.as_uint64 = 0;
|
||||
shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR;
|
||||
shared_sint.masked = false;
|
||||
shared_sint.auto_eoi = true;
|
||||
if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED)
|
||||
shared_sint.auto_eoi = false;
|
||||
else
|
||||
shared_sint.auto_eoi = true;
|
||||
|
||||
hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT,
|
||||
shared_sint.as_uint64);
|
||||
|
|
|
@ -722,8 +722,6 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size,
|
|||
5*HZ);
|
||||
post_status(&dm_device);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void hv_online_page(struct page *pg)
|
||||
|
|
|
@ -186,8 +186,6 @@ static void fcopy_send_data(struct work_struct *dummy)
|
|||
}
|
||||
}
|
||||
kfree(smsg_out);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -69,7 +69,7 @@ static const int fw_versions[] = {
|
|||
*
|
||||
* While the request/response protocol is guaranteed by the host, we further
|
||||
* ensure this by serializing packet processing in this driver - we do not
|
||||
* read additional packets from the VMBUs until the current packet is fully
|
||||
* read additional packets from the VMBUS until the current packet is fully
|
||||
* handled.
|
||||
*/
|
||||
|
||||
|
@ -397,7 +397,7 @@ kvp_send_key(struct work_struct *dummy)
|
|||
* the max lengths specified. We will however, reserve room
|
||||
* for the string terminating character - in the utf16s_utf8s()
|
||||
* function we limit the size of the buffer where the converted
|
||||
* string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to gaurantee
|
||||
* string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to guarantee
|
||||
* that the strings can be properly terminated!
|
||||
*/
|
||||
|
||||
|
@ -483,8 +483,6 @@ kvp_send_key(struct work_struct *dummy)
|
|||
}
|
||||
|
||||
kfree(message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -533,7 +531,7 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error)
|
|||
*/
|
||||
if (error) {
|
||||
/*
|
||||
* Something failed or we have timedout;
|
||||
* Something failed or we have timed out;
|
||||
* terminate the current host-side iteration.
|
||||
*/
|
||||
goto response_done;
|
||||
|
@ -607,8 +605,8 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error)
|
|||
* This callback is invoked when we get a KVP message from the host.
|
||||
* The host ensures that only one KVP transaction can be active at a time.
|
||||
* KVP implementation in Linux needs to forward the key to a user-mde
|
||||
* component to retrive the corresponding value. Consequently, we cannot
|
||||
* respond to the host in the conext of this callback. Since the host
|
||||
* component to retrieve the corresponding value. Consequently, we cannot
|
||||
* respond to the host in the context of this callback. Since the host
|
||||
* guarantees that at most only one transaction can be active at a time,
|
||||
* we stash away the transaction state in a set of global variables.
|
||||
*/
|
||||
|
|
|
@ -212,8 +212,6 @@ static void vss_send_op(void)
|
|||
}
|
||||
|
||||
kfree(vss_msg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void vss_handle_request(struct work_struct *dummy)
|
||||
|
|
|
@ -218,8 +218,8 @@ struct hv_per_cpu_context {
|
|||
|
||||
struct hv_context {
|
||||
/* We only support running on top of Hyper-V
|
||||
* So at this point this really can only contain the Hyper-V ID
|
||||
*/
|
||||
* So at this point this really can only contain the Hyper-V ID
|
||||
*/
|
||||
u64 guestid;
|
||||
|
||||
void *tsc_page;
|
||||
|
@ -248,14 +248,6 @@ struct hv_context {
|
|||
|
||||
extern struct hv_context hv_context;
|
||||
|
||||
struct hv_ring_buffer_debug_info {
|
||||
u32 current_interrupt_mask;
|
||||
u32 current_read_index;
|
||||
u32 current_write_index;
|
||||
u32 bytes_avail_toread;
|
||||
u32 bytes_avail_towrite;
|
||||
};
|
||||
|
||||
/* Hv Interface */
|
||||
|
||||
extern int hv_init(void);
|
||||
|
@ -289,9 +281,6 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
|
|||
void *buffer, u32 buflen, u32 *buffer_actual_len,
|
||||
u64 *requestid, bool raw);
|
||||
|
||||
void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info,
|
||||
struct hv_ring_buffer_debug_info *debug_info);
|
||||
|
||||
/*
|
||||
* Maximum channels is determined by the size of the interrupt page
|
||||
* which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt
|
||||
|
@ -376,7 +365,7 @@ struct vmbus_channel_message_table_entry {
|
|||
void (*message_handler)(struct vmbus_channel_message_header *msg);
|
||||
};
|
||||
|
||||
extern struct vmbus_channel_message_table_entry
|
||||
extern const struct vmbus_channel_message_table_entry
|
||||
channel_message_table[CHANNELMSG_COUNT];
|
||||
|
||||
|
||||
|
@ -403,17 +392,17 @@ int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep);
|
|||
void vmbus_on_event(unsigned long data);
|
||||
void vmbus_on_msg_dpc(unsigned long data);
|
||||
|
||||
int hv_kvp_init(struct hv_util_service *);
|
||||
int hv_kvp_init(struct hv_util_service *srv);
|
||||
void hv_kvp_deinit(void);
|
||||
void hv_kvp_onchannelcallback(void *);
|
||||
void hv_kvp_onchannelcallback(void *context);
|
||||
|
||||
int hv_vss_init(struct hv_util_service *);
|
||||
int hv_vss_init(struct hv_util_service *srv);
|
||||
void hv_vss_deinit(void);
|
||||
void hv_vss_onchannelcallback(void *);
|
||||
void hv_vss_onchannelcallback(void *context);
|
||||
|
||||
int hv_fcopy_init(struct hv_util_service *);
|
||||
int hv_fcopy_init(struct hv_util_service *srv);
|
||||
void hv_fcopy_deinit(void);
|
||||
void hv_fcopy_onchannelcallback(void *);
|
||||
void hv_fcopy_onchannelcallback(void *context);
|
||||
void vmbus_initiate_unload(bool crash);
|
||||
|
||||
static inline void hv_poll_channel(struct vmbus_channel *channel,
|
||||
|
|
|
@ -75,8 +75,6 @@ static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel)
|
|||
*/
|
||||
if (old_write == READ_ONCE(rbi->ring_buffer->read_index))
|
||||
vmbus_setevent(channel);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the next write location for the specified ring buffer. */
|
||||
|
@ -210,6 +208,7 @@ void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info,
|
|||
ring_info->ring_buffer->interrupt_mask;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hv_ringbuffer_get_debuginfo);
|
||||
|
||||
/* Initialize the ring buffer. */
|
||||
int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
|
||||
|
@ -269,14 +268,13 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
|
|||
int hv_ringbuffer_write(struct vmbus_channel *channel,
|
||||
const struct kvec *kv_list, u32 kv_count)
|
||||
{
|
||||
int i = 0;
|
||||
int i;
|
||||
u32 bytes_avail_towrite;
|
||||
u32 totalbytes_towrite = 0;
|
||||
|
||||
u32 totalbytes_towrite = sizeof(u64);
|
||||
u32 next_write_location;
|
||||
u32 old_write;
|
||||
u64 prev_indices = 0;
|
||||
unsigned long flags = 0;
|
||||
u64 prev_indices;
|
||||
unsigned long flags;
|
||||
struct hv_ring_buffer_info *outring_info = &channel->outbound;
|
||||
|
||||
if (channel->rescind)
|
||||
|
@ -285,8 +283,6 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
|
|||
for (i = 0; i < kv_count; i++)
|
||||
totalbytes_towrite += kv_list[i].iov_len;
|
||||
|
||||
totalbytes_towrite += sizeof(u64);
|
||||
|
||||
spin_lock_irqsave(&outring_info->ring_lock, flags);
|
||||
|
||||
bytes_avail_towrite = hv_get_bytes_to_write(outring_info);
|
||||
|
@ -349,18 +345,16 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
|
|||
u64 *requestid, bool raw)
|
||||
{
|
||||
u32 bytes_avail_toread;
|
||||
u32 next_read_location = 0;
|
||||
u32 next_read_location;
|
||||
u64 prev_indices = 0;
|
||||
struct vmpacket_descriptor desc;
|
||||
u32 offset;
|
||||
u32 packetlen;
|
||||
int ret = 0;
|
||||
struct hv_ring_buffer_info *inring_info = &channel->inbound;
|
||||
|
||||
if (buflen <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
|
||||
*buffer_actual_len = 0;
|
||||
*requestid = 0;
|
||||
|
||||
|
@ -371,7 +365,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
|
|||
* No error is set when there is even no header, drivers are
|
||||
* supposed to analyze buffer_actual_len.
|
||||
*/
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
init_cached_read_index(inring_info);
|
||||
|
@ -417,7 +411,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
|
|||
|
||||
hv_signal_on_read(channel);
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -787,8 +787,6 @@ static void vmbus_shutdown(struct device *child_device)
|
|||
|
||||
if (drv->shutdown)
|
||||
drv->shutdown(dev);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
@ -855,7 +853,7 @@ void vmbus_on_msg_dpc(unsigned long data)
|
|||
struct hv_message *msg = (struct hv_message *)page_addr +
|
||||
VMBUS_MESSAGE_SINT;
|
||||
struct vmbus_channel_message_header *hdr;
|
||||
struct vmbus_channel_message_table_entry *entry;
|
||||
const struct vmbus_channel_message_table_entry *entry;
|
||||
struct onmessage_work_context *ctx;
|
||||
u32 message_type = msg->header.message_type;
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ struct coresight_platform_data *of_get_coresight_platform_data(
|
|||
continue;
|
||||
|
||||
/* The local out port number */
|
||||
pdata->outports[i] = endpoint.id;
|
||||
pdata->outports[i] = endpoint.port;
|
||||
|
||||
/*
|
||||
* Get a handle on the remote port and parent
|
||||
|
|
|
@ -1719,18 +1719,13 @@ int iio_device_register(struct iio_dev *indio_dev)
|
|||
|
||||
cdev_init(&indio_dev->chrdev, &iio_buffer_fileops);
|
||||
indio_dev->chrdev.owner = indio_dev->info->driver_module;
|
||||
indio_dev->chrdev.kobj.parent = &indio_dev->dev.kobj;
|
||||
ret = cdev_add(&indio_dev->chrdev, indio_dev->dev.devt, 1);
|
||||
|
||||
ret = cdev_device_add(&indio_dev->chrdev, &indio_dev->dev);
|
||||
if (ret < 0)
|
||||
goto error_unreg_eventset;
|
||||
|
||||
ret = device_add(&indio_dev->dev);
|
||||
if (ret < 0)
|
||||
goto error_cdev_del;
|
||||
|
||||
return 0;
|
||||
error_cdev_del:
|
||||
cdev_del(&indio_dev->chrdev);
|
||||
|
||||
error_unreg_eventset:
|
||||
iio_device_unregister_eventset(indio_dev);
|
||||
error_free_sysfs:
|
||||
|
@ -1751,10 +1746,8 @@ void iio_device_unregister(struct iio_dev *indio_dev)
|
|||
{
|
||||
mutex_lock(&indio_dev->info_exist_lock);
|
||||
|
||||
device_del(&indio_dev->dev);
|
||||
cdev_device_del(&indio_dev->chrdev, &indio_dev->dev);
|
||||
|
||||
if (indio_dev->chrdev.dev)
|
||||
cdev_del(&indio_dev->chrdev);
|
||||
iio_device_unregister_debugfs(indio_dev);
|
||||
|
||||
iio_disable_all_buffers(indio_dev);
|
||||
|
|
|
@ -1205,12 +1205,15 @@ static void ib_ucm_release_dev(struct device *dev)
|
|||
struct ib_ucm_device *ucm_dev;
|
||||
|
||||
ucm_dev = container_of(dev, struct ib_ucm_device, dev);
|
||||
cdev_del(&ucm_dev->cdev);
|
||||
kfree(ucm_dev);
|
||||
}
|
||||
|
||||
static void ib_ucm_free_dev(struct ib_ucm_device *ucm_dev)
|
||||
{
|
||||
if (ucm_dev->devnum < IB_UCM_MAX_DEVICES)
|
||||
clear_bit(ucm_dev->devnum, dev_map);
|
||||
else
|
||||
clear_bit(ucm_dev->devnum - IB_UCM_MAX_DEVICES, overflow_map);
|
||||
kfree(ucm_dev);
|
||||
}
|
||||
|
||||
static const struct file_operations ucm_fops = {
|
||||
|
@ -1266,7 +1269,9 @@ static void ib_ucm_add_one(struct ib_device *device)
|
|||
if (!ucm_dev)
|
||||
return;
|
||||
|
||||
device_initialize(&ucm_dev->dev);
|
||||
ucm_dev->ib_dev = device;
|
||||
ucm_dev->dev.release = ib_ucm_release_dev;
|
||||
|
||||
devnum = find_first_zero_bit(dev_map, IB_UCM_MAX_DEVICES);
|
||||
if (devnum >= IB_UCM_MAX_DEVICES) {
|
||||
|
@ -1286,16 +1291,14 @@ static void ib_ucm_add_one(struct ib_device *device)
|
|||
cdev_init(&ucm_dev->cdev, &ucm_fops);
|
||||
ucm_dev->cdev.owner = THIS_MODULE;
|
||||
kobject_set_name(&ucm_dev->cdev.kobj, "ucm%d", ucm_dev->devnum);
|
||||
if (cdev_add(&ucm_dev->cdev, base, 1))
|
||||
goto err;
|
||||
|
||||
ucm_dev->dev.class = &cm_class;
|
||||
ucm_dev->dev.parent = device->dev.parent;
|
||||
ucm_dev->dev.devt = ucm_dev->cdev.dev;
|
||||
ucm_dev->dev.release = ib_ucm_release_dev;
|
||||
ucm_dev->dev.devt = base;
|
||||
|
||||
dev_set_name(&ucm_dev->dev, "ucm%d", ucm_dev->devnum);
|
||||
if (device_register(&ucm_dev->dev))
|
||||
goto err_cdev;
|
||||
if (cdev_device_add(&ucm_dev->cdev, &ucm_dev->dev))
|
||||
goto err_devnum;
|
||||
|
||||
if (device_create_file(&ucm_dev->dev, &dev_attr_ibdev))
|
||||
goto err_dev;
|
||||
|
@ -1304,15 +1307,11 @@ static void ib_ucm_add_one(struct ib_device *device)
|
|||
return;
|
||||
|
||||
err_dev:
|
||||
device_unregister(&ucm_dev->dev);
|
||||
err_cdev:
|
||||
cdev_del(&ucm_dev->cdev);
|
||||
if (ucm_dev->devnum < IB_UCM_MAX_DEVICES)
|
||||
clear_bit(devnum, dev_map);
|
||||
else
|
||||
clear_bit(devnum, overflow_map);
|
||||
cdev_device_del(&ucm_dev->cdev, &ucm_dev->dev);
|
||||
err_devnum:
|
||||
ib_ucm_free_dev(ucm_dev);
|
||||
err:
|
||||
kfree(ucm_dev);
|
||||
put_device(&ucm_dev->dev);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1323,7 +1322,9 @@ static void ib_ucm_remove_one(struct ib_device *device, void *client_data)
|
|||
if (!ucm_dev)
|
||||
return;
|
||||
|
||||
device_unregister(&ucm_dev->dev);
|
||||
cdev_device_del(&ucm_dev->cdev, &ucm_dev->dev);
|
||||
ib_ucm_free_dev(ucm_dev);
|
||||
put_device(&ucm_dev->dev);
|
||||
}
|
||||
|
||||
static CLASS_ATTR_STRING(abi_version, S_IRUGO,
|
||||
|
|
|
@ -1187,7 +1187,7 @@ static int ib_umad_init_port(struct ib_device *device, int port_num,
|
|||
|
||||
cdev_init(&port->cdev, &umad_fops);
|
||||
port->cdev.owner = THIS_MODULE;
|
||||
port->cdev.kobj.parent = &umad_dev->kobj;
|
||||
cdev_set_parent(&port->cdev, &umad_dev->kobj);
|
||||
kobject_set_name(&port->cdev.kobj, "umad%d", port->dev_num);
|
||||
if (cdev_add(&port->cdev, base, 1))
|
||||
goto err_cdev;
|
||||
|
@ -1206,7 +1206,7 @@ static int ib_umad_init_port(struct ib_device *device, int port_num,
|
|||
base += IB_UMAD_MAX_PORTS;
|
||||
cdev_init(&port->sm_cdev, &umad_sm_fops);
|
||||
port->sm_cdev.owner = THIS_MODULE;
|
||||
port->sm_cdev.kobj.parent = &umad_dev->kobj;
|
||||
cdev_set_parent(&port->sm_cdev, &umad_dev->kobj);
|
||||
kobject_set_name(&port->sm_cdev.kobj, "issm%d", port->dev_num);
|
||||
if (cdev_add(&port->sm_cdev, base, 1))
|
||||
goto err_sm_cdev;
|
||||
|
|
|
@ -1093,7 +1093,7 @@ static void ib_uverbs_add_one(struct ib_device *device)
|
|||
cdev_init(&uverbs_dev->cdev, NULL);
|
||||
uverbs_dev->cdev.owner = THIS_MODULE;
|
||||
uverbs_dev->cdev.ops = device->mmap ? &uverbs_mmap_fops : &uverbs_fops;
|
||||
uverbs_dev->cdev.kobj.parent = &uverbs_dev->kobj;
|
||||
cdev_set_parent(&uverbs_dev->cdev, &uverbs_dev->kobj);
|
||||
kobject_set_name(&uverbs_dev->cdev.kobj, "uverbs%d", uverbs_dev->devnum);
|
||||
if (cdev_add(&uverbs_dev->cdev, base, 1))
|
||||
goto err_cdev;
|
||||
|
|
|
@ -69,7 +69,7 @@ int hfi1_cdev_init(int minor, const char *name,
|
|||
|
||||
cdev_init(cdev, fops);
|
||||
cdev->owner = THIS_MODULE;
|
||||
cdev->kobj.parent = parent;
|
||||
cdev_set_parent(cdev, parent);
|
||||
kobject_set_name(&cdev->kobj, name);
|
||||
|
||||
ret = cdev_add(cdev, dev, 1);
|
||||
|
|
|
@ -1354,8 +1354,6 @@ static void evdev_cleanup(struct evdev *evdev)
|
|||
evdev_mark_dead(evdev);
|
||||
evdev_hangup(evdev);
|
||||
|
||||
cdev_del(&evdev->cdev);
|
||||
|
||||
/* evdev is marked dead so no one else accesses evdev->open */
|
||||
if (evdev->open) {
|
||||
input_flush_device(handle, NULL);
|
||||
|
@ -1416,12 +1414,8 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
|
|||
goto err_free_evdev;
|
||||
|
||||
cdev_init(&evdev->cdev, &evdev_fops);
|
||||
evdev->cdev.kobj.parent = &evdev->dev.kobj;
|
||||
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
|
||||
if (error)
|
||||
goto err_unregister_handle;
|
||||
|
||||
error = device_add(&evdev->dev);
|
||||
error = cdev_device_add(&evdev->cdev, &evdev->dev);
|
||||
if (error)
|
||||
goto err_cleanup_evdev;
|
||||
|
||||
|
@ -1429,7 +1423,6 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
|
|||
|
||||
err_cleanup_evdev:
|
||||
evdev_cleanup(evdev);
|
||||
err_unregister_handle:
|
||||
input_unregister_handle(&evdev->handle);
|
||||
err_free_evdev:
|
||||
put_device(&evdev->dev);
|
||||
|
@ -1442,7 +1435,7 @@ static void evdev_disconnect(struct input_handle *handle)
|
|||
{
|
||||
struct evdev *evdev = handle->private;
|
||||
|
||||
device_del(&evdev->dev);
|
||||
cdev_device_del(&evdev->cdev, &evdev->dev);
|
||||
evdev_cleanup(evdev);
|
||||
input_free_minor(MINOR(evdev->dev.devt));
|
||||
input_unregister_handle(handle);
|
||||
|
|
|
@ -742,8 +742,6 @@ static void joydev_cleanup(struct joydev *joydev)
|
|||
joydev_mark_dead(joydev);
|
||||
joydev_hangup(joydev);
|
||||
|
||||
cdev_del(&joydev->cdev);
|
||||
|
||||
/* joydev is marked dead so no one else accesses joydev->open */
|
||||
if (joydev->open)
|
||||
input_close_device(handle);
|
||||
|
@ -913,12 +911,8 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev,
|
|||
goto err_free_joydev;
|
||||
|
||||
cdev_init(&joydev->cdev, &joydev_fops);
|
||||
joydev->cdev.kobj.parent = &joydev->dev.kobj;
|
||||
error = cdev_add(&joydev->cdev, joydev->dev.devt, 1);
|
||||
if (error)
|
||||
goto err_unregister_handle;
|
||||
|
||||
error = device_add(&joydev->dev);
|
||||
error = cdev_device_add(&joydev->cdev, &joydev->dev);
|
||||
if (error)
|
||||
goto err_cleanup_joydev;
|
||||
|
||||
|
@ -926,7 +920,6 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev,
|
|||
|
||||
err_cleanup_joydev:
|
||||
joydev_cleanup(joydev);
|
||||
err_unregister_handle:
|
||||
input_unregister_handle(&joydev->handle);
|
||||
err_free_joydev:
|
||||
put_device(&joydev->dev);
|
||||
|
@ -939,7 +932,7 @@ static void joydev_disconnect(struct input_handle *handle)
|
|||
{
|
||||
struct joydev *joydev = handle->private;
|
||||
|
||||
device_del(&joydev->dev);
|
||||
cdev_device_del(&joydev->cdev, &joydev->dev);
|
||||
joydev_cleanup(joydev);
|
||||
input_free_minor(MINOR(joydev->dev.devt));
|
||||
input_unregister_handle(handle);
|
||||
|
|
|
@ -812,8 +812,6 @@ static void mousedev_cleanup(struct mousedev *mousedev)
|
|||
mousedev_mark_dead(mousedev);
|
||||
mousedev_hangup(mousedev);
|
||||
|
||||
cdev_del(&mousedev->cdev);
|
||||
|
||||
/* mousedev is marked dead so no one else accesses mousedev->open */
|
||||
if (mousedev->open)
|
||||
input_close_device(handle);
|
||||
|
@ -901,12 +899,8 @@ static struct mousedev *mousedev_create(struct input_dev *dev,
|
|||
}
|
||||
|
||||
cdev_init(&mousedev->cdev, &mousedev_fops);
|
||||
mousedev->cdev.kobj.parent = &mousedev->dev.kobj;
|
||||
error = cdev_add(&mousedev->cdev, mousedev->dev.devt, 1);
|
||||
if (error)
|
||||
goto err_unregister_handle;
|
||||
|
||||
error = device_add(&mousedev->dev);
|
||||
error = cdev_device_add(&mousedev->cdev, &mousedev->dev);
|
||||
if (error)
|
||||
goto err_cleanup_mousedev;
|
||||
|
||||
|
@ -914,7 +908,6 @@ static struct mousedev *mousedev_create(struct input_dev *dev,
|
|||
|
||||
err_cleanup_mousedev:
|
||||
mousedev_cleanup(mousedev);
|
||||
err_unregister_handle:
|
||||
if (!mixdev)
|
||||
input_unregister_handle(&mousedev->handle);
|
||||
err_free_mousedev:
|
||||
|
@ -927,7 +920,7 @@ static struct mousedev *mousedev_create(struct input_dev *dev,
|
|||
|
||||
static void mousedev_destroy(struct mousedev *mousedev)
|
||||
{
|
||||
device_del(&mousedev->dev);
|
||||
cdev_device_del(&mousedev->cdev, &mousedev->dev);
|
||||
mousedev_cleanup(mousedev);
|
||||
input_free_minor(MINOR(mousedev->dev.devt));
|
||||
if (mousedev != mousedev_mix)
|
||||
|
|
|
@ -137,24 +137,17 @@ static int __must_check cec_devnode_register(struct cec_devnode *devnode,
|
|||
|
||||
/* Part 2: Initialize and register the character device */
|
||||
cdev_init(&devnode->cdev, &cec_devnode_fops);
|
||||
devnode->cdev.kobj.parent = &devnode->dev.kobj;
|
||||
devnode->cdev.owner = owner;
|
||||
|
||||
ret = cdev_add(&devnode->cdev, devnode->dev.devt, 1);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: cdev_add failed\n", __func__);
|
||||
ret = cdev_device_add(&devnode->cdev, &devnode->dev);
|
||||
if (ret) {
|
||||
pr_err("%s: cdev_device_add failed\n", __func__);
|
||||
goto clr_bit;
|
||||
}
|
||||
|
||||
ret = device_add(&devnode->dev);
|
||||
if (ret)
|
||||
goto cdev_del;
|
||||
|
||||
devnode->registered = true;
|
||||
return 0;
|
||||
|
||||
cdev_del:
|
||||
cdev_del(&devnode->cdev);
|
||||
clr_bit:
|
||||
mutex_lock(&cec_devnode_lock);
|
||||
clear_bit(devnode->minor, cec_devnode_nums);
|
||||
|
@ -190,8 +183,7 @@ static void cec_devnode_unregister(struct cec_devnode *devnode)
|
|||
devnode->unregistered = true;
|
||||
mutex_unlock(&devnode->lock);
|
||||
|
||||
device_del(&devnode->dev);
|
||||
cdev_del(&devnode->cdev);
|
||||
cdev_device_del(&devnode->cdev, &devnode->dev);
|
||||
put_device(&devnode->dev);
|
||||
}
|
||||
|
||||
|
|
|
@ -248,31 +248,22 @@ int __must_check media_devnode_register(struct media_device *mdev,
|
|||
dev_set_name(&devnode->dev, "media%d", devnode->minor);
|
||||
device_initialize(&devnode->dev);
|
||||
|
||||
/* Part 2: Initialize and register the character device */
|
||||
/* Part 2: Initialize the character device */
|
||||
cdev_init(&devnode->cdev, &media_devnode_fops);
|
||||
devnode->cdev.owner = owner;
|
||||
devnode->cdev.kobj.parent = &devnode->dev.kobj;
|
||||
|
||||
ret = cdev_add(&devnode->cdev, MKDEV(MAJOR(media_dev_t), devnode->minor), 1);
|
||||
/* Part 3: Add the media and char device */
|
||||
ret = cdev_device_add(&devnode->cdev, &devnode->dev);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: cdev_add failed\n", __func__);
|
||||
pr_err("%s: cdev_device_add failed\n", __func__);
|
||||
goto cdev_add_error;
|
||||
}
|
||||
|
||||
/* Part 3: Add the media device */
|
||||
ret = device_add(&devnode->dev);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: device_add failed\n", __func__);
|
||||
goto device_add_error;
|
||||
}
|
||||
|
||||
/* Part 4: Activate this minor. The char device can now be used. */
|
||||
set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
|
||||
|
||||
return 0;
|
||||
|
||||
device_add_error:
|
||||
cdev_del(&devnode->cdev);
|
||||
cdev_add_error:
|
||||
mutex_lock(&media_devnode_lock);
|
||||
clear_bit(devnode->minor, media_devnode_nums);
|
||||
|
@ -298,9 +289,8 @@ void media_devnode_unregister(struct media_devnode *devnode)
|
|||
{
|
||||
mutex_lock(&media_devnode_lock);
|
||||
/* Delete the cdev on this minor as well */
|
||||
cdev_del(&devnode->cdev);
|
||||
cdev_device_del(&devnode->cdev, &devnode->dev);
|
||||
mutex_unlock(&media_devnode_lock);
|
||||
device_del(&devnode->dev);
|
||||
devnode->media_dev = NULL;
|
||||
put_device(&devnode->dev);
|
||||
}
|
||||
|
|
|
@ -419,16 +419,6 @@ config VMWARE_BALLOON
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called vmw_balloon.
|
||||
|
||||
config ARM_CHARLCD
|
||||
bool "ARM Ltd. Character LCD Driver"
|
||||
depends on PLAT_VERSATILE
|
||||
help
|
||||
This is a driver for the character LCD found on the ARM Ltd.
|
||||
Versatile and RealView Platform Baseboards. It doesn't do
|
||||
very much more than display the text "ARM Linux" on the first
|
||||
line and the Linux version on the second line, but that's
|
||||
still useful.
|
||||
|
||||
config PCH_PHUB
|
||||
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB"
|
||||
select GENERIC_NET_UTILS
|
||||
|
@ -492,284 +482,13 @@ config VEXPRESS_SYSCFG
|
|||
bus. System Configuration interface is one of the possible means
|
||||
of generating transactions on this bus.
|
||||
|
||||
config PANEL
|
||||
tristate "Parallel port LCD/Keypad Panel support"
|
||||
depends on PARPORT
|
||||
config ASPEED_LPC_CTRL
|
||||
depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
|
||||
tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control"
|
||||
---help---
|
||||
Say Y here if you have an HD44780 or KS-0074 LCD connected to your
|
||||
parallel port. This driver also features 4 and 6-key keypads. The LCD
|
||||
is accessible through the /dev/lcd char device (10, 156), and the
|
||||
keypad through /dev/keypad (10, 185). This code can either be
|
||||
compiled as a module, or linked into the kernel and started at boot.
|
||||
If you don't understand what all this is about, say N.
|
||||
|
||||
if PANEL
|
||||
|
||||
config PANEL_PARPORT
|
||||
int "Default parallel port number (0=LPT1)"
|
||||
range 0 255
|
||||
default "0"
|
||||
---help---
|
||||
This is the index of the parallel port the panel is connected to. One
|
||||
driver instance only supports one parallel port, so if your keypad
|
||||
and LCD are connected to two separate ports, you have to start two
|
||||
modules with different arguments. Numbering starts with '0' for LPT1,
|
||||
and so on.
|
||||
|
||||
config PANEL_PROFILE
|
||||
int "Default panel profile (0-5, 0=custom)"
|
||||
range 0 5
|
||||
default "5"
|
||||
---help---
|
||||
To ease configuration, the driver supports different configuration
|
||||
profiles for past and recent wirings. These profiles can also be
|
||||
used to define an approximative configuration, completed by a few
|
||||
other options. Here are the profiles :
|
||||
|
||||
0 = custom (see further)
|
||||
1 = 2x16 parallel LCD, old keypad
|
||||
2 = 2x16 serial LCD (KS-0074), new keypad
|
||||
3 = 2x16 parallel LCD (Hantronix), no keypad
|
||||
4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad
|
||||
5 = 2x40 parallel LCD (old one), with old keypad
|
||||
|
||||
Custom configurations allow you to define how your display is
|
||||
wired to the parallel port, and how it works. This is only intended
|
||||
for experts.
|
||||
|
||||
config PANEL_KEYPAD
|
||||
depends on PANEL_PROFILE="0"
|
||||
int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)"
|
||||
range 0 3
|
||||
default 0
|
||||
---help---
|
||||
This enables and configures a keypad connected to the parallel port.
|
||||
The keys will be read from character device 10,185. Valid values are :
|
||||
|
||||
0 : do not enable this driver
|
||||
1 : old 6 keys keypad
|
||||
2 : new 6 keys keypad, as used on the server at www.ant-computing.com
|
||||
3 : Nexcom NSA1045's 4 keys keypad
|
||||
|
||||
New profiles can be described in the driver source. The driver also
|
||||
supports simultaneous keys pressed when the keypad supports them.
|
||||
|
||||
config PANEL_LCD
|
||||
depends on PANEL_PROFILE="0"
|
||||
int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)"
|
||||
range 0 5
|
||||
default 0
|
||||
---help---
|
||||
This enables and configures an LCD connected to the parallel port.
|
||||
The driver includes an interpreter for escape codes starting with
|
||||
'\e[L' which are specific to the LCD, and a few ANSI codes. The
|
||||
driver will be registered as character device 10,156, usually
|
||||
under the name '/dev/lcd'. There are a total of 6 supported types :
|
||||
|
||||
0 : do not enable the driver
|
||||
1 : custom configuration and wiring (see further)
|
||||
2 : 2x16 & 2x40 parallel LCD (old wiring)
|
||||
3 : 2x16 serial LCD (KS-0074 based)
|
||||
4 : 2x16 parallel LCD (Hantronix wiring)
|
||||
5 : 2x16 parallel LCD (Nexcom wiring)
|
||||
|
||||
When type '1' is specified, other options will appear to configure
|
||||
more precise aspects (wiring, dimensions, protocol, ...). Please note
|
||||
that those values changed from the 2.4 driver for better consistency.
|
||||
|
||||
config PANEL_LCD_HEIGHT
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Number of lines on the LCD (1-2)"
|
||||
range 1 2
|
||||
default 2
|
||||
---help---
|
||||
This is the number of visible character lines on the LCD in custom profile.
|
||||
It can either be 1 or 2.
|
||||
|
||||
config PANEL_LCD_WIDTH
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Number of characters per line on the LCD (1-40)"
|
||||
range 1 40
|
||||
default 40
|
||||
---help---
|
||||
This is the number of characters per line on the LCD in custom profile.
|
||||
Common values are 16,20,24,40.
|
||||
|
||||
config PANEL_LCD_BWIDTH
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Internal LCD line width (1-40, 40 by default)"
|
||||
range 1 40
|
||||
default 40
|
||||
---help---
|
||||
Most LCDs use a standard controller which supports hardware lines of 40
|
||||
characters, although sometimes only 16, 20 or 24 of them are really wired
|
||||
to the terminal. This results in some non-visible but addressable characters,
|
||||
and is the case for most parallel LCDs. Other LCDs, and some serial ones,
|
||||
however, use the same line width internally as what is visible. The KS0074
|
||||
for example, uses 16 characters per line for 16 visible characters per line.
|
||||
|
||||
This option lets you configure the value used by your LCD in 'custom' profile.
|
||||
If you don't know, put '40' here.
|
||||
|
||||
config PANEL_LCD_HWIDTH
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Hardware LCD line width (1-64, 64 by default)"
|
||||
range 1 64
|
||||
default 64
|
||||
---help---
|
||||
Most LCDs use a single address bit to differentiate line 0 and line 1. Since
|
||||
some of them need to be able to address 40 chars with the lower bits, they
|
||||
often use the immediately superior power of 2, which is 64, to address the
|
||||
next line.
|
||||
|
||||
If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and
|
||||
64 here for a 2x40.
|
||||
|
||||
config PANEL_LCD_CHARSET
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "LCD character set (0=normal, 1=KS0074)"
|
||||
range 0 1
|
||||
default 0
|
||||
---help---
|
||||
Some controllers such as the KS0074 use a somewhat strange character set
|
||||
where many symbols are at unusual places. The driver knows how to map
|
||||
'standard' ASCII characters to the character sets used by these controllers.
|
||||
Valid values are :
|
||||
|
||||
0 : normal (untranslated) character set
|
||||
1 : KS0074 character set
|
||||
|
||||
If you don't know, use the normal one (0).
|
||||
|
||||
config PANEL_LCD_PROTO
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "LCD communication mode (0=parallel 8 bits, 1=serial)"
|
||||
range 0 1
|
||||
default 0
|
||||
---help---
|
||||
This driver now supports any serial or parallel LCD wired to a parallel
|
||||
port. But before assigning signals, the driver needs to know if it will
|
||||
be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires
|
||||
(SDA/SCL), while parallel ones use 2 or 3 wires for the control signals
|
||||
(E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits
|
||||
parallel LCD, and 1 for a serial LCD.
|
||||
|
||||
config PANEL_LCD_PIN_E
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) "
|
||||
range -17 17
|
||||
default 14
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the LCD 'E'
|
||||
signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'E' pin in custom profile is '14' (AUTOFEED).
|
||||
|
||||
config PANEL_LCD_PIN_RS
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) "
|
||||
range -17 17
|
||||
default 17
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the LCD 'RS'
|
||||
signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'RS' pin in custom profile is '17' (SELECT IN).
|
||||
|
||||
config PANEL_LCD_PIN_RW
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) "
|
||||
range -17 17
|
||||
default 16
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the LCD 'RW'
|
||||
signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'RW' pin in custom profile is '16' (INIT).
|
||||
|
||||
config PANEL_LCD_PIN_SCL
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) "
|
||||
range -17 17
|
||||
default 1
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the serial
|
||||
LCD 'SCL' signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'SCL' pin in custom profile is '1' (STROBE).
|
||||
|
||||
config PANEL_LCD_PIN_SDA
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
|
||||
int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) "
|
||||
range -17 17
|
||||
default 2
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the serial
|
||||
LCD 'SDA' signal has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'SDA' pin in custom profile is '2' (D0).
|
||||
|
||||
config PANEL_LCD_PIN_BL
|
||||
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||
int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) "
|
||||
range -17 17
|
||||
default 0
|
||||
---help---
|
||||
This describes the number of the parallel port pin to which the LCD 'BL' signal
|
||||
has been connected. It can be :
|
||||
|
||||
0 : no connection (eg: connected to ground)
|
||||
1..17 : directly connected to any of these pins on the DB25 plug
|
||||
-1..-17 : connected to the same pin through an inverter (eg: transistor).
|
||||
|
||||
Default for the 'BL' pin in custom profile is '0' (uncontrolled).
|
||||
|
||||
config PANEL_CHANGE_MESSAGE
|
||||
bool "Change LCD initialization message ?"
|
||||
default "n"
|
||||
---help---
|
||||
This allows you to replace the boot message indicating the kernel version
|
||||
and the driver version with a custom message. This is useful on appliances
|
||||
where a simple 'Starting system' message can be enough to stop a customer
|
||||
from worrying.
|
||||
|
||||
If you say 'Y' here, you'll be able to choose a message yourself. Otherwise,
|
||||
say 'N' and keep the default message with the version.
|
||||
|
||||
config PANEL_BOOT_MESSAGE
|
||||
depends on PANEL_CHANGE_MESSAGE="y"
|
||||
string "New initialization message"
|
||||
default ""
|
||||
---help---
|
||||
This allows you to replace the boot message indicating the kernel version
|
||||
and the driver version with a custom message. This is useful on appliances
|
||||
where a simple 'Starting system' message can be enough to stop a customer
|
||||
from worrying.
|
||||
|
||||
An empty message will only clear the display at driver init time. Any other
|
||||
printf()-formatted message is valid with newline and escape codes.
|
||||
|
||||
endif # PANEL
|
||||
Control Aspeed ast2400/2500 HOST LPC to BMC mappings through
|
||||
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.
|
||||
|
||||
source "drivers/misc/c2port/Kconfig"
|
||||
source "drivers/misc/eeprom/Kconfig"
|
||||
|
|
|
@ -37,7 +37,6 @@ obj-y += eeprom/
|
|||
obj-y += cb710/
|
||||
obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o
|
||||
obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o
|
||||
obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
|
||||
obj-$(CONFIG_PCH_PHUB) += pch_phub.o
|
||||
obj-y += ti-st/
|
||||
obj-y += lis3lv02d/
|
||||
|
@ -53,7 +52,7 @@ obj-$(CONFIG_GENWQE) += genwqe/
|
|||
obj-$(CONFIG_ECHO) += echo/
|
||||
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
|
||||
obj-$(CONFIG_CXL_BASE) += cxl/
|
||||
obj-$(CONFIG_PANEL) += panel.o
|
||||
obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
|
||||
|
||||
lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
|
||||
lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o
|
||||
|
@ -62,6 +61,8 @@ lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o
|
|||
lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o
|
||||
lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o
|
||||
|
||||
KCOV_INSTRUMENT_lkdtm_rodata.o := n
|
||||
|
||||
OBJCOPYFLAGS :=
|
||||
OBJCOPYFLAGS_lkdtm_rodata_objcopy.o := \
|
||||
--set-section-flags .text=alloc,readonly \
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Copyright 2017 IBM Corporation
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/aspeed-lpc-ctrl.h>
|
||||
|
||||
#define DEVICE_NAME "aspeed-lpc-ctrl"
|
||||
|
||||
#define HICR7 0x8
|
||||
#define HICR8 0xc
|
||||
|
||||
struct aspeed_lpc_ctrl {
|
||||
struct miscdevice miscdev;
|
||||
struct regmap *regmap;
|
||||
phys_addr_t mem_base;
|
||||
resource_size_t mem_size;
|
||||
u32 pnor_size;
|
||||
u32 pnor_base;
|
||||
};
|
||||
|
||||
static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file)
|
||||
{
|
||||
return container_of(file->private_data, struct aspeed_lpc_ctrl,
|
||||
miscdev);
|
||||
}
|
||||
|
||||
static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
|
||||
unsigned long vsize = vma->vm_end - vma->vm_start;
|
||||
pgprot_t prot = vma->vm_page_prot;
|
||||
|
||||
if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl->mem_size)
|
||||
return -EINVAL;
|
||||
|
||||
/* ast2400/2500 AHB accesses are not cache coherent */
|
||||
prot = pgprot_noncached(prot);
|
||||
|
||||
if (remap_pfn_range(vma, vma->vm_start,
|
||||
(lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff,
|
||||
vsize, prot))
|
||||
return -EAGAIN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long param)
|
||||
{
|
||||
struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
|
||||
void __user *p = (void __user *)param;
|
||||
struct aspeed_lpc_ctrl_mapping map;
|
||||
u32 addr;
|
||||
u32 size;
|
||||
long rc;
|
||||
|
||||
if (copy_from_user(&map, p, sizeof(map)))
|
||||
return -EFAULT;
|
||||
|
||||
if (map.flags != 0)
|
||||
return -EINVAL;
|
||||
|
||||
switch (cmd) {
|
||||
case ASPEED_LPC_CTRL_IOCTL_GET_SIZE:
|
||||
/* The flash windows don't report their size */
|
||||
if (map.window_type != ASPEED_LPC_CTRL_WINDOW_MEMORY)
|
||||
return -EINVAL;
|
||||
|
||||
/* Support more than one window id in the future */
|
||||
if (map.window_id != 0)
|
||||
return -EINVAL;
|
||||
|
||||
map.size = lpc_ctrl->mem_size;
|
||||
|
||||
return copy_to_user(p, &map, sizeof(map)) ? -EFAULT : 0;
|
||||
case ASPEED_LPC_CTRL_IOCTL_MAP:
|
||||
|
||||
/*
|
||||
* The top half of HICR7 is the MSB of the BMC address of the
|
||||
* mapping.
|
||||
* The bottom half of HICR7 is the MSB of the HOST LPC
|
||||
* firmware space address of the mapping.
|
||||
*
|
||||
* The 1 bits in the top of half of HICR8 represent the bits
|
||||
* (in the requested address) that should be ignored and
|
||||
* replaced with those from the top half of HICR7.
|
||||
* The 1 bits in the bottom half of HICR8 represent the bits
|
||||
* (in the requested address) that should be kept and pass
|
||||
* into the BMC address space.
|
||||
*/
|
||||
|
||||
/*
|
||||
* It doesn't make sense to talk about a size or offset with
|
||||
* low 16 bits set. Both HICR7 and HICR8 talk about the top 16
|
||||
* bits of addresses and sizes.
|
||||
*/
|
||||
|
||||
if ((map.size & 0x0000ffff) || (map.offset & 0x0000ffff))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Because of the way the masks work in HICR8 offset has to
|
||||
* be a multiple of size.
|
||||
*/
|
||||
if (map.offset & (map.size - 1))
|
||||
return -EINVAL;
|
||||
|
||||
if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH) {
|
||||
addr = lpc_ctrl->pnor_base;
|
||||
size = lpc_ctrl->pnor_size;
|
||||
} else if (map.window_type == ASPEED_LPC_CTRL_WINDOW_MEMORY) {
|
||||
addr = lpc_ctrl->mem_base;
|
||||
size = lpc_ctrl->mem_size;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check overflow first! */
|
||||
if (map.offset + map.size < map.offset ||
|
||||
map.offset + map.size > size)
|
||||
return -EINVAL;
|
||||
|
||||
if (map.size == 0 || map.size > size)
|
||||
return -EINVAL;
|
||||
|
||||
addr += map.offset;
|
||||
|
||||
/*
|
||||
* addr (host lpc address) is safe regardless of values. This
|
||||
* simply changes the address the host has to request on its
|
||||
* side of the LPC bus. This cannot impact the hosts own
|
||||
* memory space by surprise as LPC specific accessors are
|
||||
* required. The only strange thing that could be done is
|
||||
* setting the lower 16 bits but the shift takes care of that.
|
||||
*/
|
||||
|
||||
rc = regmap_write(lpc_ctrl->regmap, HICR7,
|
||||
(addr | (map.addr >> 16)));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return regmap_write(lpc_ctrl->regmap, HICR8,
|
||||
(~(map.size - 1)) | ((map.size >> 16) - 1));
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct file_operations aspeed_lpc_ctrl_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.mmap = aspeed_lpc_ctrl_mmap,
|
||||
.unlocked_ioctl = aspeed_lpc_ctrl_ioctl,
|
||||
};
|
||||
|
||||
static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct aspeed_lpc_ctrl *lpc_ctrl;
|
||||
struct device_node *node;
|
||||
struct resource resm;
|
||||
struct device *dev;
|
||||
int rc;
|
||||
|
||||
dev = &pdev->dev;
|
||||
|
||||
lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL);
|
||||
if (!lpc_ctrl)
|
||||
return -ENOMEM;
|
||||
|
||||
node = of_parse_phandle(dev->of_node, "flash", 0);
|
||||
if (!node) {
|
||||
dev_err(dev, "Didn't find host pnor flash node\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rc = of_address_to_resource(node, 1, &resm);
|
||||
of_node_put(node);
|
||||
if (rc) {
|
||||
dev_err(dev, "Couldn't address to resource for flash\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
lpc_ctrl->pnor_size = resource_size(&resm);
|
||||
lpc_ctrl->pnor_base = resm.start;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, lpc_ctrl);
|
||||
|
||||
node = of_parse_phandle(dev->of_node, "memory-region", 0);
|
||||
if (!node) {
|
||||
dev_err(dev, "Didn't find reserved memory\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = of_address_to_resource(node, 0, &resm);
|
||||
of_node_put(node);
|
||||
if (rc) {
|
||||
dev_err(dev, "Couldn't address to resource for reserved memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
lpc_ctrl->mem_size = resource_size(&resm);
|
||||
lpc_ctrl->mem_base = resm.start;
|
||||
|
||||
lpc_ctrl->regmap = syscon_node_to_regmap(
|
||||
pdev->dev.parent->of_node);
|
||||
if (IS_ERR(lpc_ctrl->regmap)) {
|
||||
dev_err(dev, "Couldn't get regmap\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
|
||||
lpc_ctrl->miscdev.name = DEVICE_NAME;
|
||||
lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops;
|
||||
lpc_ctrl->miscdev.parent = dev;
|
||||
rc = misc_register(&lpc_ctrl->miscdev);
|
||||
if (rc)
|
||||
dev_err(dev, "Unable to register device\n");
|
||||
else
|
||||
dev_info(dev, "Loaded at %pr\n", &resm);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int aspeed_lpc_ctrl_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
misc_deregister(&lpc_ctrl->miscdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id aspeed_lpc_ctrl_match[] = {
|
||||
{ .compatible = "aspeed,ast2400-lpc-ctrl" },
|
||||
{ .compatible = "aspeed,ast2500-lpc-ctrl" },
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver aspeed_lpc_ctrl_driver = {
|
||||
.driver = {
|
||||
.name = DEVICE_NAME,
|
||||
.of_match_table = aspeed_lpc_ctrl_match,
|
||||
},
|
||||
.probe = aspeed_lpc_ctrl_probe,
|
||||
.remove = aspeed_lpc_ctrl_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(aspeed_lpc_ctrl_driver);
|
||||
|
||||
MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Cyril Bur <cyrilbur@gmail.com>");
|
||||
MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC mappings");
|
|
@ -227,9 +227,16 @@ static const struct i2c_device_id ds1682_id[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ds1682_id);
|
||||
|
||||
static const struct of_device_id ds1682_of_match[] = {
|
||||
{ .compatible = "dallas,ds1682", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ds1682_of_match);
|
||||
|
||||
static struct i2c_driver ds1682_driver = {
|
||||
.driver = {
|
||||
.name = "ds1682",
|
||||
.of_match_table = ds1682_of_match,
|
||||
},
|
||||
.probe = ds1682_probe,
|
||||
.remove = ds1682_remove,
|
||||
|
|
|
@ -1541,12 +1541,69 @@ static const struct i2c_device_id idt_ids[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(i2c, idt_ids);
|
||||
|
||||
static const struct of_device_id idt_of_match[] = {
|
||||
{ .compatible = "idt,89hpes8nt2", },
|
||||
{ .compatible = "idt,89hpes12nt3", },
|
||||
|
||||
{ .compatible = "idt,89hpes24nt6ag2", },
|
||||
{ .compatible = "idt,89hpes32nt8ag2", },
|
||||
{ .compatible = "idt,89hpes32nt8bg2", },
|
||||
{ .compatible = "idt,89hpes12nt12g2", },
|
||||
{ .compatible = "idt,89hpes16nt16g2", },
|
||||
{ .compatible = "idt,89hpes24nt24g2", },
|
||||
{ .compatible = "idt,89hpes32nt24ag2", },
|
||||
{ .compatible = "idt,89hpes32nt24bg2", },
|
||||
|
||||
{ .compatible = "idt,89hpes12n3", },
|
||||
{ .compatible = "idt,89hpes12n3a", },
|
||||
{ .compatible = "idt,89hpes24n3", },
|
||||
{ .compatible = "idt,89hpes24n3a", },
|
||||
|
||||
{ .compatible = "idt,89hpes32h8", },
|
||||
{ .compatible = "idt,89hpes32h8g2", },
|
||||
{ .compatible = "idt,89hpes48h12", },
|
||||
{ .compatible = "idt,89hpes48h12g2", },
|
||||
{ .compatible = "idt,89hpes48h12ag2", },
|
||||
{ .compatible = "idt,89hpes16h16", },
|
||||
{ .compatible = "idt,89hpes22h16", },
|
||||
{ .compatible = "idt,89hpes22h16g2", },
|
||||
{ .compatible = "idt,89hpes34h16", },
|
||||
{ .compatible = "idt,89hpes34h16g2", },
|
||||
{ .compatible = "idt,89hpes64h16", },
|
||||
{ .compatible = "idt,89hpes64h16g2", },
|
||||
{ .compatible = "idt,89hpes64h16ag2", },
|
||||
|
||||
{ .compatible = "idt,89hpes12t3g2", },
|
||||
{ .compatible = "idt,89hpes24t3g2", },
|
||||
|
||||
{ .compatible = "idt,89hpes16t4", },
|
||||
{ .compatible = "idt,89hpes4t4g2", },
|
||||
{ .compatible = "idt,89hpes10t4g2", },
|
||||
{ .compatible = "idt,89hpes16t4g2", },
|
||||
{ .compatible = "idt,89hpes16t4ag2", },
|
||||
{ .compatible = "idt,89hpes5t5", },
|
||||
{ .compatible = "idt,89hpes6t5", },
|
||||
{ .compatible = "idt,89hpes8t5", },
|
||||
{ .compatible = "idt,89hpes8t5a", },
|
||||
{ .compatible = "idt,89hpes24t6", },
|
||||
{ .compatible = "idt,89hpes6t6g2", },
|
||||
{ .compatible = "idt,89hpes24t6g2", },
|
||||
{ .compatible = "idt,89hpes16t7", },
|
||||
{ .compatible = "idt,89hpes32t8", },
|
||||
{ .compatible = "idt,89hpes32t8g2", },
|
||||
{ .compatible = "idt,89hpes48t12", },
|
||||
{ .compatible = "idt,89hpes48t12g2", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, idt_of_match);
|
||||
|
||||
/*
|
||||
* idt_driver - IDT 89HPESx driver structure
|
||||
*/
|
||||
static struct i2c_driver idt_driver = {
|
||||
.driver = {
|
||||
.name = IDT_NAME,
|
||||
.of_match_table = idt_of_match,
|
||||
},
|
||||
.probe = idt_probe,
|
||||
.remove = idt_remove,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue