/****************************************************************************
 * drivers/i3c/device.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 "internals.h"

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: i3c_device_do_priv_xfers
 *
 * Description:
 *   Do I3C SDR private transfers directed to a specific device.
 *
 *   Initiate one or several private SDR transfers with @dev.
 *
 *   This function can sleep and thus cannot be called in atomic context.
 *
 * Input Parameters:
 *   dev    - Device with which the transfers should be done
 *   xfers  - Array of transfers
 *   nxfers - Number of transfers
 *
 * Returned Value:
 *   0 in case of success, a negative error core otherwise.
 *
 ****************************************************************************/

int i3c_device_do_priv_xfers(FAR const struct i3c_device *dev,
                             FAR struct i3c_priv_xfer *xfers,
                             int nxfers)
{
  int ret;
  int i;

  if (nxfers < 1)
    {
      return 0;
    }

  for (i = 0; i < nxfers; i++)
    {
      if (!xfers[i].len || !xfers[i].data.in)
        {
          return -EINVAL;
        }
    }

  i3c_bus_normaluse_lock(dev->bus);
  ret = i3c_dev_do_priv_xfers_locked(dev->desc, xfers, nxfers);
  i3c_bus_normaluse_unlock(dev->bus);

  return ret;
}

/****************************************************************************
 * Name: i3c_device_get_info
 *   Get I3C device information, Retrieve I3C dev info.
 *
 * Input Parameters:
 *   dev  - Device we want information on.
 *   info - The information object to fill in.
 *
 ****************************************************************************/

void i3c_device_get_info(FAR const struct i3c_device *dev,
                         FAR struct i3c_device_info *info)
{
  if (info == NULL)
    {
      return;
    }

  i3c_bus_normaluse_lock(dev->bus);
  if (dev->desc)
    {
      *info = dev->desc->info;
    }

  i3c_bus_normaluse_unlock(dev->bus);
}

/****************************************************************************
 * Name: i3c_device_disable_ibi
 *
 * Description:
 *   Disable IBIs coming from a specific device.
 *
 *   This function disable IBIs coming from a specific device and wait for
 *   all pending IBIs to be processed.
 *
 * Input Parameters:
 *   dev - Device on which IBIs should be disabled
 *
 * Returned Value:
 *   0 in case of success, a negative error core otherwise.
 *
 ****************************************************************************/

int i3c_device_disable_ibi(FAR const struct i3c_device *dev)
{
  int ret = -ENOENT;

  i3c_bus_normaluse_lock(dev->bus);
  if (dev->desc)
    {
      nxmutex_lock(&dev->desc->ibi_lock);
      ret = i3c_dev_disable_ibi_locked(dev->desc);
      nxmutex_unlock(&dev->desc->ibi_lock);
    }

  i3c_bus_normaluse_unlock(dev->bus);

  return ret;
}

/****************************************************************************
 * Name: i3c_device_enable_ibi
 *
 * Description:
 *   Enable IBIs coming from a specific device.
 *
 *   This function enable IBIs coming from a specific device and wait for
 *   all pending IBIs to be processed. This should be called on a device
 *   where i3c_device_request_ibi() has succeeded.
 *
 *   Note that IBIs from this device might be received before this function
 *   returns to its caller.
 *
 * Input Parameters:
 *   dev - Device on which IBIs should be enabled
 *
 * Returned Value:
 *   0 in case of success, a negative error core otherwise.
 *
 ****************************************************************************/

int i3c_device_enable_ibi(FAR const struct i3c_device *dev)
{
  int ret = -ENOENT;

  i3c_bus_normaluse_lock(dev->bus);
  if (dev->desc)
    {
      nxmutex_lock(&dev->desc->ibi_lock);
      ret = i3c_dev_enable_ibi_locked(dev->desc);
      nxmutex_unlock(&dev->desc->ibi_lock);
    }

  i3c_bus_normaluse_unlock(dev->bus);

  return ret;
}

/****************************************************************************
 * Name: i3c_device_request_ibi
 *
 * Description:
 *   Request an IBI.
 *
 *   This function is responsible for pre-allocating all resources needed to
 *   process IBIs coming from @dev. When this function returns, the IBI is
 *   not enabled until i3c_device_enable_ibi() is called.
 *
 * Input Parameters:
 *   dev - Device for which we should enable IBIs
 *   req - Setup requested for this IBI
 *
 * Returned Value:
 *   0 in case of success, a negative error core otherwise.
 *
 ****************************************************************************/

