From 1c492be255c85810efed931308392980a7696ffc Mon Sep 17 00:00:00 2001 From: patacongo <patacongo@42af7a65-404d-4744-a932-0658087f49c3> Date: Fri, 29 Jul 2011 18:51:56 +0000 Subject: [PATCH] Add framework for input devices and TSC2007 touchscreen driver git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@3827 42af7a65-404d-4744-a932-0658087f49c3 --- drivers/Makefile | 13 +- drivers/input/Make.defs | 52 ++++ drivers/input/tsc2007.c | 515 ++++++++++++++++++++++++++++++++++ include/debug.h | 34 ++- include/nuttx/input/tsc2007.h | 92 ++++++ 5 files changed, 699 insertions(+), 7 deletions(-) create mode 100755 drivers/input/Make.defs create mode 100644 drivers/input/tsc2007.c create mode 100644 include/nuttx/input/tsc2007.h diff --git a/drivers/Makefile b/drivers/Makefile index c46046bb77..2ba6c8f228 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -48,16 +48,17 @@ VPATH = . # files to the source file list, add its DEPPATH info, and will add # the appropriate paths to the VPATH variable -include serial/Make.defs +include bch/Make.defs +include input/Make.defs +include lcd/Make.defs +include mmcsd/Make.defs +include mtd/Make.defs include net/Make.defs include pipes/Make.defs +include sensors/Make.defs +include serial/Make.defs include usbdev/Make.defs include usbhost/Make.defs -include mmcsd/Make.defs -include lcd/Make.defs -include bch/Make.defs -include mtd/Make.defs -include sensors/Make.defs include wireless/Make.defs ifneq ($(CONFIG_NFILE_DESCRIPTORS),0) diff --git a/drivers/input/Make.defs b/drivers/input/Make.defs new file mode 100755 index 0000000000..e4758ed841 --- /dev/null +++ b/drivers/input/Make.defs @@ -0,0 +1,52 @@ +############################################################################ +# drivers/input/Make.defs +# +# Copyright (C) 2011 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt <spudmonkey@racsa.co.cr> +# +# 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. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# 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 MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, 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 DAMAGE. +# +############################################################################ + +# Don't build anything if there is no NX support for input devices + +ifeq ($(CONFIG_INPUT),y) + +# Include the TI TSC2007 drivers + +ifeq ($(CONFIG_INPUT_TSC2007),y) + CSRCS += tsc2007.c +endif + +# Include input device driver build support + +DEPPATH += --dep-path input +VPATH += :input +CFLAGS += ${shell $(TOPDIR)/tools/incdir.sh $(INCDIROPT) "$(CC)" $(TOPDIR)/drivers/input} +endif + diff --git a/drivers/input/tsc2007.c b/drivers/input/tsc2007.c new file mode 100644 index 0000000000..bb8c7f01f7 --- /dev/null +++ b/drivers/input/tsc2007.c @@ -0,0 +1,515 @@ +/**************************************************************************** + * drivers/input/tsc2007.c + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, 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 DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <sys/types.h> + +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <semaphore.h> +#include <poll.h> +#include <errno.h> +#include <assert.h> +#include <debug.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/arch.h> +#include <nuttx/fs.h> +#include <nuttx/i2c.h> +#include <nuttx/input/tsc2007.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* Driver support ***********************************************************/ +/* This format is used to construct the /dev/skel[n] device driver path. It + * defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/input%d" +#define DEV_NAMELEN 16 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct tsc2007_dev_s +{ + uint8_t crefs; /* Number of times the device has been opened */ + sem_t devsem; /* Manages exclusive access to this structure */ + FAR struct i2c_dev_s *i2c; /* Saved I2C driver instance */ + + /* The following is a list if poll structures of threads waiting for + * driver events. The 'struct pollfd' reference for each open is also + * retained in the f_priv field of the 'struct file'. + */ + +#ifndef CONFIG_DISABLE_POLL + struct pollfd *fds[CONFIG_TSC2007_NPOLLWAITERS]; +#endif +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int tsc2007_open(FAR struct file *filep); +static int tsc2007_close(FAR struct file *filep); +static ssize_t tsc2007_read(FAR struct file *filep, FAR char *buffer, size_t len); +static ssize_t tsc2007_write(FAR struct file *filep, FAR const char *buffer, size_t len); +static int tsc2007_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +#ifndef CONFIG_DISABLE_POLL +static int tsc2007_poll(FAR struct file *filep, struct pollfd *fds, bool setup); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations tsc2007_fops = +{ + tsc2007_open, /* open */ + tsc2007_close, /* close */ + tsc2007_read, /* read */ + tsc2007_write, /* write */ + 0, /* seek */ + tsc2007_ioctl /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , tsc2007_poll /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tsc2007_pollnotify + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static void tsc2007_pollnotify(FAR struct tsc2007_dev_s *priv, pollevent_t eventset) +{ + int i; + + for (i = 0; i < CONFIG_TSC2007_NPOLLWAITERS; i++) + { + struct pollfd *fds = priv->fds[i]; + if (fds) + { + fds->revents |= (fds->events & eventset); + if (fds->revents != 0) + { + ivdbg("Report events: %02x\n", fds->revents); + sem_post(fds->sem); + } + } + } +} +#else +# define tsc2007_pollnotify(priv,event) +#endif + +/**************************************************************************** + * Name: tsc2007_open + ****************************************************************************/ + +static int tsc2007_open(FAR struct file *filep) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + uint8_t tmp; + int ret; + + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Increment the reference count */ + + tmp = priv->crefs + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + goto errout_with_sem; + } + + /* Check if this is the first time that the driver has been opened. */ + + if (tmp == 1) + { + irqstate_t flags = irqsave(); + + /* Perform one time hardware initialization */ + +#warning "Missing logic" + irqrestore(flags); + } + + /* Save the new open count on success */ + + priv->crefs = tmp; + +errout_with_sem: + sem_post(&priv->devsem); + return ret; +} + +/**************************************************************************** + * Name: tsc2007_close + ****************************************************************************/ + +static int tsc2007_close(FAR struct file *filep) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + int ret; + + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Decrement the reference count unless it would decrement to zero */ + if (priv->crefs > 1) + { + priv->crefs--; + sem_post(&priv->devsem); + return OK; + } + + /* There are no more references to the port */ + + priv->crefs = 0; + + /* Perform driver teardown */ +#warning "Missing logic" + + sem_post(&priv->devsem); + return OK; +} + +/**************************************************************************** + * Name: tsc2007_read + ****************************************************************************/ + +static ssize_t tsc2007_read(FAR struct file *filep, FAR char *buffer, size_t len) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + int ret; + + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + +#warning "Not implemented" + + sem_post(&priv->devsem); + return -ENOSYS; +} + +/**************************************************************************** + * Name: tsc2007_write + ****************************************************************************/ + +static ssize_t tsc2007_write(FAR struct file *filep, FAR const char *buffer, size_t len) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + int ret; + + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + +#warning "Not implemented" + sem_post(&priv->devsem); + return -ENOSYS; +} + +/**************************************************************************** + * Name:tsc2007_ioctl + ****************************************************************************/ + +static int tsc2007_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + int ret; + + ivdbg("cmd: %d arg: %ld\n", cmd, arg); + DEBUGASSERT(filep); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Get exclusive access to the driver data structure */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + /* Process the IOCTL by command */ + + switch (cmd) + { + /* Add support for ioctl commands here */ + + default: + ret = -ENOTTY; + break; + } + + sem_post(&priv->devsem); + return ret; +} + +/**************************************************************************** + * Name: tsc2007_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static int tsc2007_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode; + FAR struct tsc2007_dev_s *priv; + pollevent_t eventset; + int ndx; + int ret = OK; + int i; + + ivdbg("setup: %d\n", (int)setup); + DEBUGASSERT(filep && fds); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct tsc2007_dev_s *)inode->i_private; + + /* Are we setting up the poll? Or tearing it down? */ + + ret = sem_wait(&priv->devsem); + if (ret < 0) + { + /* This should only happen if the wait was canceled by an signal */ + + DEBUGASSERT(errno == EINTR); + return -EINTR; + } + + if (setup) + { + /* This is a request to set up the poll. Find an available + * slot for the poll structure reference + */ + + for (i = 0; i < CONFIG_TSC2007_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!priv->fds[i]) + { + /* Bind the poll structure and this slot */ + + priv->fds[i] = fds; + fds->priv = &priv->fds[i]; + break; + } + } + + if (i >= CONFIG_TSC2007_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto errout; + } + + /* Should immediately notify on any of the requested events? */ +#warning "Missing logic" + + + } + else if (fds->priv) + { + /* This is a request to tear down the poll. */ + + struct pollfd **slot = (struct pollfd **)fds->priv; + +#ifdef CONFIG_DEBUG + if (!slot) + { + ret = -EIO; + goto errout; + } +#endif + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + +errout: + sem_post(&priv->devsem); + return ret; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tsc2007_register + * + * Description: + * Configure the TSC2007 to use the provided I2C device instance. This + * will register the driver as /dev/inputN where N is the minor device + * number + * + * Input Parameters: + * dev - An I2C driver instance + * minor - The input device minor number + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +int tsc2007_register(FAR struct i2c_dev_s *dev, int minor) +{ + FAR struct tsc2007_dev_s *priv; + char devname[DEV_NAMELEN]; + int ret; + + ivdbg("dev: %p minor: %d\n", dev, minor); + DEBUGASSERT(dev != NULL && minor > 0 && minor < 100); + + /* Create and initialize a TSC2007 device driver instance */ + + priv = (FAR struct tsc2007_dev_s *)kmalloc(sizeof(struct tsc2007_dev_s)); + if (!priv) + { + idbg("kmalloc(%d) failed\n", sizeof(struct tsc2007_dev_s)); + return -ENOMEM; + } + + /* Initialize a TSC2007 device driver instance */ + + priv->i2c = dev; + sem_init(&priv->devsem, 0, 1); + + /* Register the input device */ + + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, minor); + ivdbg("Registering %s\n", devname); + ret = register_driver(devname, &tsc2007_fops, 0666, priv); + if (ret < 0) + { + idbg("register_driver() failed: %d\n", ret); + } + return ret; +} diff --git a/include/debug.h b/include/debug.h index 9da8984850..24097fa575 100644 --- a/include/debug.h +++ b/include/debug.h @@ -1,7 +1,7 @@ /**************************************************************************** * include/debug.h * - * Copyright (C) 2007-2010 Gregory Nutt. All rights reserved. + * Copyright (C) 2007-2011 Gregory Nutt. All rights reserved. * Author: Gregory Nutt <spudmonkey@racsa.co.cr> * * Redistribution and use in source and binary forms, with or without @@ -224,6 +224,18 @@ # define fllvdbg(x...) #endif +#ifdef CONFIG_DEBUG_INPUT +# define idbg(format, arg...) dbg(format, ##arg) +# define illdbg(format, arg...) lldbg(format, ##arg) +# define ivdbg(format, arg...) vdbg(format, ##arg) +# define illvdbg(format, arg...) llvdbg(format, ##arg) +#else +# define idbg(x...) +# define illdbg(x...) +# define ivdbg(x...) +# define illvdbg(x...) +#endif + #ifdef CONFIG_DEBUG_GRAPHICS # define gdbg(format, arg...) dbg(format, ##arg) # define glldbg(format, arg...) lldbg(format, ##arg) @@ -369,6 +381,18 @@ # define fllvdbg (void) #endif +#ifdef CONFIG_DEBUG_INPUT +# define idbg dbg +# define illdbg lldbg +# define ivdbg vdbg +# define illvdbg llvdbg +#else +# define idbg (void) +# define illdbg (void) +# define ivdbg (void) +# define illvdbg (void) +#endif + #ifdef CONFIG_DEBUG_GRAPHICS # define gdbg dbg # define glldbg lldbg @@ -479,6 +503,14 @@ # define fvdbgdumpbuffer(m,b,n) #endif +#ifdef CONFIG_DEBUG_INPUT +# define idbgdumpbuffer(m,b,n) dbgdumpbuffer(m,b,n) +# define ivdbgdumpbuffer(m,b,n) vdbgdumpbuffer(m,b,n) +#else +# define idbgdumpbuffer(m,b,n) +# define ivdbgdumpbuffer(m,b,n) +#endif + #ifdef CONFIG_DEBUG_GRAPHICS # define gdbgdumpbuffer(m,b,n) dbgdumpbuffer(m,b,n) # define gvdbgdumpbuffer(m,b,n) vdbgdumpbuffer(m,b,n) diff --git a/include/nuttx/input/tsc2007.h b/include/nuttx/input/tsc2007.h new file mode 100644 index 0000000000..d6bb400ca4 --- /dev/null +++ b/include/nuttx/input/tsc2007.h @@ -0,0 +1,92 @@ +/**************************************************************************** + * include/nuttx/input/tsc2007.h + * + * Copyright (C) 2011 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt <spudmonkey@racsa.co.cr> + * + * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, 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 DAMAGE. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_INPUT_TSC2007_H +#define __INCLUDE_NUTTX_INPUT_TSC2007_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <nuttx/i2c.h> + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ +/* Maximum number of threads than can be waiting for POLL events */ + +#ifndef CONFIG_TSC2007_NPOLLWAITERS +# define CONFIG_TSC2007_NPOLLWAITERS 2 +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" { +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: tsc2007_register + * + * Description: + * Configure the TSC2007 to use the provided I2C device instance. This + * will register the driver as /dev/inputN where N is the minor device + * number + * + * Input Parameters: + * dev - An I2C driver instance + * minor - The input device minor number + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +EXTERN int tsc2007_register(FAR struct i2c_dev_s *dev, int minor); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_INPUT_TSC2007_H */ -- GitLab