diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py
index 246610215ed..5d4a9592de9 100644
--- a/tools/patman/cseries.py
+++ b/tools/patman/cseries.py
@@ -659,7 +659,7 @@ class Cseries(cser_helper.CseriesHelper):
                 tout.detail(f'Name match: ID {proj_id}')
         if not proj_id:
             raise ValueError(f"Unknown project name '{name}'")
-        self.db.settings_update(name, proj_id, link_name)
+        self.db.patchwork_update(name, proj_id, link_name)
         self.commit()
         if not quiet:
             tout.notice(f"Project '{name}' patchwork-ID {proj_id} "
@@ -674,7 +674,7 @@ class Cseries(cser_helper.CseriesHelper):
                 proj_id (int): Patchworks project ID for this project
                 link_name (str): Patchwork's link-name for the project
         """
-        return self.db.settings_get()
+        return self.db.patchwork_get()
 
     def remove(self, name, dry_run=False):
         """Remove a series from the database
diff --git a/tools/patman/database.py b/tools/patman/database.py
index 2bd225e4deb..22f21ce715f 100644
--- a/tools/patman/database.py
+++ b/tools/patman/database.py
@@ -165,12 +165,12 @@ class Database:  # pylint:disable=R0904
         self.cur.execute('ALTER TABLE ser_ver ADD COLUMN archive_tag')
 
     def _migrate_to_v5(self):
-        """Add upstream support to series, settings and upstream tables
+        """Add upstream support to series, patchwork and upstream tables
 
         - Add upstream column to series table
-        - Recreate settings table without UNIQUE constraint on name, adding
-          an upstream column (since the same project can have multiple
-          remotes)
+        - Rename and recreate patchwork table (formerly 'settings') without
+          UNIQUE constraint on name, adding an upstream column (since the
+          same project can have multiple remotes)
         - Add patchwork_url, identity, series_to, no_maintainers and
           no_tags columns to upstream table
         - Add desc column to ser_ver table
@@ -178,17 +178,17 @@ class Database:  # pylint:disable=R0904
         self.cur.execute('ALTER TABLE series ADD COLUMN upstream')
 
         self.cur.execute(
-            'CREATE TABLE settings_new '
+            'CREATE TABLE patchwork_new '
             '(name, proj_id INT, link_name, upstream)')
         self.cur.execute(
-            'INSERT INTO settings_new SELECT name, proj_id, link_name, NULL '
+            'INSERT INTO patchwork_new SELECT name, proj_id, link_name, NULL '
             'FROM settings')
         self.cur.execute('DROP TABLE settings')
-        self.cur.execute('ALTER TABLE settings_new RENAME TO settings')
+        self.cur.execute('ALTER TABLE patchwork_new RENAME TO patchwork')
         default_ups = self.upstream_get_default()
         if default_ups:
             self.cur.execute(
-                'UPDATE settings SET upstream = ?', (default_ups,))
+                'UPDATE patchwork SET upstream = ?', (default_ups,))
 
         self.cur.execute('ALTER TABLE upstream ADD COLUMN patchwork_url')
         self.cur.execute('ALTER TABLE upstream ADD COLUMN identity')
@@ -867,32 +867,43 @@ class Database:  # pylint:disable=R0904
             udict[name] = url, is_default
         return udict
 
-    # settings functions
+    # patchwork functions
 
-    def settings_update(self, name, proj_id, link_name):
-        """Set the patchwork settings of the project
+    def patchwork_update(self, name, proj_id, link_name, ups=None):
+        """Set the patchwork project details for an upstream
 
         Args:
             name (str): Name of the project to use in patchwork
             proj_id (int): Project ID for the project
             link_name (str): Link name for the project
+            ups (str or None): Upstream name to associate with, or None
         """
-        self.execute('DELETE FROM settings')
         self.execute(
-                'INSERT INTO settings (name, proj_id, link_name) '
-                'VALUES (?, ?, ?)', (name, proj_id, link_name))
+            'DELETE FROM patchwork WHERE upstream IS ?', (ups,))
+        self.execute(
+            'INSERT INTO patchwork (name, proj_id, link_name, upstream) '
+            'VALUES (?, ?, ?, ?)', (name, proj_id, link_name, ups))
+
+    def patchwork_get(self, ups=None):
+        """Get the patchwork project details for an upstream
 
-    def settings_get(self):
-        """Get the patchwork settings of the project
+        Args:
+            ups (str or None): Upstream name to look up, or None for any
 
         Returns:
-            tuple or None if there are no settings:
+            tuple or None if there is no project set:
                 name (str): Project name, e.g. 'U-Boot'
                 proj_id (int): Patchworks project ID for this project
                 link_name (str): Patchwork's link-name for the project
         """
-        res = self.execute("SELECT name, proj_id, link_name FROM settings")
+        if ups is not None:
+            res = self.execute(
+                'SELECT name, proj_id, link_name FROM patchwork '
+                'WHERE upstream = ?', (ups,))
+        else:
+            res = self.execute(
+                'SELECT name, proj_id, link_name FROM patchwork')
         recs = res.fetchall()
-        if len(recs) != 1:
+        if not recs:
             return None
         return recs[0]
diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py
index c6b90d5223d..0922c3e57f5 100644
--- a/tools/patman/test_cseries.py
+++ b/tools/patman/test_cseries.py
@@ -2502,6 +2502,66 @@ Tested-by: Mary Smith <msmith@wibble.com>   # yak
             f"Project 'U-Boot' patchwork-ID {self.PROJ_ID} link-name 'uboot'",
             out.getvalue().strip())
 
+    def test_patchwork_upstream(self):
+        """Test patchwork project with upstream association"""
+        cser = self.get_cser()
+
+        # Add two upstreams
+        cser.db.upstream_add('us', 'https://us.example.com')
+        cser.db.upstream_add('ci', 'https://ci.example.com')
+        cser.db.commit()
+
+        # Set project for a specific upstream
+        cser.db.patchwork_update('U-Boot', 6, 'uboot', 'us')
+        cser.db.commit()
+
+        # Look up by upstream
+        info = cser.db.patchwork_get('us')
+        self.assertEqual(('U-Boot', 6, 'uboot'), info)
+
+        # Different upstream has no project
+        self.assertIsNone(cser.db.patchwork_get('ci'))
+
+        # No upstream arg returns any match
+        info = cser.db.patchwork_get()
+        self.assertEqual(('U-Boot', 6, 'uboot'), info)
+
+        # Set a different project for ci
+        cser.db.patchwork_update('Linux', 10, 'linux', 'ci')
+        cser.db.commit()
+
+        self.assertEqual(('Linux', 10, 'linux'), cser.db.patchwork_get('ci'))
+        self.assertEqual(('U-Boot', 6, 'uboot'), cser.db.patchwork_get('us'))
+
+    def test_migrate_patchwork_upstream(self):
+        """Test that migrating to v5 renames settings to patchwork"""
+        db = database.Database(f'{self.tmpdir}/.patman3.db')
+        with terminal.capture():
+            db.open_it()
+
+        # Create a v4 database with an upstream and a patchwork row
+        with terminal.capture():
+            db.migrate_to(4)
+        db.execute(
+            "INSERT INTO upstream (name, url, is_default) "
+            "VALUES ('us', 'https://us.example.com', 1)")
+        db.execute(
+            "INSERT INTO settings (name, proj_id, link_name) "
+            "VALUES ('U-Boot', 6, 'uboot')")
+        db.commit()
+
+        # Migrate to v5
+        with terminal.capture():
+            db.migrate_to(5)
+
+        # The existing row should now be in 'patchwork' with the default upstream
+        res = db.execute(
+            'SELECT name, proj_id, link_name, upstream FROM patchwork')
+        recs = res.fetchall()
+        self.assertEqual(1, len(recs))
+        self.assertEqual(('U-Boot', 6, 'uboot', 'us'), recs[0])
+        db.close()
+
     def check_series_list_patches(self):
         """Test listing the patches for a series"""
         cser = self.get_cser()
