// SPDX-License-Identifier: GPL-2.0-only
/*
* Reset driver for the Mobileye EyeQ5 , EyeQ6L and EyeQ6H platforms .
*
* Controllers live in a shared register region called OLB . EyeQ5 and EyeQ6L
* have a single OLB instance for a single reset controller . EyeQ6H has seven
* OLB instances ; three host reset controllers .
*
* Each reset controller has one or more domain . Domains are of a given type
* ( see enum eqr_domain_type ) , with a valid offset mask ( up to 32 resets per
* domain ) .
*
* Domain types define expected behavior : one - register - per - reset ,
* one - bit - per - reset , status detection method , busywait duration , etc .
*
* We use eqr_ as prefix , as - in " EyeQ Reset " , but way shorter .
*
* Known resets in EyeQ5 domain 0 ( type EQR_EYEQ5_SARCR ) :
* 3 . CAN0 4 . CAN1 5 . CAN2 6 . SPI0
* 7 . SPI1 8 . SPI2 9 . SPI3 10 . UART0
* 11 . UART1 12 . UART2 13 . I2C0 14 . I2C1
* 15 . I2C2 16 . I2C3 17 . I2C4 18 . TIMER0
* 19 . TIMER1 20 . TIMER2 21 . TIMER3 22 . TIMER4
* 23 . WD0 24 . EXT0 25 . EXT1 26 . GPIO
* 27 . WD1
*
* Known resets in EyeQ5 domain 1 ( type EQR_EYEQ5_ACRP ) :
* 0 . VMP0 1 . VMP1 2 . VMP2 3 . VMP3
* 4 . PMA0 5 . PMA1 6 . PMAC0 7 . PMAC1
* 8 . MPC0 9 . MPC1 10 . MPC2 11 . MPC3
* 12 . MPC4
*
* Known resets in EyeQ5 domain 2 ( type EQR_EYEQ5_PCIE ) :
* 0 . PCIE0_CORE 1 . PCIE0_APB 2 . PCIE0_LINK_AXI 3 . PCIE0_LINK_MGMT
* 4 . PCIE0_LINK_HOT 5 . PCIE0_LINK_PIPE 6 . PCIE1_CORE 7 . PCIE1_APB
* 8 . PCIE1_LINK_AXI 9 . PCIE1_LINK_MGMT 10 . PCIE1_LINK_HOT 11 . PCIE1_LINK_PIPE
* 12 . MULTIPHY 13 . MULTIPHY_APB 15 . PCIE0_LINK_MGMT 16 . PCIE1_LINK_MGMT
* 17 . PCIE0_LINK_PM 18 . PCIE1_LINK_PM
*
* Known resets in EyeQ6L domain 0 ( type EQR_EYEQ5_SARCR ) :
* 0 . SPI0 1 . SPI1 2 . UART0 3 . I2C0
* 4 . I2C1 5 . TIMER0 6 . TIMER1 7 . TIMER2
* 8 . TIMER3 9 . WD0 10 . WD1 11 . EXT0
* 12 . EXT1 13 . GPIO
*
* Known resets in EyeQ6L domain 1 ( type EQR_EYEQ5_ACRP ) :
* 0 . VMP0 1 . VMP1 2 . VMP2 3 . VMP3
* 4 . PMA0 5 . PMA1 6 . PMAC0 7 . PMAC1
* 8 . MPC0 9 . MPC1 10 . MPC2 11 . MPC3
* 12 . MPC4
*
* Known resets in EyeQ6H west / east ( type EQR_EYEQ6H_SARCR ) :
* 0 . CAN 1 . SPI0 2 . SPI1 3 . UART0
* 4 . UART1 5 . I2C0 6 . I2C1 7 . - hole -
* 8 . TIMER0 9 . TIMER1 10 . WD 11 . EXT TIMER
* 12 . GPIO
*
* Known resets in EyeQ6H acc ( type EQR_EYEQ5_ACRP ) :
* 1 . XNN0 2 . XNN1 3 . XNN2 4 . XNN3
* 5 . VMP0 6 . VMP1 7 . VMP2 8 . VMP3
* 9 . PMA0 10 . PMA1 11 . MPC0 12 . MPC1
* 13 . MPC2 14 . MPC3 15 . PERIPH
*
* Abbreviations :
* - PMA : Programmable Macro Array
* - MPC : Multi - threaded Processing Clusters
* - VMP : Vector Microcode Processors
*
* Copyright ( C ) 2024 Mobileye Vision Technologies Ltd .
*/
#include <linux/array_size.h>
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/lockdep.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/reset-controller.h>
#include <linux/slab.h>
#include <linux/types.h>
/*
* A reset ID , as returned by eqr_of_xlate_ * ( ) , is a ( domain , offset ) pair .
* Low byte is domain , rest is offset .
*/
#define ID_DOMAIN_MASK GENMASK(7 , 0 )
#define ID_OFFSET_MASK GENMASK(31 , 8 )
enum eqr_domain_type {
EQR_EYEQ5_SARCR,
EQR_EYEQ5_ACRP,
EQR_EYEQ5_PCIE,
EQR_EYEQ6H_SARCR,
};
/*
* Domain type EQR_EYEQ5_SARCR register offsets .
*/
#define EQR_EYEQ5_SARCR_REQUEST (0 x000)
#define EQR_EYEQ5_SARCR_STATUS (0 x004)
/*
* Domain type EQR_EYEQ5_ACRP register masks .
* Registers are : base + 4 * offset .
*/
#define EQR_EYEQ5_ACRP_PD_REQ BIT(0 )
#define EQR_EYEQ5_ACRP_ST_POWER_DOWN BIT(27 )
#define EQR_EYEQ5_ACRP_ST_ACTIVE BIT(29 )
/*
* Domain type EQR_EYEQ6H_SARCR register offsets .
*/
#define EQR_EYEQ6H_SARCR_RST_REQUEST (0 x000)
#define EQR_EYEQ6H_SARCR_CLK_STATUS (0 x004)
#define EQR_EYEQ6H_SARCR_RST_STATUS (0 x008)
#define EQR_EYEQ6H_SARCR_CLK_REQUEST (0 x00C)
struct eqr_busy_wait_timings {
unsigned long sleep_us;
unsigned long timeout_us;
};
static const struct eqr_busy_wait_timings eqr_timings[] = {
[EQR_EYEQ5_SARCR] = {1 , 10 },
[EQR_EYEQ5_ACRP] = {1 , 40 * USEC_PER_MSEC}, /* LBIST implies long timeout. */
/* EQR_EYEQ5_PCIE does no busy waiting. */
[EQR_EYEQ6H_SARCR] = {1 , 400 },
};
#define EQR_MAX_DOMAIN_COUNT 3
struct eqr_domain_descriptor {
enum eqr_domain_type type;
u32 valid_mask;
unsigned int offset;
};
struct eqr_match_data {
unsigned int domain_count;
const struct eqr_domain_descriptor *domains;
};
struct eqr_private {
/*
* One mutex per domain for read - modify - write operations on registers .
* Some domains can be involved in LBIST which implies long critical
* sections ; we wouldn ' t want other domains to be impacted by that .
*/
struct mutex mutexes[EQR_MAX_DOMAIN_COUNT];
void __iomem *base;
const struct eqr_match_data *data;
struct reset_controller_dev rcdev;
};
static inline struct eqr_private *eqr_rcdev_to_priv(struct reset_controller_dev *x)
{
return container_of(x, struct eqr_private, rcdev);
}
static u32 eqr_double_readl(void __iomem *addr_a, void __iomem *addr_b,
u32 *dest_a, u32 *dest_b)
{
*dest_a = readl(addr_a);
*dest_b = readl(addr_b);
return 0 ; /* read_poll_timeout() op argument must return something. */
}
static int eqr_busy_wait_locked(struct eqr_private *priv, struct device *dev,
u32 domain, u32 offset, bool assert)
{
void __iomem *base = priv->base + priv->data->domains[domain].offset;
enum eqr_domain_type domain_type = priv->data->domains[domain].type;
unsigned long timeout_us = eqr_timings[domain_type].timeout_us;
unsigned long sleep_us = eqr_timings[domain_type].sleep_us;
u32 val, mask, rst_status, clk_status;
void __iomem *reg;
int ret;
lockdep_assert_held(&priv->mutexes[domain]);
switch (domain_type) {
case EQR_EYEQ5_SARCR:
reg = base + EQR_EYEQ5_SARCR_STATUS;
mask = BIT(offset);
ret = readl_poll_timeout(reg, val, !(val & mask) == assert,
sleep_us, timeout_us);
break ;
case EQR_EYEQ5_ACRP:
reg = base + 4 * offset;
if (assert)
mask = EQR_EYEQ5_ACRP_ST_POWER_DOWN;
else
mask = EQR_EYEQ5_ACRP_ST_ACTIVE;
ret = readl_poll_timeout(reg, val, !!(val & mask),
sleep_us, timeout_us);
break ;
case EQR_EYEQ5_PCIE:
ret = 0 ; /* No busy waiting. */
break ;
case EQR_EYEQ6H_SARCR:
/*
* Wait until both bits change :
* readl ( base + EQR_EYEQ6H_SARCR_RST_STATUS ) & BIT ( offset )
* readl ( base + EQR_EYEQ6H_SARCR_CLK_STATUS ) & BIT ( offset )
*/
mask = BIT(offset);
ret = read_poll_timeout(eqr_double_readl, val,
(!(rst_status & mask) == assert) &&
(!(clk_status & mask) == assert),
sleep_us, timeout_us, false ,
base + EQR_EYEQ6H_SARCR_RST_STATUS,
base + EQR_EYEQ6H_SARCR_CLK_STATUS,
&rst_status, &clk_status);
break ;
default :
WARN_ON(1 );
ret = -EINVAL;
break ;
}
if (ret == -ETIMEDOUT)
dev_dbg(dev, "%u-%u: timeout\n" , domain, offset);
return ret;
}
static void eqr_assert_locked(struct eqr_private *priv, u32 domain, u32 offset)
{
enum eqr_domain_type domain_type = priv->data->domains[domain].type;
void __iomem *base, *reg;
u32 val;
lockdep_assert_held(&priv->mutexes[domain]);
base = priv->base + priv->data->domains[domain].offset;
switch (domain_type) {
case EQR_EYEQ5_SARCR:
reg = base + EQR_EYEQ5_SARCR_REQUEST;
writel(readl(reg) & ~BIT(offset), reg);
break ;
case EQR_EYEQ5_ACRP:
reg = base + 4 * offset;
writel(readl(reg) | EQR_EYEQ5_ACRP_PD_REQ, reg);
break ;
case EQR_EYEQ5_PCIE:
writel(readl(base) & ~BIT(offset), base);
break ;
case EQR_EYEQ6H_SARCR:
/* RST_REQUEST and CLK_REQUEST must be kept in sync. */
val = readl(base + EQR_EYEQ6H_SARCR_RST_REQUEST);
val &= ~BIT(offset);
writel(val, base + EQR_EYEQ6H_SARCR_RST_REQUEST);
writel(val, base + EQR_EYEQ6H_SARCR_CLK_REQUEST);
break ;
default :
WARN_ON(1 );
break ;
}
}
static int eqr_assert(struct reset_controller_dev *rcdev, unsigned long id)
{
struct eqr_private *priv = eqr_rcdev_to_priv(rcdev);
u32 domain = FIELD_GET(ID_DOMAIN_MASK, id);
u32 offset = FIELD_GET(ID_OFFSET_MASK, id);
dev_dbg(rcdev->dev, "%u-%u: assert request\n" , domain, offset);
guard(mutex)(&priv->mutexes[domain]);
eqr_assert_locked(priv, domain, offset);
return eqr_busy_wait_locked(priv, rcdev->dev, domain, offset, true );
}
static void eqr_deassert_locked(struct eqr_private *priv, u32 domain,
u32 offset)
{
enum eqr_domain_type domain_type = priv->data->domains[domain].type;
void __iomem *base, *reg;
u32 val;
lockdep_assert_held(&priv->mutexes[domain]);
base = priv->base + priv->data->domains[domain].offset;
switch (domain_type) {
case EQR_EYEQ5_SARCR:
reg = base + EQR_EYEQ5_SARCR_REQUEST;
writel(readl(reg) | BIT(offset), reg);
break ;
case EQR_EYEQ5_ACRP:
reg = base + 4 * offset;
writel(readl(reg) & ~EQR_EYEQ5_ACRP_PD_REQ, reg);
break ;
case EQR_EYEQ5_PCIE:
writel(readl(base) | BIT(offset), base);
break ;
case EQR_EYEQ6H_SARCR:
/* RST_REQUEST and CLK_REQUEST must be kept in sync. */
val = readl(base + EQR_EYEQ6H_SARCR_RST_REQUEST);
val |= BIT(offset);
writel(val, base + EQR_EYEQ6H_SARCR_RST_REQUEST);
writel(val, base + EQR_EYEQ6H_SARCR_CLK_REQUEST);
break ;
default :
WARN_ON(1 );
break ;
}
}
static int eqr_deassert(struct reset_controller_dev *rcdev, unsigned long id)
{
struct eqr_private *priv = eqr_rcdev_to_priv(rcdev);
u32 domain = FIELD_GET(ID_DOMAIN_MASK, id);
u32 offset = FIELD_GET(ID_OFFSET_MASK, id);
dev_dbg(rcdev->dev, "%u-%u: deassert request\n" , domain, offset);
guard(mutex)(&priv->mutexes[domain]);
eqr_deassert_locked(priv, domain, offset);
return eqr_busy_wait_locked(priv, rcdev->dev, domain, offset, false );
}
static int eqr_status(struct reset_controller_dev *rcdev, unsigned long id)
{
u32 domain = FIELD_GET(ID_DOMAIN_MASK, id);
u32 offset = FIELD_GET(ID_OFFSET_MASK, id);
struct eqr_private *priv = eqr_rcdev_to_priv(rcdev);
enum eqr_domain_type domain_type = priv->data->domains[domain].type;
void __iomem *base, *reg;
dev_dbg(rcdev->dev, "%u-%u: status request\n" , domain, offset);
guard(mutex)(&priv->mutexes[domain]);
base = priv->base + priv->data->domains[domain].offset;
switch (domain_type) {
case EQR_EYEQ5_SARCR:
reg = base + EQR_EYEQ5_SARCR_STATUS;
return !(readl(reg) & BIT(offset));
case EQR_EYEQ5_ACRP:
reg = base + 4 * offset;
return !(readl(reg) & EQR_EYEQ5_ACRP_ST_ACTIVE);
case EQR_EYEQ5_PCIE:
return !(readl(base) & BIT(offset));
case EQR_EYEQ6H_SARCR:
reg = base + EQR_EYEQ6H_SARCR_RST_STATUS;
return !(readl(reg) & BIT(offset));
default :
return -EINVAL;
}
}
static const struct reset_control_ops eqr_ops = {
.assert = eqr_assert,
.deassert = eqr_deassert,
.status = eqr_status,
};
static int eqr_of_xlate_internal(struct reset_controller_dev *rcdev,
u32 domain, u32 offset)
{
struct eqr_private *priv = eqr_rcdev_to_priv(rcdev);
if (domain >= priv->data->domain_count || offset > 31 ||
!(priv->data->domains[domain].valid_mask & BIT(offset))) {
dev_err(rcdev->dev, "%u-%u: invalid reset\n" , domain, offset);
return -EINVAL;
}
return FIELD_PREP(ID_DOMAIN_MASK, domain) | FIELD_PREP(ID_OFFSET_MASK, offset);
}
static int eqr_of_xlate_onecell(struct reset_controller_dev *rcdev,
const struct of_phandle_args *reset_spec)
{
return eqr_of_xlate_internal(rcdev, 0 , reset_spec->args[0 ]);
}
static int eqr_of_xlate_twocells(struct reset_controller_dev *rcdev,
const struct of_phandle_args *reset_spec)
{
return eqr_of_xlate_internal(rcdev, reset_spec->args[0 ], reset_spec->args[1 ]);
}
static void eqr_of_node_put(void *_dev)
{
struct device *dev = _dev;
of_node_put(dev->of_node);
}
static int eqr_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
const struct of_device_id *match;
struct device *dev = &adev->dev;
struct eqr_private *priv;
unsigned int i;
int ret;
/*
* We are an auxiliary device of clk - eyeq . We do not have an OF node by
* default ; let ' s reuse our parent ' s OF node .
*/
WARN_ON(dev->of_node);
device_set_of_node_from_dev(dev, dev->parent);
if (!dev->of_node)
return -ENODEV;
ret = devm_add_action_or_reset(dev, eqr_of_node_put, dev);
if (ret)
return ret;
/*
* Using our newfound OF node , we can get match data . We cannot use
* device_get_match_data ( ) because it does not match reused OF nodes .
*/
match = of_match_node(dev->driver->of_match_table, dev->of_node);
if (!match || !match->data)
return -ENODEV;
priv = devm_kzalloc(dev, sizeof (*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->data = match->data;
priv->base = (void __iomem *)dev_get_platdata(dev);
priv->rcdev.ops = &eqr_ops;
priv->rcdev.owner = THIS_MODULE;
priv->rcdev.dev = dev;
priv->rcdev.of_node = dev->of_node;
if (priv->data->domain_count == 1 ) {
priv->rcdev.of_reset_n_cells = 1 ;
priv->rcdev.of_xlate = eqr_of_xlate_onecell;
} else {
priv->rcdev.of_reset_n_cells = 2 ;
priv->rcdev.of_xlate = eqr_of_xlate_twocells;
}
for (i = 0 ; i < priv->data->domain_count; i++)
mutex_init(&priv->mutexes[i]);
priv->rcdev.nr_resets = 0 ;
for (i = 0 ; i < priv->data->domain_count; i++)
priv->rcdev.nr_resets += hweight32(priv->data->domains[i].valid_mask);
ret = devm_reset_controller_register(dev, &priv->rcdev);
if (ret)
return dev_err_probe(dev, ret, "failed registering reset controller\n" );
return 0 ;
}
static const struct eqr_domain_descriptor eqr_eyeq5_domains[] = {
{
.type = EQR_EYEQ5_SARCR,
.valid_mask = 0 xFFFFFF8,
.offset = 0 x004,
},
{
.type = EQR_EYEQ5_ACRP,
.valid_mask = 0 x0001FFF,
.offset = 0 x200,
},
{
.type = EQR_EYEQ5_PCIE,
.valid_mask = 0 x007BFFF,
.offset = 0 x120,
},
};
static const struct eqr_match_data eqr_eyeq5_data = {
.domain_count = ARRAY_SIZE(eqr_eyeq5_domains),
.domains = eqr_eyeq5_domains,
};
static const struct eqr_domain_descriptor eqr_eyeq6l_domains[] = {
{
.type = EQR_EYEQ5_SARCR,
.valid_mask = 0 x3FFF,
.offset = 0 x004,
},
{
.type = EQR_EYEQ5_ACRP,
.valid_mask = 0 x00FF,
.offset = 0 x200,
},
};
static const struct eqr_match_data eqr_eyeq6l_data = {
.domain_count = ARRAY_SIZE(eqr_eyeq6l_domains),
.domains = eqr_eyeq6l_domains,
};
/* West and east OLBs each have an instance. */
static const struct eqr_domain_descriptor eqr_eyeq6h_we_domains[] = {
{
.type = EQR_EYEQ6H_SARCR,
.valid_mask = 0 x1F7F,
.offset = 0 x004,
},
};
static const struct eqr_match_data eqr_eyeq6h_we_data = {
.domain_count = ARRAY_SIZE(eqr_eyeq6h_we_domains),
.domains = eqr_eyeq6h_we_domains,
};
static const struct eqr_domain_descriptor eqr_eyeq6h_acc_domains[] = {
{
.type = EQR_EYEQ5_ACRP,
.valid_mask = 0 x7FFF,
.offset = 0 x000,
},
};
static const struct eqr_match_data eqr_eyeq6h_acc_data = {
.domain_count = ARRAY_SIZE(eqr_eyeq6h_acc_domains),
.domains = eqr_eyeq6h_acc_domains,
};
/*
* Table describes OLB system - controller compatibles .
* It does not get used to match against devicetree node .
*/
static const struct of_device_id eqr_match_table[] = {
{ .compatible = "mobileye,eyeq5-olb" , .data = &eqr_eyeq5_data },
{ .compatible = "mobileye,eyeq6l-olb" , .data = &eqr_eyeq6l_data },
{ .compatible = "mobileye,eyeq6h-west-olb" , .data = &eqr_eyeq6h_we_data },
{ .compatible = "mobileye,eyeq6h-east-olb" , .data = &eqr_eyeq6h_we_data },
{ .compatible = "mobileye,eyeq6h-acc-olb" , .data = &eqr_eyeq6h_acc_data },
{}
};
MODULE_DEVICE_TABLE(of, eqr_match_table);
static const struct auxiliary_device_id eqr_id_table[] = {
{ .name = "clk_eyeq.reset" },
{ .name = "clk_eyeq.reset_west" },
{ .name = "clk_eyeq.reset_east" },
{ .name = "clk_eyeq.reset_acc" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, eqr_id_table);
static struct auxiliary_driver eqr_driver = {
.probe = eqr_probe,
.id_table = eqr_id_table,
.driver = {
.of_match_table = eqr_match_table,
}
};
module_auxiliary_driver(eqr_driver);
Messung V0.5 in Prozent C=97 H=99 G=97
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland