diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.cpp
index 699eb47159bd70f554355f2403cd16d158cf1e23..12c837f3f185d685af1aa043b7bfc6f4fcf32e53 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.cpp
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodeploystepfactory.cpp
@@ -34,6 +34,7 @@
 #include "maemodeploystepfactory.h"
 
 #include "maemodeploybymountstep.h"
+#include "maemodirectdeviceuploadstep.h"
 #include "maemoglobal.h"
 #include "maemoinstalltosysrootstep.h"
 #include "maemouploadandinstalldeploystep.h"
@@ -104,6 +105,8 @@ QString MaemoDeployStepFactory::displayNameForId(const QString &id) const
         return MaemoInstallRpmPackageToSysrootStep::DisplayName;
     else if (id == MaemoCopyToSysrootStep::Id)
         return MaemoCopyToSysrootStep::DisplayName;
+    else if (id == MaemoDirectDeviceUploadStep::Id)
+        return MaemoDirectDeviceUploadStep::DisplayName;
     return QString();
 }
 
@@ -135,6 +138,8 @@ BuildStep *MaemoDeployStepFactory::create(BuildStepList *parent, const QString &
         return new MaemoUploadAndInstallRpmPackageStep(parent);
     } else if (id == MaemoUploadAndInstallTarPackageStep::Id) {
         return new MaemoUploadAndInstallTarPackageStep(parent);
+    } else if (id == MaemoDirectDeviceUploadStep::Id) {
+        return new MaemoDirectDeviceUploadStep(parent);
     }
 
     return 0;
