diff --git a/apps/features.txt b/apps/features.txt
index 44ef3dd..35f7bb5 100644
--- a/apps/features.txt
+++ b/apps/features.txt
@@ -270,3 +270,7 @@ recording_digital
 #if MEMORYSIZE <= 2
 lowmem
 #endif
+
+#if defined(HAVE_HARDWARE_CLICK)
+hardware_click
+#endif
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 5a59473..8e006bf 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -12205,6 +12205,40 @@
   </voice>
 </phrase>
 <phrase>
+  id: LANG_KEYCLICK_SOFTWARE
+  desc: in keyclick settings menu
+  user: core
+  <source>
+    *: none
+    hardware_click: "Headphone Keyclick"
+  </source>
+  <dest>
+    *: none
+    hardware_click: "Headphone Keyclick"
+  </dest>
+  <voice>
+    *: none
+    hardware_click: "Headphone Keyclick"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_KEYCLICK_HARDWARE
+  desc: in keyclick settings menu
+  user: core
+  <source>
+    *: none
+    hardware_click: "Speaker Keyclick"
+  </source>
+  <dest>
+    *: none
+    hardware_click: "Speaker Keyclick"
+  </dest>
+  <voice>
+    *: none
+    hardware_click: "Speaker Keyclick"
+  </voice>
+</phrase>
+<phrase>
   id: LANG_HOTKEY
   desc: hotkey menu
   user: core
diff --git a/apps/main.c b/apps/main.c
index c2dfc1a..111b25b 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -117,6 +117,10 @@
 #include "m5636.h"
 #endif
 
+#ifdef HAVE_HARDWARE_CLICK
+#include "piezo.h"
+#endif
+
 #if (CONFIG_PLATFORM & PLATFORM_NATIVE)
 #define MAIN_NORETURN_ATTR NORETURN_ATTR
 #else
@@ -505,6 +509,10 @@ static void init(void)
     radio_init();
 #endif
 
+#ifdef HAVE_HARDWARE_CLICK
+    piezo_init();
+#endif
+
     /* Keep the order of this 3 (viewportmanager handles statusbars)
      * Must be done before any code uses the multi-screen API */
     CHART(">gui_syncstatusbar_init");
