/* Note : this particular snipset of code is available under
 * the LGPL, MPL or BSD license (at your choice).
 * Jean II
 */

/* --------------------------- INCLUDE --------------------------- */

/* Backward compatibility for Wireless Extension 9 */
#ifndef IW_POWER_MODIFIER
#define IW_POWER_MODIFIER	0x000F	/* Modify a parameter */
#define IW_POWER_MIN		0x0001	/* Value is a minimum  */
#define IW_POWER_MAX		0x0002	/* Value is a maximum */
#define IW_POWER_RELATIVE	0x0004	/* Value is not in seconds/ms/us */
#endif IW_POWER_MODIFIER

struct net_local {
  int		pm_on;		// Power Management enabled
  int		pm_multi;	// Receive multicasts
  int		pm_period;	// Power Management period
  int		pm_period_auto;	// Power Management auto mode
  int		pm_max_period;	// Power Management max period
  int		pm_min_period;	// Power Management min period
  int		pm_timeout;	// Power Management timeout
};

/* --------------------------- HANDLERS --------------------------- */

static int ioctl_set_power(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_param *prq,
			   char *extra)
{
  /* Disable it ? */
  if(prq->disabled)
    {
      local->pm_on = 0;
    }
  else
    {
      /* Check mode */
      switch(prq->flags & IW_POWER_MODE)
	{
	case IW_POWER_UNICAST_R:
	  local->pm_multi = 0;
	  local->need_commit = 1;
	  break;
	case IW_POWER_ALL_R:
	  local->pm_multi = 1;
	  local->need_commit = 1;
	  break;
	case IW_POWER_ON:	/* None = ok */
	  break;
	default:	/* Invalid */
	  return(-EINVAL);
	}
      /* Set period */
      if(prq->flags & IW_POWER_PERIOD)
	{
	  int	period = prq->value/1000000;
	  /* Hum: check if within bounds... */

	  /* Activate PM */
	  local->pm_on = 1;
	  local->need_commit = 1;

	  /* Check min value */
	  if(prq->flags & IW_POWER_MIN)
	    {
	      local->pm_min_period = period;
	      local->pm_period_auto = 1;
	    }
	  else
	    /* Check max value */
	    if(prq->flags & IW_POWER_MAX)
	      {
		local->pm_max_period = period;
		local->pm_period_auto = 1;
	      }
	    else
	      {
		/* Fixed value */
		local->pm_period = period;
		local->pm_period_auto = 0;
	      }
	}
      /* Set timeout */
      if(prq->flags & IW_POWER_TIMEOUT)
	{
	  /* Activate PM */
	  local->pm_on = 1;
	  local->need_commit = 1;
	  /* Fixed value in ms */
	  local->pm_timeout = prq->value/1000;
	}
    }

  return(0);
}

static int ioctl_get_power(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_param *prq,
			   char *extra)
{
  prq->disabled = !local->pm_on;
  /* By default, display the period */
  if(!(prq->flags & IW_POWER_TIMEOUT))
    {
      int	inc_flags = prq->flags;
      prq->flags = IW_POWER_PERIOD | IW_POWER_RELATIVE;
      /* Check if auto */
      if(local->pm_period_auto)
	{
	  /* By default, the min */
	  if(!(inc_flags & IW_POWER_MAX))
	    {
	      prq->value = local->pm_min_period * 1000000;
	      prq->flags |= IW_POWER_MIN;
	    }
	  else
	    {
	      prq->value = local->pm_max_period * 1000000;
	      prq->flags |= IW_POWER_MAX;
	    }
	}
      else
	{
	  /* Fixed value. Check the flags */
	  if(inc_flags & (IW_POWER_MIN | IW_POWER_MAX))
	    return(-EINVAL);
	  else
	    prq->value = local->pm_period * 1000000;
	}
    }
  else
    {
      /* Deal with the timeout - always fixed */
      prq->flags = IW_POWER_TIMEOUT;
      prq->value = local->pm_timeout * 1000;
    }
  if(local->pm_multi)
    prq->flags |= IW_POWER_ALL_R;
  else
    prq->flags |= IW_POWER_UNICAST_R;

  return(0);
}

static int ioctl_get_range(struct net_device *dev,
			   struct iw_request_info *info,
			   struct iw_point *rrq,
			   char *extra)
{
  struct iw_range *range = (struct iw_range *) extra;

  rrq->length = sizeof(struct iw_range);

  memset(range, 0, sizeof(struct iw_range));

#if WIRELESS_EXT > 10
  /* Version we are compiled with */
  range->we_version_compiled = WIRELESS_EXT;
  /* Minimum version we recommend */
  range->we_version_source = 8;
#endif /* WIRELESS_EXT > 10 */

#if WIRELESS_EXT > 9
      range.min_pmp = 1000000;	/* 1 units */
      range.max_pmp = 12000000;	/* 12 units */
      range.min_pmt = 1000;	/* 1 ms */
      range.max_pmt = 1000000;	/* 1 s */
      range.pmp_flags = IW_POWER_PERIOD | IW_POWER_RELATIVE |
        IW_POWER_MIN | IW_POWER_MAX;
      range.pmt_flags = IW_POWER_TIMEOUT;
      range.pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT | IW_POWER_UNICAST_R;
#endif /* WIRELESS_EXT > 9 */
  return(0);
}

/* --------------------------- BINDING --------------------------- */

#if WIRELESS_EXT > 12
/* Use the new driver API, save overhead */
static const iw_handler		handler_table[] =
{
	...
	(iw_handler) ioctl_set_power,		/* SIOCSIWPOWER */
	(iw_handler) ioctl_get_power,		/* SIOCGIWPOWER */
};
#else	/* WIRELESS_EXT < 12 */
/* Use old API in the ioctl handler */
static int
do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
  struct iwreq *wrq = (struct iwreq *) ifr;
  int		err = 0;

  switch (cmd)
    {
#if WIRELESS_EXT > 8
      /* Set the desired Power Management mode */
    case SIOCSIWPOWER:
      err = ioctl_set_power(dev, NULL, &(wrq->u.power), NULL);
      break;

      /* Get the power management settings */
    case SIOCGIWPOWER:
      err = ioctl_get_power(dev, NULL, &(wrq->u.power), NULL);
      break;
#endif	/* WIRELESS_EXT > 8 */
    }
  return(err);
}
#endif	/* WIRELESS_EXT < 12 */

