diff mbox series

[v4,3/4] soc: aspeed: Add eSPI driver

Message ID 20210901033015.910-4-chiawei_wang@aspeedtech.com (mailing list archive)
State New, archived
Headers show
Series arm: aspeed: Add eSPI support | expand

Commit Message

ChiaWei Wang Sept. 1, 2021, 3:30 a.m. UTC
The Aspeed eSPI controller is slave device to communicate with
the master through the Enhanced Serial Peripheral Interface (eSPI).
All of the four eSPI channels, namely peripheral, virtual wire,
out-of-band, and flash are supported.

Signed-off-by: Chia-Wei Wang <chiawei_wang@aspeedtech.com>
Reported-by: kernel test robot <lkp@intel.com>
---
 drivers/soc/aspeed/Kconfig             |  11 +
 drivers/soc/aspeed/Makefile            |   5 +
 drivers/soc/aspeed/aspeed-espi-ctrl.c  | 214 ++++++++++
 drivers/soc/aspeed/aspeed-espi-ctrl.h  | 308 ++++++++++++++
 drivers/soc/aspeed/aspeed-espi-flash.c | 352 ++++++++++++++++
 drivers/soc/aspeed/aspeed-espi-flash.h |  45 ++
 drivers/soc/aspeed/aspeed-espi-ioc.h   | 195 +++++++++
 drivers/soc/aspeed/aspeed-espi-oob.c   | 558 +++++++++++++++++++++++++
 drivers/soc/aspeed/aspeed-espi-oob.h   |  70 ++++
 drivers/soc/aspeed/aspeed-espi-perif.c | 511 ++++++++++++++++++++++
 drivers/soc/aspeed/aspeed-espi-perif.h |  45 ++
 drivers/soc/aspeed/aspeed-espi-vw.c    | 137 ++++++
 drivers/soc/aspeed/aspeed-espi-vw.h    |  21 +
 13 files changed, 2472 insertions(+)
 create mode 100644 drivers/soc/aspeed/aspeed-espi-ctrl.c
 create mode 100644 drivers/soc/aspeed/aspeed-espi-ctrl.h
 create mode 100644 drivers/soc/aspeed/aspeed-espi-flash.c
 create mode 100644 drivers/soc/aspeed/aspeed-espi-flash.h
 create mode 100644 drivers/soc/aspeed/aspeed-espi-ioc.h
 create mode 100644 drivers/soc/aspeed/aspeed-espi-oob.c
 create mode 100644 drivers/soc/aspeed/aspeed-espi-oob.h
 create mode 100644 drivers/soc/aspeed/aspeed-espi-perif.c
 create mode 100644 drivers/soc/aspeed/aspeed-espi-perif.h
 create mode 100644 drivers/soc/aspeed/aspeed-espi-vw.c
 create mode 100644 drivers/soc/aspeed/aspeed-espi-vw.h

Comments

Jeremy Kerr Sept. 2, 2021, 3:29 a.m. UTC | #1
Hi Chiawei,

> The Aspeed eSPI controller is slave device to communicate with
> the master through the Enhanced Serial Peripheral Interface (eSPI).
> All of the four eSPI channels, namely peripheral, virtual wire,
> out-of-band, and flash are supported.

I'm still not confinced this raw packet user-ABI is the right approach
for this. The four channels that you're exposing would be much more
useful to use existing kernel subsystems.

Specifically:

1) The VW channel is essentially a GPIO interface, and we have a kernel
   subsystem to expose GPIOs. We might want to add additional support
   for in-kernel system event handlers, but that's a minor addition that
   can be done separately if we don't want that handled by a gpio
   consumer in userspace.

2) The out-of-band (OOB) channel provides a way to issue SMBus
   transactions, so could well provide an i2c controller interface.
   Additionally, the eSPI specs imply that this is intended to support
   MCTP - in which case, you'll *need* a kernel i2c controller device to
   be able to use the new kernel MCTP stack.

3) The flash channel exposes read/write/erase operations, which would be
   much more useful as an actual flash-type device, perhaps using the
   existing mtd interface? Or is there additional functionality
   expected for this?

4) The peripheral channel is the only one that would seem to require
   arbitrary cycle access, but we'll still need a proper uapi definition
   to support that. At the minimum, your ioctl definitions should go
   under include/uapi/ - you shouldn't need to duplicate the header into
   each userspace repo, as you've done for the test examples.

Cheers,


Jeremy
ChiaWei Wang Sept. 2, 2021, 6:44 a.m. UTC | #2
Hi Jeremy,

> From: Jeremy Kerr <jk@codeconstruct.com.au>
> Sent: Thursday, September 2, 2021 11:30 AM
> 
> Hi Chiawei,
> 
> > The Aspeed eSPI controller is slave device to communicate with the
> > master through the Enhanced Serial Peripheral Interface (eSPI).
> > All of the four eSPI channels, namely peripheral, virtual wire,
> > out-of-band, and flash are supported.
> 
> I'm still not confinced this raw packet user-ABI is the right approach for this.
> The four channels that you're exposing would be much more useful to use
> existing kernel subsystems.

The eSPI on BMC side is a slave device. Most of time it listen to the Host requests and then react.
This makes it not quite fit to interfaces that act as a master role.

> 
> Specifically:
> 
> 1) The VW channel is essentially a GPIO interface, and we have a kernel
>    subsystem to expose GPIOs. We might want to add additional support
>    for in-kernel system event handlers, but that's a minor addition that
>    can be done separately if we don't want that handled by a gpio
>    consumer in userspace.

eSPI VW channel can be used to forward a byte value to/from GPIO.
However, the targeted GPIO group and its direction are determined by the Host.
This is different from usual GPIO devices as it supports very limited operations.

> 
> 2) The out-of-band (OOB) channel provides a way to issue SMBus
>    transactions, so could well provide an i2c controller interface.
>    Additionally, the eSPI specs imply that this is intended to support
>    MCTP - in which case, you'll *need* a kernel i2c controller device to
>    be able to use the new kernel MCTP stack.

Could you share us more information about the i2c need of kernel MCTP kernel stack?
To our understanding, the MCTP is a bus agnostic protocol.
A generic raw packet interface makes it more flexible to adapt to different interfaces.

> 
> 3) The flash channel exposes read/write/erase operations, which would be
>    much more useful as an actual flash-type device, perhaps using the
>    existing mtd interface? Or is there additional functionality
>    expected for this?

The flash channel works in either the Master Attached Flash Sharing (MAFS) or Slave Attached Flash Sharing (SAFS) mode.

For MAFS, BMC issues eSPI flash R/W/E packets to the Host.
In this case, it may fit into the MTD interface.

For SAFS (mostly used), BMC needs to listen, parse and filter eSPI flash R/W/E packets from the Host.
And then send replies in the eSPI success/unsuccess completion packet format.
This behaves differently from MTD.

To support both the two scenario, the MTD interface might not be suitable.

> 
> 4) The peripheral channel is the only one that would seem to require
>    arbitrary cycle access, but we'll still need a proper uapi definition
>    to support that. At the minimum, your ioctl definitions should go
>    under include/uapi/ - you shouldn't need to duplicate the header into
>    each userspace repo, as you've done for the test examples.

In the first submission, I was told that the include/uapi patch is not going to be accepted.
Thereby, I refer to other existing driver implementation which places IOCTL codes in their own driver files.


In summary, considering the various applications that might be constructed upon eSPI transaction,
we thought that the raw packet paradigm might be the first step to start with.

Regards,
Chiawei
Jeremy Kerr Sept. 2, 2021, 7:04 a.m. UTC | #3
Hi Chiawei,

> The eSPI on BMC side is a slave device. Most of time it listen to the
> Host requests and then react.
> This makes it not quite fit to interfaces that act as a master role.

That's a good point, but I don't think it precludes using existing
interfaces.

> > 1) The VW channel is essentially a GPIO interface, and we have a
> > kernel    subsystem to expose GPIOs. We might want to add additional
> > support    for in-kernel system event handlers, but that's a minor
> > addition that    can be done separately if we don't want that
> > handled by a gpio    consumer in userspace.
> 
> eSPI VW channel can be used to forward a byte value to/from GPIO.

I'm not referring to the hardware GPIOs that can be connected here,
rather the logic values represented over the VW channel. The eSPI master
is transferring IO states over the channel, and we could represnt those
on the BMC side as "virtual" GPIOs.

If that model doesn't fit though, that's OK, but I think we need some
rationale there.

> > 
> > 2) The out-of-band (OOB) channel provides a way to issue SMBus
> >    transactions, so could well provide an i2c controller interface.
> >    Additionally, the eSPI specs imply that this is intended to
> > support
> >    MCTP - in which case, you'll *need* a kernel i2c controller
> > device to
> >    be able to use the new kernel MCTP stack.
> 
> Could you share us more information about the i2c need of kernel MCTP
> kernel stack?

The currently proposed mctp-i2c interface driver binds to a kernel i2c
device. If you expose a kernel i2c device here, we can connect that as
an MCTP interface.

With the packet interface proposed here, we can't do that, and would
need a whole new driver for this, implemented in userspace. The same
would apply to any other usage of the i2c bus (EEPROM access, etc).

> > 3) The flash channel exposes read/write/erase operations, which
> > would be
> >    much more useful as an actual flash-type device, perhaps using
> > the
> >    existing mtd interface? Or is there additional functionality
> >    expected for this?
> 
> The flash channel works in either the Master Attached Flash Sharing
> (MAFS) or Slave Attached Flash Sharing (SAFS) mode.
> 
> For MAFS, BMC issues eSPI flash R/W/E packets to the Host.
> In this case, it may fit into the MTD interface.
> 
> For SAFS (mostly used), BMC needs to listen, parse and filter eSPI
> flash R/W/E packets from the Host.
> And then send replies in the eSPI success/unsuccess completion packet
> format.
> This behaves differently from MTD.
> 
> To support both the two scenario, the MTD interface might not be
> suitable.

OK, that makes sense. In this case the BMC is acting as the virtual
flash device, right?

> > 4) The peripheral channel is the only one that would seem to require
> > arbitrary cycle access, but we'll still need a proper uapi
> > definition to support that. At the minimum, your ioctl definitions
> > should go under include/uapi/ - you shouldn't need to duplicate the
> > header into each userspace repo, as you've done for the test
> > examples.
> 
> In the first submission, I was told that the include/uapi patch is
> not going to be accepted.

This is what you were told in the v1 submission:

> >  create mode 100644 include/uapi/linux/aspeed-espi.h
>
> This userspace interface is not going to be accepted upstream.

It sees like that was a comment about the API itself, not the location
of the header (Rob, correct me if I'm wrong). Simply moving the header
out of the uapi directory, while retaining the same API, is not a
solution to this.

> In summary, considering the various applications that might be
> constructed upon eSPI transaction,
> we thought that the raw packet paradigm might be the first step to
> start with.

Keep in mind that you're creating a kernel ABI here, which has to stay
in place forever - even if it's the first step to something else, we
cannot break future compatibility.

Regards,


Jeremy
ChiaWei Wang Sept. 6, 2021, 1:19 a.m. UTC | #4
Hi Jeremy,

> From: Jeremy Kerr <jk@codeconstruct.com.au>
> Sent: Thursday, September 2, 2021 3:05 PM
> 
> Hi Chiawei,
> 
> > The eSPI on BMC side is a slave device. Most of time it listen to the
> > Host requests and then react.
> > This makes it not quite fit to interfaces that act as a master role.
> 
> That's a good point, but I don't think it precludes using existing interfaces.

Agree.

> 
> > > 1) The VW channel is essentially a GPIO interface, and we have a
> > > kernel    subsystem to expose GPIOs. We might want to add additional
> > > support    for in-kernel system event handlers, but that's a minor
> > > addition that    can be done separately if we don't want that
> > > handled by a gpio    consumer in userspace.
> >
> > eSPI VW channel can be used to forward a byte value to/from GPIO.
> > However, the targeted GPIO group and its direction are determined by the Host.
> > This is different from usual GPIO devices as it supports very limited operations.
> 
> I'm not referring to the hardware GPIOs that can be connected here, rather the
> logic values represented over the VW channel. The eSPI master is transferring
> IO states over the channel, and we could represnt those on the BMC side as
> "virtual" GPIOs.
> 
> If that model doesn't fit though, that's OK, but I think we need some rationale
> there.

After an internal discussion, we found that our eSPI VW device may not fit into existing GPIO model.

The reason is that GPIO direction changes through VW channel has no interrupts triggered.
And the direction is controlled by the Host as aforementioned.
Thus a busy polling on GPIO direction register is inevitable to reflect the state timely.
Even though, there is still a chance that BMC will miss GPIO direction changes in the time span of two polling read.

Regarding the current implementation, I should add an additional IOCTL code to read the current GPIO direction.

> 
> > >
> > > 2) The out-of-band (OOB) channel provides a way to issue SMBus
> > >    transactions, so could well provide an i2c controller interface.
> > >    Additionally, the eSPI specs imply that this is intended to
> > > support
> > >    MCTP - in which case, you'll *need* a kernel i2c controller
> > > device to
> > >    be able to use the new kernel MCTP stack.
> >
> > Could you share us more information about the i2c need of kernel MCTP kernel stack?
> > To our understanding, the MCTP is a bus agnostic protocol.
> > A generic raw packet interface makes it more flexible to adapt to different interfaces.
> 
> The currently proposed mctp-i2c interface driver binds to a kernel i2c device.
> If you expose a kernel i2c device here, we can connect that as an MCTP
> interface.
> 
> With the packet interface proposed here, we can't do that, and would need a
> whole new driver for this, implemented in userspace. The same would apply to
> any other usage of the i2c bus (EEPROM access, etc).

Thanks for sharing the information.
However, as mentioned above, MCTP is bus agonistic.
A raw packet, primitive interface should have better flexibility to manage MCTP packets over the OOB channel.

If userspace implementation is reinventing the wheel, we could consider to refer to the IPMI KCS BMC driver of OpenBMC Linux.
The driver has both the RAW and IPMI interfaces supported.

But we think this should be the next step.

> 
> > > 3) The flash channel exposes read/write/erase operations, which
> > > would be
> > >    much more useful as an actual flash-type device, perhaps using
> > > the
> > >    existing mtd interface? Or is there additional functionality
> > >    expected for this?
> >
> > The flash channel works in either the Master Attached Flash Sharing
> > (MAFS) or Slave Attached Flash Sharing (SAFS) mode.
> >
> > For MAFS, BMC issues eSPI flash R/W/E packets to the Host.
> > In this case, it may fit into the MTD interface.
> >
> > For SAFS (mostly used), BMC needs to listen, parse and filter eSPI
> > flash R/W/E packets from the Host.
> > And then send replies in the eSPI success/unsuccess completion packet
> > format.
> > This behaves differently from MTD.
> >
> > To support both the two scenario, the MTD interface might not be
> > suitable.
> 
> OK, that makes sense. In this case the BMC is acting as the virtual flash device,
> right?
> 
> > > 4) The peripheral channel is the only one that would seem to require
> > > arbitrary cycle access, but we'll still need a proper uapi
> > > definition to support that. At the minimum, your ioctl definitions
> > > should go under include/uapi/ - you shouldn't need to duplicate the
> > > header into each userspace repo, as you've done for the test
> > > examples.
> >
> > In the first submission, I was told that the include/uapi patch is not
> > going to be accepted.
> 
> This is what you were told in the v1 submission:
> 
> > >  create mode 100644 include/uapi/linux/aspeed-espi.h
> >
> > This userspace interface is not going to be accepted upstream.
> 
> It sees like that was a comment about the API itself, not the location of the
> header (Rob, correct me if I'm wrong). Simply moving the header out of the
> uapi directory, while retaining the same API, is not a solution to this.
> 
> > In summary, considering the various applications that might be
> > constructed upon eSPI transaction, we thought that the raw packet
> > paradigm might be the first step to start with.
> 
> Keep in mind that you're creating a kernel ABI here, which has to stay in place
> forever - even if it's the first step to something else, we cannot break future
> compatibility.

Understood.
Thanks for giving us these feedback and suggestions.

Regards,
Chiawei
Jeremy Kerr Sept. 6, 2021, 3:16 a.m. UTC | #5
Hi Chiawei,

> > If that model doesn't fit though, that's OK, but I think we need
> > some rationale
> > there.
> 
> After an internal discussion, we found that our eSPI VW device may
> not fit into existing GPIO model.
> 
> The reason is that GPIO direction changes through VW channel has no
> interrupts triggered.
> And the direction is controlled by the Host as aforementioned.

This piqued my curiosity, so I had a look through the 2500 datasheet. It
appears that the host has full control of both the direction *and*
hardware GPIO assignment though the platform-specific eSPI configuration
register set.

So, with VW GPIOs in hardware mode (ESPICTRL[9] = 0, the default), the
host has arbitrary control of all hardware GPIO lines (except for
the GPIOAC bank, I guess?).

There's a huge security implication there - for example, GPIOs that
assert physical presence can now be set by the host, possibly remotely -
so I'd *strongly* recommend that we always get ESPICTRL[9] to 1, to set
software-only mode.

With than in mind, if we're disabling hardware mode - what does the
direction control setting effect when we're in software mode
(ESPICTRL[9] == 1)? Does it even matter?

For example, what happens when the host goes a GET_VW cycle for a GPIO
that is marked as 'master-to-slave' mode? Is the state of the GPIO in
ESPI09C still reported?

If that's the case, then we can just ignore the direction settings from
ESPICFG800 completely, and have the BMC assign directions to standard
gpiodevs as appropriate.

Separate from this: I'm also proposing that we represent the system
event VWs as gpiodevs as well.

> A raw packet, primitive interface should have better flexibility to
> manage MCTP packets over the OOB channel.

OK, let me phrase this differently: can the OOB channel be used for
anything other than SMBus messaging? Is it useful to provide an
interface that isn't a standard SMBus/i2c device?

If you need custom uapi definitions for this driver, that might be okay,
but it's going to be more work for you (to define an interface that can
be supported long-term), rather than using standard infrastructure that
already exists.