diff --git a/apps/menus/settings_menu.c b/apps/menus/settings_menu.c
index 02f68aa..079e866 100644
--- a/apps/menus/settings_menu.c
+++ b/apps/menus/settings_menu.c
@@ -229,9 +229,15 @@ MAKE_MENU(limits_menu, ID2P(LANG_LIMITS_MENU), 0, Icon_NOICON,
 #if CONFIG_CODEC == SWCODEC
 MENUITEM_SETTING(keyclick, &global_settings.keyclick, NULL);
 MENUITEM_SETTING(keyclick_repeats, &global_settings.keyclick_repeats, NULL);
+#ifdef HAVE_HARDWARE_CLICK
+MENUITEM_SETTING(keyclick_hardware, &global_settings.keyclick_hardware, NULL);
+MAKE_MENU(keyclick_menu, ID2P(LANG_KEYCLICK), 0, Icon_NOICON,
+           &keyclick, &keyclick_hardware, &keyclick_repeats);
+#else
 MAKE_MENU(keyclick_menu, ID2P(LANG_KEYCLICK), 0, Icon_NOICON,
            &keyclick, &keyclick_repeats);
 #endif
+#endif
 
 
 #if CONFIG_CODEC == MAS3507D
diff --git a/apps/misc.c b/apps/misc.c
index 72457cd..105f08f 100644
--- a/apps/misc.c
+++ b/apps/misc.c
@@ -94,6 +94,10 @@
 #endif
 #endif
 
+#ifdef HAVE_HARDWARE_CLICK
+#include "piezo.h"
+#endif
+
 /* units used with output_dyn_value */
 const unsigned char * const byte_units[] =
 {
@@ -875,15 +879,49 @@ void system_sound_play(enum system_sound sound)
 /* Produce keyclick based upon button and global settings */
 void keyclick_click(int button)
 {
+    static long last_button = BUTTON_NONE;
     /* Settings filters */
-    if (global_settings.keyclick &&
-        (global_settings.keyclick_repeats || !(button & BUTTON_REPEAT)))
+    if (
+#ifdef HAVE_HARDWARE_CLICK
+        (global_settings.keyclick || global_settings.keyclick_hardware)
+#else
+        global_settings.keyclick
+#endif
+        && (global_settings.keyclick_repeats || !(button & BUTTON_REPEAT)))
     {
         /* Button filters */
         if (button != BUTTON_NONE && !(button & BUTTON_REL)
             && !(button & (SYS_EVENT|BUTTON_MULTIMEDIA)) )
         {
-            system_sound_play(SOUND_KEYCLICK);
+            if (((button & BUTTON_REPEAT) && (last_button == BUTTON_NONE))
+#ifdef HAVE_SCROLLWHEEL
+            || (last_button & (BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD))
+#endif
+            || (!(button&BUTTON_REPEAT)))
+            {
+                if (button&BUTTON_REPEAT)
+                    last_button = button;
+                else
+                    last_button = BUTTON_NONE;
+#ifdef HAVE_HARDWARE_CLICK
+                if (global_settings.keyclick)
+                {
+                    system_sound_play(SOUND_KEYCLICK);
+                }
+                if (global_settings.keyclick_hardware)
+                {
+#if !defined(SIMULATOR)
+                    piezo_button_beep(false, false);
+#endif
+                }
+#else
+                system_sound_play(SOUND_KEYCLICK);
+#endif
+            }
+        }
+        else
+        {
+            button = BUTTON_NONE;
         }
     }
 }
diff --git a/apps/settings.h b/apps/settings.h
index 92ffaf9..06eba76 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -835,6 +835,13 @@ struct user_settings
 #endif
     } hw_eq_bands[AUDIOHW_EQ_BAND_NUM];
 #endif /* AUDIOHW_HAVE_EQ */
+
+#ifdef HAVE_HARDWARE_CLICK
+#if CONFIG_CODEC == SWCODEC
+    bool keyclick_hardware; /* hardware piezo keyclick */
+#endif
+#endif
+
     char start_directory[MAX_PATHNAME+1];
 };
 
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 521d565..ae8892d 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -1757,12 +1757,23 @@ const struct settings_list settings[] = {
 #endif /* HAVE_WHEEL_ACCELERATION */
 #if CONFIG_CODEC == SWCODEC
     /* keyclick */
+#ifdef HAVE_HARDWARE_CLICK
+    CHOICE_SETTING(0, keyclick, LANG_KEYCLICK_SOFTWARE, 0,
+                   "keyclick", "off,weak,moderate,strong", NULL, 4,
+                   ID2P(LANG_OFF), ID2P(LANG_WEAK), ID2P(LANG_MODERATE),
+                   ID2P(LANG_STRONG)),
+    OFFON_SETTING(0, keyclick_repeats, LANG_KEYCLICK_REPEATS, false,
+                  "keyclick repeats", NULL),
+    OFFON_SETTING(0, keyclick_hardware, LANG_KEYCLICK_HARDWARE, false,
+        "hardware keyclick", NULL),
+#else
     CHOICE_SETTING(0, keyclick, LANG_KEYCLICK, 0,
                    "keyclick", "off,weak,moderate,strong", NULL, 4,
                    ID2P(LANG_OFF), ID2P(LANG_WEAK), ID2P(LANG_MODERATE),
                    ID2P(LANG_STRONG)),
     OFFON_SETTING(0, keyclick_repeats, LANG_KEYCLICK_REPEATS, false,
                   "keyclick repeats", NULL),
+#endif
 #endif /* CONFIG_CODEC == SWCODEC */
     TEXT_SETTING(0, playlist_catalog_dir, "playlist catalog directory",
                      PLAYLIST_CATALOG_DEFAULT_DIR, NULL, NULL),
diff --git a/firmware/SOURCES b/firmware/SOURCES
index b729a9b..8669c92 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -1158,6 +1158,7 @@ target/arm/ata-pp5020.c
 target/arm/ipod/adc-ipod-pcf.c
 target/arm/ipod/backlight-4g_color.c
 target/arm/ipod/button-clickwheel.c
+target/arm/ipod/piezo.c
 target/arm/ipod/lcd-as-gray.S
 target/arm/ipod/lcd-gray.c
 target/arm/ipod/power-ipod.c
@@ -1176,6 +1177,7 @@ target/arm/i2s-pp.c
 target/arm/ipod/adc-ipod-pcf.c
 target/arm/ipod/backlight-4g_color.c
 target/arm/ipod/button-clickwheel.c
+target/arm/ipod/piezo.c
 target/arm/ipod/lcd-color_nano.c
 target/arm/ipod/lcd-as-color-nano.S
 target/arm/ipod/power-ipod.c
@@ -1194,6 +1196,7 @@ target/arm/i2s-pp.c
 target/arm/ipod/adc-ipod-pcf.c
 target/arm/ipod/backlight-nano_video.c
 target/arm/ipod/button-clickwheel.c
+target/arm/ipod/piezo.c
 target/arm/ipod/lcd-color_nano.c
 target/arm/ipod/lcd-as-color-nano.S
 target/arm/ipod/power-ipod.c
@@ -1212,6 +1215,7 @@ target/arm/i2s-pp.c
 target/arm/ipod/adc-ipod-pcf.c
 target/arm/ipod/backlight-nano_video.c
 target/arm/ipod/button-clickwheel.c
+target/arm/ipod/piezo.c
 target/arm/ipod/power-ipod.c
 target/arm/ipod/powermgmt-ipod-pcf.c
 target/arm/ipod/video/lcd-as-video.S
@@ -1266,6 +1270,7 @@ target/arm/i2s-pp.c
 target/arm/ipod/adc-ipod-pcf.c
 target/arm/ipod/backlight-mini1g_mini2g.c
 target/arm/ipod/button-mini1g.c
+target/arm/ipod/piezo.c
 target/arm/ipod/lcd-as-gray.S
 target/arm/ipod/lcd-gray.c
 target/arm/ipod/power-ipod.c
@@ -1284,6 +1289,7 @@ target/arm/i2s-pp.c
 target/arm/ipod/adc-ipod-pcf.c
 target/arm/ipod/backlight-mini1g_mini2g.c
 target/arm/ipod/button-clickwheel.c
+target/arm/ipod/piezo.c
 target/arm/ipod/lcd-as-gray.S
 target/arm/ipod/lcd-gray.c
 target/arm/ipod/power-ipod.c
@@ -1637,6 +1643,7 @@ target/arm/s5l8700/pcm-s5l8700.c
 target/arm/s5l8700/wmcodec-s5l8700.c
 target/arm/s5l8700/ipodnano2g/audio-nano2g.c
 target/arm/s5l8700/ipodnano2g/adc-nano2g.c
+target/arm/s5l8700/ipodnano2g/piezo-nano2g.c
 #endif
 #endif
 #endif
diff --git a/firmware/export/config/ipod4g.h b/firmware/export/config/ipod4g.h
index 56d0599..f868895 100644
--- a/firmware/export/config/ipod4g.h
+++ b/firmware/export/config/ipod4g.h
@@ -209,6 +209,8 @@
 /* Define this if you can read an absolute wheel position */
 #define HAVE_WHEEL_POSITION
 
+#define HAVE_HARDWARE_CLICK
+
 #define BOOTFILE_EXT "ipod"
 #define BOOTFILE "rockbox." BOOTFILE_EXT
 #define BOOTDIR "/.rockbox"
diff --git a/firmware/export/config/ipodcolor.h b/firmware/export/config/ipodcolor.h
index ec3d2c6..debe094 100644
--- a/firmware/export/config/ipodcolor.h
+++ b/firmware/export/config/ipodcolor.h
@@ -196,6 +196,8 @@
 /* Define this if you can read an absolute wheel position */
 #define HAVE_WHEEL_POSITION
 
+#define HAVE_HARDWARE_CLICK
+
 #define BOOTFILE_EXT "ipod"
 #define BOOTFILE "rockbox." BOOTFILE_EXT
 #define BOOTDIR "/.rockbox"
diff --git a/firmware/export/config/ipodmini1g.h b/firmware/export/config/ipodmini1g.h
index 55b78de..76cc342 100644
--- a/firmware/export/config/ipodmini1g.h
+++ b/firmware/export/config/ipodmini1g.h
@@ -199,6 +199,8 @@
 /* Define this if you have adjustable CPU frequency */
 #define HAVE_ADJUSTABLE_CPU_FREQ
 
+#define HAVE_HARDWARE_CLICK
+
 #define BOOTFILE_EXT "ipod"
 #define BOOTFILE "rockbox." BOOTFILE_EXT
 #define BOOTDIR "/.rockbox"
diff --git a/firmware/export/config/ipodmini2g.h b/firmware/export/config/ipodmini2g.h
index fd332c0..6507c41 100644
--- a/firmware/export/config/ipodmini2g.h
+++ b/firmware/export/config/ipodmini2g.h
@@ -202,6 +202,8 @@
 /* Define this if you can read an absolute wheel position */
 #define HAVE_WHEEL_POSITION
 
+#define HAVE_HARDWARE_CLICK
+
 #define BOOTFILE_EXT "ipod"
 #define BOOTFILE "rockbox." BOOTFILE_EXT
 #define BOOTDIR "/.rockbox"
diff --git a/firmware/export/config/ipodnano1g.h b/firmware/export/config/ipodnano1g.h
index 9dba533..29ca806 100644
--- a/firmware/export/config/ipodnano1g.h
+++ b/firmware/export/config/ipodnano1g.h
@@ -196,6 +196,8 @@
 /* Define this if you can read an absolute wheel position */
 #define HAVE_WHEEL_POSITION
 
+#define HAVE_HARDWARE_CLICK
+
 #define BOOTFILE_EXT "ipod"
 #define BOOTFILE "rockbox." BOOTFILE_EXT
 #define BOOTDIR "/.rockbox"
diff --git a/firmware/export/config/ipodnano2g.h b/firmware/export/config/ipodnano2g.h
index a85cd61..e23abd6 100644
--- a/firmware/export/config/ipodnano2g.h
+++ b/firmware/export/config/ipodnano2g.h
@@ -188,6 +188,8 @@
 /* Define this if you can read an absolute wheel position */
 #define HAVE_WHEEL_POSITION
 
+#define HAVE_HARDWARE_CLICK
+
 /* Define this if you have adjustable CPU frequency */
 #define HAVE_ADJUSTABLE_CPU_FREQ
 
diff --git a/firmware/export/config/ipodvideo.h b/firmware/export/config/ipodvideo.h
index dd21bb5..23ce598 100644
--- a/firmware/export/config/ipodvideo.h
+++ b/firmware/export/config/ipodvideo.h
@@ -215,6 +215,8 @@
 /* Define this if you can read an absolute wheel position */
 #define HAVE_WHEEL_POSITION
 
+#define HAVE_HARDWARE_CLICK
+
 /* define this if the device has larger sectors when accessed via USB */
 /* (only relevant in disk.c, fat.c now always supports large virtual sectors) */
 #define MAX_LOG_SECTOR_SIZE 2048
diff --git a/firmware/export/thread.h b/firmware/export/thread.h
index d0f61f9..da06557 100644
--- a/firmware/export/thread.h
+++ b/firmware/export/thread.h
@@ -65,10 +65,18 @@
 #if CONFIG_CODEC == SWCODEC
 
 #ifdef HAVE_RECORDING
+#ifdef HAVE_HARDWARE_CLICK
+#define BASETHREADS  18
+#else
+#define BASETHREADS  17
+#endif
+#else
+#ifdef HAVE_HARDWARE_CLICK
 #define BASETHREADS  17
 #else
 #define BASETHREADS  16
 #endif
+#endif
 
 #else
 #define BASETHREADS  11
diff --git a/firmware/target/arm/ipod/piezo.c b/firmware/target/arm/ipod/piezo.c
new file mode 100644
index 0000000..2c3968e
--- /dev/null
+++ b/firmware/target/arm/ipod/piezo.c
@@ -0,0 +1,209 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2006-2007 Robert Keevil
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include "thread.h"
+#include "system.h"
+#include "kernel.h"
+#include "usb.h"
+#include "logf.h"
+#include "piezo.h"
+
+static long piezo_stack[DEFAULT_STACK_SIZE/sizeof(long)];
+static const char piezo_thread_name[] = "piezo";
+static struct event_queue piezo_queue;
+static unsigned int duration;
+static bool beeping;
+
+enum {
+    Q_PIEZO_BEEP = 1,
+    Q_PIEZO_BEEP_FOR_TICK,
+    Q_PIEZO_BEEP_FOR_USEC,
+    Q_PIEZO_STOP
+};
+
+static inline void piezo_hw_init(void)
+{
+#ifndef SIMULATOR
+    /*logf("PIEZO: hw_init");*/
+    outl(inl(0x70000010) & ~0xc, 0x70000010);
+    outl(inl(0x6000600c) | 0x20000, 0x6000600c);    /* enable device */
+#endif
+}
+
+static void piezo_hw_tick(unsigned int form_and_period)
+{
+#ifndef SIMULATOR
+    outl(0x80000000 | form_and_period, 0x7000a000); /* set pitch */
+#endif
+}
+
+static inline void piezo_hw_stop(void)
+{
+#ifndef SIMULATOR
+    outl(0x0, 0x7000a000);    /* piezo off */
+#endif
+}
+
+static void piezo_thread(void)
+{
+    struct queue_event ev;
+    long piezo_usec_off;
+
+    while(1)
+    {
+        queue_wait(&piezo_queue, &ev);
+        switch(ev.id)
+        {
+            case Q_PIEZO_BEEP:
+                piezo_hw_tick((unsigned int)ev.data);
+                beeping = true;
+                break;
+            case Q_PIEZO_BEEP_FOR_TICK:
+                piezo_hw_tick((unsigned int)ev.data);
+                beeping = true;
+                sleep(duration);
+                if (beeping)
+                    piezo_hw_stop();
+                beeping = false;
+                /* remove anything that appeared while sleeping */
+                queue_clear(&piezo_queue);
+                break;
+            case Q_PIEZO_BEEP_FOR_USEC:
+                piezo_usec_off = USEC_TIMER + duration;
+                piezo_hw_tick((unsigned int)ev.data);
+                beeping = true;
+                while (TIME_BEFORE(USEC_TIMER, piezo_usec_off))
+                    if (duration >= 5000) yield();
+                if (beeping)
+                    piezo_hw_stop();
+                beeping = false;
+                /* remove anything that appeared while sleeping */
+                queue_clear(&piezo_queue);
+                break;
+            case Q_PIEZO_STOP:
+                if (beeping)
+                    piezo_hw_stop();
+                beeping = false;
+                break;
+#ifndef SIMULATOR
+            case SYS_USB_CONNECTED:
+                /*logf("USB: Piezo core");*/
+                piezo_hw_stop();
+                queue_clear(&piezo_queue);
+                usb_acknowledge(SYS_USB_CONNECTED_ACK);
+                usb_wait_for_disconnect(&piezo_queue);
+                break ;
+#endif
+            case SYS_TIMEOUT:
+                break;
+        }
+        yield();
+    }
+}
+
+
+void piezo_play(unsigned short inv_freq, unsigned char form)
+{
+    queue_post(&piezo_queue, Q_PIEZO_BEEP,
+        (intptr_t)((unsigned int)form << 16 | inv_freq));
+}
+
+void piezo_play_for_tick(unsigned short inv_freq,
+                    unsigned char form, unsigned int dur)
+{
+    duration = dur;
+    queue_post(&piezo_queue, Q_PIEZO_BEEP_FOR_TICK,
+        (intptr_t)((unsigned int)form << 16 | inv_freq));
+}
+
+void piezo_play_for_usec(unsigned short inv_freq,
+                    unsigned char form, unsigned int dur)
+{
+    duration = dur;
+    queue_post(&piezo_queue, Q_PIEZO_BEEP_FOR_USEC,
+        (intptr_t)((unsigned int)form << 16 | inv_freq));
+}
+
+void piezo_stop(void)
+{
+    queue_post(&piezo_queue, Q_PIEZO_STOP, 0);
+}
+
+void piezo_clear(void)
+{
+    queue_clear(&piezo_queue);
+    piezo_stop();
+}
+
+bool piezo_busy(void)
+{
+    return !queue_empty(&piezo_queue);
+}
+
+/*  conversion factor based on the following data
+
+        period          Hz
+        10              8547
+        20              4465
+        30              3024
+        40              2286
+        50              1846
+        60              1537
+        70              1320
+        80              1165
+        90              1030
+        100             928
+
+    someone with better recording/analysing equipment should be able
+    to get more accurate figures
+*/
+unsigned int piezo_hz(unsigned int hz)
+{
+    if (hz > 0)
+        return 91225/hz;
+    else
+        return 0;
+}
+
+void piezo_init(void)
+{
+    /*logf("PIEZO: init");*/
+    piezo_hw_init();
+    queue_init(&piezo_queue, true);
+    create_thread(piezo_thread, piezo_stack, sizeof(piezo_stack), 0,
+            piezo_thread_name IF_PRIO(, PRIORITY_REALTIME)
+            IF_COP(, CPU));
+}
+
+void piezo_button_beep(bool beep, bool force)
+{
+    /* old on clickwheel action - piezo_play_for_usec(50, 0x80, 400);
+       old on button action - piezo_play_for_usec(50, 0x80, 3000); */
+
+    if (force)
+        piezo_clear();
+
+    if (queue_empty(&piezo_queue))
+    {
+        if (beep)
+            piezo_play_for_tick(40, 0x80, HZ/5);
+        else
+            piezo_play_for_usec(91, 0x80, 4000);
+    }
+}
diff --git a/firmware/target/arm/ipod/piezo.h b/firmware/target/arm/ipod/piezo.h
new file mode 100644
index 0000000..78b50d0
--- /dev/null
+++ b/firmware/target/arm/ipod/piezo.h
@@ -0,0 +1,30 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2006-2007 Robert Keevil
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+void piezo_init(void);
+void piezo_play(unsigned short inv_freq, unsigned char form);
+void piezo_play_for_tick(unsigned short inv_freq,
+                    unsigned char form, unsigned int dur);
+void piezo_play_for_usec(unsigned short inv_freq,
+                    unsigned char form, unsigned int dur);
+void piezo_stop(void);
+void piezo_clear(void);
+bool piezo_busy(void);
+unsigned int piezo_hz(unsigned int hz);
+void piezo_button_beep(bool beep, bool force);
diff --git a/firmware/target/arm/s5l8700/ipodnano2g/piezo-nano2g.c b/firmware/target/arm/s5l8700/ipodnano2g/piezo-nano2g.c
new file mode 100644
index 0000000..d555b72
--- /dev/null
+++ b/firmware/target/arm/s5l8700/ipodnano2g/piezo-nano2g.c
@@ -0,0 +1,95 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2006-2007 Robert Keevil
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include "system.h"
+#include "kernel.h"
+#include "piezo.h"
+
+static unsigned int duration;
+static bool beeping;
+
+void INT_TIMERD(void)
+{
+    /* clear interrupt */
+    TDCON = TDCON;
+    if (!(--duration))
+    {
+        beeping = 0;
+        TDCMD = (1 << 1);   /* TD_CLR */
+    }
+}
+
+void piezo_start(unsigned short cycles, unsigned short periods)
+{
+#ifndef SIMULATOR
+    duration = periods;
+    beeping = 1;
+    /* configure timer for 100 kHz */
+    TDCMD = (1 << 1);   /* TD_CLR */
+    TDPRE = 30 - 1;    /* prescaler */
+    TDCON = (1 << 13) | /* TD_INT1_EN */
+            (0 << 12) | /* TD_INT0_EN */
+            (0 << 11) | /* TD_START */
+            (2 << 8) |  /* TD_CS = PCLK / 16 */
+            (1 << 4);   /* TD_MODE_SEL = PWM mode */
+    TDDATA0 = cycles;   /* set interval period */
+    TDDATA1 = cycles << 1; /* set interval period */
+    TDCMD = (1 << 0);   /* TD_EN */
+
+    /* enable timer interrupt */
+    INTMSK |= INTMSK_TIMERD;
+#endif
+}
+
+void piezo_stop(void)
+{
+#ifndef SIMULATOR
+    TDCMD = (1 << 1);   /* TD_CLR */
+#endif
+}
+
+void piezo_clear(void)
+{
+    piezo_stop();
+}
+
+bool piezo_busy(void)
+{
+    return beeping;
+}
+
+void piezo_init(void)
+{
+    beeping = 0;
+}
+
+void piezo_button_beep(bool beep, bool force)
+{
+    if (force)
+        while (beeping)
+            yield();
+
+    if (!beeping)
+    {
+        if (beep)
+            piezo_start(22, 457);
+        else
+            piezo_start(40, 4);
+    }
+}
diff --git a/firmware/target/arm/s5l8700/ipodnano2g/piezo.h b/firmware/target/arm/s5l8700/ipodnano2g/piezo.h
new file mode 100644
index 0000000..b8eae09
--- /dev/null
+++ b/firmware/target/arm/s5l8700/ipodnano2g/piezo.h
@@ -0,0 +1,24 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2006-2007 Robert Keevil
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+void piezo_init(void);
+void piezo_stop(void);
+void piezo_clear(void);
+bool piezo_busy(void);
+void piezo_button_beep(bool beep, bool force);
