bhnd(4): Fix bcma/siba core reset behavior

Add missing support for specifying I/O control flags during core reset,
and resolve a number of siba(4)-specific reset issues:

- Add missing check for target reject flags in siba_is_hw_suspended().
- Remove incorrect wait on SIBA_TMH_BUSY when modifying any target state
  register; this should only be done when waiting for initiated
  transactions to clear.
- Add missing wait on SIBA_IM_BY when asserting SIBA_IM_RJ.
- Overwrite any previously set SIBA_TML_REJ flag when bringing the core
  out of reset. This fixes a lockup that occured when we brought up a core
  (after reboot) that had previously been placed into RESET by siba_bwn(4).

Approved by:	adrian (mentor, implicit)
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D13039
This commit is contained in:
Landon J. Fuller 2017-11-27 22:13:30 +00:00
parent 05ed3f9063
commit ac59515b98
9 changed files with 188 additions and 162 deletions

View File

@ -266,9 +266,9 @@
.Fa "device_t dev" "bus_size_t offset" "const void *value" "u_int width" .Fa "device_t dev" "bus_size_t offset" "const void *value" "u_int width"
.Fc .Fc
.Ft int .Ft int
.Fn bhnd_reset_hw "device_t dev" "uint16_t ioctl" .Fn bhnd_reset_hw "device_t dev" "uint16_t ioctl" "uint16_t reset_ioctl"
.Ft int .Ft int
.Fn bhnd_suspend_hw "device_t dev" .Fn bhnd_suspend_hw "device_t dev" "uint16_t ioctl"
.Ft bool .Ft bool
.Fn bhnd_is_hw_suspended "device_t dev" .Fn bhnd_is_hw_suspended "device_t dev"
.\" .\"
@ -1054,7 +1054,10 @@ function transitions the device
.Fa dev .Fa dev
to a low power to a low power
.Dq RESET .Dq RESET
state. state, writing
.Fa ioctl
to the I/O control flags of
.Fa dev .
The hardware may be brought out of this state using The hardware may be brought out of this state using
.Fn bhnd_reset_hw . .Fn bhnd_reset_hw .
.Pp .Pp
@ -1062,10 +1065,14 @@ The
.Fn bhnd_reset_hw .Fn bhnd_reset_hw
function first transitions the device function first transitions the device
.Fa dev .Fa dev
to a low power RESET state, and then brings the device out of RESET, writing to a low power RESET state, writing
.Fa ioctl_reset
to the I/O control flags
of
.Fa dev ,
and then brings the device out of RESET, writing
.Fa ioctl .Fa ioctl
to the I/O control flags of to the device's I/O control flags.
.Fa dev .
.Pp .Pp
The The
.Fn bhnd_is_hw_suspended .Fn bhnd_is_hw_suspended

View File