Cheers,


Jeremy
ChiaWei Wang Sept. 8, 2021, 9:16 a.m. UTC | #6
Hi Jeremy,

> From: Jeremy Kerr <jk@codeconstruct.com.au>
> Sent: Monday, September 6, 2021 11:17 AM
> 
> Hi Chiawei,
> 
> > > If that model doesn't fit though, that's OK, but I think we need
> > > some rationale there.
> >
> > After an internal discussion, we found that our eSPI VW device may not
> > fit into existing GPIO model.
> >
> > The reason is that GPIO direction changes through VW channel has no
> > interrupts triggered.
> > And the direction is controlled by the Host as aforementioned.
> 
> This piqued my curiosity, so I had a look through the 2500 datasheet. It appears
> that the host has full control of both the direction *and* hardware GPIO
> assignment though the platform-specific eSPI configuration register set.
> 
> So, with VW GPIOs in hardware mode (ESPICTRL[9] = 0, the default), the host
> has arbitrary control of all hardware GPIO lines (except for the GPIOAC bank, I
> guess?).
> 
> There's a huge security implication there - for example, GPIOs that assert
> physical presence can now be set by the host, possibly remotely - so I'd
> *strongly* recommend that we always get ESPICTRL[9] to 1, to set
> software-only mode.

Yes, there is security concern using HW mode.
Our designer is considering to remove the HW mode support in the next generation of Aspeed SoCs.
So far we haven't encountered a scenario demanding HW mode.

> 
> With than in mind, if we're disabling hardware mode - what does the direction
> control setting effect when we're in software mode (ESPICTRL[9] == 1)? Does it
> even matter?

Yes, the direction matters even in SW mode.
When the direction is 'master-to-slave' and the GPIO value is updated by the Host through PUT_VW, a VW interrupt is trigger to notify BMC.
For the 'slave-to-master' GPIO, a alert is generated to notify the Host to issue GET_VW for the GPIO value updated by the BMC by ESPI09C.

> 
> For example, what happens when the host goes a GET_VW cycle for a GPIO
> that is marked as 'master-to-slave' mode? Is the state of the GPIO in ESPI09C
> still reported?

The Host can only issue GET_VW when BMC update one of the 'slave-to-master' GPIO pins and have eSPI status VW_AVAIL set.
And the eSPI slave will not report the value of GPIO marked as 'master-to-slave'.

> 
> If that's the case, then we can just ignore the direction settings from
> ESPICFG800 completely, and have the BMC assign directions to standard
> gpiodevs as appropriate.

If the direction setting from the Host is ignored, the presented virtual GPIO does not reflect the correct state.
Plus that the direction change from the Host has no interrupt to notify BMC.
My concern is that if the gpiodevs way works partially, then it should not be adopted to avoid confusing users.

> 
> Separate from this: I'm also proposing that we represent the system event VWs
> as gpiodevs as well.
> 
> > A raw packet, primitive interface should have better flexibility to
> > manage MCTP packets over the OOB channel.
> 
> OK, let me phrase this differently: can the OOB channel be used for anything
> other than SMBus messaging? Is it useful to provide an interface that isn't a
> standard SMBus/i2c device?

Yes, the PCH spec. also defines two additional packet format for an eSPI slave to retrieve PCH Temperature Data and RTC time.
It should be trivial to prepare a byte buffer in that format and send it through the raw packet interface.
Aspeed also have demo APPs described in the header comment.

> 
> If you need custom uapi definitions for this driver, that might be okay, but it's
> going to be more work for you (to define an interface that can be supported
> long-term), rather than using standard infrastructure that already exists.

Thus I suggested that we can refer to the IPMI KCS BMC driver, which supports the selection of different user interfaces, RAW or IPMI.
If any other interface is needed afterward, the driver can be enhanced in that fashion.
While the eSPI raw packet TX/RX handling is preserved as the primitive operations.

If IOCTL is considered to be not user friendly or magic code polluting, file-based read/write on the miscdevice node is also an option.
Jeremy Kerr Sept. 9, 2021, 1:52 a.m. UTC | #7
Hi Chiawei,

> Yes, there is security concern using HW mode.
> Our designer is considering to remove the HW mode support in the next
> generation of Aspeed SoCs.
> So far we haven't encountered a scenario demanding HW mode.

Great to hear :) can we unconditionally set ESPI000[9] in the driver
then?

> > With than in mind, if we're disabling hardware mode - what does the
> > direction control setting effect when we're in software mode
> > (ESPICTRL[9] == 1)? Does it even matter?
> 
> Yes, the direction matters even in SW mode.
> When the direction is 'master-to-slave' and the GPIO value is updated
> by the Host through PUT_VW, a VW interrupt is trigger to notify BMC.
> For the 'slave-to-master' GPIO, a alert is generated to notify the
> Host to issue GET_VW for the GPIO value updated by the BMC by
> ESPI09C.

OK, but the datasheet mentions that ESPICFG804 is only applicable when
ESPI000[9] = 0, or is that not the case?

But based on what you've said: yes, it sounds like the generic gpiodev
parts won't be useful for this.

> > Separate from this: I'm also proposing that we represent the system
> > event VWs
> > as gpiodevs as well.
> > 
> > > A raw packet, primitive interface should have better flexibility
> > > to
> > > manage MCTP packets over the OOB channel.
> > 
> > OK, let me phrase this differently: can the OOB channel be used for
> > anything other than SMBus messaging? Is it useful to provide an
> > interface that isn't a standard SMBus/i2c device?
> 
> Yes, the PCH spec. also defines two additional packet format for an
> eSPI slave to retrieve PCH Temperature Data and RTC time.
> It should be trivial to prepare a byte buffer in that format and send
> it through the raw packet interface.

OK, understood.

> > If you need custom uapi definitions for this driver, that might be
> > okay, but it's going to be more work for you (to define an interface
> > that can be supported long-term), rather than using standard
> > infrastructure that already exists.
> 
> Thus I suggested that we can refer to the IPMI KCS BMC driver, which
> supports the selection of different user interfaces, RAW or IPMI.

Yep, but the KCS "raw" register set is standardised as part of the IPMI
spec too, which helps to define a stable user API. We don't have that in
this case.

Overall though, if you want to start with the "low-level" API, then
introduce "enhanced" functionality - like an actual SMBus driver -
alongside that, then that sounds like an OK approach.

> If IOCTL is considered to be not user friendly or magic code
> polluting, file-based read/write on the miscdevice node is also an
> option.

It's not really my decision to make, but a read/write event interface
would seem to be more consistent to me. Is there an obvious event format
that would be common across all channels, perhaps? We'd probably also
need a poll too - to make use of incoming events, like master-to-slave
VW changes, perhaps?

Cheers,


Jeremy
ChiaWei Wang Sept. 10, 2021, 3:23 a.m. UTC | #8
Hi Jeremy,

> From: Jeremy Kerr <jk@codeconstruct.com.au>
> Sent: Thursday, September 9, 2021 9:53 AM
>  
> > Yes, there is security concern using HW mode.
> > Our designer is considering to remove the HW mode support in the next
> > generation of Aspeed SoCs.
> > So far we haven't encountered a scenario demanding HW mode.
> 
> Great to hear :) can we unconditionally set ESPI000[9] in the driver then?

Yes for the v5 revision. As HW mode is going to be eliminated in the future.
A brief explanation will also be added.

> 
> > > With than in mind, if we're disabling hardware mode - what does the
> > > direction control setting effect when we're in software mode
> > > (ESPICTRL[9] == 1)? Does it even matter?
> >
> > Yes, the direction matters even in SW mode.
> > When the direction is 'master-to-slave' and the GPIO value is updated
> > by the Host through PUT_VW, a VW interrupt is trigger to notify BMC.
> > For the 'slave-to-master' GPIO, a alert is generated to notify the
> > Host to issue GET_VW for the GPIO value updated by the BMC by ESPI09C.
> 
> OK, but the datasheet mentions that ESPICFG804 is only applicable when
> ESPI000[9] = 0, or is that not the case?

Yes, ESPICFG804 is applicable only on HW mode (ESPI000[9]=0).
When the HW mode is selected, the eSPI slave forwards GPIO update in PUT_VW packet sent by the Host to the physical GPIO based on the ESPICFG804 mapping.
This is purely done by HW. No interrupts will be generated to notify SW.

> 
> But based on what you've said: yes, it sounds like the generic gpiodev parts
> won't be useful for this.
> 
> > > Separate from this: I'm also proposing that we represent the system
> > > event VWs as gpiodevs as well.
> > >
> > > > A raw packet, primitive interface should have better flexibility
> > > > to manage MCTP packets over the OOB channel.
> > >
> > > OK, let me phrase this differently: can the OOB channel be used for
> > > anything other than SMBus messaging? Is it useful to provide an
> > > interface that isn't a standard SMBus/i2c device?
> >
> > Yes, the PCH spec. also defines two additional packet format for an
> > eSPI slave to retrieve PCH Temperature Data and RTC time.
> > It should be trivial to prepare a byte buffer in that format and send
> > it through the raw packet interface.
> 
> OK, understood.
> 
> > > If you need custom uapi definitions for this driver, that might be
> > > okay, but it's going to be more work for you (to define an interface
> > > that can be supported long-term), rather than using standard
> > > infrastructure that already exists.
> >
> > Thus I suggested that we can refer to the IPMI KCS BMC driver, which
> > supports the selection of different user interfaces, RAW or IPMI.
> 
> Yep, but the KCS "raw" register set is standardised as part of the IPMI spec too,
> which helps to define a stable user API. We don't have that in this case.
> 
> Overall though, if you want to start with the "low-level" API, then introduce
> "enhanced" functionality - like an actual SMBus driver - alongside that, then
> that sounds like an OK approach.
> 
> > If IOCTL is considered to be not user friendly or magic code
> > polluting, file-based read/write on the miscdevice node is also an
> > option.
> 
> It's not really my decision to make, but a read/write event interface would
> seem to be more consistent to me. Is there an obvious event format that would
> be common across all channels, perhaps? We'd probably also need a poll too -
> to make use of incoming events, like master-to-slave VW changes, perhaps?

A file-based read/write/poll interface is OK to me as well. The main concern is about the peripheral and the VW channels.
For the peripheral channel, it takes two miscdevice to export TX/RX interfaces for posted and non-posted packets, respectively.
And for the VW channel, the settings GPIO direction is RO and that of GPIO value is RW. And these two should be exported individually.
diff mbox series

Patch

diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig
index 243ca196e6ad..7617a02df5cf 100644
--- a/drivers/soc/aspeed/Kconfig
+++ b/drivers/soc/aspeed/Kconfig
@@ -42,6 +42,17 @@  config ASPEED_SOCINFO
 	help
 	  Say yes to support decoding of ASPEED BMC information.
 
+config ASPEED_ESPI
+	bool "ASPEED eSPI slave driver"
+	select REGMAP
+	select MFD_SYSCON
+	default n
+	help
+	  Enable driver support for the Aspeed eSPI engine. The eSPI engine
+	  plays as a slave device in BMC to communicate with the Host over
+	  the eSPI interface. The four eSPI channels, namely peripheral,
+	  virtual wire, out-of-band, and flash are supported.
+
 endmenu
 
 endif
diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile
index fcab7192e1a4..27f05e8b69e1 100644
--- a/drivers/soc/aspeed/Makefile
+++ b/drivers/soc/aspeed/Makefile
@@ -3,3 +3,8 @@  obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
 obj-$(CONFIG_ASPEED_LPC_SNOOP)	+= aspeed-lpc-snoop.o
 obj-$(CONFIG_ASPEED_P2A_CTRL)	+= aspeed-p2a-ctrl.o
 obj-$(CONFIG_ASPEED_SOCINFO)	+= aspeed-socinfo.o
