incubator-nuttx/drivers/reset/core.c

993 lines
26 KiB
C

/****************************************************************************
* drivers/reset/core.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <errno.h>
#include <debug.h>
#include <nuttx/nuttx.h>
#include <nuttx/kmalloc.h>
#include <nuttx/reset/reset.h>
#include <nuttx/reset/reset-controller.h>
/****************************************************************************
* Private Data
****************************************************************************/
static struct list_node
g_reset_controller_list = LIST_INITIAL_VALUE(g_reset_controller_list);
static mutex_t g_reset_list_mutex = NXMUTEX_INITIALIZER;
/****************************************************************************
* Private Types
****************************************************************************/
/* struct reset_control_array - an array of reset controls
* @base: reset control for compatibility with reset control API functions
* @num_rstcs: number of reset controls
* @rstc: array of reset controls
*/
struct reset_control_array
{
struct reset_control base;
unsigned int num_rstcs;
FAR struct reset_control *rstc[];
};
/****************************************************************************
* Private Functions
****************************************************************************/
static inline FAR struct reset_control_array *
rstc_to_array(FAR struct reset_control *rstc)
{
return container_of(rstc, struct reset_control_array, base);
}
/****************************************************************************
* Name: reset_control_array_reset
*
* Description:
* This function is used to perform reset operation of a reset control
* array.
*
* Input Parameters:
* resets - An instance of a reset_control_array.
*
* Returned Value:
* A 0 in case of success, otherwise error.
****************************************************************************/
static int reset_control_array_reset(FAR struct reset_control_array *resets)
{
int ret = 0;
unsigned int i;
for (i = 0; i < resets->num_rstcs; i++)
{
ret = reset_control_reset(resets->rstc[i]);
if (ret < 0)
{
return ret;
}
}
return ret;
}
/****************************************************************************
* Name: reset_control_array_assert
*
* Description:
* This function is used to perform assert operation of a reset control
* array.
*
* Input Parameters:
* resets - An instance of a reset_control_array.
*
* Returned Value:
* A 0 in case of success, otherwise error.
****************************************************************************/
static int reset_control_array_assert(FAR struct reset_control_array *resets)
{
int ret = 0;
unsigned int i;
for (i = 0; i < resets->num_rstcs; i++)
{
ret = reset_control_assert(resets->rstc[i]);
if (ret < 0)
{
while (i--)
{
reset_control_deassert(resets->rstc[i]);
}
break;
}
}
return ret;
}
/****************************************************************************
* Name: reset_control_array_deassert
*
* Description:
* This function is used to perform deassert operation of a reset control
* array.
*
* Input Parameters:
* resets - An instance of a reset_control_array.
*
* Returned Value:
* A 0 in case of success, an negative error code otherwise.
****************************************************************************/
static int
reset_control_array_deassert(FAR struct reset_control_array *resets)
{
int ret = 0;
unsigned int i;
for (i = 0; i < resets->num_rstcs; i++)
{
ret = reset_control_deassert(resets->rstc[i]);
if (ret < 0)
{
while (i--)
{
reset_control_assert(resets->rstc[i]);
}
break;
}
}
return ret;
}
/****************************************************************************
* Name: reset_control_array_acquire
*
* Description:
* This function is used to perform acquire operation of a reset control
* array.
*
* Input Parameters:
* resets - An instance of a reset_control_array.
*
* Returned Value:
* A 0 in case of success, an negative error code otherwise.
****************************************************************************/
static int
reset_control_array_acquire(FAR struct reset_control_array *resets)
{
unsigned int i;
unsigned int ret = 0;
for (i = 0; i < resets->num_rstcs; i++)
{
ret = reset_control_acquire(resets->rstc[i]);
if (ret < 0)
{
while (i--)
{
reset_control_release(resets->rstc[i]);
}
break;
}
}
return ret;
}
/****************************************************************************
* Name: reset_control_array_release
*
* Description:
* This function is used to perform release operation of a reset control
* array.
*
* Input Parameters:
* resets - An instance of a reset_control_array.
****************************************************************************/
static void
reset_control_array_release(FAR struct reset_control_array *resets)
{
unsigned int i;
for (i = 0; i < resets->num_rstcs; i++)
{
reset_control_release(resets->rstc[i]);
}
}
/****************************************************************************
* Name: reset_control_get_internal
*
* Description:
* This function is used to return a reset_control by rcdev,index,shared,
* acquired parameters.
*
* Input Parameters:
* rcdev - An instance of reset_controller_dev type.
* index - ID of the reset controller in the reset controller device.
* shared - Is this a shared (1), or an exclusive (0) reset_control.
* acquired - Only one reset_control may be acquired for a given rcdev and
* index.
*
* Returned Value:
* Return reset_control if success, others NULL.
****************************************************************************/
static FAR struct reset_control *
reset_control_get_internal(FAR struct reset_controller_dev *rcdev,
unsigned int index, bool shared, bool acquired)
{
FAR struct reset_control *rstc;
DEBUGASSERT(nxmutex_is_locked(&g_reset_list_mutex));
list_for_every_entry(&rcdev->reset_control_head, rstc,
struct reset_control, list)
{
if (rstc->id == index)
{
/* Allow creating a secondary exclusive reset_control
* that is initially not acquired for an already
* controlled reset line.
*/
if (!rstc->shared && !shared && !acquired)
{
break;
}
/* shared reset controller */
if (!rstc->shared || !shared)
{
rsterr("not shared reset control\n");
return NULL;
}
atomic_fetch_add(&rstc->refcnt, 1);
return rstc;
}
}
rstc = kmm_zalloc(sizeof(*rstc));
if (!rstc)
{
return NULL;
}
rstc->rcdev = rcdev;
list_add_after(&rcdev->reset_control_head, &rstc->list);
rstc->id = index;
atomic_init(&rstc->refcnt, 1);
rstc->acquired = acquired;
rstc->shared = shared;
return rstc;
}
/****************************************************************************
* Name: reset_control_put_internal
*
* Description:
* This is used to free a reset_control getted by reset_control_get.
*
* Input Parameters:
* rstc - An reset control
*
****************************************************************************/
static void reset_control_put_internal(FAR struct reset_control *rstc)
{
DEBUGASSERT(nxmutex_is_locked(&g_reset_list_mutex));
if (atomic_fetch_sub(&rstc->refcnt, 1) == 1)
{
DEBUGASSERT(nxmutex_is_locked(&g_reset_list_mutex));
list_delete(&rstc->list);
kmm_free(rstc);
}
}
/****************************************************************************
* Name: reset_control_array_put
*
* Description:
* This function is used to perform a free operation of reset
* control array.
*
* Input Parameters:
* resets - An instance of reset_control_array.
*
****************************************************************************/
static void reset_control_array_put(FAR struct reset_control_array *resets)
{
unsigned int i;
nxmutex_lock(&g_reset_list_mutex);
for (i = 0; i < resets->num_rstcs; i++)
{
reset_control_put_internal(resets->rstc[i]);
}
nxmutex_unlock(&g_reset_list_mutex);
kmm_free(resets);
}
/****************************************************************************
* Name: reset_controller_get_by_name
*
* Description:
* This function is used to get a reset_controller_dev instance register
* by name.
*
* Input Parameters:
* name - An name of reset_controller_dev var register.
*
* Returned Value:
* Return reset_controller_dev if success, others failed.
****************************************************************************/
static FAR struct reset_controller_dev *
reset_controller_get_by_name(FAR const char *name)
{
FAR struct reset_controller_dev *rcdev;
nxmutex_lock(&g_reset_list_mutex);
list_for_every_entry(&g_reset_controller_list, rcdev,
struct reset_controller_dev, list)
{
if (!strcmp(name, rcdev->name))
{
nxmutex_unlock(&g_reset_list_mutex);
return rcdev;
}
}
nxmutex_unlock(&g_reset_list_mutex);
return NULL;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: reset_control_get
*
* Description:
* This function is used to get a reset control by reset controller name.
*
* Firstly, get a reset controller device from list, and then call
* reset_control_get_internal function by index, shared or acquired
* parameters retrun a reset control.
*
* Input Parameters:
* name - The reset controller name
* index - The reset controller in reset controller device
* shared - Is this a shared (1), or an exclusive (0) reset_control
* acquired - Flags that used to get a exculsive reset control
*
* Returned Value:
* Return reset_control if success, others return NULL if failed
****************************************************************************/
FAR struct reset_control *
reset_control_get(FAR const char *name, int index, bool shared,
bool acquired)
{
FAR struct reset_control *rstc;
FAR struct reset_controller_dev *rcdev;
if (name == NULL)
{
return NULL;
}
/* ID of the reset controller in the reset controller device */
if (index < 0)
{
rsterr("ID of the reset controller is invalid\n");
return NULL;
}
if (shared && acquired)
{
rsterr("shared && acquired exist meanwhile\n");
return NULL;
}
rcdev = reset_controller_get_by_name(name);
if (!rcdev)
{
return NULL;
}
nxmutex_lock(&g_reset_list_mutex);
/* g_reset_list_mutex also protects the rcdev's reset_control list */
rstc = reset_control_get_internal(rcdev, index, shared, acquired);
nxmutex_unlock(&g_reset_list_mutex);
return rstc;
}
/****************************************************************************
* Name: reset_control_reset
*
* Description:
* On a shared reset line the actual reset pulse is only triggered once for
* the lifetime of the reset_control instance: for all but the first caller
* this is a no-op.
* Consumers must not use reset_control_(de)assert on shared reset lines
* when reset_control_reset has been used.
*
* Input Parameters:
* rstc - Reset controller
*
* Returned Value:
* Returns a negative errno if not supported, a positive value if the
* reset line is asserted.
****************************************************************************/
int reset_control_reset(FAR struct reset_control *rstc)
{
int ret;
rstinfo("Enter: reset_control_reset\n");
if (rstc == NULL)
{
rsterr("rstc is null\n");
return -EINVAL;
}
if (rstc->array)
{
return reset_control_array_reset(rstc_to_array(rstc));
}
if (!rstc->rcdev->ops->reset)
{
rsterr("rstc callback is null\n");
return -ENOTSUP;
}
if (rstc->shared)
{
if (atomic_load(&rstc->deassert_count) != 0)
{
return -EINVAL;
}
if (atomic_fetch_add(&rstc->triggered_count, 1) != 0)
{
return 0;
}
}
else
{
if (!rstc->acquired)
{
return -EPERM;
}
}
ret = rstc->rcdev->ops->reset(rstc->rcdev, rstc->id);
/* shared:1 and reset failed, triggered_count subtract 1 */
if (rstc->shared && ret < 0)
{
atomic_fetch_sub(&rstc->triggered_count, 1);
}
return ret;
}
/****************************************************************************
* Name: reset_control_assert
*
* Description:
* Asserts the reset line.
*
* Calling this on an exclusive reset controller guarantees that the reset
* will be asserted. When called on a shared reset controller the line may
* still be deasserted, as long as other users keep it so.
*
* For shared reset controls a driver cannot expect the hw's registers and
* internal state to be reset, but must be prepared for this to happen.
* Consumers must not use reset_control_reset on shared reset lines when
* reset_control_(de)assert has been used.
* return -EBUSY.
*
* Input Parameters:
* rstc - Reset controller
*
* Returned Value:
* Returns a negative errno if not supported, a positive value if the
* reset line is asserted.
****************************************************************************/
int reset_control_assert(FAR struct reset_control *rstc)
{
rstinfo("Enter: reset_control_assert\n");
if (rstc == NULL)
{
rsterr("rstc is null\n");
return -EINVAL;
}
if (rstc->array)
{
return reset_control_array_assert(rstc_to_array(rstc));
}
if (rstc->shared)
{
if (atomic_load(&rstc->triggered_count) != 0)
{
return -EINVAL;
}
if (atomic_load(&rstc->deassert_count) == 0)
{
rsterr("deassert_count = 0, invalid value\n");
return -EINVAL;
}
if (atomic_fetch_sub(&rstc->deassert_count, 1) != 1)
{
return 0;
}
/* Shared reset controls allow the reset line to be in any state
* after this call, so doing nothing is a valid option.
*/
if (!rstc->rcdev->ops->assert)
{
return -EBUSY;
}
}
else
{
/* If the reset controller does not implement .assert(), there
* is no way to guarantee that the reset line is asserted after
* this call.
*/
if (!rstc->rcdev->ops->assert)
{
return -ENOTSUP;
}
if (!rstc->acquired)
{
rsterr("reset %s (ID: %u) is not acquired\n",
rstc->rcdev->name, rstc->id);
return -EPERM;
}
}
#undef assert
return rstc->rcdev->ops->assert(rstc->rcdev, rstc->id);
}
/****************************************************************************
* Name: reset_control_deassert
*
* Description:
* Deasserts the reset line.
* After calling this function, the reset is guaranteed to be deasserted.
* Consumers must not use reset_control_reset on shared reset lines when
* reset_control_(de)assert has been used.
* return -EBUSY.
*
* Input Parameters:
* rstc - Reset controller
*
* Returned Value:
* Returns a negative errno if not supported, a positive value if the
* reset line is asserted.
*
****************************************************************************/
int reset_control_deassert(FAR struct reset_control *rstc)
{
rstinfo("Enter: reset_control_deassert\n");
if (!rstc)
{
rsterr("rstc is null\n");
return -EINVAL;
}
if (rstc->array)
{
return reset_control_array_deassert(rstc_to_array(rstc));
}
if (rstc->shared)
{
if (atomic_load(&rstc->triggered_count) != 0)
{
rsterr("triggered_count != 0, invalid value\n");
return -EINVAL;
}
if (atomic_fetch_add(&rstc->deassert_count, 1) != 0)
{
return 0;
}
}
else
{
if (!rstc->acquired)
{
rsterr("reset %s (ID: %u) is not acquired\n",
rstc->rcdev->name, rstc->id);
return -EPERM;
}
}
/* If the reset controller does not implement .deassert(), we assume
* that it handles self-deasserting reset lines via .reset(). In that
* case, the reset lines are deasserted by default. If that is not the
* case, the reset controller driver should implement .deassert() and
* return -ENOTSUP.
*/
if (!rstc->rcdev->ops->deassert)
{
return -ESRCH ;
}
return rstc->rcdev->ops->deassert(rstc->rcdev, rstc->id);
}
/****************************************************************************
* Name: reset_control_status
*
* Description:
* Get the reset line status.
*
* Input Parameters:
* rstc - Reset controller
*
* Returned Value:
* Returns a negative errno if not supported, a positive value if the
* reset line is asserted.
****************************************************************************/
int reset_control_status(FAR struct reset_control *rstc)
{
if (rstc == NULL)
{
return -EINVAL;
}
if (rstc->array)
{
return -EINVAL;
}
if (rstc->rcdev->ops->status)
{
return rstc->rcdev->ops->status(rstc->rcdev, rstc->id);
}
return -ENOTSUP;
}
/****************************************************************************
* Name: reset_control_acquire()
*
* Description:
* Acquires a reset control for exclusive use.
* This is used to explicitly acquire a reset control for exclusive use.
* Note that exclusive resets are requested as acquired by default.
* In order for a second consumer to be able to control the reset, the
* first consumer has to release it first.
* Typically the easiest way to achieve this is to call the
* reset_control_get_exclusive_released() to obtain an instance of the
* reset control. Such reset controls are not acquired by default.
*
* Consumers implementing shared access to an exclusive reset need to
* follow a specific protocol in order to work together. Before consumers
* can change a reset they must acquire exclusive access using
* reset_control_acquire().
* After they are done operating the reset, they must release exclusive
* access with a call to reset_control_release(). Consumers are not
* granted exclusive access to the reset as long as another consumer
* hasn't released a reset.
* See also: reset_control_release()
*
* Input Parameters:
* rstc - Reset control
*
* Returned Value:
* Returns a negative errno if not supported, a positive value if the
* reset line is asserted.
****************************************************************************/
int reset_control_acquire(FAR struct reset_control *rstc)
{
FAR struct reset_control *rc;
if (rstc == NULL)
{
return 0;
}
if (rstc->array)
{
return reset_control_array_acquire(rstc_to_array(rstc));
}
nxmutex_lock(&g_reset_list_mutex);
if (rstc->acquired)
{
nxmutex_unlock(&g_reset_list_mutex);
return 0;
}
list_for_every_entry(&rstc->rcdev->reset_control_head, rc,
struct reset_control, list)
{
if (rstc != rc && rstc->id == rc->id)
{
if (rc->acquired)
{
nxmutex_unlock(&g_reset_list_mutex);
return -EBUSY;
}
}
}
rstc->acquired = true;
nxmutex_unlock(&g_reset_list_mutex);
return 0;
}
/****************************************************************************
* Name: reset_control_release()
*
* Discription:
* Releases exclusive access to a reset control.
*
* Releases exclusive access right to a reset control previously obtained
* by a call to reset_control_acquire(). Until a consumer calls this
* function, no other consumers will be granted exclusive access.
*
* See also: reset_control_acquire()
*
* Input Parameters:
* rstc - Reset control
****************************************************************************/
void reset_control_release(FAR struct reset_control *rstc)
{
if (rstc == NULL)
{
return;
}
if (rstc->array)
{
reset_control_array_release(rstc_to_array(rstc));
}
else
{
rstc->acquired = false;
}
}
/****************************************************************************
* Name: reset_control_put
*
* Description:
* Free the reset control.
*
* Input Parameters:
* rstc - Reset controller
****************************************************************************/
void reset_control_put(FAR struct reset_control *rstc)
{
if (!rstc)
{
return;
}
if (rstc->array)
{
reset_control_array_put(rstc_to_array(rstc));
return;
}
nxmutex_lock(&g_reset_list_mutex);
reset_control_put_internal(rstc);
nxmutex_unlock(&g_reset_list_mutex);
}
/****************************************************************************
* Name: reset_control_array_get
*
* Description:
* Get a list of reset controls using device node.
*
* Input Parameters:
* name - The reset controller name
* shared - Whether reset controls are shared or not
* acquired - Only one reset control may be acquired for a given controller
* and ID
*
* Returned Value:
* Returns pointer to allocated reset_control on success or error on
* failure
****************************************************************************/
FAR struct reset_control *
reset_control_array_get(FAR const char *name, const int id[],
const unsigned int num, bool shared, bool acquired)
{
FAR struct reset_control_array *resets;
FAR struct reset_control *rstc;
unsigned int i;
resets = kmm_zalloc(sizeof(struct reset_control_array) +
sizeof(struct reset_control) * num);
if (!resets)
{
return NULL;
}
for (i = 0; i < num; i++)
{
rstc = reset_control_get(name, id[i], shared, acquired);
if (rstc == NULL)
{
goto err_rst;
}
resets->rstc[i] = rstc;
}
resets->num_rstcs = num;
resets->base.array = true;
return &resets->base;
err_rst:
nxmutex_lock(&g_reset_list_mutex);
while (i--)
{
reset_control_put_internal(resets->rstc[i]);
}
nxmutex_unlock(&g_reset_list_mutex);
kmm_free(resets);
return rstc;
}
/****************************************************************************
* Name: reset_control_device_reset
*
* Description:
* Find reset controller associated with the device and perform reset,
* finally free the reset control.
* Convenience wrapper for reset_control_get() and reset_control_reset().
* This is useful for the common case of devices with single, dedicated
* reset lines.
*
* Input Parameters:
* name - The controller name
*
* Returned Value:
* Returns a negative errno if failed, otherwise success.
****************************************************************************/
int reset_control_device_reset(FAR const char *name)
{
FAR struct reset_control *rstc;
int ret;
rstc = reset_control_get(name, 0, false, true);
if (!rstc)
{
rsterr("get rstc failed.\n");
return -EINVAL;
}
ret = reset_control_reset(rstc);
reset_control_put(rstc);
return ret;
}
/****************************************************************************
* Nmae: reset_controller_register
*
* Description:
* Register a reset controller device
*
* Input Parameters:
* rcdev - A pointer to the initialized reset controller device
*
* Returned Value:
* Returns 0 if success, otherwise failed.
****************************************************************************/
int reset_controller_register(FAR struct reset_controller_dev *rcdev)
{
if (rcdev == NULL || rcdev->name == NULL || rcdev->ops == NULL)
{
rsterr("rcdev is null\n");
return -EINVAL;
}
list_initialize(&rcdev->reset_control_head);
nxmutex_lock(&g_reset_list_mutex);
list_add_after(&g_reset_controller_list, &rcdev->list);
nxmutex_unlock(&g_reset_list_mutex);
return 0;
}
/****************************************************************************
* Name: reset_controller_unregister
*
* Description:
* Unregister a reset controller device
*
* Input Parameters:
* rcdev - A pointer to the reset controller device
****************************************************************************/
void reset_controller_unregister(FAR struct reset_controller_dev *rcdev)
{
nxmutex_lock(&g_reset_list_mutex);
list_delete(&rcdev->list);
nxmutex_unlock(&g_reset_list_mutex);
}