Index: head/sys/dev/bhnd/bhnd.h =================================================================== --- head/sys/dev/bhnd/bhnd.h (revision 298277) +++ head/sys/dev/bhnd/bhnd.h (revision 298278) @@ -1,606 +1,646 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. * * $FreeBSD$ */ #ifndef _BHND_BHND_H_ #define _BHND_BHND_H_ #include #include #include #include "bhnd_ids.h" #include "bhnd_types.h" #include "bhnd_bus_if.h" extern devclass_t bhnd_devclass; extern devclass_t bhnd_hostb_devclass; extern devclass_t bhnd_nvram_devclass; /** * bhnd child instance variables */ enum bhnd_device_vars { BHND_IVAR_VENDOR, /**< Designer's JEP-106 manufacturer ID. */ BHND_IVAR_DEVICE, /**< Part number */ BHND_IVAR_HWREV, /**< Core revision */ BHND_IVAR_DEVICE_CLASS, /**< Core class (@sa bhnd_devclass_t) */ BHND_IVAR_VENDOR_NAME, /**< Core vendor name */ BHND_IVAR_DEVICE_NAME, /**< Core name */ BHND_IVAR_CORE_INDEX, /**< Bus-assigned core number */ BHND_IVAR_CORE_UNIT, /**< Bus-assigned core unit number, assigned sequentially (starting at 0) for each vendor/device pair. */ }; /** * bhnd device probe priority bands. */ enum { BHND_PROBE_ROOT = 0, /**< Nexus or host bridge */ BHND_PROBE_BUS = 1000, /**< Busses and bridges */ BHND_PROBE_CPU = 2000, /**< CPU devices */ BHND_PROBE_INTERRUPT = 3000, /**< Interrupt controllers. */ BHND_PROBE_TIMER = 4000, /**< Timers and clocks. */ BHND_PROBE_RESOURCE = 5000, /**< Resource discovery (including NVRAM/SPROM) */ BHND_PROBE_DEFAULT = 6000, /**< Default device priority */ }; /** * Constants defining fine grained ordering within a BHND_PROBE_* priority band. * * Example: * @code * BHND_PROBE_BUS + BHND_PROBE_ORDER_FIRST * @endcode */ enum { BHND_PROBE_ORDER_FIRST = 0, BHND_PROBE_ORDER_EARLY = 25, BHND_PROBE_ORDER_MIDDLE = 50, BHND_PROBE_ORDER_LATE = 75, BHND_PROBE_ORDER_LAST = 100 }; /* * Simplified accessors for bhnd device ivars */ #define BHND_ACCESSOR(var, ivar, type) \ __BUS_ACCESSOR(bhnd, var, BHND, ivar, type) BHND_ACCESSOR(vendor, VENDOR, uint16_t); BHND_ACCESSOR(device, DEVICE, uint16_t); BHND_ACCESSOR(hwrev, HWREV, uint8_t); BHND_ACCESSOR(class, DEVICE_CLASS, bhnd_devclass_t); BHND_ACCESSOR(vendor_name, VENDOR_NAME, const char *); BHND_ACCESSOR(device_name, DEVICE_NAME, const char *); BHND_ACCESSOR(core_index, CORE_INDEX, u_int); BHND_ACCESSOR(core_unit, CORE_UNIT, int); #undef BHND_ACCESSOR /** * Chip Identification * * This is read from the ChipCommon ID register; on earlier bhnd(4) devices * where ChipCommon is unavailable, known values must be supplied. */ struct bhnd_chipid { uint16_t chip_id; /**< chip id (BHND_CHIPID_*) */ uint8_t chip_rev; /**< chip revision */ uint8_t chip_pkg; /**< chip package (BHND_PKGID_*) */ uint8_t chip_type; /**< chip type (BHND_CHIPTYPE_*) */ bhnd_addr_t enum_addr; /**< chip_type-specific enumeration * address; either the siba(4) base * core register block, or the bcma(4) * EROM core address. */ uint8_t ncores; /**< number of cores, if known. 0 if * not available. */ }; /** * A bhnd(4) bus resource. * * This provides an abstract interface to per-core resources that may require * bus-level remapping of address windows prior to access. */ struct bhnd_resource { struct resource *res; /**< the system resource. */ bool direct; /**< false if the resource requires * bus window remapping before it * is MMIO accessible. */ }; /** * A bhnd(4) core descriptor. */ struct bhnd_core_info { uint16_t vendor; /**< vendor */ uint16_t device; /**< device */ uint16_t hwrev; /**< hardware revision */ u_int core_idx; /**< bus-assigned core index */ int unit; /**< bus-assigned core unit */ }; /** * A hardware revision match descriptor. */ struct bhnd_hwrev_match { uint16_t start; /**< first revision, or BHND_HWREV_INVALID to match on any revision. */ uint16_t end; /**< last revision, or BHND_HWREV_INVALID to match on any revision. */ }; /** * Wildcard hardware revision match descriptor. */ -#define BHND_HWREV_MATCH_ANY { BHND_HWREV_INVALID, BHND_HWREV_INVALID } +#define BHND_HWREV_ANY { BHND_HWREV_INVALID, BHND_HWREV_INVALID } +#define BHND_HWREV_IS_ANY(_m) \ + ((_m)->start == BHND_HWREV_INVALID && (_m)->end == BHND_HWREV_INVALID) - -/** A core match descriptor. */ -struct bhnd_core_match { - uint16_t vendor; /**< required JEP106 device vendor or BHND_MFGID_INVALID. */ - uint16_t device; /**< required core ID or BHND_COREID_INVALID */ - struct bhnd_hwrev_match hwrev; /**< matching revisions. */ - bhnd_devclass_t class; /**< required class or BHND_DEVCLASS_INVALID */ - int unit; /**< required core unit, or -1 */ -}; - - /** - * Revision-specific hardware quirk descriptor. + * Hardware revision match descriptor for an inclusive range. * - * Defines a set of quirk flags applicable to a range of hardware - * revisions. - */ -struct bhnd_device_quirk { - struct bhnd_hwrev_match hwrev; /**< applicable hardware revisions */ - uint32_t quirks; /**< applicable quirk flags */ -}; - -/** - * Define a bhnd_device_quirk over a range of hardware revisions. - * * @param _start The first applicable hardware revision. * @param _end The last applicable hardware revision, or BHND_HWREV_INVALID * to match on any revision. - * @param _quirks Quirk flags applicable to this revision range. */ -#define BHND_QUIRK_HWREV_RANGE(_start, _end, _quirks) \ - { .hwrev = { _start, _end }, .quirks = _quirks } +#define BHND_HWREV_RANGE(_start, _end) { _start, _end } /** - * Define a bhnd_device_quirk for a specific hardware revision. + * Hardware revision match descriptor for a single revision. * * @param _hwrev The hardware revision to match on. - * @param _quirks Quirk flags applicable to this revision. */ -#define BHND_QUIRK_HWREV_EQ(_hwrev, _quirks) \ - BHND_QUIRK_HWREV_RANGE(_hwrev, _hwrev, _quirks) +#define BHND_HWREV_EQ(_hwrev) BHND_HWREV_RANGE(_hwrev, _hwrev) /** - * Define a bhnd_device_quirk for any hardware revision equal or greater + * Hardware revision match descriptor for any revision equal to or greater * than @p _start. * * @param _start The first hardware revision to match on. - * @param _quirks Quirk flags applicable to this revision. */ -#define BHND_QUIRK_HWREV_GTE(_start, _quirks) \ - BHND_QUIRK_HWREV_RANGE(_start, BHND_HWREV_INVALID, _quirks) +#define BHND_HWREV_GTE(_start) BHND_HWREV_RANGE(_start, BHND_HWREV_INVALID) /** - * Define a bhnd_device_quirk for any hardware revision equal or less + * Hardware revision match descriptor for any revision equal to or less * than @p _end. * * @param _end The last hardware revision to match on. - * @param _quirks Quirk flags applicable to this revision. */ -#define BHND_QUIRK_HWREV_LTE(_end, _quirks) \ - BHND_QUIRK_HWREV_RANGE(0, _end, _quirks) +#define BHND_HWREV_LTE(_end) BHND_HWREV_RANGE(0, _end) -/** Mark the end of a bhnd_device_quirk table. */ -#define BHND_QUIRK_HWREV_END { BHND_HWREV_MATCH_ANY, 0 } +/** A core match descriptor. */ +struct bhnd_core_match { + uint16_t vendor; /**< required JEP106 device vendor or BHND_MFGID_INVALID. */ + uint16_t device; /**< required core ID or BHND_COREID_INVALID */ + struct bhnd_hwrev_match hwrev; /**< matching revisions. */ + bhnd_devclass_t class; /**< required class or BHND_DEVCLASS_INVALID */ + int unit; /**< required core unit, or -1 */ +}; + +/** + * Core match descriptor matching against the given @p _vendor, @p _device, + * and @p _hwrev match descriptors. + */ +#define BHND_CORE_MATCH(_vendor, _device, _hwrev) \ + { _vendor, _device, _hwrev, BHND_DEVCLASS_INVALID, -1 } + +/** + * Wildcard core match descriptor. + */ +#define BHND_CORE_MATCH_ANY \ + { \ + .vendor = BHND_MFGID_INVALID, \ + .device = BHND_COREID_INVALID, \ + .hwrev = BHND_HWREV_ANY, \ + .class = BHND_DEVCLASS_INVALID, \ + .unit = -1 \ + } + +/** + * Device quirk table descriptor. + */ +struct bhnd_device_quirk { + struct bhnd_hwrev_match hwrev; /**< applicable hardware revisions */ + uint32_t quirks; /**< quirk flags */ +}; +#define BHND_DEVICE_QUIRK_END { BHND_HWREV_ANY, 0 } +#define BHND_DEVICE_QUIRK_IS_END(_q) \ + (BHND_HWREV_IS_ANY(&(_q)->hwrev) && (_q)->quirks == 0) + +enum { + BHND_DF_ANY = 0, + BHND_DF_HOSTB = (1<<0) /**< core is serving as the bus' + * host bridge */ +}; + +/** Device probe table descriptor */ +struct bhnd_device { + const struct bhnd_core_match core; /**< core match descriptor */ + const char *desc; /**< device description, or NULL. */ + const struct bhnd_device_quirk *quirks_table; /**< quirks table for this device, or NULL */ + uint32_t device_flags; /**< required BHND_DF_* flags */ +}; + +#define _BHND_DEVICE(_device, _desc, _quirks, _flags, ...) \ + { BHND_CORE_MATCH(BHND_MFGID_BCM, BHND_COREID_ ## _device, \ + BHND_HWREV_ANY), _desc, _quirks, _flags } + +#define BHND_DEVICE(_device, _desc, _quirks, ...) \ + _BHND_DEVICE(_device, _desc, _quirks, ## __VA_ARGS__, 0) + +#define BHND_DEVICE_END { BHND_CORE_MATCH_ANY, NULL, NULL, 0 } + const char *bhnd_vendor_name(uint16_t vendor); const char *bhnd_port_type_name(bhnd_port_type port_type); const char *bhnd_find_core_name(uint16_t vendor, uint16_t device); bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device); const char *bhnd_core_name(const struct bhnd_core_info *ci); bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci); device_t bhnd_match_child(device_t dev, const struct bhnd_core_match *desc); device_t bhnd_find_child(device_t dev, bhnd_devclass_t class, int unit); const struct bhnd_core_info *bhnd_match_core( const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc); const struct bhnd_core_info *bhnd_find_core( const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class); bool bhnd_core_matches( const struct bhnd_core_info *core, const struct bhnd_core_match *desc); bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc); bool bhnd_device_matches(device_t dev, const struct bhnd_core_match *desc); +const struct bhnd_device *bhnd_device_lookup(device_t dev, + const struct bhnd_device *table, + size_t entry_size); + +uint32_t bhnd_device_quirks(device_t dev, + const struct bhnd_device *table, + size_t entry_size); + struct bhnd_core_info bhnd_get_core_info(device_t dev); int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res); void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res); struct bhnd_chipid bhnd_parse_chipid(uint32_t idreg, bhnd_addr_t enum_addr); int bhnd_read_chipid(device_t dev, struct resource_spec *rs, bus_size_t chipc_offset, struct bhnd_chipid *result); -void bhnd_set_generic_core_desc(device_t dev); - - +void bhnd_set_custom_core_desc(device_t dev, + const char *name); +void bhnd_set_default_core_desc(device_t dev); bool bhnd_bus_generic_is_hostb_device(device_t dev, device_t child); bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child); bool bhnd_bus_generic_is_region_valid(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region); int bhnd_bus_generic_read_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size); const struct bhnd_chipid *bhnd_bus_generic_get_chipid(device_t dev, device_t child); struct bhnd_resource *bhnd_bus_generic_alloc_resource (device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); int bhnd_bus_generic_release_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); int bhnd_bus_generic_activate_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); int bhnd_bus_generic_deactivate_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); /** * Return true if @p dev is serving as a host bridge for its parent bhnd * bus. * * @param dev A bhnd bus child device. */ static inline bool bhnd_is_hostb_device(device_t dev) { return (BHND_BUS_IS_HOSTB_DEVICE(device_get_parent(dev), dev)); } /** * Return true if the hardware components required by @p dev are known to be * unpopulated or otherwise unusable. * * In some cases, enumerated devices may have pins that are left floating, or * the hardware may otherwise be non-functional; this method allows a parent * device to explicitly specify if a successfully enumerated @p dev should * be disabled. * * @param dev A bhnd bus child device. */ static inline bool bhnd_is_hw_disabled(device_t dev) { return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), dev)); } /** * Allocate a resource from a device's parent bhnd(4) bus. * * @param dev The device requesting resource ownership. * @param type The type of resource to allocate. This may be any type supported * by the standard bus APIs. * @param rid The bus-specific handle identifying the resource being allocated. * @param start The start address of the resource. * @param end The end address of the resource. * @param count The size of the resource. * @param flags The flags for the resource to be allocated. These may be any * values supported by the standard bus APIs. * * To request the resource's default addresses, pass @p start and * @p end values of @c 0 and @c ~0, respectively, and * a @p count of @c 1. * * @retval NULL The resource could not be allocated. * @retval resource The allocated resource. */ static inline struct bhnd_resource * bhnd_alloc_resource(device_t dev, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { return BHND_BUS_ALLOC_RESOURCE(device_get_parent(dev), dev, type, rid, start, end, count, flags); } /** * Allocate a resource from a device's parent bhnd(4) bus, using the * resource's default start, end, and count values. * * @param dev The device requesting resource ownership. * @param type The type of resource to allocate. This may be any type supported * by the standard bus APIs. * @param rid The bus-specific handle identifying the resource being allocated. * @param flags The flags for the resource to be allocated. These may be any * values supported by the standard bus APIs. * * @retval NULL The resource could not be allocated. * @retval resource The allocated resource. */ static inline struct bhnd_resource * bhnd_alloc_resource_any(device_t dev, int type, int *rid, u_int flags) { return bhnd_alloc_resource(dev, type, rid, 0, ~0, 1, flags); } /** * Activate a previously allocated bhnd resource. * * @param dev The device holding ownership of the allocated resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_BUS_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occured while activating the resource. */ static inline int bhnd_activate_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Deactivate a previously activated bhnd resource. * * @param dev The device holding ownership of the activated resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_BUS_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occured while activating the resource. */ static inline int bhnd_deactivate_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Free a resource allocated by bhnd_alloc_resource(). * * @param dev The device holding ownership of the resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occured while activating the resource. */ static inline int bhnd_release_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_RELEASE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Return true if @p region_num is a valid region on @p port_num of * @p type attached to @p dev. * * @param dev A bhnd bus child device. * @param type The port type being queried. * @param port_num The port number being queried. * @param region_num The region number being queried. */ static inline bool bhnd_is_region_valid(device_t dev, bhnd_port_type type, u_int port_num, u_int region_num) { return (BHND_BUS_IS_REGION_VALID(device_get_parent(dev), dev, type, port_num, region_num)); } /** * Return the number of ports of type @p type attached to @p def. * * @param dev A bhnd bus child device. * @param type The port type being queried. */ static inline u_int bhnd_get_port_count(device_t dev, bhnd_port_type type) { return (BHND_BUS_GET_PORT_COUNT(device_get_parent(dev), dev, type)); } /** * Return the number of memory regions mapped to @p child @p port of * type @p type. * * @param dev A bhnd bus child device. * @param port The port number being queried. * @param type The port type being queried. */ static inline u_int bhnd_get_region_count(device_t dev, bhnd_port_type type, u_int port) { return (BHND_BUS_GET_REGION_COUNT(device_get_parent(dev), dev, type, port)); } /** * Return the resource-ID for a memory region on the given device port. * * @param dev A bhnd bus child device. * @param type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * * @retval int The RID for the given @p port and @p region on @p device. * @retval -1 No such port/region found. */ static inline int bhnd_get_port_rid(device_t dev, bhnd_port_type type, u_int port, u_int region) { return BHND_BUS_GET_PORT_RID(device_get_parent(dev), dev, type, port, region); } /** * Decode a port / region pair on @p dev defined by @p rid. * * @param dev A bhnd bus child device. * @param type The resource type. * @param rid The resource identifier. * @param[out] port_type The decoded port type. * @param[out] port The decoded port identifier. * @param[out] region The decoded region identifier. * * @retval 0 success * @retval non-zero No matching port/region found. */ static inline int bhnd_decode_port_rid(device_t dev, int type, int rid, bhnd_port_type *port_type, u_int *port, u_int *region) { return BHND_BUS_DECODE_PORT_RID(device_get_parent(dev), dev, type, rid, port_type, port, region); } /** * Get the address and size of @p region on @p port. * * @param dev A bhnd bus child device. * @param port_type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * @param[out] region_addr The region's base address. * @param[out] region_size The region's size. * * @retval 0 success * @retval non-zero No matching port/region found. */ static inline int bhnd_get_region_addr(device_t dev, bhnd_port_type port_type, u_int port, u_int region, bhnd_addr_t *region_addr, bhnd_size_t *region_size) { return BHND_BUS_GET_REGION_ADDR(device_get_parent(dev), dev, port_type, port, region, region_addr, region_size); } /* * bhnd bus-level equivalents of the bus_(read|write|set|barrier|...) * macros (compatible with bhnd_resource). * * Generated with bhnd/tools/bus_macro.sh */ #define bhnd_bus_barrier(r, o, l, f) \ ((r)->direct) ? \ bus_barrier((r)->res, (o), (l), (f)) : \ BHND_BUS_BARRIER(device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (l), (f)) #define bhnd_bus_read_1(r, o) \ ((r)->direct) ? \ bus_read_1((r)->res, (o)) : \ BHND_BUS_READ_1(device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_write_1(r, o, v) \ ((r)->direct) ? \ bus_write_1((r)->res, (o), (v)) : \ BHND_BUS_WRITE_1(device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_read_2(r, o) \ ((r)->direct) ? \ bus_read_2((r)->res, (o)) : \ BHND_BUS_READ_2(device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_write_2(r, o, v) \ ((r)->direct) ? \ bus_write_2((r)->res, (o), (v)) : \ BHND_BUS_WRITE_2(device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_read_4(r, o) \ ((r)->direct) ? \ bus_read_4((r)->res, (o)) : \ BHND_BUS_READ_4(device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_write_4(r, o, v) \ ((r)->direct) ? \ bus_write_4((r)->res, (o), (v)) : \ BHND_BUS_WRITE_4(device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #endif /* _BHND_BHND_H_ */ Index: head/sys/dev/bhnd/bhnd_subr.c =================================================================== --- head/sys/dev/bhnd/bhnd_subr.c (revision 298277) +++ head/sys/dev/bhnd/bhnd_subr.c (revision 298278) @@ -1,805 +1,892 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include "bhndreg.h" #include "bhndvar.h" /* BHND core device description table. */ static const struct bhnd_core_desc { uint16_t vendor; uint16_t device; bhnd_devclass_t class; const char *desc; } bhnd_core_descs[] = { #define BHND_CDESC(_mfg, _cid, _cls, _desc) \ { BHND_MFGID_ ## _mfg, BHND_COREID_ ## _cid, \ BHND_DEVCLASS_ ## _cls, _desc } BHND_CDESC(BCM, CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, ILINE20, OTHER, "iLine20 HPNA"), BHND_CDESC(BCM, SRAM, RAM, "SRAM"), BHND_CDESC(BCM, SDRAM, RAM, "SDRAM"), BHND_CDESC(BCM, PCI, PCI, "PCI Bridge"), BHND_CDESC(BCM, MIPS, CPU, "MIPS Core"), BHND_CDESC(BCM, ENET, ENET_MAC, "Fast Ethernet MAC"), BHND_CDESC(BCM, CODEC, OTHER, "V.90 Modem Codec"), BHND_CDESC(BCM, USB, OTHER, "USB 1.1 Device/Host Controller"), BHND_CDESC(BCM, ADSL, OTHER, "ADSL Core"), BHND_CDESC(BCM, ILINE100, OTHER, "iLine100 HPNA"), BHND_CDESC(BCM, IPSEC, OTHER, "IPsec Accelerator"), BHND_CDESC(BCM, UTOPIA, OTHER, "UTOPIA ATM Core"), BHND_CDESC(BCM, PCMCIA, PCCARD, "PCMCIA Bridge"), BHND_CDESC(BCM, SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, MEMC, MEMC, "MEMC SDRAM Controller"), BHND_CDESC(BCM, OFDM, OTHER, "OFDM PHY"), BHND_CDESC(BCM, EXTIF, OTHER, "External Interface"), BHND_CDESC(BCM, D11, WLAN, "802.11 MAC/PHY/Radio"), BHND_CDESC(BCM, APHY, WLAN_PHY, "802.11a PHY"), BHND_CDESC(BCM, BPHY, WLAN_PHY, "802.11b PHY"), BHND_CDESC(BCM, GPHY, WLAN_PHY, "802.11g PHY"), BHND_CDESC(BCM, MIPS33, CPU, "MIPS 3302 Core"), BHND_CDESC(BCM, USB11H, OTHER, "USB 1.1 Host Controller"), BHND_CDESC(BCM, USB11D, OTHER, "USB 1.1 Device Core"), BHND_CDESC(BCM, USB20H, OTHER, "USB 2.0 Host Controller"), BHND_CDESC(BCM, USB20D, OTHER, "USB 2.0 Device Core"), BHND_CDESC(BCM, SDIOH, OTHER, "SDIO Host Controller"), BHND_CDESC(BCM, ROBO, OTHER, "RoboSwitch"), BHND_CDESC(BCM, ATA100, OTHER, "Parallel ATA Controller"), BHND_CDESC(BCM, SATAXOR, OTHER, "SATA DMA/XOR Controller"), BHND_CDESC(BCM, GIGETH, ENET_MAC, "Gigabit Ethernet MAC"), BHND_CDESC(BCM, PCIE, PCIE, "PCIe Bridge"), BHND_CDESC(BCM, NPHY, WLAN_PHY, "802.11n 2x2 PHY"), BHND_CDESC(BCM, SRAMC, MEMC, "SRAM Controller"), BHND_CDESC(BCM, MINIMAC, OTHER, "MINI MAC/PHY"), BHND_CDESC(BCM, ARM11, CPU, "ARM1176 CPU"), BHND_CDESC(BCM, ARM7S, CPU, "ARM7TDMI-S CPU"), BHND_CDESC(BCM, LPPHY, WLAN_PHY, "802.11a/b/g PHY"), BHND_CDESC(BCM, PMU, PMU, "PMU"), BHND_CDESC(BCM, SSNPHY, WLAN_PHY, "802.11n Single-Stream PHY"), BHND_CDESC(BCM, SDIOD, OTHER, "SDIO Device Core"), BHND_CDESC(BCM, ARMCM3, CPU, "ARM Cortex-M3 CPU"), BHND_CDESC(BCM, HTPHY, WLAN_PHY, "802.11n 4x4 PHY"), BHND_CDESC(BCM, MIPS74K, CPU, "MIPS74k CPU"), BHND_CDESC(BCM, GMAC, ENET_MAC, "Gigabit MAC core"), BHND_CDESC(BCM, DMEMC, MEMC, "DDR1/DDR2 Memory Controller"), BHND_CDESC(BCM, PCIERC, OTHER, "PCIe Root Complex"), BHND_CDESC(BCM, OCP, SOC_BRIDGE, "OCP to OCP Bridge"), BHND_CDESC(BCM, SC, OTHER, "Shared Common Core"), BHND_CDESC(BCM, AHB, SOC_BRIDGE, "OCP to AHB Bridge"), BHND_CDESC(BCM, SPIH, OTHER, "SPI Host Controller"), BHND_CDESC(BCM, I2S, OTHER, "I2S Digital Audio Interface"), BHND_CDESC(BCM, DMEMS, MEMC, "SDR/DDR1 Memory Controller"), BHND_CDESC(BCM, UBUS_SHIM, OTHER, "BCM6362/UBUS WLAN SHIM"), BHND_CDESC(BCM, PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(ARM, APB_BRIDGE, SOC_BRIDGE, "BP135 AMBA3 AXI to APB Bridge"), BHND_CDESC(ARM, PL301, SOC_ROUTER, "PL301 AMBA3 Interconnect"), BHND_CDESC(ARM, EROM, EROM, "PL366 Device Enumeration ROM"), BHND_CDESC(ARM, OOB_ROUTER, OTHER, "PL367 OOB Interrupt Router"), BHND_CDESC(ARM, AXI_UNMAPPED, OTHER, "Unmapped Address Ranges"), BHND_CDESC(BCM, 4706_CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, NS_PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(BCM, NS_DMA, OTHER, "DMA engine"), BHND_CDESC(BCM, NS_SDIO, OTHER, "SDIO 3.0 Host Controller"), BHND_CDESC(BCM, NS_USB20H, OTHER, "USB 2.0 Host Controller"), BHND_CDESC(BCM, NS_USB30H, OTHER, "USB 3.0 Host Controller"), BHND_CDESC(BCM, NS_A9JTAG, OTHER, "ARM Cortex A9 JTAG Interface"), BHND_CDESC(BCM, NS_DDR23_MEMC, MEMC, "Denali DDR2/DD3 Memory Controller"), BHND_CDESC(BCM, NS_ROM, NVRAM, "System ROM"), BHND_CDESC(BCM, NS_NAND, NVRAM, "NAND Flash Controller"), BHND_CDESC(BCM, NS_QSPI, NVRAM, "QSPI Flash Controller"), BHND_CDESC(BCM, NS_CC_B, CC_B, "ChipCommon B Auxiliary I/O Controller"), BHND_CDESC(BCM, 4706_SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, IHOST_ARMCA9, CPU, "ARM Cortex A9 CPU"), BHND_CDESC(BCM, 4706_GMAC_CMN, ENET, "Gigabit MAC (Common)"), BHND_CDESC(BCM, 4706_GMAC, ENET_MAC, "Gigabit MAC"), BHND_CDESC(BCM, AMEMC, MEMC, "Denali DDR1/DDR2 Memory Controller"), #undef BHND_CDESC /* Derived from inspection of the BCM4331 cores that provide PrimeCell * IDs. Due to lack of documentation, the surmised device name/purpose * provided here may be incorrect. */ { BHND_MFGID_ARM, BHND_PRIMEID_EROM, BHND_DEVCLASS_OTHER, "PL364 Device Enumeration ROM" }, { BHND_MFGID_ARM, BHND_PRIMEID_SWRAP, BHND_DEVCLASS_OTHER, "PL368 Device Management Interface" }, { BHND_MFGID_ARM, BHND_PRIMEID_MWRAP, BHND_DEVCLASS_OTHER, "PL369 Device Management Interface" }, { 0, 0, 0, NULL } }; /** * Return the name for a given JEP106 manufacturer ID. * * @param vendor A JEP106 Manufacturer ID, including the non-standard ARM 4-bit * JEP106 continuation code. */ const char * bhnd_vendor_name(uint16_t vendor) { switch (vendor) { case BHND_MFGID_ARM: return "ARM"; case BHND_MFGID_BCM: return "Broadcom"; case BHND_MFGID_MIPS: return "MIPS"; default: return "unknown"; } } /** * Return the name of a port type. */ const char * bhnd_port_type_name(bhnd_port_type port_type) { switch (port_type) { case BHND_PORT_DEVICE: return ("device"); case BHND_PORT_BRIDGE: return ("bridge"); case BHND_PORT_AGENT: return ("agent"); } } static const struct bhnd_core_desc * bhnd_find_core_desc(uint16_t vendor, uint16_t device) { for (u_int i = 0; bhnd_core_descs[i].desc != NULL; i++) { if (bhnd_core_descs[i].vendor != vendor) continue; if (bhnd_core_descs[i].device != device) continue; return (&bhnd_core_descs[i]); } return (NULL); } /** * Return a human-readable name for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID * @param device The core identifier. */ const char * bhnd_find_core_name(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return ("unknown"); return desc->desc; } /** * Return the device class for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID * @param device The core identifier. */ bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return (BHND_DEVCLASS_OTHER); return desc->class; } /** * Return a human-readable name for a BHND core. * * @param ci The core's info record. */ const char * bhnd_core_name(const struct bhnd_core_info *ci) { return bhnd_find_core_name(ci->vendor, ci->device); } /** * Return the device class for a BHND core. * * @param ci The core's info record. */ bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci) { return bhnd_find_core_class(ci->vendor, ci->device); } /** * Initialize a core info record with data from from a bhnd-attached @p dev. * * @param dev A bhnd device. * @param core The record to be initialized. */ struct bhnd_core_info bhnd_get_core_info(device_t dev) { return (struct bhnd_core_info) { .vendor = bhnd_get_vendor(dev), .device = bhnd_get_device(dev), .hwrev = bhnd_get_hwrev(dev), .core_idx = bhnd_get_core_index(dev), .unit = bhnd_get_core_unit(dev) }; } /** * Find a @p class child device with @p unit on @p dev. * * @param parent The bhnd-compatible bus to be searched. * @param class The device class to match on. * @param unit The device unit number; specify -1 to return the first match * regardless of unit number. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_find_child(device_t dev, bhnd_devclass_t class, int unit) { struct bhnd_core_match md = { .vendor = BHND_MFGID_INVALID, .device = BHND_COREID_INVALID, .hwrev.start = BHND_HWREV_INVALID, .hwrev.end = BHND_HWREV_INVALID, .class = class, .unit = unit }; return bhnd_match_child(dev, &md); } /** * Find the first child device on @p dev that matches @p desc. * * @param parent The bhnd-compatible bus to be searched. * @param desc A match descriptor. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_match_child(device_t dev, const struct bhnd_core_match *desc) { device_t *devlistp; device_t match; int devcnt; int error; error = device_get_children(dev, &devlistp, &devcnt); if (error != 0) return (NULL); match = NULL; for (int i = 0; i < devcnt; i++) { device_t dev = devlistp[i]; if (bhnd_device_matches(dev, desc)) { match = dev; goto done; } } done: free(devlistp, M_TEMP); return match; } /** * Find the first core in @p cores that matches @p desc. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_match_core(const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc) { for (u_int i = 0; i < num_cores; i++) { if (bhnd_core_matches(&cores[i], desc)) return &cores[i]; } return (NULL); } /** * Find the first core in @p cores with the given @p class. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_find_core(const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class) { struct bhnd_core_match md = { .vendor = BHND_MFGID_INVALID, .device = BHND_COREID_INVALID, .hwrev.start = BHND_HWREV_INVALID, .hwrev.end = BHND_HWREV_INVALID, .class = class, .unit = -1 }; return bhnd_match_core(cores, num_cores, &md); } /** * Return true if the @p core matches @p desc. * * @param core A bhnd core descriptor. * @param desc A match descriptor to compare against @p core. * * @retval true if @p core matches @p match * @retval false if @p core does not match @p match. */ bool bhnd_core_matches(const struct bhnd_core_info *core, const struct bhnd_core_match *desc) { if (desc->vendor != BHND_MFGID_INVALID && desc->vendor != core->vendor) return (false); if (desc->device != BHND_COREID_INVALID && desc->device != core->device) return (false); if (desc->unit != -1 && desc->unit != core->unit) return (false); if (!bhnd_hwrev_matches(core->hwrev, &desc->hwrev)) return (false); if (desc->hwrev.end != BHND_HWREV_INVALID && desc->hwrev.end < core->hwrev) return (false); if (desc->class != BHND_DEVCLASS_INVALID && desc->class != bhnd_core_class(core)) return (false); return true; } /** * Return true if the @p hwrev matches @p desc. * * @param hwrev A bhnd hardware revision. * @param desc A match descriptor to compare against @p core. * * @retval true if @p hwrev matches @p match * @retval false if @p hwrev does not match @p match. */ bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc) { if (desc->start != BHND_HWREV_INVALID && desc->start > hwrev) return false; if (desc->end != BHND_HWREV_INVALID && desc->end < hwrev) return false; return true; } /** * Return true if the @p dev matches @p desc. * * @param dev A bhnd device. * @param desc A match descriptor to compare against @p dev. * * @retval true if @p dev matches @p match * @retval false if @p dev does not match @p match. */ bool bhnd_device_matches(device_t dev, const struct bhnd_core_match *desc) { struct bhnd_core_info ci = { .vendor = bhnd_get_vendor(dev), .device = bhnd_get_device(dev), .unit = bhnd_get_core_unit(dev), .hwrev = bhnd_get_hwrev(dev) }; return bhnd_core_matches(&ci, desc); } /** + * Search @p table for an entry matching @p dev. + * + * @param dev A bhnd device to match against @p table. + * @param table The device table to search. + * @param entry_size The @p table entry size, in bytes. + * + * @retval bhnd_device the first matching device, if any. + * @retval NULL if no matching device is found in @p table. + */ +const struct bhnd_device * +bhnd_device_lookup(device_t dev, const struct bhnd_device *table, + size_t entry_size) +{ + const struct bhnd_device *entry; + + for (entry = table; entry->desc != NULL; entry = + (const struct bhnd_device *) ((const char *) entry + entry_size)) + { + /* match core info */ + if (!bhnd_device_matches(dev, &entry->core)) + continue; + + /* match device flags */ + if (entry->device_flags & BHND_DF_HOSTB) { + if (!bhnd_is_hostb_device(dev)) + continue; + } + + /* device found */ + return (entry); + } + + /* not found */ + return (NULL); +} + +/** + * Scan @p table for all quirk flags applicable to @p dev. + * + * @param dev A bhnd device to match against @p table. + * @param table The device table to search. + * @param entry_size The @p table entry size, in bytes. + * + * @return returns all matching quirk flags. + */ +uint32_t +bhnd_device_quirks(device_t dev, const struct bhnd_device *table, + size_t entry_size) +{ + const struct bhnd_device *dent; + const struct bhnd_device_quirk *qtable, *qent; + uint32_t quirks; + uint16_t hwrev; + + hwrev = bhnd_get_hwrev(dev); + quirks = 0; + + /* Find the quirk table */ + if ((dent = bhnd_device_lookup(dev, table, entry_size)) == NULL) { + /* This is almost certainly a (caller) implementation bug */ + device_printf(dev, "quirk lookup did not match any device\n"); + return (0); + } + + /* Quirks aren't a mandatory field */ + if ((qtable = dent->quirks_table) == NULL) + return (0); + + /* Collect matching quirk entries */ + for (qent = qtable; !BHND_DEVICE_QUIRK_IS_END(qent); qent++) { + if (bhnd_hwrev_matches(hwrev, &qent->hwrev)) + quirks |= qent->quirks; + } + + return (quirks); +} + + +/** * Allocate bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device requesting ownership of the resources. * @param rs A standard bus resource specification. This will be updated * with the allocated resource's RIDs. * @param res On success, the allocated bhnd resources. * * @retval 0 success * @retval non-zero if allocation of any non-RF_OPTIONAL resource fails, * all allocated resources will be released and a regular * unix error code will be returned. */ int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res) { /* Initialize output array */ for (u_int i = 0; rs[i].type != -1; i++) res[i] = NULL; for (u_int i = 0; rs[i].type != -1; i++) { res[i] = bhnd_alloc_resource_any(dev, rs[i].type, &rs[i].rid, rs[i].flags); /* Clean up all allocations on failure */ if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) { bhnd_release_resources(dev, rs, res); return (ENXIO); } } return (0); }; /** * Release bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device that owns the resources. * @param rs A standard bus resource specification previously initialized * by @p bhnd_alloc_resources. * @param res The bhnd resources to be released. */ void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res) { for (u_int i = 0; rs[i].type != -1; i++) { if (res[i] == NULL) continue; bhnd_release_resource(dev, rs[i].type, rs[i].rid, res[i]); res[i] = NULL; } } /** * Parse the CHIPC_ID_* fields from the ChipCommon CHIPC_ID * register, returning its bhnd_chipid representation. * * @param idreg The CHIPC_ID register value. * @param enum_addr The enumeration address to include in the result. * * @warning * On early siba(4) devices, the ChipCommon core does not provide * a valid CHIPC_ID_NUMCORE field. On these ChipCommon revisions * (see CHIPC_NCORES_MIN_HWREV()), this function will parse and return * an invalid `ncores` value. */ struct bhnd_chipid bhnd_parse_chipid(uint32_t idreg, bhnd_addr_t enum_addr) { struct bhnd_chipid result; /* Fetch the basic chip info */ result.chip_id = CHIPC_GET_ATTR(idreg, ID_CHIP); result.chip_pkg = CHIPC_GET_ATTR(idreg, ID_PKG); result.chip_rev = CHIPC_GET_ATTR(idreg, ID_REV); result.chip_type = CHIPC_GET_ATTR(idreg, ID_BUS); result.ncores = CHIPC_GET_ATTR(idreg, ID_NUMCORE); result.enum_addr = enum_addr; return (result); } /** * Allocate the resource defined by @p rs via @p dev, use it * to read the ChipCommon ID register relative to @p chipc_offset, * then release the resource. * * @param dev The device owning @p rs. * @param rs A resource spec that encompasses the ChipCommon register block. * @param chipc_offset The offset of the ChipCommon registers within @p rs. * @param[out] result the chip identification data. * * @retval 0 success * @retval non-zero if the ChipCommon identification data could not be read. */ int bhnd_read_chipid(device_t dev, struct resource_spec *rs, bus_size_t chipc_offset, struct bhnd_chipid *result) { struct resource *res; uint32_t reg; int error, rid, rtype; /* Allocate the ChipCommon window resource and fetch the chipid data */ rid = rs->rid; rtype = rs->type; res = bus_alloc_resource_any(dev, rtype, &rid, RF_ACTIVE); if (res == NULL) { device_printf(dev, "failed to allocate bhnd chipc resource\n"); return (ENXIO); } /* Fetch the basic chip info */ reg = bus_read_4(res, chipc_offset + CHIPC_ID); *result = bhnd_parse_chipid(reg, 0x0); /* Fetch the enum base address */ error = 0; switch (result->chip_type) { case BHND_CHIPTYPE_SIBA: result->enum_addr = BHND_DEFAULT_CHIPC_ADDR; break; case BHND_CHIPTYPE_BCMA: case BHND_CHIPTYPE_BCMA_ALT: result->enum_addr = bus_read_4(res, chipc_offset + CHIPC_EROMPTR); break; case BHND_CHIPTYPE_UBUS: device_printf(dev, "unsupported ubus/bcm63xx chip type"); error = ENODEV; goto cleanup; default: device_printf(dev, "unknown chip type %hhu\n", result->chip_type); error = ENODEV; goto cleanup; } cleanup: /* Clean up */ bus_release_resource(dev, rtype, rid, res); return (error); } /** - * Using the bhnd(4) bus-level core information, populate @p dev's device - * description. + * Using the bhnd(4) bus-level core information and a custom core name, + * populate @p dev's device description. * * @param dev A bhnd-bus attached device. + * @param dev_name The core's name (e.g. "SDIO Device Core") */ void -bhnd_set_generic_core_desc(device_t dev) +bhnd_set_custom_core_desc(device_t dev, const char *dev_name) { - const char *dev_name; const char *vendor_name; char *desc; vendor_name = bhnd_get_vendor_name(dev); - dev_name = bhnd_get_device_name(dev); + asprintf(&desc, M_BHND, "%s %s, rev %hhu", vendor_name, dev_name, + bhnd_get_hwrev(dev)); - asprintf(&desc, M_BHND, "%s %s, rev %hhu", - bhnd_get_vendor_name(dev), - bhnd_get_device_name(dev), - bhnd_get_hwrev(dev)); - if (desc != NULL) { device_set_desc_copy(dev, desc); free(desc, M_BHND); } else { device_set_desc(dev, dev_name); } +} + +/** + * Using the bhnd(4) bus-level core information, populate @p dev's device + * description. + * + * @param dev A bhnd-bus attached device. + */ +void +bhnd_set_default_core_desc(device_t dev) +{ + bhnd_set_custom_core_desc(dev, bhnd_get_device_name(dev)); } /** * Helper function for implementing BHND_BUS_IS_HOSTB_DEVICE(). * * If a parent device is available, this implementation delegates the * request to the BHND_BUS_IS_HOSTB_DEVICE() method on the parent of @p dev. * * If no parent device is available (i.e. on a the bus root), false * is returned. */ bool bhnd_bus_generic_is_hostb_device(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_IS_HOSTB_DEVICE(device_get_parent(dev), child)); return (false); } /** * Helper function for implementing BHND_BUS_IS_HW_DISABLED(). * * If a parent device is available, this implementation delegates the * request to the BHND_BUS_IS_HW_DISABLED() method on the parent of @p dev. * * If no parent device is available (i.e. on a the bus root), the hardware * is assumed to be usable and false is returned. */ bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), child)); return (false); } /** * Helper function for implementing BHND_BUS_GET_CHIPID(). * * This implementation delegates the request to the BHND_BUS_GET_CHIPID() * method on the parent of @p dev. If no parent exists, the implementation * will panic. */ const struct bhnd_chipid * bhnd_bus_generic_get_chipid(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_GET_CHIPID(device_get_parent(dev), child)); panic("missing BHND_BUS_GET_CHIPID()"); } /** * Helper function for implementing BHND_BUS_ALLOC_RESOURCE(). * * This implementation of BHND_BUS_ALLOC_RESOURCE() delegates allocation * of the underlying resource to BUS_ALLOC_RESOURCE(), and activation * to @p dev's BHND_BUS_ACTIVATE_RESOURCE(). */ struct bhnd_resource * bhnd_bus_generic_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct bhnd_resource *br; struct resource *res; int error; br = NULL; res = NULL; /* Allocate the real bus resource (without activating it) */ res = BUS_ALLOC_RESOURCE(dev, child, type, rid, start, end, count, (flags & ~RF_ACTIVE)); if (res == NULL) return (NULL); /* Allocate our bhnd resource wrapper. */ br = malloc(sizeof(struct bhnd_resource), M_BHND, M_NOWAIT); if (br == NULL) goto failed; br->direct = false; br->res = res; /* Attempt activation */ if (flags & RF_ACTIVE) { error = BHND_BUS_ACTIVATE_RESOURCE(dev, child, type, *rid, br); if (error) goto failed; } return (br); failed: if (res != NULL) BUS_RELEASE_RESOURCE(dev, child, type, *rid, res); free(br, M_BHND); return (NULL); } /** * Helper function for implementing BHND_BUS_RELEASE_RESOURCE(). * * This implementation of BHND_BUS_RELEASE_RESOURCE() delegates release of * the backing resource to BUS_RELEASE_RESOURCE(). */ int bhnd_bus_generic_release_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; if ((error = BUS_RELEASE_RESOURCE(dev, child, type, rid, r->res))) return (error); free(r, M_BHND); return (0); } /** * Helper function for implementing BHND_BUS_ACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() simply calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bhnd_bus_generic_activate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { /* Try to delegate to the parent */ if (device_get_parent(dev) != NULL) return (BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r)); return (EINVAL); }; /** * Helper function for implementing BHND_BUS_DEACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() simply calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bhnd_bus_generic_deactivate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { if (device_get_parent(dev) != NULL) return (BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r)); return (EINVAL); }; \ No newline at end of file Index: head/sys/dev/bhnd/bhndb/bhndb_pci.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pci.c (revision 298277) +++ head/sys/dev/bhnd/bhndb/bhndb_pci.c (revision 298278) @@ -1,1081 +1,1085 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. */ #include __FBSDID("$FreeBSD$"); /* * PCI-specific implementation for the BHNDB bridge driver. * * Provides support for bridging from a PCI parent bus to a BHND-compatible * bus (e.g. bcma or siba) via a Broadcom PCI core configured in end-point * mode. * * This driver handles all interactions with the PCI bridge core. On the * bridged bhnd bus, the PCI core device will be claimed by a simple * bhnd_hostb driver. */ // Quirk TODO // WARs for the following are not yet implemented: // - BHND_PCI_QUIRK_SBINTVEC // - BHND_PCIE_QUIRK_ASPM_OVR // - BHND_PCIE_QUIRK_SERDES_NOPLLDOWN // Quirks (and WARs) for the following are not yet defined: // - Power savings via MDIO BLK1/PWR_MGMT3 on PCIe hwrev 15-20, 21-22 // - WOWL PME enable/disable // - 4360 PCIe SerDes Tx amplitude/deemphasis (vendor Apple, boards // BCM94360X51P2, BCM94360X51A). // - PCI latency timer (boards CB2_4321_BOARD, CB2_4321_AG_BOARD) // - Max SerDes TX drive strength (vendor Apple, pcie >= rev10, // board BCM94322X9) // - 700mV SerDes TX drive strength (chipid BCM4331, boards BCM94331X19, // BCM94331X28, BCM94331X29B, BCM94331X19C) #include #include #include #include #include #include #include #include #include #include #include #include #include "bhndb_pcireg.h" #include "bhndb_pcivar.h" #include "bhndb_private.h" static int bhndb_enable_pci_clocks(struct bhndb_pci_softc *sc); static int bhndb_disable_pci_clocks(struct bhndb_pci_softc *sc); static int bhndb_pci_compat_setregwin(struct bhndb_pci_softc *, const struct bhndb_regwin *, bhnd_addr_t); static int bhndb_pci_fast_setregwin(struct bhndb_pci_softc *, const struct bhndb_regwin *, bhnd_addr_t); static uint32_t bhndb_pcie_read_proto_reg(struct bhndb_pci_softc *sc, uint32_t addr); static void bhndb_pcie_write_proto_reg(struct bhndb_pci_softc *sc, uint32_t addr, uint32_t val); static void bhndb_init_sromless_pci_config(struct bhndb_pci_softc *sc); static int bhndb_pci_wars_register_access(struct bhndb_pci_softc *sc); static int bhndb_pci_wars_early_once(struct bhndb_pci_softc *sc); static int bhndb_pci_wars_hwup(struct bhndb_pci_softc *sc); static int bhndb_pci_wars_hwdown(struct bhndb_pci_softc *sc); static uint32_t bhndb_pci_discover_quirks(struct bhndb_pci_softc *, const struct bhndb_pci_id *); static const struct bhndb_pci_id *bhndb_pci_find_core_id( struct bhnd_core_info *core); /* * Supported PCI bridge cores. * * This table defines quirks specific to core hwrev ranges; see also * bhndb_pci_discover_quirks() for additional quirk detection. */ static const struct bhndb_pci_id bhndb_pci_ids[] = { /* PCI */ - BHNDB_PCI_ID(PCI, - BHND_QUIRK_HWREV_GTE (0, - BHNDB_PCI_QUIRK_EXT_CLOCK_GATING | - BHNDB_PCI_QUIRK_SBTOPCI2_PREF_BURST), + { BHND_COREID_PCI, BHND_PCI_REGFMT_PCI, + (struct bhnd_device_quirk[]) { + { BHND_HWREV_GTE (0), + BHNDB_PCI_QUIRK_EXT_CLOCK_GATING | + BHNDB_PCI_QUIRK_SBTOPCI2_PREF_BURST }, - BHND_QUIRK_HWREV_RANGE (0, 5, - BHNDB_PCI_QUIRK_SBINTVEC), + { BHND_HWREV_RANGE (0, 5), + BHNDB_PCI_QUIRK_SBINTVEC }, - BHND_QUIRK_HWREV_GTE (11, - BHNDB_PCI_QUIRK_SBTOPCI2_READMULTI | - BHNDB_PCI_QUIRK_CLKRUN_DSBL), + { BHND_HWREV_GTE (11), + BHNDB_PCI_QUIRK_SBTOPCI2_READMULTI | + BHNDB_PCI_QUIRK_CLKRUN_DSBL }, - BHND_QUIRK_HWREV_END - ), + BHND_DEVICE_QUIRK_END + } + }, /* PCI Gen 1 */ - BHNDB_PCI_ID(PCIE, - BHND_QUIRK_HWREV_EQ (0, - BHNDB_PCIE_QUIRK_SDR9_L0s_HANG), + { BHND_COREID_PCIE, BHND_PCI_REGFMT_PCIE, + (struct bhnd_device_quirk[]) { + { BHND_HWREV_EQ (0), + BHNDB_PCIE_QUIRK_SDR9_L0s_HANG }, - BHND_QUIRK_HWREV_RANGE (0, 1, - BHNDB_PCIE_QUIRK_UR_STATUS_FIX), + { BHND_HWREV_RANGE (0, 1), + BHNDB_PCIE_QUIRK_UR_STATUS_FIX }, - BHND_QUIRK_HWREV_EQ (1, - BHNDB_PCIE_QUIRK_PCIPM_REQEN), + { BHND_HWREV_EQ (1), + BHNDB_PCIE_QUIRK_PCIPM_REQEN }, - BHND_QUIRK_HWREV_RANGE (3, 5, - BHNDB_PCIE_QUIRK_ASPM_OVR | - BHNDB_PCIE_QUIRK_SDR9_POLARITY | - BHNDB_PCIE_QUIRK_SDR9_NO_FREQRETRY), + { BHND_HWREV_RANGE (3, 5), + BHNDB_PCIE_QUIRK_ASPM_OVR | + BHNDB_PCIE_QUIRK_SDR9_POLARITY | + BHNDB_PCIE_QUIRK_SDR9_NO_FREQRETRY }, - BHND_QUIRK_HWREV_LTE (6, - BHNDB_PCIE_QUIRK_L1_IDLE_THRESH), + { BHND_HWREV_LTE (6), + BHNDB_PCIE_QUIRK_L1_IDLE_THRESH }, - BHND_QUIRK_HWREV_GTE (6, - BHNDB_PCIE_QUIRK_SPROM_L23_PCI_RESET), + { BHND_HWREV_GTE (6), + BHNDB_PCIE_QUIRK_SPROM_L23_PCI_RESET }, - BHND_QUIRK_HWREV_EQ (7, - BHNDB_PCIE_QUIRK_SERDES_NOPLLDOWN), + { BHND_HWREV_EQ (7), + BHNDB_PCIE_QUIRK_SERDES_NOPLLDOWN }, - BHND_QUIRK_HWREV_GTE (8, - BHNDB_PCIE_QUIRK_L1_TIMER_PERF), + { BHND_HWREV_GTE (8), + BHNDB_PCIE_QUIRK_L1_TIMER_PERF }, - BHND_QUIRK_HWREV_GTE (10, - BHNDB_PCIE_QUIRK_SD_C22_EXTADDR), + { BHND_HWREV_GTE (10), + BHNDB_PCIE_QUIRK_SD_C22_EXTADDR }, - BHND_QUIRK_HWREV_END - ), + BHND_DEVICE_QUIRK_END + } + }, - { BHND_COREID_INVALID, BHND_PCI_REGFMT_PCI, NULL } + { BHND_COREID_INVALID, BHND_PCI_REGFMT_PCI } }; /* quirk flag convenience macros */ #define BHNDB_PCI_QUIRK(_sc, _name) \ ((_sc)->quirks & BHNDB_PCI_QUIRK_ ## _name) #define BHNDB_PCIE_QUIRK(_sc, _name) \ ((_sc)->quirks & BHNDB_PCIE_QUIRK_ ## _name) #define BHNDB_PCI_ASSERT_QUIRK(_sc, name) \ KASSERT(BHNDB_PCI_QUIRK((_sc), name), ("quirk " __STRING(_name) " not set")) #define BHNDB_PCIE_ASSERT_QUIRK(_sc, name) \ KASSERT(BHNDB_PCIE_QUIRK((_sc), name), ("quirk " __STRING(_name) " not set")) /* bus_(read|write)_* convenience macros */ #define BHNDB_PCI_READ_2(_sc, _reg) \ bus_read_2((_sc)->mem_res, (_sc)->mem_off + (_reg)) #define BHNDB_PCI_READ_4(_sc, _reg) \ bus_read_4((_sc)->mem_res, (_sc)->mem_off + (_reg)) #define BHNDB_PCI_WRITE_2(_sc, _reg, _val) \ bus_write_2((_sc)->mem_res, (_sc)->mem_off + (_reg), (_val)) #define BHNDB_PCI_WRITE_4(_sc, _reg, _val) \ bus_write_4((_sc)->mem_res, (_sc)->mem_off + (_reg), (_val)) /* BHNDB_PCI_REG_* convenience macros */ #define BPCI_REG_EXTRACT(_rv, _a) BHND_PCI_REG_EXTRACT(_rv, BHND_ ## _a) #define BPCI_REG_INSERT(_rv, _a, _v) BHND_PCI_REG_INSERT(_rv, BHND_ ## _a, _v) #define BPCI_COMMON_REG_EXTRACT(_r, _a) \ BHND_PCI_COMMON_REG_EXTRACT(sc->regfmt, _r, _a) #define BPCI_COMMON_REG_INSERT(_r, _a, _v) \ BHND_PCI_COMMON_REG_INSERT(sc->regfmt, _r, _a, _v) #define BPCI_COMMON_REG(_name) \ BHND_PCI_COMMON_REG(sc->regfmt, _name) #define BPCI_COMMON_REG_OFFSET(_base, _offset) \ (BPCI_COMMON_REG(_base) + BPCI_COMMON_REG(_offset)) /** * Default bhndb_pci implementation of device_probe(). * * Verifies that the parent is a PCI/PCIe device. */ static int bhndb_pci_probe(device_t dev) { device_t parent; devclass_t parent_bus; devclass_t pci; /* Our parent must be a PCI/PCIe device. */ pci = devclass_find("pci"); parent = device_get_parent(dev); parent_bus = device_get_devclass(device_get_parent(parent)); if (parent_bus != pci) return (ENXIO); device_set_desc(dev, "PCI-BHND bridge"); return (BUS_PROBE_DEFAULT); } static int bhndb_pci_attach(device_t dev) { struct bhndb_pci_softc *sc; int error, reg; sc = device_get_softc(dev); sc->dev = dev; /* Enable PCI bus mastering */ pci_enable_busmaster(device_get_parent(dev)); /* Determine our bridge device class */ sc->pci_devclass = BHND_DEVCLASS_PCI; if (pci_find_cap(device_get_parent(dev), PCIY_EXPRESS, ®) == 0) sc->pci_devclass = BHND_DEVCLASS_PCIE; /* Determine the basic set of applicable quirks. This will be updated * in bhndb_pci_init_full_config() once the PCI device core has * been enumerated. */ sc->quirks = bhndb_pci_discover_quirks(sc, NULL); /* Using the discovered quirks, apply any WARs required for basic * register access. */ if ((error = bhndb_pci_wars_register_access(sc))) return (error); /* Use siba(4)-compatible regwin handling until we know * what kind of bus is attached */ sc->set_regwin = bhndb_pci_compat_setregwin; /* Perform full bridge attach. This should call back into our * bhndb_pci_init_full_config() implementation once the bridged * bhnd(4) bus has been enumerated, but before any devices have been * probed or attached. */ if ((error = bhndb_attach(dev, sc->pci_devclass))) return (error); /* If supported, switch to the faster regwin handling */ if (sc->bhndb.chipid.chip_type != BHND_CHIPTYPE_SIBA) { atomic_store_rel_ptr((volatile void *) &sc->set_regwin, (uintptr_t) &bhndb_pci_fast_setregwin); } return (0); } /** * Initialize the full bridge configuration. * * This is called during the DEVICE_ATTACH() process by the bridged bhndb(4) * bus, prior to probe/attachment of child cores. * * At this point, we can introspect the enumerated cores, find our host * bridge device, and apply any bridge-level hardware workarounds required * for proper operation of the bridged device cores. */ static int bhndb_pci_init_full_config(device_t dev, device_t child, const struct bhndb_hw_priority *prio_table) { struct bhnd_core_info core; const struct bhndb_pci_id *id; struct bhndb_pci_softc *sc; struct bhndb_region *pcir; bhnd_addr_t pcir_addr; bhnd_size_t pcir_size; int error; sc = device_get_softc(dev); /* Let bhndb perform full discovery and initialization of the * available register windows and bridge resources. */ if ((error = bhndb_generic_init_full_config(dev, child, prio_table))) return (error); /* * Identify our PCI bridge core, its register family, and any * applicable hardware quirks. */ KASSERT(sc->bhndb.hostb_dev, ("missing hostb device\n")); core = bhnd_get_core_info(sc->bhndb.hostb_dev); id = bhndb_pci_find_core_id(&core); if (id == NULL) { device_printf(dev, "%s %s hostb core is not recognized\n", bhnd_vendor_name(core.vendor), bhnd_core_name(&core)); } sc->regfmt = id->regfmt; /* Now that we've identified the PCI bridge core, we can determine the * full set of device quirks */ sc->quirks = bhndb_pci_discover_quirks(sc, id); /* * Determine and save a reference to the bhndb resource and offset * at which the bridge core's device registers are mapped. * * All known bhnd(4) hardware provides a fixed static mapping of * the PCI core's registers. If this changes in the future -- which * is unlikely -- this driver will need to be adjusted to use * dynamic register windows. */ /* Find base address and size of the PCI core's register block. */ error = bhnd_get_region_addr(sc->bhndb.hostb_dev, BHND_PORT_DEVICE, 0, 0, &pcir_addr, &pcir_size); if (error) { device_printf(dev, "failed to locate PCI core registers\n"); return (error); } /* Find the bhndb_region that statically maps this block */ pcir = bhndb_find_resource_region(sc->bhndb.bus_res, pcir_addr, pcir_size); if (pcir == NULL || pcir->static_regwin == NULL) { device_printf(dev, "missing static PCI core register window\n"); return (ENXIO); } /* Save borrowed reference to the mapped PCI core registers */ sc->mem_off = pcir->static_regwin->win_offset; sc->mem_res = bhndb_find_regwin_resource(sc->bhndb.bus_res, pcir->static_regwin); if (sc->mem_res == NULL || !(rman_get_flags(sc->mem_res) & RF_ACTIVE)) { device_printf(dev, "no active resource maps the PCI core register window\n"); return (ENXIO); } /* Configure a direct bhnd_resource wrapper that we can pass to * bhnd_resource APIs */ sc->bhnd_mem_res = (struct bhnd_resource) { .res = sc->mem_res, .direct = true }; /* * Attach MMIO device (if this is a PCIe device), which is used for * access to the PCIe SerDes required by the quirk workarounds. */ if (sc->pci_devclass == BHND_DEVCLASS_PCIE) { sc->mdio = BUS_ADD_CHILD(dev, 0, devclass_get_name(bhnd_mdio_pci_devclass), 0); if (sc->mdio == NULL) return (ENXIO); error = bus_set_resource(sc->mdio, SYS_RES_MEMORY, 0, rman_get_start(sc->mem_res) + sc->mem_off + BHND_PCIE_MDIO_CTL, sizeof(uint32_t)*2); if (error) { device_printf(dev, "failed to set MDIO resource\n"); return (error); } if ((error = device_probe_and_attach(sc->mdio))) { device_printf(dev, "failed to attach MDIO device\n"); return (error); } } /* Apply any early one-time quirk workarounds */ if ((error = bhndb_pci_wars_early_once(sc))) return (error); /* Apply attach-time quirk workarounds, required before the bridged * bhnd(4) bus itself performs a full attach(). */ if ((error = bhndb_pci_wars_hwup(sc))) return (error); return (0); } /** * Apply any hardware workarounds that must be executed prior to attempting * register access on the bridged chipset. * * This must be called very early in attach() or resume(), after the basic * set of applicable device quirks has been determined. */ static int bhndb_pci_wars_register_access(struct bhndb_pci_softc *sc) { int error; if (BHNDB_PCI_QUIRK(sc, EXT_CLOCK_GATING)) { if ((error = bhndb_enable_pci_clocks(sc))) { device_printf(sc->dev, "failed to enable clocks\n"); return (error); } } return (0); } /** * Apply any hardware work-arounds that must be executed exactly once, early in * the attach process. * * This must be called after core enumeration and discovery of all applicable * quirks, but prior to probe/attach of any cores, parsing of * SPROM, etc. */ static int bhndb_pci_wars_early_once(struct bhndb_pci_softc *sc) { /* Determine correct polarity by observing the attach-time PCIe PHY * link status. This is used later to reset/force the SerDes * polarity */ if (BHNDB_PCIE_QUIRK(sc, SDR9_POLARITY)) { uint32_t st; bool inv; st = bhndb_pcie_read_proto_reg(sc, BHND_PCIE_PLP_STATUSREG); inv = ((st & BHND_PCIE_PLP_POLARITY_INV) != 0); sc->sdr9_quirk_polarity.inv = inv; } return (0); } /** * Apply any hardware workarounds that are required upon attach or resume * of the bridge device. */ static int bhndb_pci_wars_hwup(struct bhndb_pci_softc *sc) { /* Note that the order here matters; these work-arounds * should not be re-ordered without careful review of their * interdependencies */ /* Fix up any PoR defaults on SROMless devices */ bhndb_init_sromless_pci_config(sc); /* Enable PCI prefetch/burst/readmulti flags */ if (BHNDB_PCI_QUIRK(sc, SBTOPCI2_PREF_BURST) || BHNDB_PCI_QUIRK(sc, SBTOPCI2_READMULTI)) { uint32_t sbp2; sbp2 = BHNDB_PCI_READ_4(sc, BHND_PCI_SBTOPCI2); if (BHNDB_PCI_QUIRK(sc, SBTOPCI2_PREF_BURST)) sbp2 |= (BHND_PCI_SBTOPCI_PREF|BHND_PCI_SBTOPCI_BURST); if (BHNDB_PCI_QUIRK(sc, SBTOPCI2_READMULTI)) sbp2 |= BHND_PCI_SBTOPCI_RC_READMULTI; BHNDB_PCI_WRITE_4(sc, BHND_PCI_SBTOPCI2, sbp2); } /* Disable PCI CLKRUN# */ if (BHNDB_PCI_QUIRK(sc, CLKRUN_DSBL)) { uint32_t ctl; ctl = BHNDB_PCI_READ_4(sc, BHND_PCI_CLKRUN_CTL); ctl |= BHND_PCI_CLKRUN_DSBL; BHNDB_PCI_WRITE_4(sc, BHND_PCI_CLKRUN_CTL, ctl); } /* Enable TLP unmatched address handling work-around */ if (BHNDB_PCIE_QUIRK(sc, UR_STATUS_FIX)) { uint32_t wrs; wrs = bhndb_pcie_read_proto_reg(sc, BHND_PCIE_TLP_WORKAROUNDSREG); wrs |= BHND_PCIE_TLP_WORKAROUND_URBIT; bhndb_pcie_write_proto_reg(sc, BHND_PCIE_TLP_WORKAROUNDSREG, wrs); } /* Adjust SerDes CDR tuning to ensure that CDR is stable before sending * data during L0s to L0 exit transitions. */ if (BHNDB_PCIE_QUIRK(sc, SDR9_L0s_HANG)) { uint16_t sdv; /* Set RX track/acquire timers to 2.064us/40.96us */ sdv = BPCI_REG_INSERT(0, PCIE_SDR9_RX_TIMER1_LKTRK, (2064/16)); sdv = BPCI_REG_INSERT(sdv, PCIE_SDR9_RX_TIMER1_LKACQ, (40960/1024)); MDIO_WRITEREG(sc->mdio, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_TIMER1, sdv); /* Apply CDR frequency workaround */ sdv = BHND_PCIE_SDR9_RX_CDR_FREQ_OVR_EN; sdv = BPCI_REG_INSERT(sdv, PCIE_SDR9_RX_CDR_FREQ_OVR, 0x0); MDIO_WRITEREG(sc->mdio, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_CDR, sdv); /* Apply CDR BW tunings */ sdv = 0; sdv = BPCI_REG_INSERT(sdv, PCIE_SDR9_RX_CDRBW_INTGTRK, 0x2); sdv = BPCI_REG_INSERT(sdv, PCIE_SDR9_RX_CDRBW_INTGACQ, 0x4); sdv = BPCI_REG_INSERT(sdv, PCIE_SDR9_RX_CDRBW_PROPTRK, 0x6); sdv = BPCI_REG_INSERT(sdv, PCIE_SDR9_RX_CDRBW_PROPACQ, 0x6); MDIO_WRITEREG(sc->mdio, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_CDRBW, sdv); } /* Force correct SerDes polarity */ if (BHNDB_PCIE_QUIRK(sc, SDR9_POLARITY)) { uint16_t rxctl; rxctl = MDIO_READREG(sc->mdio, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_CTRL); rxctl |= BHND_PCIE_SDR9_RX_CTRL_FORCE; if (sc->sdr9_quirk_polarity.inv) rxctl |= BHND_PCIE_SDR9_RX_CTRL_POLARITY_INV; else rxctl &= ~BHND_PCIE_SDR9_RX_CTRL_POLARITY_INV; MDIO_WRITEREG(sc->mdio, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_CTRL, rxctl); } /* Disable startup retry on PLL frequency detection failure */ if (BHNDB_PCIE_QUIRK(sc, SDR9_NO_FREQRETRY)) { uint16_t pctl; pctl = MDIO_READREG(sc->mdio, BHND_PCIE_PHY_SDR9_PLL, BHND_PCIE_SDR9_PLL_CTRL); pctl &= ~BHND_PCIE_SDR9_PLL_CTRL_FREQDET_EN; MDIO_WRITEREG(sc->mdio, BHND_PCIE_PHY_SDR9_PLL, BHND_PCIE_SDR9_PLL_CTRL, pctl); } /* Explicitly enable PCI-PM */ if (BHNDB_PCIE_QUIRK(sc, PCIPM_REQEN)) { uint32_t lcreg; lcreg = bhndb_pcie_read_proto_reg(sc, BHND_PCIE_DLLP_LCREG); lcreg |= BHND_PCIE_DLLP_LCREG_PCIPM_EN; bhndb_pcie_write_proto_reg(sc, BHND_PCIE_DLLP_LCREG, lcreg); } /* Adjust L1 timer to fix slow L1->L0 transitions */ if (BHNDB_PCIE_QUIRK(sc, L1_IDLE_THRESH)) { uint32_t pmt; pmt = bhndb_pcie_read_proto_reg(sc, BHND_PCIE_DLLP_PMTHRESHREG); pmt = BPCI_REG_INSERT(pmt, PCIE_L1THRESHOLDTIME, BHND_PCIE_L1THRESHOLD_WARVAL); bhndb_pcie_write_proto_reg(sc, BHND_PCIE_DLLP_PMTHRESHREG, pmt); } /* Extend L1 timer for better performance. * TODO: We could enable/disable this on demand for better power * savings if we tie this to HT clock request handling */ if (BHNDB_PCIE_QUIRK(sc, L1_TIMER_PERF)) { uint32_t pmt; pmt = bhndb_pcie_read_proto_reg(sc, BHND_PCIE_DLLP_PMTHRESHREG); pmt |= BHND_PCIE_ASPMTIMER_EXTEND; bhndb_pcie_write_proto_reg(sc, BHND_PCIE_DLLP_PMTHRESHREG, pmt); } /* Enable L23READY_EXIT_NOPRST if not already set in SPROM. */ if (BHNDB_PCIE_QUIRK(sc, SPROM_L23_PCI_RESET)) { bus_size_t reg; uint16_t cfg; /* Fetch the misc cfg flags from SPROM */ reg = BHND_PCIE_SPROM_SHADOW + BHND_PCIE_SRSH_PCIE_MISC_CONFIG; cfg = BHNDB_PCI_READ_2(sc, reg); /* Write EXIT_NOPRST flag if not already set in SPROM */ if (!(cfg & BHND_PCIE_SRSH_L23READY_EXIT_NOPRST)) { cfg |= BHND_PCIE_SRSH_L23READY_EXIT_NOPRST; BHNDB_PCI_WRITE_2(sc, reg, cfg); } } return (0); } /** * Apply any hardware workarounds that are required upon resume of the * bridge device. * * This must be called before any bridged bhnd(4) cores have been resumed. */ static int bhndb_pci_wars_hwresume(struct bhndb_pci_softc *sc) { int error; /* Nothing is possible without register access */ if ((error = bhndb_pci_wars_register_access(sc))) return (error); /* Apply the general hwup workarounds */ return (bhndb_pci_wars_hwup(sc)); } /** * Apply any hardware workarounds that are required upon detach or suspend * of the bridge device. */ static int bhndb_pci_wars_hwdown(struct bhndb_pci_softc *sc) { int error; /* Reduce L1 timer for better power savings. * TODO: We could enable/disable this on demand for better power * savings if we tie this to HT clock request handling */ if (BHNDB_PCIE_QUIRK(sc, L1_TIMER_PERF)) { uint32_t pmt; pmt = bhndb_pcie_read_proto_reg(sc, BHND_PCIE_DLLP_PMTHRESHREG); pmt &= ~BHND_PCIE_ASPMTIMER_EXTEND; bhndb_pcie_write_proto_reg(sc, BHND_PCIE_DLLP_PMTHRESHREG, pmt); } /* Disable clocks */ if (BHNDB_PCI_QUIRK(sc, EXT_CLOCK_GATING)) { if ((error = bhndb_disable_pci_clocks(sc))) { device_printf(sc->dev, "failed to disable clocks\n"); return (error); } } return (0); } /* * On devices without a SROM, the PCI(e) cores will be initialized with * their Power-on-Reset defaults; this can leave the the BAR0 PCI windows * potentially mapped to the wrong core index. * * This function updates the PCI core's BAR0 PCI configuration to point at the * current PCI core. * * Applies to all PCI/PCIe revisions. Must be applied before bus devices * are probed/attached or the SPROM is parsed. */ static void bhndb_init_sromless_pci_config(struct bhndb_pci_softc *sc) { bus_size_t sprom_addr; u_int sprom_core_idx; u_int pci_core_idx; uint16_t val; /* Fetch the SPROM's configured core index */ sprom_addr = BPCI_COMMON_REG_OFFSET(SPROM_SHADOW, SRSH_PI_OFFSET); val = BHNDB_PCI_READ_2(sc, sprom_addr); /* If it doesn't match host bridge's core index, update the index * value */ sprom_core_idx = BPCI_COMMON_REG_EXTRACT(val, SRSH_PI); pci_core_idx = bhnd_get_core_index(sc->bhndb.hostb_dev); if (sprom_core_idx != pci_core_idx) { val = BPCI_COMMON_REG_INSERT(val, SRSH_PI, pci_core_idx); BHNDB_PCI_WRITE_2(sc, sprom_addr, val); } } static int bhndb_pci_detach(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); if ((error = bhndb_generic_detach(dev))) return (error); /* Apply any hardware workarounds. This may disable the clock, and * thus must be called *after* any children have been detached. */ if ((error = bhndb_pci_wars_hwdown(sc))) return (error); /* Disable PCI bus mastering */ pci_disable_busmaster(device_get_parent(dev)); return (0); } static int bhndb_pci_suspend(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); if ((error = bhndb_generic_suspend(dev))) return (error); /* Apply any hardware workarounds. This may disable the clock, and * thus must be called *after* any children have been suspended. */ if ((error = bhndb_pci_wars_hwdown(sc))) return (error); return (0); } static int bhndb_pci_resume(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Apply any resume workarounds; these may be required for bridged * device access, and thus must be called *before* any children are * resumed. */ if ((error = bhndb_pci_wars_hwresume(sc))) return (error); if ((error = bhndb_generic_resume(dev))) return (error); return (0); } static int bhndb_pci_set_window_addr(device_t dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { struct bhndb_pci_softc *sc = device_get_softc(dev); return (sc->set_regwin(sc, rw, addr)); } /** * A siba(4) and bcma(4)-compatible bhndb_set_window_addr implementation. * * On siba(4) devices, it's possible that writing a PCI window register may * not succeed; it's necessary to immediately read the configuration register * and retry if not set to the desired value. * * This is not necessary on bcma(4) devices, but other than the overhead of * validating the register, there's no harm in performing the verification. */ static int bhndb_pci_compat_setregwin(struct bhndb_pci_softc *sc, const struct bhndb_regwin *rw, bhnd_addr_t addr) { device_t parent; int error; parent = sc->bhndb.parent_dev; if (rw->win_type != BHNDB_REGWIN_T_DYN) return (ENODEV); for (u_int i = 0; i < BHNDB_PCI_BARCTRL_WRITE_RETRY; i++) { if ((error = bhndb_pci_fast_setregwin(sc, rw, addr))) return (error); if (pci_read_config(parent, rw->dyn.cfg_offset, 4) == addr) return (0); DELAY(10); } /* Unable to set window */ return (ENODEV); } /** * A bcma(4)-only bhndb_set_window_addr implementation. */ static int bhndb_pci_fast_setregwin(struct bhndb_pci_softc *sc, const struct bhndb_regwin *rw, bhnd_addr_t addr) { device_t parent = sc->bhndb.parent_dev; /* The PCI bridge core only supports 32-bit addressing, regardless * of the bus' support for 64-bit addressing */ if (addr > UINT32_MAX) return (ERANGE); switch (rw->win_type) { case BHNDB_REGWIN_T_DYN: /* Addresses must be page aligned */ if (addr % rw->win_size != 0) return (EINVAL); pci_write_config(parent, rw->dyn.cfg_offset, addr, 4); break; default: return (ENODEV); } return (0); } /** * Read a 32-bit PCIe TLP/DLLP/PLP protocol register. * * @param sc The bhndb_pci driver state. * @param addr The protocol register offset. */ static uint32_t bhndb_pcie_read_proto_reg(struct bhndb_pci_softc *sc, uint32_t addr) { uint32_t val; KASSERT(bhnd_get_class(sc->bhndb.hostb_dev) == BHND_DEVCLASS_PCIE, ("not a pcie device!")); BHNDB_LOCK(&sc->bhndb); BHNDB_PCI_WRITE_4(sc, BHND_PCIE_IND_ADDR, addr); val = BHNDB_PCI_READ_4(sc, BHND_PCIE_IND_DATA); BHNDB_UNLOCK(&sc->bhndb); return (val); } /** * Write a 32-bit PCIe TLP/DLLP/PLP protocol register value. * * @param sc The bhndb_pci driver state. * @param addr The protocol register offset. * @param val The value to write to @p addr. */ static void bhndb_pcie_write_proto_reg(struct bhndb_pci_softc *sc, uint32_t addr, uint32_t val) { KASSERT(bhnd_get_class(sc->bhndb.hostb_dev) == BHND_DEVCLASS_PCIE, ("not a pcie device!")); BHNDB_LOCK(&sc->bhndb); BHNDB_PCI_WRITE_4(sc, BHND_PCIE_IND_ADDR, addr); BHNDB_PCI_WRITE_4(sc, BHND_PCIE_IND_DATA, val); BHNDB_UNLOCK(&sc->bhndb); } /** * Enable externally managed clocks. * * Quirk Required: EXT_CLOCK_GATING * * @param sc Bridge driver state. */ static int bhndb_enable_pci_clocks(struct bhndb_pci_softc *sc) { device_t pci_parent; uint32_t gpio_in, gpio_out, gpio_en; uint32_t gpio_flags; uint16_t pci_status; BHNDB_PCI_ASSERT_QUIRK(sc, EXT_CLOCK_GATING); pci_parent = device_get_parent(sc->dev); /* Read state of XTAL pin */ gpio_in = pci_read_config(pci_parent, BHNDB_PCI_GPIO_IN, 4); if (gpio_in & BHNDB_PCI_GPIO_XTAL_ON) return (0); /* already enabled */ /* Fetch current config */ gpio_out = pci_read_config(pci_parent, BHNDB_PCI_GPIO_OUT, 4); gpio_en = pci_read_config(pci_parent, BHNDB_PCI_GPIO_OUTEN, 4); /* Set PLL_OFF/XTAL_ON pins to HIGH and enable both pins */ gpio_flags = (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON); gpio_out |= gpio_flags; gpio_en |= gpio_flags; pci_write_config(pci_parent, BHNDB_PCI_GPIO_OUT, gpio_out, 4); pci_write_config(pci_parent, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4); DELAY(1000); /* Reset PLL_OFF */ gpio_out &= ~BHNDB_PCI_GPIO_PLL_OFF; pci_write_config(pci_parent, BHNDB_PCI_GPIO_OUT, gpio_out, 4); DELAY(5000); /* Clear any PCI 'sent target-abort' flag. */ pci_status = pci_read_config(pci_parent, PCIR_STATUS, 2); pci_status &= ~PCIM_STATUS_STABORT; pci_write_config(pci_parent, PCIR_STATUS, pci_status, 2); return (0); } /** * Disable externally managed clocks. * * Quirk Required: EXT_CLOCK_GATING * * @param sc Bridge driver state. */ static int bhndb_disable_pci_clocks(struct bhndb_pci_softc *sc) { device_t parent_dev; uint32_t gpio_out, gpio_en; BHNDB_PCI_ASSERT_QUIRK(sc, EXT_CLOCK_GATING); parent_dev = device_get_parent(sc->dev); // TODO: Check board flags for BFL2_XTALBUFOUTEN? // TODO: Check PCI core revision? // TODO: Switch to 'slow' clock? /* Fetch current config */ gpio_out = pci_read_config(parent_dev, BHNDB_PCI_GPIO_OUT, 4); gpio_en = pci_read_config(parent_dev, BHNDB_PCI_GPIO_OUTEN, 4); /* Set PLL_OFF to HIGH, XTAL_ON to LOW. */ gpio_out &= ~BHNDB_PCI_GPIO_XTAL_ON; gpio_out |= BHNDB_PCI_GPIO_PLL_OFF; pci_write_config(parent_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4); /* Enable both output pins */ gpio_en |= (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON); pci_write_config(parent_dev, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4); return (0); } /** * Find the identification table entry for a core descriptor. * * @param sc bhndb PCI driver state. */ static const struct bhndb_pci_id * bhndb_pci_find_core_id(struct bhnd_core_info *core) { const struct bhndb_pci_id *id; for (id = bhndb_pci_ids; id->device != BHND_COREID_INVALID; id++) { if (core->vendor == BHND_MFGID_BCM && core->device == id->device) return (id); } return (NULL); } /** * Return all quirks known to be applicable to the host bridge. * * If the PCI bridge core has not yet been identified, no core-specific * quirk flags will be returned. This function may be called again to * rediscover applicable quirks after the host bridge core has been * identified. * * @param sc bhndb PCI driver state. * @param id The host bridge core's identification table entry, or NULL * if the host bridge core has not yet been identified. * * @return Returns the set of quirks applicable to the current hardware. */ static uint32_t bhndb_pci_discover_quirks(struct bhndb_pci_softc *sc, const struct bhndb_pci_id *id) { struct bhnd_device_quirk *qt; uint32_t quirks; uint8_t hwrev; quirks = BHNDB_PCI_QUIRK_NONE; /* Determine any device class-specific quirks */ switch (sc->pci_devclass) { case BHND_DEVCLASS_PCI: /* All PCI devices require external clock gating */ sc->quirks |= BHNDB_PCI_QUIRK_EXT_CLOCK_GATING; break; default: break; } // TODO: Additional quirk matching /* Determine any PCI core hwrev-specific device quirks */ if (id != NULL) { hwrev = bhnd_get_hwrev(sc->bhndb.hostb_dev); for (qt = id->quirks; qt->quirks != 0; qt++) { if (bhnd_hwrev_matches(hwrev, &qt->hwrev)) quirks |= qt->quirks; } } return (quirks); } /* * Support for attaching the PCIe-Gen1 MDIO driver to a parent bhndb PCIe * bridge device. */ static int bhndb_mdio_pcie_probe(device_t dev) { device_quiet(dev); return (BUS_PROBE_NOWILDCARD); } static int bhndb_mdio_pcie_attach(device_t dev) { struct bhndb_pci_softc *psc; psc = device_get_softc(device_get_parent(dev)); return (bhnd_mdio_pcie_attach(dev, &psc->bhnd_mem_res, -1, psc->mem_off + BHND_PCIE_MDIO_CTL, (psc->quirks & BHNDB_PCIE_QUIRK_SD_C22_EXTADDR) != 0)); } static device_method_t bhnd_mdio_pcie_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhndb_mdio_pcie_probe), DEVMETHOD(device_attach, bhndb_mdio_pcie_attach), DEVMETHOD_END }; static device_method_t bhndb_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhndb_pci_probe), DEVMETHOD(device_attach, bhndb_pci_attach), DEVMETHOD(device_detach, bhndb_pci_detach), DEVMETHOD(device_suspend, bhndb_pci_suspend), DEVMETHOD(device_resume, bhndb_pci_resume), /* BHNDB interface */ DEVMETHOD(bhndb_init_full_config, bhndb_pci_init_full_config), DEVMETHOD(bhndb_set_window_addr, bhndb_pci_set_window_addr), DEVMETHOD_END }; DEFINE_CLASS_1(bhndb, bhndb_pci_driver, bhndb_pci_methods, sizeof(struct bhndb_pci_softc), bhndb_driver); DEFINE_CLASS_1(bhnd_mdio_pci, bhndb_mdio_pcie_driver, bhnd_mdio_pcie_methods, sizeof(struct bhnd_mdio_pcie_softc), bhnd_mdio_pcie_driver); DRIVER_MODULE(bhnd_mdio_pcie, bhndb, bhndb_mdio_pcie_driver, bhnd_mdio_pci_devclass, NULL, NULL); MODULE_VERSION(bhndb_pci, 1); MODULE_DEPEND(bhndb_pci, bhnd_pci, 1, 1, 1); MODULE_DEPEND(bhndb_pci, pci, 1, 1, 1); MODULE_DEPEND(bhndb_pci, bhndb, 1, 1, 1); Index: head/sys/dev/bhnd/bhndb/bhndb_pcivar.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pcivar.h (revision 298277) +++ head/sys/dev/bhnd/bhndb/bhndb_pcivar.h (revision 298278) @@ -1,251 +1,242 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. * * $FreeBSD$ */ #ifndef _BHND_BHNDB_PCIVAR_H_ #define _BHND_BHNDB_PCIVAR_H_ #include #include #include "bhndbvar.h" /* * bhndb(4) PCI driver subclass. */ DECLARE_CLASS(bhndb_pci_driver); struct bhndb_pci_softc; /* * An interconnect-specific function implementing BHNDB_SET_WINDOW_ADDR */ typedef int (*bhndb_pci_set_regwin_t)(struct bhndb_pci_softc *sc, const struct bhndb_regwin *rw, bhnd_addr_t addr); /** * PCI bridge core identification table. */ struct bhndb_pci_id { uint16_t device; /**< bhnd device ID */ bhnd_pci_regfmt_t regfmt; /**< register format */ struct bhnd_device_quirk *quirks; /**< quirks table */ }; struct bhndb_pci_softc { struct bhndb_softc bhndb; /**< parent softc */ device_t dev; /**< bridge device */ bhnd_devclass_t pci_devclass; /**< PCI core's devclass */ bhndb_pci_set_regwin_t set_regwin; /**< regwin handler */ /* * Initialized in BHNDB_INIT_FULL_CONFIG() */ device_t mdio; /**< PCIe MDIO device. NULL if not PCIe. */ bhnd_pci_regfmt_t regfmt; /**< device register format */ struct resource *mem_res; /**< pci core's registers (borrowed reference) */ bus_size_t mem_off; /**< offset to the PCI core's registers within `mem_res` . */ struct bhnd_resource bhnd_mem_res; /**< bhnd resource representation of mem_res. this is a simple 'direct' resource mapping */ uint32_t quirks; /**< BHNDB_PCI(E)_QUIRK flags */ /** * Driver state specific to BHNDB_PCIE_QUIRK_SDR9_POLARITY. */ struct { /** * PCIe SerDes RX polarity. * * Initialized to the PCIe link's RX polarity * at attach time. This is used to restore the * correct polarity on resume */ bool inv; } sdr9_quirk_polarity; }; -/* Declare a bhndb_pci_id entry */ -#define BHNDB_PCI_ID(_device, _desc, ...) { \ - BHND_COREID_ ## _device, \ - BHND_PCI_REGFMT_ ## _device, \ - (struct bhnd_device_quirk[]) { \ - __VA_ARGS__ \ - } \ -} - /* * PCI/PCIe-Gen1 endpoint-mode device quirks */ enum { /** No quirks */ BHNDB_PCI_QUIRK_NONE = 0, /** * BCM4306 chips (and possibly others) do not support the idle * low-power clock. Clocking must be bootstrapped at attach/resume by * directly adjusting GPIO registers exposed in the PCI config space, * and correspondingly, explicitly shutdown at detach/suspend. */ BHNDB_PCI_QUIRK_EXT_CLOCK_GATING = (1<<1), /** * SBTOPCI_PREF and SBTOPCI_BURST must be set on the * SSB_PCICORE_SBTOPCI2 register. */ BHNDB_PCI_QUIRK_SBTOPCI2_PREF_BURST = (1<<2), /** * SBTOPCI_RC_READMULTI must be set on the SSB_PCICORE_SBTOPCI2 * register. */ BHNDB_PCI_QUIRK_SBTOPCI2_READMULTI = (1<<3), /** * Interrupt masking is handled via the interconnect configuration * registers (SBINTVEC on siba), rather than the PCI_INT_MASK * config register. */ BHNDB_PCI_QUIRK_SBINTVEC = (1<<4), /** * PCI CLKRUN# should be disabled on attach (via CLKRUN_DSBL). * * The purpose of this work-around is unclear; there is some * documentation regarding earlier Broadcom drivers supporting * a "force CLKRUN#" *enable* registry key for use on mobile * hardware. */ BHNDB_PCI_QUIRK_CLKRUN_DSBL = (1<<5), /** * TLP workaround for unmatched address handling is required. * * This TLP workaround will enable setting of the PCIe UR status bit * on memory access to an unmatched address. */ BHNDB_PCIE_QUIRK_UR_STATUS_FIX = (1<<6), /** * PCI-PM power management must be explicitly enabled via * the data link control register. */ BHNDB_PCIE_QUIRK_PCIPM_REQEN = (1<<7), /** * Fix L0s to L0 exit transition on SerDes <= rev9 devices. * * On these devices, PCIe/SerDes symbol lock can be lost if the * reference clock has not fully stabilized during the L0s to L0 * exit transition, triggering an internal reset of the chip. * * The SerDes RX CDR phase lock timers and proportional/integral * filters must be tweaked to ensure the CDR has fully stabilized * before asserting receive sequencer completion. */ BHNDB_PCIE_QUIRK_SDR9_L0s_HANG = (1<<8), /** * The idle time for entering L1 low-power state must be * explicitly set (to 114ns) to fix slow L1->L0 transition issues. */ BHNDB_PCIE_QUIRK_L1_IDLE_THRESH = (1<<9), /** * The ASPM L1 entry timer should be extended for better performance, * and restored for better power savings. */ BHNDB_PCIE_QUIRK_L1_TIMER_PERF = (1<<10), /** * ASPM and ECPM settings must be overridden manually. * * The override behavior is controlled by the BHND_BFL2_PCIEWAR_OVR * flag. If this flag is set, ASPM/CLKREQ should be overridden as * enabled; otherwise, they should be overridden as disabled. * * Attach/Resume: * - Set SRSH_ASPM_ENB flag in the SPROM ASPM register. * - Set ASPM L0S/L1 in the PCIER_LINK_CTL register. * - Set SRSH_CLKREQ_ENB flag in the SPROM CLKREQ_REV5 register. * - Clear ECPM in the PCIER_LINK_CTL register. * * Detach/Suspend: * - * - When the device enters D3 state, or system enters S3/S4 state, * clear ASPM L1 in the PCIER_LINK_CTL register. */ BHNDB_PCIE_QUIRK_ASPM_OVR = (1<<11), /** * Fix SerDes polarity on SerDes <= rev9 devices. * * The SerDes polarity must be saved at device attachment, and * restored on suspend/resume. */ BHNDB_PCIE_QUIRK_SDR9_POLARITY = (1<<12), /** * The SerDes PLL override flag (CHIPCTRL_4321_PLL_DOWN) must be set on * the ChipCommon core on resume. */ BHNDB_PCIE_QUIRK_SERDES_NOPLLDOWN = (1<<13), /** * On attach and resume, consult the SPROM to determine whether * the L2/L3-Ready w/o PCI RESET work-around must be applied. * * If L23READY_EXIT_NOPRST is not already set in the SPROM, set it */ BHNDB_PCIE_QUIRK_SPROM_L23_PCI_RESET = (1<<14), /** * The PCIe SerDes supports non-standard extended MDIO register access. * * The PCIe SerDes supports access to extended MDIO registers via * a non-standard Clause 22 address extension mechanism. */ BHNDB_PCIE_QUIRK_SD_C22_EXTADDR = (1<<15), /** * The PCIe SerDes PLL must be configured to not retry the startup * sequence upon frequency detection failure on SerDes <= rev9 devices * * The issue this workaround resolves has not be determined. */ BHNDB_PCIE_QUIRK_SDR9_NO_FREQRETRY = (1<<16), }; #endif /* _BHND_BHNDB_PCIVAR_H_ */ \ No newline at end of file Index: head/sys/dev/bhnd/cores/chipc/chipc.c =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipc.c (revision 298277) +++ head/sys/dev/bhnd/cores/chipc/chipc.c (revision 298278) @@ -1,319 +1,309 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. */ #include __FBSDID("$FreeBSD$"); /* * Broadcom ChipCommon driver. * * With the exception of some very early chipsets, the ChipCommon core * has been included in all HND SoCs and chipsets based on the siba(4) * and bcma(4) interconnects, providing a common interface to chipset * identification, bus enumeration, UARTs, clocks, watchdog interrupts, GPIO, * flash, etc. */ #include #include #include #include #include #include #include #include #include #include "chipcreg.h" #include "chipcvar.h" devclass_t bhnd_chipc_devclass; /**< bhnd(4) chipcommon device class */ static const struct resource_spec chipc_rspec[CHIPC_MAX_RSPEC] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, -1, 0 } }; +static struct bhnd_device_quirk chipc_quirks[]; + /* Supported device identifiers */ -static const struct chipc_device { - uint16_t device; -} chipc_devices[] = { - { BHND_COREID_CC }, - { BHND_COREID_INVALID } +static const struct bhnd_device chipc_devices[] = { + BHND_DEVICE(CC, "", chipc_quirks), + BHND_DEVICE_END }; + /* Device quirks table */ static struct bhnd_device_quirk chipc_quirks[] = { - BHND_QUIRK_HWREV_RANGE (0, 21, CHIPC_QUIRK_ALWAYS_HAS_SPROM), - BHND_QUIRK_HWREV_EQ (22, CHIPC_QUIRK_SPROM_CHECK_CST_R22), - BHND_QUIRK_HWREV_RANGE (23, 31, CHIPC_QUIRK_SPROM_CHECK_CST_R23), - BHND_QUIRK_HWREV_GTE (35, CHIPC_QUIRK_SUPPORTS_NFLASH), - BHND_QUIRK_HWREV_END + { BHND_HWREV_RANGE (0, 21), CHIPC_QUIRK_ALWAYS_HAS_SPROM }, + { BHND_HWREV_EQ (22), CHIPC_QUIRK_SPROM_CHECK_CST_R22 }, + { BHND_HWREV_RANGE (23, 31), CHIPC_QUIRK_SPROM_CHECK_CST_R23 }, + { BHND_HWREV_GTE (35), CHIPC_QUIRK_SUPPORTS_NFLASH }, + BHND_DEVICE_QUIRK_END }; /* quirk and capability flag convenience macros */ #define CHIPC_QUIRK(_sc, _name) \ ((_sc)->quirks & CHIPC_QUIRK_ ## _name) #define CHIPC_CAP(_sc, _name) \ ((_sc)->caps & CHIPC_ ## _name) #define CHIPC_ASSERT_QUIRK(_sc, name) \ KASSERT(CHIPC_QUIRK((_sc), name), ("quirk " __STRING(_name) " not set")) #define CHIPC_ASSERT_CAP(_sc, name) \ KASSERT(CHIPC_CAP((_sc), name), ("capability " __STRING(_name) " not set")) static int chipc_probe(device_t dev) { - const struct chipc_device *id; + const struct bhnd_device *id; - for (id = chipc_devices; id->device != BHND_COREID_INVALID; id++) - { - if (bhnd_get_vendor(dev) == BHND_MFGID_BCM && - bhnd_get_device(dev) == id->device) - { - bhnd_set_generic_core_desc(dev); - return (BUS_PROBE_DEFAULT); - } - } + id = bhnd_device_lookup(dev, chipc_devices, sizeof(chipc_devices[0])); + if (id == NULL) + return (ENXIO); - return (ENXIO); + bhnd_set_default_core_desc(dev); + return (BUS_PROBE_DEFAULT); } static int chipc_attach(device_t dev) { - struct bhnd_device_quirk *dq; struct chipc_softc *sc; bhnd_addr_t enum_addr; uint32_t ccid_reg; uint8_t chip_type; int error; sc = device_get_softc(dev); sc->dev = dev; + sc->quirks = bhnd_device_quirks(dev, chipc_devices, + sizeof(chipc_devices[0])); /* Allocate bus resources */ memcpy(sc->rspec, chipc_rspec, sizeof(sc->rspec)); if ((error = bhnd_alloc_resources(dev, sc->rspec, sc->res))) return (error); sc->core = sc->res[0]; /* Fetch our chipset identification data */ ccid_reg = bhnd_bus_read_4(sc->core, CHIPC_ID); chip_type = CHIPC_GET_ATTR(ccid_reg, ID_BUS); switch (chip_type) { case BHND_CHIPTYPE_SIBA: /* enumeration space starts at the ChipCommon register base. */ enum_addr = rman_get_start(sc->core->res); break; case BHND_CHIPTYPE_BCMA: case BHND_CHIPTYPE_BCMA_ALT: enum_addr = bhnd_bus_read_4(sc->core, CHIPC_EROMPTR); break; default: device_printf(dev, "unsupported chip type %hhu\n", chip_type); error = ENODEV; goto cleanup; } sc->ccid = bhnd_parse_chipid(ccid_reg, enum_addr); /* Fetch capability and status register values */ sc->caps = bhnd_bus_read_4(sc->core, CHIPC_CAPABILITIES); sc->cst = bhnd_bus_read_4(sc->core, CHIPC_CHIPST); - - /* Populate the set of applicable quirk flags */ - sc->quirks = 0; - for (dq = chipc_quirks; dq->quirks != 0; dq++) { - if (bhnd_hwrev_matches(bhnd_get_hwrev(dev), &dq->hwrev)) - sc->quirks |= dq->quirks; - } // TODO switch (bhnd_chipc_nvram_src(dev)) { case BHND_NVRAM_SRC_CIS: device_printf(dev, "NVRAM source: CIS\n"); break; case BHND_NVRAM_SRC_SPROM: device_printf(dev, "NVRAM source: SPROM\n"); break; case BHND_NVRAM_SRC_OTP: device_printf(dev, "NVRAM source: OTP\n"); break; case BHND_NVRAM_SRC_NFLASH: device_printf(dev, "NVRAM source: NFLASH\n"); break; case BHND_NVRAM_SRC_NONE: device_printf(dev, "NVRAM source: NONE\n"); break; } return (0); cleanup: bhnd_release_resources(dev, sc->rspec, sc->res); return (error); } static int chipc_detach(device_t dev) { struct chipc_softc *sc; sc = device_get_softc(dev); bhnd_release_resources(dev, sc->rspec, sc->res); return (0); } static int chipc_suspend(device_t dev) { return (0); } static int chipc_resume(device_t dev) { return (0); } /** * Use device-specific ChipStatus flags to determine the preferred NVRAM * data source. */ static bhnd_nvram_src_t chipc_nvram_src_chipst(struct chipc_softc *sc) { uint8_t nvram_sel; CHIPC_ASSERT_QUIRK(sc, SPROM_CHECK_CHIPST); if (CHIPC_QUIRK(sc, SPROM_CHECK_CST_R22)) { // TODO: On these devices, the official driver code always // assumes SPROM availability if CHIPC_CST_OTP_SEL is not // set; we must review against the actual behavior of our // BCM4312 hardware nvram_sel = CHIPC_GET_ATTR(sc->cst, CST_SPROM_OTP_SEL_R22); } else if (CHIPC_QUIRK(sc, SPROM_CHECK_CST_R23)) { nvram_sel = CHIPC_GET_ATTR(sc->cst, CST_SPROM_OTP_SEL_R23); } else { panic("invalid CST OTP/SPROM chipc quirk flags"); } device_printf(sc->dev, "querying chipst for 0x%x, 0x%x\n", sc->ccid.chip_id, sc->cst); switch (nvram_sel) { case CHIPC_CST_DEFCIS_SEL: return (BHND_NVRAM_SRC_CIS); case CHIPC_CST_SPROM_SEL: case CHIPC_CST_OTP_PWRDN: return (BHND_NVRAM_SRC_SPROM); case CHIPC_CST_OTP_SEL: return (BHND_NVRAM_SRC_OTP); default: device_printf(sc->dev, "unrecognized OTP/SPROM type 0x%hhx", nvram_sel); return (BHND_NVRAM_SRC_NONE); } } /** * Determine the preferred NVRAM data source. */ static bhnd_nvram_src_t chipc_nvram_src(device_t dev) { struct chipc_softc *sc; uint32_t srom_ctrl; sc = device_get_softc(dev); /* Very early devices always included a SPROM */ if (CHIPC_QUIRK(sc, ALWAYS_HAS_SPROM)) return (BHND_NVRAM_SRC_SPROM); /* Most other early devices require checking ChipStatus flags */ if (CHIPC_QUIRK(sc, SPROM_CHECK_CHIPST)) return (chipc_nvram_src_chipst(sc)); /* * Later chipset revisions standardized the NVRAM capability flags and * register interfaces. * * We check for hardware presence in order of precedence. For example, * SPROM is is always used in preference to internal OTP if found. */ if (CHIPC_CAP(sc, CAP_SPROM)) { srom_ctrl = bhnd_bus_read_4(sc->core, CHIPC_SPROM_CTRL); if (srom_ctrl & CHIPC_SRC_PRESENT) return (BHND_NVRAM_SRC_SPROM); } /* Check for OTP */ if (CHIPC_CAP(sc, CAP_OTP_SIZE)) return (BHND_NVRAM_SRC_OTP); /* * Finally, Northstar chipsets (and possibly other chipsets?) support * external NAND flash. */ if (CHIPC_QUIRK(sc, SUPPORTS_NFLASH) && CHIPC_CAP(sc, CAP_NFLASH)) return (BHND_NVRAM_SRC_NFLASH); /* No NVRAM hardware capability declared */ return (BHND_NVRAM_SRC_NONE); } static device_method_t chipc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, chipc_probe), DEVMETHOD(device_attach, chipc_attach), DEVMETHOD(device_detach, chipc_detach), DEVMETHOD(device_suspend, chipc_suspend), DEVMETHOD(device_resume, chipc_resume), /* ChipCommon interface */ DEVMETHOD(bhnd_chipc_nvram_src, chipc_nvram_src), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_chipc, chipc_driver, chipc_methods, sizeof(struct chipc_softc)); DRIVER_MODULE(bhnd_chipc, bhnd, chipc_driver, bhnd_chipc_devclass, 0, 0); MODULE_VERSION(bhnd_chipc, 1); Index: head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c =================================================================== --- head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c (revision 298277) +++ head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c (revision 298278) @@ -1,117 +1,117 @@ /*- * Copyright (c) 2015 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. */ #include __FBSDID("$FreeBSD$"); /* * Broadcom PCI-BHND Host Bridge. * * This driver is used to "eat" PCI(e) cores operating in endpoint mode when * they're attached to a bhndb_pci driver on the host side. */ #include #include #include #include #include #include #include #include #include struct bhnd_pci_hostb_softc { }; static int bhnd_pci_hostb_probe(device_t dev) { /* Ignore non-PCI cores */ switch (bhnd_get_class(dev)){ case BHND_DEVCLASS_PCI: case BHND_DEVCLASS_PCIE: break; default: return (ENXIO); } /* Ignore PCI cores not in host bridge mode. */ if (!bhnd_is_hostb_device(dev)) return (ENXIO); - bhnd_set_generic_core_desc(dev); + bhnd_set_default_core_desc(dev); return (BUS_PROBE_DEFAULT); } static int bhnd_pci_hostb_attach(device_t dev) { return (0); } static int bhnd_pci_hostb_detach(device_t dev) { return (0); } static int bhnd_pci_hostb_suspend(device_t dev) { return (0); } static int bhnd_pci_hostb_resume(device_t dev) { return (0); } static device_method_t bhnd_pci_hostb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhnd_pci_hostb_probe), DEVMETHOD(device_attach, bhnd_pci_hostb_attach), DEVMETHOD(device_detach, bhnd_pci_hostb_detach), DEVMETHOD(device_suspend, bhnd_pci_hostb_suspend), DEVMETHOD(device_resume, bhnd_pci_hostb_resume), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_pci_hostb, bhnd_pci_hostb_driver, bhnd_pci_hostb_methods, sizeof(struct bhnd_pci_hostb_softc)); DRIVER_MODULE(bhnd_hostb, bhnd, bhnd_pci_hostb_driver, bhnd_hostb_devclass, 0, 0); MODULE_VERSION(bhnd_pci_hostb, 1); MODULE_DEPEND(bhnd_pci_hostb, pci, 1, 1, 1); MODULE_DEPEND(bhnd_pci_hostb, bhnd_pci, 1, 1, 1); \ No newline at end of file