+obj-$(CONFIG_ASPEED_ESPI)	+= aspeed-espi-ctrl.o \
+				   aspeed-espi-perif.o \
+				   aspeed-espi-vw.o \
+				   aspeed-espi-oob.o \
+				   aspeed-espi-flash.o
diff --git a/drivers/soc/aspeed/aspeed-espi-ctrl.c b/drivers/soc/aspeed/aspeed-espi-ctrl.c
new file mode 100644
index 000000000000..ce2967f851f2
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-ctrl.c
@@ -0,0 +1,214 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Aspeed Technology Inc.
+ */
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/dma-mapping.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+
+#include "aspeed-espi-ioc.h"
+#include "aspeed-espi-ctrl.h"
+#include "aspeed-espi-perif.h"
+#include "aspeed-espi-vw.h"
+#include "aspeed-espi-oob.h"
+#include "aspeed-espi-flash.h"
+
+#define DEVICE_NAME "aspeed-espi-ctrl"
+
+static irqreturn_t aspeed_espi_ctrl_isr(int irq, void *arg)
+{
+	uint32_t sts;
+	struct aspeed_espi_ctrl *espi_ctrl = (struct aspeed_espi_ctrl *)arg;
+
+	regmap_read(espi_ctrl->map, ESPI_INT_STS, &sts);
+
+	if (sts & ESPI_INT_STS_PERIF_BITS) {
+		aspeed_espi_perif_event(sts, espi_ctrl->perif);
+		regmap_write(espi_ctrl->map, ESPI_INT_STS, sts & ESPI_INT_STS_PERIF_BITS);
+	}
+
+	if (sts & ESPI_INT_STS_VW_BITS) {
+		aspeed_espi_vw_event(sts, espi_ctrl->vw);
+		regmap_write(espi_ctrl->map, ESPI_INT_STS, sts & ESPI_INT_STS_VW_BITS);
+	}
+
+	if (sts & (ESPI_INT_STS_OOB_BITS)) {
+		aspeed_espi_oob_event(sts, espi_ctrl->oob);
+		regmap_write(espi_ctrl->map, ESPI_INT_STS, sts & ESPI_INT_STS_OOB_BITS);
+	}
+
+	if (sts & ESPI_INT_STS_FLASH_BITS) {
+		aspeed_espi_flash_event(sts, espi_ctrl->flash);
+		regmap_write(espi_ctrl->map, ESPI_INT_STS, sts & ESPI_INT_STS_FLASH_BITS);
+	}
+
+	if (sts & ESPI_INT_STS_HW_RST_DEASSERT) {
+		aspeed_espi_perif_enable(espi_ctrl->perif);
+		aspeed_espi_vw_enable(espi_ctrl->vw);
+		aspeed_espi_oob_enable(espi_ctrl->oob);
+		aspeed_espi_flash_enable(espi_ctrl->flash);
+
+		regmap_write(espi_ctrl->map, ESPI_SYSEVT_INT_T0, 0x0);
+		regmap_write(espi_ctrl->map, ESPI_SYSEVT_INT_T1, 0x0);
+		regmap_write(espi_ctrl->map, ESPI_SYSEVT_INT_EN, 0xffffffff);
+
+		regmap_write(espi_ctrl->map, ESPI_SYSEVT1_INT_T0, 0x1);
+		regmap_write(espi_ctrl->map, ESPI_SYSEVT1_INT_EN, 0x1);
+
+		if (espi_ctrl->model->version == ESPI_AST2500)
+			regmap_write(espi_ctrl->map, ESPI_SYSEVT_INT_T2,
+				     ESPI_SYSEVT_INT_T2_HOST_RST_WARN |
+				     ESPI_SYSEVT_INT_T2_OOB_RST_WARN);
+
+		regmap_update_bits(espi_ctrl->map, ESPI_INT_EN,
+				   ESPI_INT_EN_HW_RST_DEASSERT,
+				   ESPI_INT_EN_HW_RST_DEASSERT);
+
+		regmap_update_bits(espi_ctrl->map, ESPI_SYSEVT,
+				   ESPI_SYSEVT_SLV_BOOT_STS | ESPI_SYSEVT_SLV_BOOT_DONE,
+				   ESPI_SYSEVT_SLV_BOOT_STS | ESPI_SYSEVT_SLV_BOOT_DONE);
+
+		regmap_write(espi_ctrl->map, ESPI_INT_STS, ESPI_INT_STS_HW_RST_DEASSERT);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int aspeed_espi_ctrl_probe(struct platform_device *pdev)
+{
+	int rc = 0;
+	struct aspeed_espi_ctrl *espi_ctrl;
+	struct device *dev = &pdev->dev;
+
+	espi_ctrl = devm_kzalloc(dev, sizeof(*espi_ctrl), GFP_KERNEL);
+	if (!espi_ctrl)
+		return -ENOMEM;
+
+	espi_ctrl->model = of_device_get_match_data(dev);
+
+	espi_ctrl->map = syscon_node_to_regmap(dev->parent->of_node);
+	if (IS_ERR(espi_ctrl->map)) {
+		dev_err(dev, "cannot get remap\n");
+		return -ENODEV;
+	}
+
+	espi_ctrl->irq = platform_get_irq(pdev, 0);
+	if (espi_ctrl->irq < 0)
+		return espi_ctrl->irq;
+
+	espi_ctrl->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(espi_ctrl->clk)) {
+		dev_err(dev, "cannot get clock\n");
+		return -ENODEV;
+	}
+
+	rc = clk_prepare_enable(espi_ctrl->clk);
+	if (rc) {
+		dev_err(dev, "cannot enable clock\n");
+		return rc;
+	}
+
+	espi_ctrl->perif = aspeed_espi_perif_alloc(dev, espi_ctrl);
+	if (IS_ERR(espi_ctrl->perif)) {
+		dev_err(dev, "failed to allocate peripheral channel\n");
+		return PTR_ERR(espi_ctrl->perif);
+	}
+
+	espi_ctrl->vw = aspeed_espi_vw_alloc(dev, espi_ctrl);
+	if (IS_ERR(espi_ctrl->vw)) {
+		dev_err(dev, "failed to allocate virtual wire channel\n");
+		return PTR_ERR(espi_ctrl->vw);
+	}
+
+	espi_ctrl->oob = aspeed_espi_oob_alloc(dev, espi_ctrl);
+	if (IS_ERR(espi_ctrl->oob)) {
+		dev_err(dev, "failed to allocate out-of-band channel\n");
+		return PTR_ERR(espi_ctrl->oob);
+	}
+
+	espi_ctrl->flash = aspeed_espi_flash_alloc(dev, espi_ctrl);
+	if (rc) {
+		dev_err(dev, "failed to allocate flash channel\n");
+		return PTR_ERR(espi_ctrl->flash);
+	}
+
+	regmap_write(espi_ctrl->map, ESPI_SYSEVT_INT_T0, 0x0);
+	regmap_write(espi_ctrl->map, ESPI_SYSEVT_INT_T1, 0x0);
+	regmap_write(espi_ctrl->map, ESPI_SYSEVT_INT_EN, 0xffffffff);
+
+	regmap_write(espi_ctrl->map, ESPI_SYSEVT1_INT_T0, 0x1);
+	regmap_write(espi_ctrl->map, ESPI_SYSEVT1_INT_EN, 0x1);
+
+	rc = devm_request_irq(dev, espi_ctrl->irq,
+			      aspeed_espi_ctrl_isr,
+			      0, DEVICE_NAME, espi_ctrl);
+	if (rc) {
+		dev_err(dev, "failed to request IRQ\n");
+		return rc;
+	}
+
+	regmap_update_bits(espi_ctrl->map, ESPI_INT_EN,
+			   ESPI_INT_EN_HW_RST_DEASSERT,
+			   ESPI_INT_EN_HW_RST_DEASSERT);
+
+	dev_set_drvdata(dev, espi_ctrl);
+
+	dev_info(dev, "module loaded\n");
+
+	return 0;
+}
+
+static int aspeed_espi_ctrl_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct aspeed_espi_ctrl *espi_ctrl = dev_get_drvdata(dev);
+
+	aspeed_espi_perif_free(dev, espi_ctrl->perif);
+	aspeed_espi_vw_free(dev, espi_ctrl->vw);
+	aspeed_espi_oob_free(dev, espi_ctrl->oob);
+	aspeed_espi_flash_free(dev, espi_ctrl->flash);
+
+	return 0;
+}
+
+static const struct aspeed_espi_model ast2500_model = {
+	.version = ESPI_AST2500,
+};
+
+static const struct aspeed_espi_model ast2600_model = {
+	.version = ESPI_AST2600,
+};
+
+static const struct of_device_id aspeed_espi_ctrl_of_matches[] = {
+	{ .compatible = "aspeed,ast2500-espi-ctrl",
+	  .data = &ast2500_model },
+	{ .compatible = "aspeed,ast2600-espi-ctrl",
+	  .data = &ast2600_model },
+	{ },
+};
+
+static struct platform_driver aspeed_espi_ctrl_driver = {
+	.driver = {
+		.name = DEVICE_NAME,
+		.of_match_table = aspeed_espi_ctrl_of_matches,
+	},
+	.probe = aspeed_espi_ctrl_probe,
+	.remove = aspeed_espi_ctrl_remove,
+};
+
+module_platform_driver(aspeed_espi_ctrl_driver);
+
+MODULE_AUTHOR("Chia-Wei Wang <chiawei_wang@aspeedtech.com>");
+MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
+MODULE_DESCRIPTION("Control of Aspeed eSPI Slave Device");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/aspeed/aspeed-espi-ctrl.h b/drivers/soc/aspeed/aspeed-espi-ctrl.h
new file mode 100644
index 000000000000..247cb6ce46fb
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-ctrl.h
@@ -0,0 +1,308 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 Aspeed Technology Inc.
+ */
+#ifndef _ASPEED_ESPI_CTRL_H_
+#define _ASPEED_ESPI_CTRL_H_
+
+#include <linux/bits.h>
+
+enum aspeed_espi_version {
+	ESPI_AST2500,
+	ESPI_AST2600,
+};
+
+struct aspeed_espi_model {
+	uint32_t version;
+};
+
+struct aspeed_espi_ctrl {
+	struct device *dev;
+
+	struct regmap *map;
+	struct clk *clk;
+
+	int irq;
+
+	struct aspeed_espi_perif *perif;
+	struct aspeed_espi_vw *vw;
+	struct aspeed_espi_oob *oob;
+	struct aspeed_espi_flash *flash;
+
+	const struct aspeed_espi_model *model;
+};
+
+/* eSPI register offset */
+#define ESPI_CTRL		0x000
+#define   ESPI_CTRL_OOB_RX_SW_RST		BIT(28)
+#define   ESPI_CTRL_FLASH_TX_DMA_EN		BIT(23)
+#define   ESPI_CTRL_FLASH_RX_DMA_EN		BIT(22)
+#define   ESPI_CTRL_OOB_TX_DMA_EN		BIT(21)
+#define   ESPI_CTRL_OOB_RX_DMA_EN		BIT(20)
+#define   ESPI_CTRL_PERIF_NP_TX_DMA_EN		BIT(19)
+#define   ESPI_CTRL_PERIF_PC_TX_DMA_EN		BIT(17)
+#define   ESPI_CTRL_PERIF_PC_RX_DMA_EN		BIT(16)
+#define   ESPI_CTRL_FLASH_SW_MODE_MASK		GENMASK(11, 10)
+#define   ESPI_CTRL_FLASH_SW_MODE_SHIFT		10
+#define   ESPI_CTRL_PERIF_PC_RX_DMA_EN		BIT(16)
+#define   ESPI_CTRL_FLASH_SW_RDY		BIT(7)
+#define   ESPI_CTRL_OOB_SW_RDY			BIT(4)
+#define   ESPI_CTRL_VW_SW_RDY			BIT(3)
+#define   ESPI_CTRL_PERIF_SW_RDY		BIT(1)
+#define ESPI_STS		0x004
+#define ESPI_INT_STS		0x008
+#define   ESPI_INT_STS_HW_RST_DEASSERT		BIT(31)
+#define   ESPI_INT_STS_OOB_RX_TMOUT		BIT(23)
+#define   ESPI_INT_STS_VW_SYSEVT1		BIT(22)
+#define   ESPI_INT_STS_FLASH_TX_ERR		BIT(21)
+#define   ESPI_INT_STS_OOB_TX_ERR		BIT(20)
+#define   ESPI_INT_STS_FLASH_TX_ABT		BIT(19)
+#define   ESPI_INT_STS_OOB_TX_ABT		BIT(18)
+#define   ESPI_INT_STS_PERIF_NP_TX_ABT		BIT(17)
+#define   ESPI_INT_STS_PERIF_PC_TX_ABT		BIT(16)
+#define   ESPI_INT_STS_FLASH_RX_ABT		BIT(15)
+#define   ESPI_INT_STS_OOB_RX_ABT		BIT(14)
+#define   ESPI_INT_STS_PERIF_NP_RX_ABT		BIT(13)
+#define   ESPI_INT_STS_PERIF_PC_RX_ABT		BIT(12)
+#define   ESPI_INT_STS_PERIF_NP_TX_ERR		BIT(11)
+#define   ESPI_INT_STS_PERIF_PC_TX_ERR		BIT(10)
+#define   ESPI_INT_STS_VW_GPIOEVT		BIT(9)
+#define   ESPI_INT_STS_VW_SYSEVT		BIT(8)
+#define   ESPI_INT_STS_FLASH_TX_CMPLT		BIT(7)
+#define   ESPI_INT_STS_FLASH_RX_CMPLT		BIT(6)
+#define   ESPI_INT_STS_OOB_TX_CMPLT		BIT(5)
+#define   ESPI_INT_STS_OOB_RX_CMPLT		BIT(4)
+#define   ESPI_INT_STS_PERIF_NP_TX_CMPLT	BIT(3)
+#define   ESPI_INT_STS_PERIF_PC_TX_CMPLT	BIT(1)
+#define   ESPI_INT_STS_PERIF_PC_RX_CMPLT	BIT(0)
+#define ESPI_INT_EN		0x00c
+#define   ESPI_INT_EN_HW_RST_DEASSERT		BIT(31)
+#define   ESPI_INT_EN_OOB_RX_TMOUT		BIT(23)
+#define   ESPI_INT_EN_VW_SYSEVT1		BIT(22)
+#define   ESPI_INT_EN_FLASH_TX_ERR		BIT(21)
+#define   ESPI_INT_EN_OOB_TX_ERR		BIT(20)
+#define   ESPI_INT_EN_FLASH_TX_ABT		BIT(19)
+#define   ESPI_INT_EN_OOB_TX_ABT		BIT(18)
+#define   ESPI_INT_EN_PERIF_NP_TX_ABT		BIT(17)
+#define   ESPI_INT_EN_PERIF_PC_TX_ABT		BIT(16)
+#define   ESPI_INT_EN_FLASH_RX_ABT		BIT(15)
+#define   ESPI_INT_EN_OOB_RX_ABT		BIT(14)
+#define   ESPI_INT_EN_PERIF_NP_RX_ABT		BIT(13)
+#define   ESPI_INT_EN_PERIF_PC_RX_ABT		BIT(12)
+#define   ESPI_INT_EN_PERIF_NP_TX_ERR		BIT(11)
+#define   ESPI_INT_EN_PERIF_PC_TX_ERR		BIT(10)
+#define   ESPI_INT_EN_VW_GPIOEVT		BIT(9)
+#define   ESPI_INT_EN_VW_SYSEVT			BIT(8)
+#define   ESPI_INT_EN_FLASH_TX_CMPLT		BIT(7)
+#define   ESPI_INT_EN_FLASH_RX_CMPLT		BIT(6)
+#define   ESPI_INT_EN_OOB_TX_CMPLT		BIT(5)
+#define   ESPI_INT_EN_OOB_RX_CMPLT		BIT(4)
+#define   ESPI_INT_EN_PERIF_NP_TX_CMPLT		BIT(3)
+#define   ESPI_INT_EN_PERIF_PC_TX_CMPLT		BIT(1)
+#define   ESPI_INT_EN_PERIF_PC_RX_CMPLT		BIT(0)
+#define ESPI_PERIF_PC_RX_DMA	0x010
+#define ESPI_PERIF_PC_RX_CTRL	0x014
+#define   ESPI_PERIF_PC_RX_CTRL_PEND_SERV	BIT(31)
+#define   ESPI_PERIF_PC_RX_CTRL_LEN_MASK	GENMASK(23, 12)
+#define   ESPI_PERIF_PC_RX_CTRL_LEN_SHIFT	12
+#define   ESPI_PERIF_PC_RX_CTRL_TAG_MASK	GENMASK(11, 8)
+#define   ESPI_PERIF_PC_RX_CTRL_TAG_SHIFT	8
+#define   ESPI_PERIF_PC_RX_CTRL_CYC_MASK	GENMASK(7, 0)
+#define   ESPI_PERIF_PC_RX_CTRL_CYC_SHIFT	0
+#define ESPI_PERIF_PC_RX_PORT	0x018
+#define ESPI_PERIF_PC_TX_DMA	0x020
+#define ESPI_PERIF_PC_TX_CTRL	0x024
+#define	  ESPI_PERIF_PC_TX_CTRL_TRIGGER		BIT(31)
+#define	  ESPI_PERIF_PC_TX_CTRL_LEN_MASK	GENMASK(23, 12)
+#define	  ESPI_PERIF_PC_TX_CTRL_LEN_SHIFT	12
+#define	  ESPI_PERIF_PC_TX_CTRL_TAG_MASK	GENMASK(11, 8)
+#define	  ESPI_PERIF_PC_TX_CTRL_TAG_SHIFT	8
+#define	  ESPI_PERIF_PC_TX_CTRL_CYC_MASK	GENMASK(7, 0)
+#define	  ESPI_PERIF_PC_TX_CTRL_CYC_SHIFT	0
+#define ESPI_PERIF_PC_TX_PORT	0x028
+#define ESPI_PERIF_NP_TX_DMA	0x030
+#define ESPI_PERIF_NP_TX_CTRL	0x034
+#define   ESPI_PERIF_NP_TX_CTRL_TRIGGER		BIT(31)
+#define	  ESPI_PERIF_NP_TX_CTRL_LEN_MASK	GENMASK(23, 12)
+#define	  ESPI_PERIF_NP_TX_CTRL_LEN_SHIFT	12
+#define	  ESPI_PERIF_NP_TX_CTRL_TAG_MASK	GENMASK(11, 8)
+#define	  ESPI_PERIF_NP_TX_CTRL_TAG_SHIFT	8
+#define	  ESPI_PERIF_NP_TX_CTRL_CYC_MASK	GENMASK(7, 0)
+#define	  ESPI_PERIF_NP_TX_CTRL_CYC_SHIFT	0
+#define ESPI_PERIF_NP_TX_PORT	0x038
+#define ESPI_OOB_RX_DMA		0x040
+#define ESPI_OOB_RX_CTRL	0x044
+#define	  ESPI_OOB_RX_CTRL_PEND_SERV		BIT(31)
+#define	  ESPI_OOB_RX_CTRL_LEN_MASK		GENMASK(23, 12)
+#define	  ESPI_OOB_RX_CTRL_LEN_SHIFT		12
+#define	  ESPI_OOB_RX_CTRL_TAG_MASK		GENMASK(11, 8)
+#define	  ESPI_OOB_RX_CTRL_TAG_SHIFT		8
+#define	  ESPI_OOB_RX_CTRL_CYC_MASK		GENMASK(7, 0)
+#define	  ESPI_OOB_RX_CTRL_CYC_SHIFT		0
+#define ESPI_OOB_RX_PORT	0x048
+#define ESPI_OOB_TX_DMA		0x050
+#define ESPI_OOB_TX_CTRL	0x054
+#define	  ESPI_OOB_TX_CTRL_TRIGGER		BIT(31)
+#define	  ESPI_OOB_TX_CTRL_LEN_MASK		GENMASK(23, 12)
+#define	  ESPI_OOB_TX_CTRL_LEN_SHIFT		12
+#define	  ESPI_OOB_TX_CTRL_TAG_MASK		GENMASK(11, 8)
+#define	  ESPI_OOB_TX_CTRL_TAG_SHIFT		8
+#define	  ESPI_OOB_TX_CTRL_CYC_MASK		GENMASK(7, 0)
+#define	  ESPI_OOB_TX_CTRL_CYC_SHIFT		0
+#define ESPI_OOB_TX_PORT	0x058
+#define ESPI_FLASH_RX_DMA	0x060
+#define ESPI_FLASH_RX_CTRL	0x064
+#define	  ESPI_FLASH_RX_CTRL_PEND_SERV		BIT(31)
+#define	  ESPI_FLASH_RX_CTRL_LEN_MASK		GENMASK(23, 12)
+#define	  ESPI_FLASH_RX_CTRL_LEN_SHIFT		12
+#define	  ESPI_FLASH_RX_CTRL_TAG_MASK		GENMASK(11, 8)
+#define	  ESPI_FLASH_RX_CTRL_TAG_SHIFT		8
+#define	  ESPI_FLASH_RX_CTRL_CYC_MASK		GENMASK(7, 0)
+#define	  ESPI_FLASH_RX_CTRL_CYC_SHIFT		0
+#define ESPI_FLASH_RX_PORT	0x068
+#define ESPI_FLASH_TX_DMA	0x070
+#define ESPI_FLASH_TX_CTRL	0x074
+#define	  ESPI_FLASH_TX_CTRL_TRIGGER		BIT(31)
+#define	  ESPI_FLASH_TX_CTRL_LEN_MASK		GENMASK(23, 12)
+#define	  ESPI_FLASH_TX_CTRL_LEN_SHIFT		12
+#define	  ESPI_FLASH_TX_CTRL_TAG_MASK		GENMASK(11, 8)
+#define	  ESPI_FLASH_TX_CTRL_TAG_SHIFT		8
+#define	  ESPI_FLASH_TX_CTRL_CYC_MASK		GENMASK(7, 0)
+#define	  ESPI_FLASH_TX_CTRL_CYC_SHIFT		0
+#define ESPI_FLASH_TX_PORT	0x078
+#define ESPI_CTRL2		0x080
+#define   ESPI_CTRL2_MEMCYC_RD_DIS		BIT(6)
+#define   ESPI_CTRL2_MEMCYC_WR_DIS		BIT(4)
+#define ESPI_PERIF_PC_RX_SADDR	0x084
+#define ESPI_PERIF_PC_RX_TADDR	0x088
+#define ESPI_PERIF_PC_RX_MASK	0x08c
+#define   ESPI_PERIF_PC_RX_MASK_CFG_WP		BIT(0)
+#define ESPI_SYSEVT_INT_EN	0x094
+#define ESPI_SYSEVT		0x098
+#define   ESPI_SYSEVT_HOST_RST_ACK		BIT(27)
+#define   ESPI_SYSEVT_RST_CPU_INIT		BIT(26)
+#define   ESPI_SYSEVT_SLV_BOOT_STS		BIT(23)
+#define   ESPI_SYSEVT_NON_FATAL_ERR		BIT(22)
+#define   ESPI_SYSEVT_FATAL_ERR			BIT(21)
+#define   ESPI_SYSEVT_SLV_BOOT_DONE		BIT(20)
+#define   ESPI_SYSEVT_OOB_RST_ACK		BIT(16)
+#define   ESPI_SYSEVT_NMI_OUT			BIT(10)
+#define   ESPI_SYSEVT_SMI_OUT			BIT(9)
+#define   ESPI_SYSEVT_HOST_RST_WARN		BIT(8)
+#define   ESPI_SYSEVT_OOB_RST_WARN		BIT(6)
+#define   ESPI_SYSEVT_PLTRSTN			BIT(5)
+#define   ESPI_SYSEVT_SUSPEND			BIT(4)
+#define   ESPI_SYSEVT_S5_SLEEP			BIT(2)
+#define   ESPI_SYSEVT_S4_SLEEP			BIT(1)
+#define   ESPI_SYSEVT_S3_SLEEP			BIT(0)
+#define ESPI_VW_GPIO_VAL	0x09c
+#define ESPI_GEN_CAP_N_CONF	0x0a0
+#define ESPI_CH0_CAP_N_CONF	0x0a4
+#define ESPI_CH1_CAP_N_CONF	0x0a8
+#define ESPI_CH2_CAP_N_CONF	0x0ac
+#define ESPI_CH3_CAP_N_CONF	0x0b0
+#define ESPI_CH3_CAP_N_CONF2	0x0b4
+#define ESPI_SYSEVT1_INT_EN	0x100
+#define ESPI_SYSEVT1		0x104
+#define   ESPI_SYSEVT1_SUSPEND_ACK		BIT(20)
+#define   ESPI_SYSEVT1_SUSPEND_WARN		BIT(0)
+#define ESPI_SYSEVT_INT_T0	0x110
+#define ESPI_SYSEVT_INT_T1	0x114
+#define ESPI_SYSEVT_INT_T2	0x118
+#define   ESPI_SYSEVT_INT_T2_HOST_RST_WARN	ESPI_SYSEVT_HOST_RST_WARN
+#define   ESPI_SYSEVT_INT_T2_OOB_RST_WARN	ESPI_SYSEVT_OOB_RST_WARN
+#define ESPI_SYSEVT_INT_STS	0x11c
+#define   ESPI_SYSEVT_INT_STS_NMI_OUT		ESPI_SYSEVT_NMI_OUT
+#define   ESPI_SYSEVT_INT_STS_SMI_OUT		ESPI_SYSEVT_SMI_OUT
+#define   ESPI_SYSEVT_INT_STS_HOST_RST_WARN	ESPI_SYSEVT_HOST_RST_WARN
+#define   ESPI_SYSEVT_INT_STS_OOB_RST_WARN	ESPI_SYSEVT_OOB_RST_WARN
+#define   ESPI_SYSEVT_INT_STS_PLTRSTN		ESPI_SYSEVT_PLTRSTN
+#define   ESPI_SYSEVT_INT_STS_SUSPEND		ESPI_SYSEVT_SUSPEND
+#define   ESPI_SYSEVT_INT_STS_S5_SLEEP		ESPI_SYSEVT_INT_S5_SLEEP
+#define   ESPI_SYSEVT_INT_STS_S4_SLEEP		ESPI_SYSEVT_INT_S4_SLEEP
+#define   ESPI_SYSEVT_INT_STS_S3_SLEEP		ESPI_SYSEVT_INT_S3_SLEEP
+#define ESPI_SYSEVT1_INT_T0	0x120
+#define ESPI_SYSEVT1_INT_T1	0x124
+#define ESPI_SYSEVT1_INT_T2	0x128
+#define ESPI_SYSEVT1_INT_STS	0x12c
+#define   ESPI_SYSEVT1_INT_STS_SUSPEND_WARN	ESPI_SYSEVT1_SUSPEND_WARN
+#define ESPI_OOB_RX_DMA_RB_SIZE	0x130
+#define ESPI_OOB_RX_DMA_RD_PTR	0x134
+#define	  ESPI_OOB_RX_DMA_RD_PTR_UPDATE		BIT(31)
+#define ESPI_OOB_RX_DMA_WS_PTR	0x138
+#define   ESPI_OOB_RX_DMA_WS_PTR_RECV_EN	BIT(31)
+#define   ESPI_OOB_RX_DMA_WS_PTR_SP_MASK	GENMASK(27, 16)
+#define   ESPI_OOB_RX_DMA_WS_PTR_SP_SHIFT	16
+#define   ESPI_OOB_RX_DMA_WS_PTR_WP_MASK	GENMASK(11, 0)
+#define   ESPI_OOB_RX_DMA_WS_PTR_WP_SHIFT	0
+#define ESPI_OOB_TX_DMA_RB_SIZE	0x140
+#define ESPI_OOB_TX_DMA_RD_PTR	0x144
+#define	  ESPI_OOB_TX_DMA_RD_PTR_UPDATE		BIT(31)
+#define ESPI_OOB_TX_DMA_WR_PTR	0x148
+#define	  ESPI_OOB_TX_DMA_WR_PTR_SEND_EN	BIT(31)
+
+/* collect ESPI_INT_STS bits of eSPI channels for convenience */
+#define ESPI_INT_STS_PERIF_BITS			\
+	(ESPI_INT_STS_PERIF_NP_TX_ABT |		\
+	 ESPI_INT_STS_PERIF_PC_TX_ABT |		\
+	 ESPI_INT_STS_PERIF_NP_RX_ABT |		\
+	 ESPI_INT_STS_PERIF_PC_RX_ABT |		\
+	 ESPI_INT_STS_PERIF_NP_TX_ERR |		\
+	 ESPI_INT_STS_PERIF_PC_TX_ERR |		\
+	 ESPI_INT_STS_PERIF_NP_TX_CMPLT |	\
+	 ESPI_INT_STS_PERIF_PC_TX_CMPLT |	\
+	 ESPI_INT_STS_PERIF_PC_RX_CMPLT)
+
+#define ESPI_INT_STS_VW_BITS		\
+	(ESPI_INT_STS_VW_SYSEVT1 |	\
+	 ESPI_INT_STS_VW_GPIOEVT |	\
+	 ESPI_INT_STS_VW_SYSEVT)
+
+#define ESPI_INT_STS_OOB_BITS		\
+	(ESPI_INT_STS_OOB_RX_TMOUT |	\
+	 ESPI_INT_STS_OOB_TX_ERR |	\
+	 ESPI_INT_STS_OOB_TX_ABT |	\
+	 ESPI_INT_STS_OOB_RX_ABT |	\
+	 ESPI_INT_STS_OOB_TX_CMPLT |	\
+	 ESPI_INT_STS_OOB_RX_CMPLT)
+
+#define ESPI_INT_STS_FLASH_BITS		\
+	(ESPI_INT_STS_FLASH_TX_ERR |	\
+	 ESPI_INT_STS_FLASH_TX_ABT |	\
+	 ESPI_INT_STS_FLASH_RX_ABT |	\
+	 ESPI_INT_STS_FLASH_TX_CMPLT |	\
+	 ESPI_INT_STS_FLASH_RX_CMPLT)
+
+/* collect ESPI_INT_EN bits of eSPI channels for convenience */
+#define ESPI_INT_EN_PERIF_BITS			\
+	(ESPI_INT_EN_PERIF_NP_TX_ABT |		\
+	 ESPI_INT_EN_PERIF_PC_TX_ABT |		\
+	 ESPI_INT_EN_PERIF_NP_RX_ABT |		\
+	 ESPI_INT_EN_PERIF_PC_RX_ABT |		\
+	 ESPI_INT_EN_PERIF_NP_TX_ERR |		\
+	 ESPI_INT_EN_PERIF_PC_TX_ERR |		\
+	 ESPI_INT_EN_PERIF_NP_TX_CMPLT |	\
+	 ESPI_INT_EN_PERIF_PC_TX_CMPLT |	\
+	 ESPI_INT_EN_PERIF_PC_RX_CMPLT)
+
+#define ESPI_INT_EN_VW_BITS		\
+	(ESPI_INT_EN_VW_SYSEVT1 |	\
+	 ESPI_INT_EN_VW_GPIOEVT |	\
+	 ESPI_INT_EN_VW_SYSEVT)
+
+#define ESPI_INT_EN_OOB_BITS		\
+	(ESPI_INT_EN_OOB_RX_TMOUT |	\
+	 ESPI_INT_EN_OOB_TX_ERR |	\
+	 ESPI_INT_EN_OOB_TX_ABT |	\
+	 ESPI_INT_EN_OOB_RX_ABT |	\
+	 ESPI_INT_EN_OOB_TX_CMPLT |	\
+	 ESPI_INT_EN_OOB_RX_CMPLT)
+
+#define ESPI_INT_EN_FLASH_BITS		\
+	(ESPI_INT_EN_FLASH_TX_ERR |	\
+	 ESPI_INT_EN_FLASH_TX_ABT |	\
+	 ESPI_INT_EN_FLASH_RX_ABT |	\
+	 ESPI_INT_EN_FLASH_TX_CMPLT |	\
+	 ESPI_INT_EN_FLASH_RX_CMPLT)
+
+#endif
diff --git a/drivers/soc/aspeed/aspeed-espi-flash.c b/drivers/soc/aspeed/aspeed-espi-flash.c
new file mode 100644
index 000000000000..d1ede22f22e3
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-flash.c
@@ -0,0 +1,352 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 ASPEED Technology Inc.
+ */
+#include <linux/fs.h>
+#include <linux/of_device.h>
+#include <linux/miscdevice.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/miscdevice.h>
+#include <linux/dma-mapping.h>
+
+#include "aspeed-espi-ioc.h"
+#include "aspeed-espi-ctrl.h"
+#include "aspeed-espi-flash.h"
+
+#define FLASH_MDEV_NAME	"aspeed-espi-flash"
+
+static long aspeed_espi_flash_get_rx(struct file *fp,
+				     struct aspeed_espi_ioc *ioc,
+				     struct aspeed_espi_flash *espi_flash)
+{
+	int i, rc = 0;
+	unsigned long flags;
+	uint32_t reg;
+	uint32_t cyc, tag, len;
+	uint8_t *pkt;
+	uint32_t pkt_len;
+	struct espi_comm_hdr *hdr;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_flash->ctrl;
+
+	if (fp->f_flags & O_NONBLOCK) {
+		if (mutex_trylock(&espi_flash->get_rx_mtx))
+			return -EBUSY;
+
+		if (!espi_flash->rx_ready) {
+			rc = -ENODATA;
+			goto unlock_mtx_n_out;
+		}
+	} else {
+		mutex_lock(&espi_flash->get_rx_mtx);
+
+		if (!espi_flash->rx_ready) {
+			rc = wait_event_interruptible(espi_flash->wq,
+						      espi_flash->rx_ready);
+			if (rc == -ERESTARTSYS) {
+				rc = -EINTR;
+				goto unlock_mtx_n_out;
+			}
+		}
+	}
+
+	/* common header (i.e. cycle type, tag, and length) is taken by HW */
+	regmap_read(espi_ctrl->map, ESPI_FLASH_RX_CTRL, &reg);
+	cyc = (reg & ESPI_FLASH_RX_CTRL_CYC_MASK) >> ESPI_FLASH_RX_CTRL_CYC_SHIFT;
+	tag = (reg & ESPI_FLASH_RX_CTRL_TAG_MASK) >> ESPI_FLASH_RX_CTRL_TAG_SHIFT;
+	len = (reg & ESPI_FLASH_RX_CTRL_LEN_MASK) >> ESPI_FLASH_RX_CTRL_LEN_SHIFT;
+
+	/*
+	 * calculate the length of the rest part of the
+	 * eSPI packet to be read from HW and copied to
+	 * user space.
+	 */
+	switch (cyc) {
+	case ESPI_FLASH_READ:
+	case ESPI_FLASH_WRITE:
+	case ESPI_FLASH_ERASE:
+		pkt_len = ((len) ? len : ESPI_PLD_LEN_MAX) +
+			  sizeof(struct espi_flash_rwe);
+		break;
+	case ESPI_FLASH_SUC_CMPLT_D_MIDDLE:
+	case ESPI_FLASH_SUC_CMPLT_D_FIRST:
+	case ESPI_FLASH_SUC_CMPLT_D_LAST:
+	case ESPI_FLASH_SUC_CMPLT_D_ONLY:
+		pkt_len = ((len) ? len : ESPI_PLD_LEN_MAX) +
+			  sizeof(struct espi_flash_cmplt);
+		break;
+	case ESPI_FLASH_SUC_CMPLT:
+	case ESPI_FLASH_UNSUC_CMPLT:
+		pkt_len = len + sizeof(struct espi_flash_cmplt);
+		break;
+	default:
+		rc = -EFAULT;
+		goto unlock_mtx_n_out;
+	}
+
+	if (ioc->pkt_len < pkt_len) {
+		rc = -EINVAL;
+		goto unlock_mtx_n_out;
+	}
+
+	pkt = vmalloc(pkt_len);
+	if (!pkt) {
+		rc = -ENOMEM;
+		goto unlock_mtx_n_out;
+	}
+
+	hdr = (struct espi_comm_hdr *)pkt;
+	hdr->cyc = cyc;
+	hdr->tag = tag;
+	hdr->len_h = len >> 8;
+	hdr->len_l = len & 0xff;
+
+	if (espi_flash->dma_mode) {
+		memcpy(hdr + 1, espi_flash->dma.rx_virt,
+		       pkt_len - sizeof(*hdr));
+	} else {
+		for (i = sizeof(*hdr); i < pkt_len; ++i) {
+			regmap_read(espi_ctrl->map,
+				    ESPI_FLASH_RX_PORT, &reg);
+			pkt[i] = reg & 0xff;
+		}
+	}
+
+	if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	spin_lock_irqsave(&espi_flash->lock, flags);
+
+	regmap_write_bits(espi_ctrl->map, ESPI_FLASH_RX_CTRL,
+			  ESPI_FLASH_RX_CTRL_PEND_SERV,
+			  ESPI_FLASH_RX_CTRL_PEND_SERV);
+
+	espi_flash->rx_ready = 0;
+
+	spin_unlock_irqrestore(&espi_flash->lock, flags);
+
+free_n_out:
+	vfree(pkt);
+
+unlock_mtx_n_out:
+	mutex_unlock(&espi_flash->get_rx_mtx);
+
+	return rc;
+}
+
+static long aspeed_espi_flash_put_tx(struct file *fp,
+				     struct aspeed_espi_ioc *ioc,
+				     struct aspeed_espi_flash *espi_flash)
+{
+	int i, rc = 0;
+	uint32_t reg;
+	uint32_t cyc, tag, len;
+	uint8_t *pkt;
+	struct espi_comm_hdr *hdr;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_flash->ctrl;
+
+	if (!mutex_trylock(&espi_flash->put_tx_mtx))
+		return -EAGAIN;
+
+	regmap_read(espi_ctrl->map, ESPI_FLASH_TX_CTRL, &reg);
+	if (reg & ESPI_FLASH_TX_CTRL_TRIGGER) {
+		rc = -EBUSY;
+		goto unlock_mtx_n_out;
+	}
+
+	pkt = vmalloc(ioc->pkt_len);
+	if (!pkt) {
+		rc = -ENOMEM;
+		goto unlock_mtx_n_out;
+	}
+
+	hdr = (struct espi_comm_hdr *)pkt;
+
+	if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	/*
+	 * common header (i.e. cycle type, tag, and length)
+	 * part is written to HW registers
+	 */
+	if (espi_flash->dma_mode) {
+		memcpy(espi_flash->dma.tx_virt, hdr + 1,
+		       ioc->pkt_len - sizeof(*hdr));
+		dma_wmb();
+	} else {
+		for (i = sizeof(*hdr); i < ioc->pkt_len; ++i)
+			regmap_write(espi_ctrl->map,
+				     ESPI_FLASH_TX_PORT, pkt[i]);
+	}
+
+	cyc = hdr->cyc;
+	tag = hdr->tag;
+	len = (hdr->len_h << 8) | (hdr->len_l & 0xff);
+
+	reg = ((cyc << ESPI_FLASH_TX_CTRL_CYC_SHIFT) & ESPI_FLASH_TX_CTRL_CYC_MASK)
+		| ((tag << ESPI_FLASH_TX_CTRL_TAG_SHIFT) & ESPI_FLASH_TX_CTRL_TAG_MASK)
+		| ((len << ESPI_FLASH_TX_CTRL_LEN_SHIFT) & ESPI_FLASH_TX_CTRL_LEN_MASK)
+		| ESPI_FLASH_TX_CTRL_TRIGGER;
+
+	regmap_write(espi_ctrl->map, ESPI_FLASH_TX_CTRL, reg);
+
+free_n_out:
+	vfree(pkt);
+
+unlock_mtx_n_out:
+	mutex_unlock(&espi_flash->put_tx_mtx);
+
+	return rc;
+}
+
+static long aspeed_espi_flash_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	struct aspeed_espi_ioc ioc;
+	struct aspeed_espi_flash *espi_flash = container_of(
+			fp->private_data,
+			struct aspeed_espi_flash,
+			mdev);
+
+	if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc)))
+		return -EFAULT;
+
+	if (ioc.pkt_len > ESPI_PKT_LEN_MAX)
+		return -EINVAL;
+
+	switch (cmd) {
+	case ASPEED_ESPI_FLASH_GET_RX:
+		return aspeed_espi_flash_get_rx(fp, &ioc, espi_flash);
+	case ASPEED_ESPI_FLASH_PUT_TX:
+		return aspeed_espi_flash_put_tx(fp, &ioc, espi_flash);
+	};
+
+	return -EINVAL;
+}
+
+void aspeed_espi_flash_event(uint32_t sts, struct aspeed_espi_flash *espi_flash)
+{
+	unsigned long flags;
+
+	if (sts & ESPI_INT_STS_FLASH_RX_CMPLT) {
+		spin_lock_irqsave(&espi_flash->lock, flags);
+		espi_flash->rx_ready = 1;
+		spin_unlock_irqrestore(&espi_flash->lock, flags);
+		wake_up_interruptible(&espi_flash->wq);
+	}
+}
+
+void aspeed_espi_flash_enable(struct aspeed_espi_flash *espi_flash)
+{
+	struct aspeed_espi_flash_dma *dma = &espi_flash->dma;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_flash->ctrl;
+
+	regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+			   ESPI_CTRL_FLASH_SW_MODE_MASK,
+			   (espi_flash->safs_mode << ESPI_CTRL_FLASH_SW_MODE_SHIFT));
+
+	if (espi_flash->dma_mode) {
+		regmap_write(espi_ctrl->map, ESPI_FLASH_TX_DMA, dma->tx_addr);
+		regmap_write(espi_ctrl->map, ESPI_FLASH_RX_DMA, dma->rx_addr);
+		regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+				   ESPI_CTRL_FLASH_TX_DMA_EN | ESPI_CTRL_FLASH_RX_DMA_EN,
+				   ESPI_CTRL_FLASH_TX_DMA_EN | ESPI_CTRL_FLASH_RX_DMA_EN);
+	}
+
+	regmap_write(espi_ctrl->map, ESPI_INT_STS,
+		     ESPI_INT_STS_FLASH_BITS);
+
+	regmap_update_bits(espi_ctrl->map, ESPI_INT_EN,
+			   ESPI_INT_EN_FLASH_BITS,
+			   ESPI_INT_EN_FLASH_BITS);
+
+	regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+			   ESPI_CTRL_FLASH_SW_RDY,
+			   ESPI_CTRL_FLASH_SW_RDY);
+}
+
+static const struct file_operations aspeed_espi_flash_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = aspeed_espi_flash_ioctl,
+};
+
+void *aspeed_espi_flash_alloc(struct device *dev, struct aspeed_espi_ctrl *espi_ctrl)
+{
+	int rc = 0;
+	struct aspeed_espi_flash *espi_flash;
+	struct aspeed_espi_flash_dma *dma;
+
+	espi_flash = devm_kzalloc(dev, sizeof(*espi_flash), GFP_KERNEL);
+	if (!espi_flash)
+		return ERR_PTR(-ENOMEM);
+
+	espi_flash->ctrl = espi_ctrl;
+
+	init_waitqueue_head(&espi_flash->wq);
+
+	spin_lock_init(&espi_flash->lock);
+
+	mutex_init(&espi_flash->put_tx_mtx);
+	mutex_init(&espi_flash->get_rx_mtx);
+
+	if (of_property_read_bool(dev->of_node, "flash,dma-mode"))
+		espi_flash->dma_mode = 1;
+
+	of_property_read_u32(dev->of_node, "flash,safs-mode", &espi_flash->safs_mode);
+	if (espi_flash->safs_mode >= SAFS_MODES) {
+		dev_err(dev, "invalid SAFS mode\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (espi_flash->dma_mode) {
+		dma = &espi_flash->dma;
+
+		dma->tx_virt = dma_alloc_coherent(dev, PAGE_SIZE,
+						  &dma->tx_addr, GFP_KERNEL);
+		if (!dma->tx_virt) {
+			dev_err(dev, "cannot allocate DMA TX buffer\n");
+			return ERR_PTR(-ENOMEM);
+		}
+
+		dma->rx_virt = dma_alloc_coherent(dev, PAGE_SIZE,
+						  &dma->rx_addr, GFP_KERNEL);
+		if (!dma->rx_virt) {
+			dev_err(dev, "cannot allocate DMA RX buffer\n");
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	espi_flash->mdev.parent = dev;
+	espi_flash->mdev.minor = MISC_DYNAMIC_MINOR;
+	espi_flash->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s", FLASH_MDEV_NAME);
+	espi_flash->mdev.fops = &aspeed_espi_flash_fops;
+	rc = misc_register(&espi_flash->mdev);
+	if (rc) {
+		dev_err(dev, "cannot register device\n");
+		return ERR_PTR(rc);
+	}
+
+	aspeed_espi_flash_enable(espi_flash);
+
+	return espi_flash;
+}
+
+void aspeed_espi_flash_free(struct device *dev, struct aspeed_espi_flash *espi_flash)
+{
+	struct aspeed_espi_flash_dma *dma = &espi_flash->dma;
+
+	if (espi_flash->dma_mode) {
+		dma_free_coherent(dev, PAGE_SIZE, dma->tx_virt, dma->tx_addr);
+		dma_free_coherent(dev, PAGE_SIZE, dma->rx_virt, dma->rx_addr);
+	}
+
+	mutex_destroy(&espi_flash->put_tx_mtx);
+	mutex_destroy(&espi_flash->get_rx_mtx);
+
+	misc_deregister(&espi_flash->mdev);
+}
diff --git a/drivers/soc/aspeed/aspeed-espi-flash.h b/drivers/soc/aspeed/aspeed-espi-flash.h
new file mode 100644
index 000000000000..bd5177329e50
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-flash.h
@@ -0,0 +1,45 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 ASPEED Technology Inc.
+ */
+#ifndef _ASPEED_ESPI_FLASH_H_
+#define _ASPEED_ESPI_FLASH_H_
+
+enum aspeed_espi_flash_safs_mode {
+	SAFS_MODE_MIX,
+	SAFS_MODE_SW,
+	SAFS_MODE_HW,
+	SAFS_MODES,
+};
+
+struct aspeed_espi_flash_dma {
+	void *tx_virt;
+	dma_addr_t tx_addr;
+	void *rx_virt;
+	dma_addr_t rx_addr;
+};
+
+struct aspeed_espi_flash {
+	uint32_t safs_mode;
+
+	uint32_t dma_mode;
+	struct aspeed_espi_flash_dma dma;
+
+	uint32_t rx_ready;
+	wait_queue_head_t wq;
+
+	struct mutex get_rx_mtx;
+	struct mutex put_tx_mtx;
+
+	spinlock_t lock;
+
+	struct miscdevice mdev;
+	struct aspeed_espi_ctrl *ctrl;
+};
+
+void aspeed_espi_flash_event(uint32_t sts, struct aspeed_espi_flash *espi_flash);
+void aspeed_espi_flash_enable(struct aspeed_espi_flash *espi_flash);
+void *aspeed_espi_flash_alloc(struct device *dev, struct aspeed_espi_ctrl *espi_ctrl);
+void aspeed_espi_flash_free(struct device *dev, struct aspeed_espi_flash *espi_flash);
+
+#endif
diff --git a/drivers/soc/aspeed/aspeed-espi-ioc.h b/drivers/soc/aspeed/aspeed-espi-ioc.h
new file mode 100644
index 000000000000..a78f1069841f
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-ioc.h
@@ -0,0 +1,195 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 Aspeed Technology Inc.
+ */
+#ifndef _ASPEED_ESPI_IOC_H
+#define _ASPEED_ESPI_IOC_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/*
+ * eSPI cycle type encoding
+ *
+ * Section 5.1 Cycle Types and Packet Format,
+ * Intel eSPI Interface Base Specification, Rev 1.0, Jan. 2016.
+ */
+#define ESPI_PERIF_MEMRD32		0x00
+#define ESPI_PERIF_MEMRD64		0x02
+#define ESPI_PERIF_MEMWR32		0x01
+#define ESPI_PERIF_MEMWR64		0x03
+#define ESPI_PERIF_MSG			0x10
+#define ESPI_PERIF_MSG_D		0x11
+#define ESPI_PERIF_SUC_CMPLT		0x06
+#define ESPI_PERIF_SUC_CMPLT_D_MIDDLE	0x09
+#define ESPI_PERIF_SUC_CMPLT_D_FIRST	0x0b
+#define ESPI_PERIF_SUC_CMPLT_D_LAST	0x0d
+#define ESPI_PERIF_SUC_CMPLT_D_ONLY	0x0f
+#define ESPI_PERIF_UNSUC_CMPLT		0x0c
+#define ESPI_OOB_MSG			0x21
+#define ESPI_FLASH_READ			0x00
+#define ESPI_FLASH_WRITE		0x01
+#define ESPI_FLASH_ERASE		0x02
+#define ESPI_FLASH_SUC_CMPLT		0x06
+#define ESPI_FLASH_SUC_CMPLT_D_MIDDLE	0x09
+#define ESPI_FLASH_SUC_CMPLT_D_FIRST	0x0b
+#define ESPI_FLASH_SUC_CMPLT_D_LAST	0x0d
+#define ESPI_FLASH_SUC_CMPLT_D_ONLY	0x0f
+#define ESPI_FLASH_UNSUC_CMPLT		0x0c
+
+/*
+ * eSPI packet format structure
+ *
+ * Section 5.1 Cycle Types and Packet Format,
+ * Intel eSPI Interface Base Specification, Rev 1.0, Jan. 2016.
+ */
+struct espi_comm_hdr {
+	uint8_t cyc;
+	uint8_t len_h : 4;
+	uint8_t tag : 4;
+	uint8_t len_l;
+};
+
+struct espi_perif_mem32 {
+	uint8_t cyc;
+	uint8_t len_h : 4;
+	uint8_t tag : 4;
+	uint8_t len_l;
+	uint32_t addr_be;
+	uint8_t data[];
+} __packed;
+
+struct espi_perif_mem64 {
+	uint8_t cyc;
+	uint8_t len_h : 4;
+	uint8_t tag : 4;
+	uint8_t len_l;
+	uint32_t addr_be;
+	uint8_t data[];
+} __packed;
+
+struct espi_perif_msg {
+	uint8_t cyc;
+	uint8_t len_h : 4;
+	uint8_t tag : 4;
+	uint8_t len_l;
+	uint8_t msg_code;
+	uint8_t msg_byte[4];
+	uint8_t data[];
+} __packed;
+
+struct espi_perif_cmplt {
+	uint8_t cyc;
+	uint8_t len_h : 4;
+	uint8_t tag : 4;
+	uint8_t len_l;
+	uint8_t data[];
+} __packed;
+
+struct espi_oob_msg {
+	uint8_t cyc;
+	uint8_t len_h : 4;
+	uint8_t tag : 4;
+	uint8_t len_l;
+	uint8_t data[];
+};
+
+struct espi_flash_rwe {
+	uint8_t cyc;
+	uint8_t len_h : 4;
+	uint8_t tag : 4;
+	uint8_t len_l;
+	uint32_t addr_be;
+	uint8_t data[];
+} __packed;
+
+struct espi_flash_cmplt {
+	uint8_t cyc;
+	uint8_t len_h : 4;
+	uint8_t tag : 4;
+	uint8_t len_l;
+	uint8_t data[];
+} __packed;
+
+struct aspeed_espi_ioc {
+	uint32_t pkt_len;
+	uint8_t *pkt;
+};
+
+/*
+ * we choose the longest header and the max payload size
+ * based on the Intel specification to define the maximum
+ * eSPI packet length
+ */
+#define ESPI_PLD_LEN_MIN	(1UL << 6)
+#define ESPI_PLD_LEN_MAX	(1UL << 12)
+#define ESPI_PKT_LEN_MAX	(sizeof(struct espi_perif_msg) + ESPI_PLD_LEN_MAX)
+
+#define __ASPEED_ESPI_IOCTL_MAGIC	0xb8
+
+/*
+ * The IOCTL-based interface works in the eSPI packet in/out paradigm.
+ *
+ * Only the virtual wire IOCTL is a special case which does not send
+ * or receive an eSPI packet. However, to keep a more consisten use from
+ * userspace, we make all of the four channel drivers serve through the
+ * IOCTL interface.
+ *
+ * For the eSPI packet format, refer to
+ *   Section 5.1 Cycle Types and Packet Format,
+ *   Intel eSPI Interface Base Specification, Rev 1.0, Jan. 2016.
+ *
+ * For the example user apps using these IOCTL, refer to
+ *   https://github.com/AspeedTech-BMC/aspeed_app/tree/master/espi_test
+ */
+
+/*
+ * Peripheral Channel (CH0)
+ *  - ASPEED_ESPI_PERIF_PC_GET_RX
+ *      Receive an eSPI Posted/Completion packet
+ *  - ASPEED_ESPI_PERIF_PC_PUT_TX
+ *      Transmit an eSPI Posted/Completion packet
+ *  - ASPEED_ESPI_PERIF_NP_PUT_TX
+ *      Transmit an eSPI Non-Posted packet
+ */
+#define ASPEED_ESPI_PERIF_PC_GET_RX	_IOR(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x00, struct aspeed_espi_ioc)
+#define ASPEED_ESPI_PERIF_PC_PUT_TX	_IOW(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x01, struct aspeed_espi_ioc)
+#define ASPEED_ESPI_PERIF_NP_PUT_TX	_IOW(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x02, struct aspeed_espi_ioc)
+/*
+ * Virtual Wire Channel (CH1)
+ *  - ASPEED_ESPI_VW_GET_GPIO_VAL
+ *      Read the input value of GPIO over the VW channel
+ *  - ASPEED_ESPI_VW_PUT_GPIO_VAL
+ *      Write the output value of GPIO over the VW channel
+ */
+#define ASPEED_ESPI_VW_GET_GPIO_VAL	_IOR(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x10, uint8_t)
+#define ASPEED_ESPI_VW_PUT_GPIO_VAL	_IOW(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x11, uint8_t)
+/*
+ * Out-of-band Channel (CH2)
+ *  - ASPEED_ESPI_OOB_GET_RX
+ *      Receive an eSPI OOB packet
+ *  - ASPEED_ESPI_OOB_PUT_TX
+ *      Transmit an eSPI OOB packet
+ */
+#define ASPEED_ESPI_OOB_GET_RX		_IOR(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x20, struct aspeed_espi_ioc)
+#define ASPEED_ESPI_OOB_PUT_TX		_IOW(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x21, struct aspeed_espi_ioc)
+/*
+ * Flash Channel (CH3)
+ *  - ASPEED_ESPI_FLASH_GET_RX
+ *      Receive an eSPI flash packet
+ *  - ASPEED_ESPI_FLASH_PUT_TX
+ *      Transmit an eSPI flash packet
+ */
+#define ASPEED_ESPI_FLASH_GET_RX	_IOR(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x30, struct aspeed_espi_ioc)
+#define ASPEED_ESPI_FLASH_PUT_TX	_IOW(__ASPEED_ESPI_IOCTL_MAGIC, \
+					     0x31, struct aspeed_espi_ioc)
+
+#endif
diff --git a/drivers/soc/aspeed/aspeed-espi-oob.c b/drivers/soc/aspeed/aspeed-espi-oob.c
new file mode 100644
index 000000000000..2e0cc427b6c1
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-oob.c
@@ -0,0 +1,558 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Aspeed Technology Inc.
+ */
+#include <linux/fs.h>
+#include <linux/of_device.h>
+#include <linux/miscdevice.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/miscdevice.h>
+#include <linux/dma-mapping.h>
+
+#include "aspeed-espi-ioc.h"
+#include "aspeed-espi-ctrl.h"
+#include "aspeed-espi-oob.h"
+
+#define OOB_MDEV_NAME	"aspeed-espi-oob"
+
+/* DMA descriptor is supported since AST2600 */
+#define OOB_DMA_DESC_MAX_NUM	1024
+#define OOB_DMA_TX_DESC_CUST	0x04
+
+/* descriptor-based RX DMA handling */
+static long aspeed_espi_oob_dma_desc_get_rx(struct file *fp,
+					    struct aspeed_espi_ioc *ioc,
+					    struct aspeed_espi_oob *espi_oob)
+{
+	int rc = 0;
+	unsigned long flags;
+	uint32_t reg;
+	uint32_t wptr, sptr;
+	uint8_t *pkt;
+	uint32_t pkt_len;
+	struct espi_comm_hdr *hdr;
+	struct oob_rx_dma_desc *d;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_oob->ctrl;
+
+	regmap_read(espi_ctrl->map, ESPI_OOB_RX_DMA_WS_PTR, &reg);
+	wptr = (reg & ESPI_OOB_RX_DMA_WS_PTR_WP_MASK) >> ESPI_OOB_RX_DMA_WS_PTR_WP_SHIFT;
+	sptr = (reg & ESPI_OOB_RX_DMA_WS_PTR_SP_MASK) >> ESPI_OOB_RX_DMA_WS_PTR_SP_SHIFT;
+
+	d = &espi_oob->dma.rx_desc[sptr];
+
+	if (!d->dirty)
+		return -EFAULT;
+
+	pkt_len = ((d->len) ? d->len : 0x1000) + sizeof(struct espi_comm_hdr);
+
+	if (ioc->pkt_len < pkt_len)
+		return -EINVAL;
+
+	pkt = vmalloc(pkt_len);
+	if (!pkt)
+		return -ENOMEM;
+
+	hdr = (struct espi_comm_hdr *)pkt;
+	hdr->cyc = d->cyc;
+	hdr->tag = d->tag;
+	hdr->len_h = d->len >> 8;
+	hdr->len_l = d->len & 0xff;
+	memcpy(hdr + 1, espi_oob->dma.rx_virt + (PAGE_SIZE * sptr), pkt_len - sizeof(*hdr));
+
+	if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	spin_lock_irqsave(&espi_oob->lock, flags);
+
+	/* make current descriptor available again */
+	d->dirty = 0;
+
+	sptr = (sptr + 1) % espi_oob->dma.rx_desc_num;
+	wptr = (wptr + 1) % espi_oob->dma.rx_desc_num;
+
+	reg = ((wptr << ESPI_OOB_RX_DMA_WS_PTR_WP_SHIFT) & ESPI_OOB_RX_DMA_WS_PTR_WP_MASK)
+		| ((sptr << ESPI_OOB_RX_DMA_WS_PTR_SP_SHIFT) & ESPI_OOB_RX_DMA_WS_PTR_SP_MASK)
+		| ESPI_OOB_RX_DMA_WS_PTR_RECV_EN;
+	regmap_write(espi_ctrl->map, ESPI_OOB_RX_DMA_WS_PTR, reg);
+
+	/* set ready flag base on the next RX descriptor */
+	espi_oob->rx_ready = espi_oob->dma.rx_desc[sptr].dirty;
+
+	spin_unlock_irqrestore(&espi_oob->lock, flags);
+
+free_n_out:
+	vfree(pkt);
+
+	return rc;
+}
+
+static long aspeed_espi_oob_get_rx(struct file *fp,
+				   struct aspeed_espi_ioc *ioc,
+				   struct aspeed_espi_oob *espi_oob)
+{
+	int i, rc = 0;
+	unsigned long flags;
+	uint32_t reg;
+	uint32_t cyc, tag, len;
+	uint8_t *pkt;
+	uint32_t pkt_len;
+	struct espi_comm_hdr *hdr;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_oob->ctrl;
+
+	if (fp->f_flags & O_NONBLOCK) {
+		if (mutex_trylock(&espi_oob->get_rx_mtx))
+			return -EBUSY;
+
+		if (!espi_oob->rx_ready) {
+			rc = -ENODATA;
+			goto unlock_mtx_n_out;
+		}
+	} else {
+		mutex_lock(&espi_oob->get_rx_mtx);
+
+		if (!espi_oob->rx_ready) {
+			rc = wait_event_interruptible(espi_oob->wq,
+						      espi_oob->rx_ready);
+			if (rc == -ERESTARTSYS) {
+				rc = -EINTR;
+				goto unlock_mtx_n_out;
+			}
+		}
+	}
+
+	if (espi_oob->dma_mode && espi_ctrl->model->version != ESPI_AST2500) {
+		rc = aspeed_espi_oob_dma_desc_get_rx(fp, ioc, espi_oob);
+		goto unlock_mtx_n_out;
+	}
+
+	/* common header (i.e. cycle type, tag, and length) is taken by HW */
+	regmap_read(espi_ctrl->map, ESPI_OOB_RX_CTRL, &reg);
+	cyc = (reg & ESPI_OOB_RX_CTRL_CYC_MASK) >> ESPI_OOB_RX_CTRL_CYC_SHIFT;
+	tag = (reg & ESPI_OOB_RX_CTRL_TAG_MASK) >> ESPI_OOB_RX_CTRL_TAG_SHIFT;
+	len = (reg & ESPI_OOB_RX_CTRL_LEN_MASK) >> ESPI_OOB_RX_CTRL_LEN_SHIFT;
+
+	/*
+	 * calculate the length of the rest part of the
+	 * eSPI packet to be read from HW and copied to
+	 * user space.
+	 */
+	pkt_len = ((len) ? len : ESPI_PLD_LEN_MAX) + sizeof(struct espi_comm_hdr);
+
+	if (ioc->pkt_len < pkt_len) {
+		rc = -EINVAL;
+		goto unlock_mtx_n_out;
+	}
+
+	pkt = vmalloc(pkt_len);
+	if (!pkt) {
+		rc = -ENOMEM;
+		goto unlock_mtx_n_out;
+	}
+
+	hdr = (struct espi_comm_hdr *)pkt;
+	hdr->cyc = cyc;
+	hdr->tag = tag;
+	hdr->len_h = len >> 8;
+	hdr->len_l = len & 0xff;
+
+	if (espi_oob->dma_mode) {
+		memcpy(hdr + 1, espi_oob->dma.rx_virt,
+		       pkt_len - sizeof(*hdr));
+	} else {
+		for (i = sizeof(*hdr); i < pkt_len; ++i) {
+			regmap_read(espi_ctrl->map,
+				    ESPI_OOB_RX_PORT, &reg);
+			pkt[i] = reg & 0xff;
+		}
+	}
+
+	if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	spin_lock_irqsave(&espi_oob->lock, flags);
+
+	regmap_write_bits(espi_ctrl->map, ESPI_OOB_RX_CTRL,
+			  ESPI_OOB_RX_CTRL_PEND_SERV,
+			  ESPI_OOB_RX_CTRL_PEND_SERV);
+
+	espi_oob->rx_ready = 0;
+
+	spin_unlock_irqrestore(&espi_oob->lock, flags);
+
+free_n_out:
+	vfree(pkt);
+
+unlock_mtx_n_out:
+	mutex_unlock(&espi_oob->get_rx_mtx);
+
+	return rc;
+}
+
+/* descriptor-based TX DMA handling */
+static long aspeed_espi_oob_dma_desc_put_tx(struct file *fp,
+					    struct aspeed_espi_ioc *ioc,
+					    struct aspeed_espi_oob *espi_oob)
+{
+	int rc = 0;
+	uint32_t rptr, wptr;
+	uint8_t *pkt;
+	struct espi_comm_hdr *hdr;
+	struct oob_tx_dma_desc *d;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_oob->ctrl;
+
+	pkt = vzalloc(ioc->pkt_len);
+	if (!pkt)
+		return -ENOMEM;
+
+	hdr = (struct espi_comm_hdr *)pkt;
+
+	if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	/* kick HW to reflect the up-to-date read/write pointer */
+	regmap_write(espi_ctrl->map, ESPI_OOB_TX_DMA_RD_PTR,
+		     ESPI_OOB_TX_DMA_RD_PTR_UPDATE);
+
+	regmap_read(espi_ctrl->map, ESPI_OOB_TX_DMA_RD_PTR, &rptr);
+	regmap_read(espi_ctrl->map, ESPI_OOB_TX_DMA_WR_PTR, &wptr);
+
+	if (((wptr + 1) % espi_oob->dma.tx_desc_num) == rptr) {
+		rc = -EBUSY;
+		goto free_n_out;
+	}
+
+	d = &espi_oob->dma.tx_desc[wptr];
+	d->cyc = hdr->cyc;
+	d->tag = hdr->tag;
+	d->len = (hdr->len_h << 8) | (hdr->len_l & 0xff);
+	d->msg_type = OOB_DMA_TX_DESC_CUST;
+
+	memcpy(espi_oob->dma.tx_virt + (PAGE_SIZE * wptr), hdr + 1,
+	       ioc->pkt_len - sizeof(*hdr));
+
+	dma_wmb();
+
+	wptr = (wptr + 1) % espi_oob->dma.tx_desc_num;
+	wptr |= ESPI_OOB_TX_DMA_WR_PTR_SEND_EN;
+	regmap_write(espi_ctrl->map, ESPI_OOB_TX_DMA_WR_PTR, wptr);
+
+free_n_out:
+	vfree(pkt);
+
+	return rc;
+}
+
+static long aspeed_espi_oob_put_tx(struct file *fp,
+				   struct aspeed_espi_ioc *ioc,
+				   struct aspeed_espi_oob *espi_oob)
+{
+	int i, rc = 0;
+	uint32_t reg;
+	uint32_t cyc, tag, len;
+	uint8_t *pkt;
+	struct espi_comm_hdr *hdr;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_oob->ctrl;
+
+	if (!mutex_trylock(&espi_oob->put_tx_mtx))
+		return -EBUSY;
+
+	if (espi_oob->dma_mode && espi_ctrl->model->version != ESPI_AST2500) {
+		rc = aspeed_espi_oob_dma_desc_put_tx(fp, ioc, espi_oob);
+		goto unlock_mtx_n_out;
+	}
+
+	regmap_read(espi_ctrl->map, ESPI_OOB_TX_CTRL, &reg);
+	if (reg & ESPI_OOB_TX_CTRL_TRIGGER) {
+		rc = -EBUSY;
+		goto unlock_mtx_n_out;
+	}
+
+	if (ioc->pkt_len > ESPI_PKT_LEN_MAX) {
+		rc = -EINVAL;
+		goto unlock_mtx_n_out;
+	}
+
+	pkt = vmalloc(ioc->pkt_len);
+	if (!pkt) {
+		rc = -ENOMEM;
+		goto unlock_mtx_n_out;
+	}
+
+	hdr = (struct espi_comm_hdr *)pkt;
+
+	if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	/*
+	 * common header (i.e. cycle type, tag, and length)
+	 * part is written to HW registers
+	 */
+	if (espi_oob->dma_mode) {
+		memcpy(espi_oob->dma.tx_virt, hdr + 1,
+		       ioc->pkt_len - sizeof(*hdr));
+		dma_wmb();
+	} else {
+		for (i = sizeof(*hdr); i < ioc->pkt_len; ++i)
+			regmap_write(espi_ctrl->map,
+				     ESPI_OOB_TX_PORT, pkt[i]);
+	}
+
+	cyc = hdr->cyc;
+	tag = hdr->tag;
+	len = (hdr->len_h << 8) | (hdr->len_l & 0xff);
+
+	reg = ((cyc << ESPI_OOB_TX_CTRL_CYC_SHIFT) & ESPI_OOB_TX_CTRL_CYC_MASK)
+		| ((tag << ESPI_OOB_TX_CTRL_TAG_SHIFT) & ESPI_OOB_TX_CTRL_TAG_MASK)
+		| ((len << ESPI_OOB_TX_CTRL_LEN_SHIFT) & ESPI_OOB_TX_CTRL_LEN_MASK)
+		| ESPI_OOB_TX_CTRL_TRIGGER;
+
+	regmap_write(espi_ctrl->map, ESPI_OOB_TX_CTRL, reg);
+
+free_n_out:
+	vfree(pkt);
+
+unlock_mtx_n_out:
+	mutex_unlock(&espi_oob->put_tx_mtx);
+
+	return rc;
+}
+
+static long aspeed_espi_oob_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	struct aspeed_espi_ioc ioc;
+	struct aspeed_espi_oob *espi_oob = container_of(
+			fp->private_data,
+			struct aspeed_espi_oob,
+			mdev);
+
+	if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc)))
+		return -EFAULT;
+
+	if (ioc.pkt_len > ESPI_PKT_LEN_MAX)
+		return -EINVAL;
+
+	switch (cmd) {
+	case ASPEED_ESPI_OOB_GET_RX:
+		return aspeed_espi_oob_get_rx(fp, &ioc, espi_oob);
+	case ASPEED_ESPI_OOB_PUT_TX:
+		return aspeed_espi_oob_put_tx(fp, &ioc, espi_oob);
+	};
+
+	return -EINVAL;
+}
+
+void aspeed_espi_oob_event(uint32_t sts, struct aspeed_espi_oob *espi_oob)
+{
+	unsigned long flags;
+
+	if (sts & ESPI_INT_STS_OOB_RX_CMPLT) {
+		spin_lock_irqsave(&espi_oob->lock, flags);
+		espi_oob->rx_ready = 1;
+		spin_unlock_irqrestore(&espi_oob->lock, flags);
+
+		wake_up_interruptible(&espi_oob->wq);
+	}
+}
+
+void aspeed_espi_oob_enable(struct aspeed_espi_oob *espi_oob)
+{
+	int i;
+	struct aspeed_espi_oob_dma *dma = &espi_oob->dma;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_oob->ctrl;
+
+	regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+			   ESPI_CTRL_OOB_SW_RDY | ESPI_CTRL_OOB_RX_SW_RST, 0);
+
+	if (espi_oob->dma_mode)
+		regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+				   ESPI_CTRL_OOB_TX_DMA_EN | ESPI_CTRL_OOB_RX_DMA_EN, 0);
+	else
+		regmap_write(espi_ctrl->map, ESPI_OOB_RX_CTRL, ESPI_OOB_RX_CTRL_PEND_SERV);
+
+	/*
+	 * cleanup OOB RX FIFO to get rid of the data
+	 * of OOB early init side-effect
+	 */
+	regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+			   ESPI_CTRL_OOB_RX_SW_RST, ESPI_CTRL_OOB_RX_SW_RST);
+
+	regmap_write(espi_ctrl->map, ESPI_OOB_RX_CTRL,
+		     ESPI_OOB_RX_CTRL_PEND_SERV);
+
+	if (espi_oob->dma_mode) {
+		regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+				   ESPI_CTRL_OOB_TX_DMA_EN | ESPI_CTRL_OOB_RX_DMA_EN,
+				   ESPI_CTRL_OOB_TX_DMA_EN | ESPI_CTRL_OOB_RX_DMA_EN);
+
+		if (espi_ctrl->model->version == ESPI_AST2500) {
+			regmap_write(espi_ctrl->map, ESPI_OOB_TX_DMA, dma->tx_addr);
+			regmap_write(espi_ctrl->map, ESPI_OOB_RX_DMA, dma->rx_addr);
+		} else {
+			for (i = 0; i < dma->tx_desc_num; ++i)
+				dma->tx_desc[i].data_addr = dma->tx_addr + (i * PAGE_SIZE);
+
+			for (i = 0; i < dma->rx_desc_num; ++i) {
+				dma->rx_desc[i].data_addr = dma->rx_addr + (i * PAGE_SIZE);
+				dma->rx_desc[i].dirty = 0;
+			}
+
+			regmap_write(espi_ctrl->map, ESPI_OOB_TX_DMA, dma->tx_desc_addr);
+			regmap_write(espi_ctrl->map, ESPI_OOB_TX_DMA_RB_SIZE, dma->tx_desc_num);
+
+			regmap_write(espi_ctrl->map, ESPI_OOB_RX_DMA, dma->rx_desc_addr);
+			regmap_write(espi_ctrl->map, ESPI_OOB_RX_DMA_RB_SIZE, dma->rx_desc_num);
+			regmap_update_bits(espi_ctrl->map, ESPI_OOB_RX_DMA_WS_PTR,
+					   ESPI_OOB_RX_DMA_WS_PTR_RECV_EN,
+					   ESPI_OOB_RX_DMA_WS_PTR_RECV_EN);
+		}
+	}
+
+	regmap_write(espi_ctrl->map, ESPI_INT_STS,
+		     ESPI_INT_STS_OOB_BITS);
+
+	regmap_update_bits(espi_ctrl->map, ESPI_INT_EN,
+			   ESPI_INT_EN_OOB_BITS,
+			   ESPI_INT_EN_OOB_BITS);
+
+	regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+			   ESPI_CTRL_OOB_SW_RDY,
+			   ESPI_CTRL_OOB_SW_RDY);
+}
+
+static const struct file_operations aspeed_espi_oob_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = aspeed_espi_oob_ioctl,
+};
+
+void *aspeed_espi_oob_alloc(struct device *dev, struct aspeed_espi_ctrl *espi_ctrl)
+{
+	int rc = 0;
+	struct aspeed_espi_oob *espi_oob;
+	struct aspeed_espi_oob_dma *dma;
+
+	espi_oob = devm_kzalloc(dev, sizeof(*espi_oob), GFP_KERNEL);
+	if (!espi_oob)
+		return ERR_PTR(-ENOMEM);
+
+	espi_oob->ctrl = espi_ctrl;
+
+	init_waitqueue_head(&espi_oob->wq);
+
+	spin_lock_init(&espi_oob->lock);
+
+	mutex_init(&espi_oob->put_tx_mtx);
+	mutex_init(&espi_oob->get_rx_mtx);
+
+	if (of_property_read_bool(dev->of_node, "oob,dma-mode"))
+		espi_oob->dma_mode = 1;
+
+	if (espi_oob->dma_mode) {
+		dma = &espi_oob->dma;
+
+		/* Descriptor based OOB DMA is supported since AST2600 */
+		if (espi_ctrl->model->version != ESPI_AST2500) {
+			of_property_read_u32(dev->of_node, "oob,dma-tx-desc-num",
+					     &dma->tx_desc_num);
+			of_property_read_u32(dev->of_node, "oob,dma-rx-desc-num",
+					     &dma->rx_desc_num);
+
+			if (!dma->tx_desc_num || !dma->rx_desc_num) {
+				dev_err(dev, "invalid zero number of DMA channels\n");
+				return ERR_PTR(-EINVAL);
+			}
+
+			if (dma->tx_desc_num >= OOB_DMA_DESC_MAX_NUM ||
+			    dma->rx_desc_num >= OOB_DMA_DESC_MAX_NUM) {
+				dev_err(dev, "too many number of DMA channels\n");
+				return ERR_PTR(-EINVAL);
+			}
+
+			dma->tx_desc = dma_alloc_coherent(dev,
+							  sizeof(*dma->tx_desc) * dma->tx_desc_num,
+							  &dma->tx_desc_addr, GFP_KERNEL);
+			if (!dma->tx_desc) {
+				dev_err(dev, "cannot allocate DMA TX descriptor\n");
+				return ERR_PTR(-ENOMEM);
+			}
+
+			dma->rx_desc = dma_alloc_coherent(dev,
+							  sizeof(*dma->rx_desc) * dma->rx_desc_num,
+							  &dma->rx_desc_addr, GFP_KERNEL);
+			if (!dma->rx_desc) {
+				dev_err(dev, "cannot allocate DMA RX descriptor\n");
+				return ERR_PTR(-ENOMEM);
+			}
+		}
+
+		/*
+		 * DMA descriptors are consumed in the circular
+		 * queue paradigm. Therefore, one dummy slot is
+		 * reserved to detect the full condition.
+		 *
+		 * For AST2500 without DMA descriptors supported,
+		 * the number of the queue slot should be 1 here.
+		 */
+		dma->tx_desc_num += 1;
+		dma->rx_desc_num += 1;
+
+		dma->tx_virt = dma_alloc_coherent(dev, PAGE_SIZE * dma->tx_desc_num,
+						  &dma->tx_addr, GFP_KERNEL);
+		if (!dma->tx_virt) {
+			dev_err(dev, "cannot allocate DMA TX buffer\n");
+			return ERR_PTR(-ENOMEM);
+		}
+
+		dma->rx_virt = dma_alloc_coherent(dev, PAGE_SIZE * dma->rx_desc_num,
+						  &dma->rx_addr, GFP_KERNEL);
+		if (!dma->rx_virt) {
+			dev_err(dev, "cannot allocate DMA RX buffer\n");
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	espi_oob->mdev.parent = dev;
+	espi_oob->mdev.minor = MISC_DYNAMIC_MINOR;
+	espi_oob->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s", OOB_MDEV_NAME);
+	espi_oob->mdev.fops = &aspeed_espi_oob_fops;
+	rc = misc_register(&espi_oob->mdev);
+	if (rc) {
+		dev_err(dev, "cannot register device\n");
+		return ERR_PTR(rc);
+	}
+
+	aspeed_espi_oob_enable(espi_oob);
+
+	return espi_oob;
+}
+
+void aspeed_espi_oob_free(struct device *dev, struct aspeed_espi_oob *espi_oob)
+{
+	struct aspeed_espi_oob_dma *dma = &espi_oob->dma;
+
+	if (espi_oob->dma_mode) {
+		dma_free_coherent(dev, sizeof(*dma->tx_desc) * dma->tx_desc_num,
+				  dma->tx_desc, dma->tx_desc_addr);
+		dma_free_coherent(dev, sizeof(*dma->rx_desc) * dma->rx_desc_num,
+				  dma->rx_desc, dma->rx_desc_addr);
+		dma_free_coherent(dev, PAGE_SIZE * dma->tx_desc_num,
+				  dma->tx_virt, dma->tx_addr);
+		dma_free_coherent(dev, PAGE_SIZE * dma->rx_desc_num,
+				  dma->rx_virt, dma->rx_addr);
+	}
+
+	mutex_destroy(&espi_oob->put_tx_mtx);
+	mutex_destroy(&espi_oob->get_rx_mtx);
+
+	misc_deregister(&espi_oob->mdev);
+}
diff --git a/drivers/soc/aspeed/aspeed-espi-oob.h b/drivers/soc/aspeed/aspeed-espi-oob.h
new file mode 100644
index 000000000000..03d74ef39e8b
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-oob.h
@@ -0,0 +1,70 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 Aspeed Technology Inc.
+ */
+#ifndef _ASPEED_ESPI_OOB_H_
+#define _ASPEED_ESPI_OOB_H_
+
+struct oob_tx_dma_desc {
+	uint32_t data_addr;
+	uint8_t cyc;
+	uint16_t tag : 4;
+	uint16_t len : 12;
+	uint8_t msg_type : 3;
+	uint8_t raz0 : 1;
+	uint8_t pec : 1;
+	uint8_t int_en : 1;
+	uint8_t pause : 1;
+	uint8_t raz1 : 1;
+	uint32_t raz2;
+	uint32_t raz3;
+} __packed;
+
+struct oob_rx_dma_desc {
+	uint32_t data_addr;
+	uint8_t cyc;
+	uint16_t tag : 4;
+	uint16_t len : 12;
+	uint8_t raz : 7;
+	uint8_t dirty : 1;
+} __packed;
+
+struct aspeed_espi_oob_dma {
+	uint32_t tx_desc_num;
+	uint32_t rx_desc_num;
+
+	struct oob_tx_dma_desc *tx_desc;
+	dma_addr_t tx_desc_addr;
+
+	struct oob_rx_dma_desc *rx_desc;
+	dma_addr_t rx_desc_addr;
+
+	void *tx_virt;
+	dma_addr_t tx_addr;
+
+	void *rx_virt;
+	dma_addr_t rx_addr;
+};
+
+struct aspeed_espi_oob {
+	uint32_t dma_mode;
+	struct aspeed_espi_oob_dma dma;
+
+	uint32_t rx_ready;
+	wait_queue_head_t wq;
+
+	struct mutex get_rx_mtx;
+	struct mutex put_tx_mtx;
+
+	spinlock_t lock;
+
+	struct miscdevice mdev;
+	struct aspeed_espi_ctrl *ctrl;
+};
+
+void aspeed_espi_oob_event(uint32_t sts, struct aspeed_espi_oob *espi_oob);
+void aspeed_espi_oob_enable(struct aspeed_espi_oob *espi_oob);
+void *aspeed_espi_oob_alloc(struct device *dev, struct aspeed_espi_ctrl *espi_ctrl);
+void aspeed_espi_oob_free(struct device *dev, struct aspeed_espi_oob *espi_oob);
+
+#endif
diff --git a/drivers/soc/aspeed/aspeed-espi-perif.c b/drivers/soc/aspeed/aspeed-espi-perif.c
new file mode 100644
index 000000000000..ebf3d9978b4a
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-perif.c
@@ -0,0 +1,511 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 ASPEED Technology Inc.
+ */
+#include <linux/fs.h>
+#include <linux/of_device.h>
+#include <linux/miscdevice.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/miscdevice.h>
+#include <linux/dma-mapping.h>
+
+#include "aspeed-espi-ioc.h"
+#include "aspeed-espi-ctrl.h"
+#include "aspeed-espi-perif.h"
+
+#define PERIF_MDEV_NAME		"aspeed-espi-peripheral"
+#define PERIF_MEMCYC_UNLOCK_KEY	0xfedc756e
+#define PERIF_MEMCYC_SIZE_MIN	0x10000
+
+static long aspeed_espi_perif_pc_get_rx(struct file *fp,
+					struct aspeed_espi_ioc *ioc,
+					struct aspeed_espi_perif *espi_perif)
+{
+	int i, rc = 0;
+	uint32_t reg;
+	uint32_t cyc, tag, len;
+	uint8_t *pkt;
+	uint32_t pkt_len;
+	struct espi_comm_hdr *hdr;
+	unsigned long flags;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_perif->ctrl;
+
+	if (fp->f_flags & O_NONBLOCK) {
+		if (mutex_trylock(&espi_perif->pc_rx_mtx))
+			return -EBUSY;
+
+		if (!espi_perif->rx_ready) {
+			rc = -ENODATA;
+			goto unlock_mtx_n_out;
+		}
+	} else {
+		mutex_lock(&espi_perif->pc_rx_mtx);
+
+		if (!espi_perif->rx_ready) {
+			rc = wait_event_interruptible(espi_perif->wq,
+						      espi_perif->rx_ready);
+			if (rc == -ERESTARTSYS) {
+				rc = -EINTR;
+				goto unlock_mtx_n_out;
+			}
+		}
+	}
+
+	/* common header (i.e. cycle type, tag, and length) is taken by HW */
+	regmap_read(espi_ctrl->map, ESPI_PERIF_PC_RX_CTRL, &reg);
+	cyc = (reg & ESPI_PERIF_PC_RX_CTRL_CYC_MASK) >> ESPI_PERIF_PC_RX_CTRL_CYC_SHIFT;
+	tag = (reg & ESPI_PERIF_PC_RX_CTRL_TAG_MASK) >> ESPI_PERIF_PC_RX_CTRL_TAG_SHIFT;
+	len = (reg & ESPI_PERIF_PC_RX_CTRL_LEN_MASK) >> ESPI_PERIF_PC_RX_CTRL_LEN_SHIFT;
+
+	/*
+	 * calculate the length of the rest part of the
+	 * eSPI packet to be read from HW and copied to
+	 * user space.
+	 */
+	switch (cyc) {
+	case ESPI_PERIF_MSG:
+		pkt_len = len + sizeof(struct espi_perif_msg);
+		break;
+	case ESPI_PERIF_MSG_D:
+		pkt_len = ((len) ? len : ESPI_PLD_LEN_MAX) +
+			  sizeof(struct espi_perif_msg);
+		break;
+	case ESPI_PERIF_SUC_CMPLT_D_MIDDLE:
+	case ESPI_PERIF_SUC_CMPLT_D_FIRST:
+	case ESPI_PERIF_SUC_CMPLT_D_LAST:
+	case ESPI_PERIF_SUC_CMPLT_D_ONLY:
+		pkt_len = ((len) ? len : ESPI_PLD_LEN_MAX) +
+			  sizeof(struct espi_perif_cmplt);
+		break;
+	case ESPI_PERIF_SUC_CMPLT:
+	case ESPI_PERIF_UNSUC_CMPLT:
+		pkt_len = len + sizeof(struct espi_perif_cmplt);
+		break;
+	default:
+		rc = -EFAULT;
+		goto unlock_mtx_n_out;
+	}
+
+	if (ioc->pkt_len < pkt_len) {
+		rc = -EINVAL;
+		goto unlock_mtx_n_out;
+	}
+
+	pkt = vmalloc(pkt_len);
+	if (!pkt) {
+		rc = -ENOMEM;
+		goto unlock_mtx_n_out;
+	}
+
+	hdr = (struct espi_comm_hdr *)pkt;
+	hdr->cyc = cyc;
+	hdr->tag = tag;
+	hdr->len_h = len >> 8;
+	hdr->len_l = len & 0xff;
+
+	if (espi_perif->dma_mode) {
+		memcpy(hdr + 1, espi_perif->dma.pc_rx_virt,
+		       pkt_len - sizeof(*hdr));
+	} else {
+		for (i = sizeof(*hdr); i < pkt_len; ++i) {
+			regmap_read(espi_ctrl->map,
+				    ESPI_PERIF_PC_RX_PORT, &reg);
+			pkt[i] = reg & 0xff;
+		}
+	}
+
+	if (copy_to_user((void __user *)ioc->pkt, pkt, pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	spin_lock_irqsave(&espi_perif->lock, flags);
+
+	regmap_write_bits(espi_ctrl->map, ESPI_PERIF_PC_RX_CTRL,
+			  ESPI_PERIF_PC_RX_CTRL_PEND_SERV,
+			  ESPI_PERIF_PC_RX_CTRL_PEND_SERV);
+
+	espi_perif->rx_ready = 0;
+
+	spin_unlock_irqrestore(&espi_perif->lock, flags);
+
+free_n_out:
+	vfree(pkt);
+
+unlock_mtx_n_out:
+	mutex_unlock(&espi_perif->pc_rx_mtx);
+
+	return rc;
+}
+
+static long aspeed_espi_perif_pc_put_tx(struct file *fp,
+					struct aspeed_espi_ioc *ioc,
+					struct aspeed_espi_perif *espi_perif)
+{
+	int i, rc = 0;
+	uint32_t reg;
+	uint32_t cyc, tag, len;
+	uint8_t *pkt;
+	struct espi_comm_hdr *hdr;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_perif->ctrl;
+
+	if (!mutex_trylock(&espi_perif->pc_tx_mtx))
+		return -EAGAIN;
+
+	regmap_read(espi_ctrl->map, ESPI_PERIF_PC_TX_CTRL, &reg);
+	if (reg & ESPI_PERIF_PC_TX_CTRL_TRIGGER) {
+		rc = -EBUSY;
+		goto unlock_n_out;
+	}
+
+	pkt = vmalloc(ioc->pkt_len);
+	if (!pkt) {
+		rc = -ENOMEM;
+		goto unlock_n_out;
+	}
+
+	hdr = (struct espi_comm_hdr *)pkt;
+
+	if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	/*
+	 * common header (i.e. cycle type, tag, and length)
+	 * part is written to HW registers
+	 */
+	if (espi_perif->dma_mode) {
+		memcpy(espi_perif->dma.pc_tx_virt, hdr + 1,
+		       ioc->pkt_len - sizeof(*hdr));
+		dma_wmb();
+	} else {
+		for (i = sizeof(*hdr); i < ioc->pkt_len; ++i)
+			regmap_write(espi_ctrl->map,
+				     ESPI_PERIF_PC_TX_PORT, pkt[i]);
+	}
+
+	cyc = hdr->cyc;
+	tag = hdr->tag;
+	len = (hdr->len_h << 8) | (hdr->len_l & 0xff);
+
+	reg = ((cyc << ESPI_PERIF_PC_TX_CTRL_CYC_SHIFT) & ESPI_PERIF_PC_TX_CTRL_CYC_MASK)
+		| ((tag << ESPI_PERIF_PC_TX_CTRL_TAG_SHIFT) & ESPI_PERIF_PC_TX_CTRL_TAG_MASK)
+		| ((len << ESPI_PERIF_PC_TX_CTRL_LEN_SHIFT) & ESPI_PERIF_PC_TX_CTRL_LEN_MASK)
+		| ESPI_PERIF_PC_TX_CTRL_TRIGGER;
+
+	regmap_write(espi_ctrl->map, ESPI_PERIF_PC_TX_CTRL, reg);
+
+free_n_out:
+	vfree(pkt);
+
+unlock_n_out:
+	mutex_unlock(&espi_perif->pc_tx_mtx);
+
+	return rc;
+}
+
+static long aspeed_espi_perif_np_put_tx(struct file *fp,
+					struct aspeed_espi_ioc *ioc,
+					struct aspeed_espi_perif *espi_perif)
+{
+	int i, rc = 0;
+	uint32_t reg;
+	uint32_t cyc, tag, len;
+	uint8_t *pkt;
+	struct espi_comm_hdr *hdr;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_perif->ctrl;
+
+	if (!mutex_trylock(&espi_perif->np_tx_mtx))
+		return -EAGAIN;
+
+	regmap_read(espi_ctrl->map, ESPI_PERIF_NP_TX_CTRL, &reg);
+	if (reg & ESPI_PERIF_NP_TX_CTRL_TRIGGER) {
+		rc = -EBUSY;
+		goto unlock_n_out;
+	}
+
+	pkt = vmalloc(ioc->pkt_len);
+	if (!pkt) {
+		rc = -ENOMEM;
+		goto unlock_n_out;
+	}
+
+	hdr = (struct espi_comm_hdr *)pkt;
+
+	if (copy_from_user(pkt, (void __user *)ioc->pkt, ioc->pkt_len)) {
+		rc = -EFAULT;
+		goto free_n_out;
+	}
+
+	/*
+	 * common header (i.e. cycle type, tag, and length)
+	 * part is written to HW registers
+	 */
+	if (espi_perif->dma_mode) {
+		memcpy(espi_perif->dma.np_tx_virt, hdr + 1,
+		       ioc->pkt_len - sizeof(*hdr));
+		dma_wmb();
+	} else {
+		for (i = sizeof(*hdr); i < ioc->pkt_len; ++i)
+			regmap_write(espi_ctrl->map,
+				     ESPI_PERIF_NP_TX_PORT, pkt[i]);
+	}
+
+	cyc = hdr->cyc;
+	tag = hdr->tag;
+	len = (hdr->len_h << 8) | (hdr->len_l & 0xff);
+
+	reg = ((cyc << ESPI_PERIF_NP_TX_CTRL_CYC_SHIFT) & ESPI_PERIF_NP_TX_CTRL_CYC_MASK)
+		| ((tag << ESPI_PERIF_NP_TX_CTRL_TAG_SHIFT) & ESPI_PERIF_NP_TX_CTRL_TAG_MASK)
+		| ((len << ESPI_PERIF_NP_TX_CTRL_LEN_SHIFT) & ESPI_PERIF_NP_TX_CTRL_LEN_MASK)
+		| ESPI_PERIF_NP_TX_CTRL_TRIGGER;
+
+	regmap_write(espi_ctrl->map, ESPI_PERIF_NP_TX_CTRL, reg);
+
+free_n_out:
+	vfree(pkt);
+
+unlock_n_out:
+	mutex_unlock(&espi_perif->np_tx_mtx);
+
+	return rc;
+
+}
+
+static long aspeed_espi_perif_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	struct aspeed_espi_ioc ioc;
+	struct aspeed_espi_perif *espi_perif = container_of(
+			fp->private_data,
+			struct aspeed_espi_perif,
+			mdev);
+
+	if (copy_from_user(&ioc, (void __user *)arg, sizeof(ioc)))
+		return -EFAULT;
+
+	if (ioc.pkt_len > ESPI_PKT_LEN_MAX)
+		return -EINVAL;
+
+	switch (cmd) {
+	case ASPEED_ESPI_PERIF_PC_GET_RX:
+		return aspeed_espi_perif_pc_get_rx(fp, &ioc, espi_perif);
+	case ASPEED_ESPI_PERIF_PC_PUT_TX:
+		return aspeed_espi_perif_pc_put_tx(fp, &ioc, espi_perif);
+	case ASPEED_ESPI_PERIF_NP_PUT_TX:
+		return aspeed_espi_perif_np_put_tx(fp, &ioc, espi_perif);
+	};
+
+	return -EINVAL;
+}
+
+static int aspeed_espi_perif_mmap(struct file *fp, struct vm_area_struct *vma)
+{
+	struct aspeed_espi_perif *espi_perif = container_of(
+			fp->private_data,
+			struct aspeed_espi_perif,
+			mdev);
+	unsigned long vm_size = vma->vm_end - vma->vm_start;
+	pgprot_t prot = vma->vm_page_prot;
+
+	if (!espi_perif->mcyc_enable)
+		return -EPERM;
+
+	if (((vma->vm_pgoff << PAGE_SHIFT) + vm_size) > espi_perif->mcyc_size)
+		return -EINVAL;
+
+	prot = pgprot_noncached(prot);
+
+	if (remap_pfn_range(vma, vma->vm_start,
+			    (espi_perif->mcyc_taddr >> PAGE_SHIFT) + vma->vm_pgoff,
+			    vm_size, prot))
+		return -EAGAIN;
+
+	return 0;
+}
+
+void aspeed_espi_perif_event(uint32_t sts, struct aspeed_espi_perif *espi_perif)
+{
+	unsigned long flags;
+
+	if (sts & ESPI_INT_STS_PERIF_PC_RX_CMPLT) {
+		spin_lock_irqsave(&espi_perif->lock, flags);
+		espi_perif->rx_ready = 1;
+		spin_unlock_irqrestore(&espi_perif->lock, flags);
+
+		wake_up_interruptible(&espi_perif->wq);
+	}
+}
+
+void aspeed_espi_perif_enable(struct aspeed_espi_perif *espi_perif)
+{
+	struct aspeed_espi_perif_dma *dma = &espi_perif->dma;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_perif->ctrl;
+
+	if (espi_perif->mcyc_enable) {
+		if (espi_ctrl->model->version == ESPI_AST2500) {
+			regmap_write(espi_ctrl->map, ESPI_PERIF_PC_RX_MASK,
+				     PERIF_MEMCYC_UNLOCK_KEY);
+			regmap_write(espi_ctrl->map, ESPI_PERIF_PC_RX_MASK,
+				     espi_perif->mcyc_mask);
+		} else {
+			regmap_write(espi_ctrl->map, ESPI_PERIF_PC_RX_MASK,
+				     espi_perif->mcyc_mask | ESPI_PERIF_PC_RX_MASK_CFG_WP);
+			regmap_update_bits(espi_ctrl->map, ESPI_CTRL2,
+					   ESPI_CTRL2_MEMCYC_RD_DIS | ESPI_CTRL2_MEMCYC_WR_DIS, 0);
+		}
+
+		regmap_write(espi_ctrl->map, ESPI_PERIF_PC_RX_SADDR, espi_perif->mcyc_saddr);
+		regmap_write(espi_ctrl->map, ESPI_PERIF_PC_RX_TADDR, espi_perif->mcyc_taddr);
+	}
+
+	if (espi_perif->dma_mode) {
+		regmap_write(espi_ctrl->map, ESPI_PERIF_PC_RX_DMA, dma->pc_rx_addr);
+		regmap_write(espi_ctrl->map, ESPI_PERIF_PC_TX_DMA, dma->pc_tx_addr);
+		regmap_write(espi_ctrl->map, ESPI_PERIF_NP_TX_DMA, dma->np_tx_addr);
+
+		regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+				   ESPI_CTRL_PERIF_NP_TX_DMA_EN |
+				   ESPI_CTRL_PERIF_PC_TX_DMA_EN |
+				   ESPI_CTRL_PERIF_PC_RX_DMA_EN,
+				   ESPI_CTRL_PERIF_NP_TX_DMA_EN |
+				   ESPI_CTRL_PERIF_PC_TX_DMA_EN |
+				   ESPI_CTRL_PERIF_PC_RX_DMA_EN);
+	}
+
+	regmap_write(espi_ctrl->map, ESPI_INT_STS,
+		     ESPI_INT_STS_PERIF_BITS);
+
+	regmap_update_bits(espi_ctrl->map, ESPI_INT_EN,
+			   ESPI_INT_EN_PERIF_BITS,
+			   ESPI_INT_EN_PERIF_BITS);
+
+	regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+			   ESPI_CTRL_PERIF_SW_RDY,
+			   ESPI_CTRL_PERIF_SW_RDY);
+}
+
+static const struct file_operations aspeed_espi_perif_fops = {
+	.owner = THIS_MODULE,
+	.mmap = aspeed_espi_perif_mmap,
+	.unlocked_ioctl = aspeed_espi_perif_ioctl,
+};
+
+void *aspeed_espi_perif_alloc(struct device *dev, struct aspeed_espi_ctrl *espi_ctrl)
+{
+	int rc;
+	struct aspeed_espi_perif *espi_perif;
+	struct aspeed_espi_perif_dma *dma;
+
+	espi_perif = devm_kzalloc(dev, sizeof(*espi_perif), GFP_KERNEL);
+	if (!espi_perif)
+		return ERR_PTR(-ENOMEM);
+
+	espi_perif->ctrl = espi_ctrl;
+
+	init_waitqueue_head(&espi_perif->wq);
+
+	spin_lock_init(&espi_perif->lock);
+
+	mutex_init(&espi_perif->pc_rx_mtx);
+	mutex_init(&espi_perif->pc_tx_mtx);
+	mutex_init(&espi_perif->np_tx_mtx);
+
+	espi_perif->mcyc_enable = of_property_read_bool(dev->of_node, "perif,memcyc-enable");
+	if (espi_perif->mcyc_enable) {
+		rc = of_property_read_u32(dev->of_node, "perif,memcyc-src-addr",
+					  &espi_perif->mcyc_saddr);
+		if (rc) {
+			dev_err(dev, "cannot get Host source address for memory cycle\n");
+			return ERR_PTR(-ENODEV);
+		}
+
+		rc = of_property_read_u32(dev->of_node, "perif,memcyc-size",
+					  &espi_perif->mcyc_size);
+		if (rc) {
+			dev_err(dev, "cannot get size for memory cycle\n");
+			return ERR_PTR(-ENODEV);
+		}
+
+		if (espi_perif->mcyc_size < PERIF_MEMCYC_SIZE_MIN)
+			espi_perif->mcyc_size = PERIF_MEMCYC_SIZE_MIN;
+		else
+			espi_perif->mcyc_size = roundup_pow_of_two(espi_perif->mcyc_size);
+
+		espi_perif->mcyc_mask = ~(espi_perif->mcyc_size - 1);
+		espi_perif->mcyc_virt = dma_alloc_coherent(dev, espi_perif->mcyc_size,
+							   &espi_perif->mcyc_taddr, GFP_KERNEL);
+		if (!espi_perif->mcyc_virt) {
+			dev_err(dev, "cannot allocate memory cycle region\n");
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	if (of_property_read_bool(dev->of_node, "perif,dma-mode")) {
+		dma = &espi_perif->dma;
+
+		dma->pc_tx_virt = dma_alloc_coherent(dev, PAGE_SIZE,
+						     &dma->pc_tx_addr, GFP_KERNEL);
+		if (!dma->pc_tx_virt) {
+			dev_err(dev, "cannot allocate posted TX DMA buffer\n");
+			return ERR_PTR(-ENOMEM);
+		}
+
+		dma->pc_rx_virt = dma_alloc_coherent(dev, PAGE_SIZE,
+						     &dma->pc_rx_addr, GFP_KERNEL);
+		if (!dma->pc_rx_virt) {
+			dev_err(dev, "cannot allocate posted RX DMA buffer\n");
+			return ERR_PTR(-ENOMEM);
+		}
+
+		dma->np_tx_virt = dma_alloc_coherent(dev, PAGE_SIZE,
+				&dma->np_tx_addr, GFP_KERNEL);
+		if (!dma->np_tx_virt) {
+			dev_err(dev, "cannot allocate non-posted TX DMA buffer\n");
+			return ERR_PTR(-ENOMEM);
+		}
+
+		espi_perif->dma_mode = 1;
+	}
+
+	espi_perif->mdev.parent = dev;
+	espi_perif->mdev.minor = MISC_DYNAMIC_MINOR;
+	espi_perif->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s", PERIF_MDEV_NAME);
+	espi_perif->mdev.fops = &aspeed_espi_perif_fops;
+	rc = misc_register(&espi_perif->mdev);
+	if (rc) {
+		dev_err(dev, "cannot register device\n");
+		return ERR_PTR(rc);
+	}
+
+	aspeed_espi_perif_enable(espi_perif);
+
+	return espi_perif;
+}
+
+void aspeed_espi_perif_free(struct device *dev, struct aspeed_espi_perif *espi_perif)
+{
+	struct aspeed_espi_perif_dma *dma = &espi_perif->dma;
+
+	if (espi_perif->mcyc_virt)
+		dma_free_coherent(dev, espi_perif->mcyc_size,
+				  espi_perif->mcyc_virt,
+				  espi_perif->mcyc_taddr);
+
+	if (espi_perif->dma_mode) {
+		dma_free_coherent(dev, PAGE_SIZE, dma->pc_tx_virt,
+				  dma->pc_tx_addr);
+		dma_free_coherent(dev, PAGE_SIZE, dma->pc_rx_virt,
+				  dma->pc_rx_addr);
+		dma_free_coherent(dev, PAGE_SIZE, dma->np_tx_virt,
+				  dma->np_tx_addr);
+	}
+
+	mutex_destroy(&espi_perif->pc_tx_mtx);
+	mutex_destroy(&espi_perif->np_tx_mtx);
+
+	misc_deregister(&espi_perif->mdev);
+}
diff --git a/drivers/soc/aspeed/aspeed-espi-perif.h b/drivers/soc/aspeed/aspeed-espi-perif.h
new file mode 100644
index 000000000000..1b964e4680f5
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-perif.h
@@ -0,0 +1,45 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 ASPEED Technology Inc.
+ */
+#ifndef _ASPEED_ESPI_PERIF_H_
+#define _ASPEED_ESPI_PERIF_H_
+
+struct aspeed_espi_perif_dma {
+	void *pc_tx_virt;
+	dma_addr_t pc_tx_addr;
+	void *pc_rx_virt;
+	dma_addr_t pc_rx_addr;
+	void *np_tx_virt;
+	dma_addr_t np_tx_addr;
+};
+
+struct aspeed_espi_perif {
+	uint32_t mcyc_enable;
+	void *mcyc_virt;
+	uint32_t mcyc_saddr;
+	phys_addr_t mcyc_taddr;
+	uint32_t mcyc_size;
+	uint32_t mcyc_mask;
+
+	uint32_t dma_mode;
+	struct aspeed_espi_perif_dma dma;
+
+	uint32_t rx_ready;
+	wait_queue_head_t wq;
+
+	spinlock_t lock;
+	struct mutex pc_rx_mtx;
+	struct mutex pc_tx_mtx;
+	struct mutex np_tx_mtx;
+
+	struct miscdevice mdev;
+	struct aspeed_espi_ctrl *ctrl;
+};
+
+void aspeed_espi_perif_event(uint32_t sts, struct aspeed_espi_perif *espi_perif);
+void aspeed_espi_perif_enable(struct aspeed_espi_perif *espi_perif);
+void *aspeed_espi_perif_alloc(struct device *dev, struct aspeed_espi_ctrl *espi_ctrl);
+void aspeed_espi_perif_free(struct device *dev, struct aspeed_espi_perif *espi_perif);
+
+#endif
diff --git a/drivers/soc/aspeed/aspeed-espi-vw.c b/drivers/soc/aspeed/aspeed-espi-vw.c
new file mode 100644
index 000000000000..819b3ae2f7ea
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-vw.c
@@ -0,0 +1,137 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 ASPEED Technology Inc.
+ */
+#include <linux/fs.h>
+#include <linux/of_device.h>
+#include <linux/miscdevice.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/miscdevice.h>
+#include <linux/dma-mapping.h>
+
+#include "aspeed-espi-ioc.h"
+#include "aspeed-espi-ctrl.h"
+#include "aspeed-espi-vw.h"
+
+#define VW_MDEV_NAME	"aspeed-espi-vw"
+
+static long aspeed_espi_vw_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	uint32_t val;
+
+	struct aspeed_espi_vw *espi_vw = container_of(fp->private_data,
+						      struct aspeed_espi_vw,
+						      mdev);
+	struct aspeed_espi_ctrl *espi_ctrl = espi_vw->ctrl;
+
+	switch (cmd) {
+	case ASPEED_ESPI_VW_GET_GPIO_VAL:
+		regmap_read(espi_ctrl->map, ESPI_VW_GPIO_VAL, &val);
+		if (put_user(val, (uint32_t __user *)arg))
+			return -EFAULT;
+		break;
+
+	case ASPEED_ESPI_VW_PUT_GPIO_VAL:
+		if (get_user(val, (uint32_t __user *)arg))
+			return -EFAULT;
+		regmap_write(espi_ctrl->map, ESPI_VW_GPIO_VAL, val);
+		break;
+
+	default:
+		return -EINVAL;
+	};
+
+	return 0;
+}
+
+void aspeed_espi_vw_event(uint32_t sts, struct aspeed_espi_vw *espi_vw)
+{
+	uint32_t sysevt_sts;
+	struct aspeed_espi_ctrl *espi_ctrl = espi_vw->ctrl;
+
+	regmap_read(espi_ctrl->map, ESPI_INT_STS, &sts);
+
+	if (sts & ESPI_INT_STS_VW_SYSEVT) {
+		regmap_read(espi_ctrl->map, ESPI_SYSEVT_INT_STS, &sysevt_sts);
+
+		if (espi_ctrl->model->version == ESPI_AST2500) {
+			if (sysevt_sts & ESPI_SYSEVT_INT_STS_HOST_RST_WARN)
+				regmap_update_bits(espi_ctrl->map, ESPI_SYSEVT,
+						   ESPI_SYSEVT_HOST_RST_ACK,
+						   ESPI_SYSEVT_HOST_RST_ACK);
+
+			if (sysevt_sts & ESPI_SYSEVT_INT_STS_OOB_RST_WARN)
+				regmap_update_bits(espi_ctrl->map, ESPI_SYSEVT,
+						   ESPI_SYSEVT_OOB_RST_ACK,
+						   ESPI_SYSEVT_OOB_RST_ACK);
+		}
+
+		regmap_write(espi_ctrl->map, ESPI_SYSEVT_INT_STS, sysevt_sts);
+	}
+
+	if (sts & ESPI_INT_STS_VW_SYSEVT1) {
+		regmap_read(espi_ctrl->map, ESPI_SYSEVT1_INT_STS, &sysevt_sts);
+
+		if (sysevt_sts & ESPI_SYSEVT1_INT_STS_SUSPEND_WARN)
+			regmap_update_bits(espi_ctrl->map, ESPI_SYSEVT1,
+					   ESPI_SYSEVT1_SUSPEND_ACK,
+					   ESPI_SYSEVT1_SUSPEND_ACK);
+
+		regmap_write(espi_ctrl->map, ESPI_SYSEVT1_INT_STS, sysevt_sts);
+	}
+}
+
+void aspeed_espi_vw_enable(struct aspeed_espi_vw *espi_vw)
+{
+	struct aspeed_espi_ctrl *espi_ctrl = espi_vw->ctrl;
+
+	regmap_write(espi_ctrl->map, ESPI_INT_STS,
+		     ESPI_INT_STS_VW_BITS);
+
+	regmap_update_bits(espi_ctrl->map, ESPI_INT_EN,
+			   ESPI_INT_EN_VW_BITS,
+			   ESPI_INT_EN_VW_BITS);
+
+	regmap_update_bits(espi_ctrl->map, ESPI_CTRL,
+			   ESPI_CTRL_VW_SW_RDY,
+			   ESPI_CTRL_VW_SW_RDY);
+}
+
+static const struct file_operations aspeed_espi_vw_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = aspeed_espi_vw_ioctl,
+};
+
+void *aspeed_espi_vw_alloc(struct device *dev, struct aspeed_espi_ctrl *espi_ctrl)
+{
+	int rc;
+	struct aspeed_espi_vw *espi_vw;
+
+	espi_vw = devm_kzalloc(dev, sizeof(*espi_vw), GFP_KERNEL);
+	if (!espi_vw)
+		return ERR_PTR(-ENOMEM);
+
+	espi_vw->ctrl = espi_ctrl;
+
+	espi_vw->mdev.parent = dev;
+	espi_vw->mdev.minor = MISC_DYNAMIC_MINOR;
+	espi_vw->mdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s", VW_MDEV_NAME);
+	espi_vw->mdev.fops = &aspeed_espi_vw_fops;
+	rc = misc_register(&espi_vw->mdev);
+	if (rc) {
+		dev_err(dev, "cannot register device\n");
+		return ERR_PTR(rc);
+	}
+
+	aspeed_espi_vw_enable(espi_vw);
+
+	return espi_vw;
+}
+
+void aspeed_espi_vw_free(struct device *dev, struct aspeed_espi_vw *espi_vw)
+{
+	misc_deregister(&espi_vw->mdev);
+}
diff --git a/drivers/soc/aspeed/aspeed-espi-vw.h b/drivers/soc/aspeed/aspeed-espi-vw.h
new file mode 100644
index 000000000000..aba9c414ac1b
--- /dev/null
+++ b/drivers/soc/aspeed/aspeed-espi-vw.h
@@ -0,0 +1,21 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 ASPEED Technology Inc.
+ */
+#ifndef _ASPEED_ESPI_VW_H_
+#define _ASPEED_ESPI_VW_H_
+
+struct aspeed_espi_vw {
+	int irq;
+	int irq_reset;
+
+	struct miscdevice mdev;
+	struct aspeed_espi_ctrl *ctrl;
+};
+
+void aspeed_espi_vw_event(uint32_t sts, struct aspeed_espi_vw *espi_vw);
+void aspeed_espi_vw_enable(struct aspeed_espi_vw *espi_vw);
+void *aspeed_espi_vw_alloc(struct device *dev, struct aspeed_espi_ctrl *espi_ctrl);
+void aspeed_espi_vw_free(struct device *dev, struct aspeed_espi_vw *espi_vw);
+
+#endif