int i3c_device_request_ibi(FAR const struct i3c_device *dev,
                           FAR const struct i3c_ibi_setup *req)
{
  int ret = -ENOENT;

  if (req->handler == NULL || !req->num_slots)
    {
      return -EINVAL;
    }

  i3c_bus_normaluse_lock(dev->bus);
  if (dev->desc)
    {
      nxmutex_lock(&dev->desc->ibi_lock);
      ret = i3c_dev_request_ibi_locked(dev->desc, req);
      nxmutex_unlock(&dev->desc->ibi_lock);
    }

  i3c_bus_normaluse_unlock(dev->bus);

  return ret;
}

/****************************************************************************
 * Name: i3c_device_free_ibi
 *
 * Description:
 *   Free all resources needed for IBI handling.
 *
 *   This function is responsible for de-allocating resources previously
 *   allocated by i3c_device_request_ibi(). It should be called after
 *   disabling IBIs with i3c_device_disable_ibi().
 *
 * Input Parameters:
 *   dev - Device on which you want to release IBI resources
 *
 ****************************************************************************/

void i3c_device_free_ibi(FAR const struct i3c_device *dev)
{
  i3c_bus_normaluse_lock(dev->bus);
  if (dev->desc)
    {
      nxmutex_lock(&dev->desc->ibi_lock);
      i3c_dev_free_ibi_locked(dev->desc);
      nxmutex_unlock(&dev->desc->ibi_lock);
    }

  i3c_bus_normaluse_unlock(dev->bus);
}

/****************************************************************************
 * Name: i3c_device_send_ccc_cmd
 *
 * Description:
 *   This function is used to send a common ccc command.
 *
 * Input Parameters:
 *   dev  - An I3C device descriptor will be used for
 *   cmd  - The buf of ccc commands to transfer, only one frame at a time
 *
 * Returned Value:
 *   0 or positive if Success, nagative otherwise.
 ****************************************************************************/

int i3c_device_send_ccc_cmd(FAR const struct i3c_device *dev,
                            FAR struct i3c_ccc_cmd *cmd)
{
  int ret;
  FAR struct i3c_master_controller *master;

  if (dev == NULL || cmd == NULL)
    {
      return -EINVAL;
    }

  master = i3c_dev_get_master(dev->desc);

  i3c_bus_normaluse_lock(dev->bus);

  ret = i3c_master_send_ccc_cmd_locked(master, cmd);

  i3c_bus_normaluse_unlock(dev->bus);

  return ret;
}

/****************************************************************************
 * Name: i3c_master_find_i3c_dev
 *
 * Description:
 *   This function is used to be find a i3c_device address by master handle
 *   and provisional ID.
 *
 * Input Parameters:
 *   master - The master used to get i3c_device on the bus
 *   id     - An instance of i3c_device_id, include manufid,partid and so on.
 *
 * Returned Value:
 *   Struct i3c_device var in case of success, NULL otherwise.
 ****************************************************************************/

FAR const struct i3c_device *i3c_master_find_i3c_dev(
                              FAR struct i3c_master_controller *master,
                              FAR const struct i3c_device_id *id)
{
  FAR struct i3c_dev_desc *desc;
  uint16_t manuf;
  uint16_t part;
  uint16_t ext_info;
  bool rndpid;

  i3c_bus_normaluse_lock(&master->bus);
  i3c_bus_for_each_i3cdev(&master->bus, desc)
    {
      manuf = I3C_PID_MANUF_ID(desc->info.pid);
      part = I3C_PID_PART_ID(desc->info.pid);
      ext_info = I3C_PID_EXTRA_INFO(desc->info.pid);
      rndpid = I3C_PID_RND_LOWER_32BITS(desc->info.pid);

      if ((id->match_flags & I3C_MATCH_DCR) &&
           id->dcr != desc->info.dcr)
        {
          continue;
        }

      if ((id->match_flags & I3C_MATCH_MANUF) &&
           id->manuf_id != manuf)
        {
          continue;
        }

      if ((id->match_flags & I3C_MATCH_PART) &&
          (rndpid || id->part_id != part))
        {
          continue;
        }

      if ((id->match_flags & I3C_MATCH_EXTRA_INFO) &&
          (rndpid || id->extra_info != ext_info))
        {
          continue;
        }

      i3c_bus_normaluse_unlock(&master->bus);
      return desc->dev;
    }

  i3c_bus_normaluse_unlock(&master->bus);
  return NULL;
}