@@ -189,6 +194,9 @@ BuildStep *MaemoDeployStepFactory::clone(BuildStepList *parent, BuildStep *produ
     } else if (product->id() == MaemoCopyToSysrootStep::Id) {
         return new MaemoCopyToSysrootStep(parent,
             qobject_cast<MaemoCopyToSysrootStep *>(product));
+    } else if (product->id() == MaemoDirectDeviceUploadStep::Id) {
+        return new MaemoDirectDeviceUploadStep(parent,
+            qobject_cast<MaemoDirectDeviceUploadStep *>(product));
     }
     return 0;
 }
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodirectdeviceuploadstep.cpp b/src/plugins/qt4projectmanager/qt-maemo/maemodirectdeviceuploadstep.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b7a1b1c3053885375a02c64ad5fc27001a80ff8f
--- /dev/null
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodirectdeviceuploadstep.cpp
@@ -0,0 +1,217 @@
+#include "maemodirectdeviceuploadstep.h"
+
+#include "maemodeployable.h"
+#include "maemoglobal.h"
+#include "qt4maemodeployconfiguration.h"
+
+#include <utils/ssh/sftpchannel.h>
+#include <utils/ssh/sshremoteprocess.h>
+
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+
+#define ASSERT_BASE_STATE(state) ASSERT_STATE_GENERIC(BaseState, state, baseState())
+#define ASSERT_STATE(state) ASSERT_STATE_GENERIC(ExtendedState, state, m_extendedState)
+
+using namespace ProjectExplorer;
+using namespace Utils;
+
+namespace Qt4ProjectManager {
+namespace Internal {
+
+MaemoDirectDeviceUploadStep::MaemoDirectDeviceUploadStep(BuildStepList *parent)
+    : AbstractMaemoDeployStep(parent, Id)
+{
+    ctor();
+}
+
+MaemoDirectDeviceUploadStep::MaemoDirectDeviceUploadStep(BuildStepList *parent,
+    MaemoDirectDeviceUploadStep *other)
+    : AbstractMaemoDeployStep(parent, other)
+{
+    ctor();
+}
+
+MaemoDirectDeviceUploadStep::~MaemoDirectDeviceUploadStep() {}
+
+
+void MaemoDirectDeviceUploadStep::ctor()
+{
+    setDefaultDisplayName(DisplayName);
+    m_extendedState = Inactive;
+}
+
+bool MaemoDirectDeviceUploadStep::isDeploymentPossibleInternal(QString &whyNot) const
+{
+    Q_UNUSED(whyNot);
+    return true;
+}
+
+bool MaemoDirectDeviceUploadStep::isDeploymentNeeded(const QString &hostName) const
+{
+    m_filesToUpload.clear();
+    const QSharedPointer<MaemoDeployables> deployables
+        = maemoDeployConfig()->deployables();
+    const int deployableCount = deployables->deployableCount();
+    for (int i = 0; i < deployableCount; ++i)
+        checkDeploymentNeeded(hostName, deployables->deployableAt(i));
+    return !m_filesToUpload.isEmpty();
+}
+
+void MaemoDirectDeviceUploadStep::checkDeploymentNeeded(const QString &hostName,
+    const MaemoDeployable &deployable) const
+{
+    QFileInfo fileInfo(deployable.localFilePath);
+    if (fileInfo.isDir()) {
+        const QStringList files = QDir(deployable.localFilePath)
+            .entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
+        if (files.isEmpty() && currentlyNeedsDeployment(hostName, deployable))
+            m_filesToUpload << deployable;
+        foreach (const QString &fileName, files) {
+            const QString localFilePath = deployable.localFilePath
+                + QLatin1Char('/') + fileName;
+            const QString remoteDir = deployable.remoteDir + QLatin1Char('/')
+                + fileInfo.fileName();
+            checkDeploymentNeeded(hostName,
+                MaemoDeployable(localFilePath, remoteDir));
+        }
+    } else if (currentlyNeedsDeployment(hostName, deployable)) {
+        m_filesToUpload << deployable;
+    }
+}
+
+
+void MaemoDirectDeviceUploadStep::startInternal()
+{
+    Q_ASSERT(m_extendedState == Inactive);
+
+    m_uploader = connection()->createSftpChannel();
+    connect(m_uploader.data(), SIGNAL(initialized()),
+        SLOT(handleSftpInitialized()));
+    connect(m_uploader.data(), SIGNAL(initializationFailed(QString)),
+        SLOT(handleSftpInitializationFailed(QString)));
+    m_uploader->initialize();
+    m_extendedState = InitializingSftp;
+}
+
+void MaemoDirectDeviceUploadStep::handleSftpInitializationFailed(const QString &errorMessage)
+{
+    ASSERT_STATE(QList<ExtendedState>() << Inactive << InitializingSftp);
+
+    if (m_extendedState == InitializingSftp) {
+        raiseError(tr("SFTP initialization failed: %1").arg(errorMessage));
+        setFinished();
+    }
+}
+
+void MaemoDirectDeviceUploadStep::handleSftpInitialized()
+{
+    ASSERT_STATE(QList<ExtendedState>() << Inactive << InitializingSftp);
+    if (m_extendedState == InitializingSftp) {
+        Q_ASSERT(!m_filesToUpload.isEmpty());
+        connect(m_uploader.data(), SIGNAL(finished(Utils::SftpJobId, QString)),
+            SLOT(handleUploadFinished(Utils::SftpJobId,QString)));
+        uploadNextFile();
+    }
+}
+
+void MaemoDirectDeviceUploadStep::uploadNextFile()
+{
+    if (m_filesToUpload.isEmpty()) {
+        writeOutput(tr("All files successfully deployed."));
+        setFinished();
+        return;
+    }
+
+    const MaemoDeployable &d = m_filesToUpload.first();
+    QString dirToCreate = d.remoteDir;
+    QFileInfo fi(d.localFilePath);
+    if (fi.isDir())
+        dirToCreate += QLatin1Char('/') + fi.fileName();
+    const QByteArray command = "mkdir -p " + dirToCreate.toUtf8();
+    m_mkdirProc = connection()->createRemoteProcess(command);
+    connect(m_mkdirProc.data(), SIGNAL(closed(int)),
+        SLOT(handleMkdirFinished(int)));
+    // TODO: Connect stderr.
+    writeOutput(tr("Uploading file '%1'...")
+        .arg(QDir::toNativeSeparators(d.localFilePath)));
+    m_mkdirProc->start();
+    m_extendedState = Uploading;
+}
+
+void MaemoDirectDeviceUploadStep::handleMkdirFinished(int exitStatus)
+{
+    ASSERT_STATE(QList<ExtendedState>() << Inactive << Uploading);
+    if (m_extendedState == Inactive)
+        return;
+
+    const MaemoDeployable &d = m_filesToUpload.first();
+    QFileInfo fi(d.localFilePath);
+    const QString nativePath = QDir::toNativeSeparators(d.localFilePath);
+    if (exitStatus != SshRemoteProcess::ExitedNormally
+        || m_mkdirProc->exitCode() != 0) {
+        raiseError(tr("Failed to upload file '%1'.").arg(nativePath));
+        setFinished();
+    } else if (fi.isDir()) {
+        setDeployed(deviceConfig()->sshParameters().host, d);
+        m_filesToUpload.removeFirst();
+        uploadNextFile();
+    } else {
+        const SftpJobId job = m_uploader->uploadFile(d.localFilePath,
+            d.remoteDir + QLatin1Char('/')  + fi.fileName(),
+            SftpOverwriteExisting);
+        if (job == SftpInvalidJob) {
+            raiseError(tr("Failed to upload file '%1': "
+                "Could not open for reading.").arg(nativePath));
+            setFinished();
+        }
+    }
+}
+
+void MaemoDirectDeviceUploadStep::handleUploadFinished(Utils::SftpJobId jobId,
+    const QString &errorMsg)
+{
+    Q_UNUSED(jobId);
+
+    ASSERT_STATE(QList<ExtendedState>() << Inactive << Uploading);
+    if (m_extendedState == Inactive)
+        return;
+
+    const MaemoDeployable d = m_filesToUpload.takeFirst();
+    if (!errorMsg.isEmpty()) {
+        raiseError(tr("Upload of file '%1' failed: %2")
+            .arg(QDir::toNativeSeparators(d.localFilePath), errorMsg));
+        setFinished();
+    } else {
+        setDeployed(connection()->connectionParameters().host, d);
+        uploadNextFile();
+    }
+}
+
+void MaemoDirectDeviceUploadStep::stopInternal()
+{
+    ASSERT_BASE_STATE(StopRequested);
+    ASSERT_STATE(QList<ExtendedState>() << InitializingSftp << Uploading);
+
+    setFinished();
+}
+
+void MaemoDirectDeviceUploadStep::setFinished()
+{
+    m_extendedState = Inactive;
+    if (m_mkdirProc) {
+        disconnect(m_mkdirProc.data(), 0, this, 0);
+    }
+    if (m_uploader) {
+        disconnect(m_uploader.data(), 0, this, 0);
+        m_uploader->closeChannel();
+    }
+    setDeploymentFinished();
+}
+
+const QString MaemoDirectDeviceUploadStep::Id("MaemoDirectDeviceUploadStep");
+const QString MaemoDirectDeviceUploadStep::DisplayName
+    = tr("Upload files via SFTP");
+
+} // namespace Internal
+} // namespace Qt4ProjectManager
diff --git a/src/plugins/qt4projectmanager/qt-maemo/maemodirectdeviceuploadstep.h b/src/plugins/qt4projectmanager/qt-maemo/maemodirectdeviceuploadstep.h
new file mode 100644
index 0000000000000000000000000000000000000000..57704580c5f2b38dad4b623b3cd3384dabd5981e
--- /dev/null
+++ b/src/plugins/qt4projectmanager/qt-maemo/maemodirectdeviceuploadstep.h
@@ -0,0 +1,62 @@
+#ifndef MAEMODIRECTDEVICEUPLOADSTEP_H
+#define MAEMODIRECTDEVICEUPLOADSTEP_H
+
+#include "abstractmaemodeploystep.h"
+
+#include <utils/ssh/sftpdefs.h>
+
+#include <QtCore/QList>
+#include <QtCore/QSharedPointer>
+
+namespace Utils {
+class SshRemoteProcess;
+class SftpChannel;
+}
+
+namespace Qt4ProjectManager {
+namespace Internal {
+class MaemoDeployable;
+
+class MaemoDirectDeviceUploadStep : public AbstractMaemoDeployStep
+{
+    Q_OBJECT
+public:
+    MaemoDirectDeviceUploadStep(ProjectExplorer::BuildStepList *bc);
+    MaemoDirectDeviceUploadStep(ProjectExplorer::BuildStepList *bc,
+        MaemoDirectDeviceUploadStep *other);
+    ~MaemoDirectDeviceUploadStep();
+
+    static const QString Id;
+    static const QString DisplayName;
+
+private slots:
+    void handleSftpInitialized();
+    void handleSftpInitializationFailed(const QString &errorMessage);
+    void handleUploadFinished(Utils::SftpJobId jobId, const QString &errorMsg);
+    void handleMkdirFinished(int exitStatus);
+
+private:
+    enum ExtendedState { Inactive, InitializingSftp, Uploading };
+
+    virtual bool isDeploymentPossibleInternal(QString &whynot) const;
+    virtual bool isDeploymentNeeded(const QString &hostName) const;
+    virtual void startInternal();
+    virtual void stopInternal();
+    virtual const AbstractMaemoPackageCreationStep *packagingStep() const { return 0; }
+
+    void ctor();
+    void setFinished();
+    void checkDeploymentNeeded(const QString &hostName,
+        const MaemoDeployable &deployable) const;
+    void uploadNextFile();
+
+    QSharedPointer<Utils::SftpChannel> m_uploader;
+    QSharedPointer<Utils::SshRemoteProcess> m_mkdirProc;
+    mutable QList<MaemoDeployable> m_filesToUpload;
+    ExtendedState m_extendedState;
+};
+
+} // namespace Internal
+} // namespace Qt4ProjectManager
+
+#endif // MAEMODIRECTDEVICEUPLOADSTEP_H
diff --git a/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri b/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri
index 0c43ec8ec0bc93643022471eff17558744170a8e..d42d24ad40e9ceab5bab4a70aba32a09d9aa80a7 100644
--- a/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri
+++ b/src/plugins/qt4projectmanager/qt-maemo/qt-maemo.pri
@@ -58,7 +58,8 @@ HEADERS += \
     $$PWD/maemoremotecopyfacility.h \
     $$PWD/abstractmaemodeploystep.h \
     $$PWD/maemodeploybymountstep.h \
-    $$PWD/maemouploadandinstalldeploystep.h
+    $$PWD/maemouploadandinstalldeploystep.h \
+    $$PWD/maemodirectdeviceuploadstep.h
 
 SOURCES += \
     $$PWD/maemoconfigtestdialog.cpp \
@@ -117,7 +118,8 @@ SOURCES += \
     $$PWD/maemoremotecopyfacility.cpp \
     $$PWD/abstractmaemodeploystep.cpp \
     $$PWD/maemodeploybymountstep.cpp \
-    $$PWD/maemouploadandinstalldeploystep.cpp
+    $$PWD/maemouploadandinstalldeploystep.cpp \
+    $$PWD/maemodirectdeviceuploadstep.cpp
 
 FORMS += \
     $$PWD/maemoconfigtestdialog.ui \