@ -296,22 +296,22 @@ bcma_is_hw_suspended(device_t dev, device_t child)
} }
static int static int
bcma_reset_hw(device_t dev, device_t child, uint16_t ioctl) bcma_reset_hw(device_t dev, device_t child, uint16_t ioctl,
uint16_t reset_ioctl)
{ {
struct bcma_devinfo *dinfo; struct bcma_devinfo *dinfo;
struct bhnd_core_pmu_info *pm; struct bhnd_resource *r;
struct bhnd_resource *r; uint16_t clkflags;
int error; int error;
if (device_get_parent(child) != dev) if (device_get_parent(child) != dev)
return (EINVAL); return (EINVAL);
dinfo = device_get_ivars(child); dinfo = device_get_ivars(child);
pm = dinfo->pmu_info;
/* We require exclusive control over BHND_IOCTL_CLK_EN and /* We require exclusive control over BHND_IOCTL_CLK_(EN|FORCE) */
* BHND_IOCTL_CLK_FORCE. */ clkflags = BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE;
if (ioctl & (BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE)) if (ioctl & clkflags)
return (EINVAL); return (EINVAL);
/* Can't suspend the core without access to the agent registers */ /* Can't suspend the core without access to the agent registers */
@ -319,7 +319,7 @@ bcma_reset_hw(device_t dev, device_t child, uint16_t ioctl)
return (ENODEV); return (ENODEV);
/* Place core into known RESET state */ /* Place core into known RESET state */
if ((error = BHND_BUS_SUSPEND_HW(dev, child))) if ((error = bhnd_suspend_hw(child, reset_ioctl)))
return (error); return (error);
/* /*
@ -329,9 +329,7 @@ bcma_reset_hw(device_t dev, device_t child, uint16_t ioctl)
* - Force clock distribution to ensure propagation throughout the * - Force clock distribution to ensure propagation throughout the
* core. * core.
*/ */
error = bhnd_write_ioctl(child, if ((error = bhnd_write_ioctl(child, ioctl | clkflags, UINT16_MAX)))
ioctl | BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE, UINT16_MAX);
if (error)
return (error); return (error);
/* Bring the core out of reset */ /* Bring the core out of reset */
@ -347,11 +345,11 @@ bcma_reset_hw(device_t dev, device_t child, uint16_t ioctl)
} }
static int static int
bcma_suspend_hw(device_t dev, device_t child) bcma_suspend_hw(device_t dev, device_t child, uint16_t ioctl)
{ {
struct bcma_devinfo *dinfo; struct bcma_devinfo *dinfo;
struct bhnd_resource *r; struct bhnd_resource *r;
uint32_t rst; uint16_t clkflags;
int error; int error;
if (device_get_parent(child) != dev) if (device_get_parent(child) != dev)
@ -359,6 +357,11 @@ bcma_suspend_hw(device_t dev, device_t child)
dinfo = device_get_ivars(child); dinfo = device_get_ivars(child);
/* We require exclusive control over BHND_IOCTL_CLK_(EN|FORCE) */
clkflags = BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE;
if (ioctl & clkflags)
return (EINVAL);
/* Can't suspend the core without access to the agent registers */ /* Can't suspend the core without access to the agent registers */
if ((r = dinfo->res_agent) == NULL) if ((r = dinfo->res_agent) == NULL)
return (ENODEV); return (ENODEV);
@ -367,17 +370,12 @@ bcma_suspend_hw(device_t dev, device_t child)
if ((error = bcma_dmp_wait_reset(child, dinfo))) if ((error = bcma_dmp_wait_reset(child, dinfo)))
return (error); return (error);
/* Already in reset? */ /* Put core into reset (if not already in reset) */
rst = bhnd_bus_read_4(r, BCMA_DMP_RESETCTRL);
if (rst & BCMA_DMP_RC_RESET)
return (0);
/* Put core into reset */
if ((error = bcma_dmp_write_reset(child, dinfo, BCMA_DMP_RC_RESET))) if ((error = bcma_dmp_write_reset(child, dinfo, BCMA_DMP_RC_RESET)))
return (error); return (error);
/* Clear core flags */ /* Write core flags (and clear CLK_EN/CLK_FORCE) */
if ((error = bhnd_write_ioctl(child, 0x0, UINT16_MAX))) if ((error = bhnd_write_ioctl(child, ioctl, ~clkflags)))
return (error); return (error);
return (0); return (0);

View File

@ -594,9 +594,16 @@ bcma_dmp_wait_reset(device_t child, struct bcma_devinfo *dinfo)
int int
bcma_dmp_write_reset(device_t child, struct bcma_devinfo *dinfo, uint32_t value) bcma_dmp_write_reset(device_t child, struct bcma_devinfo *dinfo, uint32_t value)
{ {
uint32_t rst;
if (dinfo->res_agent == NULL) if (dinfo->res_agent == NULL)
return (ENODEV); return (ENODEV);
/* Already in requested reset state? */
rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL);
if (rst == value)
return (0);
bhnd_bus_write_4(dinfo->res_agent, BCMA_DMP_RESETCTRL, value); bhnd_bus_write_4(dinfo->res_agent, BCMA_DMP_RESETCTRL, value);
bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL); /* read-back */ bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL); /* read-back */
DELAY(10); DELAY(10);

View File

@ -818,23 +818,27 @@ bhnd_is_hw_suspended(device_t dev)
} }
/** /**
* Place the bhnd(4) device's hardware into a reset state, and then bring the * Place the bhnd(4) device's hardware into a low-power RESET state with
* hardware out of reset with BHND_IOCTL_CLK_EN and @p ioctl flags set. * the @p reset_ioctl I/O control flags set, and then bring the hardware out of
* RESET with the @p ioctl I/O control flags set.
* *
* Any clock or resource PMU requests previously made by @p dev will be * Any clock or resource PMU requests previously made by @p child will be
* invalidated. * invalidated.
* *
* @param dev The device to be reset. * @param dev The device to be reset.
* @param ioctl Device-specific core ioctl flags to be supplied on reset * @param ioctl Device-specific I/O control flags to be set when bringing
* (see BHND_IOCTL_*). * the core out of its RESET state (see BHND_IOCTL_*).
* @param reset_ioctl Device-specific I/O control flags to be set when placing
* the core into its RESET state.
* *
* @retval 0 success * @retval 0 success
* @retval non-zero error * @retval non-zero error
*/ */
static inline int static inline int
bhnd_reset_hw(device_t dev, uint16_t ioctl) bhnd_reset_hw(device_t dev, uint16_t ioctl, uint16_t reset_ioctl)
{ {
return (BHND_BUS_RESET_HW(device_get_parent(dev), dev, ioctl)); return (BHND_BUS_RESET_HW(device_get_parent(dev), dev, ioctl,
reset_ioctl));
} }
/** /**
@ -851,9 +855,9 @@ bhnd_reset_hw(device_t dev, uint16_t ioctl)
* @retval non-zero error * @retval non-zero error
*/ */
static inline int static inline int
bhnd_suspend_hw(device_t dev) bhnd_suspend_hw(device_t dev, uint16_t ioctl)
{ {
return (BHND_BUS_SUSPEND_HW(device_get_parent(dev), dev)); return (BHND_BUS_SUSPEND_HW(device_get_parent(dev), dev, ioctl));
} }
/** /**

View File

@ -96,7 +96,8 @@ CODE {
} }
static int static int
bhnd_bus_null_reset_hw(device_t dev, device_t child, uint16_t ioctl) bhnd_bus_null_reset_hw(device_t dev, device_t child, uint16_t ioctl,
uint16_t reset_ioctl)
{ {
panic("bhnd_bus_reset_hw unimplemented"); panic("bhnd_bus_reset_hw unimplemented");
} }
@ -624,16 +625,19 @@ METHOD bool is_hw_suspended {
} DEFAULT bhnd_bus_null_is_hw_suspended; } DEFAULT bhnd_bus_null_is_hw_suspended;
/** /**
* Place the bhnd(4) device's hardware into a reset state, and then bring the * Place the bhnd(4) device's hardware into a low-power RESET state with
* hardware out of reset with BHND_IOCTL_CLK_EN and @p ioctl flags set. * the @p reset_ioctl I/O control flags set, and then bring the hardware out of
* RESET with the @p ioctl I/O control flags set.
* *
* Any clock or resource PMU requests previously made by @p child will be * Any clock or resource PMU requests previously made by @p child will be
* invalidated. * invalidated.
* *
* @param dev The bhnd bus parent of @p child. * @param dev The bhnd bus parent of @p child.
* @param child The device to be reset. * @param child The device to be reset.
* @param ioctl Device-specific core ioctl flags to be supplied on reset * @param ioctl Device-specific I/O control flags to be set when bringing
* (see BHND_IOCTL_*). * the core out of its RESET state (see BHND_IOCTL_*).
* @param reset_ioctl Device-specific I/O control flags to be set when placing
* the core into its RESET state.
* *
* @retval 0 success * @retval 0 success
* @retval non-zero error * @retval non-zero error
@ -642,18 +646,21 @@ METHOD int reset_hw {
device_t dev; device_t dev;
device_t child; device_t child;
uint16_t ioctl; uint16_t ioctl;
uint16_t reset_ioctl;
} DEFAULT bhnd_bus_null_reset_hw; } DEFAULT bhnd_bus_null_reset_hw;
/** /**
* Suspend @p child's hardware in a low-power reset state. * Suspend @p child's hardware in a low-power RESET state.
* *
* Any clock or resource PMU requests previously made by @p dev will be * Any clock or resource PMU requests previously made by @p dev will be
* invalidated. * invalidated.
* *
* The hardware may be brought out of reset via bhnd_reset_hw(). * The hardware may be brought out of RESET via bhnd_reset_hw().
* *
* @param dev The bhnd bus parent of @p child. * @param dev The bhnd bus parent of @p child.
* @param dev The device to be suspended. * @param dev The device to be suspended.
* @param ioctl Device-specific I/O control flags to be set when placing
* the core into its RESET state (see BHND_IOCTL_*).
* *
* @retval 0 success * @retval 0 success
* @retval non-zero error * @retval non-zero error
@ -661,6 +668,7 @@ METHOD int reset_hw {
METHOD int suspend_hw { METHOD int suspend_hw {
device_t dev; device_t dev;
device_t child; device_t child;
uint16_t ioctl;
} DEFAULT bhnd_bus_null_suspend_hw; } DEFAULT bhnd_bus_null_suspend_hw;
/** /**

View File

@ -98,7 +98,7 @@ bhnd_usb_attach(device_t dev)
sc = device_get_softc(dev); sc = device_get_softc(dev);
bhnd_reset_hw(dev, 0); bhnd_reset_hw(dev, 0, 0);
/* /*
* Allocate the resources which the parent bus has already * Allocate the resources which the parent bus has already

View File

@ -603,8 +603,9 @@ siba_write_ioctl(device_t dev, device_t child, uint16_t value, uint16_t mask)
ts_mask = (mask << SIBA_TML_SICF_SHIFT) & SIBA_TML_SICF_MASK; ts_mask = (mask << SIBA_TML_SICF_SHIFT) & SIBA_TML_SICF_MASK;
ts_low = (value << SIBA_TML_SICF_SHIFT) & ts_mask; ts_low = (value << SIBA_TML_SICF_SHIFT) & ts_mask;
return (siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
ts_low, ts_mask)); ts_low, ts_mask);
return (0);
} }
static bool static bool
@ -626,6 +627,10 @@ siba_is_hw_suspended(device_t dev, device_t child)
if (ts_low & SIBA_TML_RESET) if (ts_low & SIBA_TML_RESET)
return (true); return (true);
/* Is target reject enabled? */
if (ts_low & SIBA_TML_REJ_MASK)
return (true);
/* Is core clocked? */ /* Is core clocked? */
ioctl = SIBA_REG_GET(ts_low, TML_SICF); ioctl = SIBA_REG_GET(ts_low, TML_SICF);
if (!(ioctl & BHND_IOCTL_CLK_EN)) if (!(ioctl & BHND_IOCTL_CLK_EN))
@ -635,11 +640,13 @@ siba_is_hw_suspended(device_t dev, device_t child)
} }
static int static int
siba_reset_hw(device_t dev, device_t child, uint16_t ioctl) siba_reset_hw(device_t dev, device_t child, uint16_t ioctl,
uint16_t reset_ioctl)
{ {
struct siba_devinfo *dinfo; struct siba_devinfo *dinfo;
struct bhnd_resource *r; struct bhnd_resource *r;
uint32_t ts_low, imstate; uint32_t ts_low, imstate;
uint16_t clkflags;
int error; int error;
if (device_get_parent(child) != dev) if (device_get_parent(child) != dev)
@ -651,66 +658,60 @@ siba_reset_hw(device_t dev, device_t child, uint16_t ioctl)
if ((r = dinfo->cfg_res[0]) == NULL) if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV); return (ENODEV);
/* We require exclusive control over BHND_IOCTL_CLK_EN and /* We require exclusive control over BHND_IOCTL_CLK_(EN|FORCE) */
* BHND_IOCTL_CLK_FORCE. */ clkflags = BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE;
if (ioctl & (BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE)) if (ioctl & clkflags)
return (EINVAL); return (EINVAL);
/* Place core into known RESET state */ /* Place core into known RESET state */
if ((error = BHND_BUS_SUSPEND_HW(dev, child))) if ((error = bhnd_suspend_hw(child, reset_ioctl)))
return (error); return (error);
/* Leaving the core in reset, set the caller's IOCTL flags and /* Set RESET, clear REJ, set the caller's IOCTL flags, and
* enable the core's clocks. */ * force clocks to ensure the signal propagates throughout the
ts_low = (ioctl | BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE) << * core. */
SIBA_TML_SICF_SHIFT; ts_low = SIBA_TML_RESET |
error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, (ioctl << SIBA_TML_SICF_SHIFT) |
ts_low, SIBA_TML_SICF_MASK); (BHND_IOCTL_CLK_EN << SIBA_TML_SICF_SHIFT) |
if (error) (BHND_IOCTL_CLK_FORCE << SIBA_TML_SICF_SHIFT);
return (error);
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
ts_low, UINT32_MAX);
/* Clear any target errors */ /* Clear any target errors */
if (bhnd_bus_read_4(r, SIBA_CFG0_TMSTATEHIGH) & SIBA_TMH_SERR) { if (bhnd_bus_read_4(r, SIBA_CFG0_TMSTATEHIGH) & SIBA_TMH_SERR) {
error = siba_write_target_state(child, dinfo, siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATEHIGH,
SIBA_CFG0_TMSTATEHIGH, 0, SIBA_TMH_SERR); 0x0, SIBA_TMH_SERR);
if (error)
return (error);
} }
/* Clear any initiator errors */ /* Clear any initiator errors */
imstate = bhnd_bus_read_4(r, SIBA_CFG0_IMSTATE); imstate = bhnd_bus_read_4(r, SIBA_CFG0_IMSTATE);
if (imstate & (SIBA_IM_IBE|SIBA_IM_TO)) { if (imstate & (SIBA_IM_IBE|SIBA_IM_TO)) {
error = siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, 0x0,
0, SIBA_IM_IBE|SIBA_IM_TO); SIBA_IM_IBE|SIBA_IM_TO);
if (error)
return (error);
} }
/* Release from RESET while leaving clocks forced, ensuring the /* Release from RESET while leaving clocks forced, ensuring the
* signal propagates throughout the core */ * signal propagates throughout the core */
error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0,
0x0, SIBA_TML_RESET); SIBA_TML_RESET);
if (error)
return (error);
/* The core should now be active; we can clear the BHND_IOCTL_CLK_FORCE /* The core should now be active; we can clear the BHND_IOCTL_CLK_FORCE
* bit and allow the core to manage clock gating. */ * bit and allow the core to manage clock gating. */
error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0,
0x0, (BHND_IOCTL_CLK_FORCE << SIBA_TML_SICF_SHIFT)); (BHND_IOCTL_CLK_FORCE << SIBA_TML_SICF_SHIFT));
if (error)
return (error);
return (0); return (0);
} }
static int static int
siba_suspend_hw(device_t dev, device_t child) siba_suspend_hw(device_t dev, device_t child, uint16_t ioctl)
{ {
struct siba_softc *sc; struct siba_softc *sc;
struct siba_devinfo *dinfo; struct siba_devinfo *dinfo;
struct bhnd_resource *r; struct bhnd_resource *r;
uint32_t idl, ts_low; uint32_t idl, ts_low, ts_mask;
uint16_t ioctl; uint16_t cflags, clkflags;
int error; int error;
if (device_get_parent(child) != dev) if (device_get_parent(child) != dev)
@ -723,30 +724,37 @@ siba_suspend_hw(device_t dev, device_t child)
if ((r = dinfo->cfg_res[0]) == NULL) if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV); return (ENODEV);
/* We require exclusive control over BHND_IOCTL_CLK_(EN|FORCE) */
clkflags = BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE;
if (ioctl & clkflags)
return (EINVAL);
/* Already in RESET? */ /* Already in RESET? */
ts_low = bhnd_bus_read_4(r, SIBA_CFG0_TMSTATELOW); ts_low = bhnd_bus_read_4(r, SIBA_CFG0_TMSTATELOW);
if (ts_low & SIBA_TML_RESET) { if (ts_low & SIBA_TML_RESET)
/* Clear IOCTL flags, ensuring the clock is disabled */ return (0);
return (siba_write_target_state(child, dinfo,
SIBA_CFG0_TMSTATELOW, 0x0, SIBA_TML_SICF_MASK));
/* If clocks are already disabled, we can place the core directly
* into RESET|REJ while setting the caller's IOCTL flags. */
cflags = SIBA_REG_GET(ts_low, TML_SICF);
if (!(cflags & BHND_IOCTL_CLK_EN)) {
ts_low = SIBA_TML_RESET | SIBA_TML_REJ |
(ioctl << SIBA_TML_SICF_SHIFT);
ts_mask = SIBA_TML_RESET | SIBA_TML_REJ | SIBA_TML_SICF_MASK;
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
ts_low, ts_mask);
return (0); return (0);
} }
/* If clocks are already disabled, we can put the core directly /* Reject further transactions reaching this core */
* into RESET */ siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
ioctl = SIBA_REG_GET(ts_low, TML_SICF);
if (!(ioctl & BHND_IOCTL_CLK_EN)) {
/* Set RESET and clear IOCTL flags */
return (siba_write_target_state(child, dinfo,
SIBA_CFG0_TMSTATELOW,
SIBA_TML_RESET,
SIBA_TML_RESET | SIBA_TML_SICF_MASK));
}
/* Reject any further target backplane transactions */
error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
SIBA_TML_REJ, SIBA_TML_REJ); SIBA_TML_REJ, SIBA_TML_REJ);
/* Wait for transaction busy flag to clear for all transactions
* initiated by this core */
error = siba_wait_target_state(child, dinfo, SIBA_CFG0_TMSTATEHIGH,
0x0, SIBA_TMH_BUSY, 100000);
if (error) if (error)
return (error); return (error);
@ -754,44 +762,47 @@ siba_suspend_hw(device_t dev, device_t child)
* transactions too. */ * transactions too. */
idl = bhnd_bus_read_4(r, SIBA_CFG0_IDLOW); idl = bhnd_bus_read_4(r, SIBA_CFG0_IDLOW);
if (idl & SIBA_IDL_INIT) { if (idl & SIBA_IDL_INIT) {
error = siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, /* Reject further initiator transactions */
siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE,
SIBA_IM_RJ, SIBA_IM_RJ); SIBA_IM_RJ, SIBA_IM_RJ);
/* Wait for initiator busy flag to clear */
error = siba_wait_target_state(child, dinfo, SIBA_CFG0_IMSTATE,
0x0, SIBA_IM_BY, 100000);
if (error) if (error)
return (error); return (error);
} }
/* Put the core into RESET|REJECT, forcing clocks to ensure the RESET /* Put the core into RESET, set the caller's IOCTL flags, and
* signal propagates throughout the core, leaving REJECT asserted. */ * force clocks to ensure the RESET signal propagates throughout the
ts_low = SIBA_TML_RESET; * core. */
ts_low |= (BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE) << ts_low = SIBA_TML_RESET |
SIBA_TML_SICF_SHIFT; (ioctl << SIBA_TML_SICF_SHIFT) |
(BHND_IOCTL_CLK_EN << SIBA_TML_SICF_SHIFT) |
(BHND_IOCTL_CLK_FORCE << SIBA_TML_SICF_SHIFT);
ts_mask = SIBA_TML_RESET |
SIBA_TML_SICF_MASK;
error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, ts_low,
ts_low, ts_low); ts_mask);
if (error) if (error)
return (error); return (error);
/* Give RESET ample time */ /* Give RESET ample time */
DELAY(10); DELAY(10);
/* Leaving core in reset, disable all clocks, clear REJ flags and
* IOCTL state */
error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW,
SIBA_TML_RESET,
SIBA_TML_RESET | SIBA_TML_REJ | SIBA_TML_SICF_MASK);
if (error)
return (error);
/* Clear previously asserted initiator reject */ /* Clear previously asserted initiator reject */
if (idl & SIBA_IDL_INIT) { if (idl & SIBA_IDL_INIT) {
error = siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, 0x0,
0, SIBA_IM_RJ); SIBA_IM_RJ);
if (error)
return (error);
} }
/* Disable all clocks, leaving RESET and REJ asserted */
siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0,
(BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE) << SIBA_TML_SICF_SHIFT);
/* /*
* Core is now in RESET, with clocks disabled and REJ not asserted. * Core is now in RESET.
* *
* If the core holds any PWRCTL clock reservations, we need to release * If the core holds any PWRCTL clock reservations, we need to release
* those now. This emulates the standard bhnd(4) PMU behavior of RESET * those now. This emulates the standard bhnd(4) PMU behavior of RESET

View File

@ -624,83 +624,73 @@ siba_parse_admatch(uint32_t am, uint32_t *addr, uint32_t *size)
} }
/** /**
* Write @p value to @p dev's CFG0 target/initiator state register and * Write @p value to @p dev's CFG0 target/initiator state register, performing
* wait for completion. * required read-back and waiting for completion.
* *
* @param dev The siba(4) child device. * @param dev The siba(4) child device.
* @param reg The state register to write (e.g. SIBA_CFG0_TMSTATELOW, * @param reg The CFG0 state register to write (e.g. SIBA_CFG0_TMSTATELOW,
* SIBA_CFG0_IMSTATE) * SIBA_CFG0_IMSTATE)
* @param value The value to write to @p reg. * @param value The value to write to @p reg.
* @param mask The mask of bits to be included from @p value. * @param mask The mask of bits to be included from @p value.
*
* @retval 0 success.
* @retval ENODEV if SIBA_CFG0 is not mapped by @p dinfo.
* @retval ETIMEDOUT if a timeout occurs prior to SIBA_TMH_BUSY clearing.
*/ */
int void
siba_write_target_state(device_t dev, struct siba_devinfo *dinfo, siba_write_target_state(device_t dev, struct siba_devinfo *dinfo,
bus_size_t reg, uint32_t value, uint32_t mask) bus_size_t reg, uint32_t value, uint32_t mask)
{ {
struct bhnd_resource *r; struct bhnd_resource *r;
uint32_t rval; uint32_t rval;
/* Must have a CFG0 block */ r = dinfo->cfg_res[0];
if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV);
/* Verify the register offset falls within CFG register block */ KASSERT(r != NULL, ("%s missing CFG0 mapping",
if (reg > SIBA_CFG_SIZE-4) device_get_nameunit(dev)));
return (EFAULT); KASSERT(reg <= SIBA_CFG_SIZE-4, ("%s invalid CFG0 register offset %#jx",
device_get_nameunit(dev), (uintmax_t)reg));
for (int i = 0; i < 300; i += 10) { rval = bhnd_bus_read_4(r, reg);
rval = bhnd_bus_read_4(r, reg); rval &= ~mask;
rval &= ~mask; rval |= (value & mask);
rval |= (value & mask);
bhnd_bus_write_4(r, reg, rval); bhnd_bus_write_4(r, reg, rval);
bhnd_bus_read_4(r, reg); /* read-back */ bhnd_bus_read_4(r, reg); /* read-back */
DELAY(1); DELAY(1);
/* If the write has completed, wait for target busy state
* to clear */
rval = bhnd_bus_read_4(r, reg);
if ((rval & mask) == (value & mask))
return (siba_wait_target_busy(dev, dinfo, 100000));
DELAY(10);
}
return (ETIMEDOUT);
} }
/** /**
* Spin for up to @p usec waiting for SIBA_TMH_BUSY to clear in * Spin for up to @p usec waiting for @p dev's CFG0 target/initiator state
* @p dev's SIBA_CFG0_TMSTATEHIGH register. * register value to be equal to @p value after applying @p mask bits to both
* values.
* *
* @param dev The siba(4) child device to wait on. * @param dev The siba(4) child device to wait on.
* @param dinfo The @p dev's device info * @param dinfo The @p dev's device info
* @param reg The state register to read (e.g. SIBA_CFG0_TMSTATEHIGH,
* SIBA_CFG0_IMSTATE)
* @param value The value against which @p reg will be compared.
* @param mask The mask to be applied when comparing @p value with @p reg.
* @param usec The maximum number of microseconds to wait for completion.
* *
* @retval 0 if SIBA_TMH_BUSY is cleared prior to the @p usec timeout. * @retval 0 if SIBA_TMH_BUSY is cleared prior to the @p usec timeout.
* @retval ENODEV if SIBA_CFG0 is not mapped by @p dinfo. * @retval ENODEV if SIBA_CFG0 is not mapped by @p dinfo.
* @retval ETIMEDOUT if a timeout occurs prior to SIBA_TMH_BUSY clearing. * @retval ETIMEDOUT if a timeout occurs.
*/ */
int int
siba_wait_target_busy(device_t dev, struct siba_devinfo *dinfo, int usec) siba_wait_target_state(device_t dev, struct siba_devinfo *dinfo, bus_size_t reg,
uint32_t value, uint32_t mask, u_int usec)
{ {
struct bhnd_resource *r; struct bhnd_resource *r;
uint32_t ts_high; uint32_t rval;
if ((r = dinfo->cfg_res[0]) == NULL) if ((r = dinfo->cfg_res[0]) == NULL)
return (ENODEV); return (ENODEV);
value &= mask;
for (int i = 0; i < usec; i += 10) { for (int i = 0; i < usec; i += 10) {
ts_high = bhnd_bus_read_4(r, SIBA_CFG0_TMSTATEHIGH); rval = bhnd_bus_read_4(r, reg);
if (!(ts_high & SIBA_TMH_BUSY)) if ((rval & mask) == value)
return (0); return (0);
DELAY(10); DELAY(10);
} }
device_printf(dev, "SIBA_TMH_BUSY wait timeout\n");
return (ETIMEDOUT); return (ETIMEDOUT);
} }

View File

@ -117,11 +117,12 @@ u_int siba_admatch_offset(uint8_t addrspace);
int siba_parse_admatch(uint32_t am, uint32_t *addr, int siba_parse_admatch(uint32_t am, uint32_t *addr,
uint32_t *size); uint32_t *size);
int siba_write_target_state(device_t dev, void siba_write_target_state(device_t dev,
struct siba_devinfo *dinfo, bus_size_t reg, struct siba_devinfo *dinfo, bus_size_t reg,
uint32_t value, uint32_t mask); uint32_t value, uint32_t mask);
int siba_wait_target_busy(device_t child, int siba_wait_target_state(device_t dev,
struct siba_devinfo *dinfo, int usec); struct siba_devinfo *dinfo, bus_size_t reg,
uint32_t value, uint32_t mask, u_int usec);
/* Sonics configuration register blocks */ /* Sonics configuration register blocks */